diff --git a/cluster/platform/innoventions/harbor-credentials.yaml b/cluster/platform/innoventions/harbor-credentials.yaml new file mode 100644 index 0000000..740ab03 --- /dev/null +++ b/cluster/platform/innoventions/harbor-credentials.yaml @@ -0,0 +1,38 @@ +# ExternalSecret for Tekton robot account credentials +# Syncs from 1Password item: harbor-robot-accounts +# Creates a docker-registry type secret for Tekton pipeline authentication +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: harbor-tekton-robot + namespace: innoventions +spec: + refreshInterval: 1h + secretStoreRef: + name: onepassword-connect + kind: ClusterSecretStore + target: + name: harbor-credentials + creationPolicy: Owner + template: + type: kubernetes.io/dockerconfigjson + data: + .dockerconfigjson: | + { + "auths": { + "the-seas.local.mk-labs.cloud": { + "username": "{{ .tekton_username }}", + "password": "{{ .tekton_password }}", + "auth": "{{ printf "%s:%s" .tekton_username .tekton_password | b64enc }}" + } + } + } + data: + - secretKey: tekton_username + remoteRef: + key: harbor-robot-accounts + property: tekton-builder-username + - secretKey: tekton_password + remoteRef: + key: harbor-robot-accounts + property: tekton-builder-password diff --git a/cluster/tekton/pipelines/container-build.yaml b/cluster/tekton/pipelines/container-build.yaml new file mode 100644 index 0000000..155e689 --- /dev/null +++ b/cluster/tekton/pipelines/container-build.yaml @@ -0,0 +1,99 @@ +apiVersion: tekton.dev/v1beta1 +kind: Pipeline +metadata: + name: container-build + namespace: innoventions + labels: + app.kubernetes.io/version: "0.1" + annotations: + tekton.dev/pipelines.minVersion: "0.29.0" + tekton.dev/categories: Container Build + tekton.dev/tags: git, kaniko, build + tekton.dev/displayName: "Container Build Pipeline" +spec: + description: >- + This pipeline clones a git repository and builds a container image from it using Kaniko. + + The pipeline performs the following steps: + 1. Clone the git repository to a shared workspace + 2. Build and push the container image to Harbor registry + + params: + - name: git-url + type: string + description: The git repository URL to clone + - name: git-revision + type: string + description: The git revision (branch, tag, sha) to checkout + default: "main" + - name: image-name + type: string + description: The complete image name including registry (e.g., the-seas.local.mk-labs.cloud/library/app) + - name: image-tag + type: string + description: The image tag to apply + default: "latest" + - name: dockerfile + type: string + description: Path to the Dockerfile relative to the repository root + default: "./Dockerfile" + - name: context + type: string + description: Path to the build context relative to the repository root + default: "./" + + workspaces: + - name: shared-workspace + description: | + This workspace will hold the cloned git repository and is shared between tasks. + It should be backed by a PersistentVolumeClaim or VolumeClaimTemplate. + - name: docker-credentials + description: | + This workspace contains the docker config.json for authenticating to Harbor. + It should be backed by a Secret containing .dockerconfigjson + + tasks: + - name: fetch-repository + taskRef: + name: git-clone + workspaces: + - name: output + workspace: shared-workspace + params: + - name: url + value: $(params.git-url) + - name: revision + value: $(params.git-revision) + - name: deleteExisting + value: "true" + + - name: build-and-push + taskRef: + name: kaniko-build + runAfter: + - fetch-repository + workspaces: + - name: source + workspace: shared-workspace + - name: dockerconfig + workspace: docker-credentials + params: + - name: IMAGE + value: "$(params.image-name):$(params.image-tag)" + - name: DOCKERFILE + value: $(params.dockerfile) + - name: CONTEXT + value: $(params.context) + - name: EXTRA_ARGS + value: + - --skip-tls-verify + - --verbosity=info + - --skip-unused-stages=true + + results: + - name: IMAGE_DIGEST + description: The digest of the built image + value: $(tasks.build-and-push.results.IMAGE_DIGEST) + - name: IMAGE_URL + description: The URL of the built image + value: $(tasks.build-and-push.results.IMAGE_URL) diff --git a/cluster/tekton/tasks/git-clone.yaml b/cluster/tekton/tasks/git-clone.yaml new file mode 100644 index 0000000..33ee7c5 --- /dev/null +++ b/cluster/tekton/tasks/git-clone.yaml @@ -0,0 +1,175 @@ +apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: git-clone + namespace: innoventions + labels: + app.kubernetes.io/version: "0.1" + annotations: + tekton.dev/pipelines.minVersion: "0.29.0" + tekton.dev/categories: Git + tekton.dev/tags: git + tekton.dev/displayName: "git clone" + tekton.dev/platforms: "linux/amd64,linux/s390x,linux/ppc64le,linux/arm64" +spec: + description: >- + Clone a git repository to the output workspace. + + This Task can be used to initialize a workspace by cloning a git repo. + It supports public repos and HTTPS authentication via basic auth credentials. + + workspaces: + - name: output + description: The git repo will be cloned onto the volume backing this Workspace. + + params: + - name: url + description: Repository URL to clone from. + type: string + - name: revision + description: Revision to checkout. (branch, tag, sha, ref, etc...) + type: string + default: "main" + - name: refspec + description: Refspec to fetch before checking out revision. + default: "" + - name: submodules + description: Initialize and fetch git submodules. + type: string + default: "false" + - name: depth + description: Perform a shallow clone, fetching only the most recent N commits. + type: string + default: "1" + - name: sslVerify + description: Set the `http.sslVerify` global git config. Setting this to `false` is not advised unless you are sure that you trust your git remote. + type: string + default: "true" + - name: subdirectory + description: Subdirectory inside the `output` workspace to clone the repo into. + type: string + default: "" + - name: deleteExisting + description: Clean out the contents of the destination directory if it already exists before cloning. + type: string + default: "true" + - name: httpProxy + description: HTTP proxy server for non-SSL requests. + type: string + default: "" + - name: httpsProxy + description: HTTPS proxy server for SSL requests. + type: string + default: "" + - name: noProxy + description: Opt out of proxying HTTP/HTTPS requests. + type: string + default: "" + - name: verbose + description: Log the commands that are executed during `git-clone`'s operation. + type: string + default: "false" + - name: gitInitImage + description: The image providing the git-init binary that this Task runs. + type: string + default: "gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.40.2" + - name: userHome + description: | + Absolute path to the user's home directory. Set this explicitly if you are running the image as a non-root user or have overridden + the gitInitImage param with an image containing custom user configuration. + type: string + default: "/tekton/home" + + results: + - name: commit + description: The precise commit SHA that was fetched by this Task. + - name: url + description: The precise URL that was fetched by this Task. + + steps: + - name: clone + image: "$(params.gitInitImage)" + env: + - name: HOME + value: "$(params.userHome)" + - name: PARAM_URL + value: $(params.url) + - name: PARAM_REVISION + value: $(params.revision) + - name: PARAM_REFSPEC + value: $(params.refspec) + - name: PARAM_SUBMODULES + value: $(params.submodules) + - name: PARAM_DEPTH + value: $(params.depth) + - name: PARAM_SSL_VERIFY + value: $(params.sslVerify) + - name: PARAM_SUBDIRECTORY + value: $(params.subdirectory) + - name: PARAM_DELETE_EXISTING + value: $(params.deleteExisting) + - name: PARAM_HTTP_PROXY + value: $(params.httpProxy) + - name: PARAM_HTTPS_PROXY + value: $(params.httpsProxy) + - name: PARAM_NO_PROXY + value: $(params.noProxy) + - name: PARAM_VERBOSE + value: $(params.verbose) + - name: PARAM_USER_HOME + value: $(params.userHome) + - name: WORKSPACE_OUTPUT_PATH + value: $(workspaces.output.path) + script: | + #!/usr/bin/env sh + set -eu + + if [ "${PARAM_VERBOSE}" = "true" ] ; then + set -x + fi + + if [ "${PARAM_DELETE_EXISTING}" = "true" ] ; then + cleandir() { + # Delete any existing contents of the repo directory if it exists. + # + # We don't just "rm -rf ${CHECKOUT_DIR}" because ${CHECKOUT_DIR} might be "/" + # or the root of a mounted volume. + if [ -d "${CHECKOUT_DIR}" ] ; then + # Delete non-hidden files and directories + rm -rf "${CHECKOUT_DIR:?}"/* + # Delete files and directories starting with . but excluding .. + rm -rf "${CHECKOUT_DIR}"/.[!.]* + # Delete files and directories starting with .. plus any other character + rm -rf "${CHECKOUT_DIR}"/..?* + fi + } + + if [ "${PARAM_SUBDIRECTORY}" != "" ] ; then + CHECKOUT_DIR="${WORKSPACE_OUTPUT_PATH}/${PARAM_SUBDIRECTORY}" + cleandir + else + CHECKOUT_DIR="${WORKSPACE_OUTPUT_PATH}" + cleandir + fi + fi + + test -z "${PARAM_HTTP_PROXY}" || export HTTP_PROXY="${PARAM_HTTP_PROXY}" + test -z "${PARAM_HTTPS_PROXY}" || export HTTPS_PROXY="${PARAM_HTTPS_PROXY}" + test -z "${PARAM_NO_PROXY}" || export NO_PROXY="${PARAM_NO_PROXY}" + + /ko-app/git-init \ + -url="${PARAM_URL}" \ + -revision="${PARAM_REVISION}" \ + -refspec="${PARAM_REFSPEC}" \ + -path="${CHECKOUT_DIR}" \ + -sslVerify="${PARAM_SSL_VERIFY}" \ + -submodules="${PARAM_SUBMODULES}" \ + -depth="${PARAM_DEPTH}" + cd "${CHECKOUT_DIR}" + RESULT_SHA="$(git rev-parse HEAD)" + EXIT_CODE="$?" + if [ "${EXIT_CODE}" != 0 ] ; then + exit "${EXIT_CODE}" + fi + printf "%s" "${RESULT_SHA}" > "$(results.commit.path)" + printf "%s" "${PARAM_URL}" > "$(results.url.path)" diff --git a/cluster/tekton/tasks/kaniko-build.yaml b/cluster/tekton/tasks/kaniko-build.yaml new file mode 100644 index 0000000..d8888b4 --- /dev/null +++ b/cluster/tekton/tasks/kaniko-build.yaml @@ -0,0 +1,69 @@ +apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: kaniko-build + namespace: innoventions + labels: + app.kubernetes.io/version: "0.1" + annotations: + tekton.dev/pipelines.minVersion: "0.29.0" + tekton.dev/categories: Image Build + tekton.dev/tags: image-build, kaniko + tekton.dev/displayName: "kaniko build" + tekton.dev/platforms: "linux/amd64,linux/arm64" +spec: + description: >- + This Task builds source into a container image using Google's Kaniko tool. + + Kaniko doesn't depend on a Docker daemon and executes each command within a Dockerfile + completely in userspace. This enables building container images in environments that + can't easily or securely run a Docker daemon, such as a standard Kubernetes cluster. + + params: + - name: IMAGE + description: Name (reference) of the image to build. + - name: DOCKERFILE + description: Path to the Dockerfile to build. + default: ./Dockerfile + - name: CONTEXT + description: The build context used by Kaniko. + default: ./ + - name: EXTRA_ARGS + type: array + default: [] + - name: BUILDER_IMAGE + description: The image on which builds will run (default is v1.23.2) + default: gcr.io/kaniko-project/executor:v1.23.2@sha256:7f77bc1c6b96e5da7fdb56fb0dc1c16ba0f7fc8b5d1eeaa75fa5db39ec2f4a4d + workspaces: + - name: source + description: Holds the context and Dockerfile + - name: dockerconfig + description: Includes a docker `config.json` + optional: true + mountPath: /kaniko/.docker + results: + - name: IMAGE_DIGEST + description: Digest of the image just built. + - name: IMAGE_URL + description: URL of the image just built. + steps: + - name: build-and-push + workingDir: $(workspaces.source.path) + image: $(params.BUILDER_IMAGE) + args: + - $(params.EXTRA_ARGS) + - --dockerfile=$(params.DOCKERFILE) + - --context=$(workspaces.source.path)/$(params.CONTEXT) + - --destination=$(params.IMAGE) + - --digest-file=$(results.IMAGE_DIGEST.path) + # kaniko assumes it is running as root, which means this example fails on platforms + # that default to run containers as random uid (like OpenShift). Adding this securityContext + # makes it explicit that it needs to run as root. + securityContext: + runAsUser: 0 + - name: write-url + image: docker.io/library/bash:5.1.4@sha256:c523c636b722339f41b6a431b44588ab2f762c5de5ec3bd7964420ff982fb1d9 + script: | + set -e + image="$(params.IMAGE)" + echo -n "${image}" | tee "$(results.IMAGE_URL.path)" diff --git a/test-app.Dockerfile b/test-app.Dockerfile new file mode 100644 index 0000000..3d454e2 --- /dev/null +++ b/test-app.Dockerfile @@ -0,0 +1,6 @@ +FROM alpine:3.21 +RUN apk add --no-cache curl +LABEL org.opencontainers.image.source="https://gitea.mk-labs.cloud/rblundon/homelab" +LABEL org.opencontainers.image.description="Test application for Tekton pipeline validation" +LABEL org.opencontainers.image.title="tekton-test-app" +CMD ["echo", "Hello from Tekton Pipeline! Build successful."]