Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Surface Step Results in BuildRun Status #1

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions cmd/bundle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package main
import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
Expand All @@ -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)")
}

Expand Down Expand Up @@ -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
}

Expand Down
55 changes: 55 additions & 0 deletions cmd/bundle/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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(
Expand All @@ -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()))
})
})
})
})
})
29 changes: 22 additions & 7 deletions cmd/git/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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
}

Expand Down
35 changes: 35 additions & 0 deletions cmd/git/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
})
})
})
})
})
39 changes: 39 additions & 0 deletions deploy/crds/shipwright.io_buildruns.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 45 additions & 3 deletions docs/buildrun.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
Loading