From b46d711fce43cfde99340c1f547c9c28e5e42a17 Mon Sep 17 00:00:00 2001 From: joe miller Date: Thu, 21 May 2020 08:41:28 -0700 Subject: [PATCH] migrate CI to github actions --- .github/workflows/main.yaml | 137 +++++++++++++++++++++++++++++++ .gitignore | 1 + .goreleaser.yml | 4 +- Makefile | 7 +- README.md | 45 +++++++--- azure-pipelines.yml | 110 ------------------------- cmd/integration_test.go | 11 ++- cmd/list.go | 6 -- pkg/store/keychain_test.go | 1 + pkg/store/pass_test.go | 4 +- pkg/store/secret_service_test.go | 1 + pkg/store/store_test.go | 1 + pkg/store/wincred_test.go | 1 + vagrant/README.md | 7 +- vagrant/windows/Vagrantfile | 7 +- 15 files changed, 200 insertions(+), 143 deletions(-) create mode 100644 .github/workflows/main.yaml delete mode 100644 azure-pipelines.yml diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..a618615 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,137 @@ +name: main +on: [push, pull_request] + +# TODO: implement support for [skip ci], https://timheuer.com/blog/skipping-ci-github-actions-workflows/ +# TODO: bonus: can we achiever apple codesigning in CI and remove the local script step? +# TODO: update CI/CD section in readme to remove azure refs +# TODO: switch readme CI badge to github actions +# TODO: document autotag git branch+tags stuff in autotag README + +jobs: + lint: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + if: github.event_name == 'push' && !contains(toJson(github.event.commits), '[ci skip]') && !contains(toJson(github.event.commits), '[skip ci]') + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + - uses: actions/cache@v1 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: install golangci-lint + run: | + mkdir -p "$HOME/bin" + curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b "$HOME/bin" v1.26.0 + echo "::add-path::$HOME/bin" + shell: bash # force windows to use git-bash for access to curl + + - name: install goreleaser + # only need to lint goreleaser on one platform: + if: startsWith(runner.os, 'Linux') + run: curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | sudo sh -s -- -b /usr/local/bin + + - name: make lint + env: + CI: "true" + run: make lint + shell: bash + + test: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + if: github.event_name == 'push' && !contains(toJson(github.event.commits), '[ci skip]') && !contains(toJson(github.event.commits), '[skip ci]') + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + - uses: actions/cache@v1 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: install go deps + run: make deps + + - name: install pass (linux) + if: startsWith(runner.os, 'Linux') + run: | + sudo apt-get -qy update + sudo apt-get -qy install pass + + - name: install pass (macos) + if: startsWith(runner.os, 'macOS') + run: | + brew install pass + + - name: make test + env: + CI: "true" + run: make test + + release-test: + needs: [lint, test] + # don't waste time running a goreleaser test build on master since we will run a full release: + if: github.ref != 'refs/heads/master' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + - uses: actions/cache@v1 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: install go deps + run: make deps + - run: make snapshot + + release: + needs: [lint, test] + # only create a release on master builds: + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + - uses: actions/cache@v1 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Unshallow + run: | + # fetch all tags and history so that goreleaser can generate a proper changelog + # and autotag can calculate the next version tag: + git fetch --tags --unshallow --prune + + if [ $(git rev-parse --abbrev-ref HEAD) != "master" ]; then + # ensure a local 'master' branch exists for autotag to work correctly: + git branch --track master origin/master + fi + - name: install go deps + run: make deps + + - name: install autotag + run: | + curl -sL https://git.io/autotag-install | sudo sh -s -- -b /usr/local/bin + + - name: run autotag to increment version + run: | + autotag + + - name: build and push release artifacts + env: + GITHUB_TOKEN: ${{ secrets.BREW_GITHUB_TOKEN }} + # GPG_KEY contents must be base64 encoded: + GPG_KEY: ${{ secrets.GPG_KEY }} + run: | + make release diff --git a/.gitignore b/.gitignore index d79a534..592aaa5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ vault-token-helper.exe .DS_Store .envrc vault-token-helper.signing-key.gpg +.Attic diff --git a/.goreleaser.yml b/.goreleaser.yml index bed7928..aa2bc2f 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -101,14 +101,14 @@ nfpms: - rpm overrides: rpm: - name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Arch }}" + file_name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Arch }}" replacements: amd64: x86_64 386: i686 arm: armhfp arm64: aarch64 deb: - name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + file_name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" replacements: 386: i386 arm: armel diff --git a/Makefile b/Makefile index 2f93776..bc9e5d6 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,12 @@ deps: @go get lint: - @golangci-lint run -v + @golangci-lint run -v --timeout=3m + @if command -v goreleaser >/dev/null; then \ + goreleaser check; \ + else \ + echo "goreleaser not installed, skiping goreleaser linting"; \ + fi test: @go test -coverprofile=cover.out -v ./... diff --git a/README.md b/README.md index 0dadcc6..4ea2515 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ vault-token-helper ================== -[![Build Status](https://dev.azure.com/joeym0501/vault-token-helper/_apis/build/status/joemiller.vault-token-helper?branchName=master)](https://dev.azure.com/joeym0501/vault-token-helper/_build/latest?definitionId=1&branchName=master) +![main](https://github.com/joemiller/vault-token-helper/workflows/main/badge.svg) A @hashicorp Vault [token helper](https://www.vaultproject.io/docs/commands/token-helper.html) with -support for native secret storage backends on macOS, Linux, and Windows. +support for native secret storage on macOS, Linux, and Windows. Features -------- @@ -17,7 +17,30 @@ Supported backends: * macOS Keychain * Linux (DBus Secret Service compatible backends, eg: Gnome Keyring) * Windows (WinCred) -* [pass](https://www.passwordstore.org/) +* [pass](https://www.passwordstore.org/) (GPG) + +Quickstart (macOS) +------------------ + +Install: + + brew install joemiller/taps/vault-token-helper + +Configure Vault to use the token helper. This will create the `~/.vault` config file: + + vault-token-helper enable + +Authenticate to a Vault instance to encrypt and store a new token locally, for example +with the Okta auth backend: + + export VAULT_ADDR=https://vault:8200 + vault login -method=okta username=joe@dom.tld + +List stored tokens: + + vault-token-helper list -e + +Keep reading for further details and installation methods. Install ------- @@ -56,9 +79,10 @@ Clone this repo and compile for the current architecture: make build ``` -Binaries for all supported platforms are built using the [dockercore/golang-cross](https://github.com/docker/golang-cross) -image. This is the same image used by the docker cli project. The image makes it possible to -cross-compile and link to platform-specific libraries such as the OSX SDK on macOS: +Binaries for all supported platforms are built using the +[dockercore/golang-cross](https://github.com/docker/golang-cross) image. This is the same image used +by the docker cli project for cross-compiling and linking with platform-specific libraries such +as macOS' Keychain and Windows' WinCred. ```sh make snapshot @@ -122,7 +146,7 @@ A fully annotated example config file is available in [./vault-token-helper.anno Set `VAULT_ADDR` to the URL of your Vault instance and run `vault` commands like normal. For example, to login and store a token on a Vault instance with the Okta auth plugin enabled: -```sh +```console export VAULT_ADDR=https://vault:8200 vault login -method=okta username=joe@dom.tld ``` @@ -181,10 +205,10 @@ The most complete way to run all tests would be to run `make test` under each pl ### CI/CD -Azure DevOps Pipelines is used for CI and CD because it provides support for macos, windows, -and linux. +[Github Actions](https://github.com/joemiller/vault-token-helper/actions) is used for CI/CD. -Tests are run on pull requests and releases are generated on successful master branch builds. +Tests are run on pull requests and versioned releases are generated on all successful master branch +builds. ### Release Management @@ -234,5 +258,4 @@ TODO * ci/cd: * [x] `sign` checksum.txt and assets in goreleaser.yaml GPG key * [ ] apple `codesign` the macos binaries - * [ ] figure out how to cache go modules in azure pipelines, using this task maybe - https://github.com/microsoft/azure-pipelines-artifact-caching-tasks * [ ] linux tests, figure out how to test dbus secret-service in headless CI. probably need a stub to connect to Dbus and provide the 'prompt' service diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 86403f1..0000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,110 +0,0 @@ -trigger: # run on all commits to master branch - branches: - include: - - master - -pr: # run pipeline on PRs - branches: - include: - - '*' - -resources: - containers: - - container: linux - image: golang:1.14 - # XXX: hack to install sudo inside the docker container - https://github.com/Microsoft/azure-pipelines-agent/issues/2043 - options: "--name ci-container -v /usr/bin/docker:/tmp/docker:ro" - -stages: - - - stage: test - jobs: - - job: linux - pool: - vmImage: 'ubuntu-16.04' # this is the only pool image that can run linux containers - https://docs.microsoft.com/en-us/azure/devops/pipelines/process/container-phases?view=azure-devops&tabs=yaml - container: linux - steps: - - script: | - # XXX: hack to install sudo inside the docker container - https://github.com/Microsoft/azure-pipelines-agent/issues/2043 - /tmp/docker exec -t -u 0 ci-container \ - sh -c "apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confold" -y install sudo" - displayName: Set up sudo - - script: go get - displayName: Install go deps - - script: | - set -e - sudo apt-get -qy update - sudo apt-get -qy install pass - displayName: Install package deps - - script: make test - env: - CI: "true" - displayName: make test - - - job: macos - pool: - vmImage: 'macOS-10.14' - steps: - - script: | - brew install pass - displayName: Install package deps - - script: go get - displayName: Install go deps - - script: make test - env: - CI: "true" - displayName: make test - - - job: windows - pool: - vmImage: 'windows-2019' - steps: - - script: go get - displayName: Install go deps - - pwsh: make test - env: - CI: "true" - displayName: make test - - - stage: release_test - jobs: - - job: release_test - pool: - vmImage: 'ubuntu-16.04' # this is the only pool image that can run linux containers - https://docs.microsoft.com/en-us/azure/devops/pipelines/process/container-phases?view=azure-devops&tabs=yaml - steps: - - script: make snapshot - displayName: make snapshot - - - stage: release - jobs: - - job: release - # only generate releases on master branches. - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) - pool: - vmImage: 'ubuntu-16.04' # this is the only pool image that can run linux containers - https://docs.microsoft.com/en-us/azure/devops/pipelines/process/container-phases?view=azure-devops&tabs=yaml - steps: - - task: DownloadSecureFile@1 - name: gpg_signing_key - inputs: - secureFile: 'vault-token-helper.signing-key.gpg' - - script: | - set -e - # XXX: not sure if this is the best approach here. Without this, autotag will fail - # with 'error getting head commit: object does not exist'. It should be ok for now, - # since we only cut releases from master. - git checkout master - git reset --hard $(Build.SourceVersion) - - curl -s https://api.github.com/repos/pantheon-systems/autotag/releases/latest | \ - grep browser_download | \ - grep -i linux | \ - cut -d '"' -f 4 | \ - xargs curl -o ~/autotag -L \ - && chmod 755 ~/autotag - ~/autotag - - export GPG_KEY=`cat $(gpg_signing_key.secureFilePath) | base64` - make release - env: - GITHUB_TOKEN: $(GITHUB_TOKEN) - displayName: make release \ No newline at end of file diff --git a/cmd/integration_test.go b/cmd/integration_test.go index 0f2d44c..1706975 100644 --- a/cmd/integration_test.go +++ b/cmd/integration_test.go @@ -7,7 +7,6 @@ import ( "strings" "testing" - "github.com/davecgh/go-spew/spew" "github.com/joemiller/vault-token-helper/cmd" "github.com/stretchr/testify/assert" ) @@ -80,6 +79,9 @@ func TestGetCmd_Match(t *testing.T) { stdin := "" env := []string{"VAULT_ADDR=https://foo.bar:8200"} stdout, stderr, err := execCmd(env, stdin, "get") + assert.Nil(t, err) // vault-token-helper should exit 0 if no token is stored for the $VAULT_ADDR + assert.Equal(t, "", stdout) + assert.Equal(t, "", stderr) stdin = "token-foo" _, _, err = execCmd(env, stdin, "store") @@ -88,6 +90,9 @@ func TestGetCmd_Match(t *testing.T) { stdout, stderr, err = execCmd(env, "", "get") assert.Nil(t, err) - spew.Dump(stdout) - spew.Dump(stderr) + // spew.Dump(stdout) + // spew.Dump(stderr) + assert.Nil(t, err) // vault-token-helper should exit 0 if no token is stored for the $VAULT_ADDR + assert.Equal(t, "token-foo", stdout) + assert.Equal(t, "", stderr) } diff --git a/cmd/list.go b/cmd/list.go index dcf7b1e..ac77fe5 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -50,12 +50,6 @@ var listCmd = &cobra.Command{ }, } -type lookupResult struct { - err error - addr string - secret *vault.Secret -} - func extendedList() error { wg := &sync.WaitGroup{} diff --git a/pkg/store/keychain_test.go b/pkg/store/keychain_test.go index 3d2313c..6f73a62 100644 --- a/pkg/store/keychain_test.go +++ b/pkg/store/keychain_test.go @@ -39,6 +39,7 @@ func TestKeychainStore(t *testing.T) { // GetAll tokens tokens, err = st.List() + assert.Nil(t, err) assert.NotEmpty(t, tokens) // Get a token by addr. Mixed case addr should be normalized for a successful lookup diff --git a/pkg/store/pass_test.go b/pkg/store/pass_test.go index 3714cae..188fa5a 100644 --- a/pkg/store/pass_test.go +++ b/pkg/store/pass_test.go @@ -35,7 +35,8 @@ func setup(t *testing.T) (string, func(t *testing.T)) { // Create a temporary GPG dir gnupghome := filepath.Join(tmpdir, ".gnupg") - os.Mkdir(gnupghome, os.FileMode(int(0700))) + err = os.Mkdir(gnupghome, os.FileMode(int(0700))) + require.Nil(t, err) os.Setenv("GNUPGHOME", gnupghome) os.Unsetenv("GPG_AGENT_INFO") os.Unsetenv("GPG_TTY") @@ -85,6 +86,7 @@ func TestPassStore(t *testing.T) { // GetAll tokens tokens, err := st.List() + assert.Nil(t, err) assert.NotEmpty(t, tokens) // Get a token by addr. Mixed case addr should be normalized for a successful lookup diff --git a/pkg/store/secret_service_test.go b/pkg/store/secret_service_test.go index 4602774..7922463 100644 --- a/pkg/store/secret_service_test.go +++ b/pkg/store/secret_service_test.go @@ -47,6 +47,7 @@ func TestSecretServiceStore(t *testing.T) { // GetAll tokens tokens, err = st.List() + assert.Nil(t, err) assert.NotEmpty(t, tokens) // Get a token by addr. Mixed case addr should be normalized for a successful lookup diff --git a/pkg/store/store_test.go b/pkg/store/store_test.go index 542a77b..6a65588 100644 --- a/pkg/store/store_test.go +++ b/pkg/store/store_test.go @@ -36,6 +36,7 @@ func TestStore(t *testing.T) { // GetAll tokens tokens, err = st.List() + assert.Nil(t, err) assert.NotEmpty(t, tokens) // Get a token by addr. Mixed case addr should be normalized for a successful lookup diff --git a/pkg/store/wincred_test.go b/pkg/store/wincred_test.go index 6c720fc..59e6629 100644 --- a/pkg/store/wincred_test.go +++ b/pkg/store/wincred_test.go @@ -39,6 +39,7 @@ func TestWincredStore(t *testing.T) { // GetAll tokens tokens, err = st.List() + assert.Nil(t, err) assert.NotEmpty(t, tokens) // Get a token by addr. Mixed case addr should be normalized for a successful lookup diff --git a/vagrant/README.md b/vagrant/README.md index 9a80561..dfd8cfb 100644 --- a/vagrant/README.md +++ b/vagrant/README.md @@ -58,9 +58,4 @@ vagrant up A GUI will open up. Login and open cmd or powershell. -The root of the project will be mounted to `C:\src` - -```sh -cd C:\src -go test -v . -``` \ No newline at end of file +The root of the project will be mounted to `C:\vault-token-helper` diff --git a/vagrant/windows/Vagrantfile b/vagrant/windows/Vagrantfile index 97c61fd..f6a71fa 100644 --- a/vagrant/windows/Vagrantfile +++ b/vagrant/windows/Vagrantfile @@ -18,10 +18,11 @@ SHELL config.vm.provision "shell", inline: "choco install -y git" config.vm.provision "shell", inline: "choco install -y golang" + config.vm.provision "shell", inline: "choco install -y make" config.vm.provision "shell", inline: "choco install -y vault" - # mount the project into c:\src - config.vm.synced_folder "../..", "/src" + # mount the project into c:\vault-token-helper + config.vm.synced_folder "../..", "/vault-token-helper" # TODO: remove below when done using local fork - config.vm.synced_folder "../../../keyring", "/keyring" + # config.vm.synced_folder "../../../keyring", "/keyring" end \ No newline at end of file