Skip to content

Commit

Permalink
Deployment Configuration (#5)
Browse files Browse the repository at this point in the history
Adds a Dockerfile and build process for Google Cloud Run deployment.
Edits the configuration for the current and correct production
environment.
  • Loading branch information
bbengfort authored Jul 14, 2021
1 parent 21d1c83 commit d873e55
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 35 deletions.
14 changes: 14 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# The .dockerignore file excludes files from the container build process.
#
# https://docs.docker.com/engine/reference/builder/#dockerignore-file

# Exclude locally vendored dependencies.
vendor/

# Exclude "build-time" ignore files.
.dockerignore
.gcloudignore

# Exclude git history and configuration.
.git
.gitignore
Empty file added .gcloudignore
Empty file.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@
# vendor/

# Environment variables
.env
.env

# Fixtures and data
fixtures/
42 changes: 42 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Use the offical golang image to create a binary.
# This is based on Debian and sets the GOPATH to /go.
# https://hub.docker.com/_/golang
FROM golang:1.16-buster as builder

# Create and change to the app directory.
WORKDIR /app

# Retrieve application dependencies.
# This allows the container build to reuse cached dependencies.
# Expecting to copy go.mod and if present go.sum.
COPY go.* ./
RUN go mod download

# Copy local code to the container image.
COPY . ./

# Build the binary.
RUN go build -v ./cmd/whisper

# Use the official Debian slim image for a lean production container.
# https://hub.docker.com/_/debian
FROM debian:buster-slim

LABEL maintainer="Rotational Labs <info@rotational.io>"
LABEL description="Whisper, a secret management utility"

RUN set -x && apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates && \
rm -rf /var/lib/apt/lists/*

# Copy the binary to the production image from the builder stage.
COPY --from=builder /app/whisper /app/whisper

# Production environment defaults
ENV WHISPER_MAINTENANCE=false
ENV WHISPER_MODE=release
ENV WHISPER_LOG_LEVEL=info
ENV WHISPER_CONSOLE_LOG=false

# Run the web service on container startup.
CMD ["/app/whisper", "serve"]
5 changes: 2 additions & 3 deletions cmd/whisper/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,8 @@ func serve(c *cli.Context) (err error) {
}

// Update from CLI flags
conf.BindAddr = c.String("addr")
if c.Bool("no-secure") {
conf.UseTLS = false
if addr := c.String("addr"); addr != "" {
conf.BindAddr = addr
}

// Create and run the whisper server
Expand Down
Empty file added fixtures/.gitkeep
Empty file.
39 changes: 34 additions & 5 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ settings.
package config

import (
"errors"
"fmt"
"os"
"strings"

"github.com/gin-gonic/gin"
"github.com/kelseyhightower/envconfig"
"github.com/rs/zerolog"
)
Expand All @@ -18,22 +21,37 @@ import (
type Config struct {
Maintenance bool `split_words:"true" default:"false"`
Mode string `split_words:"true" default:"debug"`
BindAddr string `split_words:"true" default:":8318"`
UseTLS bool `split_words:"true" default:"false"`
Domain string `split_words:"true" default:"localhost"`
SecretKey string `split_words:"true" required:"true"`
DatabaseURL string `split_words:"true" required:"true"`
BindAddr string `split_words:"true" required:"false"`
LogLevel LogLevelDecoder `split_words:"true" default:"info"`
ConsoleLog bool `split_words:"true" default:"false"`
Google GoogleConfig
processed bool
}

type GoogleConfig struct {
Credentials string `envconfig:"GOOGLE_APPLICATION_CREDENTIALS" required:"false"`
Project string `envconfig:"GOOGLE_PROJECT_NAME" required:"true"`
}

// New creates a new Config object, loading environment variables and defaults.
func New() (_ Config, err error) {
var conf Config
if err = envconfig.Process("whisper", &conf); err != nil {
return Config{}, err
}

// If the BindAddr is not set, try setting it from $PORT (Google Cloud Run)
if conf.BindAddr == "" {
if port := os.Getenv("PORT"); port != "" {
conf.BindAddr = ":" + port
}
}

// Extra validation of the configuration
if err = conf.Validate(); err != nil {
return Config{}, err
}

conf.processed = true
return conf, nil
}
Expand All @@ -46,6 +64,17 @@ func (c Config) IsZero() bool {
return !c.processed
}

func (c Config) Validate() error {
if c.BindAddr == "" {
return errors.New("must specify either $WHISPER_BIND_ADDR or $PORT")
}

if c.Mode != gin.ReleaseMode && c.Mode != gin.DebugMode && c.Mode != gin.TestMode {
return fmt.Errorf("%q is not a valid gin mode", c.Mode)
}
return nil
}

// LogLevelDecoder deserializes the log level from a config string.
type LogLevelDecoder zerolog.Level

Expand Down
30 changes: 13 additions & 17 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@ import (
)

var testEnv = map[string]string{
"WHISPER_MAINTENANCE": "false",
"WHISPER_MODE": "release",
"WHISPER_BIND_ADDR": ":443",
"WHISPER_USE_TLS": "false",
"WHISPER_DOMAIN": "localhost",
"WHISPER_SECRET_KEY": "theeaglefliesatmidnight",
"WHISPER_DATABASE_URL": "postgresql://localhost:5432/whisper",
"WHISPER_LOG_LEVEL": "debug",
"WHISPER_CONSOLE_LOG": "true",
"WHISPER_MAINTENANCE": "false",
"WHISPER_MODE": "release",
"WHISPER_BIND_ADDR": ":443",
"WHISPER_LOG_LEVEL": "debug",
"WHISPER_CONSOLE_LOG": "true",
"GOOGLE_APPLICATION_CREDENTIALS": "fixtures/whisper-sa.json",
"GOOGLE_PROJECT_NAME": "test-project",
}

func TestConfig(t *testing.T) {
Expand All @@ -43,17 +41,15 @@ func TestConfig(t *testing.T) {
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, testEnv["GOOGLE_APPLICATION_CREDENTIALS"], conf.Google.Credentials)
require.Equal(t, testEnv["GOOGLE_PROJECT_NAME"], conf.Google.Project)
require.Equal(t, true, conf.ConsoleLog)
}

func TestRequiredConfig(t *testing.T) {
// Set required environment variables and cleanup after
prevEnv := curEnv("WHISPER_DATABASE_URL", "WHISPER_SECRET_KEY")
prevEnv := curEnv("WHISPER_BIND_ADDR", "GOOGLE_PROJECT_NAME")
t.Cleanup(func() {
for key, val := range prevEnv {
if val != "" {
Expand All @@ -66,14 +62,14 @@ func TestRequiredConfig(t *testing.T) {

_, err := config.New()
require.Error(t, err)
setEnv("WHISPER_DATABASE_URL", "WHISPER_SECRET_KEY")
setEnv("WHISPER_BIND_ADDR", "GOOGLE_PROJECT_NAME")

conf, err := config.New()
require.NoError(t, err)

// Test required configuration
require.Equal(t, testEnv["WHISPER_DATABASE_URL"], conf.DatabaseURL)
require.Equal(t, testEnv["WHISPER_SECRET_KEY"], conf.SecretKey)
require.Equal(t, testEnv["WHISPER_BIND_ADDR"], conf.BindAddr)
require.Equal(t, testEnv["GOOGLE_PROJECT_NAME"], conf.Google.Project)
}

// Returns the current environment for the specified keys, or if no keys are specified
Expand Down
16 changes: 7 additions & 9 deletions pkg/whisper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ import (
)

var testEnv = map[string]string{
"WHISPER_MAINTENANCE": "false",
"WHISPER_MODE": gin.TestMode,
"WHISPER_BIND_ADDR": "127.0.0.1:8311",
"WHISPER_USE_TLS": "false",
"WHISPER_DOMAIN": "localhost",
"WHISPER_SECRET_KEY": "supersecretkey",
"WHISPER_DATABASE_URL": "file::memory:?cache=shared",
"WHISPER_LOG_LEVEL": "debug",
"WHISPER_CONSOLE_LOG": "false",
"WHISPER_MAINTENANCE": "false",
"WHISPER_MODE": gin.TestMode,
"WHISPER_BIND_ADDR": "127.0.0.1:8311",
"WHISPER_LOG_LEVEL": "debug",
"WHISPER_CONSOLE_LOG": "false",
"GOOGLE_APPLICATION_CREDENTIALS": "fixtures/test.json",
"GOOGLE_PROJECT_NAME": "test",
}

// WhisperTestSuite mocks the database and gin/http requests for testing endpoints.
Expand Down

0 comments on commit d873e55

Please sign in to comment.