Skip to content

Commit a95f155

Browse files
authored
[artefacts] Enhance artefact management (#83)
<!-- Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved. SPDX-License-Identifier: Proprietary --> ### Description - Improved how artefacts are managed and downloaded ### Test Coverage <!-- Please put an `x` in the correct box e.g. `[x]` to indicate the testing coverage of this change. --> - [x] This change is covered by existing or additional automated tests. - [ ] Manual testing has been performed (and evidence provided) as automated testing was not feasible. - [ ] Additional tests are not required for this change (e.g. documentation update).
1 parent 0916426 commit a95f155

File tree

8 files changed

+444
-116
lines changed

8 files changed

+444
-116
lines changed

changes/20241029184949.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:sparkles: `[artefacts]` Enhance artefact management

utils/artefacts/artefacts.go

Lines changed: 192 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import (
1414
"path/filepath"
1515

1616
"github.com/ARM-software/embedded-development-services-client-utils/utils/api"
17+
paginationUtils "github.com/ARM-software/embedded-development-services-client-utils/utils/pagination"
1718
"github.com/ARM-software/embedded-development-services-client/client"
19+
"github.com/ARM-software/golang-utils/utils/collection/pagination"
1820
"github.com/ARM-software/golang-utils/utils/commonerrors"
1921
"github.com/ARM-software/golang-utils/utils/filesystem"
2022
"github.com/ARM-software/golang-utils/utils/hashing"
@@ -23,47 +25,68 @@ import (
2325
)
2426

2527
type (
26-
getArtefactManagerFunc = func(ctx context.Context, job, artefact string) (*client.ArtefactManagerItem, *http.Response, error)
27-
getArtefactContentFunc = func(ctx context.Context, job, artefactID string) (*os.File, *http.Response, error)
28+
// GetArtefactManagersFirstPageFunc defines the function which can retrieve the first page of artefact managers.
29+
GetArtefactManagersFirstPageFunc = func(ctx context.Context, job string) (*client.ArtefactManagerCollection, *http.Response, error)
30+
// FollowLinkToArtefactManagersPageFunc is a function able to follow a link to an artefact manager page.
31+
FollowLinkToArtefactManagersPageFunc = func(ctx context.Context, href string) (*client.ArtefactManagerCollection, *http.Response, error)
32+
// GetArtefactManagerFunc is a function which retrieves information about an artefact manager.
33+
GetArtefactManagerFunc = func(ctx context.Context, job, artefact string) (*client.ArtefactManagerItem, *http.Response, error)
34+
// GetArtefactContentFunc is a function able to return the content of any artefact managers.
35+
GetArtefactContentFunc = func(ctx context.Context, job, artefactID string) (*os.File, *http.Response, error)
2836
)
2937

3038
type ArtefactManager struct {
31-
getArtefactManagerFunc getArtefactManagerFunc
32-
getArtefactContentFunc getArtefactContentFunc
39+
getArtefactManagerFunc GetArtefactManagerFunc
40+
getArtefactContentFunc GetArtefactContentFunc
41+
getArtefactManagersFirstPageFunc GetArtefactManagersFirstPageFunc
42+
getArtefactManagersFollowLinkFunc FollowLinkToArtefactManagersPageFunc
3343
}
3444

35-
func NewArtefactManager(getArtefactManager getArtefactManagerFunc, getOutputArtefact getArtefactContentFunc) *ArtefactManager {
45+
// NewArtefactManager returns an artefact manager.
46+
func NewArtefactManager(getArtefactManagersFirstPage GetArtefactManagersFirstPageFunc, getArtefactsManagersPage FollowLinkToArtefactManagersPageFunc, getArtefactManager GetArtefactManagerFunc, getOutputArtefact GetArtefactContentFunc) IArtefactManager {
3647
return &ArtefactManager{
37-
getArtefactManagerFunc: getArtefactManager,
38-
getArtefactContentFunc: getOutputArtefact,
48+
getArtefactManagerFunc: getArtefactManager,
49+
getArtefactContentFunc: getOutputArtefact,
50+
getArtefactManagersFirstPageFunc: getArtefactManagersFirstPage,
51+
getArtefactManagersFollowLinkFunc: getArtefactsManagersPage,
3952
}
4053
}
4154

42-
func (m *ArtefactManager) DownloadJobArtefact(ctx context.Context, jobName string, outputDirectory string, artefactManagerItem client.HalLinkData) (err error) {
55+
func (m *ArtefactManager) DownloadJobArtefact(ctx context.Context, jobName string, outputDirectory string, artefactManager *client.ArtefactManagerItem) (err error) {
4356
err = parallelisation.DetermineContextError(ctx)
4457
if err != nil {
4558
return
4659
}
60+
if m.getArtefactManagerFunc == nil || m.getArtefactContentFunc == nil {
61+
err = fmt.Errorf("%w: function to retrieve an artefact manager was not properly defined", commonerrors.ErrUndefined)
62+
return
63+
}
4764

48-
artefactManagerName := artefactManagerItem.GetName()
49-
artefactManager, resp, apierr := m.getArtefactManagerFunc(ctx, jobName, artefactManagerName)
50-
defer func() {
51-
if resp != nil {
52-
_ = resp.Body.Close()
53-
}
54-
}()
55-
err = api.CheckAPICallSuccess(ctx, fmt.Sprintf("cannot fetch artefact's manager [%v]", artefactManager), resp, apierr)
65+
err = filesystem.MkDir(outputDirectory)
5666
if err != nil {
67+
err = fmt.Errorf("%w: failed creating the output directory [%v] for job artefact: %v", commonerrors.ErrUnexpected, outputDirectory, err.Error())
5768
return
5869
}
5970

60-
artefactFilenamePtr, ok := artefactManager.GetTitleOk()
61-
if !ok {
62-
err = fmt.Errorf("%w: could not fetch artefact's title from artefact's manager [%v]", commonerrors.ErrUndefined, artefactManagerName)
71+
fileHasher, err := filesystem.NewFileHash(hashing.HashSha256)
72+
if err != nil {
73+
return
74+
}
75+
if artefactManager == nil {
76+
err = fmt.Errorf("%w: missing artefact manager", commonerrors.ErrUndefined)
77+
return
78+
}
79+
80+
artefactManagerName := artefactManager.GetName()
81+
if artefactManagerName == "" {
82+
err = fmt.Errorf("%w: missing artefact name", commonerrors.ErrUndefined)
6383
return
6484
}
6585

66-
artefactFilename := *artefactFilenamePtr
86+
artefactFilename := artefactManagerName
87+
if artefactManager.HasTitle() {
88+
artefactFilename = artefactManager.GetTitle()
89+
}
6790
if unescapedName, err := url.PathUnescape(artefactFilename); err == nil {
6891
artefactFilename = unescapedName
6992
}
@@ -82,14 +105,7 @@ func (m *ArtefactManager) DownloadJobArtefact(ctx context.Context, jobName strin
82105
}
83106
expectedHash := *expectedHashPtr
84107

85-
artefactNamePtr, ok := artefactManager.GetNameOk()
86-
if !ok {
87-
err = fmt.Errorf("%w: could not fetch artefact's name from artefact's manager [%v]", commonerrors.ErrUndefined, artefactManagerName)
88-
return
89-
}
90-
artefactName := *artefactNamePtr
91-
92-
artefact, resp, apierr := m.getArtefactContentFunc(ctx, jobName, artefactName)
108+
artefact, resp, apierr := m.getArtefactContentFunc(ctx, jobName, artefactManagerName)
93109
defer func() {
94110
if resp != nil {
95111
_ = resp.Body.Close()
@@ -111,11 +127,6 @@ func (m *ArtefactManager) DownloadJobArtefact(ctx context.Context, jobName strin
111127
}
112128
defer func() { _ = destination.Close() }()
113129

114-
fileHasher, err := filesystem.NewFileHash(hashing.HashSha256)
115-
if err != nil {
116-
return
117-
}
118-
119130
actualSize, err := safeio.CopyDataWithContext(ctx, artefact, destination)
120131
if err != nil {
121132
err = fmt.Errorf("%w: failed to copy artefact [%v]: %v", commonerrors.ErrUnexpected, artefactFilename, err.Error())
@@ -148,4 +159,152 @@ func (m *ArtefactManager) DownloadJobArtefact(ctx context.Context, jobName strin
148159

149160
err = parallelisation.DetermineContextError(ctx)
150161
return
162+
163+
}
164+
165+
func (m *ArtefactManager) DownloadJobArtefactFromLink(ctx context.Context, jobName string, outputDirectory string, artefactManagerItem *client.HalLinkData) (err error) {
166+
err = parallelisation.DetermineContextError(ctx)
167+
if err != nil {
168+
return
169+
}
170+
if m.getArtefactManagerFunc == nil || m.getArtefactContentFunc == nil {
171+
err = fmt.Errorf("%w: function to retrieve an artefact manager was not properly defined", commonerrors.ErrUndefined)
172+
return
173+
}
174+
if artefactManagerItem == nil {
175+
err = fmt.Errorf("%w: missing artefact link", commonerrors.ErrUndefined)
176+
return
177+
}
178+
179+
artefactManagerName := artefactManagerItem.GetName()
180+
artefactManager, resp, apierr := m.getArtefactManagerFunc(ctx, jobName, artefactManagerName)
181+
defer func() {
182+
if resp != nil {
183+
_ = resp.Body.Close()
184+
}
185+
}()
186+
err = api.CheckAPICallSuccess(ctx, fmt.Sprintf("cannot fetch artefact's manager [%+v]", artefactManager), resp, apierr)
187+
if err != nil {
188+
return
189+
}
190+
if resp != nil {
191+
_ = resp.Body.Close()
192+
}
193+
err = m.DownloadJobArtefact(ctx, jobName, outputDirectory, artefactManager)
194+
return
195+
}
196+
197+
func (m *ArtefactManager) ListJobArtefacts(ctx context.Context, jobName string) (pagination.IPaginatorAndPageFetcher, error) {
198+
err := parallelisation.DetermineContextError(ctx)
199+
if err != nil {
200+
return nil, err
201+
}
202+
return pagination.NewStaticPagePaginator(ctx, func(context.Context) (pagination.IStaticPage, error) {
203+
return m.fetchJobArtefactsFirstPage(ctx, jobName)
204+
}, m.fetchJobArtefactsNextPage)
205+
}
206+
207+
func (m *ArtefactManager) fetchJobArtefactsFirstPage(ctx context.Context, jobName string) (page pagination.IStaticPage, err error) {
208+
if m.getArtefactManagersFirstPageFunc == nil {
209+
err = fmt.Errorf("%w: function to retrieve artefact managers was not properly defined", commonerrors.ErrUndefined)
210+
return
211+
}
212+
clientPage, resp, apierr := m.getArtefactManagersFirstPageFunc(ctx, jobName)
213+
if resp != nil {
214+
_ = resp.Body.Close()
215+
}
216+
err = api.CheckAPICallSuccess(ctx, fmt.Sprintf("could not list artefact managers for job [%v]", jobName), resp, apierr)
217+
if err == nil {
218+
page = paginationUtils.ToPage(clientPage)
219+
}
220+
return
221+
}
222+
223+
func (m *ArtefactManager) fetchJobArtefactsNextPage(ctx context.Context, currentPage pagination.IStaticPage) (nextPage pagination.IStaticPage, err error) {
224+
err = parallelisation.DetermineContextError(ctx)
225+
if err != nil {
226+
return
227+
}
228+
if currentPage == nil {
229+
return
230+
}
231+
if m.getArtefactManagersFollowLinkFunc == nil {
232+
err = fmt.Errorf("%w: function to retrieve artefact managers was not properly defined", commonerrors.ErrUndefined)
233+
return
234+
}
235+
page, ok := paginationUtils.ToClientPage(currentPage).(*client.BuildJobCollection)
236+
if !ok {
237+
err = fmt.Errorf("%w: returned build job page [%T] is not of the expected type [%v]", commonerrors.ErrUnexpected, currentPage, "*BuildJobCollection")
238+
return
239+
}
240+
links, has := page.GetLinksOk()
241+
if !has {
242+
err = fmt.Errorf("%w: returned page of build jobs has no links", commonerrors.ErrUnexpected)
243+
return
244+
}
245+
if !links.HasNext() {
246+
err = fmt.Errorf("%w: returned page of build job has no `next` link", commonerrors.ErrUnexpected)
247+
return
248+
}
249+
link := links.GetNext()
250+
clientPage, resp, apierr := m.getArtefactManagersFollowLinkFunc(ctx, link.GetHref())
251+
if resp != nil {
252+
_ = resp.Body.Close()
253+
}
254+
err = api.CheckAPICallSuccess(ctx, fmt.Sprintf("could not follow `next` link [%v]", link), resp, apierr)
255+
if err == nil {
256+
nextPage = paginationUtils.ToPage(clientPage)
257+
}
258+
return
259+
}
260+
261+
func (m *ArtefactManager) DownloadAllJobArtefacts(ctx context.Context, jobName string, outputDirectory string) (err error) {
262+
err = parallelisation.DetermineContextError(ctx)
263+
if err != nil {
264+
return
265+
}
266+
err = filesystem.MkDir(outputDirectory)
267+
if err != nil {
268+
err = fmt.Errorf("%w: failed creating the output directory [%v] for job artefacts: %v", commonerrors.ErrUnexpected, outputDirectory, err.Error())
269+
return
270+
}
271+
paginator, err := m.ListJobArtefacts(ctx, jobName)
272+
if err != nil {
273+
return
274+
}
275+
stop := paginator.Stop()
276+
defer stop()
277+
for {
278+
if !paginator.HasNext() {
279+
return
280+
}
281+
item, subErr := paginator.GetNext()
282+
if subErr != nil {
283+
err = fmt.Errorf("%w: failed getting information about job artefacts: %v", commonerrors.ErrUnexpected, subErr.Error())
284+
return
285+
}
286+
artefactLink, ok := item.(*client.HalLinkData)
287+
if ok {
288+
subErr = m.DownloadJobArtefactFromLink(ctx, jobName, outputDirectory, artefactLink)
289+
if subErr != nil {
290+
err = subErr
291+
return
292+
}
293+
294+
} else {
295+
artefactManager, ok := item.(*client.ArtefactManagerItem)
296+
if ok {
297+
subErr = m.DownloadJobArtefact(ctx, jobName, outputDirectory, artefactManager)
298+
if subErr != nil {
299+
err = subErr
300+
return
301+
}
302+
} else {
303+
err = fmt.Errorf("%w: the type of the response from service cannot be interpreted", commonerrors.ErrMarshalling)
304+
return
305+
}
306+
307+
}
308+
309+
}
151310
}

0 commit comments

Comments
 (0)