Skip to content

Commit

Permalink
add lock tests and fix locks
Browse files Browse the repository at this point in the history
  • Loading branch information
tobikris committed Oct 5, 2022
1 parent 0a5df0f commit 261ac0e
Show file tree
Hide file tree
Showing 16 changed files with 625 additions and 428 deletions.
141 changes: 6 additions & 135 deletions cmd/terraform-backend.go
Original file line number Diff line number Diff line change
@@ -1,144 +1,15 @@
package main

import (
"fmt"
"io"
"net/http"

"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"

"github.com/nimbolus/terraform-backend/pkg/auth"
"github.com/nimbolus/terraform-backend/pkg/kms"
"github.com/nimbolus/terraform-backend/pkg/lock"
"github.com/nimbolus/terraform-backend/pkg/storage"
"github.com/nimbolus/terraform-backend/pkg/terraform"
"github.com/nimbolus/terraform-backend/pkg/server"
)

func httpResponse(w http.ResponseWriter, code int, body string) {
log.Tracef("response: %d %s", code, body)
w.WriteHeader(code)
fmt.Fprint(w, body)
}

func stateHandler(store storage.Storage, locker lock.Locker, kms kms.KMS) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
body, err := io.ReadAll(req.Body)
defer req.Body.Close()
if err != nil {
httpResponse(w, http.StatusInternalServerError, err.Error())
return
}

vars := mux.Vars(req)
state := &terraform.State{
ID: terraform.GetStateID(vars["project"], vars["name"]),
Project: vars["project"],
Name: vars["name"],
}

log.Infof("%s %s", req.Method, req.URL.Path)
log.Trace("request: %s %s: %s", req.Method, req.URL.Path, body)

if ok, err := auth.Authenticate(req, state); err != nil {
log.Warnf("failed process authentication for state id %s: %v", state.ID, err)
httpResponse(w, http.StatusForbidden, err.Error())
return
} else if !ok {
log.Warnf("failed to authenticate request for state id %s", state.ID)
httpResponse(w, http.StatusForbidden, "Permission denied")
return
}

switch req.Method {
case "LOCK":
log.Debugf("try to lock state with id %s", state.ID)
state.Lock = body

if ok, err := locker.Lock(state); err != nil {
log.Errorf("failed to lock state with id %s: %v", state.ID, err)
httpResponse(w, http.StatusInternalServerError, "")
} else if !ok {
log.Warnf("state with id %s is already locked by %s", state.ID, state.Lock)
httpResponse(w, http.StatusLocked, string(state.Lock))
} else {
log.Debugf("state with id %s was locked successfully", state.ID)
httpResponse(w, http.StatusOK, "")
}
return
case "UNLOCK":
log.Debugf("try to unlock state with id %s", state.ID)
state.Lock = body

if ok, err := locker.Unlock(state); err != nil {
log.Errorf("failed to unlock state with id %s: %v", state.ID, err)
httpResponse(w, http.StatusInternalServerError, "")
} else if !ok {
log.Warnf("failed to unlock state with id %s: %v", state.ID, err)
httpResponse(w, http.StatusBadRequest, string(state.Lock))
} else {
log.Debugf("state with id %s was unlocked successfully", state.ID)
httpResponse(w, http.StatusOK, "")
}
return
case http.MethodGet:
log.Debugf("get state with id %s", state.ID)
stateID := state.ID
state, err = store.GetState(state.ID)
if err != nil {
log.Warnf("failed to get state with id %s: %v", stateID, err)
httpResponse(w, http.StatusBadRequest, err.Error())
return
}

if kms != nil && len(state.Data) > 0 {
state.Data, err = kms.Decrypt(state.Data)
if err != nil {
log.Errorf("failed to decrypt state with id %s: %v", state.ID, err)
httpResponse(w, http.StatusInternalServerError, "")
return
}
}

httpResponse(w, http.StatusOK, string(state.Data))
return
case http.MethodPost:
log.Debugf("save state with id %s", state.ID)

state.Data, err = kms.Encrypt(body)
if err != nil {
log.Errorf("failed to encrypt state with id %s: %v", state.ID, err)
httpResponse(w, http.StatusInternalServerError, "")
return
}

err := store.SaveState(state)
if err != nil {
log.Warnf("failed to save state with id %s: %v", state.ID, err)
httpResponse(w, http.StatusBadRequest, err.Error())
return
}

httpResponse(w, http.StatusOK, "")
return
case http.MethodDelete:
log.Debugf("delete state with id %s", state.ID)
httpResponse(w, http.StatusNotImplemented, "Delete state is not implemented")
return
default:
log.Warnf("unknown method %s called", req.Method)
httpResponse(w, http.StatusNotImplemented, "Not implemented")
return
}
}
}

func healthHandler(w http.ResponseWriter, req *http.Request) {
log.Debugf("%s %s", req.Method, req.URL.Path)
httpResponse(w, http.StatusOK, "")
}

func main() {
viper.AutomaticEnv()
viper.SetDefault("log_level", "info")
Expand All @@ -150,19 +21,19 @@ func main() {
log.Infof("set log level to %s", level.String())
log.SetLevel(level)

store, err := storage.GetStorage()
store, err := server.GetStorage()
if err != nil {
log.Fatal(err.Error())
}
log.Infof("initialized %s storage backend", store.GetName())

locker, err := lock.GetLocker()
locker, err := server.GetLocker()
if err != nil {
log.Fatal(err.Error())
}
log.Infof("initialized %s lock backend", locker.GetName())

kms, err := kms.GetKMS()
kms, err := server.GetKMS()
if err != nil {
log.Fatal(err.Error())
}
Expand All @@ -174,8 +45,8 @@ func main() {
tlsCert := viper.GetString("tls_cert")

r := mux.NewRouter().StrictSlash(true)
r.HandleFunc("/state/{project}/{name}", stateHandler(store, locker, kms))
r.HandleFunc("/health", healthHandler)
r.HandleFunc("/state/{project}/{name}", server.StateHandler(store, locker, kms))
r.HandleFunc("/health", server.HealthHandler)

if tlsKey != "" && tlsCert != "" {
log.Printf("listening on %s with tls", addr)
Expand Down
11 changes: 4 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,28 @@ go 1.19

require (
github.com/coreos/go-oidc/v3 v3.4.0
github.com/go-redis/redis/v8 v8.11.5
github.com/go-redsync/redsync/v4 v4.6.0
github.com/gomodule/redigo v1.8.2
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/hashicorp/vault/api v1.7.2
github.com/minio/minio-go/v7 v7.0.37
github.com/sirupsen/logrus v1.9.0
github.com/spf13/viper v1.13.0
go.uber.org/multierr v1.6.0
)

require (
github.com/armon/go-metrics v0.4.1 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-test/deep v1.0.8 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.3.0 // indirect
Expand All @@ -46,9 +46,7 @@ require (
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.15.10 // indirect
github.com/klauspost/cpuid v1.3.1 // indirect
github.com/klauspost/cpuid/v2 v2.1.1 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
Expand All @@ -72,7 +70,6 @@ require (
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.4.0 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
go.uber.org/atomic v1.10.0 // indirect
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 // indirect
Expand Down
Loading

0 comments on commit 261ac0e

Please sign in to comment.