diff --git a/.editorconfig b/.editorconfig index dd23c213..0f61cebc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -23,3 +23,7 @@ indent_size = 2 [Makefile] indent_style = tab indent_size = 4 + +[*.sh] +indent_size = 2 +indent_style = tab diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 47be8c15..8c65ca27 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: markdownlint run: make markdownlint - name: prune diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index 38613c93..874bd69d 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -9,6 +9,6 @@ jobs: name: Shellcheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Run shellcheck in container run: make shellcheck diff --git a/.markdownlint.json b/.markdownlint.json deleted file mode 100644 index 67d2ae55..00000000 --- a/.markdownlint.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "MD013": false -} diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 00000000..c2658d7d --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1 @@ +node_modules/ diff --git a/.templateversionrc b/.templateversionrc deleted file mode 100644 index e6952ab7..00000000 --- a/.templateversionrc +++ /dev/null @@ -1 +0,0 @@ -92efcc1 diff --git a/Makefile b/Makefile index 37f64831..95d3cd06 100644 --- a/Makefile +++ b/Makefile @@ -19,8 +19,7 @@ help: ## help target to show available commands with information .PHONY: markdownlint markdownlint: ## Validate markdown files - docker-compose run docs markdownlint .github/ --ignore node_modules - docker-compose run docs markdownlint . --ignore node_modules + docker-compose run docs markdownlint . .PHONY: zsh zsh: ## open dev container with build environment @@ -32,4 +31,4 @@ prune: ## delete the whole environment .Phony: shellcheck shellcheck: ## run shellcheck - docker-compose run shellcheck -x src/entrypoint.sh + docker-compose run shellcheck -x src/*.sh diff --git a/README.md b/README.md index 728cf3ea..8a9c0069 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,10 @@ This GitHub action will help you to keep track of the template changes. ## Usage +### Update + +starting with version v0.5.2-draft the `templateversionrc` file is not needed anymore. You can delete that file from the target repositories. + ### GitHub Actions Add this configuration to your github action @@ -59,7 +63,8 @@ You will receive a pull request within your repository if there are some changes | github_token | Token for the repo. Can be passed in using `$\{{ secrets.GITHUB_TOKEN }}` | `true` | | | source_repo_path | Repository path of the template | `true` | | | upstream_branch | The target branch | `true` | `main` | -| source_repo_ssh_private_key | `[optional]` private ssh key for the source repository. E.q. useful if using a private template repository. [see](#private-template-repository)| `false` | | +| source_repo_ssh_private_key | `[optional]` private ssh key for the source repository. E.q. useful if using a private template repository. +[see](#private-template-repository)| `false` | | | pr_branch_name_prefix | `[optional]` the prefix of branches created by this action | `false` | `chore/template_sync` | | pr_title | `[optional]` the title of PRs opened by this action. Must be already created. | `false` | `upstream merge template repository` | | pr_labels | `[optional]` comma separated list. [pull request labels][pr-labels]. Must be already created. | `false` | | @@ -85,8 +90,9 @@ If you have a private template repository. #### SSH You have various options to use ssh keys with GitHub. -An example are [deployment keys][deployment-keys]. For our use case write permissions are not needed. -Within the repository where the GitHub action is enabled add a secret (e.q. `SOURCE_REPO_SSH_PRIVATE_KEY`) with the content of your private SSH key. Make sure that the read permissions of that secret fulfil your use case. +An example are [deployment keys][deployment-keys]. For our use case write permissions are not needed.i +Within the repository where the GitHub action is enabled add a secret (e.q. `SOURCE_REPO_SSH_PRIVATE_KEY`) with the content of your private SSH key. +Make sure that the read permissions of that secret fulfil your use case. Set the optional `source_repo_ssh_private_key` input parameter. ```yaml @@ -110,7 +116,8 @@ jobs: ## Ignore Files -Create a `.templatesyncignore` file. Just like writing a `.gitignore` file, follow the [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) in defining the files and folders that should be excluded from syncing with the template repository. +Create a `.templatesyncignore` file. Just like writing a `.gitignore` file, follow the [glob pattern](https://en.wikipedia.org/wiki/Glob_(programming)) +in defining the files and folders that should be excluded from syncing with the template repository. It can also be stored inside `.github` folder. @@ -118,7 +125,8 @@ _Note: It is not possible to sync also the `.templatesyncignore` itself. Any cha ## Debug -You must create a secret named `ACTIONS_STEP_DEBUG` with the value `true` to see the debug messages set by this command in the log. For more information, see "[Enabling debug logging.][enabling-debug-logging]" +You must create a secret named `ACTIONS_STEP_DEBUG` with the value `true` to see the debug messages set by this command in the log. +For more information, see "[Enabling debug logging.][enabling-debug-logging]" ## DEV diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index f25c1f29..e403dfd6 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -23,11 +23,9 @@ SshConfigureEntry[Configure ssh related variables] GitConfigureEntry[Configure git global settings] EnvCheckSync{Required environment variables exists} -SshConfigureSync[Configure SSH variables] +SshConfigureSync[Eventually configure SSH variables] SetVariablesSync[Set the needed variables, e.q. with reading remote repository] -CheckTemplateFileExists{"Check if the .templatesyncrc file exists\n(First inside .github folder, then in root)"} -WriteTemplateVersionSync["Read and write the template sync version into variable"] -CompareTemplateVersionSync{"Compare the source repository version"} +CheckCommitLocalExistent{"Check if source commit hash is present in target repo"} GitCheckoutSync["Create git branch "] GitPullSync["Pull from remote repository"] CheckIgnoreFileExistsSync{"Check if .templatesyncignore file exists\n(First inside .github folder, then in root)"} @@ -67,15 +65,12 @@ EnvCheckSync -->|do exist| SshConfigureSync SshConfigureSync --> SetVariablesSync subgraph compareVersion["Compare the sync version"] -SetVariablesSync --> CheckTemplateFileExists -CheckTemplateFileExists -->|exists| WriteTemplateVersionSync -CheckTemplateFileExists -->|does not exist| CompareTemplateVersionSync -WriteTemplateVersionSync --> CompareTemplateVersionSync -CompareTemplateVersionSync -->|equal versions| Exit +SetVariablesSync --> CheckCommitLocalExistent +CheckCommitLocalExistent -->|commit hash already in target history| Exit end subgraph git["Git Actions"] -CompareTemplateVersionSync -->|versions not equal| GitCheckoutSync +CheckCommitLocalExistent -->|commit hash not in target history| GitCheckoutSync GitCheckoutSync --> GitPullSync GitPullSync --> CheckIgnoreFileExistsSync CheckIgnoreFileExistsSync -->|does not exist| GitCommitSync diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index 49bc4c28..00000000 --- a/docs/_config.yml +++ /dev/null @@ -1,4 +0,0 @@ -markdown: GFM -# remote_theme: pages-themes/leap-day@v0.2.0 -# plugins: -# - jekyll-remote-theme # add this line to the plugins list if you already have one diff --git a/src/Dockerfile b/src/Dockerfile index 883d43e4..b4a06278 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -22,7 +22,8 @@ RUN tar --strip-components=1 -xf ghcli.tar.gz ADD *.sh /bin/ RUN chmod +x /bin/entrypoint.sh \ - && chmod +x /bin/sync_template.sh + && chmod +x /bin/sync_template.sh \ + && chmod +x /bin/sync_common.sh RUN mkdir -p /root/.ssh \ && ssh-keyscan -t rsa github.com >> /root/.ssh/known_hosts diff --git a/src/entrypoint.sh b/src/entrypoint.sh index 4c5e283f..10df812c 100755 --- a/src/entrypoint.sh +++ b/src/entrypoint.sh @@ -3,13 +3,16 @@ set -e # set -u set -x +# shellcheck source=src/sync_common.sh +source sync_common.sh + [ -z "${GITHUB_TOKEN}" ] && { - echo "::error::Missing input 'github_token: \${{ secrets.GITHUB_TOKEN }}'."; + err "Missing input 'github_token: \${{ secrets.GITHUB_TOKEN }}'."; exit 1; }; if [[ -z "${SOURCE_REPO_PATH}" ]]; then - echo "::error::Missing input 'source_repo_path: \${{ input.source_repo_path }}'.;" + err "Missing input 'source_repo_path: \${{ input.source_repo_path }}'."; exit 1 fi @@ -18,28 +21,43 @@ SOURCE_REPO_HOSTNAME="${HOSTNAME:-github.com}" # In case of private template repository this will be overwritten SOURCE_REPO_PREFIX="https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@${SOURCE_REPO_HOSTNAME}/" -# Forward to /dev/null to swallow the output of the private key -if [[ -n "${SSH_PRIVATE_KEY_SRC}" ]] &>/dev/null; then +function ssh_setup() { + echo "::group::ssh setup" + + info "prepare ssh" SRC_SSH_FILE_DIR="/tmp/.ssh" SRC_SSH_PRIVATEKEY_FILE_NAME="id_rsa_actions_template_sync" export SRC_SSH_PRIVATEKEY_ABS_PATH="${SRC_SSH_FILE_DIR}/${SRC_SSH_PRIVATEKEY_FILE_NAME}" - echo "::debug::We are using SSH within a private source repo" + debug "We are using SSH within a private source repo" mkdir -p "${SRC_SSH_FILE_DIR}" # use cat <<< instead of echo to swallow output of the private key cat <<< "${SSH_PRIVATE_KEY_SRC}" | sed 's/\\n/\n/g' > "${SRC_SSH_PRIVATEKEY_ABS_PATH}" chmod 600 "${SRC_SSH_PRIVATEKEY_ABS_PATH}" SOURCE_REPO_PREFIX="git@${SOURCE_REPO_HOSTNAME}:" + + echo "::endgroup::" +} + +# Forward to /dev/null to swallow the output of the private key +if [[ -n "${SSH_PRIVATE_KEY_SRC}" ]] &>/dev/null; then + ssh_setup fi export SOURCE_REPO="${SOURCE_REPO_PREFIX}${SOURCE_REPO_PATH}" -echo "::group::git init" -echo "set git global configuration" -git config --global user.email "github-action@actions-template-sync.noreply.${SOURCE_REPO_HOSTNAME}" -git config --global user.name "${GITHUB_ACTOR}" -git config --global pull.rebase false -git config --global --add safe.directory /github/workspace -echo "::endgroup::" +function git_init() { + echo "::group::git init" + info "set git global configuration" + + git config --global user.email "github-action@actions-template-sync.noreply.${SOURCE_REPO_HOSTNAME}" + git config --global user.name "${GITHUB_ACTOR}" + git config --global pull.rebase false + git config --global --add safe.directory /github/workspace + + echo "::endgroup::" +} + +git_init # shellcheck source=src/sync_template.sh source sync_template.sh diff --git a/src/sync_common.sh b/src/sync_common.sh new file mode 100755 index 00000000..86313f40 --- /dev/null +++ b/src/sync_common.sh @@ -0,0 +1,41 @@ +#! /usr/bin/env bash + +set -e +# set -u +set -x + +####################################### +# write a message to STDERR. +# Arguments: +# message to print. +####################################### +err() { + echo "::error::[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2; +} + +####################################### +# write a debug message. +# Arguments: +# message to print. +####################################### +debug() { + echo "::debug::$*"; +} + +####################################### +# write a warn message. +# Arguments: +# message to print. +####################################### +warn() { + echo "::warn::$*"; +} + +####################################### +# write a info message. +# Arguments: +# message to print. +####################################### +info() { + echo "::info::$*"; +} diff --git a/src/sync_template.sh b/src/sync_template.sh index 915966af..945cbfef 100755 --- a/src/sync_template.sh +++ b/src/sync_template.sh @@ -1,109 +1,100 @@ #! /usr/bin/env bash + set -e # set -u set -x +# shellcheck source=src/sync_template.sh +source sync_common.sh + if [[ -z "${PR_COMMIT_MSG}" ]]; then - echo "::error::Missing env variable 'PR_COMMIT_MSG'" >&2; + err "Missing env variable 'PR_COMMIT_MSG'"; exit 1; fi if [[ -z "${SOURCE_REPO}" ]]; then - echo "::error::Missing env variable 'SOURCE_REPO'" >&2; + err "Missing env variable 'SOURCE_REPO'"; exit 1; fi if [[ -z "${UPSTREAM_BRANCH}" ]]; then - echo "::error::Missing env variable 'UPSTREAM_BRANCH'" >&2; + err "Missing env variable 'UPSTREAM_BRANCH'"; exit 1; fi if ! [ -x "$(command -v gh)" ]; then - echo "::error::github-cli gh is not installed. 'https://github.com/cli/cli'" >&2; + err "github-cli gh is not installed. 'https://github.com/cli/cli'"; exit 1; fi if [[ -n "${SRC_SSH_PRIVATEKEY_ABS_PATH}" ]]; then - echo "::debug:: using ssh private key for private source repository" + debug "using ssh private key for private source repository" export GIT_SSH_COMMAND="ssh -i ${SRC_SSH_PRIVATEKEY_ABS_PATH}" fi -TEMPLATE_VERSION_FILE_PATH=".templateversionrc" TEMPLATE_SYNC_IGNORE_FILE_PATH=".templatesyncignore" TEMPLATE_REMOTE_GIT_HASH=$(git ls-remote "${SOURCE_REPO}" HEAD | awk '{print $1}') NEW_TEMPLATE_GIT_HASH=$(git rev-parse --short "${TEMPLATE_REMOTE_GIT_HASH}") NEW_BRANCH="${PR_BRANCH_NAME_PREFIX}_${NEW_TEMPLATE_GIT_HASH}" +debug "new Git HASH ${NEW_TEMPLATE_GIT_HASH}" echo "::group::Check new changes" -echo "::debug::new Git HASH ${NEW_TEMPLATE_GIT_HASH}" -# Check if the Version File exists inside .github folder or if it doesn't exist at all -if [[ -f ".github/${TEMPLATE_VERSION_FILE_PATH}" || ! -f "${TEMPLATE_VERSION_FILE_PATH}" ]]; then - echo "::debug::using version file as in .github folder" - TEMPLATE_VERSION_FILE_PATH=".github/${TEMPLATE_VERSION_FILE_PATH}" -fi -if [ -r ${TEMPLATE_VERSION_FILE_PATH} ]; then - CURRENT_TEMPLATE_GIT_HASH=$(cat ${TEMPLATE_VERSION_FILE_PATH}) - echo "::debug::Current git hash ${CURRENT_TEMPLATE_GIT_HASH}" +git cat-file -e "${TEMPLATE_REMOTE_GIT_HASH}" || COMMIT_NOT_IN_HIST=true +if [ "$COMMIT_NOT_IN_HIST" != true ] ; then + warn "repository is up to date!" + exit 0 fi -if [ "${NEW_TEMPLATE_GIT_HASH}" == "${CURRENT_TEMPLATE_GIT_HASH}" ]; then - echo "::warn::repository is up to date" - exit 0 -fi echo "::endgroup::" echo "::group::Pull template" -echo "::debug::create new branch from default branch with name ${NEW_BRANCH}" +debug "create new branch from default branch with name ${NEW_BRANCH}" git checkout -b "${NEW_BRANCH}" -echo "::debug::pull changes from template" +debug "pull changes from template" +# TODO(anau) eventually make squash optional git pull "${SOURCE_REPO}" --allow-unrelated-histories --squash --strategy=recursive -X theirs echo "::endgroup::" -if [ -s ${TEMPLATE_SYNC_IGNORE_FILE_NAME} ] -then +if [ -s "${TEMPLATE_SYNC_IGNORE_FILE_NAME}" ]; then echo "::group::restore ignore file" - git reset ${TEMPLATE_SYNC_IGNORE_FILE_NAME} - git checkout -- ${TEMPLATE_SYNC_IGNORE_FILE_NAME} + info "restore the ignore file" + git reset "${TEMPLATE_SYNC_IGNORE_FILE_NAME}" + git checkout -- "${TEMPLATE_SYNC_IGNORE_FILE_NAME}" echo "::endgroup::" fi -echo "::group::persist template version" -echo "write new template version file" -echo "${NEW_TEMPLATE_GIT_HASH}" > ${TEMPLATE_VERSION_FILE_PATH} -echo "::debug::wrote new template version file with content $(cat ${TEMPLATE_VERSION_FILE_PATH})" -echo "::endgroup::" - -echo "::group::commit and push changes" +echo "::group::commit changes" git add . # Check if the Ignore File exists inside .github folder or if it doesn't exist at all if [[ -f ".github/${TEMPLATE_SYNC_IGNORE_FILE_PATH}" || ! -f "${TEMPLATE_SYNC_IGNORE_FILE_PATH}" ]]; then - echo "::debug::using ignore file as in .github folder" + debug "using ignore file as in .github folder" TEMPLATE_SYNC_IGNORE_FILE_PATH=".github/${TEMPLATE_SYNC_IGNORE_FILE_PATH}" fi # we are checking the ignore file if it exists or is empty # -s is true if the file contains whitespaces if [ -s ${TEMPLATE_SYNC_IGNORE_FILE_PATH} ]; then - echo "::debug::unstage files from template sync ignore" + debug "unstage files from template sync ignore" git reset --pathspec-from-file="${TEMPLATE_SYNC_IGNORE_FILE_PATH}" - echo "::debug::clean untracked files" + debug "clean untracked files" git clean -df - echo "::debug::discard all unstaged changes" + debug "discard all unstaged changes" git checkout -- . fi git commit -m "${PR_COMMIT_MSG}" +echo "::endgroup::" + push_and_create_pr () { if [ "$IS_DRY_RUN" != "true" ]; then - echo "::debug::push changes" + echo "::group::push changes and create PR" + debug "push changes" git push --set-upstream origin "${NEW_BRANCH}" - echo "::endgroup::" - echo "::group::create pull request" gh pr create \ --title "${PR_TITLE}" \ --body "Merge ${SOURCE_REPO_PATH} ${NEW_TEMPLATE_GIT_HASH}" \ @@ -111,7 +102,7 @@ push_and_create_pr () { -l "${PR_LABELS}" echo "::endgroup::" else - echo "::warn::dry_run option is set to off. Skipping push changes and skip create pr" + warn "dry_run option is set to off. Skipping push changes and skip create pr" fi }