Skip to content

Commit

Permalink
Adds resolver to provide task resolution for images
Browse files Browse the repository at this point in the history
This partially addresses the desire to fetch tasks
from an OCI image artifact.

Issue: #1839

Signed-off-by: Sunil Thaha <sthaha@redhat.com>
  • Loading branch information
sthaha committed Mar 4, 2020
1 parent 06b5b04 commit 0721b59
Show file tree
Hide file tree
Showing 13 changed files with 862 additions and 5 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ require (
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect
github.com/onsi/ginkgo v1.10.1 // indirect
github.com/onsi/ginkgo v1.10.2 // indirect
github.com/onsi/gomega v1.7.0 // indirect
github.com/pkg/errors v0.8.1
github.com/prometheus/common v0.7.0 // indirect
Expand All @@ -49,7 +49,7 @@ require (
gopkg.in/yaml.v2 v2.2.5 // indirect
k8s.io/api v0.17.2
k8s.io/apimachinery v0.17.2
k8s.io/client-go v0.17.0
k8s.io/client-go v0.17.1
k8s.io/code-generator v0.17.1
k8s.io/gengo v0.0.0-20191108084044-e500ee069b5c // indirect
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
Expand Down
12 changes: 10 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t
github.com/GoogleCloudPlatform/cloud-builders/gcs-fetcher v0.0.0-20191203181535-308b93ad1f39 h1:Pjo3SOZigEnIGevhFqcbFndnqyCH8WimcREd3hRM9vU=
github.com/GoogleCloudPlatform/cloud-builders/gcs-fetcher v0.0.0-20191203181535-308b93ad1f39/go.mod h1:yfGmCjKuUzk9WzubMlW2zwjhCraIc/J+M40cufdemRM=
github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
Expand Down Expand Up @@ -293,6 +294,7 @@ github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk=
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
Expand Down Expand Up @@ -342,6 +344,7 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
Expand All @@ -361,6 +364,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
Expand Down Expand Up @@ -391,8 +395,8 @@ github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.2 h1:uqH7bpe+ERSiDa34FDOF7RikN6RzXgduUF8yarlZp94=
github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
Expand All @@ -413,6 +417,7 @@ github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4P
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
Expand Down Expand Up @@ -480,7 +485,9 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
Expand All @@ -491,6 +498,7 @@ github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
Expand Down
110 changes: 110 additions & 0 deletions pkg/remote/oci.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
Copyright 2019 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package remote

import (
"fmt"
"io/ioutil"
"log"
"strings"

"github.com/google/go-containerregistry/pkg/authn"
imgname "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/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"github.com/tektoncd/pipeline/pkg/client/clientset/versioned/scheme"
"k8s.io/apimachinery/pkg/runtime"
)

func init() {
// Add Tekton's resources to the K8s deserializer
schemeBuilder := runtime.NewSchemeBuilder(v1beta1.AddToScheme)
err := schemeBuilder.AddToScheme(scheme.Scheme)
if err != nil {
log.Panic(err)
}
}

// ImageResolver will attempt to fetch Tekton resources from an OCI compliant image repository.
type OCIResolver struct {
imageReference string
}

// GetTask will retrieve the specified task from the resolver's defined image and return its spec. If it cannot be
// retrieved for any reason, an error is returned.
func (o OCIResolver) GetTask(taskName string) (*v1beta1.TaskSpec, error) {
taskContents, err := o.readImageLayer("task", taskName)
if err != nil {
return nil, err
}

// Deserialize the task into a valid task spec.
var task v1beta1.Task
_, _, err = scheme.Codecs.UniversalDeserializer().Decode(taskContents, nil, &task)
if err != nil {
return nil, fmt.Errorf("Invalid remote task %s: %w", taskName, err)
}

return &task.Spec, nil
}

func (o OCIResolver) readImageLayer(kind string, name string) ([]byte, error) {
// TODO: When this is moved into the Tekton controller, authorize this
// pull as a Service Account in the cluster, and don't rely on the
// contents of ~/.docker/config.json (which won't exist).
imgRef, err := imgname.ParseReference(o.imageReference)
if err != nil {
return nil, fmt.Errorf("%s is an unparseable task image reference: %w", o.imageReference, err)
}

img, err := remote.Image(imgRef, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
return nil, fmt.Errorf("Error pulling %q: %w", o.imageReference, err)
}

m, err := img.Manifest()
if err != nil {
return nil, err
}
ls, err := img.Layers()
if err != nil {
return nil, err
}
var layer v1.Layer
for idx, l := range m.Layers {
if l.Annotations["org.opencontainers.image.title"] != getLayerName(kind, name) {
continue
}

// TODO: Check for application/vnd.cdf.tekton.catalog.v1beta1+yaml or similar as the media type.
layer = ls[idx]
}
if layer == nil {
return nil, fmt.Errorf("Resource %s/%s not found", kind, name)
}
rc, err := layer.Uncompressed()
if err != nil {
return nil, err
}
defer rc.Close()
return ioutil.ReadAll(rc)
}

func getLayerName(kind string, name string) string {
return fmt.Sprintf("%s/%s", strings.ToLower(kind), name)
}
94 changes: 94 additions & 0 deletions pkg/remote/oci_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package remote_test

import (
"fmt"
"net/http/httptest"
"net/url"
"strings"
"testing"

"github.com/ghodss/yaml"
"github.com/google/go-cmp/cmp"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/registry"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
remoteimg "github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/tarball"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"github.com/tektoncd/pipeline/pkg/remote"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestOCIResolver(t *testing.T) {
// Set up a fake registry to push an image to.
s := httptest.NewServer(registry.New())
defer s.Close()
u, err := url.Parse(s.URL)
if err != nil {
t.Fatal(err)
}

imgRef, err := name.ParseReference(fmt.Sprintf("%s/test/ociresolver", u.Host))
if err != nil {
t.Errorf("undexpected error producing image reference %s", err.Error())
}

// Create the image using an example task.
task := v1beta1.Task{
ObjectMeta: metav1.ObjectMeta{
Name: "hello-world",
},
Spec: v1beta1.TaskSpec{
Steps: []v1beta1.Step{
{
Container: v1.Container{
Image: "ubuntu",
},
Script: "echo \"Hello World!\"",
},
},
},
}
taskRaw, err := yaml.Marshal(task)
if err != nil {
t.Errorf("invalid sample task def %s", err.Error())
}

img := empty.Image
layer, err := tarball.LayerFromReader(strings.NewReader(string(taskRaw)))
if err != nil {
t.Errorf("unexpected error adding task layer to image %s", err.Error())
}

img, err = mutate.Append(img, mutate.Addendum{
Layer: layer,
Annotations: map[string]string{
"org.opencontainers.image.title": "task/hello-world",
},
})
if err != nil {
t.Errorf("could not add layer to image %s", err.Error())
}

if err := remoteimg.Write(imgRef, img); err != nil {
t.Errorf("could not push example image to registry")
}

// Now we can call our resolver and see if the spec returned is the same.
digest, err := img.Digest()
if err != nil {
t.Errorf("unexpected error getting digest of image: %s", err.Error())
}
resolver := remote.NewResolver(imgRef.Context().Digest(digest.String()).String())

actual, err := resolver.GetTask("hello-world")
if err != nil {
t.Errorf("failed to fetch task hello-world: %s", err.Error())
}

if diff := cmp.Diff(actual, &task.Spec); diff != "" {
t.Error(diff)
}
}
31 changes: 31 additions & 0 deletions pkg/remote/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Copyright 2019 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package remote

import "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"

// Resolver will retreive Tekton resources like Tasks from remote repositories like an OCI image repositories.
type Resolver interface {
GetTask(taskName string) (*v1beta1.TaskSpec, error)
}

// TODO: Right now, there is only one resolver type. When more are added, this will need to be updated.
func NewResolver(imageReference string) Resolver {
return OCIResolver{
imageReference: imageReference,
}
}
2 changes: 2 additions & 0 deletions third_party/github.com/hashicorp/errwrap/go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions third_party/github.com/hashicorp/go-multierror/go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 0721b59

Please sign in to comment.