Skip to content

Commit

Permalink
GitHub Actions CI/CD
Browse files Browse the repository at this point in the history
Adds GitHub actions for continuous integration and testing and uses the
goreleaser GitHub action for continuous deployment.
  • Loading branch information
bbengfort committed Jun 27, 2021
1 parent 8c71d89 commit e6411ff
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 9 deletions.
69 changes: 69 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -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/[email protected]
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/...
37 changes: 37 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -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 }}
140 changes: 140 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -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}"]
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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**
6 changes: 6 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions pkg/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
60 changes: 54 additions & 6 deletions pkg/whisper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -53,10 +55,56 @@ 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))
}

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)
}
}
}

0 comments on commit e6411ff

Please sign in to comment.