From 1112aaa2fe5fb5aa349251a34c2fccc529498b0d Mon Sep 17 00:00:00 2001 From: Zbynek Roubalik Date: Thu, 25 Aug 2022 15:03:19 +0200 Subject: [PATCH] feat: S2I strategy for on cluster build (#1191) * feat: S2I strategy for on cluster build Signed-off-by: Zbynek Roubalik * use upstream `s2i` task only Signed-off-by: Zbynek Roubalik * use custom func-s2i task Signed-off-by: Zbynek Roubalik Signed-off-by: Zbynek Roubalik --- cmd/client.go | 6 +- docs/reference/on_cluster_build.md | 16 +- .../tekton/task/func-s2i/0.1/func-s2i.yaml | 105 +++++++++++++ pipelines/tekton/resources.go | 148 +++++++++++------- pipelines/tekton/resources_test.go | 54 +++++++ pipelines/tekton/tasks.go | 48 +++++- pipelines/tekton/validate.go | 25 ++- pipelines/tekton/validate_test.go | 82 +++++++--- s2i/builder_test.go | 56 +++++++ 9 files changed, 444 insertions(+), 96 deletions(-) create mode 100644 pipelines/resources/tekton/task/func-s2i/0.1/func-s2i.yaml create mode 100644 pipelines/tekton/resources_test.go diff --git a/cmd/client.go b/cmd/client.go index 8080e0be..039a804d 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -57,8 +57,9 @@ func NewClientFactory(n func() *fn.Client) ClientFactory { // the currently configured is used. // 'Verbose' indicates the system should write out a higher amount of logging. // Example: -// client, done := NewClient("",false) -// defer done() +// +// client, done := NewClient("",false) +// defer done() func NewClient(cfg ClientConfig, options ...fn.Option) (*fn.Client, func()) { var ( p = progress.New(cfg.Verbose) // updates the CLI @@ -143,7 +144,6 @@ func newTektonPipelinesProvider(namespace string, progress *progress.Bar, creds } return tekton.NewPipelinesProvider(options...) - } func newKnativeDeployer(namespace string, verbose bool) fn.Deployer { diff --git a/docs/reference/on_cluster_build.md b/docs/reference/on_cluster_build.md index 808002d4..88f314c9 100644 --- a/docs/reference/on_cluster_build.md +++ b/docs/reference/on_cluster_build.md @@ -1,6 +1,6 @@ # Building Functions on Cluster with Tekton Pipelines -This guide describes how you can build a Function on Cluster with Tekton Pipelines. The on cluster build is enabled by fetching Function source code from a remote Git repository. +This guide describes how you can build a Function on Cluster with Tekton Pipelines. The on cluster build is enabled by fetching Function source code from a remote Git repository. Buildpacks or S2I builder strategy can be used to build the Function image. ## Prerequisite 1. Install Tekton Pipelines on the cluster. Please refer to [Tekton Pipelines documentation](https://github.com/tektoncd/pipeline/blob/main/docs/install.md) or run the following command: @@ -14,10 +14,15 @@ In each namespace that you would like to run Pipelines and deploy a Function you ```bash kubectl apply -f https://raw.githubusercontent.com/tektoncd/catalog/master/task/git-clone/0.4/git-clone.yaml ``` -2. Install the Functions Buildpacks Tekton Task to be able to build the Function image: -```bash -kubectl apply -f https://raw.githubusercontent.com/knative-sandbox/kn-plugin-func/main/pipelines/resources/tekton/task/func-buildpacks/0.1/func-buildpacks.yaml -``` +2. Install a Tekton Task responsible for building the Function, based on the builder preference (Buildpacks or S2I) + 1. For Buildpacks builder install the Functions Buildpacks Tekton Task: + ```bash + kubectl apply -f https://raw.githubusercontent.com/knative-sandbox/kn-plugin-func/main/pipelines/resources/tekton/task/func-buildpacks/0.1/func-buildpacks.yaml + ``` + 2. For S2I builder install the S2I task: + ```bash + kubectl apply -f https://raw.githubusercontent.com/knative-sandbox/kn-plugin-func/main/pipelines/resources/tekton/task/func-s2i/0.1/func-s2i.yaml + ``` 3. Install the `kn func` Deploy Tekton Task to be able to deploy the Function on in the Pipeline: ```bash kubectl apply -f https://raw.githubusercontent.com/knative-sandbox/kn-plugin-func/main/pipelines/resources/tekton/task/func-deploy/0.1/func-deploy.yaml @@ -82,6 +87,7 @@ export NAMESPACE= kubectl delete clusterrolebinding $NAMESPACE:knative-serving-namespaced-admin kubectl delete task.tekton.dev git-clone kubectl delete task.tekton.dev func-buildpacks +kubectl delete task.tekton.dev func-s2i kubectl delete task.tekton.dev func-deploy ``` 2. Uninstall Tekton Pipelines diff --git a/pipelines/resources/tekton/task/func-s2i/0.1/func-s2i.yaml b/pipelines/resources/tekton/task/func-s2i/0.1/func-s2i.yaml new file mode 100644 index 00000000..51c47e3a --- /dev/null +++ b/pipelines/resources/tekton/task/func-s2i/0.1/func-s2i.yaml @@ -0,0 +1,105 @@ +apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: func-s2i + labels: + app.kubernetes.io/version: "0.1" + annotations: + tekton.dev/pipelines.minVersion: "0.17.0" + tekton.dev/categories: Image Build + tekton.dev/tags: image-build + tekton.dev/platforms: "linux/amd64" +spec: + description: >- + Knative Functions Source-to-Image (S2I) is a toolkit and workflow for building reproducible + container images from source code + + S2I produces images by injecting source code into a base S2I container image + and letting the container prepare that source code for execution. The base + S2I container images contains the language runtime and build tools needed for + building and running the source code. + + params: + - name: BUILDER_IMAGE + description: The location of the s2i builder image. + - name: IMAGE + description: Reference of the image S2I will produce. + - name: PATH_CONTEXT + description: The location of the path to run s2i from. + default: . + - name: TLSVERIFY + description: Verify the TLS on the registry endpoint (for push/pull to a non-TLS registry) + default: "true" + - name: LOGLEVEL + description: Log level when running the S2I binary + default: "0" + - name: ENV_VARS + type: array + description: Environment variables to set during _build-time_. + default: [] + workspaces: + - name: source + - name: sslcertdir + optional: true + - name: dockerconfig + description: >- + An optional workspace that allows providing a .docker/config.json file + for Buildah to access the container registry. + The file should be placed at the root of the Workspace with name config.json. + optional: true + results: + - name: IMAGE_DIGEST + description: Digest of the image just built. + steps: + - name: generate + image: quay.io/openshift-pipeline/s2i:nightly + workingDir: $(workspaces.source.path) + args: ["$(params.ENV_VARS[*])"] + script: | + echo "Processing Build Environment Variables" + echo "" > /env-vars/env-file + for var in "$@" + do + echo "$var" >> /env-vars/env-file + done + + echo "Generated Build Env Var file" + echo "------------------------------" + cat /env-vars/env-file + echo "------------------------------" + + /usr/local/bin/s2i --loglevel=$(params.LOGLEVEL) build $(params.PATH_CONTEXT) $(params.BUILDER_IMAGE) \ + --as-dockerfile /gen-source/Dockerfile.gen --environment-file /env-vars/env-file + volumeMounts: + - mountPath: /gen-source + name: gen-source + - mountPath: /env-vars + name: env-vars + - name: build + image: quay.io/buildah/stable:v1.17.0 + workingDir: /gen-source + script: | + [[ "$(workspaces.sslcertdir.bound)" == "true" ]] && CERT_DIR_FLAG="--cert-dir $(workspaces.sslcertdir.path)" + buildah ${CERT_DIR_FLAG} bud --storage-driver=vfs --tls-verify=$(params.TLSVERIFY) --layers \ + -f /gen-source/Dockerfile.gen -t $(params.IMAGE) . + + [[ "$(workspaces.dockerconfig.bound)" == "true" ]] && export DOCKER_CONFIG="$(workspaces.dockerconfig.path)" + buildah ${CERT_DIR_FLAG} push --storage-driver=vfs --tls-verify=$(params.TLSVERIFY) --digestfile $(workspaces.source.path)/image-digest \ + $(params.IMAGE) docker://$(params.IMAGE) + + cat $(workspaces.source.path)/image-digest | tee /tekton/results/IMAGE_DIGEST + volumeMounts: + - name: varlibcontainers + mountPath: /var/lib/containers + - mountPath: /gen-source + name: gen-source + securityContext: + capabilities: + add: ["SETFCAP"] + volumes: + - emptyDir: {} + name: varlibcontainers + - emptyDir: {} + name: gen-source + - emptyDir: {} + name: env-vars diff --git a/pipelines/tekton/resources.go b/pipelines/tekton/resources.go index 790d9344..5eb7fb33 100644 --- a/pipelines/tekton/resources.go +++ b/pipelines/tekton/resources.go @@ -12,6 +12,7 @@ import ( fn "knative.dev/kn-plugin-func" "knative.dev/kn-plugin-func/builders" "knative.dev/kn-plugin-func/buildpacks" + "knative.dev/kn-plugin-func/s2i" ) func deletePipelines(ctx context.Context, namespaceOverride string, listOptions metav1.ListOptions) (err error) { @@ -33,6 +34,8 @@ func deletePipelineRuns(ctx context.Context, namespaceOverride string, listOptio } func generatePipeline(f fn.Function, labels map[string]string) *pplnv1beta1.Pipeline { + + // ----- General properties pipelineName := getPipelineName(f) params := []pplnv1beta1.ParamSpec{ @@ -56,7 +59,7 @@ func generatePipeline(f fn.Function, labels map[string]string) *pplnv1beta1.Pipe }, { Name: "builderImage", - Description: "Buildpacks builder image to be used", + Description: "Builder image to be used", }, { Name: "buildEnvs", @@ -67,14 +70,30 @@ func generatePipeline(f fn.Function, labels map[string]string) *pplnv1beta1.Pipe workspaces := []pplnv1beta1.PipelineWorkspaceDeclaration{ {Name: "source-workspace", Description: "Directory where function source is located."}, - {Name: "cache-workspace", Description: "Directory where Buildpacks cache is stored."}, {Name: "dockerconfig-workspace", Description: "Directory containing image registry credentials stored in `config.json` file.", Optional: true}, } + var taskBuild pplnv1beta1.PipelineTask + + // Deploy step that uses an image produced by S2I builds needs explicit reference to the image + referenceImageFromPreviousTaskResults := false + + if f.Builder == builders.Pack { + // ----- Buildpacks related properties + workspaces = append(workspaces, pplnv1beta1.PipelineWorkspaceDeclaration{Name: "cache-workspace", Description: "Directory where Buildpacks cache is stored."}) + taskBuild = taskBuildpacks(taskNameFetchSources) + + } else if f.Builder == builders.S2I { + // ----- S2I build related properties + taskBuild = taskS2iBuild(taskNameFetchSources) + referenceImageFromPreviousTaskResults = true + } + + // ----- Pipeline definition tasks := pplnv1beta1.PipelineTaskList{ taskFetchSources(), - taskBuild(taskNameFetchSources), - taskDeploy(taskNameBuild), + taskBuild, + taskDeploy(taskNameBuild, referenceImageFromPreviousTaskResults), } return &pplnv1beta1.Pipeline{ @@ -92,11 +111,15 @@ func generatePipeline(f fn.Function, labels map[string]string) *pplnv1beta1.Pipe func generatePipelineRun(f fn.Function, labels map[string]string) *pplnv1beta1.PipelineRun { + // ----- General properties revision := "" if f.Git.Revision != nil { revision = *f.Git.Revision } contextDir := "" + if f.Builder == builders.S2I { + contextDir = "." + } if f.Git.ContextDir != nil { contextDir = *f.Git.ContextDir } @@ -113,66 +136,73 @@ func generatePipelineRun(f fn.Function, labels map[string]string) *pplnv1beta1.P buildEnvs = pplnv1beta1.NewArrayOrString(envs[0], envs[1:]...) } + params := []pplnv1beta1.Param{ + { + Name: "gitRepository", + Value: *pplnv1beta1.NewArrayOrString(*f.Git.URL), + }, + { + Name: "gitRevision", + Value: *pplnv1beta1.NewArrayOrString(revision), + }, + { + Name: "contextDir", + Value: *pplnv1beta1.NewArrayOrString(contextDir), + }, + { + Name: "imageName", + Value: *pplnv1beta1.NewArrayOrString(f.Image), + }, + { + Name: "builderImage", + Value: *pplnv1beta1.NewArrayOrString(getBuilderImage(f)), + }, + { + Name: "buildEnvs", + Value: *buildEnvs, + }, + } + + workspaces := []pplnv1beta1.WorkspaceBinding{ + { + Name: "source-workspace", + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: getPipelinePvcName(f), + }, + SubPath: "source", + }, + { + Name: "dockerconfig-workspace", + Secret: &corev1.SecretVolumeSource{ + SecretName: getPipelineSecretName(f), + }, + }, + } + + if f.Builder == builders.Pack { + // ----- Buildpacks related properties + + workspaces = append(workspaces, pplnv1beta1.WorkspaceBinding{ + Name: "cache-workspace", + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: getPipelinePvcName(f), + }, + SubPath: "cache", + }) + } + + // ----- PipelineRun definition return &pplnv1beta1.PipelineRun{ ObjectMeta: v1.ObjectMeta{ GenerateName: fmt.Sprintf("%s-run-", getPipelineName(f)), Labels: labels, }, - Spec: pplnv1beta1.PipelineRunSpec{ PipelineRef: &pplnv1beta1.PipelineRef{ Name: getPipelineName(f), }, - - Params: []pplnv1beta1.Param{ - { - Name: "gitRepository", - Value: *pplnv1beta1.NewArrayOrString(*f.Git.URL), - }, - { - Name: "gitRevision", - Value: *pplnv1beta1.NewArrayOrString(revision), - }, - { - Name: "contextDir", - Value: *pplnv1beta1.NewArrayOrString(contextDir), - }, - { - Name: "imageName", - Value: *pplnv1beta1.NewArrayOrString(f.Image), - }, - { - Name: "builderImage", - Value: *pplnv1beta1.NewArrayOrString(getBuilderImage(f)), - }, - { - Name: "buildEnvs", - Value: *buildEnvs, - }, - }, - - Workspaces: []pplnv1beta1.WorkspaceBinding{ - { - Name: "source-workspace", - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: getPipelinePvcName(f), - }, - SubPath: "source", - }, - { - Name: "cache-workspace", - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: getPipelinePvcName(f), - }, - SubPath: "cache", - }, - { - Name: "dockerconfig-workspace", - Secret: &corev1.SecretVolumeSource{ - SecretName: getPipelineSecretName(f), - }, - }, - }, + Params: params, + Workspaces: workspaces, }, } } @@ -182,12 +212,16 @@ func generatePipelineRun(f fn.Function, labels map[string]string) *pplnv1beta1.P // language runtime. Errors are checked elsewhere, so at this level they // manifest as an inability to get a builder image = empty string. func getBuilderImage(f fn.Function) (name string) { - name, _ = buildpacks.BuilderImage(f, builders.Pack) + if f.Builder == builders.S2I { + name, _ = s2i.BuilderImage(f, builders.S2I) + } else { + name, _ = buildpacks.BuilderImage(f, builders.Pack) + } return } func getPipelineName(f fn.Function) string { - return fmt.Sprintf("%s-%s-pipeline", f.Name, f.BuildType) + return fmt.Sprintf("%s-%s-%s-pipeline", f.Name, f.BuildType, f.Builder) } func getPipelineSecretName(f fn.Function) string { diff --git a/pipelines/tekton/resources_test.go b/pipelines/tekton/resources_test.go new file mode 100644 index 00000000..6edaedff --- /dev/null +++ b/pipelines/tekton/resources_test.go @@ -0,0 +1,54 @@ +//go:build !integration +// +build !integration + +package tekton + +import ( + "testing" + + fn "knative.dev/kn-plugin-func" + "knative.dev/kn-plugin-func/builders" +) + +func Test_generatePipeline(t *testing.T) { + testGitRepo := "http://git-repo/git.git" + testGit := fn.Git{ + URL: &testGitRepo, + } + + tests := []struct { + name string + function fn.Function + taskBuildName string + }{ + { + name: "Pack builder - use buildpacks", + function: fn.Function{Builder: builders.Pack, Git: testGit}, + taskBuildName: "func-buildpacks", + }, + { + name: "s2i builder - use", + function: fn.Function{Builder: builders.S2I, Git: testGit}, + taskBuildName: "func-s2i", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ppl := generatePipeline(tt.function, map[string]string{}) + + for _, task := range ppl.Spec.Tasks { + // let's check what is the Task used for build task + if task.Name == taskNameBuild { + if task.TaskRef.Name != tt.taskBuildName { + t.Errorf("generatePipeline(), for builder = %q: wanted build Task = %q, got = %q", tt.function.Builder, tt.taskBuildName, task.TaskRef.Name) + } + return + } + } + + // we haven't found the build Task -> fail + t.Errorf("generatePipeline(), wasn't able to find build related task named = %q", taskNameBuild) + }) + } +} diff --git a/pipelines/tekton/tasks.go b/pipelines/tekton/tasks.go index bb26187c..c3a611fe 100644 --- a/pipelines/tekton/tasks.go +++ b/pipelines/tekton/tasks.go @@ -1,6 +1,8 @@ package tekton import ( + "fmt" + pplnv1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" ) @@ -27,7 +29,7 @@ func taskFetchSources() pplnv1beta1.PipelineTask { } } -func taskBuild(runAfter string) pplnv1beta1.PipelineTask { +func taskBuildpacks(runAfter string) pplnv1beta1.PipelineTask { return pplnv1beta1.PipelineTask{ Name: taskNameBuild, TaskRef: &pplnv1beta1.TaskRef{ @@ -57,9 +59,47 @@ func taskBuild(runAfter string) pplnv1beta1.PipelineTask { }}, }, } + +} +func taskS2iBuild(runAfter string) pplnv1beta1.PipelineTask { + params := []pplnv1beta1.Param{ + {Name: "IMAGE", Value: *pplnv1beta1.NewArrayOrString("$(params.imageName)")}, + {Name: "PATH_CONTEXT", Value: *pplnv1beta1.NewArrayOrString("$(params.contextDir)")}, + {Name: "BUILDER_IMAGE", Value: *pplnv1beta1.NewArrayOrString("$(params.builderImage)")}, + {Name: "ENV_VARS", Value: pplnv1beta1.ArrayOrString{ + Type: pplnv1beta1.ParamTypeArray, + ArrayVal: []string{"$(params.buildEnvs[*])"}, + }}, + } + return pplnv1beta1.PipelineTask{ + Name: taskNameBuild, + TaskRef: &pplnv1beta1.TaskRef{ + Name: "func-s2i", + }, + RunAfter: []string{runAfter}, + Workspaces: []pplnv1beta1.WorkspacePipelineTaskBinding{ + { + Name: "source", + Workspace: "source-workspace", + }, + { + Name: "dockerconfig", + Workspace: "dockerconfig-workspace", + }}, + Params: params, + } + } -func taskDeploy(runAfter string) pplnv1beta1.PipelineTask { +func taskDeploy(runAfter string, referenceImageFromPreviousTaskResults bool) pplnv1beta1.PipelineTask { + + params := []pplnv1beta1.Param{{Name: "path", Value: *pplnv1beta1.NewArrayOrString("$(workspaces.source.path)/$(params.contextDir)")}} + + // Deploy step that uses an image produced by S2I builds needs explicit reference to the image + if referenceImageFromPreviousTaskResults { + params = append(params, pplnv1beta1.Param{Name: "image", Value: *pplnv1beta1.NewArrayOrString(fmt.Sprintf("$(params.imageName)@$(tasks.%s.results.IMAGE_DIGEST)", runAfter))}) + } + return pplnv1beta1.PipelineTask{ Name: taskNameDeploy, TaskRef: &pplnv1beta1.TaskRef{ @@ -70,8 +110,6 @@ func taskDeploy(runAfter string) pplnv1beta1.PipelineTask { Name: "source", Workspace: "source-workspace", }}, - Params: []pplnv1beta1.Param{ - {Name: "path", Value: *pplnv1beta1.NewArrayOrString("$(workspaces.source.path)/$(params.contextDir)")}, - }, + Params: params, } } diff --git a/pipelines/tekton/validate.go b/pipelines/tekton/validate.go index 464e729f..330d2667 100644 --- a/pipelines/tekton/validate.go +++ b/pipelines/tekton/validate.go @@ -5,6 +5,8 @@ import ( "fmt" fn "knative.dev/kn-plugin-func" + "knative.dev/kn-plugin-func/builders" + "knative.dev/kn-plugin-func/s2i" ) var ( @@ -23,16 +25,23 @@ func (e ErrRuntimeNotSupported) Error() string { } func validatePipeline(f fn.Function) error { - if f.Runtime == "" { - return ErrRuntimeRequired - } + if f.Builder == builders.Pack { + if f.Runtime == "" { + return ErrRuntimeRequired + } - if f.Runtime == "go" || f.Runtime == "rust" { - return ErrRuntimeNotSupported{f.Runtime} - } + if f.Runtime == "go" || f.Runtime == "rust" { + return ErrRuntimeNotSupported{f.Runtime} + } - if len(f.Buildpacks) > 0 { - return ErrBuilpacksNotSupported + if len(f.Buildpacks) > 0 { + return ErrBuilpacksNotSupported + } + } else if f.Builder == builders.S2I { + _, err := s2i.BuilderImage(f, builders.S2I) + return err + } else { + return builders.ErrUnknownBuilder{Name: f.Builder} } return nil diff --git a/pipelines/tekton/validate_test.go b/pipelines/tekton/validate_test.go index cfe47405..e822adb7 100644 --- a/pipelines/tekton/validate_test.go +++ b/pipelines/tekton/validate_test.go @@ -7,6 +7,7 @@ import ( "testing" fn "knative.dev/kn-plugin-func" + "knative.dev/kn-plugin-func/builders" ) func Test_validatePipeline(t *testing.T) { @@ -19,45 +20,90 @@ func Test_validatePipeline(t *testing.T) { wantErr bool }{ { - name: "Without runtime - without additional Buildpacks", + name: "Without runtime - without builder - without additional Buildpacks", function: fn.Function{}, wantErr: true, }, { - name: "Without runtime - with additional Buildpacks", + name: "Without runtime - pack builder - without additional Buildpacks", + function: fn.Function{Builder: builders.Pack}, + wantErr: true, + }, + { + name: "Without runtime - s2i builder", + function: fn.Function{Builder: builders.S2I}, + wantErr: true, + }, + { + name: "Without runtime - without builder - with additional Buildpacks", function: fn.Function{Buildpacks: testBuildpacks}, wantErr: true, }, { - name: "Supported runtime - without additional Buildpacks", + name: "Without runtime - pack builder - with additional Buildpacks", + function: fn.Function{Builder: builders.Pack, Buildpacks: testBuildpacks}, + wantErr: true, + }, + { + name: "Without runtime - s2i builder", + function: fn.Function{Builder: builders.S2I, Buildpacks: testBuildpacks}, + wantErr: true, + }, + { + name: "Supported runtime - without builder - without additional Buildpacks", function: fn.Function{Runtime: "node"}, + wantErr: true, + }, + { + name: "Supported runtime - pack builder - without additional Buildpacks", + function: fn.Function{Builder: builders.Pack, Runtime: "node"}, wantErr: false, }, { - name: "Supported runtime - with additional Buildpacks", - function: fn.Function{Runtime: "node", Buildpacks: testBuildpacks}, - wantErr: true, - }, - { - name: "Unsupported runtime - Go - without additional Buildpacks", - function: fn.Function{Runtime: "go"}, - wantErr: true, - }, - { - name: "Supported runtime - Quarkus - without additional Buildpacks", - function: fn.Function{Runtime: "quarkus"}, + name: "Supported runtime - s2i builder", + function: fn.Function{Builder: builders.S2I, Runtime: "node"}, wantErr: false, }, { - name: "Unsupported runtime - Rust - without additional Buildpacks", - function: fn.Function{Runtime: "rust"}, + name: "Supported runtime - pack builder - with additional Buildpacks", + function: fn.Function{Builder: builders.Pack, Runtime: "node", Buildpacks: testBuildpacks}, wantErr: true, }, { - name: "Unsupported runtime - Go - with additional Buildpacks", + name: "Unsupported runtime - Go - pack builder - without additional Buildpacks", + function: fn.Function{Builder: builders.Pack, Runtime: "go"}, + wantErr: true, + }, + { + name: "Unsupported runtime - Go - pack builder - with additional Buildpacks", function: fn.Function{Runtime: "go", Buildpacks: testBuildpacks}, wantErr: true, }, + { + name: "Unsupported runtime - Go - s2i builder", + function: fn.Function{Builder: builders.S2I, Runtime: "go"}, + wantErr: true, + }, + { + name: "Supported runtime - Quarkus - pack builder - without additional Buildpacks", + function: fn.Function{Builder: builders.Pack, Runtime: "quarkus"}, + wantErr: false, + }, + { + name: "Supported runtime - Quarkus - s2i builder", + function: fn.Function{Builder: builders.S2I, Runtime: "quarkus"}, + wantErr: false, + }, + { + name: "Unsupported runtime - Rust - pack builder - without additional Buildpacks", + function: fn.Function{Builder: builders.Pack, Runtime: "rust"}, + wantErr: true, + }, + { + name: "Unsupported runtime - Rust - s2i builder", + function: fn.Function{Builder: builders.S2I, Runtime: "rust"}, + wantErr: true, + }, } for _, tt := range tests { diff --git a/s2i/builder_test.go b/s2i/builder_test.go index 381ea428..63600b57 100644 --- a/s2i/builder_test.go +++ b/s2i/builder_test.go @@ -31,6 +31,62 @@ import ( . "knative.dev/kn-plugin-func/testing" ) +// Test_BuildImages ensures that supported runtimes returns builder image +func Test_BuildImages(t *testing.T) { + + tests := []struct { + name string + function fn.Function + wantErr bool + }{ + { + name: "Without builder - without runtime", + function: fn.Function{}, + wantErr: true, + }, + { + name: "Without builder - supported runtime - node", + function: fn.Function{Runtime: "node"}, + wantErr: false, + }, + { + name: "Without builder - supported runtime - typescript", + function: fn.Function{Runtime: "typescript"}, + wantErr: false, + }, + { + name: "Without builder - supported runtime - quarkus", + function: fn.Function{Runtime: "quarkus"}, + wantErr: false, + }, + { + name: "Without builder - unsupported runtime - go", + function: fn.Function{Runtime: "go"}, + wantErr: true, + }, + { + name: "Without builder - unsupported runtime - python", + function: fn.Function{Runtime: "python"}, + wantErr: true, + }, + { + name: "Without builder - unsupported runtime - rust", + function: fn.Function{Runtime: "rust"}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := s2i.BuilderImage(tt.function, builders.S2I) + if (err != nil) != tt.wantErr { + t.Errorf("BuilderImage() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} + // Test_BuilderImageDefault ensures that a function being built which does not // define a Builder Image will default. func Test_BuilderImageDefault(t *testing.T) {