diff --git a/.dockerignore b/.dockerignore index 144eec08..d20e37cf 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,7 +3,6 @@ internal/cmd/mocks/ Jenkinsfile dev/ -dist/ output/ **/*.sw[po] diff --git a/.goreleaser.yml b/.goreleaser.yml index 08094ae8..19ebf2a3 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -7,7 +7,8 @@ before: # you may remove this if you don't need go generate - go generate ./... builds: -- main: ./cmd/conjur +- id: conjur-cli-go + main: ./cmd/conjur binary: conjur env: - CGO_ENABLED=0 @@ -21,7 +22,7 @@ builds: - linux - darwin - windows - goamd64: + goamd64: - v1 # The `Tag` override is there to provide the git commit information in the # final binary. See `Static long version tags` in the `Building` section @@ -41,9 +42,31 @@ builds: # binary-windows_amd64.exe. - mkdir -p "{{ dir .Path }}/../binaries" - cp "{{ .Path }}" "{{ dir .Path }}/../binaries/conjur_{{ .Target }}{{ .Ext }}" +- id: integration + command: test + main: ./cmd/integration + binary: integration + no_main_check: true + env: + - CGO_ENABLED=0 + # Tag 'netgo' is a Go build tag that ensures a pure Go networking stack + # in the resulting binary instead of using the default host's stack to + # ensure a fully static artifact that has no dependencies. + flags: + - -tags=netgo,dev,integration + - -a + - -c + - -v + goos: + - linux + goarch: + - amd64 + goamd64: + - v1 archives: - id: conjur-cli-go-archive + builds: [conjur-cli-go] files: - CHANGELOG.md - LICENSE diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d74e749..03af792e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Nothing should go in this section, please add to the latest unreleased version (and update the corresponding date), or add a new version. -## [8.0.0] - 2023-01-12 +## [8.0.0] - 2023-01-20 ### Added - Initial release of Conjur CLI written in Golang + +## [0.0.0] - 2023-01-01 + +### Added +- Placeholder version to capture the reset of the repository diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..fd3549fa --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM alpine:3.17.1 as conjur-cli-go +LABEL org.opencontainers.image.authors="CyberArk Software Ltd." + +ENTRYPOINT [ "/usr/local/bin/conjur" ] + +COPY dist/goreleaser/binaries/conjur_linux_amd64_v1 /usr/local/bin/conjur diff --git a/Jenkinsfile b/Jenkinsfile index 2da5c569..760f2684 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,8 +15,13 @@ if (params.MODE == "PROMOTE") { release.promote(params.VERSION_TO_PROMOTE) { sourceVersion, targetVersion, assetDirectory -> // Any assets from sourceVersion Github release are available in assetDirectory // Any version number updates from sourceVersion to targetVersion occur here - // Any publishing of stargetVersion artifacts occur here + // Any publishing of targetVersion artifacts occur here // Anything added to assetDirectory will be attached to the Github Release + + // Promote source version to target version. + + // NOTE: the use of --pull to ensure source images are pulled from internal registry + sh "source ./bin/build_utils && ./bin/publish_container_images --promote --source ${sourceVersion}-\$(git_commit) --target ${targetVersion} --pull" } return } @@ -76,34 +81,58 @@ pipeline { } } - stage('Run unit tests') { - steps { - sh './bin/test_unit' - } - post { - always { - sh './bin/coverage' - junit 'junit.xml' - - cobertura autoUpdateHealth: false, - autoUpdateStability: false, - coberturaReportFile: 'coverage.xml', - conditionalCoverageTargets: '70, 0, 0', - failUnhealthy: false, - failUnstable: false, - maxNumberOfBuilds: 0, - lineCoverageTargets: '70, 0, 0', - methodCoverageTargets: '70, 0, 0', - onlyStable: false, - sourceEncoding: 'ASCII', - zoomCoverageChart: false - ccCoverage("gocov", "--prefix github.com/cyberark/conjur-cli-go") + stage('Build while unit testing') { + parallel { + stage('Run unit tests') { + steps { + sh './bin/test_unit' + } + post { + always { + sh './bin/coverage' + junit 'junit.xml' + + cobertura autoUpdateHealth: false, + autoUpdateStability: false, + coberturaReportFile: 'coverage.xml', + conditionalCoverageTargets: '70, 0, 0', + failUnhealthy: false, + failUnstable: false, + maxNumberOfBuilds: 0, + lineCoverageTargets: '70, 0, 0', + methodCoverageTargets: '70, 0, 0', + onlyStable: false, + sourceEncoding: 'ASCII', + zoomCoverageChart: false + ccCoverage("gocov", "--prefix github.com/cyberark/conjur-cli-go") + } + } + } + + stage('Build release artifacts') { + steps { + dir('./pristine-checkout') { + // Go releaser requires a pristine checkout + checkout scm + + // Create release artifacts without releasing to Github + sh "cp ../VERSION ./VERSION" + sh "./bin/build_release --skip-validate --rm-dist" + + // Build container images + sh "./bin/build_container_images" + + // Archive release artifacts + archiveArtifacts 'dist/goreleaser/' + } + } } } } + stage('Run integration tests') { steps { - dir('ci') { + dir('./pristine-checkout/ci') { script { try{ sh 'summon -f ./okta/secrets.yml ./test_integration' @@ -115,20 +144,6 @@ pipeline { } } - stage('Build release artifacts') { - steps { - dir('./pristine-checkout') { - // Go releaser requires a pristine checkout - checkout scm - - // Create release packages without releasing to Github - sh "cp ../VERSION ./VERSION" - sh "./bin/build_release --skip-validate --rm-dist" - archiveArtifacts 'dist/goreleaser/' - } - } - } - stage('Release') { when { expression { @@ -147,6 +162,9 @@ pipeline { sh """go-bom --tools "${toolsDirectory}" --go-mod ./go.mod --image "golang" --main "cmd/conjur/" --output "${billOfMaterialsDirectory}/go-app-bom.json" """ // Create Go module SBOM sh """go-bom --tools "${toolsDirectory}" --go-mod ./go.mod --image "golang" --output "${billOfMaterialsDirectory}/go-mod-bom.json" """ + + // Publish container images to internal registry + sh './bin/publish_container_images --internal' } } } diff --git a/bin/build_container_images b/bin/build_container_images new file mode 100755 index 00000000..e74bc5ff --- /dev/null +++ b/bin/build_container_images @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -eo pipefail + +# Navigate to the bin directory (where this script lives) to ensure we can run this script +# from anywhere. +cd "$(dirname "$0")" + +. ./build_utils + +function main() { + local REPO_ROOT="$(repo_root)" + local CONTAINER_IMAGE_AND_TAG="conjur-cli:$(project_version_with_commit)" + + # Build container image/s by copying binaries + # + echo "Building ${CONTAINER_IMAGE_AND_TAG} container image" + docker build \ + --tag "${CONTAINER_IMAGE_AND_TAG}" \ + --rm \ + --file "${REPO_ROOT}/Dockerfile" \ + "${REPO_ROOT}" +} + + +main diff --git a/bin/build_release b/bin/build_release index 8810ee99..38b16d46 100755 --- a/bin/build_release +++ b/bin/build_release @@ -2,31 +2,36 @@ set -eo pipefail -PROJECT_NAME=conjur-cli-go -REPO_ROOT="$(git rev-parse --show-toplevel)" - -# Get the version of Go specified by the "go directive" in go.mod -# Grep it to avoid Go binary dependency -GO_VERSION="v$(grep "^\bgo\b" "${REPO_ROOT}/go.mod" | awk '{print $2}')" - -# Determine where VERSION file is based on goreleaser argument -VERSION=$(<"${REPO_ROOT}/VERSION") - -# Remove Jenkins build number from VERSION -VERSION="${VERSION/-*/}" - -# Use a GoReleaser Docker image containing cross-compilation tools -# This image is recommended by the official GoReleaser docs -# https://goreleaser.com/cookbooks/cgo-and-crosscompiling/ -GORELEASER_IMAGE="goreleaser/goreleaser-cross:latest" - -echo "Docker image for release build: ${GORELEASER_IMAGE}" - -docker run --rm \ - --env VERSION="${VERSION}" \ - --env GO_VERSION="${GO_VERSION}" \ - --volume "${REPO_ROOT}:/${PROJECT_NAME}" \ - --workdir /${PROJECT_NAME} \ - "${GORELEASER_IMAGE}" --rm-dist "$@" - -echo "Releases built. Archives can be found in dist/goreleaser" +# Navigate to the bin directory (where this script lives) to ensure we can run this script +# from anywhere. +cd "$(dirname "$0")" + +. ./build_utils + +function main() { + local REPO_ROOT="$(repo_root)" + local PROJECT_WD="github.com/cyberark/conjur-cli-go" + local VERSION="$(project_semantic_version)" + + # Get the version of Go specified by the "go directive" in go.mod + # Grep it to avoid Go binary dependency + local GO_VERSION="v$(grep "^\bgo\b" "${REPO_ROOT}/go.mod" | awk '{print $2}')" + + # Use a GoReleaser Docker image containing cross-compilation tools + # This image is recommended by the official GoReleaser docs + # https://goreleaser.com/cookbooks/cgo-and-crosscompiling/ + local GORELEASER_IMAGE="goreleaser/goreleaser-cross:latest" + + # Compile binaries with Go Releaser + # + echo "Docker image for release build: ${GORELEASER_IMAGE}" + docker run --rm \ + --env VERSION="${VERSION}" \ + --env GO_VERSION="${GO_VERSION}" \ + --volume "${REPO_ROOT}:/${PROJECT_WD}" \ + --workdir /${PROJECT_WD} \ + "${GORELEASER_IMAGE}" --rm-dist "$@" + echo "Releases built. Archives can be found in dist/goreleaser" +} + +main "$@" diff --git a/bin/build_utils b/bin/build_utils index 5ef9a78d..672690fb 100644 --- a/bin/build_utils +++ b/bin/build_utils @@ -26,6 +26,51 @@ function retrieve_cyberark_ca_cert() { fi } -repo_root() { +function repo_root() { git rev-parse --show-toplevel } + +function git_commit() { + git rev-parse --short HEAD +} + +function project_version() { + # VERSION derived from CHANGELOG and automated release library + echo "$(<"$(repo_root)/VERSION")" +} + +function project_semantic_version() { + local version=$(project_version) + + # Remove Jenkins build number from VERSION + echo "${version/-*/}" +} + +function project_semantic_version_with_commit() { + echo "$(project_semantic_version)-$(git_commit)" +} + +function project_version_with_commit() { + echo "$(project_version)-$(git_commit)" +} + +# generate less specific versions, eg. given 1.2.3 will print 1.2 and 1 +# (note: the argument itself is not printed, append it explicitly if needed) +function gen_versions() { + local version=$1 + while [[ $version = *.* ]]; do + version=${version%.*} + echo $version + done +} + +function tag_and_push() { + local source="$1" + shift + local target="$1" + shift + + echo "Tagging and pushing $target..." + docker tag "${source}" "${target}" + docker push "${target}" +} diff --git a/bin/publish_container_images b/bin/publish_container_images new file mode 100755 index 00000000..633626ae --- /dev/null +++ b/bin/publish_container_images @@ -0,0 +1,151 @@ +#!/bin/bash + +set -e + +# Navigate to the bin directory (where this script lives) to ensure we can run this script +# from anywhere. +cd "$(dirname "${0}")" + +. ./build_utils + +function print_help() { + echo "Internal Release Usage: ${0} --internal" + echo "External Release Usage: ${0} --edge" + echo "Promote Usage: ${0} --promote --source --target " + echo " --internal: publish images to registry.tld" + echo " --edge: publish docker images to docker hub" + echo " --source : specify version number of local image" + echo " --target : specify version number of remote image" +} + +# Fail if no arguments are given. +if [[ $# -lt 1 ]]; then + print_help + exit 1 +fi + +PUBLISH_INTERNAL=false +PUBLISH_EDGE=false +PROMOTE=false +PULL_SOURCE_IMAGES=false + +while [[ $# -gt 0 ]]; do + case "${1}" in + --internal) + PUBLISH_INTERNAL=true + ;; + --pull) + PULL_SOURCE_IMAGES=true + ;; + --edge) + PUBLISH_EDGE=true + ;; + --promote) + PROMOTE=true + ;; + --source) + SOURCE_ARG="${2}" + shift + ;; + --target) + TARGET_ARG="${2}" + shift + ;; + --help) + print_help + exit 1 + ;; + *) + echo "Unknown option: ${1}" + print_help + exit 1 + ;; + esac + shift +done + +readonly REGISTRY="cyberark" +readonly LOCAL_REGISTRY="registry.tld" +# Version derived from CHANGLEOG and automated release library +VERSION_WITH_COMMIT="$(project_version_with_commit)" +readonly VERSION_WITH_COMMIT +readonly IMAGES=( + "conjur-cli" +) + +if [[ ${PUBLISH_INTERNAL} = true ]]; then + echo "Publishing built images internally to registry.tld." + SOURCE_TAG=${VERSION_WITH_COMMIT} + REMOTE_TAG=${VERSION_WITH_COMMIT} + + echo "SOURCE_TAG=${SOURCE_TAG}, REMOTE_TAG=${REMOTE_TAG}" + for IMAGE_NAME in "${IMAGES[@]}"; do + tag_and_push "${IMAGE_NAME}:${SOURCE_TAG}" "${LOCAL_REGISTRY}/${IMAGE_NAME}:${REMOTE_TAG}" + done +fi + +if [[ ${PUBLISH_EDGE} = true ]]; then + echo "Performing edge release." + SOURCE_TAG=${VERSION_WITH_COMMIT} + + if [[ ${PULL_SOURCE_IMAGES} = true ]]; then + echo "Pulling source images from local registry" + for IMAGE_NAME in "${IMAGES[@]}"; do + docker pull "${LOCAL_REGISTRY}/${IMAGE_NAME}:${SOURCE_TAG}" + done + fi + + for IMAGE_NAME in "${IMAGES[@]}"; do + tag_and_push "${IMAGE_NAME}:${SOURCE_TAG}" "${REGISTRY}/${IMAGE_NAME}:edge" + done +fi + +if [[ ${PROMOTE} = true ]]; then + if [[ -z ${SOURCE_ARG:-} || -z ${TARGET_ARG:-} ]]; then + echo "When promoting, --source and --target flags are required." + print_help + exit 1 + fi + + # Update vars to utilize build_utils + SOURCE_TAG=${SOURCE_ARG} + REMOTE_TAG=${TARGET_ARG} + + echo "Promoting image to ${REMOTE_TAG}" + readonly TAGS=( + "${REMOTE_TAG}" + "latest" + ) + + if [[ ${PULL_SOURCE_IMAGES} = true ]]; then + echo "Pulling source images from local registry" + for IMAGE_NAME in "${IMAGES[@]}"; do + docker pull "${LOCAL_REGISTRY}/${IMAGE_NAME}:${SOURCE_TAG}" + done + fi + + for IMAGE_NAME in "${IMAGES[@]}"; do + for tag in "${TAGS[@]}" $(gen_versions "${REMOTE_TAG}"); do + tag_and_push "${LOCAL_REGISTRY}/${IMAGE_NAME}:${SOURCE_TAG}" "${REGISTRY}/${IMAGE_NAME}:${tag}" + done + done + + # # Publish only latest to Redhat Registries + # echo "Tagging and pushing ${REDHAT_IMAGE}" + # docker tag "${LOCAL_REGISTRY}/${REDHAT_LOCAL_IMAGE}:${SOURCE_TAG}" "${REDHAT_IMAGE}:${REMOTE_TAG}" + + # # Publish RedHat image to RedHat Registry + # if docker login scan.connect.redhat.com -u unused -p "${REDHAT_API_KEY}"; then + # # you can't push the same tag twice to redhat registry, so ignore errors + # if ! docker push "${REDHAT_IMAGE}:${REMOTE_TAG}"; then + # echo 'RedHat push FAILED! (maybe the image was pushed already?)' + # exit 0 + # fi + + # # scan image with preflight tool + # scan_redhat_image "${REDHAT_IMAGE}:${REMOTE_TAG}" "${REDHAT_CERT_PID}" + # else + # echo 'Failed to log in to scan.connect.redhat.com' + # exit 1 + # fi +fi diff --git a/ci/docker-compose.yml b/ci/docker-compose.yml index bcd715bc..73fac63d 100644 --- a/ci/docker-compose.yml +++ b/ci/docker-compose.yml @@ -29,23 +29,21 @@ services: - conjur restart: on-failure - # TODO: integration tests should be carried out against release asset binary (not the one created by make install)! - cli: - image: golang:1.19 + test: + image: alpine environment: - OKTA_CLIENT_ID=$OKTA_CLIENT_ID - OKTA_CLIENT_SECRET=$OKTA_CLIENT_SECRET - OKTA_PROVIDER_URI=$OKTA_PROVIDER_URI - OKTA_USERNAME=$OKTA_USERNAME - OKTA_PASSWORD=$OKTA_PASSWORD - command: bash -c "cd ${PWD}/..; make install; sleep infinity" working_dir: ${PWD}/.. restart: on-failure ports: - 8080 volumes: - ${PWD}/..:${PWD}/.. - - go-modules:/go/pkg/mod # Put modules cache into a separate volume + keycloak: image: jboss/keycloak:4.3.0.Final environment: @@ -72,5 +70,3 @@ services: - ./keycloak:/scripts - ./keycloak/standalone.xml:/opt/jboss/keycloak/standalone/configuration/standalone.xml -volumes: - go-modules: diff --git a/ci/test_integration b/ci/test_integration index 3cf25fd2..621abb3b 100755 --- a/ci/test_integration +++ b/ci/test_integration @@ -1,17 +1,16 @@ #!/usr/bin/env bash set -eox pipefail -source "./shared.sh" - # Navigate to the ci directory (where this script lives) to ensure we can run this script # from anywhere. cd "$(dirname "$0")" -services=(pg conjur proxy cli keycloak) +source "./shared.sh" + +services=(pg conjur proxy keycloak) # set the COMPOSE_PROJECT_NAME for the tests you'll be running COMPOSE_PROJECT_NAME="$(openssl rand -hex 3)" - export COMPOSE_PROJECT_NAME cleanup_and_dump_logs() { @@ -47,7 +46,9 @@ function main() { configure_oidc_providers echo 'Finished setting up Keycloak' - docker-compose exec -T cli make integration + docker-compose run -T \ + -e PATH_TO_CONJUR_BINARY="${PWD}/../dist/goreleaser/conjur-cli-go_linux_amd64_v1/conjur" \ + test "${PWD}/../dist/goreleaser/integration_linux_amd64_v1/integration" -test.v -test.parallel=1 -test.count=1 } main diff --git a/cmd/integration/oidc_integration_test.go b/cmd/integration/oidc_integration_test.go index 8e4f8c16..946b3b93 100644 --- a/cmd/integration/oidc_integration_test.go +++ b/cmd/integration/oidc_integration_test.go @@ -234,8 +234,8 @@ func TestOidcIntegrationOkta(t *testing.T) { } func setupKeycloakAuthenticator(account string) { - loadPolicyFile(account, "../../ci/keycloak/policy.yml") - loadPolicyFile(account, "../../ci/keycloak/users.yml") + loadPolicyFile(account, "./ci/keycloak/policy.yml") + loadPolicyFile(account, "./ci/keycloak/users.yml") createSecret(account, "conjur/authn-oidc/keycloak/provider-uri", "https://keycloak:8443/auth/realms/master") createSecret(account, "conjur/authn-oidc/keycloak/client-id", "conjurClient") @@ -246,8 +246,8 @@ func setupKeycloakAuthenticator(account string) { // NOTE: Depends on Summon variables in CLI container func setupOktaAuthenticator(account string) { - loadPolicyFile(account, "../../ci/okta/policy.yml") - loadPolicyFile(account, "../../ci/okta/users.yml") + loadPolicyFile(account, "./ci/okta/policy.yml") + loadPolicyFile(account, "./ci/okta/users.yml") createSecret(account, "conjur/authn-oidc/okta-2/provider-uri", os.Getenv("OKTA_PROVIDER_URI")+"oauth2/default") createSecret(account, "conjur/authn-oidc/okta-2/client-id", os.Getenv("OKTA_CLIENT_ID")) diff --git a/cmd/integration/shared.go b/cmd/integration/shared.go index 47c7da44..c10dc919 100644 --- a/cmd/integration/shared.go +++ b/cmd/integration/shared.go @@ -16,7 +16,7 @@ import ( "github.com/stretchr/testify/assert" ) -const pathToBinary = "conjur" +var pathToBinary = os.Getenv("PATH_TO_CONJUR_BINARY") const insecureModeWarning = "Warning: Running the command with '--insecure' makes your system vulnerable to security attacks\n" + "If you prefer to communicate with the server securely you must reinitialize the client in secure mode.\n"