# Build with Shipwright in OpenFunction
## Shipwright
Shipwright is an extensible framework for building container images on Kubernetes.
Shipwright supports any tool that can build container images in Kubernetes clusters, such as:
- Kaniko
- Cloud Native Buildpacks
- BuildKit
- Buildah
## Integrated
Shipwright can build images through Dockerfile, which can greatly enrich the ability of OpenFunction.
Now the Shipwright is at a relatively early stage, many functions are not perfect. Based on this fact, I designed three different methods to integrated Shipwright to OpenFunction.
### Plan A: Use Shipwright to manage build
Now the OpenFunction used tekton to build images. OpenFunction controller needs to manage the task, taskrun, pipeline, pipelinerun and other objects. The Shipwright encapsulates tekton, simplifying these objects into `Build`, `BuildRun`, and `Strategy`.
We can use Shipwright to build the image to reduce the work of the controller.
Firstly, define a `ClusterBuildStrategy` named `openfunction` like this.
```yaml=
apiVersion: shipwright.io/v1alpha1
kind: ClusterBuildStrategy
metadata:
name: openfunction
spec:
parameters:
- name: APP_IMAGE
description: The name of where to store the app image.
- name: SOURCE_SUBPATH
description: A subpath within the source input where the source to build is located.
default: ""
- name: PROCESS_TYPE
description: The default process type to set on the image.
default: "web"
- name: RUN_IMAGE
description: Reference to a run image to use.
default: ""
- name: CACHE_IMAGE
description: The name of the persistent app cache image (if no cache workspace is provided).
default: ""
- name: SKIP_RESTORE
description: Do not write layer metadata or restore cached layers.
default: "false"
- name: USER_ID
description: The user ID of the builder image user.
default: "1000"
- name: GROUP_ID
description: The group ID of the builder image user.
default: "1000"
- name: PLATFORM_DIR
description: The name of the platform directory.
default: empty-dir
buildSteps:
- name: prepare
image: docker.io/library/bash:5.1.4@sha256:b208215a4655538be652b2769d82e576bc4d0a2bb132144c060efc5be8c3f5d6
command: ["/bin/sh"]
args:
- -c
- |
#!/usr/bin/env bash
set -e
for path in "/cache" "/tekton/home" "/layers" "/workspace/source"; do
echo "> Setting permissions on '$path'..."
chown -R "$(params.USER_ID):$(params.GROUP_ID)" "$path"
done
volumeMounts:
- name: cache
mountPath: /cache
- name: layers-dir
mountPath: /layers
- name: $(params.PLATFORM_DIR)
mountPath: /platform
securityContext:
privileged: true
- name: create
image: openfunction/builder:v1
imagePullPolicy: Always
command: [ "/cnb/lifecycle/creator" ]
args:
- "-app=/workspace/source/$(params.SOURCE_SUBPATH)"
- "-cache-dir=/cache"
- "-uid=$(params.USER_ID)"
- "-gid=$(params.GROUP_ID)"
- "-layers=/layers"
- "-platform=/platform"
- "-report=/layers/report.toml"
- "-process-type=$(params.PROCESS_TYPE)"
- "-skip-restore=$(params.SKIP_RESTORE)"
- "-previous-image=$(params.APP_IMAGE)"
- "-run-image=$(params.RUN_IMAGE)"
- "$(params.APP_IMAGE)"
volumeMounts:
- name: cache
mountPath: /cache
- name: layers-dir
mountPath: /layers
- name: $(params.PLATFORM_DIR)
mountPath: /platform
securityContext:
runAsUser: 1000
runAsGroup: 1000
- name: results
image: docker.io/library/bash:5.1.4@sha256:b208215a4655538be652b2769d82e576bc4d0a2bb132144c060efc5be8c3f5d6
command: ["/bin/sh"]
args:
- -c
- |
#!/usr/bin/env bash
set -e
cat /layers/report.toml | grep "digest" | cut -d'"' -f2 | cut -d'"' -f2 | tr -d '\n' | tee $(results.shp-image-digest.path)
volumeMounts:
- name: layers-dir
mountPath: /layers
```
Secondly, create a `Build`.
```yaml=
apiVersion: shipwright.io/v1alpha1
kind: Build
metadata:
name: buildpack-hello-world
spec:
source:
url: https://github.com/OpenFunction/samples
contextDir: functions-framework/Knative/simple-hello/userfunction
strategy:
name: openfunction
kind: ClusterBuildStrategy
output:
image: docker.io/openfunctiondev/hellow-world:latest
credentials:
name: push-secret
paramValues:
- name: APP_IMAGE
value: docker.io/openfunctiondev/hellow-world:latest
```
The `push-secret` is a secret to access your container registry, you can create it like this.
```shell
REGISTRY_SERVER=https://index.docker.io/v1/ REGISTRY_USER=<your_registry_user> REGISTRY_PASSWORD=<your_registry_password>
kubectl create secret docker-registry push-secret \
--docker-server=$REGISTRY_SERVER \
--docker-username=$REGISTRY_USER \
--docker-password=$REGISTRY_PASSWORD \
--docker-email=<your_email>
```
Thirdly, create a `BuildRun` to start build the image.
```yaml=
apiVersion: shipwright.io/v1alpha1
kind: BuildRun
metadata:
generateName: buildpack-hellow-world-buildrun-
spec:
buildRef:
name: buildpack-hellow-world
```
The `Function` CRD needs to change to this.
```yaml=
apiVersion: core.openfunction.io/v1alpha1
kind: Function
metadata:
name: function-sample
spec:
version: "v1.0.0"
image: "openfunctiondev/sample-go-func:latest"
#port: 8080 # default to 8080
build:
strategy: Dockerfile
dockerfile: cmd/Dockerfile
params:
FUNC_NAME: "HelloWorld"
FUNC_TYPE: "http"
#FUNC_SRC: "main.py"
srcRepo:
url: "https://github.com/OpenFunction/samples.git"
sourceSubPath: "functions/Knative/hello-world-go"
registry:
url: "https://index.docker.io/v1/"
credentials:
name: push-secret
```
| key | Describe | Type |
| -------- | -------- | -------- |
| strategy | The `Strategy` which used to build, `Dockerfile` or `Openfunction` | string |
| dockerfile | The Dockerfile used to build image | string |
| credentials | The secret to accessing your container registry | corev1.LocalObjectReference |
The `Builder` CRD needs to change to this.
```yaml=
apiVersion: core.openfunction.io/v1alpha1
kind: Builder
metadata:
name: function-sample-builder
spec:
version: "v1.0.0"
image: "openfunctiondev/sample-go-func:latest"
#port: 8080 # default to 8080
strategy: Dockerfile
dockerfile: cmd/Dockerfile
params:
FUNC_NAME: "HelloWorld"
FUNC_TYPE: "http"
#FUNC_SRC: "main.py"
srcRepo:
url: "https://github.com/OpenFunction/samples.git"
sourceSubPath: "functions/Knative/hello-world-go"
registry:
url: "https://index.docker.io/v1/"
credentials:
name: push-secret
```
### Plan B: Use Shipwright to build Dockerfile
Keep the existing build logic unchanged, only use Shipwright to build the dockerfile.
The `Function` CRD needs to add a filed named `dokerfile`.
```yaml=
apiVersion: core.openfunction.io/v1alpha1
kind: Function
metadata:
name: function-sample
spec:
build:
dockerfile: Dockerfile
```
The `dockerfile` is the Dockerfile used to build the image. When the `dockerfile` is not nil, Openfunction used Shipwright to build the image.
The `Builder` CRD needs to add the filed `dokerfile` too.
```yaml=
apiVersion: core.openfunction.io/v1alpha1
kind: Builder
metadata:
name: function-sample-builder
spec:
dockerfile: Dockerfile
```
### Plan C: Don't use Shipwright
Don't use Shipwright, use tekton to implement the ability to build Dockerfile.
These images are based on the GCP, and support building Dockerfile.
- `heroku/buildpacks:18`
- `paketobuildpacks/builder:full`
We can use these images to implement the ability to build Dockerfile.
The CRD changes are the same as the `Plan B`, when the `dockerfile` is not nil, change the builder image to `heroku/buildpacks:18` or `paketobuildpacks/builder:full`.