Use custom jammy paketo builder (#1911)

* chore: use custom jammy paketo builder

Use our own modified jammy builder with additional buildpacks for
GoFunc and Rust. This enables on cluster build for Go and Rust functions.
Where possible (Go, Java) we use "tiny" variant, other runtimes use "base"
variant.

The updated task is new file instead of modifying existing task
this is done for sake of keeping compatiblility.

Signed-off-by: Matej Vasek <mvasek@redhat.com>

* fixup: remove unnecessary code per review request

Signed-off-by: Matej Vasek <mvasek@redhat.com>

* fixup

Signed-off-by: Matej Vasek <mvasek@redhat.com>

* fixup: podman test refers correct tkn task yamls

Signed-off-by: Matej Vasek <mvasek@redhat.com>

---------

Signed-off-by: Matej Vasek <mvasek@redhat.com>
This commit is contained in:
Matej Vasek 2023-08-10 05:25:35 +02:00 committed by GitHub
parent 399ab7dd17
commit efb04bef0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 7403 additions and 7223 deletions

View File

@ -11,7 +11,7 @@ jobs:
name: E2E Test
strategy:
matrix:
runtime: ["node", "go", "python", "quarkus", "springboot", "typescript"]
runtime: ["node", "go", "python", "quarkus", "springboot", "typescript", "rust"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ podman_pid=$!
DOCKER_HOST="unix://$(podman info -f '{{.Host.RemoteSocket.Path}}' 2> /dev/null)"
export DOCKER_HOST
go test -test.timeout=15m -tags integration ./... -v
make test-integration
e=$?
kill -TERM "$podman_pid" > /dev/null 2>&1

View File

@ -27,18 +27,18 @@ import (
// DefaultName when no WithName option is provided to NewBuilder
const DefaultName = builders.Pack
var DefaultBaseBuilder = "gcr.io/paketo-buildpacks/builder:base"
var DefaultRustBuilder = "gcr.io/paketo-buildpacks/builder:full-cf"
var DefaultBaseBuilder = "ghcr.io/knative/builder-jammy-base:latest"
var DefaultTinyBuilder = "ghcr.io/knative/builder-jammy-tiny:latest"
var (
DefaultBuilderImages = map[string]string{
"node": DefaultBaseBuilder,
"nodejs": DefaultBaseBuilder,
"typescript": DefaultBaseBuilder,
"go": DefaultBaseBuilder,
"go": DefaultTinyBuilder,
"python": DefaultBaseBuilder,
"quarkus": DefaultBaseBuilder,
"rust": DefaultRustBuilder,
"quarkus": DefaultTinyBuilder,
"rust": DefaultBaseBuilder,
"springboot": DefaultBaseBuilder,
}
@ -50,11 +50,10 @@ var (
"docker.io/paketobuildpacks/",
"ghcr.io/vmware-tanzu/function-buildpacks-for-knative/",
"gcr.io/buildpacks/",
"ghcr.io/knative/",
}
defaultBuildpacks = map[string][]string{
"go": {"paketo-buildpacks/go-dist", "ghcr.io/boson-project/go-function-buildpack:tip"},
}
defaultBuildpacks = map[string][]string{}
)
// Builder will build Function using Pack.

View File

@ -13,8 +13,8 @@ func defaultPodSecurityContext() *corev1.PodSecurityContext {
if IsOpenShift() {
return nil
}
runAsUser := int64(1000)
runAsGroup := int64(1000)
runAsUser := int64(1001)
runAsGroup := int64(1002)
return &corev1.PodSecurityContext{
RunAsUser: &runAsUser,
RunAsGroup: &runAsGroup,

View File

@ -0,0 +1,230 @@
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: func-buildpacks
labels:
app.kubernetes.io/version: "0.1"
annotations:
tekton.dev/categories: Image Build
tekton.dev/pipelines.minVersion: "0.17.0"
tekton.dev/tags: image-build
tekton.dev/displayName: "Knative Functions Buildpacks"
tekton.dev/platforms: "linux/amd64"
spec:
description: >-
The Knative Functions Buildpacks task builds source into a container image and pushes it to a registry,
using Cloud Native Buildpacks. This task is based on the Buildpacks Tekton task v 0.4.
workspaces:
- name: source
description: Directory where application source is located.
- name: cache
description: Directory where cache is stored (when no cache image is provided).
optional: true
- name: dockerconfig
description: >-
An optional workspace that allows providing a .docker/config.json file
for Buildpacks lifecycle binary to access the container registry.
The file should be placed at the root of the Workspace with name config.json.
optional: true
params:
- name: APP_IMAGE
description: The name of where to store the app image.
- name: REGISTRY
description: The registry associated with the function image.
- name: BUILDER_IMAGE
description: The image on which builds will run (must include lifecycle and compatible buildpacks).
- name: SOURCE_SUBPATH
description: A subpath within the `source` input where the source to build is located.
default: ""
- name: ENV_VARS
type: array
description: Environment variables to set during _build-time_.
default: []
- 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: "1001"
- name: GROUP_ID
description: The group ID of the builder image user.
default: "0"
##############################################################
##### "default" has been changed to "0" for Knative Functions
- name: PLATFORM_DIR
description: The name of the platform directory.
default: empty-dir
results:
- name: IMAGE_DIGEST
description: The digest of the built `APP_IMAGE`.
stepTemplate:
env:
- name: CNB_PLATFORM_API
value: "0.10"
steps:
- name: prepare
image: docker.io/library/bash:5.1.4@sha256:b208215a4655538be652b2769d82e576bc4d0a2bb132144c060efc5be8c3f5d6
args:
- "--env-vars"
- "$(params.ENV_VARS[*])"
script: |
#!/usr/bin/env bash
set -e
if [[ "$(workspaces.cache.bound)" == "true" ]]; then
echo "> Setting permissions on '$(workspaces.cache.path)'..."
chown -R "$(params.USER_ID):$(params.GROUP_ID)" "$(workspaces.cache.path)"
fi
#######################################################
##### "/emptyDir" has been added for Knative Functions
for path in "/tekton/home" "/layers" "/emptyDir" "$(workspaces.source.path)"; do
echo "> Setting permissions on '$path'..."
chown -R "$(params.USER_ID):$(params.GROUP_ID)" "$path"
if [[ "$path" == "$(workspaces.source.path)" ]]; then
chmod 775 "$(workspaces.source.path)"
fi
done
echo "> Parsing additional configuration..."
parsing_flag=""
envs=()
for arg in "$@"; do
if [[ "$arg" == "--env-vars" ]]; then
echo "-> Parsing env variables..."
parsing_flag="env-vars"
elif [[ "$parsing_flag" == "env-vars" ]]; then
envs+=("$arg")
fi
done
echo "> Processing any environment variables..."
ENV_DIR="/platform/env"
echo "--> Creating 'env' directory: $ENV_DIR"
mkdir -p "$ENV_DIR"
for env in "${envs[@]}"; do
IFS='=' read -r key value <<< "$env"
if [[ "$key" != "" && "$value" != "" ]]; then
path="${ENV_DIR}/${key}"
echo "--> Writing ${path}..."
echo -n "$value" > "$path"
fi
done
############################################
##### Added part for Knative Functions #####
############################################
func_file="$(workspaces.source.path)/func.yaml"
if [ "$(params.SOURCE_SUBPATH)" != "" ]; then
func_file="$(workspaces.source.path)/$(params.SOURCE_SUBPATH)/func.yaml"
fi
echo "--> Saving 'func.yaml'"
cp $func_file /emptyDir/func.yaml
############################################
volumeMounts:
- name: layers-dir
mountPath: /layers
- name: $(params.PLATFORM_DIR)
mountPath: /platform
########################################################
##### "/emptyDir" has been added for Knative Functions
- name: empty-dir
mountPath: /emptyDir
- name: create
image: $(params.BUILDER_IMAGE)
imagePullPolicy: Always
command: ["/cnb/lifecycle/creator"]
env:
- name: DOCKER_CONFIG
value: $(workspaces.dockerconfig.path)
args:
- "-app=$(workspaces.source.path)/$(params.SOURCE_SUBPATH)"
- "-cache-dir=$(workspaces.cache.path)"
- "-cache-image=$(params.CACHE_IMAGE)"
- "-uid=$(params.USER_ID)"
- "-gid=$(params.GROUP_ID)"
- "-layers=/layers"
- "-platform=/platform"
- "-report=/layers/report.toml"
- "-skip-restore=$(params.SKIP_RESTORE)"
- "-previous-image=$(params.APP_IMAGE)"
- "-run-image=$(params.RUN_IMAGE)"
- "$(params.APP_IMAGE)"
volumeMounts:
- name: layers-dir
mountPath: /layers
- name: $(params.PLATFORM_DIR)
mountPath: /platform
securityContext:
runAsUser: 1001
#################################################################
##### "runAsGroup" has been changed to "0" for Knative Functions
runAsGroup: 0
- name: results
image: docker.io/library/bash:5.1.4@sha256:b208215a4655538be652b2769d82e576bc4d0a2bb132144c060efc5be8c3f5d6
script: |
#!/usr/bin/env bash
set -e
cat /layers/report.toml | grep "digest" | cut -d'"' -f2 | cut -d'"' -f2 | tr -d '\n' | tee $(results.IMAGE_DIGEST.path)
############################################
##### Added part for Knative Functions #####
############################################
digest=$(cat $(results.IMAGE_DIGEST.path))
func_file="$(workspaces.source.path)/func.yaml"
if [ "$(params.SOURCE_SUBPATH)" != "" ]; then
func_file="$(workspaces.source.path)/$(params.SOURCE_SUBPATH)/func.yaml"
fi
if [[ ! -f "$func_file" ]]; then
echo "--> Restoring 'func.yaml'"
mkdir -p "$(workspaces.source.path)/$(params.SOURCE_SUBPATH)"
cp /emptyDir/func.yaml $func_file
fi
echo ""
sed -i "s|^image:.*$|image: $(params.APP_IMAGE)|" "$func_file"
echo "Function image name: $(params.APP_IMAGE)"
sed -i "s/^imageDigest:.*$/imageDigest: $digest/" "$func_file"
echo "Function image digest: $digest"
sed -i "s|^registry:.*$|registry: $(params.REGISTRY)|" "$func_file"
echo "Function image registry: $(params.REGISTRY)"
############################################
volumeMounts:
- name: layers-dir
mountPath: /layers
########################################################
##### "/emptyDir" has been added for Knative Functions
- name: empty-dir
mountPath: /emptyDir
volumes:
- name: empty-dir
emptyDir: {}
- name: layers-dir
emptyDir: {}

View File

@ -34,6 +34,7 @@ import (
"k8s.io/apimachinery/pkg/util/uuid"
"knative.dev/pkg/apis"
"knative.dev/func/pkg/builders/buildpacks"
"knative.dev/func/pkg/docker"
fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/k8s"
@ -79,7 +80,7 @@ func TestGitlab(t *testing.T) {
URL: strings.TrimSuffix(glabEnv.HTTPProjectURL, ".git"),
Revision: "devel",
},
BuilderImages: map[string]string{"pack": "docker.io/paketobuildpacks/builder:tiny"},
BuilderImages: map[string]string{"pack": buildpacks.DefaultTinyBuilder},
Builder: "pack",
PVCSize: "256Mi",
},

View File

@ -16,6 +16,7 @@ import (
"testing"
"time"
"knative.dev/func/pkg/builders/buildpacks"
"knative.dev/func/pkg/docker"
fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/pipelines/tekton"
@ -153,12 +154,12 @@ func createSimpleGoProject(t *testing.T, ns string) fn.Function {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(projDir, "main.go"), []byte(simpleGOSvc), 0644)
err = os.WriteFile(filepath.Join(projDir, "handle.go"), []byte(simpleGOSvc), 0644)
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(projDir, "go.mod"), []byte("module web\n\ngo 1.20\n"), 0644)
err = os.WriteFile(filepath.Join(projDir, "go.mod"), []byte("module function\n\ngo 1.20\n"), 0644)
if err != nil {
t.Fatal(err)
}
@ -173,7 +174,7 @@ func createSimpleGoProject(t *testing.T, ns string) fn.Function {
Invoke: "none",
Build: fn.BuildSpec{
BuilderImages: map[string]string{
"pack": "docker.io/paketobuildpacks/builder:base",
"pack": buildpacks.DefaultTinyBuilder,
"s2i": "registry.access.redhat.com/ubi8/go-toolset",
},
},
@ -189,40 +190,16 @@ func createSimpleGoProject(t *testing.T, ns string) fn.Function {
return f
}
const simpleGOSvc = `package main
const simpleGOSvc = `package function
import (
"context"
"net"
"net/http"
"os"
"os/signal"
"syscall"
)
func main() {
sigs := make(chan os.Signal, 5)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
s := http.Server{
Handler: http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
resp.Header().Add("Content-Type", "text/plain")
resp.WriteHeader(200)
_, _ = resp.Write([]byte("OK"))
}),
}
go func() {
<-sigs
_ = s.Shutdown(context.Background())
}()
port := "8080"
if p, ok := os.LookupEnv("PORT"); ok {
port = p
}
l, err := net.Listen("tcp4", ":"+port)
if err != nil {
panic(err)
}
_ = s.Serve(l)
func Handle(ctx context.Context, resp http.ResponseWriter, req *http.Request) {
resp.Header().Add("Content-Type", "text/plain")
resp.WriteHeader(200)
_, _ = resp.Write([]byte("Hello World!\n"))
}
`

View File

@ -30,14 +30,10 @@ func (pp *PipelinesProvider) ConfigurePAC(ctx context.Context, f fn.Function, me
return fmt.Errorf("incorrect type of pipelines metadata: %T", metadata)
}
var warningMsg string
var err error
if warningMsg, err = validatePipeline(f); err != nil {
if err = validatePipeline(f); err != nil {
return err
}
if warningMsg != "" {
pp.progressListener.Increment(warningMsg)
}
if data.ConfigureLocalResources {
if err := pp.createLocalPACResources(ctx, f); err != nil {

View File

@ -118,14 +118,10 @@ func NewPipelinesProvider(opts ...Opt) *PipelinesProvider {
// After the PipelineRun is being initialized, the progress of the PipelineRun is being watched and printed to the output.
func (pp *PipelinesProvider) Run(ctx context.Context, f fn.Function) error {
pp.progressListener.Increment("Creating Pipeline resources")
var warningMsg string
var err error
if warningMsg, err = validatePipeline(f); err != nil {
if err = validatePipeline(f); err != nil {
return err
}
if warningMsg != "" {
pp.progressListener.Increment(warningMsg)
}
client, namespace, err := NewTektonClientAndResolvedNamespace(pp.namespace)
if err != nil {

View File

@ -29,7 +29,7 @@ const (
// Tasks references for PAC PipelineRun that are defined in the annotations
taskGitCloneRef = "git-clone"
taskFuncS2iPACPipelineRunRef = "https://raw.githubusercontent.com/%s/%s/pkg/pipelines/resources/tekton/task/func-s2i/0.1/func-s2i.yaml"
taskFuncBuildpacksPACPipelineRunRef = "https://raw.githubusercontent.com/%s/%s/pkg/pipelines/resources/tekton/task/func-buildpacks/0.1/func-buildpacks.yaml"
taskFuncBuildpacksPACPipelineRunRef = "https://raw.githubusercontent.com/%s/%s/pkg/pipelines/resources/tekton/task/func-buildpacks/0.2/func-buildpacks.yaml"
taskFuncDeployPACPipelineRunRef = "https://raw.githubusercontent.com/%s/%s/pkg/pipelines/resources/tekton/task/func-deploy/0.1/func-deploy.yaml"
// Following section contains references for Tasks to be used in Pipeline templates,

View File

@ -5,7 +5,6 @@ import (
"fmt"
"knative.dev/func/pkg/builders"
"knative.dev/func/pkg/builders/buildpacks"
"knative.dev/func/pkg/builders/s2i"
fn "knative.dev/func/pkg/functions"
)
@ -25,33 +24,21 @@ func (e ErrRuntimeNotSupported) Error() string {
return fmt.Sprintf("runtime %q is not supported for on cluster build with default builders", e.Runtime)
}
func validatePipeline(f fn.Function) (string, error) {
var warningMsg string
func validatePipeline(f fn.Function) error {
if f.Build.Builder == builders.Pack {
if f.Runtime == "" {
return "", ErrRuntimeRequired
}
if f.Runtime == "go" || f.Runtime == "rust" {
builder := f.Build.BuilderImages[builders.Pack]
defaultBuilder := buildpacks.DefaultBuilderImages[f.Runtime]
if builder != "" && builder != defaultBuilder {
warningMsg = fmt.Sprintf("runtime %q is not supported for on cluster build with default builders, "+
"continuing with the custom builder provided", f.Runtime)
} else {
return "", ErrRuntimeNotSupported{f.Runtime}
}
return ErrRuntimeRequired
}
if len(f.Build.Buildpacks) > 0 {
return "", ErrBuilpacksNotSupported
return ErrBuilpacksNotSupported
}
} else if f.Build.Builder == builders.S2I {
_, err := s2i.BuilderImage(f, builders.S2I)
return "", err
return err
} else {
return "", builders.ErrUnknownBuilder{Name: f.Build.Builder}
return builders.ErrUnknownBuilder{Name: f.Build.Builder}
}
return warningMsg, nil
return nil
}

View File

@ -70,9 +70,9 @@ func Test_validatePipeline(t *testing.T) {
wantErr: true,
},
{
name: "Unsupported runtime - Go - pack builder - without additional Buildpacks",
name: "Supported runtime - Go - pack builder",
function: fn.Function{Build: fn.BuildSpec{Builder: builders.Pack}, Runtime: "go"},
wantErr: true,
wantErr: false,
},
{
name: "Unsupported runtime - Go - pack builder - with additional Buildpacks",
@ -95,9 +95,9 @@ func Test_validatePipeline(t *testing.T) {
wantErr: false,
},
{
name: "Unsupported runtime - Rust - pack builder - without additional Buildpacks",
name: "Supported runtime - Rust - pack builder",
function: fn.Function{Build: fn.BuildSpec{Builder: builders.Pack}, Runtime: "rust"},
wantErr: true,
wantErr: false,
},
{
name: "Unsupported runtime - Rust - s2i builder",
@ -108,7 +108,7 @@ func Test_validatePipeline(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := validatePipeline(tt.function)
err := validatePipeline(tt.function)
if (err != nil) != tt.wantErr {
t.Errorf("validatePipeline() error = %v, wantErr %v", err, tt.wantErr)
return

View File

@ -1,2 +0,0 @@
buildpacks:
- docker.io/paketocommunity/rust

View File

@ -18,6 +18,7 @@ import (
var runtimeSupportMap = map[string][]string{
"node": {"pack", "s2i"},
"go": {"pack"},
"rust": {"pack"},
"python": {"pack", "s2i"},
"quarkus": {"pack", "s2i"},
"springboot": {"pack"},

View File

@ -15,7 +15,8 @@ import (
var runtimeSupportMap = map[string][]string{
"node": {"pack", "s2i"},
"go": {},
"go": {"pack"},
"rust": {"pack"},
"python": {"pack", "s2i"},
"quarkus": {"pack", "s2i"},
"springboot": {"pack"},