Skip to content

Commit 5cf3083

Browse files
bobcatfishtekton-robot
authored andcommitted
Add Task for publishing tekton pipeline images + yaml
Add a `Task` which invokes `ko` to build and publish all images and yaml config required for installing Tekton Pipelines. This Task will: * Build and publish the "base image" using Kaniko * Generate a .ko.yaml * Invoke ko to build/publish images and generate a release.yaml * Parse the release.yaml for built images; ensuring that the expected images were built (and no more) * Tag the built images with the correct version and also tag in all regions (us, asia, eu) This should be the same functionality that could previously be seen in https://github.com/tektoncd/pipeline/blob/master/hack/release.sh (which used https://github.com/knative/test-infra/blob/master/scripts/release.sh). We can remove release.sh once we have completed tektoncd#530 as well. Some functionality has been implemented in a python script, which has its own tests. Since it is currently difficult to update the pull request test logic to do additional things (such as run python unit tests), I'm hoping we are okay with waiting until tektoncd#532 to add automatic running of these tests). Use actual production bucket and registry by default (tektoncd#527) Fixes tektoncd#528 Fixes tektoncd#529
1 parent 97465d0 commit 5cf3083

11 files changed

+1032
-5
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,6 @@
3636

3737
# JetBrains IDE config
3838
.idea
39+
40+
# Python
41+
*.pyc

.ko.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
baseImageOverrides:
2-
# TODO(jasonhall): Use build-base in the build-pipeline path, when it's build/released.
2+
# TODO(christiewilson): Use our built base image
33
github.com/tektoncd/pipeline/cmd/creds-init: gcr.io/knative-nightly/github.com/knative/build/build-base:latest
44
github.com/tektoncd/pipeline/cmd/git-init: gcr.io/knative-nightly/github.com/knative/build/build-base:latest
55
github.com/tektoncd/pipeline/cmd/bash: busybox # image should have shell in $PATH

.ko.yaml.release

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
baseImageOverrides:
2-
# TODO(jasonhall): Use build-base in the build-pipeline path, when it's build/released.
2+
# TODO(christiewilson): Use our built base image
33
github.com/tektoncd/pipeline/cmd/creds-init: gcr.io/knative-release/github.com/knative/build/build-base:latest
44
github.com/tektoncd/pipeline/cmd/git-init: gcr.io/knative-release/github.com/knative/build/build-base:latest
5-
github.com/tektoncd/pipeline/cmd/bash: busybox
6-
github.com/tektoncd/pipeline/cmd/gsutil: google/cloud-sdk:alpine
5+
github.com/tektoncd/pipeline/cmd/bash: busybox # image should have shell in $PATH
6+
github.com/tektoncd/pipeline/cmd/entrypoint: busybox # image should have shell in $PATH
7+
github.com/tektoncd/pipeline/cmd/gsutil: google/cloud-sdk:alpine # image should have gsutil in $PATH

