feat: S2I strategy for on cluster build (#1191)

* feat: S2I strategy for on cluster build

Signed-off-by: Zbynek Roubalik <zroubalik@gmail.com>

* use upstream `s2i` task only

Signed-off-by: Zbynek Roubalik <zroubalik@gmail.com>

* use custom func-s2i task

Signed-off-by: Zbynek Roubalik <zroubalik@gmail.com>

Signed-off-by: Zbynek Roubalik <zroubalik@gmail.com>
This commit is contained in:
Zbynek Roubalik 2022-08-25 15:03:19 +02:00 committed by GitHub
parent e6ec11b0e5
commit 1112aaa2fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 444 additions and 96 deletions

View File

@ -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 {

View File

@ -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=<INSERT_YOUR_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

View File

@ -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

View File

@ -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 {

View File

@ -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)
})
}
}

View File

@ -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,
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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) {