apiVersion: tekton.dev/v1 kind: Task metadata: labels: app.kubernetes.io/version: "0.1" annotations: tekton.dev/categories: Git tekton.dev/displayName: git clone tekton.dev/pipelines.minVersion: 0.21.0 tekton.dev/platforms: linux/amd64,linux/s390x,linux/ppc64le,linux/arm64 tekton.dev/tags: git name: git-clone spec: description: The git-clone Task will clone a repo from the provided url into the output Workspace. By default the repo will be cloned into the root of your Workspace. params: - description: Repository URL to clone from. name: url type: string - default: "" description: Revision to checkout. (branch, tag, sha, ref, etc...) name: revision type: string - default: "" description: Refspec to fetch before checking out revision. name: refspec type: string - default: "true" description: Initialize and fetch git submodules. name: submodules type: string - name: submodulePaths description: | Comma-separated list of specific submodule paths to initialize and fetch. Only submodules in the specified directories and their subdirectories will be fetched. Empty string fetches all submodules. Parameter "submodules" must be set to "true" to make this parameter applicable. type: string default: "" - default: "1" description: Perform a shallow clone, fetching only the most recent N commits. name: depth type: string - name: shortCommitLength description: Length of short commit SHA type: string default: "7" - default: "true" 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. name: sslVerify type: string - default: source description: Subdirectory inside the `output` Workspace to clone the repo into. name: subdirectory type: string - default: "" description: Define the directory patterns to match or exclude when performing a sparse checkout. name: sparseCheckoutDirectories type: string - default: "true" description: Clean out the contents of the destination directory if it already exists before cloning. name: deleteExisting type: string - default: "" description: HTTP proxy server for non-SSL requests. name: httpProxy type: string - default: "" description: HTTPS proxy server for SSL requests. name: httpsProxy type: string - default: "" description: Opt out of proxying HTTP/HTTPS requests. name: noProxy type: string - default: "false" description: Log the commands that are executed during `git-clone`'s operation. name: verbose type: string - default: "" description: Deprecated. Has no effect. Will be removed in the future. name: gitInitImage - default: /tekton/home description: | Absolute path to the user's home directory. Set this explicitly if you are running the image as a non-root user. name: userHome type: string - default: "true" description: | Check symlinks in the repo. If they're pointing outside of the repo, the build will fail. name: enableSymlinkCheck type: string - default: "false" description: Fetch all tags for the repo. name: fetchTags type: string - name: caTrustConfigMapName type: string description: The name of the ConfigMap to read CA bundle data from. default: trusted-ca - name: caTrustConfigMapKey type: string description: The name of the key in the ConfigMap that contains the CA bundle data. default: ca-bundle.crt - name: mergeTargetBranch description: Set to "true" to merge the targetBranch into the checked-out revision. type: string default: "false" - name: targetBranch description: The target branch to merge into the revision (if mergeTargetBranch is true). type: string default: main - name: mergeSourceRepoUrl description: | URL of the repository to fetch the target branch from when mergeTargetBranch is true. If empty, uses the same repository (origin). This allows merging a branch from a different repository. type: string default: "" - name: mergeSourceDepth description: | Perform a shallow fetch of the target branch, fetching only the most recent N commits. If empty, fetches the full history of the target branch. type: string default: "" results: - description: The precise commit SHA that was fetched by this Task. name: commit - description: The commit SHA that was fetched by this Task limited to params.shortCommitLength number of characters name: short-commit - description: The precise URL that was fetched by this Task. name: url - description: The commit timestamp of the checkout name: commit-timestamp - description: The precise URL that was fetched by this Task. This result uses Chains type hinting to include in the provenance. name: CHAINS-GIT_URL - description: The precise commit SHA that was fetched by this Task. This result uses Chains type hinting to include in the provenance. name: CHAINS-GIT_COMMIT - name: merged_sha description: The SHA of the commit after merging the target branch (if the param mergeTargetBranch is true). steps: - name: clone 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_SUBMODULE_PATHS value: $(params.submodulePaths) - name: PARAM_DEPTH value: $(params.depth) - name: PARAM_SHORT_COMMIT_LENGTH value: $(params.shortCommitLength) - 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_SPARSE_CHECKOUT_DIRECTORIES value: $(params.sparseCheckoutDirectories) - name: PARAM_USER_HOME value: $(params.userHome) - name: PARAM_FETCH_TAGS value: $(params.fetchTags) - name: PARAM_GIT_INIT_IMAGE value: $(params.gitInitImage) - name: PARAM_MERGE_TARGET_BRANCH value: $(params.mergeTargetBranch) - name: PARAM_TARGET_BRANCH value: $(params.targetBranch) - name: PARAM_MERGE_SOURCE_REPO_URL value: $(params.mergeSourceRepoUrl) - name: PARAM_MERGE_SOURCE_DEPTH value: $(params.mergeSourceDepth) - name: WORKSPACE_OUTPUT_PATH value: $(workspaces.output.path) - name: WORKSPACE_SSH_DIRECTORY_BOUND value: $(workspaces.ssh-directory.bound) - name: WORKSPACE_SSH_DIRECTORY_PATH value: $(workspaces.ssh-directory.path) - name: WORKSPACE_BASIC_AUTH_DIRECTORY_BOUND value: $(workspaces.basic-auth.bound) - name: WORKSPACE_BASIC_AUTH_DIRECTORY_PATH value: $(workspaces.basic-auth.path) image: quay.io/redhat-appstudio/rhtap-task-runner:latest computeResources: {} securityContext: runAsUser: 0 volumeMounts: - name: trusted-ca mountPath: /mnt/trusted-ca readOnly: true script: | #!/usr/bin/env sh set -eu if [ "${PARAM_VERBOSE}" = "true" ] ; then set -x fi if [ -n "${PARAM_GIT_INIT_IMAGE}" ]; then echo "WARNING: provided deprecated gitInitImage parameter has no effect." fi if [ "${WORKSPACE_BASIC_AUTH_DIRECTORY_BOUND}" = "true" ] ; then if [ -f "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.git-credentials" ] && [ -f "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.gitconfig" ]; then cp "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.git-credentials" "${PARAM_USER_HOME}/.git-credentials" cp "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/.gitconfig" "${PARAM_USER_HOME}/.gitconfig" # Compatibility with kubernetes.io/basic-auth secrets elif [ -f "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/username" ] && [ -f "${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/password" ]; then HOSTNAME=$(echo $PARAM_URL | awk -F/ '{print $3}') echo "https://$(cat ${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/username):$(cat ${WORKSPACE_BASIC_AUTH_DIRECTORY_PATH}/password)@$HOSTNAME" > "${PARAM_USER_HOME}/.git-credentials" echo -e "[credential \"https://$HOSTNAME\"]\n helper = store" > "${PARAM_USER_HOME}/.gitconfig" else echo "Unknown basic-auth workspace format" exit 1 fi chmod 400 "${PARAM_USER_HOME}/.git-credentials" chmod 400 "${PARAM_USER_HOME}/.gitconfig" fi # Should be called after the gitconfig is copied from the repository secret ca_bundle=/mnt/trusted-ca/ca-bundle.crt if [ -f "$ca_bundle" ]; then echo "INFO: Using mounted CA bundle: $ca_bundle" git config --global http.sslCAInfo "$ca_bundle" fi if [ "${WORKSPACE_SSH_DIRECTORY_BOUND}" = "true" ] ; then cp -R "${WORKSPACE_SSH_DIRECTORY_PATH}" "${PARAM_USER_HOME}"/.ssh chmod 700 "${PARAM_USER_HOME}"/.ssh chmod -R 400 "${PARAM_USER_HOME}"/.ssh/* fi CHECKOUT_DIR="${WORKSPACE_OUTPUT_PATH}/${PARAM_SUBDIRECTORY}" 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_DELETE_EXISTING}" = "true" ] ; then cleandir 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}" \ -submodulePaths="${PARAM_SUBMODULE_PATHS}" \ -depth="${PARAM_DEPTH}" \ -sparseCheckoutDirectories="${PARAM_SPARSE_CHECKOUT_DIRECTORIES}" \ -retryMaxAttempts=10 cd "${CHECKOUT_DIR}" RESULT_SHA="$(git rev-parse HEAD)" RESULT_SHA_SHORT="$(git rev-parse --short="${PARAM_SHORT_COMMIT_LENGTH}" HEAD)" EXIT_CODE="$?" if [ "${EXIT_CODE}" != 0 ] ; then exit "${EXIT_CODE}" fi if [ "${PARAM_MERGE_TARGET_BRANCH}" = "true" ]; then echo "Merge option enabled. Attempting to merge target branch '${PARAM_TARGET_BRANCH}' into HEAD (${RESULT_SHA})." if [ "${PARAM_DEPTH}" = "1" ]; then echo "WARNING: Shallow clone with depth=1 may cause merge conflicts due to insufficient commit history." >&2 fi if [ "${PARAM_MERGE_SOURCE_DEPTH}" = "1" ]; then echo "WARNING: Shallow fetch with mergeSourceDepth=1 may cause merge conflicts due to insufficient commit history." >&2 fi # Determine if merging from a different repository or the same one if [ -n "${PARAM_MERGE_SOURCE_REPO_URL}" ]; then # Normalize URLs for comparison (remove trailing slashes and .git suffix) normalize_url() { echo "$1" | sed -e 's#/$##' -e 's#\.git$##' } NORMALIZED_ORIGIN_URL=$(normalize_url "${PARAM_URL}") NORMALIZED_MERGE_URL=$(normalize_url "${PARAM_MERGE_SOURCE_REPO_URL}") if [ "${NORMALIZED_ORIGIN_URL}" = "${NORMALIZED_MERGE_URL}" ]; then echo "Merge source URL is the same as origin. Using existing 'origin' remote." MERGE_REMOTE="origin" else echo "Merging from different repository: ${PARAM_MERGE_SOURCE_REPO_URL}" echo "Adding remote 'merge-source'..." git remote add merge-source "${PARAM_MERGE_SOURCE_REPO_URL}" MERGE_REMOTE="merge-source" fi else echo "Merging from the same repository (origin)" MERGE_REMOTE="origin" fi echo "Fetching target branch '${PARAM_TARGET_BRANCH}' from ${MERGE_REMOTE}..." if [ -n "${PARAM_MERGE_SOURCE_DEPTH}" ]; then retry git fetch --depth="${PARAM_MERGE_SOURCE_DEPTH}" ${MERGE_REMOTE} "${PARAM_TARGET_BRANCH}" else retry git fetch ${MERGE_REMOTE} "${PARAM_TARGET_BRANCH}" fi FETCH_EXIT_CODE="$?" if [ "${FETCH_EXIT_CODE}" != "0" ]; then echo "ERROR: Failed to fetch target branch '${PARAM_TARGET_BRANCH}' from ${MERGE_REMOTE}." >&2 exit "${FETCH_EXIT_CODE}" fi echo "Merging ${MERGE_REMOTE}/${PARAM_TARGET_BRANCH} into current HEAD..." git config --global user.email "tekton-git-clone@tekton.dev" git config --global user.name "Tekton Git Clone Task" git merge "${MERGE_REMOTE}/${PARAM_TARGET_BRANCH}" --no-commit --no-ff --allow-unrelated-histories MERGE_CHECK_EXIT_CODE="$?" if [ "${MERGE_CHECK_EXIT_CODE}" != "0" ] ; then echo "ERROR: Merge conflict detected or merge failed before commit." >&2 echo "--- Git Status ---" git status echo "------------------" exit "${MERGE_CHECK_EXIT_CODE}" else # Check if there are changes that need to be merged, and if so, create a merge commit. if git diff --staged --quiet; then echo "No diff was found, skipping merge..." >&2 else echo "Merge successful (no conflicts found), committing..." git commit -m "Merge branch '${PARAM_TARGET_BRANCH}' from ${MERGE_REMOTE} into ${RESULT_SHA}" COMMIT_EXIT_CODE="$?" if [ "${COMMIT_EXIT_CODE}" != "0" ]; then echo "ERROR: Failed to commit merge." >&2 exit "${COMMIT_EXIT_CODE}" fi MERGED_SHA=$(git rev-parse HEAD) echo "New HEAD after merge: ${MERGED_SHA}" echo "${MERGED_SHA}" > "$(results.merged_sha.path)" fi fi else echo "Merge option disabled. Using checked-out revision ${RESULT_SHA} directly." fi printf "%s" "${RESULT_SHA}" > "$(results.commit.path)" printf "%s" "${RESULT_SHA}" > "$(results.CHAINS-GIT_COMMIT.path)" printf "%s" "${RESULT_SHA_SHORT}" > "$(results.short-commit.path)" printf "%s" "${PARAM_URL}" > "$(results.url.path)" printf "%s" "${PARAM_URL}" > "$(results.CHAINS-GIT_URL.path)" printf "%s" "$(git log -1 --pretty=%ct)" > "$(results.commit-timestamp.path)" if [ "${PARAM_FETCH_TAGS}" = "true" ] ; then echo "Fetching tags" retry git fetch --tags fi imagePullPolicy: Always - name: symlink-check image: quay.io/redhat-appstudio/rhtap-task-runner:latest # per https://kubernetes.io/docs/concepts/containers/images/#imagepullpolicy-defaulting # the cluster will set imagePullPolicy to IfNotPresent env: - name: PARAM_ENABLE_SYMLINK_CHECK value: $(params.enableSymlinkCheck) - name: PARAM_SUBDIRECTORY value: $(params.subdirectory) - name: WORKSPACE_OUTPUT_PATH value: $(workspaces.output.path) computeResources: {} script: | #!/usr/bin/env bash set -euo pipefail CHECKOUT_DIR="${WORKSPACE_OUTPUT_PATH}/${PARAM_SUBDIRECTORY}" check_symlinks() { FOUND_SYMLINK_POINTING_OUTSIDE_OF_REPO=false while read -r symlink do target=$(readlink -m "$symlink") if ! [[ "$target" =~ ^$CHECKOUT_DIR ]]; then echo "The cloned repository contains symlink pointing outside of the cloned repository: $symlink" FOUND_SYMLINK_POINTING_OUTSIDE_OF_REPO=true fi done < <(find $CHECKOUT_DIR -type l -print) if [ "$FOUND_SYMLINK_POINTING_OUTSIDE_OF_REPO" = true ] ; then return 1 fi } if [ "${PARAM_ENABLE_SYMLINK_CHECK}" = "true" ] ; then echo "Running symlink check" check_symlinks fi imagePullPolicy: Always workspaces: - description: The git repo will be cloned onto the volume backing this Workspace. name: output - description: | A .ssh directory with private key, known_hosts, config, etc. Copied to the user's home before git commands are executed. Used to authenticate with the git remote when performing the clone. Binding a Secret to this Workspace is strongly recommended over other volume types. name: ssh-directory optional: true - description: | A Workspace containing a .gitconfig and .git-credentials file or username and password. These will be copied to the user's home before any git commands are run. Any other files in this Workspace are ignored. It is strongly recommended to use ssh-directory over basic-auth whenever possible and to bind a Secret to this Workspace over other volume types. name: basic-auth optional: true volumes: - name: trusted-ca configMap: name: $(params.caTrustConfigMapName) items: - key: $(params.caTrustConfigMapKey) path: ca-bundle.crt optional: true