diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 0bba8dd823d..a18ddb9a849 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -111,6 +111,7 @@ func main() { taskInformer := pipelineInformerFactory.Pipeline().V1alpha1().Tasks() taskRunInformer := pipelineInformerFactory.Pipeline().V1alpha1().TaskRuns() + resourceInformer := pipelineInformerFactory.Pipeline().V1alpha1().PipelineResources() buildInformer := buildInformerFactory.Build().V1alpha1().Builds() pipelineInformer := pipelineInformerFactory.Pipeline().V1alpha1().Pipelines() @@ -122,6 +123,7 @@ func main() { taskRunInformer, taskInformer, buildInformer, + resourceInformer, ), pipelinerun.NewController(opt, pipelineRunInformer, @@ -147,6 +149,7 @@ func main() { taskInformer.Informer().HasSynced, taskRunInformer.Informer().HasSynced, buildInformer.Informer().HasSynced, + resourceInformer.Informer().HasSynced, } { if ok := cache.WaitForCacheSync(stopCh, synced); !ok { logger.Fatalf("failed to wait for cache at index %v to sync", i) diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index 0a54191bf5d..a1eea41946b 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -83,8 +83,9 @@ func main() { Client: kubeClient, Options: options, Handlers: map[schema.GroupVersionKind]webhook.GenericCRD{ - v1alpha1.SchemeGroupVersion.WithKind("Pipeline"): &v1alpha1.Pipeline{}, - v1alpha1.SchemeGroupVersion.WithKind("Task"): &v1alpha1.Task{}, + v1alpha1.SchemeGroupVersion.WithKind("Pipeline"): &v1alpha1.Pipeline{}, + v1alpha1.SchemeGroupVersion.WithKind("Task"): &v1alpha1.Task{}, + v1alpha1.SchemeGroupVersion.WithKind("PipelineResource"): &v1alpha1.PipelineResource{}, }, Logger: logger, } diff --git a/config/200-clusterrole.yaml b/config/200-clusterrole.yaml index d9c6fe47149..3753c607dea 100644 --- a/config/200-clusterrole.yaml +++ b/config/200-clusterrole.yaml @@ -16,8 +16,8 @@ rules: resources: ["customresourcedefinitions"] verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] - apiGroups: ["pipeline.knative.dev"] - resources: ["tasks", "taskruns", "pipelines", "pipelineruns", "pipelineparams", "resources"] + resources: ["tasks", "taskruns", "pipelines", "pipelineruns", "pipelineparams", "pipelineresources"] verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] - apiGroups: ["build.knative.dev"] resources: ["builds", "buildtemplates", "clusterbuildtemplates"] - verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] + verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] \ No newline at end of file diff --git a/config/300-resource.yaml b/config/300-resource.yaml index dc1978c0678..c6ea1388e89 100644 --- a/config/300-resource.yaml +++ b/config/300-resource.yaml @@ -4,12 +4,12 @@ metadata: creationTimestamp: null labels: controller-tools.k8s.io: "1.0" - name: resources.pipeline.knative.dev + name: pipelineresources.pipeline.knative.dev spec: group: pipeline.knative.dev names: - kind: Resource - plural: resources + kind: PipelineResource + plural: pipelineresources scope: Namespaced version: v1alpha1 status: diff --git a/docs/pipeline-resources.md b/docs/pipeline-resources.md index bef0bf3d9d1..71b2ef31fe8 100644 --- a/docs/pipeline-resources.md +++ b/docs/pipeline-resources.md @@ -10,7 +10,7 @@ Use the following example to understand the syntax and strucutre of a Git Resour ``` apiVersion: pipeline.knative.dev/v1alpha1 -kind: Resource +kind: PipelineResource metadata: name: wizzbang-git namespace: default diff --git a/examples/pipelines/guestbook-resources.yaml b/examples/pipelines/guestbook-resources.yaml index 42779e57041..576cc11c301 100644 --- a/examples/pipelines/guestbook-resources.yaml +++ b/examples/pipelines/guestbook-resources.yaml @@ -1,5 +1,5 @@ apiVersion: pipeline.knative.dev/v1alpha1 -kind: Resource +kind: PipelineResource metadata: name: guestbook-resources-git namespace: default @@ -12,7 +12,7 @@ spec: value: HEAD --- apiVersion: pipeline.knative.dev/v1alpha1 -kind: Resource +kind: PipelineResource metadata: name: guestbook-resources-redis-docker namespace: default @@ -25,7 +25,7 @@ spec: value: HEAD --- apiVersion: pipeline.knative.dev/v1alpha1 -kind: Resource +kind: PipelineResource metadata: name: guestbookstagingimage namespace: default @@ -36,7 +36,7 @@ spec: value: gcr.io/demo-staging --- apiVersion: pipeline.knative.dev/v1alpha1 -kind: Resource +kind: PipelineResource metadata: name: redisstagingimage namespace: default diff --git a/examples/pipelines/kritis-resources.yaml b/examples/pipelines/kritis-resources.yaml index 727bd6f12a9..b64957c052d 100644 --- a/examples/pipelines/kritis-resources.yaml +++ b/examples/pipelines/kritis-resources.yaml @@ -1,5 +1,5 @@ apiVersion: pipeline.knative.dev/v1alpha1 -kind: Resource +kind: PipelineResource metadata: name: kritis-resources-git namespace: default @@ -12,7 +12,7 @@ spec: value: master --- apiVersion: pipeline.knative.dev/v1alpha1 -kind: Resource +kind: PipelineResource metadata: name: kritis-resources-test-git namespace: default @@ -25,7 +25,7 @@ spec: value: https://github.com/grafeas/kritis-test --- apiVersion: pipeline.knative.dev/v1alpha1 -kind: Resource +kind: PipelineResource metadata: name: kritis-resources-image namespace: default diff --git a/pkg/apis/pipeline/v1alpha1/pipelineresource_validation.go b/pkg/apis/pipeline/v1alpha1/pipelineresource_validation.go new file mode 100644 index 00000000000..e0e0e107a44 --- /dev/null +++ b/pkg/apis/pipeline/v1alpha1/pipelineresource_validation.go @@ -0,0 +1,37 @@ +/* +Copyright 2018 The Knative 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 v1alpha1 + +import ( + "github.com/knative/pkg/apis" + "k8s.io/apimachinery/pkg/api/equality" +) + +func (r *PipelineResource) Validate() *apis.FieldError { + if err := validateObjectMetadata(r.GetObjectMeta()); err != nil { + return err.ViaField("metadata") + } + return nil +} + +func (rs *PipelineResourceSpec) Validate() *apis.FieldError { + if equality.Semantic.DeepEqual(rs, &PipelineResourceSpec{}) { + return apis.ErrMissingField(apis.CurrentField) + } + + return nil +} diff --git a/pkg/apis/pipeline/v1alpha1/resource_defaults.go b/pkg/apis/pipeline/v1alpha1/resource_defaults.go new file mode 100644 index 00000000000..d4b3bad0a03 --- /dev/null +++ b/pkg/apis/pipeline/v1alpha1/resource_defaults.go @@ -0,0 +1,25 @@ +/* +Copyright 2018 The Knative 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 v1alpha1 + +func (t *PipelineResource) SetDefaults() { + t.Spec.SetDefaults() +} + +func (ts *PipelineResourceSpec) SetDefaults() { + return +} diff --git a/pkg/apis/pipeline/v1alpha1/resource_types.go b/pkg/apis/pipeline/v1alpha1/resource_types.go index 92128965673..dbf48bcec60 100644 --- a/pkg/apis/pipeline/v1alpha1/resource_types.go +++ b/pkg/apis/pipeline/v1alpha1/resource_types.go @@ -17,6 +17,8 @@ limitations under the License. package v1alpha1 import ( + "github.com/knative/pkg/apis" + "github.com/knative/pkg/webhook" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -46,17 +48,26 @@ type PipelineResourceInterface interface { GetVersion() string } +// PipelineResourceSpec defines an individual resources used in the pipeline. +type PipelineResourceSpec struct { + Type PipelineResourceType `json:"type"` + Params []Param `json:"params"` + // +optional + Generation int64 `json:"generation,omitempty"` +} + // PipelineResourceStatus should implment status for PipelineResource type PipelineResourceStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file } -// PipelineResourceSpec defines an individual resources used in the pipeline. -type PipelineResourceSpec struct { - Type PipelineResourceType `json:"type"` - Params []Param `json:"params"` -} +// Check that PipelineResource may be validated and defaulted. +var _ apis.Validatable = (*PipelineResource)(nil) +var _ apis.Defaultable = (*PipelineResource)(nil) + +// Assert that PipelineResource implements the GenericCRD interface. +var _ webhook.GenericCRD = (*PipelineResource)(nil) // TaskResource defines an input or output Resource declared as a requirement // by a Task. The Name field will be used to refer to these Resources within diff --git a/pkg/reconciler/v1alpha1/taskrun/resources/input_resources.go b/pkg/reconciler/v1alpha1/taskrun/resources/input_resources.go index adc52759b22..463b5db2b05 100644 --- a/pkg/reconciler/v1alpha1/taskrun/resources/input_resources.go +++ b/pkg/reconciler/v1alpha1/taskrun/resources/input_resources.go @@ -32,8 +32,11 @@ func AddInputResource( logger *zap.SugaredLogger, ) (*buildv1alpha1.Build, error) { - var gitResource *v1alpha1.GitResource + if task.Spec.Inputs == nil { + return build, nil + } + var gitResource *v1alpha1.GitResource for _, input := range task.Spec.Inputs.Resources { resource, err := pipelineResourceLister.PipelineResources(task.Namespace).Get(input.Name) if err != nil { diff --git a/pkg/reconciler/v1alpha1/taskrun/taskrun.go b/pkg/reconciler/v1alpha1/taskrun/taskrun.go index 9478b3c7964..7f61c0cb234 100644 --- a/pkg/reconciler/v1alpha1/taskrun/taskrun.go +++ b/pkg/reconciler/v1alpha1/taskrun/taskrun.go @@ -23,6 +23,7 @@ import ( "github.com/knative/build-pipeline/pkg/apis/pipeline/v1alpha1" "github.com/knative/build-pipeline/pkg/reconciler" + resources "github.com/knative/build-pipeline/pkg/reconciler/v1alpha1/taskrun/resources" buildv1alpha1 "github.com/knative/build/pkg/apis/build/v1alpha1" buildinformers "github.com/knative/build/pkg/client/informers/externalversions/build/v1alpha1" buildlisters "github.com/knative/build/pkg/client/listers/build/v1alpha1" @@ -62,12 +63,11 @@ type Reconciler struct { *reconciler.Base // listers index properties about resources - taskRunLister listers.TaskRunLister - taskLister listers.TaskLister - buildLister buildlisters.BuildLister - buildTemplateLister buildlisters.BuildTemplateLister - clusterBuildTemplateLister buildlisters.ClusterBuildTemplateLister - tracker tracker.Interface + taskRunLister listers.TaskRunLister + taskLister listers.TaskLister + buildLister buildlisters.BuildLister + resourceLister listers.PipelineResourceLister + tracker tracker.Interface } // Check that our Reconciler implements controller.Reconciler @@ -79,14 +79,15 @@ func NewController( taskRunInformer informers.TaskRunInformer, taskInformer informers.TaskInformer, buildInformer buildinformers.BuildInformer, - + resourceInformer informers.PipelineResourceInformer, ) *controller.Impl { c := &Reconciler{ - Base: reconciler.NewBase(opt, taskRunAgentName), - taskRunLister: taskRunInformer.Lister(), - taskLister: taskInformer.Lister(), - buildLister: buildInformer.Lister(), + Base: reconciler.NewBase(opt, taskRunAgentName), + taskRunLister: taskRunInformer.Lister(), + taskLister: taskInformer.Lister(), + buildLister: buildInformer.Lister(), + resourceLister: resourceInformer.Lister(), } impl := controller.NewImpl(c, c.Logger, taskRunControllerName) @@ -103,10 +104,17 @@ func NewController( // DeleteFunc: impl.Enqueue, // }) - c.tracker = tracker.New(impl.EnqueueKey, 30*time.Minute) + c.tracker = tracker.New(impl.EnqueueKey, 30*time.Second) buildInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: c.tracker.OnChanged, UpdateFunc: controller.PassNew(c.tracker.OnChanged), }) + + resourceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: impl.Enqueue, + UpdateFunc: controller.PassNew(impl.Enqueue), + }) + return impl } @@ -135,18 +143,6 @@ func (c *Reconciler) Reconcile(ctx context.Context, key string) error { // Don't modify the informer's copy. tr := original.DeepCopy() - // TODO(aaron-prindle) verify that build does not have to exist to create a tracker - buildRef := corev1.ObjectReference{ - APIVersion: "build.knative.dev/v1alpha1", - Kind: "Build", - Namespace: tr.Namespace, - Name: tr.Name, - } - if err := c.tracker.Track(buildRef, tr); err != nil { - logger.Errorf("failed to create tracker for build %s for taskrun %s: %v", buildRef, tr.Name, err) - return err - } - // Reconcile this copy of the task run and then write back any status // updates regardless of whether the reconciliation errored out. err = c.reconcile(ctx, tr) @@ -165,17 +161,15 @@ func (c *Reconciler) Reconcile(ctx context.Context, key string) error { func (c *Reconciler) reconcile(ctx context.Context, tr *v1alpha1.TaskRun) error { logger := logging.FromContext(ctx) - haveBuild := false // get build the same as the taskrun, this is the value we use for 1:1 mapping and retrieval b, err := c.getBuild(tr.Namespace, tr.Name) if errors.IsNotFound(err) { - // haveBuild = false + if b, err = c.makeBuild(tr, logger); err != nil { + return fmt.Errorf("Failed to create a build for taskrun: %v", err) + } } else if err != nil { - return fmt.Errorf("retrieving build %s for taskRun %s: %v", tr.Name, tr.Name, err) - } else { - haveBuild = true + return fmt.Errorf("Failed retrieving build %s for taskRun %s: %v", tr.Name, tr.Name, err) } - // handle cases where build with name exists but handled by another controller // switch ownerref := metav1.GetControllerOf(b); { @@ -186,29 +180,15 @@ func (c *Reconciler) reconcile(ctx context.Context, tr *v1alpha1.TaskRun) error // taskrun has finished (as child build has finished and status is synced) if len(tr.Status.Conditions) > 0 && tr.Status.Conditions[0].Status != corev1.ConditionUnknown { - logger.Infof("finished %s", tr.Name) + logger.Infof("Finished %s", tr.Name) return nil } - if !haveBuild { - // Get related task for taskrun - t, err := c.taskLister.Tasks(tr.Namespace).Get(tr.Spec.TaskRef.Name) - if err != nil { - return err - } - - // make build obj from task buildspec - logger.Infof("make build: %s", tr.Name) - if b, err = c.makeBuild(t, tr); err != nil { - return fmt.Errorf("failed to create a build for taskrun: %v", err) - } - } - // sync build status with taskrun status if len(b.Status.Conditions) > 0 { - logger.Infof("syncing taskrun conditions with build conditions %s", b.Status.Conditions[0]) + logger.Infof("Syncing taskrun conditions with build conditions %s", b.Status.Conditions[0]) } else { - logger.Infof("syncing taskrun conditions with build conditions []") + logger.Infof("Syncing taskrun conditions with build conditions []") } tr.Status.Conditions = b.Status.Conditions return nil @@ -241,9 +221,15 @@ func (c *Reconciler) deleteTaskRun(namespace, name string) error { } // makeBuild creates a build from the task, using the task's buildspec. -func (c *Reconciler) makeBuild(t *v1alpha1.Task, tr *v1alpha1.TaskRun) (*buildv1alpha1.Build, error) { +func (c *Reconciler) makeBuild(tr *v1alpha1.TaskRun, logger *zap.SugaredLogger) (*buildv1alpha1.Build, error) { + // Get related task for taskrun + t, err := c.taskLister.Tasks(tr.Namespace).Get(tr.Spec.TaskRef.Name) + if err != nil { + return nil, err + } + if t.Spec.BuildSpec == nil { - return nil, fmt.Errorf("nil BuildSpec") + return nil, fmt.Errorf("BuildSpec for task %s is nil", t.Name) } b := &buildv1alpha1.Build{ ObjectMeta: metav1.ObjectMeta{ @@ -255,9 +241,29 @@ func (c *Reconciler) makeBuild(t *v1alpha1.Task, tr *v1alpha1.TaskRun) (*buildv1 }, Spec: *t.Spec.BuildSpec, } - return c.BuildClientSet.BuildV1alpha1().Builds(tr.Namespace).Create(b) -} -func (c *Reconciler) now() metav1.Time { - return metav1.Now() + build, err := resources.AddInputResource(b, t, tr, c.resourceLister, logger) + if err != nil { + logger.Errorf("Failed to create a build for taskrun: %s due to input resource error %v", tr.Name, err) + return nil, err + } + + createdBuild, err := c.BuildClientSet.BuildV1alpha1().Builds(tr.Namespace).Create(build) + if err != nil { + logger.Errorf("Failed to create build for taskrun %s, %v", tr.Name, err) + return nil, err + } + + buildRef := corev1.ObjectReference{ + APIVersion: "build.knative.dev/v1alpha1", + Kind: "Build", + Namespace: tr.Namespace, + Name: tr.Name, + } + if err := c.tracker.Track(buildRef, tr); err != nil { + logger.Errorf("Failed to create tracker for build %s for taskrun %s: %v", buildRef, tr.Name, err) + return nil, err + } + + return createdBuild, nil } diff --git a/pkg/reconciler/v1alpha1/taskrun/taskrun_test.go b/pkg/reconciler/v1alpha1/taskrun/taskrun_test.go index a1896568d82..50a2883a8fa 100644 --- a/pkg/reconciler/v1alpha1/taskrun/taskrun_test.go +++ b/pkg/reconciler/v1alpha1/taskrun/taskrun_test.go @@ -126,6 +126,7 @@ func getController(d testData) (*controller.Impl, *observer.ObservedLogs, *fakeb taskRunInformer := sharedInformer.Pipeline().V1alpha1().TaskRuns() taskInformer := sharedInformer.Pipeline().V1alpha1().Tasks() + resourceInformer := sharedInformer.Pipeline().V1alpha1().PipelineResources() for _, tr := range d.taskruns { taskRunInformer.Informer().GetIndexer().Add(tr) @@ -145,6 +146,7 @@ func getController(d testData) (*controller.Impl, *observer.ObservedLogs, *fakeb taskRunInformer, taskInformer, buildInformer, + resourceInformer, ), logs, buildClient } diff --git a/test/clients.go b/test/clients.go index caaec07eea5..40b7725e204 100644 --- a/test/clients.go +++ b/test/clients.go @@ -29,11 +29,12 @@ import ( type clients struct { KubeClient *knativetest.KubeClient - PipelineClient v1alpha1.PipelineInterface - TaskClient v1alpha1.TaskInterface - TaskRunClient v1alpha1.TaskRunInterface - PipelineRunClient v1alpha1.PipelineRunInterface - PipelineParamsClient v1alpha1.PipelineParamsInterface + PipelineClient v1alpha1.PipelineInterface + TaskClient v1alpha1.TaskInterface + TaskRunClient v1alpha1.TaskRunInterface + PipelineRunClient v1alpha1.PipelineRunInterface + PipelineParamsClient v1alpha1.PipelineParamsInterface + PipelineResourceClient v1alpha1.PipelineResourceInterface BuildClient buildv1alpha1.BuildInterface } @@ -64,6 +65,7 @@ func newClients(configPath, clusterName, namespace string) (*clients, error) { c.TaskRunClient = cs.PipelineV1alpha1().TaskRuns(namespace) c.PipelineRunClient = cs.PipelineV1alpha1().PipelineRuns(namespace) c.PipelineParamsClient = cs.PipelineV1alpha1().PipelineParamses(namespace) + c.PipelineResourceClient = cs.PipelineV1alpha1().PipelineResources(namespace) bcs, err := buildversioned.NewForConfig(cfg) if err != nil { diff --git a/test/crd_checks.go b/test/crd_checks.go index 1b5b0de739c..159bdc0987d 100644 --- a/test/crd_checks.go +++ b/test/crd_checks.go @@ -33,7 +33,7 @@ const ( // we can get to that failure faster - knative/serving is currently using `6 * time.Minute` // which we could use, or we could use timeouts more specific to what each `Task` is // actually expected to do. - timeout = 60 * time.Second + timeout = 120 * time.Second ) // WaitForTaskRunState polls the status of the TaskRun called name from client every diff --git a/test/kanikoBuildTask/app/Dockerfile b/test/kanikoBuildTask/app/Dockerfile new file mode 100644 index 00000000000..26874a94ef4 --- /dev/null +++ b/test/kanikoBuildTask/app/Dockerfile @@ -0,0 +1,15 @@ +FROM golang + +# Copy the local package files to the container's workspace. +ADD . /go/src/github.com/knative/build-pipeline + +# Build the outyet command inside the container. +# (You may fetch or manage dependencies here, +# either manually or with a tool like "godep".) +RUN go install github.com/knative/build-pipeline/test/kanikoBuildTask/app + +# Run the outyet command by default when the container starts. +ENTRYPOINT /go/bin/app + +# Document that the service listens on port 8080. +EXPOSE 8080 \ No newline at end of file diff --git a/test/kanikoBuildTask/app/main.go b/test/kanikoBuildTask/app/main.go new file mode 100644 index 00000000000..e0f612c1338 --- /dev/null +++ b/test/kanikoBuildTask/app/main.go @@ -0,0 +1,32 @@ +/* +Copyright 2018 Knative Authors LLC +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 main + +import ( + "fmt" + "log" + "net/http" +) + +func handler(w http.ResponseWriter, r *http.Request) { + log.Print("Hello world received a request.") + fmt.Fprintf(w, "Hello World! \n") +} + +func main() { + log.Print("Hello world sample started.") + + http.HandleFunc("/", handler) + http.ListenAndServe(":8080", nil) +} diff --git a/test/kaniko_task_test.go b/test/kaniko_task_test.go new file mode 100644 index 00000000000..e14e3f55f9b --- /dev/null +++ b/test/kaniko_task_test.go @@ -0,0 +1,185 @@ +// +build e2e + +/* +Copyright 2018 Knative Authors LLC +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 test + +import ( + "bufio" + "bytes" + "fmt" + "io" + "os" + "strings" + "testing" + "time" + + buildv1alpha1 "github.com/knative/build/pkg/apis/build/v1alpha1" + knativetest "github.com/knative/pkg/test" + "github.com/knative/pkg/test/logging" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/knative/build-pipeline/pkg/apis/pipeline/v1alpha1" + + // Mysteriously by k8s libs, or they fail to create `KubeClient`s from config. Apparently just importing it is enough. @_@ side effects @_@. https://github.com/kubernetes/client-go/issues/242 + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" +) + +const ( + kanikoTaskName = "kanikotask" + kanikoTaskRunName = "kanikotask-run" + kanikoResourceName = "go-example-git" + kanikoBuildOutput = "Build successful" +) + +func getGitResource(namespace string) *v1alpha1.PipelineResource { + return &v1alpha1.PipelineResource{ + ObjectMeta: metav1.ObjectMeta{ + Name: kanikoResourceName, + Namespace: namespace, + }, + Spec: v1alpha1.PipelineResourceSpec{ + Type: v1alpha1.PipelineResourceTypeGit, + Params: []v1alpha1.Param{ + v1alpha1.Param{ + Name: "Url", + Value: "https://github.com/pivotal-nader-ziada/gohelloworld", + }, + }, + }, + } +} + +func getTask(namespace string, t *testing.T) *v1alpha1.Task { + dockerRepo := os.Getenv("DOCKER_REPO_OVERRIDE") + if dockerRepo == "" { + t.Fatalf("DOCKER_REPO_OVERRIDE env variable is required") + } + + return &v1alpha1.Task{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: kanikoTaskName, + }, + Spec: v1alpha1.TaskSpec{ + Inputs: &v1alpha1.Inputs{ + Resources: []v1alpha1.TaskResource{ + v1alpha1.TaskResource{ + Name: kanikoResourceName, + Type: v1alpha1.PipelineResourceTypeGit, + }, + }, + }, + BuildSpec: &buildv1alpha1.BuildSpec{ + Timeout: &metav1.Duration{Duration: 2 * time.Minute}, + Steps: []corev1.Container{{ + Name: "kaniko", + Image: "gcr.io/kaniko-project/executor", + Args: []string{"--dockerfile=/workspace/Dockerfile", + fmt.Sprintf("--destination=%s/kanikotasktest", dockerRepo), + "--no-push"}, + }}, + }, + }, + } +} + +func getTaskRun(namespace string) *v1alpha1.TaskRun { + return &v1alpha1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: kanikoTaskRunName, + }, + Spec: v1alpha1.TaskRunSpec{ + TaskRef: v1alpha1.TaskRef{ + Name: kanikoTaskName, + }, + Trigger: v1alpha1.TaskTrigger{ + TriggerRef: v1alpha1.TaskTriggerRef{ + Type: v1alpha1.TaskTriggerTypeManual, + }, + }, + Inputs: v1alpha1.TaskRunInputs{ + Resources: []v1alpha1.PipelineResourceVersion{ + v1alpha1.PipelineResourceVersion{ + ResourceRef: v1alpha1.PipelineResourceRef{ + Name: kanikoResourceName, + }, + Version: "master", + }, + }, + }, + }, + } +} + +// TestTaskRun is an integration test that will verify a TaskRun using kaniko +func TestKanikoTaskRun(t *testing.T) { + logger := logging.GetContextLogger(t.Name()) + c, namespace := setup(t, logger) + + knativetest.CleanupOnInterrupt(func() { tearDown(logger, c.KubeClient, namespace) }, logger) + defer tearDown(logger, c.KubeClient, namespace) + + if _, err := c.PipelineResourceClient.Create(getGitResource(namespace)); err != nil { + t.Fatalf("Failed to create Pipeline Resource `%s`: %s", kanikoResourceName, err) + } + + // Create task + if _, err := c.TaskClient.Create(getTask(namespace, t)); err != nil { + t.Fatalf("Failed to create Task `%s`: %s", kanikoTaskName, err) + } + + // Create TaskRun + if _, err := c.TaskRunClient.Create(getTaskRun(namespace)); err != nil { + t.Fatalf("Failed to create TaskRun `%s`: %s", kanikoTaskRunName, err) + } + + // Verify status of TaskRun (wait for it) + if err := WaitForTaskRunState(c, kanikoTaskRunName, func(tr *v1alpha1.TaskRun) (bool, error) { + if len(tr.Status.Conditions) > 0 && tr.Status.Conditions[0].Status == corev1.ConditionTrue { + return true, nil + } + return false, nil + }, "TaskRunCompleted"); err != nil { + t.Errorf("Error waiting for TaskRun %s to finish: %s", kanikoTaskRunName, err) + } + + // The Build created by the TaskRun will have the same name + b, err := c.BuildClient.Get(kanikoTaskRunName, metav1.GetOptions{}) + if err != nil { + t.Errorf("Expected there to be a Build with the same name as TaskRun %s but got error: %s", kanikoTaskRunName, err) + } + cluster := b.Status.Cluster + if cluster == nil || cluster.PodName == "" { + t.Fatalf("Expected build status to have a podname but it didn't!") + } + podName := cluster.PodName + pods := c.KubeClient.Kube.CoreV1().Pods(namespace) + t.Logf("Retrieved pods for podname %s: %s\n", podName, pods) + + req := pods.GetLogs(podName, &corev1.PodLogOptions{}) + readCloser, err := req.Stream() + if err != nil { + t.Fatalf("Failed to open stream to read: %v", err) + } + defer readCloser.Close() + var buf bytes.Buffer + out := bufio.NewWriter(&buf) + _, err = io.Copy(out, readCloser) + if !strings.Contains(buf.String(), kanikoBuildOutput) { + t.Fatalf("Expected output %s from pod %s but got %s", kanikoBuildOutput, podName, buf.String()) + } +}