-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
99aa936
commit c3eb715
Showing
14 changed files
with
673 additions
and
4 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package cmd | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"net/http" | ||
|
||
"github.com/skuid/go-middlewares/authn/google" | ||
"github.com/skuid/helm-value-store/server" | ||
"github.com/skuid/spec" | ||
"github.com/skuid/spec/lifecycle" | ||
"github.com/skuid/spec/middlewares" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/pflag" | ||
"github.com/spf13/viper" | ||
"go.uber.org/zap" | ||
"go.uber.org/zap/zapcore" | ||
) | ||
|
||
var level = zapcore.InfoLevel | ||
|
||
// serveCmd represents the serve command | ||
var serveCmd = &cobra.Command{ | ||
Use: "serve", | ||
Short: "A brief description of your command", | ||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
l, err := spec.NewStandardLevelLogger(level) | ||
if err != nil { | ||
return fmt.Errorf("Error initializing logger: %q", err) | ||
} | ||
zap.ReplaceGlobals(l) | ||
return nil | ||
}, | ||
Run: func(cmd *cobra.Command, args []string) { | ||
middlewareList := []middlewares.Middleware{middlewares.InstrumentRoute()} | ||
loggingClosures := []func(*http.Request) []zapcore.Field{} | ||
serverOpts := []server.ControllerOpt{} | ||
|
||
if viper.GetBool("auth-enabled") { | ||
authorizer := google.New(google.WithAuthorizedDomains(viper.GetString("email-domain"))) | ||
serverOpts = append(serverOpts, server.WithAuthorizers(authorizer)) | ||
middlewareList = append([]middlewares.Middleware{authorizer.Authorize()}, middlewareList...) | ||
loggingClosures = append(loggingClosures, authorizer.LoggingClosure) | ||
} | ||
|
||
apiController := server.NewApiController(releaseStore, serverOpts...) | ||
middlewareList = append(middlewareList, middlewares.Logging(loggingClosures...)) | ||
|
||
authMux := http.NewServeMux() | ||
authMux.HandleFunc("/apply", apiController.ApplyChart) | ||
|
||
mux := http.NewServeMux() | ||
mux.Handle("/", middlewares.Apply(authMux, middlewareList...)) | ||
|
||
go spec.MetricsServer(viper.GetInt("metrics-port")) | ||
|
||
hostPort := fmt.Sprintf(":%d", viper.GetInt("port")) | ||
httpServer := &http.Server{Addr: hostPort, Handler: mux} | ||
lifecycle.ShutdownOnTerm(httpServer) | ||
|
||
zap.L().Info("Starting helm-value-store server 🃏 ", zap.Int("port", viper.GetInt("port"))) | ||
if err := httpServer.ListenAndServe(); err != http.ErrServerClosed { | ||
zap.L().Fatal("Error listening", zap.Error(err)) | ||
} | ||
zap.L().Info("Server gracefully stopped") | ||
|
||
}, | ||
} | ||
|
||
func init() { | ||
RootCmd.AddCommand(serveCmd) | ||
|
||
// Hack to make level work | ||
set := flag.NewFlagSet("temp", flag.ExitOnError) | ||
set.Var(&level, "level", "Log level") | ||
levelPFlag := pflag.PFlagFromGoFlag(set.Lookup("level")) | ||
levelPFlag.Shorthand = "l" | ||
|
||
localFlagSet := serveCmd.Flags() | ||
localFlagSet.AddFlag(levelPFlag) | ||
localFlagSet.Int64P("timeout", "t", 300, "Time in seconds to timeout on installation/update of releases") | ||
localFlagSet.IntP("port", "p", 3000, "The port to listen on") | ||
localFlagSet.Int("metrics-port", 3001, "The port to listen on for metrics/health checks") | ||
localFlagSet.String("email-domain", "", "The email domain to filter on") | ||
localFlagSet.Bool("auth-enabled", true, "Enable authentication/authorization") | ||
|
||
viper.BindPFlags(localFlagSet) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package server | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/skuid/helm-value-store/store" | ||
"go.uber.org/zap" | ||
"go.uber.org/zap/zapcore" | ||
) | ||
|
||
type applyRequest struct { | ||
UUID string `json:"uuid"` | ||
} | ||
|
||
type applyResponse struct { | ||
Status string `json:"status"` | ||
Message string `json:"message"` | ||
} | ||
|
||
type upsertConfig struct { | ||
location string | ||
timeout int64 | ||
} | ||
|
||
func upsertRelease(r *store.Release, conf upsertConfig) error { | ||
_, err := r.Get() | ||
|
||
if err != nil && !strings.Contains(err.Error(), "not found") { | ||
return err | ||
} | ||
|
||
if err != nil && strings.Contains(err.Error(), "not found") { | ||
_, err = r.Install(conf.location, false, conf.timeout) | ||
} else if err == nil { | ||
_, err = r.Upgrade(conf.location, false, conf.timeout) | ||
} | ||
|
||
return err | ||
} | ||
|
||
// ApplyChart applies a chart to a tiller server | ||
func (c ApiController) ApplyChart(w http.ResponseWriter, r *http.Request) { | ||
var err error | ||
if r.Method != http.MethodPost { | ||
w.WriteHeader(http.StatusNotFound) | ||
return | ||
} | ||
|
||
// Fields for the autit log | ||
var auditFields []zapcore.Field | ||
for _, a := range c.authorizers { | ||
auditFields = append(auditFields, a.LoggingClosure(r)...) | ||
} | ||
|
||
defer func() { | ||
successful := err == nil | ||
auditFields = append( | ||
auditFields, | ||
zap.String("controller", "apply"), | ||
zap.Bool("successful", successful), | ||
) | ||
zap.L().Info("Audit Log", auditFields...) | ||
}() | ||
|
||
applyReq := &applyRequest{} | ||
|
||
if err = json.NewDecoder(r.Body).Decode(applyReq); err != nil { | ||
w.WriteHeader(http.StatusBadRequest) | ||
zap.L().Error("Error decoding request", zap.Error(err)) | ||
return | ||
} | ||
auditFields = append(auditFields, zap.String("uuid", applyReq.UUID)) | ||
|
||
release := &store.Release{} | ||
release, err = c.releaseStore.Get(r.Context(), applyReq.UUID) | ||
|
||
applyResp := &applyResponse{} | ||
|
||
if err != nil { | ||
zap.L().Error("Error getting release", zap.Error(err)) | ||
|
||
applyResp.Status = "error" | ||
applyResp.Message = "Error getting release" | ||
err = json.NewEncoder(w).Encode(applyResp) | ||
if err != nil { | ||
zap.L().Error("Error marshaling response", zap.Error(err)) | ||
} | ||
return | ||
} | ||
auditFields = append(auditFields, | ||
zap.String("chart", release.Chart), | ||
zap.String("release", release.Name), | ||
zap.String("version", release.Version), | ||
zap.String("namespace", release.Namespace), | ||
) | ||
|
||
var location string | ||
location, err = release.Download() | ||
|
||
if err != nil { | ||
zap.L().Error("Error downloading release", zap.Error(err)) | ||
|
||
w.WriteHeader(http.StatusInternalServerError) | ||
applyResp.Status = "error" | ||
applyResp.Message = "Error downloading release" | ||
err = json.NewEncoder(w).Encode(applyResp) | ||
|
||
if err != nil { | ||
zap.L().Error("Error marshaling response", zap.Error(err)) | ||
} | ||
return | ||
} | ||
|
||
err = upsertRelease(release, upsertConfig{ | ||
location: location, | ||
timeout: c.timeout, | ||
}) | ||
|
||
if err != nil { | ||
zap.L().Error("Error applying release", zap.Error(err)) | ||
|
||
w.WriteHeader(http.StatusInternalServerError) | ||
applyResp.Status = "error" | ||
applyResp.Message = "Error applying release" | ||
err = json.NewEncoder(w).Encode(applyResp) | ||
if err != nil { | ||
zap.L().Error("Error marshaling response", zap.Error(err)) | ||
} | ||
return | ||
} | ||
|
||
applyResp.Status = "success" | ||
applyResp.Message = fmt.Sprintf("Successfully installed %s", release.Name) | ||
err = json.NewEncoder(w).Encode(applyResp) | ||
if err != nil { | ||
zap.L().Error("Error marshaling response", zap.Error(err)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package server | ||
|
||
import ( | ||
"github.com/skuid/go-middlewares" | ||
"github.com/skuid/helm-value-store/store" | ||
) | ||
|
||
// ApiController stores metadata for the API | ||
type ApiController struct { | ||
releaseStore store.ReleaseStore | ||
authorizers []go_middlewares.Authorizer | ||
timeout int64 | ||
} | ||
|
||
// ControllerOpt is a func that modifies an ApiController | ||
type ControllerOpt func(*ApiController) | ||
|
||
// WithAutorizer sets the authorizer for an ApiController | ||
func WithAuthorizers(azs ...go_middlewares.Authorizer) ControllerOpt { | ||
return func(a *ApiController) { | ||
a.authorizers = azs | ||
} | ||
} | ||
|
||
// WithTimeout sets the timeout in seconds on an ApiController | ||
func WithTimeout(timeout int64) ControllerOpt { | ||
return func(a *ApiController) { | ||
a.timeout = timeout | ||
} | ||
} | ||
|
||
// NewApiController returns a new API controller with a default timeout of 300 seconds | ||
func NewApiController(s store.ReleaseStore, opts ...ControllerOpt) *ApiController { | ||
response := &ApiController{ | ||
releaseStore: s, | ||
timeout: 300, | ||
} | ||
for _, opt := range opts { | ||
opt(response) | ||
} | ||
|
||
return response | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
3 changes: 3 additions & 0 deletions
3
vendor/github.com/skuid/go-middlewares/authn/google/README.md
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.