docs/resources.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@ spec:
309309
value: gcs
310310
- name: location
311311
value: gs://some-bucket
312+
- name: dir
313+
value: "y" # This can have any value to be considered "true"
312314
```
313315

314316
Params that can be added are the following:
@@ -361,7 +363,7 @@ service account.
361363
- name: location
362364
value: gs://some-private-bucket
363365
- name: dir
364-
value: "directory"
366+
value: "y"
365367
secrets:
366368
- fieldName: GOOGLE_APPLICATION_CREDENTIALS
367369
secretName: bucket-sa

hack/release.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Creating a new Tekton Pipeline release
22

3+
**Note: we are transitioning to a Pipelines based test and release process, see [tekton/README.md](../tekton/README.md).**
4+
35
The `release.sh` script automates the creation of Tekton Pipeline releases,
46
either nightly or versioned ones.
57

tekton/README.md

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Tekton Repo CI/CD
2+
3+
We dogfood our project by using Tekton Pipelines to build, test and release Tekton Pipelines!
4+
5+
This directory contains the [`Tasks`](https://github.com/knative/build-pipeline/blob/master/docs/tasks.md)
6+
and [`Pipelines`](https://github.com/knative/build-pipeline/blob/master/docs/pipelines.md) that we (will)
7+
use.
8+
9+
TODO(#538): In #538 or #537 we will update [Prow](https://github.com/knative/build-pipeline/blob/master/CONTRIBUTING.md#pull-request-process)
10+
to invoke these `Pipelines` automatically, but for now we will have to invoke them manually.
11+
12+
## Release Pipeline
13+
14+
The `Tasks` which make up our release `Pipeline` are:
15+
16+
* [`publish.yaml`](publish.yaml) - This `Task` uses [`kaniko`](https://github.com/GoogleContainerTools/kaniko)
17+
to build and publish base images, and uses [`ko`](https://github.com/google/go-containerregistry/tree/master/cmd/ko)
18+
to build all of the container images we release and generate the `release.yaml`
19+
20+
### Running
21+
22+
To run these `Pipelines` and `Tasks`, you must have Tekton Pipelines installed, either via
23+
[an official release](https://github.com/knative/build-pipeline/blob/master/docs/install.md)
24+
or [from `HEAD`](https://github.com/knative/build-pipeline/blob/master/DEVELOPMENT.md#install-pipeline).
25+
26+
TODO(#531): Add the Pipeline, for now all we have are `Tasks` which we can invoke individually
27+
by creating [`TaskRuns`](https://github.com/knative/build-pipeline/blob/master/docs/taskruns.md)
28+
and [`PipelineResources`](https://github.com/knative/build-pipeline/blob/master/docs/resources.md).
29+
30+
TODO(#569): Normally we'd use the image `PipelineResources` to control which image registry the images are pushed to.
31+
However since we have so many images, all going to the same registry, we are cheating and using a parameter
32+
for the image registry instead.
33+
34+
* [`publish-run.yaml`](publish-run.yaml) - This example `TaskRun` and `PipelineResources` demonstrate
35+
how to invoke `publish.yaml`:
36+
37+
```bash
38+
kubectl apply -f tekton/publish.yaml
39+
kubectl apply -f tekton/publish-run.yaml
40+
```
41+
42+
### Authentication
43+
44+
Users executing the publish task must be able to:
45+
46+
* Push to the image registry (production registry is `gcr.io/tekton-releases`)
47+
* Write to the GCS bucket (production bucket is `gs://tekton-releases`)
48+
49+
To be able to publish images via `kaniko` or `ko`, you must be able to push to your image registry.
50+
At the moment, the publish `Task` will try to use your default service account in the namespace where
51+
you create the `TaskRun`. If that default service account is able to push to your image registry,
52+
you are good to go. Otherwise, you need to use [a secret annotated with your docker registry
53+
credentials](https://github.com/tektoncd/pipeline/blob/master/docs/auth.md#basic-authentication-docker).
54+
55+
TODO(#631) Ensure that we are supporting folks using credentials other than the cluster defaults; not
56+
sure how this will play out with publishing to our prod registry!
57+
58+
#### Production credentials
59+
60+
TODO(dlorenc, bobcatfish): We need to setup a group which users can be added to, as well as guidelines
61+
around who should be added to this group.
62+
63+
For now, users who need access to our production registry (`gcr.io/tekton-releases`) and production
64+
GCS bucket (`gs://tekton-releases`) should ping @bobcatfish or @dlorenc to get added to the authorized
65+
users.
66+
67+
## Supporting scripts
68+
69+
Some supporting scripts have been written using Python 2.7:
70+
71+
* [koparse](./koparse) - Contains logic for parsing `release.yaml` files created by `ko`

tekton/koparse/koparse.py

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env python2.7
2+
3+
"""
4+
koparse.py parses release.yaml files from `ko`
5+
6+
The `ko` tool (https://github.com/google/go-containerregistry/tree/master/cmd/ko)
7+
builds images and embeds the full names of the built images in the resulting
8+
yaml files.
9+
10+
This script does two things:
11+
12+
* Parses those image names out of the release.yaml, including their digests, and
13+
outputs those to stdout
14+
* Verifies the list of built images against an expected list, to be sure that all
15+
expected images were built (and no extra images were built)
16+
"""
17+
18+
import argparse
19+
import os
20+
import re
21+
import string
22+
import sys
23+
24+
25+
DIGEST_MARKER = "@sha256"
26+
27+
28+
class ImagesMismatchError(Exception):
29+
def __init__(self, missing, extra):
30+
self.missing = missing
31+
self.extra = extra
32+
33+
def __str__(self):
34+
errs = []
35+
if self.missing:
36+
errs.append("Images %s were expected but missing." % self.missing)
37+
if self.extra:
38+
errs.append("Images %s were present but not expected." %
39+
self.extra)
40+
return string.join(errs, " ")
41+
42+
43+
class BadActualImageFormatError(Exception):
44+
def __init__(self, image):
45+
self.image = image
46+
47+
def __str__(self):
48+
return "Format of image %s was unexpected, did not contain %s" % (self.image, DIGEST_MARKER)
49+
50+
51+
def parse_release(base, path):
52+
"""Extracts built images from the release.yaml at path
53+
54+
Args:
55+
base: The built images will be expected to start with this string,
56+
other images will be ignored
57+
path: The path to the file (release.yaml) that will contain the built images
58+
Returns:
59+
list of the images parsed from the file
60+
"""
61+
images = []
62+
with open(path) as f:
63+
for line in f:
64+
match = re.search(base + ".*@sha256:[0-9a-f]*", line)
65+
if match:
66+
images.append(match.group(0))
67+
return images
68+
69+
70+
def compare_expected_images(expected, actual):
71+
"""Ensures that the list of actual images includes only the expected images
72+
73+
Args:
74+
expected: A list of all of the names of images that are expected to have
75+
been built, including the path to the image without the digest
76+
actual: A list of the names of the built images, including the path to the
77+
image and the digest
78+
"""
79+
for image in actual:
80+
if DIGEST_MARKER not in image:
81+
raise BadActualImageFormatError(image)
82+
83+
actual_no_digest = [string.split(image, DIGEST_MARKER)[0]
84+
for image in actual]
85+
86+
missing = set(expected) - set(actual_no_digest)
87+
extra = set(actual_no_digest) - set(expected)
88+
89+
if missing or extra:
90+
raise ImagesMismatchError(list(missing), list(extra))
91+
92+
93+
if __name__ == "__main__":
94+
arg_parser = argparse.ArgumentParser(
95+
description="Parse expected built images from a release.yaml created by `ko`")
96+
arg_parser.add_argument("--path", type=str, required=True,
97+
help="Path to the release.yaml")
98+
arg_parser.add_argument("--base", type=str, required=True,
99+
help="String prefix which is used to find images within the release.yaml")
100+
arg_parser.add_argument("--images", type=str, required=True, nargs="+",
101+
help="List of all images expected to be built, without digests")
102+
args = arg_parser.parse_args()
103+
104+
try:
105+
images = parse_release(args.base, args.path)
106+
compare_expected_images(args.images, images)
107+
except (IOError, BadActualImageFormatError) as e:
108+
sys.stderr.write("Error determining built images: %s\n" % e)
109+
sys.exit(1)
110+
except (ImagesMismatchError) as e:
111+
sys.stderr.write("Expected images did not match: %s\n" % e)
112+
with open(args.path) as f:
113+
sys.stderr.write(f.read())
114+
sys.exit(1)
115+
116+
print("\n".join(images))

tekton/koparse/test_koparse.py

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/usr/bin/env python2.7
2+
3+
import os
4+
import unittest
5+
6+
import koparse
7+
8+
9+
IMAGE_BASE = "gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/"
10+
PATH_TO_TEST_RELEASE_YAML = os.path.join(os.path.dirname(
11+
os.path.abspath(__file__)), "test_release.yaml")
12+
PATH_TO_WRONG_FILE = os.path.join(os.path.dirname(
13+
os.path.abspath(__file__)), "koparse.py")
14+
BUILT_IMAGES = [
15+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/kubeconfigwriter@sha256:68453f5bb4b76c0eab98964754114d4f79d3a50413872520d8919a6786ea2b35",
16+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/creds-init@sha256:67448da79e4731ab534b91df08da547bc434ab08e41d905858f2244e70290f48",
17+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/git-init@sha256:7d5520efa2d55e1346c424797988c541327ee52ef810a840b5c6f278a9de934a",
18+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/nop@sha256:3784d6b8f73043a29d2c1d6196801bee46fe808fbb94ba4fd21ca52dce503183",
19+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/bash@sha256:d55917ef5c92627027e3755bfc577fbfa2fb783cccfb13a98632cb6ba6088cd6",
20+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/gsutil@sha256:421a261436e16af4057b4a069fdae8a5aca6e37269952209ad9932a774aa0003",
21+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/controller@sha256:bdc6f22a44944c829983c30213091b60f490b41f89577e8492f6a2936be0df41",
22+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/webhook@sha256:cca7069a11aaf0d9d214306d456bc40b2e33e5839429bf07c123ad964d495d8a",
23+
]
24+
EXPECTED_IMAGES = [
25+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/kubeconfigwriter",
26+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/creds-init",
27+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/git-init",
28+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/nop",
29+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/bash",
30+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/gsutil",
31+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/controller",
32+
"gcr.io/knative-releases/github.com/knative/build-pipeline/cmd/webhook",
33+
]
34+
35+
36+
class TestKoparse(unittest.TestCase):
37+
38+
def test_parse_release(self):
39+
images = koparse.parse_release(IMAGE_BASE, PATH_TO_TEST_RELEASE_YAML)
40+
self.assertListEqual(images, BUILT_IMAGES)
41+
42+
def test_parse_release_no_file(self):
43+
with self.assertRaises(IOError):
44+
koparse.parse_release(IMAGE_BASE, "whoops")
45+
46+
def test_parse_release_wrong_contents(self):
47+
images = koparse.parse_release(IMAGE_BASE, PATH_TO_WRONG_FILE)
48+
self.assertEqual(images, [])
49+
50+
def test_compare_expected_images(self):
51+
koparse.compare_expected_images(EXPECTED_IMAGES, BUILT_IMAGES)
52+
53+
def test_compare_expected_images_bad_format(self):
54+
with self.assertRaises(koparse.BadActualImageFormatError):
55+
koparse.compare_expected_images(EXPECTED_IMAGES, EXPECTED_IMAGES)
56+
57+
def test_compare_expected_images_missing(self):
58+
extra_expected = (EXPECTED_IMAGES[:] +
59+
["gcr.io/knative-releases/something-else"])
60+
with self.assertRaises(koparse.ImagesMismatchError):
61+
koparse.compare_expected_images(extra_expected, BUILT_IMAGES)
62+
63+
def test_compare_expected_images_too_many(self):
64+
extra_actual = (BUILT_IMAGES[:] +
65+
["gcr.io/knative-releases/something-else@sha256:somedigest"])
66+
with self.assertRaises(koparse.ImagesMismatchError):
67+
koparse.compare_expected_images(EXPECTED_IMAGES, extra_actual)
68+
69+
70+
if __name__ == "__main__":
71+
unittest.main()

0 commit comments

Comments
 (0)