From c108f3e5e1e3983b14877f365b0cd219528d8795 Mon Sep 17 00:00:00 2001 From: Nader Ziada Date: Wed, 3 Oct 2018 16:43:53 -0400 Subject: [PATCH] git resource for pipeline #58 - add resource to Build as an input source --- docs/pipeline-resources.md | 79 +++++ pkg/apis/pipeline/v1alpha1/git_resource.go | 28 ++ .../taskrun/resources/input_resource_test.go | 313 ++++++++++++++++++ .../taskrun/resources/input_resources.go | 73 ++++ 4 files changed, 493 insertions(+) create mode 100644 docs/pipeline-resources.md create mode 100644 pkg/reconciler/v1alpha1/taskrun/resources/input_resource_test.go create mode 100644 pkg/reconciler/v1alpha1/taskrun/resources/input_resources.go diff --git a/docs/pipeline-resources.md b/docs/pipeline-resources.md new file mode 100644 index 00000000000..a695692c8b5 --- /dev/null +++ b/docs/pipeline-resources.md @@ -0,0 +1,79 @@ +### Pipeline Resources + +## Git Resource + +Git resource represents a [git](https://git-scm.com/) repository, that containes the source code to be built by the pipeline. Adding the git resource as an input to a task will clone this repository and allow the task to perform the required actions on the contents of the repo. + +Use the following example to understand the syntax and strucutre of a Git Resource + + #### Create a git resource using the `PipelineResource` CRD + + ``` +apiVersion: pipeline.knative.dev/v1alpha1 +kind: Resource +metadata: + name: wizzbang-git + namespace: default +spec: + type: git + params: + - name: url + value: github.com/wizzbangcorp/wizzbang + ``` + + Params that can be added are the following: + + 1. URL: represents the location of the git repository + 1. Revision: Git [revision](https://git-scm.com/docs/gitrevisions#_specifying_revisions ) (branch, tag, commit SHA or ref) to clone. If no revision is specified, the resource will default to `latest` from `master` + 1. Service Account: specifies the `name` of a `ServiceAccount` resource object. Add this paramater to run your task with the privileges of the specified service account. If no serviceAccountName field is specified, your task runs using the default service account that is in the namespace of the Pipeline resource object. + + #### Use the defined git resource in a `Task` definition + +``` +apiVersion: pipeline.knative.dev/v1alpha1 +kind: Task +metadata: + name: build-push-task + namespace: default +spec: + inputs: + resources: + - name: wizzbang-git + type: git + params: + - name: PATH_TO_DOCKERFILE + value: string + outputs: + resources: + - name: builtImage + buildSpec: + template: + name: kaniko + arguments: + - name: DOCKERFILE + value: ${PATH_TO_DOCKERFILE} + - name: REGISTRY + value: ${REGISTRY} +``` + + #### And finally set the version in the `TaskRun` definition + +``` +apiVersion: pipeline.knative.dev/v1alpha1 +kind: TaskRun +metadata: + name: build-push-task-run + namespace: default +spec: + taskRef: + name: build-push-task + inputs: + resourcesVersion: + - resourceRef: + name: wizzbang-git + version: HEAD + outputs: + artifacts: + - name: builtImage + type: image +``` diff --git a/pkg/apis/pipeline/v1alpha1/git_resource.go b/pkg/apis/pipeline/v1alpha1/git_resource.go index b0ff88047aa..fdb07ea2c74 100644 --- a/pkg/apis/pipeline/v1alpha1/git_resource.go +++ b/pkg/apis/pipeline/v1alpha1/git_resource.go @@ -16,6 +16,10 @@ limitations under the License. package v1alpha1 +import ( + "strings" +) + // GitResource is an endpoint from which to get data which is required // by a Build/Task for context (e.g. a repo from which to build an image). type GitResource struct { @@ -30,6 +34,25 @@ type GitResource struct { ServiceAccount string `json:"serviceAccount,omitempty"` } +// NewGitResource create a new git resource to pass to Knativve Build +func NewGitResource(r *PipelineResource) *GitResource { + gitResource := GitResource{ + Name: r.Name, + Type: r.Spec.Type, + } + for _, param := range r.Spec.Params { + switch { + case strings.EqualFold(param.Name, "URL"): + gitResource.URL = param.Value + case strings.EqualFold(param.Name, "serviceAccount"): + gitResource.ServiceAccount = param.Value + case strings.EqualFold(param.Name, "Revision"): + gitResource.Revision = param.Value + } + } + return &gitResource +} + // GetName returns the name of the resource func (s GitResource) GetName() string { return s.Name @@ -52,5 +75,10 @@ func (s *GitResource) GetServiceAccountName() string { return s.ServiceAccount } +// GetURL returns the url to be used with this resource +func (s *GitResource) GetURL() string { + return s.URL +} + // GetParams returns the resoruce params func (s GitResource) GetParams() []Param { return []Param{} } diff --git a/pkg/reconciler/v1alpha1/taskrun/resources/input_resource_test.go b/pkg/reconciler/v1alpha1/taskrun/resources/input_resource_test.go new file mode 100644 index 00000000000..3a82dcce38f --- /dev/null +++ b/pkg/reconciler/v1alpha1/taskrun/resources/input_resource_test.go @@ -0,0 +1,313 @@ +/* +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 resources + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + v1alpha1 "github.com/knative/build-pipeline/pkg/apis/pipeline/v1alpha1" + fakeclientset "github.com/knative/build-pipeline/pkg/client/clientset/versioned/fake" + informers "github.com/knative/build-pipeline/pkg/client/informers/externalversions" + listers "github.com/knative/build-pipeline/pkg/client/listers/pipeline/v1alpha1" + "github.com/knative/build-pipeline/pkg/logging" + buildv1alpha1 "github.com/knative/build/pkg/apis/build/v1alpha1" + "go.uber.org/zap" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var ( + pipelineResourceLister listers.PipelineResourceLister + logger *zap.SugaredLogger +) + +func setUp() { + logger, _ = logging.NewLogger("", "") + fakeClient := fakeclientset.NewSimpleClientset() + sharedInfomer := informers.NewSharedInformerFactory(fakeClient, 0) + pipelineResourceInformer := sharedInfomer.Pipeline().V1alpha1().PipelineResources() + pipelineResourceLister = pipelineResourceInformer.Lister() + + res := &v1alpha1.PipelineResource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "workspace", + Namespace: "marshmallow", + }, + Spec: v1alpha1.PipelineResourceSpec{ + Type: "git", + Params: []v1alpha1.Param{ + v1alpha1.Param{ + Name: "Url", + Value: "https://github.com/grafeas/kritis", + }, + }, + }, + } + resWithServiceAccount := &v1alpha1.PipelineResource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "workspace-sa", + Namespace: "marshmallow", + }, + Spec: v1alpha1.PipelineResourceSpec{ + Type: "git", + Params: []v1alpha1.Param{ + v1alpha1.Param{ + Name: "Url", + Value: "https://github.com/grafeas/kritis", + }, + v1alpha1.Param{ + Name: "ServiceAccount", + Value: "kritis-service-account", + }, + }, + }, + } + pipelineResourceInformer.Informer().GetIndexer().Add(res) + pipelineResourceInformer.Informer().GetIndexer().Add(resWithServiceAccount) +} + +func TestAddResourceToBuild(t *testing.T) { + boolTrue := true + + task := &v1alpha1.Task{ + ObjectMeta: metav1.ObjectMeta{ + Name: "build-from-repo", + Namespace: "marshmallow", + }, + Spec: v1alpha1.TaskSpec{ + Inputs: &v1alpha1.Inputs{ + Sources: []v1alpha1.Source{ + v1alpha1.Source{ + Name: "workspace", + Type: "git", + }, + }, + }, + }, + } + taskRun := &v1alpha1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "build-from-repo-run", + Namespace: "marshmallow", + }, + Spec: v1alpha1.TaskRunSpec{ + TaskRef: v1alpha1.TaskRef{ + Name: "simpleTask", + }, + Inputs: v1alpha1.TaskRunInputs{ + Resources: []v1alpha1.PipelineResourceVersion{ + v1alpha1.PipelineResourceVersion{ + ResourceRef: v1alpha1.PipelineResourceRef{ + Name: "workspace", + }, + Version: "master", + }, + }, + }, + }, + } + build := &buildv1alpha1.Build{ + TypeMeta: metav1.TypeMeta{ + Kind: "Build", + APIVersion: "build.knative.dev/v1alpha1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "build-from-repo", + Namespace: "marshmallow", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "pipeline.knative.dev/v1alpha1", + Kind: "TaskRun", + Name: "build-from-repo-run", + Controller: &boolTrue, + BlockOwnerDeletion: &boolTrue, + }, + }, + }, + Spec: buildv1alpha1.BuildSpec{}, + } + + for _, c := range []struct { + desc string + task *v1alpha1.Task + taskRun *v1alpha1.TaskRun + build *buildv1alpha1.Build + wantErr bool + want *buildv1alpha1.Build + }{{ + desc: "simple", + task: task, + taskRun: taskRun, + build: build, + wantErr: false, + want: &buildv1alpha1.Build{ + TypeMeta: metav1.TypeMeta{ + Kind: "Build", + APIVersion: "build.knative.dev/v1alpha1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "build-from-repo", + Namespace: "marshmallow", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "pipeline.knative.dev/v1alpha1", + Kind: "TaskRun", + Name: "build-from-repo-run", + Controller: &boolTrue, + BlockOwnerDeletion: &boolTrue, + }, + }, + }, + Spec: buildv1alpha1.BuildSpec{ + Source: &buildv1alpha1.SourceSpec{ + Git: &buildv1alpha1.GitSourceSpec{ + Url: "https://github.com/grafeas/kritis", + Revision: "master", + }, + }, + }, + }, + }, { + desc: "set revision to default value", + task: task, + taskRun: &v1alpha1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "build-from-repo-run", + Namespace: "marshmallow", + }, + Spec: v1alpha1.TaskRunSpec{ + TaskRef: v1alpha1.TaskRef{ + Name: "simpleTask", + }, + Inputs: v1alpha1.TaskRunInputs{ + Resources: []v1alpha1.PipelineResourceVersion{ + v1alpha1.PipelineResourceVersion{ + ResourceRef: v1alpha1.PipelineResourceRef{ + Name: "workspace", + }, + }, + }, + }, + }, + }, + build: build, + wantErr: false, + want: &buildv1alpha1.Build{ + TypeMeta: metav1.TypeMeta{ + Kind: "Build", + APIVersion: "build.knative.dev/v1alpha1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "build-from-repo", + Namespace: "marshmallow", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "pipeline.knative.dev/v1alpha1", + Kind: "TaskRun", + Name: "build-from-repo-run", + Controller: &boolTrue, + BlockOwnerDeletion: &boolTrue, + }, + }, + }, + Spec: buildv1alpha1.BuildSpec{ + Source: &buildv1alpha1.SourceSpec{ + Git: &buildv1alpha1.GitSourceSpec{ + Url: "https://github.com/grafeas/kritis", + Revision: "master", + }, + }, + }, + }, + }, { + desc: "set service account if provided", + task: &v1alpha1.Task{ + ObjectMeta: metav1.ObjectMeta{ + Name: "build-from-repo", + Namespace: "marshmallow", + }, + Spec: v1alpha1.TaskSpec{ + Inputs: &v1alpha1.Inputs{ + Sources: []v1alpha1.Source{ + v1alpha1.Source{ + Name: "workspace-sa", + Type: "git", + }, + }, + }, + }, + }, + taskRun: taskRun, + build: build, + wantErr: false, + want: &buildv1alpha1.Build{ + TypeMeta: metav1.TypeMeta{ + Kind: "Build", + APIVersion: "build.knative.dev/v1alpha1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "build-from-repo", + Namespace: "marshmallow", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "pipeline.knative.dev/v1alpha1", + Kind: "TaskRun", + Name: "build-from-repo-run", + Controller: &boolTrue, + BlockOwnerDeletion: &boolTrue, + }, + }, + }, + Spec: buildv1alpha1.BuildSpec{ + Source: &buildv1alpha1.SourceSpec{ + Git: &buildv1alpha1.GitSourceSpec{ + Url: "https://github.com/grafeas/kritis", + Revision: "master", + }, + }, + ServiceAccountName: "kritis-service-account", + }, + }, + }, { + desc: "invalid resource name", + task: &v1alpha1.Task{ + ObjectMeta: metav1.ObjectMeta{ + Name: "build-from-repo", + Namespace: "marshmallow", + }, + Spec: v1alpha1.TaskSpec{ + Inputs: &v1alpha1.Inputs{ + Sources: []v1alpha1.Source{ + v1alpha1.Source{ + Name: "workspace-invalid", + Type: "git", + }, + }, + }, + }, + }, + taskRun: taskRun, + build: build, + wantErr: true, + want: nil, + }, + } { + setUp() + t.Run(c.desc, func(t *testing.T) { + got, err := AddInputResource(c.build, c.task, c.taskRun, pipelineResourceLister, logger) + if (err != nil) != c.wantErr { + t.Errorf("Test: %q; NewControllerConfigFromConfigMap() error = %v, WantErr %v", c.desc, err, c.wantErr) + } + if d := cmp.Diff(got, c.want); d != "" { + t.Errorf("Diff:\n%s", d) + } + }) + } +} diff --git a/pkg/reconciler/v1alpha1/taskrun/resources/input_resources.go b/pkg/reconciler/v1alpha1/taskrun/resources/input_resources.go new file mode 100644 index 00000000000..c3363f09d31 --- /dev/null +++ b/pkg/reconciler/v1alpha1/taskrun/resources/input_resources.go @@ -0,0 +1,73 @@ +/* +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 resources + +import ( + v1alpha1 "github.com/knative/build-pipeline/pkg/apis/pipeline/v1alpha1" + listers "github.com/knative/build-pipeline/pkg/client/listers/pipeline/v1alpha1" + buildv1alpha1 "github.com/knative/build/pkg/apis/build/v1alpha1" + "go.uber.org/zap" +) + +// AddInputResource will update the input build with the input resource from the task +func AddInputResource( + build *buildv1alpha1.Build, + task *v1alpha1.Task, + taskRun *v1alpha1.TaskRun, + pipelineResourceLister listers.PipelineResourceLister, + logger *zap.SugaredLogger, +) (*buildv1alpha1.Build, error) { + + var gitResource *v1alpha1.GitResource + + for _, input := range task.Spec.Inputs.Sources { + resource, err := pipelineResourceLister.PipelineResources(task.Namespace).Get(input.Name) + if err != nil { + + logger.Errorf("Failed to reconcile TaskRun: %q failed to Get Pipeline Resource: %q", task.Name, input.Name) + return nil, err + } + if resource.Spec.Type == v1alpha1.PipelineResourceTypeGit { + gitResource = v1alpha1.NewGitResource(resource) + for _, trInput := range taskRun.Spec.Inputs.Resources { + if trInput.ResourceRef.Name == input.Name { + gitResource.Revision = trInput.Version + break + } + } + break + } + } + // default revision to master is nothing is provided + if gitResource.Revision == "" { + gitResource.Revision = "master" + } + + gitSourceSpec := &buildv1alpha1.GitSourceSpec{ + Url: gitResource.URL, + Revision: gitResource.Revision, + } + + build.Spec.Source = &buildv1alpha1.SourceSpec{Git: gitSourceSpec} + // add service account name if available, otherwise Build will + // use the default service account in the namespace + if gitResource.ServiceAccount != "" { + build.Spec.ServiceAccountName = gitResource.ServiceAccount + } + + return build, nil +}