Skip to content

Commit

Permalink
updates to pr #721
Browse files Browse the repository at this point in the history
- run the image digest exporter after each step
- move the index path to the task definition instead of the resource since the user needs to know this at the time of creating the task so it can be used as part of the steps scripting if needed
- rename indexpath to outputimagepath
- add example yaml
- refactor image exporter code and add more unit tests
  • Loading branch information
nader-ziada committed Apr 29, 2019
1 parent 24ceae5 commit 6232343
Show file tree
Hide file tree
Showing 12 changed files with 669 additions and 136 deletions.
9 changes: 8 additions & 1 deletion cmd/imagedigestexporter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ var (
images = flag.String("images", "", "List of images resources built by task in json format")
)

/* The input of this go program will be a JSON string with all the output PipelineResources of type
Image, which will include the path to where the index.json file will be located. The program will
read the related index.json file(s) and log another JSON string including the name of the image resource
and the digests.
The input is an array of ImageResource, ex: [{"name":"srcimg1","type":"image","url":"gcr.io/some-image-1","digest":"","OutputImagePath":"/path/image"}]
The output is an array of PipelineResourceResult, ex: [{"name":"image","digest":"sha256:eed29..660"}]
*/
func main() {
flag.Parse()

Expand All @@ -42,7 +49,7 @@ func main() {

output := []v1alpha1.PipelineResourceResult{}
for _, imageResource := range imageResources {
ii, err := layout.ImageIndexFromPath(imageResource.IndexPath)
ii, err := layout.ImageIndexFromPath(imageResource.OutputImagePath)
if err != nil {
// if this image doesn't have a builder that supports index.josn file,
// then it will be skipped
Expand Down
51 changes: 48 additions & 3 deletions docs/resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,55 @@ spec:

#### Surfacing the image digest built in a task

To surface the image digest in the output of the `taskRun` the builder tool should produce this information in a [oci-layout-image](https://github.com/opencontainers/image-spec/blob/master/image-layout.md) `index.json` file. This file should be placed on a location as specified in
the image resource `indexpath`.
To surface the image digest in the output of the `taskRun` the builder tool should produce this information in a [oci-layout-image](https://github.com/opencontainers/image-spec/blob/master/image-layout.md) `index.json` file. This file should be placed on a location as specified in the task definition under the resource `outputImagePath`

The `taskRun` will include the image digest in the `resourcesResult` field that is part of the taskRun.Status.
for example this build-push task defines the `outputImagePath` for the `buildImage` resource in `/worksapce/buildImage`
```yaml
apiVersion: tekton.dev/v1alpha1
kind: Task
metadata:
name: build-push
spec:
inputs:
resources:
- name: workspace
type: git
outputs:
resources:
- name: builtImage
type: image
outputImagePath: /workspace/builtImage
steps: ...
```
If no value is specified for `outputImagePath`, it will default to `/tools/image-outputs/{resource-name}`.

*Please check the builder tool used on how to pass this path to create the output file.*

The `taskRun` will include the image digest in the `resourcesResult` field that is part of the `taskRun.Status`

for example:

```yaml
status:
completionTime: 2019-04-29T14:20:41Z
conditions:
- lastTransitionTime: 2019-04-29T14:20:41Z
status: "True"
type: Succeeded
podName: build-push-run-pod-0a1777
resourcesResult:
- digest: sha256:eed29cd0b6feeb1a92bc3c4f977fd203c63b376a638731c88cacefe3adb1c660
name: skaffold-image-leeroy-web
startTime: 2019-04-29T14:20:30Z
steps:
- name: build-and-push
terminated:
containerID: docker://140fbe0d923bd0196e0ef1edbf088731ff1e8d37bfa9b04307701ccbdae77426
exitCode: 0
finishedAt: 2019-04-29T14:20:39Z
reason: Completed
startedAt: 2019-04-29T14:20:36Z
```

If the `index.json` file is not produced, the image digest will not be included in the `taskRun` output.

Expand Down
103 changes: 103 additions & 0 deletions examples/taskruns/task-output-image.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: skaffold-image-leeroy-web
spec:
type: image
params:
- name: url
value: gcr.io/christiewilson-catfactory/leeroy-web # Replace this URL with ${KO_DOCKER_REPO}
---
# This demo modifies the cluster (deploys to it) you must use a service
# account with permission to admin the cluster (or make your default user an admin
# of the `default` namespace with default-cluster-admin.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: default-cluster-admin
subjects:
- kind: ServiceAccount
name: default
namespace: default
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
---
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: skaffold-git
spec:
type: git
params:
- name: revision
value: master
- name: url
value: https://github.com/GoogleContainerTools/skaffold
---
#Builds an image via kaniko and pushes it to registry.
apiVersion: tekton.dev/v1alpha1
kind: Task
metadata:
name: build-push-kaniko
spec:
inputs:
resources:
- name: workspace
type: git
outputs:
resources:
- name: builtImage
type: image
outputImagePath: /workspace/workspace
steps:
- name: build-and-push
image: busybox
command:
- /bin/sh
args:
- -ce
- |
set -e
cat <<EOF > /workspace/workspace/index.json
{
"schemaVersion": 2,
"manifests": [
{
"mediaType": "application/vnd.oci.image.index.v1+json",
"size": 314,
"digest": "sha256:05f95b26ed10668b7183c1e2da98610e91372fa9f510046d4ce5812addad86b5"
}
]
}
EOF
- name: echo
image: busybox
command:
- /bin/sh
args:
- -ce
- |
set -e
cat /workspace/workspace/index.json
---
apiVersion: tekton.dev/v1alpha1
kind: TaskRun
metadata:
name: build-push-run
spec:
taskRef:
name: build-push-kaniko
trigger:
type: manual
inputs:
resources:
- name: workspace
resourceRef:
name: skaffold-git
outputs:
resources:
- name: builtImage
resourceRef:
name: skaffold-image-leeroy-web
27 changes: 12 additions & 15 deletions pkg/apis/pipeline/v1alpha1/image_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ func NewImageResource(r *PipelineResource) (*ImageResource, error) {
ir.URL = param.Value
case strings.EqualFold(param.Name, "Digest"):
ir.Digest = param.Value
case strings.EqualFold(param.Name, "IndexPath"):
ir.IndexPath = param.Value
}
}

Expand All @@ -50,11 +48,11 @@ func NewImageResource(r *PipelineResource) (*ImageResource, error) {

// ImageResource defines an endpoint where artifacts can be stored, such as images.
type ImageResource struct {
Name string `json:"name"`
Type PipelineResourceType `json:"type"`
URL string `json:"url"`
Digest string `json:"digest"`
IndexPath string `json:"indexpath"`
Name string `json:"name"`
Type PipelineResourceType `json:"type"`
URL string `json:"url"`
Digest string `json:"digest"`
OutputImagePath string
}

// GetName returns the name of the resource
Expand All @@ -73,11 +71,10 @@ func (s ImageResource) GetParams() []Param { return []Param{} }
// Replacements is used for template replacement on an ImageResource inside of a Taskrun.
func (s *ImageResource) Replacements() map[string]string {
return map[string]string{
"name": s.Name,
"type": string(s.Type),
"url": s.URL,
"digest": s.Digest,
"indexpath": s.IndexPath,
"name": s.Name,
"type": string(s.Type),
"url": s.URL,
"digest": s.Digest,
}
}

Expand All @@ -95,9 +92,9 @@ func (s *ImageResource) GetDownloadContainerSpec() ([]corev1.Container, error) {
func (s *ImageResource) SetDestinationDirectory(path string) {
}

// GetIndexPath return the path to get the index.json file
func (s *ImageResource) GetIndexPath() string {
return s.IndexPath
// GetOutputImagePath return the path to get the index.json file
func (s *ImageResource) GetOutputImagePath() string {
return s.OutputImagePath
}

func (s ImageResource) String() string {
Expand Down
4 changes: 3 additions & 1 deletion pkg/apis/pipeline/v1alpha1/resource_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ type TaskResource struct {
// +optional
// TargetPath is the path in workspace directory where the task resource will be copied.
TargetPath string `json:"targetPath"`
// Resource Value stuff
// +optional
// Path to the index.json file for output container images
OutputImagePath string `json:"outputImagePath"`
}

// +genclient
Expand Down
15 changes: 14 additions & 1 deletion pkg/apis/pipeline/v1alpha1/task_defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,25 @@ limitations under the License.

package v1alpha1

import "context"
import (
"context"
"fmt"
)

func (t *Task) SetDefaults(ctx context.Context) {
t.Spec.SetDefaults(ctx)
}

// SetDefaults set any defaults for the task spec
func (ts *TaskSpec) SetDefaults(ctx context.Context) {
if ts.Outputs != nil && len(ts.Outputs.Resources) > 0 {
for i, o := range ts.Outputs.Resources {
if o.Type == PipelineResourceTypeImage {
if o.OutputImagePath == "" {
ts.Outputs.Resources[i].OutputImagePath = fmt.Sprintf("/tools/image-outputs/%s", o.Name)
}
}
}
}
return
}
5 changes: 5 additions & 0 deletions pkg/apis/pipeline/v1alpha1/task_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ func validateResourceVariables(steps []corev1.Container, inputs *Inputs, outputs
if outputs != nil {
for _, r := range outputs.Resources {
resourceNames[r.Name] = struct{}{}
if r.Type == PipelineResourceTypeImage {
if r.OutputImagePath == "" {
return apis.ErrMissingField("OutputImagePath")
}
}
}
}
return validateVariables(steps, "resources", resourceNames)
Expand Down
20 changes: 19 additions & 1 deletion pkg/apis/pipeline/v1alpha1/task_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ var validResource = TaskResource{
Type: "git",
}

var validImageResource = TaskResource{
Name: "source",
Type: "image",
}

var validBuildSteps = []corev1.Container{{
Name: "mystep",
Image: "myimage",
Expand Down Expand Up @@ -84,6 +89,17 @@ func TestTaskSpecValidate(t *testing.T) {
},
BuildSteps: validBuildSteps,
},
}, {
name: "output image resoure",
fields: fields{
Inputs: &Inputs{
Resources: []TaskResource{validImageResource},
},
Outputs: &Outputs{
Resources: []TaskResource{validImageResource},
},
BuildSteps: validBuildSteps,
},
}, {
name: "valid template variable",
fields: fields{
Expand Down Expand Up @@ -116,7 +132,9 @@ func TestTaskSpecValidate(t *testing.T) {
Outputs: tt.fields.Outputs,
Steps: tt.fields.BuildSteps,
}
if err := ts.Validate(context.Background()); err != nil {
ctx := context.Background()
ts.SetDefaults(ctx)
if err := ts.Validate(ctx); err != nil {
t.Errorf("TaskSpec.Validate() = %v", err)
}
})
Expand Down
Loading

0 comments on commit 6232343

Please sign in to comment.