diff --git a/ci/pipeline.yml b/ci/pipeline.yml new file mode 100644 index 0000000..c5d9bab --- /dev/null +++ b/ci/pipeline.yml @@ -0,0 +1,361 @@ +--- +# +# ci/pipeline.yml +# +# Pipeline structure file for a Go Project pipeline +# +# DO NOT MAKE CHANGES TO THIS FILE. Instead, modify +# ci/settings.yml and override what needs overridden. +# This uses spruce, so you have some options there. +# +# author: James Hunt +# created: 2016-03-30 + +meta: + name: (( param "Please name your pipeline" )) + release: (( concat meta.name " Release" )) + target: (( param "Please identify the name of the target Concourse CI" )) + pipeline: (( grab meta.name )) + + git: + email: (( param "Please provide the git email for automated commits" )) + name: (( param "Please provide the git name for automated commits" )) + + go: + version: 1.13 + module: (( concat "github.com/" meta.github.owner "/" meta.github.repo )) + cmd_module: (( grab meta.go.module )) + binary: (( grab meta.github.repo )) + force_static_binary: false + + image: + name: starkandwayne/concourse-go + tag: (( grab meta.go.version )) + + aws: + bucket: (( concat meta.pipeline "-pipeline" )) + region_name: us-east-1 + access_key: (( param "Please set your AWS Access Key ID" )) + secret_key: (( param "Please set your AWS Secret Key ID" )) + + github: + uri: (( concat "git@github.com:" meta.github.owner "/" meta.github.repo )) + owner: (( param "Please specify the name of the user / organization that owns the Github repository" )) + repo: (( param "Please specify the name of the Github repository" )) + branch: master + private_key: (( param "Please generate an SSH Deployment Key for this repo and specify it here" )) + access_token: (( param "Please generate a Personal Access Token and specify it here" )) + + slack: + webhook: (( param "Please specify your Slack Incoming Webhook Integration URL" )) + notification: '(( concat ":sadpanda: " meta.pipeline " build failed!
URL-GOES-HERE" ))' + channel: (( param "Please specify the channel (#name) or user (@user) to send messages to" )) + username: concourse + icon: http://cl.ly/image/3e1h0H3H2s0P/concourse-logo.png + fail_moji: ":airplane_arriving:" + success_moji: ":airplane_departure:" + upset_moji: ":sad_panda:" + fail_url: '(( concat "<" meta.url "/teams/$BUILD_TEAM_NAME/pipelines/$BUILD_PIPELINE_NAME/jobs/$BUILD_JOB_NAME/builds/$BUILD_NAME| Concourse Failure! " meta.slack.upset_moji ">" ))' + + url: (( param "What is the URL for the Concourse instance this pipeline is located on?" )) + +groups: + - name: (( grab meta.pipeline )) + jobs: + - test + - test-pr + - rc + - shipit + - major + - minor + - bump-patch + +jobs: + - name: test + public: true + serial: true + plan: + - do: + - { get: git, trigger: true } + - task: test + config: + platform: linux + image_resource: + type: docker-image + source: + repository: (( grab meta.image.name )) + tag: (( grab meta.image.tag )) + inputs: + - name: git + path: (( concat "gopath/src/" meta.go.module )) + run: + path: (( concat "./gopath/src/" meta.go.module "/ci/scripts/test" )) + args: [] + params: + MODULE: (( grab meta.go.module )) + on_failure: + put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + text: '(( concat meta.slack.fail_url " " meta.pipeline ": test job failed" ))' + + - name: test-pr + public: true + serial: true + plan: + - do: + - { get: git-pull-requests, trigger: true, version: every } + - put: git-pull-requests + params: + path: git-pull-requests + status: pending + - task: test + config: + platform: linux + image_resource: + type: docker-image + source: + repository: (( grab meta.image.name )) + tag: (( grab meta.image.tag )) + inputs: + - name: git-pull-requests + path: (( concat "gopath/src/" meta.go.module )) + run: + path: (( concat "./gopath/src/" meta.go.module "/ci/scripts/test" )) + args: [] + params: + MODULE: (( grab meta.go.module )) + on_success: + put: git-pull-requests + params: + path: git-pull-requests + status: success + on_failure: + put: git-pull-requests + params: + path: git-pull-requests + status: failure + - task: pr-success-message + config: + platform: linux + image_resource: + type: docker-image + source: + repository: (( grab meta.image.name )) + tag: (( grab meta.image.tag )) + inputs: + - { name: git-pull-requests } + outputs: + - { name: message } + run: + path: sh + args: + - -ce + - | + cd git-pull-requests + pr_url=$(git config --get pullrequest.url) + cd - + echo "<${pr_url}|Pull request passed test> Merge when ready: ${pr_url}" > message/body + on_failure: + put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + text: '(( concat meta.slack.fail_url " " meta.pipeline ": test job failed" ))' + + - name: rc + public: true + plan: + - do: + - in_parallel: + - { get: git, trigger: true, passed: [test] } + - { get: version, trigger: true, params: {pre: rc} } + - put: shipit-version + params: {file: version/number} + on_failure: + put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + text: '(( concat meta.slack.fail_url " " meta.pipeline ": rc job failed" ))' + + - name: bump-patch + public: true + plan: + - do: + - { get: version, trigger: true, params: { bump: patch, pre: rc }, passed: [shipit] } # bump rc + patch, so that subesquent version bumps can trigger a new bump-patch + - { put: version, params: { file: version/number} } + on_failure: + put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + text: '(( concat meta.slack.fail_url " " meta.pipeline ": bump-patch job failed" ))' + + - name: minor + public: true + plan: + - do: + - { get: version, trigger: false, params: {bump: minor} } + - { put: version, params: {file: version/number} } + on_failure: + put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + text: '(( concat meta.slack.fail_url " " meta.pipeline ": minor job failed" ))' + + - name: major + public: true + plan: + - do: + - { get: version, trigger: false, params: {bump: major} } + - { put: version, params: {file: version/number} } + on_failure: + put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + text: '(( concat meta.slack.fail_url " " meta.pipeline ": major job failed" ))' + + - name: shipit + public: true + serial: true + plan: + - do: + - in_parallel: + - { get: version, resource: shipit-version, passed: [rc], params: {bump: final} } + - { get: git, passed: [rc] } + - task: release + config: + image_resource: + type: docker-image + source: + repository: (( grab meta.image.name )) + tag: (( grab meta.image.tag )) + platform: linux + inputs: + - name: version + - name: git + path: (( concat "gopath/src/" meta.go.module )) + outputs: + - name: gh + - name: pushme + run: + path: (( concat "./gopath/src/" meta.go.module "/ci/scripts/shipit" )) + args: [] + params: + BINARY: (( grab meta.go.binary )) + REPO_ROOT: (( concat "gopath/src/" meta.go.module )) + VERSION_FROM: version/number + RELEASE_NAME: (( grab meta.release )) + RELEASE_ROOT: gh + REPO_OUT: pushme + BRANCH: (( grab meta.github.branch )) + CMD_PKG: (( grab meta.go.cmd_module )) + STATIC_BINARY: (( grab meta.go.force_static_binary )) + GIT_EMAIL: (( grab meta.git.email )) + GIT_NAME: (( grab meta.git.name )) + - put: version + params: { bump: final } + - put: git + params: + rebase: true + repository: pushme/git + - put: github + params: + name: gh/name + tag: gh/tag + body: gh/notes.md + globs: [gh/artifacts/*] + - in_parallel: + - put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + #I want this to actually print out the safe version, but for now.... may as well print _something_ + #text_file: notifications/message + text: A new version of safe was released! + on_failure: + put: notify + params: + channel: (( grab meta.slack.channel )) + username: (( grab meta.slack.username )) + icon_url: (( grab meta.slack.icon )) + text: '(( concat meta.slack.fail_url " " meta.pipeline ": shipit job failed" ))' + +resource_types: + - name: slack-notification + type: docker-image + source: + repository: cfcommunity/slack-notification-resource + + - name: pull-request + type: docker-image + source: + repository: jtarchie/pr + +resources: + - name: git + type: git + check_every: 60m + webhook_token: ((webhook.token)) + source: + uri: (( grab meta.github.uri )) + branch: (( grab meta.github.branch )) + private_key: (( grab meta.github.private_key )) + + - name: version + type: semver + source : + driver: s3 + bucket: (( grab meta.aws.bucket )) + region_name: (( grab meta.aws.region_name )) + key: version + access_key_id: (( grab meta.aws.access_key )) + secret_access_key: (( grab meta.aws.secret_key )) + initial_version: (( grab meta.initial_version || "0.0.1" )) + + - name: shipit-version + type: semver + source : + driver: s3 + bucket: (( grab meta.aws.bucket )) + region_name: (( grab meta.aws.region_name )) + key: shipit-version + access_key_id: (( grab meta.aws.access_key )) + secret_access_key: (( grab meta.aws.secret_key )) + initial_version: (( grab meta.initial_version || "0.0.1" )) + + - name: notify + check_every: 24h + type: slack-notification + source: + url: (( grab meta.slack.webhook )) + + - name: github + type: github-release + check_every: 60m + webhook_token: ((webhook.token)) + source: + user: (( grab meta.github.owner )) + repository: (( grab meta.github.repo )) + access_token: (( grab meta.github.access_token )) + + - name: git-pull-requests + type: pull-request + check_every: 60m + webhook_token: ((webhook.token)) + source: + access_token: (( grab meta.github.access_token )) + private_key: (( grab meta.github.private_key )) + repo: (( concat meta.github.owner "/" meta.github.repo )) + base: (( grab meta.github.branch )) diff --git a/ci/repipe b/ci/repipe new file mode 100755 index 0000000..1390631 --- /dev/null +++ b/ci/repipe @@ -0,0 +1,114 @@ +#!/bin/bash +# +# ci/repipe +# +# Script for merging together pipeline configuration files +# (via Spruce!) and configuring Concourse. +# +# author: James Hunt +# Dennis Bell +# created: 2016-03-04 + +need_command() { + local cmd=${1:?need_command() - no command name given} + + if [[ ! -x "$(command -v $cmd)" ]]; then + echo >&2 "${cmd} is not installed." + if [[ "${cmd}" == "spruce" ]]; then + echo >&2 "Please download it from https://github.com/geofffranks/spruce/releases" + fi + exit 2 + fi +} + +NO_FLY= +SAVE_MANIFEST= +VALIDATE_PIPELINE= +NON_INTERACTIVE= + +cleanup() { + rm -f save-manifest.yml + if [[ -n ${SAVE_MANIFEST} && -e .deploy.yml ]]; then + mv .deploy.yml save-manifest.yml + fi + rm -f .deploy.yml +} + +usage() { + echo Command line arguments: + echo "no-fly Do not execute any fly commands" + echo "save-manifest Save manifest to file save-manifest" + echo "validate Validatei pipeline instead of set pipeline" + echo "validate-strict Validate pipeline with strict mode" + echo "non-interactive Run set-pipeline in non-interactive mode" +} + +for arg do + case "${arg}" in + no-fly|no_fly) NO_FLY="yes" ;; + save-manifest|save_manifest) SAVE_MANIFEST="yes" ;; + validate) VALIDATE_PIPELINE="normal" ;; + validate-strict|validate_strict) VALIDATE_PIPELINE="strict" ;; + non-interactive|non_interactive) NON_INTERACTIVE="--non-interactive" ;; + help|-h|--help) usage; exit 0 ;; + *) echo Invalid argument + usage + exit 1 + esac +done + +cd $(dirname $BASH_SOURCE[0]) +echo "Working in $(pwd)" +need_command spruce + +# Allow for target-specific settings +settings_file="$(ls -1 settings.yml ${CONCOURSE_TARGET:+"settings-${CONCOURSE_TARGET}.yml"} 2>/dev/null | tail -n1)" +if [[ -z "$settings_file" ]] +then + echo >&2 "Missing local settings in ci/settings.yml${CONCOURSE_TARGET:+" or ci/settings-${CONCOURSE_TARGET}.yml"}!" + exit 1 +fi + +echo >&2 "Using settings found in ${settings_file}" + +set -e +trap "cleanup" QUIT TERM EXIT INT +spruce merge pipeline.yml ${settings_file} > .deploy.yml +PIPELINE=$(spruce json .deploy.yml | jq -r '.meta.pipeline // ""') +if [[ -z ${PIPELINE} ]]; then + echo >&2 "Missing pipeline name in ci/settings.yml!" + exit 1 +fi + +TARGET_FROM_SETTINGS=$(spruce json .deploy.yml | jq -r '.meta.target // ""') +if [[ -z ${CONCOURSE_TARGET} ]]; then + TARGET=${TARGET_FROM_SETTINGS} +elif [[ "$CONCOURSE_TARGET" != "$TARGET_FROM_SETTINGS" ]] +then + echo >&2 "Target in {$settings_file} differs from target in \$CONCOURSE_TARGET" + echo >&2 " \$CONCOURSE_TARGET: $CONCOURSE_TARGET" + echo >&2 " Target in file: $TARGET_FROM_SETTINGS" + exit 1 +else + TARGET=${CONCOURSE_TARGET} +fi + +if [[ -z ${TARGET} ]]; then + echo >&2 "Missing Concourse Target in ci/settings.yml!" + exit 1 +fi + +fly_cmd="${FLY_CMD:-fly}" + +[[ -n ${NO_FLY} ]] && { echo no fly execution requested ; exit 0; } + +case "${VALIDATE_PIPELINE}" in + normal) fly_opts="validate-pipeline" ;; + strict) fly_opts="validate-pipeline --strict" ;; + *) fly_opts="set-pipeline ${NON_INTERACTIVE} --pipeline ${PIPELINE}" ;; +esac + +set +x +$fly_cmd --target ${TARGET} ${fly_opts} --config .deploy.yml +[[ -n ${VALIDATE_PIPELINE} ]] && exit 0 +$fly_cmd --target ${TARGET} unpause-pipeline --pipeline ${PIPELINE} diff --git a/ci/scripts/shipit b/ci/scripts/shipit new file mode 100755 index 0000000..6da4d4a --- /dev/null +++ b/ci/scripts/shipit @@ -0,0 +1,94 @@ +#!/bin/bash + +# +# ci/scripts/shipit +# +# Script for generating Github release / tag assets +# and managing release notes for a software pipeline +# +# author: James Hunt +# created: 2016-03-30 +# + +set -eu + +: ${GIT_EMAIL:?required} +: ${GIT_NAME:?required} + +if [[ -z ${VERSION_FROM} ]]; then + echo >&2 "VERSION_FROM environment variable not set, or empty. Did you misconfigure Concourse?" + exit 2 +fi +if [[ ! -f ${VERSION_FROM} ]]; then + echo >&2 "Version file (${VERSION_FROM}) not found. Did you misconfigure Concourse?" + exit 2 +fi +VERSION=$(cat ${VERSION_FROM}) +if [[ -z ${VERSION} ]]; then + echo >&2 "Version file (${VERSION_FROM}) was empty. Did you misconfigure Concourse?" + exit 2 +fi + +if [[ ! -f ${REPO_ROOT}/ci/release_notes.md ]]; then + echo >&2 "ci/release_notes.md not found. Did you forget to write them?" + exit 1 +fi + +############################################################### + +go version; echo; echo +#?#ORIGIN=$(pwd) +TARGETS=${TARGETS:-linux/amd64 darwin/amd64 windows/amd64} +ROOT=$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd ) + +pushd $REPO_ROOT +newgopath=${ROOT%%/gopath/*}/gopath +if [[ -d ${newgopath} ]]; then + if [[ -z ${GOPATH} ]]; then + GOPATH="${newgopath}" + else + GOPATH="${newgopath}:${GOPATH}" + fi + PATH="${PATH}:${newgopath}/bin" +fi +echo ">> Using GOPATH ${GOPATH}" +go get github.com/mitchellh/gox +popd + +if [[ -n ${STATIC_BINARY} && ${STATIC_BINARY} != "false" ]]; then + export CGO_ENABLED=0 +fi + +pushd $REPO_ROOT +mkdir artifacts +gox -osarch="${TARGETS}" --output="artifacts/${BINARY}-{{.OS}}-{{.Arch}}" -ldflags="-X main.Version=${VERSION}" ./... +go build -o "${BINARY}" -ldflags="-X main.Version=${VERSION}" ${CMD_PKG:-.} +./${BINARY} -v +popd + +echo "v${VERSION}" > ${RELEASE_ROOT}/tag +echo "${RELEASE_NAME} v${VERSION}" > ${RELEASE_ROOT}/name +mv ${REPO_ROOT}/ci/release_notes.md ${RELEASE_ROOT}/notes.md +mv ${REPO_ROOT}/artifacts ${RELEASE_ROOT}/artifacts + +cat > ${RELEASE_ROOT}/notification < New ${RELEASE_NAME} v${VERSION} released! +EOF + + +# GIT! +if [[ -z $(git config --global user.email) ]]; then + git config --global user.email "${GIT_EMAIL}" +fi +if [[ -z $(git config --global user.name) ]]; then + git config --global user.name "${GIT_NAME}" +fi + +(cd ${REPO_ROOT} + git merge --no-edit ${BRANCH} + git add -A + git status + git commit -m "release v${VERSION}") + +# so that future steps in the pipeline can push our changes +cp -a ${REPO_ROOT} ${REPO_OUT}/git diff --git a/ci/scripts/test b/ci/scripts/test new file mode 100755 index 0000000..7327879 --- /dev/null +++ b/ci/scripts/test @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +export GOPATH=${PWD}/gopath +export PATH=${PATH}:${GOPATH}/bin +cd ${GOPATH}/src/${MODULE} + +# safe uses the Makefile, and doesn't run +# any `go test` / `go vet` stuff... + +go version; echo; echo +make test diff --git a/ci/settings.yml b/ci/settings.yml new file mode 100644 index 0000000..64d2c84 --- /dev/null +++ b/ci/settings.yml @@ -0,0 +1,30 @@ +--- +meta: + name: carousel + release: Carousel + target: pipes + url: https://pipes.starkandwayne.com + + initial_version: 0.0.1 + + go: + force_static_binary: true + + git: + email: ci@starkandwayne.com + name: CI Bot + + aws: + access_key: ((cfcommunity.access)) + secret_key: ((cfcommunity.secret)) + + github: + owner: starkandwayne + repo: carousel + branch: main + private_key: ((github.private-key)) + access_token: ((github.access-token)) + + slack: + webhook: ((slack.webhook)) + channel: '#carousel'