Skip to content

Commit 5b9b536

Browse files
authored
feat(artifactory): allow to re run actions (#5913)
1 parent 8dc52d6 commit 5b9b536

File tree

2 files changed

+163
-50
lines changed
  • contrib/integrations/artifactory

2 files changed

+163
-50
lines changed

contrib/integrations/artifactory/plugin-artifactory-build-info/main.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package main
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
7+
"net/http"
68
"net/url"
79
"os"
810
"path/filepath"
@@ -13,6 +15,7 @@ import (
1315
"github.com/golang/protobuf/ptypes/empty"
1416
"github.com/jfrog/jfrog-client-go/artifactory"
1517
"github.com/jfrog/jfrog-client-go/artifactory/buildinfo"
18+
"github.com/jfrog/jfrog-client-go/artifactory/services/utils"
1619
"github.com/jfrog/jfrog-client-go/utils/log"
1720

1821
"github.com/ovh/cds/contrib/grpcplugins"
@@ -81,6 +84,11 @@ func (e *artifactoryBuildInfoPlugin) Run(_ context.Context, opts *integrationplu
8184

8285
buildInfoName := fmt.Sprintf("%s/%s/%s", buildInfo, projectKey, workflowName)
8386

87+
// Check existing build info
88+
if err := e.deleteExistingBuild(artiClient, artifactoryProjectKey, buildInfoName, version); err != nil {
89+
return fail("unable to clean existing build: %v", err)
90+
}
91+
8492
nodeRunURL := opts.GetOptions()["cds.ui.pipeline.run"]
8593
runURL := nodeRunURL[0:strings.Index(nodeRunURL, "/node/")]
8694

@@ -130,6 +138,34 @@ func (e *artifactoryBuildInfoPlugin) Run(_ context.Context, opts *integrationplu
130138
}, nil
131139
}
132140

141+
type DeleteBuildRequest struct {
142+
Project string `json:"project"`
143+
BuildName string `json:"buildName"`
144+
BuildNumbers []string `json:"buildNumbers"`
145+
DeleteArtifacts bool `json:"deleteArtifacts"`
146+
DeleteAll bool `json:"deleteAll"`
147+
}
148+
149+
func (e *artifactoryBuildInfoPlugin) deleteExistingBuild(client artifactory.ArtifactoryServicesManager, artifactoryProjectKey string, buildName string, buildVersion string) error {
150+
httpDetails := client.GetConfig().GetServiceDetails().CreateHttpClientDetails()
151+
utils.SetContentType("application/json", &httpDetails.Headers)
152+
request := DeleteBuildRequest{
153+
Project: artifactoryProjectKey,
154+
BuildName: buildName,
155+
BuildNumbers: []string{buildVersion},
156+
}
157+
bts, _ := json.Marshal(request)
158+
deleteBuildURL := fmt.Sprintf("%sapi/build/delete", client.GetConfig().GetServiceDetails().GetUrl())
159+
re, body, err := client.Client().SendPost(deleteBuildURL, bts, &httpDetails)
160+
if err != nil {
161+
return err
162+
}
163+
if re.StatusCode == http.StatusNotFound || re.StatusCode < 400 {
164+
return nil
165+
}
166+
return fmt.Errorf("unable to delete build: %s", string(body))
167+
}
168+
133169
func (e *artifactoryBuildInfoPlugin) computeBuildInfoModules(client artifactory.ArtifactoryServicesManager, execContext executionContext) ([]buildinfo.Module, error) {
134170
modules := make([]buildinfo.Module, 0)
135171
runResults, err := grpcplugins.GetRunResults(e.HTTPPort)

contrib/integrations/artifactory/plugin-artifactory-release/main.go

Lines changed: 127 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ package main
33
import (
44
"context"
55
"fmt"
6-
"path/filepath"
7-
86
"os"
7+
"path/filepath"
98
"regexp"
109
"strings"
1110

@@ -44,6 +43,11 @@ Build the present binaries and import in CDS:
4443
$ cdsctl admin plugins binary-add artifactory-release-plugin artifactory-release-plugin-bin.yml <path-to-binary-file>
4544
*/
4645

46+
type promotedArtifact struct {
47+
Pattern string
48+
Target string
49+
}
50+
4751
type artifactoryReleasePlugin struct {
4852
integrationplugin.Common
4953
}
@@ -102,19 +106,14 @@ func (e *artifactoryReleasePlugin) Run(_ context.Context, opts *integrationplugi
102106

103107
artSplitted := strings.Split(artifactList, ",")
104108
artRegs := make([]*regexp.Regexp, 0, len(artSplitted))
105-
for _, art := range artSplitted {
106-
r, err := regexp.Compile(art)
109+
for _, arti := range artSplitted {
110+
r, err := regexp.Compile(arti)
107111
if err != nil {
108112
return fail("unable compile regexp in artifact list: %v", err)
109113
}
110114
artRegs = append(artRegs, r)
111115
}
112116

113-
type promotedArtifact struct {
114-
Pattern string
115-
Target string
116-
}
117-
118117
artifactPromoted := make([]promotedArtifact, 0)
119118
patternUsed := make(map[string]struct{})
120119
for _, r := range runResult {
@@ -169,37 +168,9 @@ func (e *artifactoryReleasePlugin) Run(_ context.Context, opts *integrationplugi
169168
}
170169

171170
// Release bundle
172-
buildInfoName := fmt.Sprintf("%s/%s/%s", buildInfo, projectKey, workflowName)
173-
174-
params := services.NewCreateReleaseBundleParams(strings.Replace(buildInfoName, "/", "-", -1), version)
175-
params.ReleaseNotes = releaseNote
176-
params.ReleaseNotesSyntax = "plain_text"
177-
178-
paramsBuild := fmt.Sprintf("%s/%s", strings.Replace(buildInfoName, "/", "\\/", -1), version)
179-
if artifactList == "" {
180-
params.SpecFiles = []*utils.ArtifactoryCommonParams{
181-
{
182-
Recursive: true,
183-
Build: paramsBuild,
184-
},
185-
}
186-
} else {
187-
params.SpecFiles = make([]*utils.ArtifactoryCommonParams, 0, len(artifactPromoted))
188-
for _, art := range artifactPromoted {
189-
query := &utils.ArtifactoryCommonParams{
190-
Recursive: true,
191-
Build: paramsBuild,
192-
Pattern: art.Pattern,
193-
Target: art.Target,
194-
}
195-
params.SpecFiles = append(params.SpecFiles, query)
196-
}
197-
}
198-
params.SignImmediately = true
199-
fmt.Printf("Creating release %s %s\n", params.Name, params.Version)
200-
201-
if _, err := distriClient.CreateReleaseBundle(params); err != nil {
202-
return fail("unable to create release bundle: %v", err)
171+
releaseName, releaseVersion, err := e.createReleaseBundle(distriClient, projectKey, workflowName, version, buildInfo, artifactList, releaseNote, artifactPromoted, artifactoryURL, releaseToken)
172+
if err != nil {
173+
return fail(err.Error())
203174
}
204175

205176
fmt.Printf("Listing Edge nodes to distribute the release \n")
@@ -209,8 +180,8 @@ func (e *artifactoryReleasePlugin) Run(_ context.Context, opts *integrationplugi
209180
}
210181
edges = e.removeNonEdge(edges)
211182

212-
fmt.Printf("Distribute Release %s %s\n", params.Name, params.Version)
213-
distributionParams := services.NewDistributeReleaseBundleParams(params.Name, params.Version)
183+
fmt.Printf("Distribute Release %s %s\n", releaseName, releaseVersion)
184+
distributionParams := services.NewDistributeReleaseBundleParams(releaseName, releaseVersion)
214185
distributionParams.DistributionRules = make([]*distriUtils.DistributionCommonParams, 0, len(edges))
215186
for _, e := range edges {
216187
distributionParams.DistributionRules = append(distributionParams.DistributionRules, &distriUtils.DistributionCommonParams{
@@ -219,15 +190,60 @@ func (e *artifactoryReleasePlugin) Run(_ context.Context, opts *integrationplugi
219190
CountryCodes: []string{e.City.CountryCode},
220191
})
221192
}
222-
if err := distriClient.DistributeReleaseBundle(distributionParams); err != nil {
223-
return fail("unable to distribution version: %v", err)
193+
if err := distriClient.DistributeReleaseBundleSync(distributionParams, 10); err != nil {
194+
return fail("unable to distribute version: %v", err)
224195
}
225196

226197
return &integrationplugin.RunResult{
227198
Status: sdk.StatusSuccess,
228199
}, nil
229200
}
230201

202+
func (e *artifactoryReleasePlugin) createReleaseBundle(distriClient *distribution.DistributionServicesManager, projectKey, workflowName, version, buildInfo string, artifactList, releaseNote string, artifactPromoted []promotedArtifact, artifactoryURL, releaseToken string) (string, string, error) {
203+
buildInfoName := fmt.Sprintf("%s/%s/%s", buildInfo, projectKey, workflowName)
204+
205+
params := services.NewCreateReleaseBundleParams(strings.Replace(buildInfoName, "/", "-", -1), version)
206+
207+
exist, err := e.checkReleaseBundleExist(distriClient, artifactoryURL, releaseToken, params.Name, params.Version)
208+
if err != nil {
209+
return "", "", err
210+
}
211+
if !exist {
212+
params.ReleaseNotes = releaseNote
213+
params.ReleaseNotesSyntax = "plain_text"
214+
215+
paramsBuild := fmt.Sprintf("%s/%s", strings.Replace(buildInfoName, "/", "\\/", -1), version)
216+
if artifactList == "" {
217+
params.SpecFiles = []*utils.ArtifactoryCommonParams{
218+
{
219+
Recursive: true,
220+
Build: paramsBuild,
221+
},
222+
}
223+
} else {
224+
params.SpecFiles = make([]*utils.ArtifactoryCommonParams, 0, len(artifactPromoted))
225+
for _, arti := range artifactPromoted {
226+
query := &utils.ArtifactoryCommonParams{
227+
Recursive: true,
228+
Build: paramsBuild,
229+
Pattern: arti.Pattern,
230+
Target: arti.Target,
231+
}
232+
params.SpecFiles = append(params.SpecFiles, query)
233+
}
234+
}
235+
params.SignImmediately = true
236+
fmt.Printf("Creating release %s %s\n", params.Name, params.Version)
237+
238+
if _, err := distriClient.CreateReleaseBundle(params); err != nil {
239+
return "", "", fmt.Errorf("unable to create release bundle %s/%s: %v", params.Name, params.Version, err)
240+
}
241+
} else {
242+
fmt.Printf("Release bundle %s/%s already exist", params.Name, params.Version)
243+
}
244+
return params.Name, params.Version, nil
245+
}
246+
231247
func (e *artifactoryReleasePlugin) listEdgeNodes(distriClient *distribution.DistributionServicesManager, url, token string) ([]EdgeNode, error) {
232248
// action=x distribute
233249
listEdgeNodePath := fmt.Sprintf("api/ui/distribution/edge_nodes?action=x")
@@ -274,14 +290,24 @@ func (e *artifactoryReleasePlugin) promoteFile(artiClient artifactory.Artifactor
274290
params.Pattern = fmt.Sprintf("%s/%s", srcRepo, data.Path)
275291
params.Target = fmt.Sprintf("%s/%s", targetRepo, data.Path)
276292
params.Flat = true
277-
fmt.Printf("Promoting file %s from %s to %s\n", data.Name, srcRepo, targetRepo)
278-
nbSuccess, nbFailed, err := artiClient.Move(params)
293+
294+
// Check if artifact already exist on destination
295+
exist, err := e.checkArtifactExists(artiClient, targetRepo, data.Path)
279296
if err != nil {
280297
return err
281298
}
282-
if nbFailed > 0 || nbSuccess == 0 {
283-
return fmt.Errorf("%s: copy failed with no reason", data.Name)
299+
if !exist {
300+
fmt.Printf("Promoting file %s from %s to %s\n", data.Name, srcRepo, targetRepo)
301+
nbSuccess, nbFailed, err := artiClient.Move(params)
302+
if err != nil {
303+
return err
304+
}
305+
if nbFailed > 0 || nbSuccess == 0 {
306+
return fmt.Errorf("%s: copy failed with no reason", data.Name)
307+
}
308+
return nil
284309
}
310+
fmt.Printf("%s has been already promoted", data.Name)
285311
return nil
286312
}
287313

@@ -290,8 +316,59 @@ func (e *artifactoryReleasePlugin) promoteDockerImage(artiClient artifactory.Art
290316
targetRepo := fmt.Sprintf("%s-%s", data.RepoName, highMaturity)
291317
params := artService.NewDockerPromoteParams(data.Path, sourceRepo, targetRepo)
292318
params.Copy = false
293-
fmt.Printf("Promoting docker image %s from %s to %s\n", data.Name, params.SourceRepo, params.TargetRepo)
294-
return artiClient.PromoteDocker(params)
319+
320+
// Check if artifact already exist on destination
321+
exist, err := e.checkArtifactExists(artiClient, targetRepo, data.Path)
322+
if err != nil {
323+
return err
324+
}
325+
if !exist {
326+
fmt.Printf("Promoting docker image %s from %s to %s\n", data.Name, params.SourceRepo, params.TargetRepo)
327+
return artiClient.PromoteDocker(params)
328+
}
329+
fmt.Printf("%s has been already promoted", data.Name)
330+
return nil
331+
}
332+
333+
func (e *artifactoryReleasePlugin) checkArtifactExists(artiClient artifactory.ArtifactoryServicesManager, repoName string, artiName string) (bool, error) {
334+
httpDetails := artiClient.GetConfig().GetServiceDetails().CreateHttpClientDetails()
335+
fileInfoURL := fmt.Sprintf("%sapi/storage/%s/%s", artiClient.GetConfig().GetServiceDetails().GetUrl(), repoName, artiName)
336+
re, body, _, err := artiClient.Client().SendGet(fileInfoURL, true, &httpDetails)
337+
if err != nil {
338+
return false, fmt.Errorf("unable to get file info %s/%s: %v", repoName, artiName, err)
339+
}
340+
if re.StatusCode == 404 {
341+
return false, nil
342+
}
343+
if re.StatusCode >= 400 {
344+
return false, fmt.Errorf("unable to call artifactory [HTTP: %d] %s %s", re.StatusCode, fileInfoURL, string(body))
345+
}
346+
return true, nil
347+
}
348+
349+
func (e *artifactoryReleasePlugin) checkReleaseBundleExist(client *distribution.DistributionServicesManager, url string, token string, name string, version string) (bool, error) {
350+
getReleasePath := fmt.Sprintf("api/v1/release_bundle/%s/%s?format=json", name, version)
351+
dtb := authdistrib.NewDistributionDetails()
352+
dtb.SetUrl(strings.Replace(url, "/artifactory/", "/distribution/", -1))
353+
dtb.SetAccessToken(token)
354+
355+
fakeService := services.NewCreateReleaseBundleService(client.Client())
356+
fakeService.DistDetails = dtb
357+
clientDetail := fakeService.DistDetails.CreateHttpClientDetails()
358+
getReleaseURL := fmt.Sprintf("%s%s", fakeService.DistDetails.GetUrl(), getReleasePath)
359+
utils.SetContentType("application/json", &clientDetail.Headers)
360+
361+
resp, body, _, err := client.Client().SendGet(getReleaseURL, true, &clientDetail)
362+
if err != nil {
363+
return false, fmt.Errorf("unable to get release bundle %s/%s from distribution: %v", name, version, err)
364+
}
365+
if resp.StatusCode == 404 {
366+
return false, nil
367+
}
368+
if resp.StatusCode >= 400 {
369+
return false, fmt.Errorf("http error %d: %s", resp.StatusCode, string(body))
370+
}
371+
return true, nil
295372
}
296373

297374
func main() {

0 commit comments

Comments
 (0)