diff --git a/contrib/grpcplugins/action/docker-push/Makefile b/contrib/grpcplugins/action/docker-push/Makefile new file mode 100644 index 0000000000..9be16ad248 --- /dev/null +++ b/contrib/grpcplugins/action/docker-push/Makefile @@ -0,0 +1,18 @@ +PLUGIN_NAME = docker-push +TARGET_NAME = docker-push + +##### ^^^^^^ EDIT ABOVE ^^^^^^ ##### + +include ../../../../.build/core.mk +include ../../../../.build/go.mk +include ../../../../.build/plugin.mk + +build: mk_go_build_plugin ## build action plugin and prepare configuration for publish + +clean: mk_go_clean ## clean binary and tests results + +test: mk_go_test ## run unit tests + +publish: mk_v2_plugin_publish ## publish the plugin on CDS. This use your cdsctl default context and commands cdsctl admin plugins import / binary-add. + +package: mk_plugin_package ## prepare the tar.gz file, with all binaries / conf files diff --git a/contrib/grpcplugins/action/docker-push/docker-push.yml b/contrib/grpcplugins/action/docker-push/docker-push.yml new file mode 100644 index 0000000000..ce3acb4579 --- /dev/null +++ b/contrib/grpcplugins/action/docker-push/docker-push.yml @@ -0,0 +1,34 @@ +name: docker-push +type: action +author: "François SAMIN " +description: | + This pushes Docker image +inputs: + image: + type: string + description: Image name + required: true + tags: + type: string + description: |- + The tags to associate with the image on the registry. + + This parameter can be empty if you want to keep the same tag. + required: false + registry: + type: string + description: |- + Docker registry to push on. + + This parameter can be empty when an Artifactory integration is set up. + required: false + registryAuth: + type: string + description: |- + Docker base64url-encoded auth configuration. + + See docker authentication section for more details: https://docs.docker.com/engine/api/v1.41/#section/Authentication. + + This parameter can be empty when an Artifactory integration is set up. + required: false + diff --git a/contrib/grpcplugins/action/docker-push/main.go b/contrib/grpcplugins/action/docker-push/main.go new file mode 100644 index 0000000000..820e3bcf46 --- /dev/null +++ b/contrib/grpcplugins/action/docker-push/main.go @@ -0,0 +1,315 @@ +package main + +import ( + "context" + "encoding/base64" + "encoding/json" + "net/url" + "os" + "strings" + "time" + + "github.com/docker/cli/cli/streams" + "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/jsonmessage" + "github.com/docker/go-units" + "github.com/golang/protobuf/ptypes/empty" + "github.com/moby/moby/client" + "github.com/pkg/errors" + + "github.com/ovh/cds/contrib/grpcplugins" + "github.com/ovh/cds/engine/worker/pkg/workerruntime" + "github.com/ovh/cds/sdk" + "github.com/ovh/cds/sdk/grpcplugin/actionplugin" +) + +type dockerPushPlugin struct { + actionplugin.Common +} + +func main() { + actPlugin := dockerPushPlugin{} + if err := actionplugin.Start(context.Background(), &actPlugin); err != nil { + panic(err) + } +} + +func (actPlugin *dockerPushPlugin) Manifest(_ context.Context, _ *empty.Empty) (*actionplugin.ActionPluginManifest, error) { + return &actionplugin.ActionPluginManifest{ + Name: "docker-push", + Author: "François SAMIN ", + Description: "Push an image docker on a docker registry", + Version: sdk.VERSION, + }, nil +} + +// Run implements actionplugin.ActionPluginServer. +func (actPlugin *dockerPushPlugin) Run(ctx context.Context, q *actionplugin.ActionQuery) (*actionplugin.ActionResult, error) { + res := &actionplugin.ActionResult{ + Status: sdk.StatusSuccess, + } + + image := q.GetOptions()["image"] + tags := q.GetOptions()["tags"] + registry := q.GetOptions()["registry"] + auth := q.GetOptions()["registryAuth"] + + tags = strings.Replace(tags, " ", ",", -1) // If tags are separated by + tags = strings.Replace(tags, ";", ",", -1) // If tags are separated by + tagSlice := strings.Split(tags, ",") + + if err := actPlugin.perform(ctx, image, tagSlice, registry, auth); err != nil { + res.Status = sdk.StatusFail + res.Status = err.Error() + return res, err + } + + return res, nil +} + +type img struct { + repository string + tag string + imageID string + created string + size string +} + +func (actPlugin *dockerPushPlugin) perform(ctx context.Context, image string, tags []string, registry, registryAuth string) error { + if image == "" { + return sdk.Errorf("wrong usage: parameter should not be empty") + } + + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return sdk.Errorf("unable to get instanciate docker client: %v", err) + } + + imageSummaries, err := cli.ImageList(ctx, types.ImageListOptions{All: true}) + if err != nil { + return sdk.Errorf("unable to get docker image %q: %v", image, err) + } + + images := []img{} + for _, image := range imageSummaries { + repository := "" + tag := "" + if len(image.RepoTags) > 0 { + splitted := strings.Split(image.RepoTags[0], ":") + repository = splitted[0] + tag = splitted[1] + } else if len(image.RepoDigests) > 0 { + repository = strings.Split(image.RepoDigests[0], "@")[0] + } + duration := HumanDuration(image.Created) + size := HumanSize(image.Size) + images = append(images, img{repository: repository, tag: tag, imageID: image.ID[7:19], created: duration, size: size}) + } + + var imgFound *img + for i := range images { + grpcplugins.Logf("image %s:%s", images[i].repository, images[i].tag) + if images[i].repository+":"+images[i].tag == image { + imgFound = &images[i] + break + } + } + + if imgFound == nil { + return sdk.Errorf("image %q not found", image) + } + + if len(tags) == 0 { // If no tag is provided, keep the actual tag + tags = []string{imgFound.tag} + } + + for _, tag := range tags { + result, d, err := actPlugin.performImage(ctx, cli, image, imgFound, registry, registryAuth, strings.TrimSpace(tag)) + if err != nil { + grpcplugins.Error(err.Error()) + return err + } + grpcplugins.Logf("Image %s pushed in %.3fs", result.Name(), d.Seconds()) + } + + return nil +} + +func (actPlugin *dockerPushPlugin) performImage(ctx context.Context, cli *client.Client, source string, img *img, registry string, registryAuth string, tag string) (*sdk.V2WorkflowRunResult, time.Duration, error) { + var t0 = time.Now() + + // Create run result at status "pending" + var runResultRequest = workerruntime.V2RunResultRequest{ + RunResult: &sdk.V2WorkflowRunResult{ + IssuedAt: time.Now(), + Type: sdk.V2WorkflowRunResultTypeDocker, + Status: sdk.V2WorkflowRunResultStatusPending, + Detail: sdk.V2WorkflowRunResultDetail{ + Data: sdk.V2WorkflowRunResultDockerDetail{ + Name: source, + ID: img.imageID, + HumanSize: img.size, + HumanCreated: img.created, + }, + }, + }, + } + + response, err := grpcplugins.CreateRunResult(ctx, &actPlugin.Common, &runResultRequest) + if err != nil { + return nil, time.Since(t0), err + } + + result := response.RunResult + + var destination string + // Upload the file to an artifactory or the docker registry + switch { + case result.ArtifactManagerIntegrationName != nil: + integration, err := grpcplugins.GetIntegrationByName(ctx, &actPlugin.Common, *response.RunResult.ArtifactManagerIntegrationName) + if err != nil { + return nil, time.Since(t0), err + } + + repository := integration.Config[sdk.ArtifactoryConfigRepositoryPrefix].Value + "-docker" + rtURLRaw := integration.Config[sdk.ArtifactoryConfigURL].Value + if !strings.HasSuffix(rtURLRaw, "/") { + rtURLRaw = rtURLRaw + "/" + } + rtURL, err := url.Parse(rtURLRaw) + if err != nil { + return nil, time.Since(t0), err + } + + destination = repository + "." + rtURL.Host + "/" + img.repository + ":" + tag + + result.Detail.Data = sdk.V2WorkflowRunResultDockerDetail{ + Name: destination, + ID: img.imageID, + HumanSize: img.size, + HumanCreated: img.created, + } + + if tag != img.tag { // if the image already has the right tag, nothing to do + if err := cli.ImageTag(ctx, img.imageID, destination); err != nil { + return nil, time.Since(t0), errors.Errorf("unable to tag %q to %q: %v", source, destination, err) + } + } + + auth := types.AuthConfig{ + Username: integration.Config[sdk.ArtifactoryConfigTokenName].Value, + Password: integration.Config[sdk.ArtifactoryConfigToken].Value, + ServerAddress: repository + "." + rtURL.Host, + } + buf, _ := json.Marshal(auth) + registryAuth = base64.URLEncoding.EncodeToString(buf) + + output, err := cli.ImagePush(ctx, destination, types.ImagePushOptions{RegistryAuth: registryAuth}) + if err != nil { + return nil, time.Since(t0), errors.Errorf("unable to push %q: %v", destination, err) + } + + if err := jsonmessage.DisplayJSONMessagesToStream(output, streams.NewOut(os.Stdout), nil); err != nil { + return nil, time.Since(t0), errors.Errorf("unable to push %q: %v", destination, err) + } + + var rtConfig = grpcplugins.ArtifactoryConfig{ + URL: rtURL.String(), + Token: integration.Config[sdk.ArtifactoryConfigToken].Value, + } + + rtFolderPath := img.repository + "/" + tag + rtFolderPathInfo, err := grpcplugins.GetArtifactoryFolderInfo(ctx, &actPlugin.Common, rtConfig, repository, rtFolderPath) + if err != nil { + return nil, time.Since(t0), err + } + + var manifestFound bool + for _, child := range rtFolderPathInfo.Children { + if strings.HasSuffix(child.URI, "manifest.json") { // Can be manifest.json of list.manifest.json for multi-arch docker image + rtPathInfo, err := grpcplugins.GetArtifactoryFileInfo(ctx, &actPlugin.Common, rtConfig, repository, rtFolderPath+child.URI) + if err != nil { + return nil, time.Since(t0), err + } + manifestFound = true + result.ArtifactManagerMetadata = &sdk.V2WorkflowRunResultArtifactManagerMetadata{} + result.ArtifactManagerMetadata.Set("repository", repository) // This is the virtual repository + result.ArtifactManagerMetadata.Set("type", "docker") + result.ArtifactManagerMetadata.Set("maturity", integration.Config[sdk.ArtifactoryConfigPromotionLowMaturity].Value) + result.ArtifactManagerMetadata.Set("name", destination) + result.ArtifactManagerMetadata.Set("path", rtPathInfo.Path) + result.ArtifactManagerMetadata.Set("md5", rtPathInfo.Checksums.Md5) + result.ArtifactManagerMetadata.Set("sha1", rtPathInfo.Checksums.Sha1) + result.ArtifactManagerMetadata.Set("sha256", rtPathInfo.Checksums.Sha256) + result.ArtifactManagerMetadata.Set("uri", rtPathInfo.URI) + result.ArtifactManagerMetadata.Set("mimeType", rtPathInfo.MimeType) + result.ArtifactManagerMetadata.Set("downloadURI", rtPathInfo.DownloadURI) + result.ArtifactManagerMetadata.Set("createdBy", rtPathInfo.CreatedBy) + result.ArtifactManagerMetadata.Set("localRepository", rtPathInfo.Repo) + result.ArtifactManagerMetadata.Set("id", img.imageID) + break + } + } + if !manifestFound { + return nil, time.Since(t0), errors.New("unable to get uploaded image manifest") + } + + default: + // Push on the registry set as parameter + if registry == "" && registryAuth == "" { + return nil, time.Since(t0), errors.New("wrong usage: and parameters should not be both empty") + } + + destination = img.repository + ":" + tag + if registry != "" { + destination = registry + "/" + destination + } + + if tag != img.tag { // if the image already has the right tag, nothing to do + if err := cli.ImageTag(ctx, img.imageID, destination); err != nil { + return nil, time.Since(t0), errors.Errorf("unable to tag %q to %q: %v", source, destination, err) + } + } + + output, err := cli.ImagePush(ctx, destination, types.ImagePushOptions{RegistryAuth: registryAuth}) + if err != nil { + return nil, time.Since(t0), errors.Errorf("unable to push %q: %v", destination, err) + } + + if err := jsonmessage.DisplayJSONMessagesToStream(output, streams.NewOut(os.Stdout), nil); err != nil { + return nil, time.Since(t0), errors.Errorf("unable to push %q: %v", destination, err) + } + + result.ArtifactManagerMetadata = &sdk.V2WorkflowRunResultArtifactManagerMetadata{} + result.ArtifactManagerMetadata.Set("registry", registry) + result.ArtifactManagerMetadata.Set("name", destination) + result.ArtifactManagerMetadata.Set("id", img.imageID) + } + + details, err := result.GetDetailAsV2WorkflowRunResultDockerDetail() + if err != nil { + return nil, time.Since(t0), err + } + details.Name = destination + result.Detail.Data = details + result.Status = sdk.V2WorkflowRunResultStatusCompleted + + updatedRunresult, err := grpcplugins.UpdateRunResult(ctx, &actPlugin.Common, &workerruntime.V2RunResultRequest{RunResult: result}) + return updatedRunresult.RunResult, time.Since(t0), err + +} + +func HumanDuration(seconds int64) string { + createdAt := time.Unix(seconds, 0) + + if createdAt.IsZero() { + return "" + } + // https://github.com/docker/cli/blob/0e70f1b7b831565336006298b9443b015c3c87a5/cli/command/formatter/buildcache.go#L156 + return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago" +} + +func HumanSize(size int64) string { + // https://github.com/docker/cli/blob/0e70f1b7b831565336006298b9443b015c3c87a5/cli/command/formatter/buildcache.go#L148 + return units.HumanSizeWithPrecision(float64(size), 3) +} diff --git a/contrib/grpcplugins/action/docker-push/main_test.go b/contrib/grpcplugins/action/docker-push/main_test.go new file mode 100644 index 0000000000..c035ae9788 --- /dev/null +++ b/contrib/grpcplugins/action/docker-push/main_test.go @@ -0,0 +1,193 @@ +package main + +import ( + "bytes" + "context" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "testing" + + "github.com/golang/mock/gomock" + "github.com/ovh/cds/engine/worker/pkg/workerruntime" + "github.com/ovh/cds/engine/worker/pkg/workerruntime/mock_workerruntime" + "github.com/ovh/cds/sdk" + "github.com/ovh/cds/sdk/cdsclient/mock_cdsclient" + "github.com/ovh/cds/sdk/grpcplugin/actionplugin" + "github.com/rockbears/log" + "github.com/stretchr/testify/require" +) + +func Test_dockerPushPlugin_perform(t *testing.T) { + artifactoryRepoPrefix := os.Getenv("ARTIFACTORY_REPO_PRPEFIX") + artifactoryURL := os.Getenv("ARTIFACTORY_URL") + artifactoryToken := os.Getenv("ARTIFACTORY_TOKEN") + artifactoryUsername := os.Getenv("ARTIFACTORY_USERNAME") + + if artifactoryRepoPrefix == "" { + artifactoryRepoPrefix = "fsamin-default" + } + if artifactoryURL == "" { + artifactoryURL = "https://artifactory.localhost.local/artifactory" + } + if artifactoryToken == "" { + artifactoryToken = "xxxx" + } + if artifactoryUsername == "" { + artifactoryUsername = "workflow_v2_test_it" + } + + rtURL, err := url.Parse(artifactoryURL) + require.NoError(t, err) + + rtHost := rtURL.Host + + log.Factory = log.NewTestingWrapper(t) + ctrl := gomock.NewController(t) + t.Cleanup(ctrl.Finish) + mockHTTPClient := mock_cdsclient.NewMockHTTPClient(ctrl) + mockWorker := mock_workerruntime.NewMockRuntime(ctrl) + + type args struct { + image string + tags []string + registry string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "test 1", + args: args{ + image: "alpine:latest", + tags: []string{"test-1"}, + registry: "fsamin-default-docker" + rtHost, + }, + }, + } + + mockHTTPClient.EXPECT().Do(sdk.ReqMatcher{Method: "POST", URLPath: "/v2/result"}).DoAndReturn( + func(req *http.Request) (*http.Response, error) { + var rrRequest workerruntime.V2RunResultRequest + btes, err := io.ReadAll(req.Body) + require.NoError(t, err) + require.NoError(t, sdk.JSONUnmarshal(btes, &rrRequest)) + require.Equal(t, "alpine:latest", rrRequest.RunResult.Detail.Data.(*sdk.V2WorkflowRunResultDockerDetail).Name) + + h := workerruntime.V2_runResultHandler(context.TODO(), mockWorker) + + rec := httptest.NewRecorder() + apiReq := http.Request{ + Method: "POST", + URL: &url.URL{}, + } + apiReq.Body = io.NopCloser(bytes.NewBuffer(btes)) + h(rec, &apiReq) + return rec.Result(), nil + }, + ) + + mockHTTPClient.EXPECT().Do(sdk.ReqMatcher{Method: "GET", URLPath: "/v2/integrations/artifactory-integration"}).DoAndReturn( + func(req *http.Request) (*http.Response, error) { + h := workerruntime.V2_integrationsHandler(context.TODO(), mockWorker) + rec := httptest.NewRecorder() + apiReq := http.Request{ + Method: "POST", + URL: &url.URL{}, + } + q := apiReq.URL.Query() + q.Add("name", "artifactory-integration") + apiReq.URL.RawQuery = q.Encode() + h(rec, &apiReq) + return rec.Result(), nil + }, + ) + + mockHTTPClient.EXPECT().Do(sdk.ReqHostMatcher{Host: rtHost}).DoAndReturn( + func(req *http.Request) (*http.Response, error) { + return http.DefaultClient.Do(req) + }, + ).AnyTimes() + + mockWorker.EXPECT().V2AddRunResult(gomock.Any(), gomock.Any()).DoAndReturn( + func(ctx context.Context, req workerruntime.V2RunResultRequest) (*workerruntime.V2AddResultResponse, error) { + require.Equal(t, "alpine:latest", req.RunResult.Detail.Data.(*sdk.V2WorkflowRunResultDockerDetail).Name) + var s = "artifactory-integration" + req.RunResult.ArtifactManagerIntegrationName = &s + req.RunResult.ID = sdk.UUID() + return &workerruntime.V2AddResultResponse{ + RunResult: req.RunResult, + }, nil + }, + ) + + integ := sdk.ProjectIntegration{ + ID: 1, + ProjectID: 1, + Name: "artifactory-integration", + IntegrationModelID: 1, + Model: sdk.ArtifactoryIntegration, + Config: sdk.ArtifactoryIntegration.DefaultConfig.Clone(), + } + + integ.Config.SetValue(sdk.ArtifactoryConfigRepositoryPrefix, artifactoryRepoPrefix) + integ.Config.SetValue(sdk.ArtifactoryConfigURL, artifactoryURL) + integ.Config.SetValue(sdk.ArtifactoryConfigToken, artifactoryToken) + integ.Config.SetValue(sdk.ArtifactoryConfigTokenName, artifactoryUsername) + + mockWorker.EXPECT().V2GetIntegrationByName(gomock.Any(), "artifactory-integration").Return( + &integ, nil, + ) + + mockHTTPClient.EXPECT().Do(sdk.ReqMatcher{Method: "PUT", URLPath: "/v2/result"}).DoAndReturn( + func(req *http.Request) (*http.Response, error) { + var rrRequest workerruntime.V2RunResultRequest + btes, err := io.ReadAll(req.Body) + require.NoError(t, err) + require.NoError(t, sdk.JSONUnmarshal(btes, &rrRequest)) + require.Equal(t, sdk.V2WorkflowRunResultStatusCompleted, rrRequest.RunResult.Status) + + h := workerruntime.V2_runResultHandler(context.TODO(), mockWorker) + + rec := httptest.NewRecorder() + apiReq := http.Request{ + Method: "PUT", + URL: &url.URL{}, + } + apiReq.Body = io.NopCloser(bytes.NewBuffer(btes)) + h(rec, &apiReq) + return rec.Result(), nil + }, + ) + + mockWorker.EXPECT().V2UpdateRunResult(gomock.Any(), gomock.Any()).DoAndReturn( + func(ctx context.Context, req workerruntime.V2RunResultRequest) (*workerruntime.V2UpdateResultResponse, error) { + details, err := req.RunResult.GetDetailAsV2WorkflowRunResultDockerDetail() + require.NoError(t, err) + require.Equal(t, artifactoryRepoPrefix+"-docker."+rtHost+"/alpine:test-1", details.Name) + t.Logf("details:s %+v", details) + t.Logf("metadata:s %+v", req.RunResult.ArtifactManagerMetadata) + return &workerruntime.V2UpdateResultResponse{ + RunResult: req.RunResult, + }, nil + }, + ) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actPlugin := &dockerPushPlugin{ + Common: actionplugin.Common{ + HTTPClient: mockHTTPClient, + HTTPPort: 1, + }, + } + if err := actPlugin.perform(context.TODO(), tt.args.image, tt.args.tags, tt.args.registry, ""); (err != nil) != tt.wantErr { + t.Errorf("dockerPushPlugin.perform() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/contrib/grpcplugins/action/plugin-archive/go.mod b/contrib/grpcplugins/action/plugin-archive/go.mod index 75c2ecd3db..b714a96d80 100644 --- a/contrib/grpcplugins/action/plugin-archive/go.mod +++ b/contrib/grpcplugins/action/plugin-archive/go.mod @@ -27,6 +27,7 @@ require ( github.com/blang/semver v3.5.1+incompatible // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect + github.com/creack/pty v1.1.21 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect diff --git a/contrib/grpcplugins/action/plugin-archive/go.sum b/contrib/grpcplugins/action/plugin-archive/go.sum index 6f6b4021c7..a509b2770a 100644 --- a/contrib/grpcplugins/action/plugin-archive/go.sum +++ b/contrib/grpcplugins/action/plugin-archive/go.sum @@ -111,8 +111,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/contrib/grpcplugins/action/plugin-artifactory-release-bundle-create/go.sum b/contrib/grpcplugins/action/plugin-artifactory-release-bundle-create/go.sum index bda996fd7e..07d3c011ee 100644 --- a/contrib/grpcplugins/action/plugin-artifactory-release-bundle-create/go.sum +++ b/contrib/grpcplugins/action/plugin-artifactory-release-bundle-create/go.sum @@ -127,9 +127,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/contrib/grpcplugins/action/plugin-tmpl/go.mod b/contrib/grpcplugins/action/plugin-tmpl/go.mod index 7a35c81d6f..17ccb2d4c2 100644 --- a/contrib/grpcplugins/action/plugin-tmpl/go.mod +++ b/contrib/grpcplugins/action/plugin-tmpl/go.mod @@ -25,6 +25,7 @@ require ( github.com/blang/semver v3.5.1+incompatible // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect + github.com/creack/pty v1.1.21 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/eapache/go-resiliency v1.3.0 // indirect diff --git a/contrib/grpcplugins/action/plugin-tmpl/go.sum b/contrib/grpcplugins/action/plugin-tmpl/go.sum index 6f6b4021c7..a509b2770a 100644 --- a/contrib/grpcplugins/action/plugin-tmpl/go.sum +++ b/contrib/grpcplugins/action/plugin-tmpl/go.sum @@ -111,8 +111,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/contrib/grpcplugins/action/uploadArtifact/main.go b/contrib/grpcplugins/action/uploadArtifact/main.go index e74716fb6e..8674b13c84 100644 --- a/contrib/grpcplugins/action/uploadArtifact/main.go +++ b/contrib/grpcplugins/action/uploadArtifact/main.go @@ -231,7 +231,7 @@ func (actPlugin *runActionUploadArtifactPlugin) perform(ctx context.Context, dir path := filepath.Join(jobRun.ProjectKey, jobRun.WorkflowName, jobContext.Git.SemverCurrent) response.RunResult.ArtifactManagerMetadata = &sdk.V2WorkflowRunResultArtifactManagerMetadata{} - response.RunResult.ArtifactManagerMetadata.Set("repository", repository) + response.RunResult.ArtifactManagerMetadata.Set("repository", repository) // This is the virtual repository response.RunResult.ArtifactManagerMetadata.Set("type", "generic") response.RunResult.ArtifactManagerMetadata.Set("maturity", maturity) response.RunResult.ArtifactManagerMetadata.Set("name", r.Result) diff --git a/contrib/grpcplugins/grpcplugins.go b/contrib/grpcplugins/grpcplugins.go index 20745a5074..b39730ad6b 100644 --- a/contrib/grpcplugins/grpcplugins.go +++ b/contrib/grpcplugins/grpcplugins.go @@ -8,6 +8,8 @@ import ( "io" "net/http" "net/url" + "path/filepath" + "time" "github.com/pkg/errors" @@ -268,3 +270,105 @@ func GetJobContext(ctx context.Context, c *actionplugin.Common) (*sdk.WorkflowRu } return &context, nil } + +type ArtifactoryConfig struct { + URL string + Token string +} + +type ArtifactoryFileInfo struct { + Repo string `json:"repo"` + Path string `json:"path"` + Created time.Time `json:"created"` + CreatedBy string `json:"createdBy"` + DownloadURI string `json:"downloadUri"` + MimeType string `json:"mimeType"` + Size string `json:"size"` + Checksums struct { + Sha1 string `json:"sha1"` + Md5 string `json:"md5"` + Sha256 string `json:"sha256"` + } `json:"checksums"` + OriginalChecksums struct { + Sha1 string `json:"sha1"` + Md5 string `json:"md5"` + Sha256 string `json:"sha256"` + } `json:"originalChecksums"` + URI string `json:"uri"` +} + +type ArtifactoryFolderInfo struct { + Repo string `json:"repo"` + Path string `json:"path"` + Created time.Time `json:"created"` + CreatedBy string `json:"createdBy"` + URI string `json:"uri"` + Children []struct { + URI string `json:"uri"` + Folder bool `json:"folder"` + } `json:"children"` +} + +func GetArtifactoryFileInfo(ctx context.Context, c *actionplugin.Common, config ArtifactoryConfig, repo, path string) (*ArtifactoryFileInfo, error) { + uri := config.URL + "api/storage/" + filepath.Join(repo, path) + req, err := http.NewRequestWithContext(ctx, "GET", uri, nil) + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", "Bearer "+config.Token) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return nil, err + } + btes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode > 200 { + Error(string(btes)) + return nil, errors.Errorf("unable to get Artifactory file info %s: error %d", uri, resp.StatusCode) + } + + var res ArtifactoryFileInfo + if err := json.Unmarshal(btes, &res); err != nil { + Error(string(btes)) + return nil, errors.Errorf("unable to get Artifactory file info: %v", err) + } + + return &res, nil +} + +func GetArtifactoryFolderInfo(ctx context.Context, c *actionplugin.Common, config ArtifactoryConfig, repo, path string) (*ArtifactoryFolderInfo, error) { + uri := config.URL + "api/storage/" + filepath.Join(repo, path) + req, err := http.NewRequestWithContext(ctx, "GET", uri, nil) + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", "Bearer "+config.Token) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return nil, err + } + btes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode > 200 { + Error(string(btes)) + return nil, errors.Errorf("unable to get Artifactory folder info %s: error %d", uri, resp.StatusCode) + } + + var res ArtifactoryFolderInfo + if err := json.Unmarshal(btes, &res); err != nil { + Error(string(btes)) + return nil, errors.Errorf("unable to get Artifactory folder info: %v", err) + } + + return &res, nil +} diff --git a/contrib/integrations/artifactory/artifactory-build-info-plugin/go.mod b/contrib/integrations/artifactory/artifactory-build-info-plugin/go.mod index dc98170641..f190659a7b 100644 --- a/contrib/integrations/artifactory/artifactory-build-info-plugin/go.mod +++ b/contrib/integrations/artifactory/artifactory-build-info-plugin/go.mod @@ -25,6 +25,7 @@ require ( github.com/blang/semver v3.5.1+incompatible // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect + github.com/creack/pty v1.1.21 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/eapache/go-resiliency v1.3.0 // indirect diff --git a/contrib/integrations/artifactory/artifactory-build-info-plugin/go.sum b/contrib/integrations/artifactory/artifactory-build-info-plugin/go.sum index db38720f66..feecac6e05 100644 --- a/contrib/integrations/artifactory/artifactory-build-info-plugin/go.sum +++ b/contrib/integrations/artifactory/artifactory-build-info-plugin/go.sum @@ -110,8 +110,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/contrib/integrations/artifactory/artifactory-promote-plugin/go.mod b/contrib/integrations/artifactory/artifactory-promote-plugin/go.mod index 2e8316b514..6978c6d51f 100644 --- a/contrib/integrations/artifactory/artifactory-promote-plugin/go.mod +++ b/contrib/integrations/artifactory/artifactory-promote-plugin/go.mod @@ -25,6 +25,7 @@ require ( github.com/blang/semver v3.5.1+incompatible // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect + github.com/creack/pty v1.1.21 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/eapache/go-resiliency v1.3.0 // indirect diff --git a/contrib/integrations/artifactory/artifactory-promote-plugin/go.sum b/contrib/integrations/artifactory/artifactory-promote-plugin/go.sum index db38720f66..feecac6e05 100644 --- a/contrib/integrations/artifactory/artifactory-promote-plugin/go.sum +++ b/contrib/integrations/artifactory/artifactory-promote-plugin/go.sum @@ -110,8 +110,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/contrib/integrations/artifactory/artifactory-release-plugin/go.mod b/contrib/integrations/artifactory/artifactory-release-plugin/go.mod index 3aca3a8251..588d8cb832 100644 --- a/contrib/integrations/artifactory/artifactory-release-plugin/go.mod +++ b/contrib/integrations/artifactory/artifactory-release-plugin/go.mod @@ -25,6 +25,7 @@ require ( github.com/blang/semver v3.5.1+incompatible // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect + github.com/creack/pty v1.1.21 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/eapache/go-resiliency v1.3.0 // indirect diff --git a/contrib/integrations/artifactory/artifactory-release-plugin/go.sum b/contrib/integrations/artifactory/artifactory-release-plugin/go.sum index db38720f66..feecac6e05 100644 --- a/contrib/integrations/artifactory/artifactory-release-plugin/go.sum +++ b/contrib/integrations/artifactory/artifactory-release-plugin/go.sum @@ -110,8 +110,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/engine/cache/redis.go b/engine/cache/redis.go index 23cf36870c..bb5bb691eb 100644 --- a/engine/cache/redis.go +++ b/engine/cache/redis.go @@ -30,7 +30,7 @@ func NewRedisStore(host, password string, dbindex, ttl int) (*RedisStore, error) var client *redis.Client //if host is line master@localhost:26379,localhost:26380 => it's a redis sentinel cluster - if strings.Contains(host, "@") && strings.Contains(host, ",") { + if strings.Contains(host, "@") { masterName := strings.Split(host, "@")[0] sentinelsStr := strings.Split(host, "@")[1] sentinels := strings.Split(sentinelsStr, ",") diff --git a/engine/worker/pkg/workerruntime/handlers_v2.go b/engine/worker/pkg/workerruntime/handlers_v2.go index 23a7992c3d..a0aad38e76 100644 --- a/engine/worker/pkg/workerruntime/handlers_v2.go +++ b/engine/worker/pkg/workerruntime/handlers_v2.go @@ -146,8 +146,17 @@ func V2_integrationsHandler(ctx context.Context, wk Runtime) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) name := vars["name"] + if name == "" && len(r.URL.Query()["name"]) > 0 { + name = r.URL.Query()["name"][0] // This part if for unit test + } + if name == "" { + log.Error(ctx, "missing parameter 'name'") + writeError(w, r, sdk.ErrNotFound) + return + } integ, err := wk.V2GetIntegrationByName(r.Context(), name) if err != nil { + log.Error(ctx, "unable to get integration %q", name) writeError(w, r, err) return } diff --git a/go.mod b/go.mod index 1df2626ee8..bd68267a71 100644 --- a/go.mod +++ b/go.mod @@ -16,9 +16,11 @@ require ( github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/confluentinc/bincover v0.2.0 github.com/coreos/go-oidc v2.2.1+incompatible + github.com/docker/cli v25.0.1+incompatible github.com/docker/distribution v2.8.2+incompatible github.com/docker/docker v23.0.3+incompatible github.com/docker/go-connections v0.4.0 + github.com/docker/go-units v0.3.2 github.com/eapache/go-resiliency v1.3.0 github.com/fatih/color v1.13.0 github.com/fsamin/go-dump v1.0.9 @@ -120,6 +122,7 @@ require ( cloud.google.com/go/firestore v1.9.0 // indirect cloud.google.com/go/longrunning v0.4.1 // indirect dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.18 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect @@ -140,10 +143,8 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/coreos/go-semver v0.3.0 // indirect - github.com/creack/pty v1.1.18 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/go-units v0.3.2 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect diff --git a/go.sum b/go.sum index 1e80634189..cf04f69113 100644 --- a/go.sum +++ b/go.sum @@ -197,6 +197,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/cli v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbTO1lpcGSkU= +github.com/docker/cli v25.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v23.0.3+incompatible h1:9GhVsShNWz1hO//9BNg/dpMnZW25KydO4wtVxWAIbho= @@ -1212,6 +1214,7 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/sdk/util.go b/sdk/util.go index 5f8c50f0f3..b998fb192c 100644 --- a/sdk/util.go +++ b/sdk/util.go @@ -1,7 +1,9 @@ package sdk import ( + "fmt" "math/rand" + "net/http" "time" ) @@ -84,3 +86,38 @@ func StringFirstN(s string, i int) string { } return s[:i] } + +type ReqHostMatcher struct { + Host string +} + +func (m ReqHostMatcher) Matches(x interface{}) bool { + switch i := x.(type) { + case *http.Request: + return i.URL.Host == m.Host + default: + return false + } +} + +func (m ReqHostMatcher) String() string { + return fmt.Sprintf("Host is %q", m.Host) +} + +type ReqMatcher struct { + Method string + URLPath string +} + +func (m ReqMatcher) Matches(x interface{}) bool { + switch i := x.(type) { + case *http.Request: + return i.URL.Path == m.URLPath && m.Method == i.Method + default: + return false + } +} + +func (m ReqMatcher) String() string { + return fmt.Sprintf("Method is %q, URL Path is %q", m.Method, m.URLPath) +} diff --git a/sdk/v2_workflow_run.go b/sdk/v2_workflow_run.go index 5d1fb29edc..64ca46fd9c 100644 --- a/sdk/v2_workflow_run.go +++ b/sdk/v2_workflow_run.go @@ -449,6 +449,24 @@ func (r *V2WorkflowRunResult) GetDetail() (any, error) { return r.Detail.Data, nil } +func (r *V2WorkflowRunResult) GetDetailAsV2WorkflowRunResultDockerDetail() (*V2WorkflowRunResultDockerDetail, error) { + if err := r.Detail.castData(); err != nil { + return nil, err + } + i, ok := r.Detail.Data.(*V2WorkflowRunResultDockerDetail) + if !ok { + var ii V2WorkflowRunResultDockerDetail + ii, ok = r.Detail.Data.(V2WorkflowRunResultDockerDetail) + if ok { + i = &ii + } + } + if !ok { + return nil, errors.New("unable to cast detail as V2WorkflowRunResultDockerDetail") + } + return i, nil +} + func (r *V2WorkflowRunResult) GetDetailAsV2WorkflowRunResultGenericDetail() (*V2WorkflowRunResultGenericDetail, error) { if err := r.Detail.castData(); err != nil { return nil, err @@ -474,6 +492,11 @@ func (r *V2WorkflowRunResult) Name() string { if err == nil { return string(r.Type) + ":" + detail.Name } + case V2WorkflowRunResultTypeDocker: + detail, err := r.GetDetailAsV2WorkflowRunResultDockerDetail() + if err == nil { + return string(r.Type) + ":" + detail.Name + } case V2WorkflowRunResultTypeVariable: detail, ok := r.Detail.Data.(*V2WorkflowRunResultVariableDetail) if ok { @@ -545,6 +568,13 @@ func (s *V2WorkflowRunResultDetail) castData() error { } s.Data = detail return nil + case "V2WorkflowRunResultDockerDetail": + var detail = new(V2WorkflowRunResultDockerDetail) + if err := mapstructure.Decode(s.Data, &detail); err != nil { + return WrapError(err, "cannot unmarshal V2WorkflowRunResultDockerDetail") + } + s.Data = detail + return nil default: return errors.Errorf("unsupported type %q", s.Type) } @@ -635,6 +665,13 @@ type V2WorkflowRunResultGenericDetail struct { SHA256 string `json:"sha256" mapstructure:"sha256"` } +type V2WorkflowRunResultDockerDetail struct { + Name string `json:"name" mapstructure:"name"` + ID string `json:"id" mapstructure:"id"` + HumanSize string `json:"human_size" mapstructure:"human_size"` + HumanCreated string `json:"human_created" mapstructure:"human_created"` +} + type V2WorkflowRunResultVariableDetail struct { Name string `json:"name" mapstructure:"name"` Value string `json:"value" mapstructure:"value"` diff --git a/tests/fixtures/04SCWorkflowRunSimplePlugin/go.mod b/tests/fixtures/04SCWorkflowRunSimplePlugin/go.mod index 3a6cbe3751..d5a96432c9 100644 --- a/tests/fixtures/04SCWorkflowRunSimplePlugin/go.mod +++ b/tests/fixtures/04SCWorkflowRunSimplePlugin/go.mod @@ -23,6 +23,7 @@ require ( github.com/blang/semver v3.5.1+incompatible // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect + github.com/creack/pty v1.1.21 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/eapache/go-resiliency v1.3.0 // indirect diff --git a/tests/fixtures/04SCWorkflowRunSimplePlugin/go.sum b/tests/fixtures/04SCWorkflowRunSimplePlugin/go.sum index 8a6d4e323a..55cc41f518 100644 --- a/tests/fixtures/04SCWorkflowRunSimplePlugin/go.sum +++ b/tests/fixtures/04SCWorkflowRunSimplePlugin/go.sum @@ -110,8 +110,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=