From e6411ffe56020af9db816cb625a0a9a6ad73cb04 Mon Sep 17 00:00:00 2001 From: Benjamin Bengfort Date: Sun, 27 Jun 2021 15:52:17 -0400 Subject: [PATCH] GitHub Actions CI/CD Adds GitHub actions for continuous integration and testing and uses the goreleaser GitHub action for continuous deployment. --- .github/workflows/build.yaml | 69 ++++++++++++++++ .github/workflows/release.yaml | 37 +++++++++ .goreleaser.yml | 140 +++++++++++++++++++++++++++++++++ README.md | 7 ++ pkg/config/config_test.go | 6 ++ pkg/status_test.go | 2 +- pkg/version.go | 4 +- pkg/whisper_test.go | 60 ++++++++++++-- 8 files changed, 316 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/build.yaml create mode 100644 .github/workflows/release.yaml create mode 100644 .goreleaser.yml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..d047435 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,69 @@ +name: CI +on: + push: + branches: + - main + tags: + - 'v*' + pull_request: + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.16 + + - name: Install Staticcheck + run: go install honnef.co/go/tools/cmd/staticcheck@latest + + - name: Checkout Code + uses: actions/checkout@v2 + + - name: Lint Go Code + run: staticcheck ./... + + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.16 + + - name: Checkout Code + uses: actions/checkout@v2 + + - name: Install Dependencies + run: go version + + - name: Run Unit Tests + run: go test -v -coverprofile=coverage.txt -covermode=atomic --race ./... + + - name: Upload Coverage report to CodeCov + uses: codecov/codecov-action@v1.0.0 + with: + token: ${{secrets.CODECOV_TOKEN}} + file: ./coverage.txt + + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.16 + + - name: Checkout Code + uses: actions/checkout@v2 + + - name: Install Dependencies + run: go version + + - name: Build + run: go build ./cmd/... diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..97705db --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,37 @@ +name: CD +on: + push: + tags: + - 'v*' + +jobs: + release: + name: Release on GitHub + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.16 + + - name: Import GPG key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v3 + with: + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.PASSPHRASE }} + + - name: Create Release + uses: goreleaser/goreleaser-action@v2 + with: + distribution: goreleaser + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..9e20a71 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,140 @@ +project_name: whisper +dist: dist +builds: + # Define multiple builds as a yaml list, specify by a unique ID + - id: "cmd-whisper-build" + + # Path to project's (sub)directory containing Go code. + dir: . + + # Path to main.go file or main package. + main: ./cmd/whisper + + # Binary name (can be a path to wrap binary in a directory) + binary: whisper + + # Custom flags templates + flags: + - -v + + # Custom ldflags templates. + ldflags: -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} + + # Custom environment variables to be set during the build + env: + - CGO_ENABLED=0 + + # GOOS list to build for + # For more info refer to: https://golang.org/doc/install/source#environment + goos: + - linux + - darwin + + # GOARCH to build for. + # For more info refer to: https://golang.org/doc/install/source#environment + goarch: + - amd64 + - "386" + - arm64 + + # GOARM to build for when GOARCH is arm. + # For more info refer to: https://golang.org/doc/install/source#environment + goarm: + - "6" + + # List of combinations of GOOS + GOARCH + GOARM to ignore. + ignore: + - goos: darwin + goarch: 386 + + # Set the modified timestamp on the output binary, typically + # you would do this to ensure a build was reproducible. Pass + # empty string to skip modifying the output. + mod_timestamp: '{{ .CommitTimestamp }}' + +# Create .tar.gz and .zip archives +archives: + # tar.gz archive of the binaries + - id: "whisper-archive-tgz" + format: tar.gz + builds: + - "cmd-whisper-build" + name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + wrap_in_directory: true + files: + - LICENSE + - README.md + + # zip archive of the binaries + - id: "whisper-archive-zip" + format: zip + builds: + - "cmd-whisper-build" + name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + wrap_in_directory: true + files: + - LICENSE + - README.md + +# Used to validate if downloaded files are correct +checksum: + name_template: '{{ .ProjectName }}_{{ .Version }}_checksums.txt' + algorithm: sha256 + +# Publish the release on GitHub +release: + # Repo in which the release will be created. + # Default is extracted from the origin remote URL or empty if its private hosted. + # Valid options are either github, gitlab or gitea + github: + owner: rotationalio + name: whisper + + # You can change the name of the release. + name_template: '{{.Tag}}' + + # If set to auto, will mark the release as not ready for production + # in case there is an indicator for this in the tag e.g. v1.0.0-rc1 + # If set to true, will mark the release as not ready for production. + prerelease: auto + + # Header for the release body. + header: | + The whisper service is an internal helper tool used at Rotational Labs to share + secrets and secret files securely. We've made the code open source and are happy to + have general contributions that enhance the project, and have made these releases + freely available with no warranty to anyone who would like to use them. + + ## Changes + + [TODO: describe changes] + + # Footer for the release body. + footer: | + ### About + + Please note that this service is an internal-only tool and has been constructed as + such. Rotational Labs makes no guarantees or warranties about the security of this + software project and provides all compiled binaries as is for general use. Use at + your own risk! + + # If set to true, will not auto-publish the release. + disable: false + +changelog: + # Set it to true if you wish to skip the changelog generation. + skip: false + + filters: + # Commit messages matching the regexp listed here will be removed from the changelog + exclude: + - (?i)typo + - (?i)^f$ + +source: + enabled: true + format: 'zip' + +signs: + - artifacts: checksum + args: ["--batch", "-u", "{{ .Env.GPG_FINGERPRINT }}", "--output", "${signature}", "--detach-sign", "${artifact}"] diff --git a/README.md b/README.md index 2a2822f..cfa60d6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ # Whisper +[![Go Reference](https://pkg.go.dev/badge/github.com/rotationalio/whisper.svg)](https://pkg.go.dev/github.com/rotationalio/whisper) +[![Go Report Card](https://goreportcard.com/badge/github.com/rotationalio/whisper)](https://goreportcard.com/report/github.com/rotationalio/whisper) +![GitHub Actions CI](https://github.com/rotationalio/whisper/actions/workflows/build.yaml/badge.svg?branch=main) +![GitHub Actions CD](https://github.com/rotationalio/whisper/actions/workflows/release.yaml/badge.svg) +[![codecov](https://codecov.io/gh/bbengfort/whisper/branch/main/graph/badge.svg?token=fntqI5yFeY)](https://codecov.io/gh/bbengfort/whisper) + + **There are many secrets management utilities, this one is ours … shhh** \ No newline at end of file diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 431b84f..964e0ed 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -4,6 +4,7 @@ import ( "os" "testing" + "github.com/gin-gonic/gin" "github.com/rotationalio/whisper/pkg/config" "github.com/rs/zerolog" "github.com/stretchr/testify/require" @@ -40,7 +41,12 @@ func TestConfig(t *testing.T) { // Test configuration set from the environment require.Equal(t, false, conf.Maintenance) + require.Equal(t, gin.ReleaseMode, conf.Mode) require.Equal(t, testEnv["WHISPER_BIND_ADDR"], conf.BindAddr) + require.Equal(t, false, conf.UseTLS) + require.Equal(t, testEnv["WHISPER_DOMAIN"], conf.Domain) + require.Equal(t, testEnv["WHISPER_SECRET_KEY"], conf.SecretKey) + require.Equal(t, testEnv["WHISPER_DATABASE_URL"], conf.DatabaseURL) require.Equal(t, zerolog.DebugLevel, conf.GetLogLevel()) require.Equal(t, true, conf.ConsoleLog) } diff --git a/pkg/status_test.go b/pkg/status_test.go index 62ae3a5..354e8fb 100644 --- a/pkg/status_test.go +++ b/pkg/status_test.go @@ -11,7 +11,7 @@ import ( func (s *WhisperTestSuite) TestStatus() { w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/v1/status", nil) + req, _ := http.NewRequest("GET", "/v0/status", nil) s.router.ServeHTTP(w, req) result := w.Result() diff --git a/pkg/version.go b/pkg/version.go index ef77a3a..5e0d16e 100644 --- a/pkg/version.go +++ b/pkg/version.go @@ -9,8 +9,8 @@ import ( // Version component constants for the current build. const ( - VersionMajor = 1 - VersionMinor = 0 + VersionMajor = 0 + VersionMinor = 1 VersionPatch = 0 VersionReleaseLevel = "" VersionReleaseNumber = 0 diff --git a/pkg/whisper_test.go b/pkg/whisper_test.go index 3de628d..2e247f1 100644 --- a/pkg/whisper_test.go +++ b/pkg/whisper_test.go @@ -26,16 +26,18 @@ var testEnv = map[string]string{ // WhisperTestSuite mocks the database and gin/http requests for testing endpoints. type WhisperTestSuite struct { suite.Suite - api *Server - conf config.Config - router http.Handler + api *Server + conf config.Config + router http.Handler + prevEnv map[string]string } func (s *WhisperTestSuite) SetupSuite() { + // Store the previous environment to restore test suite + s.prevEnv = curEnv() + // Update the test environment - for key, val := range testEnv { - os.Setenv(key, val) - } + setEnv() // Create test configuration for mocked database and server var err error @@ -53,6 +55,17 @@ func (s *WhisperTestSuite) SetupSuite() { s.api.SetHealth(true) } +func (s *WhisperTestSuite) TearDownSuite() { + // Restore the previous environment + for key, val := range s.prevEnv { + if val != "" { + os.Setenv(key, val) + } else { + os.Unsetenv(key) + } + } +} + func TestWhisper(t *testing.T) { suite.Run(t, new(WhisperTestSuite)) } @@ -60,3 +73,38 @@ func TestWhisper(t *testing.T) { func (s *WhisperTestSuite) TestGinMode() { s.Equal(gin.TestMode, gin.Mode()) } + +// Returns the current environment for the specified keys, or if no keys are specified +// then returns the current environment for all keys in testEnv. +func curEnv(keys ...string) map[string]string { + env := make(map[string]string) + if len(keys) > 0 { + for _, envvar := range keys { + if val, ok := os.LookupEnv(envvar); ok { + env[envvar] = val + } + } + } else { + for key := range testEnv { + env[key] = os.Getenv(key) + } + } + + return env +} + +// Sets the environment variable from the testEnv, if no keys are specified, then sets +// all environment variables from the test env. +func setEnv(keys ...string) { + if len(keys) > 0 { + for _, key := range keys { + if val, ok := testEnv[key]; ok { + os.Setenv(key, val) + } + } + } else { + for key, val := range testEnv { + os.Setenv(key, val) + } + } +}