diff --git a/cmd/bundle/main.go b/cmd/bundle/main.go index b51ce94172..27defff547 100644 --- a/cmd/bundle/main.go +++ b/cmd/bundle/main.go @@ -7,6 +7,7 @@ package main import ( "context" "fmt" + "io/ioutil" "log" "os" "path/filepath" @@ -21,14 +22,16 @@ import ( ) var flagValues struct { - image string - target string - secretPath string + image string + target string + secretPath string + imageDigest string } func init() { pflag.StringVar(&flagValues.image, "image", "", "Location of the bundle image (mandatory)") pflag.StringVar(&flagValues.target, "target", "/workspace/source", "The target directory to place the code") + pflag.StringVar(&flagValues.imageDigest, "result-file-image-digest", "", "A file to write the image digest") pflag.StringVar(&flagValues.secretPath, "secret-path", "", "A directory that contains access credentials (optional)") } @@ -57,15 +60,31 @@ func Do(ctx context.Context) error { } log.Printf("Pulling image %q", ref) - if err := bundle.PullAndUnpack( + + img, err := bundle.PullAndUnpack( ref, flagValues.target, remote.WithContext(ctx), - remote.WithAuth(auth)); err != nil { + remote.WithAuth(auth)) + if err != nil { return err } log.Printf("Image content was extracted to %s\n", flagValues.target) + + digest, err := (*img).Digest() + if err != nil { + return fmt.Errorf("digesting new image: %v", err) + } + + if flagValues.imageDigest != "" { + if err = ioutil.WriteFile( + flagValues.imageDigest, []byte(digest.String()), 0644, + ); err != nil { + return err + } + } + return nil } diff --git a/cmd/bundle/main_test.go b/cmd/bundle/main_test.go index 574ee1e4ff..a5aebc7e5b 100644 --- a/cmd/bundle/main_test.go +++ b/cmd/bundle/main_test.go @@ -11,6 +11,9 @@ import ( "os" "path/filepath" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -32,6 +35,40 @@ var _ = Describe("Bundle Loader", func() { f(path) } + withTempFile := func(pattern string, f func(filename string)) { + file, err := ioutil.TempFile(os.TempDir(), pattern) + Expect(err).ToNot(HaveOccurred()) + defer os.Remove(file.Name()) + + f(file.Name()) + } + + filecontent := func(path string) string { + data, err := ioutil.ReadFile(path) + Expect(err).ToNot(HaveOccurred()) + return string(data) + } + + getImage := func(tag name.Tag) v1.Image { + ref, err := name.ParseReference(tag.String()) + Expect(err).To(BeNil()) + + desc, err := remote.Get(ref) + Expect(err).To(BeNil()) + + img, err := desc.Image() + Expect(err).To(BeNil()) + + return img + } + + getImageDigest := func(tag name.Tag) v1.Hash { + digest, err := getImage(tag).Digest() + Expect(err).To(BeNil()) + + return digest + } + Context("Error cases", func() { It("should fail in case the image is not specified", func() { Expect(run( @@ -53,5 +90,23 @@ var _ = Describe("Bundle Loader", func() { Expect(filepath.Join(target, "LICENSE")).To(BeAnExistingFile()) }) }) + + It("should store image digest into file specified in --result-file-image-digest flags", func() { + withTempDir(func(target string) { + withTempFile("image-digest", func(filename string) { + Expect(run( + "--image", exampleImage, + "--target", target, + "--result-file-image-digest", + filename, + )).ToNot(HaveOccurred()) + + tag, err := name.NewTag(exampleImage) + Expect(err).To(BeNil()) + + Expect(filecontent(filename)).To(Equal(getImageDigest(tag).String())) + }) + }) + }) }) }) diff --git a/cmd/git/main.go b/cmd/git/main.go index a97f848f90..c11977f6d4 100644 --- a/cmd/git/main.go +++ b/cmd/git/main.go @@ -40,13 +40,14 @@ func (e ExitError) Error() string { } type settings struct { - url string - revision string - depth uint - target string - resultFileCommitSha string - secretPath string - skipValidation bool + url string + revision string + depth uint + target string + resultFileCommitSha string + resultFileCommitAuthor string + secretPath string + skipValidation bool } var flagValues settings @@ -64,6 +65,7 @@ func init() { pflag.StringVar(&flagValues.revision, "revision", "", "The revision of the Git repository to be cloned. Optional, defaults to the default branch.") pflag.StringVar(&flagValues.target, "target", "", "The target directory of the clone operation") pflag.StringVar(&flagValues.resultFileCommitSha, "result-file-commit-sha", "", "A file to write the commit sha to.") + pflag.StringVar(&flagValues.resultFileCommitAuthor, "result-file-commit-author", "", "A file to write the commit author to.") pflag.StringVar(&flagValues.secretPath, "secret-path", "", "A directory that contains a secret. Either username and password for basic authentication. Or a SSH private key and optionally a known hosts file. Optional.") // Optional flag to be able to override the default shallow clone depth, @@ -129,6 +131,19 @@ func runGitClone(ctx context.Context) error { } } + if flagValues.resultFileCommitAuthor != "" { + output, err := git(ctx, "-C", flagValues.target, "log", "-1", "--pretty=format:%an") + if err != nil { + return err + } + + if err = ioutil.WriteFile( + flagValues.resultFileCommitAuthor, []byte(output), 0644, + ); err != nil { + return err + } + } + return nil } diff --git a/cmd/git/main_test.go b/cmd/git/main_test.go index 7f2c3a1d2f..c582929075 100644 --- a/cmd/git/main_test.go +++ b/cmd/git/main_test.go @@ -331,4 +331,39 @@ var _ = Describe("Git Resource", func() { }) }) }) + + Context("store result", func() { + const exampleRepo = "https://github.com/shipwright-io/sample-go" + + It("should store commit-sha into file specified in --result-file-commit-sha flag", func() { + withTempFile("commit-sha", func(filename string) { + withTempDir(func(target string) { + Expect(run( + "--url", exampleRepo, + "--target", target, + "--revision", "v0.1.0", + "--result-file-commit-sha", filename, + )).ToNot(HaveOccurred()) + + Expect(filecontent(filename)).To(Equal("8016b0437a7a09079f961e5003e81e5ad54e6c26")) + }) + }) + }) + + It("should store commit-author into file specified in --result-file-commit-author flag", func() { + withTempFile("commit-author", func(filename string) { + + withTempDir(func(target string) { + Expect(run( + "--url", exampleRepo, + "--target", target, + "--revision", "v0.1.0", + "--result-file-commit-author", filename, + )).ToNot(HaveOccurred()) + + Expect(filecontent(filename)).To(Equal("Enrique Encalada")) + }) + }) + }) + }) }) diff --git a/deploy/crds/shipwright.io_buildruns.yaml b/deploy/crds/shipwright.io_buildruns.yaml index ec30e601cf..581647c6ff 100644 --- a/deploy/crds/shipwright.io_buildruns.yaml +++ b/deploy/crds/shipwright.io_buildruns.yaml @@ -309,6 +309,45 @@ spec: latestTaskRunRef: description: "LatestTaskRunRef is the name of the TaskRun responsible for executing this BuildRun. \n TODO: This should be called something like \"TaskRunName\"" type: string + output: + description: Output holds the results emitted from step definition of an output + properties: + digest: + description: Digest holds the digest of output image + type: string + size: + description: Size holds the compressed size of output image + type: string + type: object + sources: + description: Sources holds the results emitted from the step definition of different sources + items: + description: SourceResult holds the results emitted from the different sources + properties: + bundle: + description: Bundle holds the results emitted from from the step definition of bundle source + properties: + digest: + description: Digest hold the image digest result + type: string + type: object + git: + description: Git holds the results emitted from from the step definition of a git source + properties: + commitAuthor: + description: CommitAuthor holds the commit author of a git source + type: string + commitSha: + description: CommitSha holds the commit sha of git source + type: string + type: object + name: + description: Name is the name of source + type: string + required: + - name + type: object + type: array startTime: description: StartTime is the time the build is actually started. format: date-time diff --git a/docs/buildrun.md b/docs/buildrun.md index 9bc0a3e2b1..dcbe9023b2 100644 --- a/docs/buildrun.md +++ b/docs/buildrun.md @@ -10,12 +10,14 @@ SPDX-License-Identifier: Apache-2.0 - [BuildRun Controller](#buildrun-controller) - [Configuring a BuildRun](#configuring-a-buildrun) - [Defining the BuildRef](#defining-the-buildref) - - [Defining paramValues](#defining-paramvalues) + - [Defining ParamValues](#defining-paramvalues) - [Defining the ServiceAccount](#defining-the-serviceaccount) -- [Canceling a `BuildRun`](#canceling-a-buildrun) +- [Canceling a `BuildRun`](#canceling-a-buildrun) - [BuildRun Status](#buildrun-status) - - [Understanding the state of a BuildRun](#understanding-the-state-of-a-BuildRun) + - [Understanding the state of a BuildRun](#understanding-the-state-of-a-buildrun) - [Understanding failed BuildRuns](#understanding-failed-buildruns) + - [Step Results in BuildRun Status](#step-results-in-buildrun-status) + - [Build Snapshot](#build-snapshot) - [Relationship with Tekton Tasks](#relationship-with-tekton-tasks) ## Overview @@ -213,6 +215,46 @@ To make it easier for users to understand why did a BuildRun failed, users can i In addition, the `Status.Conditions` will host under the `Message` field a compacted message containing the `kubectl` command to trigger, in order to retrieve the logs. +### Step Results in BuildRun Status + +After the successful completion of a `BuildRun`, the `.status` field contains the results (`.status.taskResults`) emitted from the `TaskRun` steps. These results contain valuable metadata for users, like the _image digest_ or the _commit sha_ of the source code used for building. +The results from the source step will be surfaced to the `.status.sources` and the results from +the [output step](https://github.com/shipwright-io/build/blob/main/docs/buildstrategies.md#system-results) +will be surfaced to the `.status.output` field of a `BuildRun`. + +Example of a `BuildRun` with surfaced results for `git` source: + +```yaml +# [...] +status: + buildSpec: + # [...] + output: + digest: sha256:07626e3c7fdd28d5328a8d6df8d29cd3da760c7f5e2070b534f9b880ed093a53 + size: "1989004" + sources: + - git: + commitAuthor: xxx xxxxxx + commitSha: f25822b85021d02059c9ac8a211ef3804ea8fdde + name: default +``` + +Another example of a `BuildRun` with surfaced results for local source code(`bundle`) source: + +```yaml +# [...] +status: + buildSpec: + # [...] + output: + digest: sha256:07626e3c7fdd28d5328a8d6df8d29cd3da760c7f5e2070b534f9b880ed093a53 + size: "1989004" + sources: + - bundle: + digest: sha256:0f5e2070b534f9b880ed093a537626e3c7fdd28d5328a8d6df8d29cd3da760c7 + name: default +``` + ### Build Snapshot For every BuildRun controller reconciliation, the `buildSpec` in the Status of the `BuildRun` is updated if an existing owned `TaskRun` is present. During this update, a `Build` resource snapshot is generated and embedded into the `status.buildSpec` path of the `BuildRun`. A `buildSpec` is just a copy of the original `Build` spec, from where the `BuildRun` executed a particular image build. The snapshot approach allows developers to see the original `Build` configuration. diff --git a/docs/buildstrategies.md b/docs/buildstrategies.md index fa2adf0001..a8a83df288 100644 --- a/docs/buildstrategies.md +++ b/docs/buildstrategies.md @@ -6,36 +6,36 @@ SPDX-License-Identifier: Apache-2.0 # BuildStrategies -- [Overview](#overview) -- [Available ClusterBuildStrategies](#available-clusterbuildstrategies) -- [Available BuildStrategies](#available-buildstrategies) -- [Buildah](#buildah) - - [Installing Buildah Strategy](#installing-buildah-strategy) -- [Buildpacks v3](#buildpacks-v3) - - [Installing Buildpacks v3 Strategy](#installing-buildpacks-v3-strategy) -- [Kaniko](#kaniko) - - [Installing Kaniko Strategy](#installing-kaniko-strategy) - - [Scanning with Trivy](#scanning-with-trivy) -- [BuildKit](#buildkit) - - [Cache Exporters](#cache-exporters) - - [Known Limitations](#known-limitations) - - [Usage in Clusters with Pod Security Standards](#usage-in-clusters-with-pod-security-standards) - - [Installing BuildKit Strategy](#installing-buildkit-strategy) -- [ko](#ko) - - [Installing ko Strategy](#installing-ko-strategy) - - [Parameters](#parameters) -- [Source to Image](#source-to-image) - - [Installing Source to Image Strategy](#installing-source-to-image-strategy) - - [Build Steps](#build-steps) -- [Strategy parameters](#strategy-parameters) -- [System parameters](#system-parameters) -- [System parameters vs Strategy Parameters Comparison](#system-parameters-vs-strategy-parameters-comparison) -- [System results](#system-results) -- [Steps Resource Definition](#steps-resource-definition) - - [Strategies with different resources](#strategies-with-different-resources) - - [How does Tekton Pipelines handle resources](#how-does-tekton-pipelines-handle-resources) - - [Examples of Tekton resources management](#examples-of-tekton-resources-management) -- [Annotations](#annotations) + - [Overview](#overview) + - [Available ClusterBuildStrategies](#available-clusterbuildstrategies) + - [Available BuildStrategies](#available-buildstrategies) + - [Buildah](#buildah) + - [Installing Buildah Strategy](#installing-buildah-strategy) + - [Buildpacks v3](#buildpacks-v3) + - [Installing Buildpacks v3 Strategy](#installing-buildpacks-v3-strategy) + - [Kaniko](#kaniko) + - [Installing Kaniko Strategy](#installing-kaniko-strategy) + - [Scanning with Trivy](#scanning-with-trivy) + - [BuildKit](#buildkit) + - [Cache Exporters](#cache-exporters) + - [Known Limitations](#known-limitations) + - [Usage in Clusters with Pod Security Standards](#usage-in-clusters-with-pod-security-standards) + - [Installing BuildKit Strategy](#installing-buildkit-strategy) + - [ko](#ko) + - [Installing ko Strategy](#installing-ko-strategy) + - [Parameters](#parameters) + - [Source to Image](#source-to-image) + - [Installing Source to Image Strategy](#installing-source-to-image-strategy) + - [Build Steps](#build-steps) + - [Strategy parameters](#strategy-parameters) + - [System parameters](#system-parameters) + - [System parameters vs Strategy Parameters Comparison](#system-parameters-vs-strategy-parameters-comparison) + - [System results](#system-results) + - [Steps Resource Definition](#steps-resource-definition) + - [Strategies with different resources](#strategies-with-different-resources) + - [How does Tekton Pipelines handle resources](#how-does-tekton-pipelines-handle-resources) + - [Examples of Tekton resources management](#examples-of-tekton-resources-management) + - [Annotations](#annotations) ## Overview @@ -277,7 +277,7 @@ Contrary to the strategy `spec.parameters`, you can use system parameters and th ## System results -You can optionally store the size and digest of the image your build strategy created to some files. This information will eventually be made available in the status of the BuildRun. +You can optionally store the size and digest of the image your build strategy created to some files. | Result file | Description | | ---------------------------------- | ----------------------------------------------- | @@ -286,6 +286,20 @@ You can optionally store the size and digest of the image your build strategy cr You can look at sample build strategies, such as [Kaniko](../samples/buildstrategy/kaniko/buildstrategy_kaniko_cr.yaml), or [Buildpacks](../samples/buildstrategy/buildpacks-v3/buildstrategy_buildpacks-v3_cr.yaml), to see how they fill some or all of the results files. +This information will be available in the `.status.output` field of the BuildRun. + +```yaml +apiVersion: shipwright.io/v1alpha1 +kind: BuildRun +# [...] +status: + # [...] + output: + digest: sha256:07626e3c7fdd28d5328a8d6df8d29cd3da760c7f5e2070b534f9b880ed093a53 + size: "1989004" + # [...] +``` + ## Steps Resource Definition All strategies steps can include a definition of resources(_limits and requests_) for CPU, memory and disk. For strategies with more than one step, each step(_container_) could require more resources than others. Strategy admins are free to define the values that they consider the best fit for each step. Also, identical strategies with the same steps that are only different in their name and step resources can be installed on the cluster to allow users to create a build with smaller and larger resource requirements. diff --git a/pkg/apis/build/v1alpha1/buildrun_types.go b/pkg/apis/build/v1alpha1/buildrun_types.go index b6371b346c..b0a75533e3 100644 --- a/pkg/apis/build/v1alpha1/buildrun_types.go +++ b/pkg/apis/build/v1alpha1/buildrun_types.go @@ -64,8 +64,61 @@ const ( BuildRunStatePodEvicted = "PodEvicted" ) +// SourceResult holds the results emitted from the different sources +type SourceResult struct { + // Name is the name of source + Name string `json:"name"` + + // Git holds the results emitted from from the + // step definition of a git source + // + // +optional + Git *GitSourceResult `json:"git,omitempty"` + + // Bundle holds the results emitted from from the + // step definition of bundle source + // + // +optional + Bundle *BundleSourceResult `json:"bundle,omitempty"` +} + +// BundleSourceResult holds the results emitted from the bundle source +type BundleSourceResult struct { + // Digest hold the image digest result + Digest string `json:"digest,omitempty"` +} + +// GitSourceResult holds the results emitted from the git source +type GitSourceResult struct { + // CommitSha holds the commit sha of git source + CommitSha string `json:"commitSha,omitempty"` + + // CommitAuthor holds the commit author of a git source + CommitAuthor string `json:"commitAuthor,omitempty"` +} + +// Output holds the results emitted from the output step (build-and-push) +type Output struct { + // Digest holds the digest of output image + Digest string `json:"digest,omitempty"` + + // Size holds the compressed size of output image + Size string `json:"size,omitempty"` +} + // BuildRunStatus defines the observed state of BuildRun type BuildRunStatus struct { + // Sources holds the results emitted from the step definition + // of different sources + // + // +optional + Sources []SourceResult `json:"sources"` + + // Output holds the results emitted from step definition of an output + // + // +optional + Output *Output `json:"output"` + // Conditions holds the latest available observations of a resource's current state. Conditions Conditions `json:"conditions,omitempty"` diff --git a/pkg/bundle/bundle.go b/pkg/bundle/bundle.go index b3b11eba9a..238615dece 100644 --- a/pkg/bundle/bundle.go +++ b/pkg/bundle/bundle.go @@ -18,6 +18,7 @@ import ( "github.com/go-git/go-git/v5/plumbing/format/gitignore" "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/remote" @@ -68,21 +69,29 @@ func PackAndPush(ref name.Reference, directory string, options ...remote.Option) // PullAndUnpack a container image layer content into a local directory. Analog // to the bundle.PackAndPush function, optional remote.Option can be used to // configure settings for the image pull, i.e. access credentials. -func PullAndUnpack(ref name.Reference, targetPath string, options ...remote.Option) error { +func PullAndUnpack( + ref name.Reference, + targetPath string, + options ...remote.Option, +) (*v1.Image, error) { desc, err := remote.Get(ref, options...) if err != nil { - return err + return nil, err } image, err := desc.Image() if err != nil { - return err + return nil, err } rc := mutate.Extract(image) defer rc.Close() - return Unpack(rc, targetPath) + if err = Unpack(rc, targetPath); err != nil { + return nil, err + } + + return &image, nil } // Pack reads a directory and creates a tar stream with its content by: diff --git a/pkg/reconciler/buildrun/buildrun.go b/pkg/reconciler/buildrun/buildrun.go index bbb68f393c..8b779e1365 100644 --- a/pkg/reconciler/buildrun/buildrun.go +++ b/pkg/reconciler/buildrun/buildrun.go @@ -297,6 +297,8 @@ func (r *ReconcileBuildRun) Reconcile(request reconcile.Request) (reconcile.Resu return reconcile.Result{}, nil } + resources.UpdateBuildRunUsingTaskResults(buildRun, lastTaskRun) + trCondition := lastTaskRun.Status.GetCondition(apis.ConditionSucceeded) if trCondition != nil { if err := resources.UpdateBuildRunUsingTaskRunCondition(ctx, r.client, buildRun, lastTaskRun, trCondition); err != nil { diff --git a/pkg/reconciler/buildrun/resources/results.go b/pkg/reconciler/buildrun/resources/results.go new file mode 100644 index 0000000000..7e3091f015 --- /dev/null +++ b/pkg/reconciler/buildrun/resources/results.go @@ -0,0 +1,89 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package resources + +import ( + "fmt" + + buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" +) + +type sourceResult struct { + defaultSource, + bundleSource buildv1alpha1.SourceResult +} + +const ( + defaultSourceName = "default" + commitSHAResult = "commit-sha" + commitAuthorResult = "commit-author" + bundleImageDigestResult = "bundle-image-digest" + imageDigestResult = "image-digest" + imageSizeResult = "image-size" +) + +// UpdateBuildRunUsingTaskResults surface the task results +// to the buildrun +func UpdateBuildRunUsingTaskResults( + buildRun *buildv1alpha1.BuildRun, + lastTaskRun *v1beta1.TaskRun, +) { + var sources sourceResult + + // Initializing source result + sources.defaultSource.Git = &buildv1alpha1.GitSourceResult{} + sources.bundleSource.Bundle = &buildv1alpha1.BundleSourceResult{} + + // Initializing output result + buildRun.Status.Output = &buildv1alpha1.Output{} + + for _, result := range lastTaskRun.Status.TaskRunResults { + updateBuildRunStatus(buildRun, result, &sources) + } + + // Appending the source result only if the defined source + // from build spec emitting the results + if sources.defaultSource.Name != "" { + buildRun.Status.Sources = append(buildRun.Status.Sources, sources.defaultSource) + } + + if sources.bundleSource.Name != "" { + buildRun.Status.Sources = append(buildRun.Status.Sources, sources.bundleSource) + } +} + +func updateBuildRunStatus( + buildRun *buildv1alpha1.BuildRun, + result v1beta1.TaskRunResult, + sources *sourceResult, +) { + switch result.Name { + case generateSourceResultName(defaultSourceName, commitSHAResult): + // Source name is default as `spec.source` has no name field + sources.defaultSource.Name = defaultSourceName + sources.defaultSource.Git.CommitSha = result.Value + case generateSourceResultName(defaultSourceName, commitAuthorResult): + // Source name is default as `spec.source` has no name field + sources.defaultSource.Name = defaultSourceName + sources.defaultSource.Git.CommitAuthor = result.Value + case generateSourceResultName(defaultSourceName, bundleImageDigestResult): + // Source name is default as `spec.source` has no name field + sources.bundleSource.Name = defaultSourceName + sources.bundleSource.Bundle.Digest = result.Value + case generateOutputResultName(imageDigestResult): + buildRun.Status.Output.Digest = result.Value + case generateOutputResultName(imageSizeResult): + buildRun.Status.Output.Size = result.Value + } +} + +func generateSourceResultName(source string, resultName string) string { + return fmt.Sprintf("%s-source-%s-%s", prefixParamsResultsVolumes, defaultSourceName, resultName) +} + +func generateOutputResultName(resultName string) string { + return fmt.Sprintf("%s-%s", prefixParamsResultsVolumes, resultName) +} diff --git a/pkg/reconciler/buildrun/resources/results_test.go b/pkg/reconciler/buildrun/resources/results_test.go new file mode 100644 index 0000000000..0b9cee1871 --- /dev/null +++ b/pkg/reconciler/buildrun/resources/results_test.go @@ -0,0 +1,107 @@ +// Copyright The Shipwright Contributors +// +// SPDX-License-Identifier: Apache-2.0 + +package resources_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/shipwright-io/build/pkg/apis/build/v1alpha1" + "github.com/shipwright-io/build/pkg/reconciler/buildrun/resources" + "github.com/shipwright-io/build/test" + pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" +) + +var _ = Describe("TaskRun results to BuildRun", func() { + var ctl test.Catalog + + Context("when a BuildRun complete successfully", func() { + var ( + br *v1alpha1.BuildRun + tr *pipelinev1beta1.TaskRun + ) + + BeforeEach(func() { + tr = ctl.DefaultTaskRun("foo", "bar") + br = ctl.DefaultBuildRun("foo", "bar") + }) + + It("should surface the TaskRun results emitting from default(git) source step", func() { + commitSha := "0e0583421a5e4bf562ffe33f3651e16ba0c78591" + + tr.Status.TaskRunResults = append(tr.Status.TaskRunResults, pipelinev1beta1.TaskRunResult{ + Name: "shp-source-default-commit-sha", + Value: commitSha, + }, pipelinev1beta1.TaskRunResult{ + Name: "shp-source-default-commit-author", + Value: "foo bar", + }) + + resources.UpdateBuildRunUsingTaskResults(br, tr) + + Expect(len(br.Status.Sources)).To(Equal(1)) + Expect(br.Status.Sources[0].Git.CommitSha).To(Equal(commitSha)) + Expect(br.Status.Sources[0].Git.CommitAuthor).To(Equal("foo bar")) + }) + + It("should surface the TaskRun results emitting from default(bundle) source step", func() { + bundleImageDigest := "sha256:fe1b73cd25ac3f11dec752755e2" + + tr.Status.TaskRunResults = append(tr.Status.TaskRunResults, pipelinev1beta1.TaskRunResult{ + Name: "shp-source-default-bundle-image-digest", + Value: bundleImageDigest, + }) + + resources.UpdateBuildRunUsingTaskResults(br, tr) + + Expect(len(br.Status.Sources)).To(Equal(1)) + Expect(br.Status.Sources[0].Bundle.Digest).To(Equal(bundleImageDigest)) + }) + + It("should surface the TaskRun results emitting from output step", func() { + imageDigest := "sha256:fe1b73cd25ac3f11dec752755e2" + + tr.Status.TaskRunResults = append(tr.Status.TaskRunResults, pipelinev1beta1.TaskRunResult{ + Name: "shp-image-digest", + Value: imageDigest, + }, pipelinev1beta1.TaskRunResult{ + Name: "shp-image-size", + Value: "230", + }) + + resources.UpdateBuildRunUsingTaskResults(br, tr) + + Expect(br.Status.Output.Digest).To(Equal(imageDigest)) + Expect(br.Status.Output.Size).To(Equal("230")) + }) + + It("should surface the TaskRun results emitting from source and output step", func() { + commitSha := "0e0583421a5e4bf562ffe33f3651e16ba0c78591" + imageDigest := "sha256:fe1b73cd25ac3f11dec752755e2" + + tr.Status.TaskRunResults = append(tr.Status.TaskRunResults, pipelinev1beta1.TaskRunResult{ + Name: "shp-source-default-commit-sha", + Value: commitSha, + }, pipelinev1beta1.TaskRunResult{ + Name: "shp-source-default-commit-author", + Value: "foo bar", + }, pipelinev1beta1.TaskRunResult{ + Name: "shp-image-digest", + Value: imageDigest, + }, pipelinev1beta1.TaskRunResult{ + Name: "shp-image-size", + Value: "230", + }) + + resources.UpdateBuildRunUsingTaskResults(br, tr) + + Expect(len(br.Status.Sources)).To(Equal(1)) + Expect(br.Status.Sources[0].Git.CommitSha).To(Equal(commitSha)) + Expect(br.Status.Sources[0].Git.CommitAuthor).To(Equal("foo bar")) + Expect(br.Status.Output.Digest).To(Equal(imageDigest)) + Expect(br.Status.Output.Size).To(Equal("230")) + }) + }) +}) diff --git a/pkg/reconciler/buildrun/resources/sources.go b/pkg/reconciler/buildrun/resources/sources.go index 56f2c29d54..0da18a6d6a 100644 --- a/pkg/reconciler/buildrun/resources/sources.go +++ b/pkg/reconciler/buildrun/resources/sources.go @@ -20,10 +20,10 @@ func AmendTaskSpecWithSources( // create the step for spec.source, either Git or Bundle switch { case build.Spec.Source.BundleContainer != nil: - sources.AppendBundleStep(cfg, taskSpec, build.Spec.Source, "default") + sources.AppendBundleStep(cfg, taskSpec, build.Spec.Source, defaultSourceName) case build.Spec.Source.URL != "": - sources.AppendGitStep(cfg, taskSpec, build.Spec.Source, "default") + sources.AppendGitStep(cfg, taskSpec, build.Spec.Source, defaultSourceName) } // create the step for spec.sources, this will eventually change into different steps depending on the type of the source diff --git a/pkg/reconciler/buildrun/resources/sources/bundle.go b/pkg/reconciler/buildrun/resources/sources/bundle.go index 0c72b256a7..45eea121f9 100644 --- a/pkg/reconciler/buildrun/resources/sources/bundle.go +++ b/pkg/reconciler/buildrun/resources/sources/bundle.go @@ -21,6 +21,12 @@ func AppendBundleStep( source build.Source, name string, ) { + // append the result + taskSpec.Results = append(taskSpec.Results, pipeline.TaskResult{ + Name: fmt.Sprintf("%s-source-%s-bundle-image-digest", prefixParamsResultsVolumes, name), + Description: "The digest of the bundle image.", + }) + // initialize the step from the template bundleStep := pipeline.Step{ Container: *cfg.BundleContainerTemplate.DeepCopy(), @@ -31,6 +37,11 @@ func AppendBundleStep( bundleStep.Container.Args = []string{ "--image", source.BundleContainer.Image, "--target", fmt.Sprintf("$(params.%s-%s)", prefixParamsResultsVolumes, paramSourceRoot), + "--result-file-image-digest", + fmt.Sprintf( + "$(results.%s-source-%s-bundle-image-digest.path)", + prefixParamsResultsVolumes, name, + ), } // add credentials mount, if provided diff --git a/pkg/reconciler/buildrun/resources/sources/git.go b/pkg/reconciler/buildrun/resources/sources/git.go index 9f222b5f4b..6fdf3b61dc 100644 --- a/pkg/reconciler/buildrun/resources/sources/git.go +++ b/pkg/reconciler/buildrun/resources/sources/git.go @@ -24,6 +24,9 @@ func AppendGitStep( taskSpec.Results = append(taskSpec.Results, tektonv1beta1.TaskResult{ Name: fmt.Sprintf("%s-source-%s-commit-sha", prefixParamsResultsVolumes, name), Description: "The commit SHA of the cloned source.", + }, tektonv1beta1.TaskResult{ + Name: fmt.Sprintf("%s-source-%s-commit-author", prefixParamsResultsVolumes, name), + Description: "The commit author of the cloned source.", }) // initialize the step from the template @@ -40,6 +43,8 @@ func AppendGitStep( fmt.Sprintf("$(params.%s-%s)", prefixParamsResultsVolumes, paramSourceRoot), "--result-file-commit-sha", fmt.Sprintf("$(results.%s-source-%s-commit-sha.path)", prefixParamsResultsVolumes, name), + "--result-file-commit-author", + fmt.Sprintf("$(results.%s-source-%s-commit-author.path)", prefixParamsResultsVolumes, name), } // Check if a revision is defined diff --git a/pkg/reconciler/buildrun/resources/sources/git_test.go b/pkg/reconciler/buildrun/resources/sources/git_test.go index 023e6da973..b6bd167f9d 100644 --- a/pkg/reconciler/buildrun/resources/sources/git_test.go +++ b/pkg/reconciler/buildrun/resources/sources/git_test.go @@ -34,9 +34,10 @@ var _ = Describe("Git", func() { }, "default") }) - It("adds a result for the commit sha", func() { - Expect(len(taskSpec.Results)).To(Equal(1)) + It("adds results for the commit sha and commit author", func() { + Expect(len(taskSpec.Results)).To(Equal(2)) Expect(taskSpec.Results[0].Name).To(Equal("shp-source-default-commit-sha")) + Expect(taskSpec.Results[1].Name).To(Equal("shp-source-default-commit-author")) }) It("adds a step", func() { @@ -50,6 +51,8 @@ var _ = Describe("Git", func() { "$(params.shp-source-root)", "--result-file-commit-sha", "$(results.shp-source-default-commit-sha.path)", + "--result-file-commit-author", + "$(results.shp-source-default-commit-author.path)", })) }) }) @@ -71,9 +74,10 @@ var _ = Describe("Git", func() { }, "default") }) - It("adds a result for the commit sha", func() { - Expect(len(taskSpec.Results)).To(Equal(1)) + It("adds results for the commit sha and commit author", func() { + Expect(len(taskSpec.Results)).To(Equal(2)) Expect(taskSpec.Results[0].Name).To(Equal("shp-source-default-commit-sha")) + Expect(taskSpec.Results[1].Name).To(Equal("shp-source-default-commit-author")) }) It("adds a volume for the secret", func() { @@ -94,6 +98,8 @@ var _ = Describe("Git", func() { "$(params.shp-source-root)", "--result-file-commit-sha", "$(results.shp-source-default-commit-sha.path)", + "--result-file-commit-author", + "$(results.shp-source-default-commit-author.path)", "--secret-path", "/workspace/shp-source-secret", })) diff --git a/pkg/reconciler/buildrun/resources/taskrun_test.go b/pkg/reconciler/buildrun/resources/taskrun_test.go index 29ca2dc1e1..bb8dd96da3 100644 --- a/pkg/reconciler/buildrun/resources/taskrun_test.go +++ b/pkg/reconciler/buildrun/resources/taskrun_test.go @@ -84,6 +84,8 @@ var _ = Describe("GenerateTaskrun", func() { "$(params.shp-source-root)", "--result-file-commit-sha", "$(results.shp-source-default-commit-sha.path)", + "--result-file-commit-author", + "$(results.shp-source-default-commit-author.path)", })) }) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index c65e32b47c..72272fdb55 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -5,6 +5,7 @@ package e2e_test import ( + "fmt" "os" . "github.com/onsi/ginkgo" @@ -572,4 +573,56 @@ var _ = Describe("For a Kubernetes cluster with Tekton and build installed", fun }) }) }) + + Context("when results from TaskRun surface to BuildRun", func() { + BeforeEach(func() { + testID = generateTestID("kaniko") + }) + + It("should BuildRun status field contains results from default(git) step", func() { + // create the build definition + build = createBuild( + testBuild, + testID, + "samples/build/build_kaniko_cr.yaml", + ) + + buildRun, err = buildRunTestData(testBuild.Namespace, testID, "samples/buildrun/buildrun_kaniko_cr.yaml") + Expect(err).ToNot(HaveOccurred(), "Error retrieving buildrun test data") + + validateBuildRunToSucceed(testBuild, buildRun) + validateBuildRunResultsFromGitSource(buildRun) + }) + + It("should BuildRun status field contains results from default(bundle) step", func() { + inputImage := "quay.io/shipwright/source-bundle:latest" + outputImage := fmt.Sprintf("%s/%s:%s", + os.Getenv(EnvVarImageRepo), + testID, + "latest", + ) + + build, err = NewBuildPrototype(). + ClusterBuildStrategy("kaniko"). + Name(testID). + Namespace(testBuild.Namespace). + SourceBundle(inputImage). + SourceContextDir("docker-build"). + Dockerfile("Dockerfile"). + OutputImage(outputImage). + OutputImageCredentials(os.Getenv(EnvVarImageRepoSecret)). + Create() + Expect(err).ToNot(HaveOccurred()) + + buildRun, err = NewBuildRunPrototype(). + Name(testID). + ForBuild(build). + GenerateServiceAccount(). + Create() + Expect(err).ToNot(HaveOccurred()) + + validateBuildRunToSucceed(testBuild, buildRun) + validateBuildRunResultsFromBundleSource(buildRun) + }) + }) }) diff --git a/test/e2e/validators_test.go b/test/e2e/validators_test.go index 3da67f5956..2645badeab 100644 --- a/test/e2e/validators_test.go +++ b/test/e2e/validators_test.go @@ -147,6 +147,50 @@ func validateBuildRunToSucceed(testBuild *utils.TestBuild, testBuildRun *buildv1 Logf("Test build '%s' is completed after %v !", testBuildRun.GetName(), testBuildRun.Status.CompletionTime.Time.Sub(testBuildRun.Status.StartTime.Time)) } +func validateBuildRunResultsFromGitSource(testBuildRun *buildv1alpha1.BuildRun) { + testBuildRun, err := testBuild.GetBR(testBuildRun.Name) + Expect(err).ToNot(HaveOccurred()) + + tr, err := testBuild.GetTaskRunFromBuildRun(testBuildRun.Name) + Expect(err).ToNot(HaveOccurred()) + + Expect(len(testBuildRun.Status.Sources)).To(Equal(1)) + + for _, result := range tr.Status.TaskRunResults { + switch result.Name { + case "shp-source-default-commit-sha": + Expect(result.Value).To(Equal(testBuildRun.Status.Sources[0].Git.CommitSha)) + case "shp-source-default-commit-author": + Expect(result.Value).To(Equal(testBuildRun.Status.Sources[0].Git.CommitAuthor)) + case "shp-image-digest": + Expect(result.Value).To(Equal(testBuildRun.Status.Output.Digest)) + case "shp-image-size": + Expect(result.Value).To(Equal(testBuildRun.Status.Output.Size)) + } + } +} + +func validateBuildRunResultsFromBundleSource(testBuildRun *buildv1alpha1.BuildRun) { + testBuildRun, err := testBuild.GetBR(testBuildRun.Name) + Expect(err).ToNot(HaveOccurred()) + + tr, err := testBuild.GetTaskRunFromBuildRun(testBuildRun.Name) + Expect(err).ToNot(HaveOccurred()) + + Expect(len(testBuildRun.Status.Sources)).To(Equal(1)) + + for _, result := range tr.Status.TaskRunResults { + switch result.Name { + case "shp-source-default-bundle-image-digest": + Expect(result.Value).To(Equal(testBuildRun.Status.Sources[0].Bundle.Digest)) + case "shp-image-digest": + Expect(result.Value).To(Equal(testBuildRun.Status.Output.Digest)) + case "shp-image-size": + Expect(result.Value).To(Equal(testBuildRun.Status.Output.Size)) + } + } +} + // validateBuildRunToFail creates the build run and watches its flow until it fails. func validateBuildRunToFail(testBuild *utils.TestBuild, testBuildRun *buildv1alpha1.BuildRun) { trueCondition := corev1.ConditionTrue