diff --git a/.mockery.yaml b/.mockery.yaml new file mode 100644 index 000000000000..1537cb3b6e02 --- /dev/null +++ b/.mockery.yaml @@ -0,0 +1,7 @@ +with-expecter: true +packages: + github.com/kyma-project/test-infra/pkg/azuredevops/pipelines: + config: + include-regex: ".*Client" + dir: "{{.InterfaceDir}}/mocks" + outpkg: "{{.PackageName}}mocks" diff --git a/cmd/image-builder/config.go b/cmd/image-builder/config.go index c80d2dea529e..2a9d4b7ded44 100644 --- a/cmd/image-builder/config.go +++ b/cmd/image-builder/config.go @@ -4,12 +4,14 @@ import ( "fmt" "os" + adoPipelines "github.com/kyma-project/test-infra/pkg/azuredevops/pipelines" "github.com/kyma-project/test-infra/pkg/sign" "github.com/kyma-project/test-infra/pkg/tags" "gopkg.in/yaml.v3" ) type Config struct { + AdoConfig adoPipelines.Config `yaml:"ado-config,omitempty" json:"ado-config,omitempty"` // Registry is URL where clean build should land. Registry Registry `yaml:"registry" json:"registry"` // DevRegistry is Registry URL where development/dirty images should land. diff --git a/cmd/image-builder/images/kaniko/Dockerfile b/cmd/image-builder/images/kaniko/Dockerfile index e1adbaf40d20..972950347de8 100644 --- a/cmd/image-builder/images/kaniko/Dockerfile +++ b/cmd/image-builder/images/kaniko/Dockerfile @@ -1,5 +1,12 @@ -FROM gcr.io/kaniko-project/executor:v1.9.0 +FROM europe-docker.pkg.dev/kyma-project/prod/testimages/buildpack-go:v20231128-9bb59ac6 AS builder -COPY ./image-builder /image-builder +WORKDIR / +COPY . /app/ +RUN cd /app/cmd/image-builder && CGO_ENABLED=0 go build -o /app/image-builder -a -ldflags '-extldflags "-static"' . -ENTRYPOINT ["/image-builder"] \ No newline at end of file + +FROM gcr.io/kaniko-project/executor:v1.14.0 + +COPY --from=builder /app/image-builder /image-builder + +ENTRYPOINT ["/image-builder"] diff --git a/cmd/image-builder/main.go b/cmd/image-builder/main.go index 3cdbfae8f23b..cccffd91be32 100644 --- a/cmd/image-builder/main.go +++ b/cmd/image-builder/main.go @@ -2,10 +2,12 @@ package main import ( "bufio" + "encoding/json" "flag" "fmt" "io" "io/fs" + "net/http" "os" "os/exec" "path" @@ -13,10 +15,14 @@ import ( "strconv" "strings" "sync" + "time" + adopipelines "github.com/kyma-project/test-infra/pkg/azuredevops/pipelines" "github.com/kyma-project/test-infra/pkg/sets" "github.com/kyma-project/test-infra/pkg/sign" "github.com/kyma-project/test-infra/pkg/tags" + "github.com/microsoft/azure-devops-go-api/azuredevops/v7/pipelines" + "golang.org/x/net/context" errutil "k8s.io/apimachinery/pkg/util/errors" ) @@ -36,6 +42,11 @@ type options struct { buildArgs sets.Tags platforms sets.Strings exportTags bool + // signOnly only sign images. No build will be performed. + signOnly bool + imagesToSign sets.Strings + buildInADO bool + parseTagsOnly bool } const ( @@ -162,13 +173,161 @@ func runInKaniko(o options, name string, destinations, platforms []string, build return cmd.Run() } -func runBuildJob(o options, vs Variants, envs map[string]string) error { +func prepareADOTemplateParameters(options options) (adopipelines.OCIImageBuilderTemplateParams, error) { + templateParameters := make(adopipelines.OCIImageBuilderTemplateParams) + + repo, present := os.LookupEnv("REPO_NAME") + if !present { + return nil, fmt.Errorf("REPO_NAME environment variable is not set, please set it to valid repository name") + } + templateParameters.SetRepoName(repo) + + owner, present := os.LookupEnv("REPO_OWNER") + if !present { + return nil, fmt.Errorf("REPO_OWNER environment variable is not set, please set it to valid repository owner") + } + templateParameters.SetRepoOwner(owner) + + jobType, present := os.LookupEnv("JOB_TYPE") + if !present { + return nil, fmt.Errorf("JOB_TYPE environment variable is not set, please set it to valid job type") + } + if jobType == "presubmit" { + templateParameters.SetPresubmitJobType() + } else if jobType == "postsubmit" { + templateParameters.SetPostsubmitJobType() + } else { + return nil, fmt.Errorf("JOB_TYPE environment variable is not set to valid value, please set it to either 'presubmit' or 'postsubmit'") + } + + number, present := os.LookupEnv("PULL_NUMBER") + if !present { + return nil, fmt.Errorf("PULL_NUMBER environment variable is not set, please set it to valid pull request number") + } + templateParameters.SetPullNumber(number) + + baseSHA, present := os.LookupEnv("PULL_BASE_SHA") + if !present { + return nil, fmt.Errorf("PULL_BASE_SHA environment variable is not set, please set it to valid pull base SHA") + } + templateParameters.SetBaseSHA(baseSHA) + + pullSHA, present := os.LookupEnv("PULL_PULL_SHA") + if present { + templateParameters.SetPullSHA(pullSHA) + } + + templateParameters.SetImageName(options.name) + + templateParameters.SetDockerfilePath(options.dockerfile) + + templateParameters.SetBuildContext(options.context) + + templateParameters.SetExportTags(options.exportTags) + + if len(options.buildArgs) > 0 { + templateParameters.SetBuildArgs(options.buildArgs.String()) + } + + if len(options.tags) > 0 { + templateParameters.SetImageTags(options.tags.String()) + } + + err := templateParameters.Validate() + if err != nil { + return nil, fmt.Errorf("failed validating ADO template parameters, err: %w", err) + } + + return templateParameters, nil +} + +// TODO(dekiel): refactor this function to accept clients as parameters to make it testable with mocks. +func buildInADO(o options) error { + fmt.Println("Building image in ADO pipeline.") + adoPAT, present := os.LookupEnv("ADO_PAT") + if !present { + return fmt.Errorf("build in ADO failed, ADO_PAT environment variable is not set, please set it to valid ADO PAT") + } + + fmt.Println("Preparing ADO template parameters.") + templateParameters, err := prepareADOTemplateParameters(o) + if err != nil { + return fmt.Errorf("build in ADO failed, failed preparing ADO template parameters, err: %s", err) + } + + fmt.Println("Running ADO pipeline.") + adoClient := adopipelines.NewClient(o.AdoConfig.ADOOrganizationURL, adoPAT) + + fmt.Println("Triggering ADO build pipeline") + ctx := context.Background() + pipelineRun, err := adopipelines.Run(ctx, adoClient, templateParameters, o.AdoConfig.GetADOConfig()) + if err != nil { + return fmt.Errorf("build in ADO failed, failed running ADO pipeline, err: %s", err) + } + + pipelineRunResult, err := adopipelines.GetRunResult(ctx, adoClient, o.AdoConfig.GetADOConfig(), pipelineRun.Id, 30*time.Second) + if err != nil { + return fmt.Errorf("build in ADO failed, failed getting ADO pipeline run result, err: %s", err) + } + fmt.Printf("ADO pipeline run finished with status: %s", *pipelineRunResult) + + fmt.Println("Getting ADO pipeline run logs.") + adoBuildClient, err := adopipelines.NewBuildClient(o.AdoConfig.ADOOrganizationURL, adoPAT) + if err != nil { + fmt.Printf("Can't read ADO pipeline run logs, failed creating ADO build client, err: %s", err) + } + logs, err := adopipelines.GetRunLogs(ctx, adoBuildClient, &http.Client{}, o.AdoConfig.GetADOConfig(), pipelineRun.Id, adoPAT) + if err != nil { + fmt.Printf("Failed read ADO pipeline run logs, err: %s", err) + } else { + fmt.Printf("ADO pipeline image build logs:\n%s", logs) + } + + if *pipelineRunResult == pipelines.RunResultValues.Failed || *pipelineRunResult == pipelines.RunResultValues.Unknown { + return fmt.Errorf("build in ADO finished with status: %s", *pipelineRunResult) + } + return nil +} + +func buildLocally(o options) error { runFunc := runInKaniko if os.Getenv("USE_BUILDKIT") == "true" { runFunc = runInBuildKit } var sha, pr string var err error + var variant Variants + var envs map[string]string + + context, err := filepath.Abs(o.context) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // TODO(dekiel): validating if envFile or variants.yaml file exists should be done in validateOptions or in a separate function. + // We should call this function before calling image building functions. + dockerfilePath := filepath.Join(context, filepath.Dir(o.dockerfile)) + + if len(o.envFile) > 0 { + envs, err = loadEnv(os.DirFS(dockerfilePath), o.envFile) + if err != nil { + return fmt.Errorf("load env failed, error: %w", err) + } + + } else { + variantsFile := filepath.Join(dockerfilePath, "variants.yaml") + variant, err = GetVariants(o.variant, variantsFile, os.ReadFile) + if err != nil { + if !os.IsNotExist(err) { + return fmt.Errorf("get variants failed, error: %w", err) + } + } + if len(variant) > 0 { + return fmt.Errorf("building variants is not not working and is not supported anymore") + } + } + repo := o.Config.Registry if o.isCI { presubmit := os.Getenv("JOB_TYPE") == "presubmit" @@ -210,23 +369,20 @@ func runBuildJob(o options, vs Variants, envs map[string]string) error { appendMissing(&buildArgs, o.buildArgs) - if len(vs) == 0 { - // variants.yaml file not present or either empty. Run single build. - destinations := gatherDestinations(repo, o.name, parsedTags) - fmt.Println("Starting build for image: ", strings.Join(destinations, ", ")) - err = runFunc(o, "build", destinations, o.platforms, buildArgs) - if err != nil { - return fmt.Errorf("build encountered error: %w", err) - } + // variants.yaml file not present or either empty. Run single build. + destinations := gatherDestinations(repo, o.name, parsedTags) + fmt.Println("Starting build for image: ", strings.Join(destinations, ", ")) + err = runFunc(o, "build", destinations, o.platforms, buildArgs) + if err != nil { + return fmt.Errorf("build encountered error: %w", err) + } - err := signImages(&o, destinations) - if err != nil { - return fmt.Errorf("sign encountered error: %w", err) - } - fmt.Println("Successfully built image:", strings.Join(destinations, ", ")) - return nil + err = signImages(&o, destinations) + if err != nil { + return fmt.Errorf("sign encountered error: %w", err) } - return fmt.Errorf("building variants is not supported at this moment") + fmt.Println("Successfully built image:", strings.Join(destinations, ", ")) + return nil } // appendMissing appends key, values pairs from source array to target map @@ -240,6 +396,7 @@ func appendMissing(target *map[string]string, source []tags.Tag) { } } +// TODO: write tests for this function func signImages(o *options, images []string) error { // use o.orgRepo as default value since someone might have loaded is as a flag orgRepo := o.orgRepo @@ -383,15 +540,38 @@ func gatherDestinations(repo []string, name string, tags []tags.Tag) []string { // validateOptions handles options validation. All checks should be provided here func validateOptions(o options) error { var errs []error + if o.context == "" { errs = append(errs, fmt.Errorf("flag '--context' is missing")) } + if o.name == "" { errs = append(errs, fmt.Errorf("flag '--name' is missing")) } + if o.dockerfile == "" { errs = append(errs, fmt.Errorf("flag '--dockerfile' is missing")) } + + if o.configPath == "" { + errs = append(errs, fmt.Errorf("'--config' flag is missing or has empty value, please provide the path to valid 'config.yaml' file")) + } + + if o.signOnly && len(o.imagesToSign) == 0 { + errs = append(errs, fmt.Errorf("flag '--images-to-sign' is missing, please provide at least one image to sign")) + } + if !o.signOnly && len(o.imagesToSign) > 0 { + errs = append(errs, fmt.Errorf("flag '--sign-only' is missing or has false value, please set it to true when using '--images-to-sign' flag")) + } + + if o.envFile != "" && o.buildInADO { + errs = append(errs, fmt.Errorf("envFile flag is not supported when running in ADO")) + } + + if o.variant != "" && o.buildInADO { + errs = append(errs, fmt.Errorf("variant flag is not supported when running in ADO")) + } + return errutil.NewAggregate(errs) } @@ -450,15 +630,21 @@ func (o *options) gatherOptions(flagSet *flag.FlagSet) *flag.FlagSet { flagSet.StringVar(&o.configPath, "config", "/config/image-builder-config.yaml", "Path to application config file") flagSet.StringVar(&o.context, "context", ".", "Path to build directory context") flagSet.StringVar(&o.envFile, "env-file", "", "Path to file with environment variables to be loaded in build") - flagSet.StringVar(&o.name, "name", "", "Name of the image to be built") - flagSet.StringVar(&o.dockerfile, "dockerfile", "Dockerfile", "Path to Dockerfile file relative to context") + flagSet.StringVar(&o.name, "name", "", "name of the image to be built") + flagSet.StringVar(&o.dockerfile, "dockerfile", "dockerfile", "Path to dockerfile file relative to context") flagSet.StringVar(&o.variant, "variant", "", "If variants.yaml file is present, define which variant should be built. If variants.yaml is not present, this flag will be ignored") flagSet.StringVar(&o.logDir, "log-dir", "/logs/artifacts", "Path to logs directory where GCB logs will be stored") + // TODO: What is expected value repo only or org/repo? How this flag influence an image builder behaviour? flagSet.StringVar(&o.orgRepo, "repo", "", "Load repository-specific configuration, for example, signing configuration") flagSet.Var(&o.tags, "tag", "Additional tag that the image will be tagged with. Optionally you can pass the name in the format name=value which will be used by export-tags") - flagSet.Var(&o.buildArgs, "build-arg", "Flag to pass additional arguments to build Dockerfile. It can be used in the name=value format.") + flagSet.Var(&o.buildArgs, "build-arg", "Flag to pass additional arguments to build dockerfile. It can be used in the name=value format.") flagSet.Var(&o.platforms, "platform", "Only supported with BuildKit. Platform of the image that is built") - flagSet.BoolVar(&o.exportTags, "export-tags", false, "Export parsed tags as build-args into Dockerfile. Each tag will have format TAG_x, where x is the tag name passed along with the tag") + flagSet.BoolVar(&o.exportTags, "export-tags", false, "Export parsed tags as build-args into dockerfile. Each tag will have format TAG_x, where x is the tag name passed along with the tag") + flagSet.BoolVar(&o.signOnly, "sign-only", false, "Only sign the image, do not build it") + flagSet.Var(&o.imagesToSign, "images-to-sign", "Comma-separated list of images to sign. Only used when sign-only flag is set") + flagSet.BoolVar(&o.buildInADO, "build-in-ado", false, "Build in Azure DevOps pipeline environment") + flagSet.BoolVar(&o.parseTagsOnly, "parse-tags-only", false, "Only parse tags and print them to stdout") + return flagSet } @@ -470,10 +656,13 @@ func main() { fmt.Println(err) os.Exit(1) } - if o.configPath == "" { - fmt.Println("'--config' flag is missing or has empty value, please provide the path to valid 'config.yaml' file") + + // validate if options provided by flags and config file are fine + if err := validateOptions(o); err != nil { + fmt.Println(err) os.Exit(1) } + c, err := os.ReadFile(o.configPath) if err != nil { fmt.Println(err) @@ -485,40 +674,58 @@ func main() { os.Exit(1) } - // validate if options provided by flags and config file are fine - if err := validateOptions(o); err != nil { - fmt.Println(err) - os.Exit(1) + if o.signOnly { + err = signImages(&o, o.imagesToSign) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + os.Exit(0) } - context, err := filepath.Abs(o.context) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - dockerfilePath := filepath.Join(context, filepath.Dir(o.dockerfile)) + // TODO(dekiel): refactor this function to move all logic to separate function and make it testable. + if o.parseTagsOnly { + var sha, pr string + if o.isCI { + presubmit := os.Getenv("JOB_TYPE") == "presubmit" + if presubmit { + if n := os.Getenv("PULL_NUMBER"); n != "" { + pr = n + } + } - var variant Variants - var envs map[string]string - if len(o.envFile) > 0 { - envs, err = loadEnv(os.DirFS(dockerfilePath), o.envFile) + if c := os.Getenv("PULL_BASE_SHA"); c != "" { + sha = c + } + } + + // if sha is still not set, fail the pipeline + if sha == "" { + fmt.Println("'sha' could not be determined") + os.Exit(1) + } + parsedTags, err := getTags(pr, sha, append(o.tags, o.TagTemplate)) if err != nil { fmt.Println(err) os.Exit(1) } - - } else { - variantsFile := filepath.Join(dockerfilePath, "variants.yaml") - variant, err = GetVariants(o.variant, variantsFile, os.ReadFile) + // Print parsed tags to stdout as json + jsonTags, err := json.Marshal(parsedTags) if err != nil { - if !os.IsNotExist(err) { - fmt.Println(err) - os.Exit(1) - } + fmt.Println(err) + os.Exit(1) } + fmt.Printf("%s", jsonTags) + os.Exit(0) } - - err = runBuildJob(o, variant, envs) + if o.buildInADO { + err = buildInADO(o) + if err != nil { + fmt.Printf("Image build failed with error: %s", err) + os.Exit(1) + } + } + err = buildLocally(o) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/image-builder/main_test.go b/cmd/image-builder/main_test.go index b5e4d7c08642..18dfbeb78375 100644 --- a/cmd/image-builder/main_test.go +++ b/cmd/image-builder/main_test.go @@ -102,7 +102,8 @@ func Test_validateOptions(t *testing.T) { opts: options{ context: "directory/", name: "test-image", - dockerfile: "Dockerfile", + dockerfile: "dockerfile", + configPath: "config.yaml", }, }, { @@ -110,7 +111,7 @@ func Test_validateOptions(t *testing.T) { expectErr: true, opts: options{ name: "test-image", - dockerfile: "Dockerfile", + dockerfile: "dockerfile", }, }, { @@ -118,7 +119,7 @@ func Test_validateOptions(t *testing.T) { expectErr: true, opts: options{ context: "directory/", - dockerfile: "Dockerfile", + dockerfile: "dockerfile", }, }, { @@ -129,6 +130,63 @@ func Test_validateOptions(t *testing.T) { name: "test-image", }, }, + { + name: "Empty configPath", + expectErr: true, + opts: options{ + context: "directory/", + name: "test-image", + dockerfile: "dockerfile", + }, + }, + { + name: "signOnly without imagesToSign", + expectErr: true, + opts: options{ + context: "directory/", + name: "test-image", + dockerfile: "dockerfile", + configPath: "config.yaml", + signOnly: true, + imagesToSign: []string{}, + }, + }, + { + name: "imagesToSign without signOnly", + expectErr: true, + opts: options{ + context: "directory/", + name: "test-image", + dockerfile: "dockerfile", + configPath: "config.yaml", + signOnly: false, + imagesToSign: []string{"image1"}, + }, + }, + { + name: "envFile with buildInADO", + expectErr: true, + opts: options{ + context: "directory/", + name: "test-image", + dockerfile: "dockerfile", + configPath: "config.yaml", + envFile: "envfile", + buildInADO: true, + }, + }, + { + name: "variant with buildInADO", + expectErr: true, + opts: options{ + context: "directory/", + name: "test-image", + dockerfile: "dockerfile", + configPath: "config.yaml", + variant: "variant", + buildInADO: true, + }, + }, } for _, c := range tc { t.Run(c.name, func(t *testing.T) { @@ -155,7 +213,7 @@ func TestFlags(t *testing.T) { expectedOpts: options{ context: ".", configPath: "/config/image-builder-config.yaml", - dockerfile: "Dockerfile", + dockerfile: "dockerfile", logDir: "/logs/artifacts", }, expectedErr: true, @@ -174,14 +232,14 @@ func TestFlags(t *testing.T) { }, context: "prow/build", configPath: "config.yaml", - dockerfile: "Dockerfile", + dockerfile: "dockerfile", logDir: "prow/logs", orgRepo: "kyma-project/test-infra", silent: true, }, args: []string{ "--config=config.yaml", - "--dockerfile=Dockerfile", + "--dockerfile=dockerfile", "--repo=kyma-project/test-infra", "--name=test-image", "--tag=latest", @@ -196,7 +254,7 @@ func TestFlags(t *testing.T) { expectedOpts: options{ context: ".", configPath: "/config/image-builder-config.yaml", - dockerfile: "Dockerfile", + dockerfile: "dockerfile", logDir: "/logs/artifacts", exportTags: true, }, @@ -209,7 +267,7 @@ func TestFlags(t *testing.T) { expectedOpts: options{ context: ".", configPath: "/config/image-builder-config.yaml", - dockerfile: "Dockerfile", + dockerfile: "dockerfile", logDir: "/logs/artifacts", buildArgs: sets.Tags{ tags.Tag{Name: "BIN", Value: "test"}, @@ -500,3 +558,11 @@ func Test_appendMissing(t *testing.T) { } } } + +type mockSigner struct { + signFunc func([]string) error +} + +func (m *mockSigner) Sign(images []string) error { + return m.signFunc(images) +} diff --git a/configs/kaniko-build-config.yaml b/configs/kaniko-build-config.yaml index dab1edaffd1e..6b7a4c06b18e 100644 --- a/configs/kaniko-build-config.yaml +++ b/configs/kaniko-build-config.yaml @@ -5,6 +5,10 @@ dev-registry: - europe-docker.pkg.dev/kyma-project/dev reproducible: false log-format: json +ado-config: + ado-organization-url: https://dev.azure.com/hyperspace-pipelines + ado-project-name: kyma + ado-pipeline-id: 14902 cache: enabled: true cache-repo: europe-docker.pkg.dev/kyma-project/cache/cache diff --git a/go.mod b/go.mod index 159c3e3c6246..303818c51cf1 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/imdario/mergo v0.3.16 github.com/jamiealquiza/envy v1.1.0 github.com/jinzhu/copier v0.4.0 + github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0 github.com/onsi/ginkgo/v2 v2.13.2 github.com/onsi/gomega v1.30.0 github.com/pkg/errors v0.9.1 @@ -43,6 +44,7 @@ require ( k8s.io/apimachinery v0.27.2 k8s.io/client-go v0.27.2 k8s.io/test-infra v0.0.0-20230719172213-72ce78578482 + k8s.io/utils v0.0.0-20230726121419-3b25d923346b sigs.k8s.io/yaml v1.4.0 ) @@ -129,6 +131,7 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mattn/go-zglob v0.0.2 // indirect @@ -153,7 +156,7 @@ require ( github.com/prometheus/procfs v0.9.0 // indirect github.com/prometheus/statsd_exporter v0.21.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rs/zerolog v1.26.1 // indirect + github.com/rs/zerolog v1.29.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shurcooL/githubv4 v0.0.0-20211117020012-5800b9de5b8b // indirect @@ -197,7 +200,6 @@ require ( k8s.io/component-base v0.27.2 // indirect k8s.io/klog/v2 v2.90.1 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect - k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect knative.dev/pkg v0.0.0-20230221145627-8efb3485adcf // indirect sigs.k8s.io/controller-runtime v0.15.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/go.sum b/go.sum index 0e51494cd1ca..fbf51b6d33f2 100644 --- a/go.sum +++ b/go.sum @@ -148,7 +148,7 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creachadair/staticfile v0.1.3/go.mod h1:a3qySzCIXEprDGxk6tSxSI+dBBdLzqeBOMhZ+o2d3pM= @@ -474,11 +474,14 @@ github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= @@ -490,6 +493,8 @@ github.com/mattn/go-zglob v0.0.2/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb44 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0 h1:mmJCWLe63QvybxhW1iBmQWEaCKdc4SKgALfTNZ+OphU= +github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0/go.mod h1:mDunUZ1IUJdJIRHvFb+LPBUtxe3AYB5MI6BMXNg8194= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -575,9 +580,9 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= -github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= +github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= @@ -655,7 +660,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zricethezav/gitleaks/v8 v8.18.1 h1:Jlv3Pu6dritmro5Cy0rbBGW7fvP7izn3NMqN4Dk4c2o= github.com/zricethezav/gitleaks/v8 v8.18.1/go.mod h1:8Dn6XSzCXjbkxc2e/o1M+dwIHPAoyY7HsYjLWzgg+Zs= go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= @@ -690,7 +694,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= @@ -857,7 +860,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc 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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -940,7 +943,6 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1136,8 +1138,8 @@ k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5F k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= k8s.io/test-infra v0.0.0-20230719172213-72ce78578482 h1:TbTu68BRJ8z63/pgbZUDnYWmScJYOuc3Ry1GM2xRQ84= k8s.io/test-infra v0.0.0-20230719172213-72ce78578482/go.mod h1:DuTZeR5hCmlTTyTNkyPtItq2lQZjdCcpB0nsxdT57Gw= -k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= -k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= knative.dev/pkg v0.0.0-20230221145627-8efb3485adcf h1:TwvZFDpkyqpK2OCAwvNGV2Zjk14FzIh8X8Ci/du3jYI= knative.dev/pkg v0.0.0-20230221145627-8efb3485adcf/go.mod h1:VO/fcEsq43seuONRQxZyftWHjpMabYzRHDtpSEQ/eoQ= pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= diff --git a/pkg/azuredevops/pipelines/mocks/mock_BuildClient.go b/pkg/azuredevops/pipelines/mocks/mock_BuildClient.go new file mode 100644 index 000000000000..c9a1c3eedb72 --- /dev/null +++ b/pkg/azuredevops/pipelines/mocks/mock_BuildClient.go @@ -0,0 +1,97 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package pipelinesmocks + +import ( + context "context" + + build "github.com/microsoft/azure-devops-go-api/azuredevops/v7/build" + + mock "github.com/stretchr/testify/mock" +) + +// MockBuildClient is an autogenerated mock type for the BuildClient type +type MockBuildClient struct { + mock.Mock +} + +type MockBuildClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockBuildClient) EXPECT() *MockBuildClient_Expecter { + return &MockBuildClient_Expecter{mock: &_m.Mock} +} + +// GetBuildLogs provides a mock function with given fields: ctx, args +func (_m *MockBuildClient) GetBuildLogs(ctx context.Context, args build.GetBuildLogsArgs) (*[]build.BuildLog, error) { + ret := _m.Called(ctx, args) + + if len(ret) == 0 { + panic("no return value specified for GetBuildLogs") + } + + var r0 *[]build.BuildLog + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, build.GetBuildLogsArgs) (*[]build.BuildLog, error)); ok { + return rf(ctx, args) + } + if rf, ok := ret.Get(0).(func(context.Context, build.GetBuildLogsArgs) *[]build.BuildLog); ok { + r0 = rf(ctx, args) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*[]build.BuildLog) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, build.GetBuildLogsArgs) error); ok { + r1 = rf(ctx, args) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockBuildClient_GetBuildLogs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBuildLogs' +type MockBuildClient_GetBuildLogs_Call struct { + *mock.Call +} + +// GetBuildLogs is a helper method to define mock.On call +// - ctx context.Context +// - args build.GetBuildLogsArgs +func (_e *MockBuildClient_Expecter) GetBuildLogs(ctx interface{}, args interface{}) *MockBuildClient_GetBuildLogs_Call { + return &MockBuildClient_GetBuildLogs_Call{Call: _e.mock.On("GetBuildLogs", ctx, args)} +} + +func (_c *MockBuildClient_GetBuildLogs_Call) Run(run func(ctx context.Context, args build.GetBuildLogsArgs)) *MockBuildClient_GetBuildLogs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(build.GetBuildLogsArgs)) + }) + return _c +} + +func (_c *MockBuildClient_GetBuildLogs_Call) Return(_a0 *[]build.BuildLog, _a1 error) *MockBuildClient_GetBuildLogs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockBuildClient_GetBuildLogs_Call) RunAndReturn(run func(context.Context, build.GetBuildLogsArgs) (*[]build.BuildLog, error)) *MockBuildClient_GetBuildLogs_Call { + _c.Call.Return(run) + return _c +} + +// NewMockBuildClient creates a new instance of MockBuildClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockBuildClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockBuildClient { + mock := &MockBuildClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/azuredevops/pipelines/mocks/mock_Client.go b/pkg/azuredevops/pipelines/mocks/mock_Client.go new file mode 100644 index 000000000000..16794aa87cee --- /dev/null +++ b/pkg/azuredevops/pipelines/mocks/mock_Client.go @@ -0,0 +1,155 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package pipelinesmocks + +import ( + context "context" + + pipelines "github.com/microsoft/azure-devops-go-api/azuredevops/v7/pipelines" + mock "github.com/stretchr/testify/mock" +) + +// MockClient is an autogenerated mock type for the Client type +type MockClient struct { + mock.Mock +} + +type MockClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockClient) EXPECT() *MockClient_Expecter { + return &MockClient_Expecter{mock: &_m.Mock} +} + +// GetRun provides a mock function with given fields: ctx, args +func (_m *MockClient) GetRun(ctx context.Context, args pipelines.GetRunArgs) (*pipelines.Run, error) { + ret := _m.Called(ctx, args) + + if len(ret) == 0 { + panic("no return value specified for GetRun") + } + + var r0 *pipelines.Run + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, pipelines.GetRunArgs) (*pipelines.Run, error)); ok { + return rf(ctx, args) + } + if rf, ok := ret.Get(0).(func(context.Context, pipelines.GetRunArgs) *pipelines.Run); ok { + r0 = rf(ctx, args) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*pipelines.Run) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, pipelines.GetRunArgs) error); ok { + r1 = rf(ctx, args) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockClient_GetRun_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRun' +type MockClient_GetRun_Call struct { + *mock.Call +} + +// GetRun is a helper method to define mock.On call +// - ctx context.Context +// - args pipelines.GetRunArgs +func (_e *MockClient_Expecter) GetRun(ctx interface{}, args interface{}) *MockClient_GetRun_Call { + return &MockClient_GetRun_Call{Call: _e.mock.On("GetRun", ctx, args)} +} + +func (_c *MockClient_GetRun_Call) Run(run func(ctx context.Context, args pipelines.GetRunArgs)) *MockClient_GetRun_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(pipelines.GetRunArgs)) + }) + return _c +} + +func (_c *MockClient_GetRun_Call) Return(_a0 *pipelines.Run, _a1 error) *MockClient_GetRun_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockClient_GetRun_Call) RunAndReturn(run func(context.Context, pipelines.GetRunArgs) (*pipelines.Run, error)) *MockClient_GetRun_Call { + _c.Call.Return(run) + return _c +} + +// RunPipeline provides a mock function with given fields: ctx, args +func (_m *MockClient) RunPipeline(ctx context.Context, args pipelines.RunPipelineArgs) (*pipelines.Run, error) { + ret := _m.Called(ctx, args) + + if len(ret) == 0 { + panic("no return value specified for RunPipeline") + } + + var r0 *pipelines.Run + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, pipelines.RunPipelineArgs) (*pipelines.Run, error)); ok { + return rf(ctx, args) + } + if rf, ok := ret.Get(0).(func(context.Context, pipelines.RunPipelineArgs) *pipelines.Run); ok { + r0 = rf(ctx, args) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*pipelines.Run) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, pipelines.RunPipelineArgs) error); ok { + r1 = rf(ctx, args) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockClient_RunPipeline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RunPipeline' +type MockClient_RunPipeline_Call struct { + *mock.Call +} + +// RunPipeline is a helper method to define mock.On call +// - ctx context.Context +// - args pipelines.RunPipelineArgs +func (_e *MockClient_Expecter) RunPipeline(ctx interface{}, args interface{}) *MockClient_RunPipeline_Call { + return &MockClient_RunPipeline_Call{Call: _e.mock.On("RunPipeline", ctx, args)} +} + +func (_c *MockClient_RunPipeline_Call) Run(run func(ctx context.Context, args pipelines.RunPipelineArgs)) *MockClient_RunPipeline_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(pipelines.RunPipelineArgs)) + }) + return _c +} + +func (_c *MockClient_RunPipeline_Call) Return(_a0 *pipelines.Run, _a1 error) *MockClient_RunPipeline_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockClient_RunPipeline_Call) RunAndReturn(run func(context.Context, pipelines.RunPipelineArgs) (*pipelines.Run, error)) *MockClient_RunPipeline_Call { + _c.Call.Return(run) + return _c +} + +// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockClient { + mock := &MockClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/azuredevops/pipelines/mocks/mock_HTTPClient.go b/pkg/azuredevops/pipelines/mocks/mock_HTTPClient.go new file mode 100644 index 000000000000..f05fd7bddd80 --- /dev/null +++ b/pkg/azuredevops/pipelines/mocks/mock_HTTPClient.go @@ -0,0 +1,94 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package pipelinesmocks + +import ( + http "net/http" + + mock "github.com/stretchr/testify/mock" +) + +// MockHTTPClient is an autogenerated mock type for the HTTPClient type +type MockHTTPClient struct { + mock.Mock +} + +type MockHTTPClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockHTTPClient) EXPECT() *MockHTTPClient_Expecter { + return &MockHTTPClient_Expecter{mock: &_m.Mock} +} + +// Do provides a mock function with given fields: req +func (_m *MockHTTPClient) Do(req *http.Request) (*http.Response, error) { + ret := _m.Called(req) + + if len(ret) == 0 { + panic("no return value specified for Do") + } + + var r0 *http.Response + var r1 error + if rf, ok := ret.Get(0).(func(*http.Request) (*http.Response, error)); ok { + return rf(req) + } + if rf, ok := ret.Get(0).(func(*http.Request) *http.Response); ok { + r0 = rf(req) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*http.Response) + } + } + + if rf, ok := ret.Get(1).(func(*http.Request) error); ok { + r1 = rf(req) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockHTTPClient_Do_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Do' +type MockHTTPClient_Do_Call struct { + *mock.Call +} + +// Do is a helper method to define mock.On call +// - req *http.Request +func (_e *MockHTTPClient_Expecter) Do(req interface{}) *MockHTTPClient_Do_Call { + return &MockHTTPClient_Do_Call{Call: _e.mock.On("Do", req)} +} + +func (_c *MockHTTPClient_Do_Call) Run(run func(req *http.Request)) *MockHTTPClient_Do_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*http.Request)) + }) + return _c +} + +func (_c *MockHTTPClient_Do_Call) Return(_a0 *http.Response, _a1 error) *MockHTTPClient_Do_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockHTTPClient_Do_Call) RunAndReturn(run func(*http.Request) (*http.Response, error)) *MockHTTPClient_Do_Call { + _c.Call.Return(run) + return _c +} + +// NewMockHTTPClient creates a new instance of MockHTTPClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockHTTPClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockHTTPClient { + mock := &MockHTTPClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/azuredevops/pipelines/pipelines.go b/pkg/azuredevops/pipelines/pipelines.go new file mode 100644 index 000000000000..896b057a4e7c --- /dev/null +++ b/pkg/azuredevops/pipelines/pipelines.go @@ -0,0 +1,127 @@ +// Package pipelines provides a clients for calling Azure DevOps pipelines API +// TODO: Add more structured logging with debug severity to track execution in case of troubleshooting +package pipelines + +import ( + "fmt" + "io" + "net/http" + "time" + + adov7 "github.com/microsoft/azure-devops-go-api/azuredevops/v7" + "github.com/microsoft/azure-devops-go-api/azuredevops/v7/build" + "github.com/microsoft/azure-devops-go-api/azuredevops/v7/pipelines" + "golang.org/x/net/context" + "k8s.io/utils/ptr" +) + +type Client interface { + RunPipeline(ctx context.Context, args pipelines.RunPipelineArgs) (*pipelines.Run, error) + GetRun(ctx context.Context, args pipelines.GetRunArgs) (*pipelines.Run, error) +} + +type BuildClient interface { + GetBuildLogs(ctx context.Context, args build.GetBuildLogsArgs) (*[]build.BuildLog, error) +} + +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +type Config struct { + // ADO organization URL to call for triggering ADO pipeline + ADOOrganizationURL string `yaml:"ado-organization-url" json:"ado-organization-url"` + // ADO project name to call for triggering ADO pipeline + ADOProjectName string `yaml:"ado-project-name" json:"ado-project-name"` + // ADO pipeline ID to call for triggering ADO pipeline + ADOPipelineID int `yaml:"ado-pipeline-id" json:"ado-pipeline-id"` + // ADO pipeline version to call for triggering ADO pipeline + ADOPipelineVersion int `yaml:"ado-pipeline-version,omitempty" json:"ado-pipeline-version,omitempty"` +} + +func (c Config) GetADOConfig() Config { + return c +} + +// TODO: write tests which use BeAssignableToTypeOf matcher https://onsi.github.io/gomega/#beassignabletotypeofexpected-interface +func NewClient(adoOrganizationURL, adoPAT string) Client { + adoConnection := adov7.NewPatConnection(adoOrganizationURL, adoPAT) + ctx := context.Background() + return pipelines.NewClient(ctx, adoConnection) +} + +// TODO: write tests which use BeAssignableToTypeOf matcher https://onsi.github.io/gomega/#beassignabletotypeofexpected-interface +func NewBuildClient(adoOrganizationURL, adoPAT string) (BuildClient, error) { + buildConnection := adov7.NewPatConnection(adoOrganizationURL, adoPAT) + ctx := context.Background() + return build.NewClient(ctx, buildConnection) +} + +// TODO: implement sleep parameter to be passed as a functional option +func GetRunResult(ctx context.Context, adoClient Client, adoConfig Config, pipelineRunID *int, sleep time.Duration) (*pipelines.RunResult, error) { + for { + time.Sleep(sleep) + pipelineRun, err := adoClient.GetRun(ctx, pipelines.GetRunArgs{ + Project: &adoConfig.ADOProjectName, + PipelineId: &adoConfig.ADOPipelineID, + RunId: pipelineRunID, + }) + if err != nil { + return nil, fmt.Errorf("failed getting ADO pipeline run, err: %w", err) + } + if *pipelineRun.State == pipelines.RunStateValues.Completed { + return pipelineRun.Result, nil + } + // TODO: use structured logging with info severity + fmt.Printf("Pipeline run still in progress. Waiting for %s\n", sleep) + } +} + +func GetRunLogs(ctx context.Context, buildClient BuildClient, httpClient HTTPClient, adoConfig Config, pipelineRunID *int, adoPAT string) (string, error) { + buildLogs, err := buildClient.GetBuildLogs(ctx, build.GetBuildLogsArgs{ + Project: &adoConfig.ADOProjectName, + BuildId: pipelineRunID, + }) + if err != nil { + return "", fmt.Errorf("failed getting build logs metadata, err: %w", err) + } + + // Last item in a list represent logs from all pipeline steps visible in ADO GUI + lastLog := (*buildLogs)[len(*buildLogs)-1] + req, err := http.NewRequest("GET", *lastLog.Url, nil) + if err != nil { + return "", fmt.Errorf("failed creating http request getting build log, err: %w", err) + } + req.SetBasicAuth("", adoPAT) + // TODO: implement checking http response status code, if it's not 2xx, return error + resp, err := httpClient.Do(req) + if err != nil { + return "", fmt.Errorf("failed http request getting build log, err: %w", err) + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("failed reading http body with build log, err: %w", err) + } + err = resp.Body.Close() + if err != nil { + return "", fmt.Errorf("failed closing http body with build log, err: %w", err) + } + return string(body), nil +} + +func Run(ctx context.Context, adoClient Client, templateParameters map[string]string, adoConfig Config) (*pipelines.Run, error) { + adoRunPipelineArgs := pipelines.RunPipelineArgs{ + Project: &adoConfig.ADOProjectName, + PipelineId: &adoConfig.ADOPipelineID, + RunParameters: &pipelines.RunPipelineParameters{ + PreviewRun: ptr.To(false), + TemplateParameters: &templateParameters, + }, + } + if adoConfig.ADOPipelineVersion != 0 { + adoRunPipelineArgs.PipelineVersion = &adoConfig.ADOPipelineVersion + } + // TODO: use structured logging with debug severity + fmt.Printf("Using TemplateParameters: %+v\n", adoRunPipelineArgs.RunParameters.TemplateParameters) + return adoClient.RunPipeline(ctx, adoRunPipelineArgs) +} diff --git a/pkg/azuredevops/pipelines/pipelines_suite_test.go b/pkg/azuredevops/pipelines/pipelines_suite_test.go new file mode 100644 index 000000000000..21a0dce713f1 --- /dev/null +++ b/pkg/azuredevops/pipelines/pipelines_suite_test.go @@ -0,0 +1,13 @@ +package pipelines_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestPipelines(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Pipelines Suite") +} diff --git a/pkg/azuredevops/pipelines/pipelines_test.go b/pkg/azuredevops/pipelines/pipelines_test.go new file mode 100644 index 000000000000..d0414ef59eaa --- /dev/null +++ b/pkg/azuredevops/pipelines/pipelines_test.go @@ -0,0 +1,215 @@ +package pipelines_test + +import ( + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "context" + "fmt" + "io" + "net/http" + "strings" + + "github.com/kyma-project/test-infra/pkg/azuredevops/pipelines" + "github.com/kyma-project/test-infra/pkg/azuredevops/pipelines/mocks" + + "github.com/microsoft/azure-devops-go-api/azuredevops/v7/build" + adoPipelines "github.com/microsoft/azure-devops-go-api/azuredevops/v7/pipelines" + "github.com/stretchr/testify/mock" + "k8s.io/utils/ptr" +) + +type ginkgoT struct { + GinkgoTInterface +} + +func (t ginkgoT) Cleanup(f func()) { + f() +} + +var _ = Describe("Pipelines", func() { + var ( + ctx context.Context + mockADOClient *pipelinesmocks.MockClient + adoConfig pipelines.Config + t ginkgoT + ) + + BeforeEach(func() { + ctx = context.Background() + t = ginkgoT{} + t.GinkgoTInterface = GinkgoT() + mockADOClient = pipelinesmocks.NewMockClient(t) + adoConfig = pipelines.Config{ + ADOOrganizationURL: "https://dev.azure.com", + ADOProjectName: "example-project", + ADOPipelineID: 123, + ADOPipelineVersion: 1, + } + }) + + Describe("GetRunResult", func() { + var ( + runArgs adoPipelines.GetRunArgs + mockRunInProgress *adoPipelines.Run + mockRunSucceeded *adoPipelines.Run + ) + + BeforeEach(func() { + runArgs = adoPipelines.GetRunArgs{ + Project: &adoConfig.ADOProjectName, + PipelineId: &adoConfig.ADOPipelineID, + RunId: ptr.To(42), + } + mockRunInProgress = &adoPipelines.Run{State: &adoPipelines.RunStateValues.InProgress} + mockRunSucceeded = &adoPipelines.Run{State: &adoPipelines.RunStateValues.Completed, Result: &adoPipelines.RunResultValues.Succeeded} + }) + + It("should return the pipeline run result succeeded", func() { + mockADOClient.On("GetRun", ctx, runArgs).Return(mockRunSucceeded, nil) + + result, err := pipelines.GetRunResult(ctx, mockADOClient, adoConfig, ptr.To(42), 3*time.Second) + + Expect(err).ToNot(HaveOccurred()) + Expect(result).To(Equal(&adoPipelines.RunResultValues.Succeeded)) + mockADOClient.AssertCalled(t, "GetRun", ctx, runArgs) + mockADOClient.AssertNumberOfCalls(t, "GetRun", 1) + mockADOClient.AssertExpectations(GinkgoT()) + }) + + It("should handle pipeline still in progress", func() { + mockADOClient.On("GetRun", ctx, runArgs).Return(mockRunInProgress, nil).Once() + mockADOClient.On("GetRun", ctx, runArgs).Return(mockRunSucceeded, nil).Once() + + result, err := pipelines.GetRunResult(ctx, mockADOClient, adoConfig, ptr.To(42), 3*time.Second) + + Expect(err).ToNot(HaveOccurred()) + Expect(result).To(Equal(&adoPipelines.RunResultValues.Succeeded)) + mockADOClient.AssertCalled(t, "GetRun", ctx, runArgs) + mockADOClient.AssertNumberOfCalls(t, "GetRun", 2) + mockADOClient.AssertExpectations(GinkgoT()) + }) + + It("should handle ADO client error", func() { + mockADOClient.On("GetRun", ctx, runArgs).Return(nil, fmt.Errorf("ADO client error")) + + _, err := pipelines.GetRunResult(ctx, mockADOClient, adoConfig, ptr.To(42), 3*time.Second) + + Expect(err).To(HaveOccurred()) + mockADOClient.AssertCalled(t, "GetRun", ctx, runArgs) + mockADOClient.AssertNumberOfCalls(t, "GetRun", 1) + mockADOClient.AssertExpectations(GinkgoT()) + }) + }) + + Describe("GetRunLogs", func() { + var ( + mockBuildClient *pipelinesmocks.MockBuildClient + mockHTTPClient *pipelinesmocks.MockHTTPClient + getBuildLogsArgs build.GetBuildLogsArgs + mockBuildLogs *[]build.BuildLog + ) + + BeforeEach(func() { + mockBuildClient = pipelinesmocks.NewMockBuildClient(t) + mockHTTPClient = pipelinesmocks.NewMockHTTPClient(t) + getBuildLogsArgs = build.GetBuildLogsArgs{ + Project: &adoConfig.ADOProjectName, + BuildId: ptr.To(42), + } + mockBuildLogs = &[]build.BuildLog{{Url: ptr.To("https://example.com/log")}} + }) + + // TODO: Need a test for HTTP response status code != 2xx + // TODO: Need a tests for other errors returned by GetBuildLogs. + It("should return build logs", func() { + mockBuildClient.On("GetBuildLogs", ctx, getBuildLogsArgs).Return(mockBuildLogs, nil) + mockHTTPClient.On("Do", mock.AnythingOfType("*http.Request")).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader("log content")), + }, nil) + + logs, err := pipelines.GetRunLogs(ctx, mockBuildClient, mockHTTPClient, adoConfig, ptr.To(42), "somePAT") + + Expect(err).ToNot(HaveOccurred()) + Expect(logs).To(Equal("log content")) + mockBuildClient.AssertCalled(t, "GetBuildLogs", ctx, getBuildLogsArgs) + mockBuildClient.AssertNumberOfCalls(t, "GetBuildLogs", 1) + mockBuildClient.AssertExpectations(GinkgoT()) + mockHTTPClient.AssertCalled(t, "Do", mock.AnythingOfType("*http.Request")) + mockHTTPClient.AssertNumberOfCalls(t, "Do", 1) + mockHTTPClient.AssertExpectations(GinkgoT()) + }) + + It("should handle build client error", func() { + mockBuildClient.On("GetBuildLogs", ctx, getBuildLogsArgs).Return(nil, fmt.Errorf("build client error")) + + _, err := pipelines.GetRunLogs(ctx, mockBuildClient, mockHTTPClient, adoConfig, ptr.To(42), "somePAT") + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError("failed getting build logs metadata, err: build client error")) + mockBuildClient.AssertCalled(t, "GetBuildLogs", ctx, getBuildLogsArgs) + mockBuildClient.AssertNumberOfCalls(t, "GetBuildLogs", 1) + mockBuildClient.AssertExpectations(GinkgoT()) + mockHTTPClient.AssertNotCalled(t, "Do", mock.AnythingOfType("*http.Request")) + mockHTTPClient.AssertNumberOfCalls(t, "Do", 0) + mockHTTPClient.AssertExpectations(GinkgoT()) + }) + + It("should handle HTTP request error", func() { + mockBuildClient.On("GetBuildLogs", ctx, getBuildLogsArgs).Return(mockBuildLogs, nil) + mockHTTPClient.On("Do", mock.AnythingOfType("*http.Request")).Return(nil, fmt.Errorf("HTTP request error")) + + _, err := pipelines.GetRunLogs(ctx, mockBuildClient, mockHTTPClient, adoConfig, ptr.To(42), "somePAT") + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError("failed http request getting build log, err: HTTP request error")) + mockBuildClient.AssertCalled(t, "GetBuildLogs", ctx, getBuildLogsArgs) + mockBuildClient.AssertNumberOfCalls(t, "GetBuildLogs", 1) + mockBuildClient.AssertExpectations(GinkgoT()) + mockHTTPClient.AssertCalled(t, "Do", mock.AnythingOfType("*http.Request")) + mockHTTPClient.AssertNumberOfCalls(t, "Do", 1) + mockHTTPClient.AssertExpectations(GinkgoT()) + }) + }) + + Describe("Run", func() { + var ( + templateParams map[string]string + runPipelineArgs adoPipelines.RunPipelineArgs + ) + + BeforeEach(func() { + templateParams = map[string]string{"param1": "value1", "param2": "value2"} + runPipelineArgs = adoPipelines.RunPipelineArgs{ + Project: &adoConfig.ADOProjectName, + PipelineId: &adoConfig.ADOPipelineID, + RunParameters: &adoPipelines.RunPipelineParameters{ + PreviewRun: ptr.To(false), + TemplateParameters: &templateParams, + }, + PipelineVersion: &adoConfig.ADOPipelineVersion, + } + }) + + It("should run the pipeline", func() { + mockRun := &adoPipelines.Run{Id: ptr.To(123)} + mockADOClient.On("RunPipeline", ctx, runPipelineArgs).Return(mockRun, nil) + + run, err := pipelines.Run(ctx, mockADOClient, templateParams, adoConfig) + Expect(err).ToNot(HaveOccurred()) + Expect(run.Id).To(Equal(ptr.To(123))) + mockADOClient.AssertExpectations(GinkgoT()) + }) + + It("should handle ADO client error", func() { + mockADOClient.On("RunPipeline", ctx, runPipelineArgs).Return(nil, fmt.Errorf("ADO client error")) + + _, err := pipelines.Run(ctx, mockADOClient, templateParams, adoConfig) + Expect(err).To(HaveOccurred()) + mockADOClient.AssertExpectations(GinkgoT()) + }) + }) +}) diff --git a/pkg/azuredevops/pipelines/templatesParams.go b/pkg/azuredevops/pipelines/templatesParams.go new file mode 100644 index 000000000000..a2617cb9d237 --- /dev/null +++ b/pkg/azuredevops/pipelines/templatesParams.go @@ -0,0 +1,122 @@ +// TODO: Add more structured logging with debug severity to track execution in case of troubleshooting + +package pipelines + +import ( + "fmt" + "strconv" +) + +// ErrRequiredParamNotSet is returned when required parameter is not set +type ErrRequiredParamNotSet string + +func (e ErrRequiredParamNotSet) Error() string { + return "required parameter not set: " + string(e) +} + +// OCIImageBuilderTemplateParams is a map of parameters for OCIImageBuilderTemplate +type OCIImageBuilderTemplateParams map[string]string + +// SetRepoName sets required parameter RepoName +func (p OCIImageBuilderTemplateParams) SetRepoName(repo string) { + p["RepoName"] = repo +} + +// SetRepoOwner sets required parameter RepoOwner +func (p OCIImageBuilderTemplateParams) SetRepoOwner(owner string) { + p["RepoOwner"] = owner +} + +// SetPresubmitJobType sets required parameter JobType to presubmit. +func (p OCIImageBuilderTemplateParams) SetPresubmitJobType() { + p["JobType"] = "presubmit" +} + +// SetPostsubmitJobType sets required parameter JobType to postsubmit. +func (p OCIImageBuilderTemplateParams) SetPostsubmitJobType() { + p["JobType"] = "postsubmit" +} + +// SetPullNumber sets optional parameter PullNumber. +func (p OCIImageBuilderTemplateParams) SetPullNumber(number string) { + p["PullNumber"] = number +} + +// SetBaseSHA sets required parameter BaseSHA. +func (p OCIImageBuilderTemplateParams) SetBaseSHA(sha string) { + // TODO: Rename key to BaseSHA + p["PullBaseSHA"] = sha +} + +// SetPullSHA sets optional parameter PullSHA. +func (p OCIImageBuilderTemplateParams) SetPullSHA(sha string) { + // TODO: Rename key to PullSHA + p["PullPullSHA"] = sha +} + +// SetImageName sets required parameter ImageName. +func (p OCIImageBuilderTemplateParams) SetImageName(name string) { + // TODO: Rename key to ImageName + p["Name"] = name +} + +// SetDockerfilePath sets required parameter DockerfilePath. +func (p OCIImageBuilderTemplateParams) SetDockerfilePath(path string) { + // TODO: Rename key to DockerfilePath + p["Dockerfile"] = path +} + +// SetBuildContext sets required parameter BuildContext. +func (p OCIImageBuilderTemplateParams) SetBuildContext(context string) { + // TODO: Rename key to BuildContext + p["Context"] = context +} + +// SetExportTags sets optional parameter ExportTags. +func (p OCIImageBuilderTemplateParams) SetExportTags(export bool) { + p["ExportTags"] = strconv.FormatBool(export) +} + +// SetBuildArgs sets optional parameter BuildArgs. +func (p OCIImageBuilderTemplateParams) SetBuildArgs(args string) { + p["BuildArgs"] = args +} + +// SetImageTags sets optional parameter ImageTags. +func (p OCIImageBuilderTemplateParams) SetImageTags(tags string) { + // TODO: Rename key to ImageTags + p["Tags"] = tags +} + +// Validate validates if required OCIImageBuilderTemplateParams are set +func (p OCIImageBuilderTemplateParams) Validate() error { + var ( + jobType string + ok bool + ) + if _, ok = p["RepoName"]; !ok { + return ErrRequiredParamNotSet("RepoName") + } + if _, ok = p["RepoOwner"]; !ok { + return ErrRequiredParamNotSet("RepoOwner") + } + if jobType, ok = p["JobType"]; !ok { + return ErrRequiredParamNotSet("JobType") + } + if jobType != "presubmit" && jobType != "postsubmit" { + return fmt.Errorf("JobType must be either presubmit or postsubmit, got: %s", jobType) + } + if _, ok = p["PullBaseSHA"]; !ok { + return ErrRequiredParamNotSet("BaseSHA") + } + if _, ok = p["Name"]; !ok { + return ErrRequiredParamNotSet("ImageName") + } + if _, ok = p["Dockerfile"]; !ok { + return ErrRequiredParamNotSet("DockerfilePath") + } + if _, ok = p["Context"]; !ok { + return ErrRequiredParamNotSet("BuildContext") + } + return nil +} diff --git a/pkg/azuredevops/pipelines/templatesParams_test.go b/pkg/azuredevops/pipelines/templatesParams_test.go new file mode 100644 index 000000000000..be9b0ccbffd8 --- /dev/null +++ b/pkg/azuredevops/pipelines/templatesParams_test.go @@ -0,0 +1,108 @@ +package pipelines_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/kyma-project/test-infra/pkg/azuredevops/pipelines" +) + +var _ = Describe("Test OCIImageBuilderTemplateParams", func() { + var params pipelines.OCIImageBuilderTemplateParams + + BeforeEach(func() { + params = make(map[string]string) + }) + + It("sets the correct RepoName", func() { + params.SetRepoName("testName") + Expect(params["RepoName"]).To(Equal("testName")) + }) + + It("sets the correct RepoOwner", func() { + params.SetRepoOwner("testOwner") + Expect(params["RepoOwner"]).To(Equal("testOwner")) + }) + + It("sets the correct JobType to presubmit", func() { + params.SetPresubmitJobType() + Expect(params["JobType"]).To(Equal("presubmit")) + }) + + It("sets the correct JobType to postsubmit", func() { + params.SetPostsubmitJobType() + Expect(params["JobType"]).To(Equal("postsubmit")) + }) + + It("sets the correct PullNumber", func() { + params.SetPullNumber("123") + Expect(params["PullNumber"]).To(Equal("123")) + }) + + It("sets the correct BaseSHA", func() { + params.SetBaseSHA("abc") + Expect(params["PullBaseSHA"]).To(Equal("abc")) + }) + + It("sets the correct PullSHA", func() { + params.SetPullSHA("def") + Expect(params["PullPullSHA"]).To(Equal("def")) + }) + + It("sets the correct ImageName", func() { + params.SetImageName("my-image") + Expect(params["Name"]).To(Equal("my-image")) + }) + + It("sets the correct DockerfilePath", func() { + params.SetDockerfilePath("/path/to/dockerfile") + Expect(params["Dockerfile"]).To(Equal("/path/to/dockerfile")) + }) + + It("sets the correct BuildContext", func() { + params.SetBuildContext("/path/to/context") + Expect(params["Context"]).To(Equal("/path/to/context")) + }) + + It("sets the correct ExportTags", func() { + params.SetExportTags(true) + Expect(params["ExportTags"]).To(Equal("true")) + }) + + It("sets the correct BuildArgs", func() { + params.SetBuildArgs("arg1 arg2") + Expect(params["BuildArgs"]).To(Equal("arg1 arg2")) + }) + + It("sets the correct ImageTags", func() { + params.SetImageTags("tag1 tag2") + Expect(params["Tags"]).To(Equal("tag1 tag2")) + }) + + // TODO: Improve assertions with more specific matchers and values. + It("validates the params correctly", func() { + // Set all required parameters + params.SetRepoName("testName") + params.SetRepoOwner("testOwner") + params.SetPresubmitJobType() + params.SetBaseSHA("abc") + params.SetImageName("my-image") + params.SetDockerfilePath("/path/to/dockerfile") + params.SetBuildContext("/path/to/context") + + err := params.Validate() + Expect(err).To(BeNil()) + }) + + It("returns error if parameters are not set", func() { + err := params.Validate() + Expect(err).NotTo(BeNil()) + }) + + It("returns error if JobType is not presubmit or postsubmit", func() { + params["JobType"] = "otherType" + + err := params.Validate() + Expect(err).NotTo(BeNil()) + }) +}) diff --git a/prow/jobs/kyma-project/test-infra/images.yaml b/prow/jobs/kyma-project/test-infra/images.yaml index bce35eacd2e9..fda5bd19fe65 100644 --- a/prow/jobs/kyma-project/test-infra/images.yaml +++ b/prow/jobs/kyma-project/test-infra/images.yaml @@ -1,5 +1,52 @@ presubmits: # runs on PRs kyma-project/test-infra: + - name: pull-build-image-builder + annotations: + description: "build image-builder image" + owner: "neighbors" + labels: + prow.k8s.io/pubsub.project: "sap-kyma-prow" + prow.k8s.io/pubsub.runID: "pull-build-image-builder" + prow.k8s.io/pubsub.topic: "prowjobs" + preset-sa-kyma-push-images: "true" + run_if_changed: ^development/.*.go|cmd/.*.go|^go.mod + decorate: true + cluster: untrusted-workload + max_concurrency: 10 + spec: + containers: + - image: "eu.gcr.io/sap-kyma-neighbors-dev/image-builder:v20230313-8dfce5f0b" + securityContext: + privileged: false + seccompProfile: + type: RuntimeDefault + allowPrivilegeEscalation: false + command: + - "/image-builder" + args: + - "--name=image-builder" + - "--config=/config/kaniko-build-config.yaml" + - "--context=." + - "--dockerfile=cmd/image-builder/images/kaniko/Dockerfile" + - "--export-tags=true" + resources: + requests: + memory: 500Mi + cpu: 500m + volumeMounts: + - name: config + mountPath: /config + readOnly: true + - name: signify-secret + mountPath: /secret + readOnly: true + volumes: + - name: config + configMap: + name: kaniko-build-config + - name: signify-secret + secret: + secretName: signify-dev-secret - name: pull-main-build-testimages decorate: true labels: @@ -67,4 +114,4 @@ postsubmits: privileged: true seccompProfile: type: Unconfined - allowPrivilegeEscalation: true \ No newline at end of file + allowPrivilegeEscalation: true