Skip to content

Commit

Permalink
feat(artifactory): allow to re run actions (#5913)
Browse files Browse the repository at this point in the history
  • Loading branch information
sguiheux committed Sep 1, 2021
1 parent 8dc52d6 commit 5b9b536
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 50 deletions.
Expand Up @@ -2,7 +2,9 @@ package main

import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"path/filepath"
Expand All @@ -13,6 +15,7 @@ import (
"github.com/golang/protobuf/ptypes/empty"
"github.com/jfrog/jfrog-client-go/artifactory"
"github.com/jfrog/jfrog-client-go/artifactory/buildinfo"
"github.com/jfrog/jfrog-client-go/artifactory/services/utils"
"github.com/jfrog/jfrog-client-go/utils/log"

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

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

// Check existing build info
if err := e.deleteExistingBuild(artiClient, artifactoryProjectKey, buildInfoName, version); err != nil {
return fail("unable to clean existing build: %v", err)
}

nodeRunURL := opts.GetOptions()["cds.ui.pipeline.run"]
runURL := nodeRunURL[0:strings.Index(nodeRunURL, "/node/")]

Expand Down Expand Up @@ -130,6 +138,34 @@ func (e *artifactoryBuildInfoPlugin) Run(_ context.Context, opts *integrationplu
}, nil
}

type DeleteBuildRequest struct {
Project string `json:"project"`
BuildName string `json:"buildName"`
BuildNumbers []string `json:"buildNumbers"`
DeleteArtifacts bool `json:"deleteArtifacts"`
DeleteAll bool `json:"deleteAll"`
}

func (e *artifactoryBuildInfoPlugin) deleteExistingBuild(client artifactory.ArtifactoryServicesManager, artifactoryProjectKey string, buildName string, buildVersion string) error {
httpDetails := client.GetConfig().GetServiceDetails().CreateHttpClientDetails()
utils.SetContentType("application/json", &httpDetails.Headers)
request := DeleteBuildRequest{
Project: artifactoryProjectKey,
BuildName: buildName,
BuildNumbers: []string{buildVersion},
}
bts, _ := json.Marshal(request)
deleteBuildURL := fmt.Sprintf("%sapi/build/delete", client.GetConfig().GetServiceDetails().GetUrl())
re, body, err := client.Client().SendPost(deleteBuildURL, bts, &httpDetails)
if err != nil {
return err
}
if re.StatusCode == http.StatusNotFound || re.StatusCode < 400 {
return nil
}
return fmt.Errorf("unable to delete build: %s", string(body))
}

func (e *artifactoryBuildInfoPlugin) computeBuildInfoModules(client artifactory.ArtifactoryServicesManager, execContext executionContext) ([]buildinfo.Module, error) {
modules := make([]buildinfo.Module, 0)
runResults, err := grpcplugins.GetRunResults(e.HTTPPort)
Expand Down
177 changes: 127 additions & 50 deletions contrib/integrations/artifactory/plugin-artifactory-release/main.go
Expand Up @@ -3,9 +3,8 @@ package main
import (
"context"
"fmt"
"path/filepath"

"os"
"path/filepath"
"regexp"
"strings"

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

type promotedArtifact struct {
Pattern string
Target string
}

type artifactoryReleasePlugin struct {
integrationplugin.Common
}
Expand Down Expand Up @@ -102,19 +106,14 @@ func (e *artifactoryReleasePlugin) Run(_ context.Context, opts *integrationplugi

artSplitted := strings.Split(artifactList, ",")
artRegs := make([]*regexp.Regexp, 0, len(artSplitted))
for _, art := range artSplitted {
r, err := regexp.Compile(art)
for _, arti := range artSplitted {
r, err := regexp.Compile(arti)
if err != nil {
return fail("unable compile regexp in artifact list: %v", err)
}
artRegs = append(artRegs, r)
}

type promotedArtifact struct {
Pattern string
Target string
}

artifactPromoted := make([]promotedArtifact, 0)
patternUsed := make(map[string]struct{})
for _, r := range runResult {
Expand Down Expand Up @@ -169,37 +168,9 @@ func (e *artifactoryReleasePlugin) Run(_ context.Context, opts *integrationplugi
}

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

params := services.NewCreateReleaseBundleParams(strings.Replace(buildInfoName, "/", "-", -1), version)
params.ReleaseNotes = releaseNote
params.ReleaseNotesSyntax = "plain_text"

paramsBuild := fmt.Sprintf("%s/%s", strings.Replace(buildInfoName, "/", "\\/", -1), version)
if artifactList == "" {
params.SpecFiles = []*utils.ArtifactoryCommonParams{
{
Recursive: true,
Build: paramsBuild,
},
}
} else {
params.SpecFiles = make([]*utils.ArtifactoryCommonParams, 0, len(artifactPromoted))
for _, art := range artifactPromoted {
query := &utils.ArtifactoryCommonParams{
Recursive: true,
Build: paramsBuild,
Pattern: art.Pattern,
Target: art.Target,
}
params.SpecFiles = append(params.SpecFiles, query)
}
}
params.SignImmediately = true
fmt.Printf("Creating release %s %s\n", params.Name, params.Version)

if _, err := distriClient.CreateReleaseBundle(params); err != nil {
return fail("unable to create release bundle: %v", err)
releaseName, releaseVersion, err := e.createReleaseBundle(distriClient, projectKey, workflowName, version, buildInfo, artifactList, releaseNote, artifactPromoted, artifactoryURL, releaseToken)
if err != nil {
return fail(err.Error())
}

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

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

return &integrationplugin.RunResult{
Status: sdk.StatusSuccess,
}, nil
}

func (e *artifactoryReleasePlugin) createReleaseBundle(distriClient *distribution.DistributionServicesManager, projectKey, workflowName, version, buildInfo string, artifactList, releaseNote string, artifactPromoted []promotedArtifact, artifactoryURL, releaseToken string) (string, string, error) {
buildInfoName := fmt.Sprintf("%s/%s/%s", buildInfo, projectKey, workflowName)

params := services.NewCreateReleaseBundleParams(strings.Replace(buildInfoName, "/", "-", -1), version)

exist, err := e.checkReleaseBundleExist(distriClient, artifactoryURL, releaseToken, params.Name, params.Version)
if err != nil {
return "", "", err
}
if !exist {
params.ReleaseNotes = releaseNote
params.ReleaseNotesSyntax = "plain_text"

paramsBuild := fmt.Sprintf("%s/%s", strings.Replace(buildInfoName, "/", "\\/", -1), version)
if artifactList == "" {
params.SpecFiles = []*utils.ArtifactoryCommonParams{
{
Recursive: true,
Build: paramsBuild,
},
}
} else {
params.SpecFiles = make([]*utils.ArtifactoryCommonParams, 0, len(artifactPromoted))
for _, arti := range artifactPromoted {
query := &utils.ArtifactoryCommonParams{
Recursive: true,
Build: paramsBuild,
Pattern: arti.Pattern,
Target: arti.Target,
}
params.SpecFiles = append(params.SpecFiles, query)
}
}
params.SignImmediately = true
fmt.Printf("Creating release %s %s\n", params.Name, params.Version)

if _, err := distriClient.CreateReleaseBundle(params); err != nil {
return "", "", fmt.Errorf("unable to create release bundle %s/%s: %v", params.Name, params.Version, err)
}
} else {
fmt.Printf("Release bundle %s/%s already exist", params.Name, params.Version)
}
return params.Name, params.Version, nil
}

func (e *artifactoryReleasePlugin) listEdgeNodes(distriClient *distribution.DistributionServicesManager, url, token string) ([]EdgeNode, error) {
// action=x distribute
listEdgeNodePath := fmt.Sprintf("api/ui/distribution/edge_nodes?action=x")
Expand Down Expand Up @@ -274,14 +290,24 @@ func (e *artifactoryReleasePlugin) promoteFile(artiClient artifactory.Artifactor
params.Pattern = fmt.Sprintf("%s/%s", srcRepo, data.Path)
params.Target = fmt.Sprintf("%s/%s", targetRepo, data.Path)
params.Flat = true
fmt.Printf("Promoting file %s from %s to %s\n", data.Name, srcRepo, targetRepo)
nbSuccess, nbFailed, err := artiClient.Move(params)

// Check if artifact already exist on destination
exist, err := e.checkArtifactExists(artiClient, targetRepo, data.Path)
if err != nil {
return err
}
if nbFailed > 0 || nbSuccess == 0 {
return fmt.Errorf("%s: copy failed with no reason", data.Name)
if !exist {
fmt.Printf("Promoting file %s from %s to %s\n", data.Name, srcRepo, targetRepo)
nbSuccess, nbFailed, err := artiClient.Move(params)
if err != nil {
return err
}
if nbFailed > 0 || nbSuccess == 0 {
return fmt.Errorf("%s: copy failed with no reason", data.Name)
}
return nil
}
fmt.Printf("%s has been already promoted", data.Name)
return nil
}

Expand All @@ -290,8 +316,59 @@ func (e *artifactoryReleasePlugin) promoteDockerImage(artiClient artifactory.Art
targetRepo := fmt.Sprintf("%s-%s", data.RepoName, highMaturity)
params := artService.NewDockerPromoteParams(data.Path, sourceRepo, targetRepo)
params.Copy = false
fmt.Printf("Promoting docker image %s from %s to %s\n", data.Name, params.SourceRepo, params.TargetRepo)
return artiClient.PromoteDocker(params)

// Check if artifact already exist on destination
exist, err := e.checkArtifactExists(artiClient, targetRepo, data.Path)
if err != nil {
return err
}
if !exist {
fmt.Printf("Promoting docker image %s from %s to %s\n", data.Name, params.SourceRepo, params.TargetRepo)
return artiClient.PromoteDocker(params)
}
fmt.Printf("%s has been already promoted", data.Name)
return nil
}

func (e *artifactoryReleasePlugin) checkArtifactExists(artiClient artifactory.ArtifactoryServicesManager, repoName string, artiName string) (bool, error) {
httpDetails := artiClient.GetConfig().GetServiceDetails().CreateHttpClientDetails()
fileInfoURL := fmt.Sprintf("%sapi/storage/%s/%s", artiClient.GetConfig().GetServiceDetails().GetUrl(), repoName, artiName)
re, body, _, err := artiClient.Client().SendGet(fileInfoURL, true, &httpDetails)
if err != nil {
return false, fmt.Errorf("unable to get file info %s/%s: %v", repoName, artiName, err)
}
if re.StatusCode == 404 {
return false, nil
}
if re.StatusCode >= 400 {
return false, fmt.Errorf("unable to call artifactory [HTTP: %d] %s %s", re.StatusCode, fileInfoURL, string(body))
}
return true, nil
}

func (e *artifactoryReleasePlugin) checkReleaseBundleExist(client *distribution.DistributionServicesManager, url string, token string, name string, version string) (bool, error) {
getReleasePath := fmt.Sprintf("api/v1/release_bundle/%s/%s?format=json", name, version)
dtb := authdistrib.NewDistributionDetails()
dtb.SetUrl(strings.Replace(url, "/artifactory/", "/distribution/", -1))
dtb.SetAccessToken(token)

fakeService := services.NewCreateReleaseBundleService(client.Client())
fakeService.DistDetails = dtb
clientDetail := fakeService.DistDetails.CreateHttpClientDetails()
getReleaseURL := fmt.Sprintf("%s%s", fakeService.DistDetails.GetUrl(), getReleasePath)
utils.SetContentType("application/json", &clientDetail.Headers)

resp, body, _, err := client.Client().SendGet(getReleaseURL, true, &clientDetail)
if err != nil {
return false, fmt.Errorf("unable to get release bundle %s/%s from distribution: %v", name, version, err)
}
if resp.StatusCode == 404 {
return false, nil
}
if resp.StatusCode >= 400 {
return false, fmt.Errorf("http error %d: %s", resp.StatusCode, string(body))
}
return true, nil
}

func main() {
Expand Down

0 comments on commit 5b9b536

Please sign in to comment.