Refactor install api with service

pull/8330/head
devdinu 5 years ago
parent 929c64261b
commit 2b515a08b6

1
.gitignore vendored

@ -7,3 +7,4 @@
_dist/
bin/
vendor/
*.swp

@ -4,10 +4,9 @@ import (
"fmt"
"net/http"
"helm.sh/helm/v3/pkg/http/api/install"
"helm.sh/helm/v3/pkg/http/api"
"helm.sh/helm/v3/pkg/http/api/list"
"helm.sh/helm/v3/pkg/http/api/ping"
"helm.sh/helm/v3/pkg/http/api/upgrade"
"helm.sh/helm/v3/pkg/servercontext"
)
@ -18,10 +17,13 @@ func main() {
func startServer(appconfig *servercontext.Application) {
router := http.NewServeMux()
//TODO: use gorilla mux and add middleware to write content type and other headers
cfg := servercontext.App().Config
service := api.NewService(cfg)
router.Handle("/ping", ping.Handler())
router.Handle("/list", list.Handler())
router.Handle("/install", install.Handler())
router.Handle("/upgrade", upgrade.Handler())
router.Handle("/install", api.Install(service))
router.Handle("/upgrade", api.Upgrade(service))
err := http.ListenAndServe(fmt.Sprintf(":%d", 8080), router)
if err != nil {

@ -45,6 +45,7 @@ require (
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.1.0
github.com/xenolf/lego v0.3.2-0.20160613233155-a9d8cec0e656 // indirect
go.uber.org/zap v1.10.0
golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 // indirect
golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934 // indirect

@ -506,8 +506,11 @@ go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=

@ -0,0 +1,47 @@
package api
import (
"encoding/json"
"net/http"
"helm.sh/helm/v3/pkg/http/api/logger"
)
type InstallRequest struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Chart string `json:"chart"`
Values map[string]interface{} `json:"values"`
}
type InstallResponse struct {
Error string `json:"error,omitempty"`
Status string
}
// RODO: we could use interface as well if everything's in same package
func Install(svc Service) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req InstallRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
logger.Errorf("[Install] error decoding request: %v", err)
w.WriteHeader(http.StatusBadRequest)
return
}
defer r.Body.Close()
var response InstallResponse
res, err := svc.Install(r.Context(), req.Chart, req.Values)
if err != nil {
response.Error = err.Error()
logger.Errorf("[Install] error while installing chart: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
response.Status = res.status
if err := json.NewEncoder(w).Encode(response); err != nil {
logger.Errorf("[Install] error writing response %v", err)
return
}
})
}

@ -1,14 +0,0 @@
package install
type InstallRequest struct {
RequestID string
ReleaseName string
ReleaseNamespace string
ChartPath string
Values string
}
type InstallResponse struct {
Status bool
ReleaseStatus string
}

@ -1,85 +0,0 @@
package install
import (
"encoding/json"
"fmt"
"net/http"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/cli/values"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/servercontext"
)
func Handler() http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "application/json")
defer req.Body.Close()
var request InstallRequest
decoder := json.NewDecoder(req.Body)
decoder.UseNumber()
if err := decoder.Decode(&request); err != nil {
fmt.Printf("error in request: %v", err)
return
}
request.RequestID = req.Header.Get("Request-Id")
request.ReleaseName = req.Header.Get("Release-Name")
request.ReleaseNamespace = req.Header.Get("Release-Namespace")
request.ChartPath = req.Header.Get("Chart-Path")
request.Values = req.Header.Get("Values")
valueOpts := &values.Options{}
valueOpts.Values = append(valueOpts.Values, request.Values)
status, releaseStatus, err := InstallChart(request.ReleaseName, request.ReleaseNamespace, request.ChartPath, valueOpts)
if err != nil {
fmt.Printf("error in request: %v", err)
return
}
response := InstallResponse{Status: status, ReleaseStatus: releaseStatus}
payload, err := json.Marshal(response)
if err != nil {
fmt.Printf("error parsing response %v", err)
return
}
res.Write(payload)
})
}
func InstallChart(releaseName, releaseNamespace, chartPath string, valueOpts *values.Options) (bool, string, error) {
install := action.NewInstall(servercontext.App().ActionConfig)
install.ReleaseName = releaseName
install.Namespace = releaseNamespace
vals, err := valueOpts.MergeValues(getter.All(servercontext.App().Config))
if err != nil {
return false, "", err
}
cp, err := install.ChartPathOptions.LocateChart(chartPath, servercontext.App().Config)
if err != nil {
fmt.Printf("error in locating chart: %v", err)
return false, "", err
}
var requestedChart *chart.Chart
if requestedChart, err = loader.Load(cp); err != nil {
fmt.Printf("error in loading chart: %v", err)
return false, "", err
}
release, err := install.Run(requestedChart, vals)
if err != nil {
fmt.Printf("error in installing chart: %v", err)
return false, "", err
}
return true, release.Info.Status.String(), nil
}

@ -0,0 +1,40 @@
package logger
import (
"go.uber.org/zap"
)
var log *zap.SugaredLogger
func Debugf(fmt string, args ...interface{}) {
log.Debugf(fmt, args...)
}
func Fatalf(fmt string, args ...interface{}) {
log.Fatalf(fmt, args...)
}
func Errorf(fmt string, args ...interface{}) {
log.Errorf(fmt, args...)
}
func Infof(fmt string, args ...interface{}) {
log.Infof(fmt, args...)
}
func Setup(level string) {
//TODO: map from env config
var zlog *zap.Logger
switch level {
case "debug":
zlog, _ = zap.NewDevelopment()
case "none":
zlog = zap.NewNop()
default:
zlog, _ = zap.NewProduction()
}
// This condition is to avoid race conditions in test cases
if log == nil {
log = zlog.Sugar()
}
}

@ -0,0 +1,71 @@
package api
import (
"context"
"fmt"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/cli/values"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/servercontext"
)
type Service struct {
config *cli.EnvSettings
install *action.Install
}
type chartValues map[string]interface{}
type installResult struct {
status string
}
func (s Service) getValues(vals chartValues) (chartValues, error) {
valueOpts := &values.Options{}
//valueOpts.Values = append(valueOpts.Values, vals)
return valueOpts.MergeValues(getter.All(servercontext.App().Config))
}
func (s Service) Install(ctx context.Context, chartName string, values chartValues) (installResult, error) {
var result installResult
chart, err := s.loadChart(chartName)
if err != nil {
return result, err
}
vals, err := s.getValues(values)
if err != nil {
return result, fmt.Errorf("error merging values: %v", err)
}
release, err := s.install.Run(chart, vals)
if err != nil {
return result, fmt.Errorf("error in installing chart: %v", err)
}
if release.Info != nil {
result.status = release.Info.Status.String()
}
return result, nil
}
func (s Service) loadChart(chartName string) (*chart.Chart, error) {
cp, err := s.install.ChartPathOptions.LocateChart(chartName, s.config)
if err != nil {
return nil, fmt.Errorf("error in locating chart: %v", err)
}
var requestedChart *chart.Chart
if requestedChart, err = loader.Load(cp); err != nil {
return nil, fmt.Errorf("error loading chart: %v", err)
}
return requestedChart, nil
}
func NewService(cfg *cli.EnvSettings) Service {
return Service{
config: cfg,
//TODO: not sure why this's needed, but we can refactor later,could be passed as param
install: action.NewInstall(servercontext.App().ActionConfig),
}
}

@ -0,0 +1,124 @@
package api
import (
"context"
"encoding/json"
"fmt"
"net/http"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/cli/values"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/servercontext"
"helm.sh/helm/v3/pkg/storage/driver"
)
type UpgradeRequest struct {
RequestID string
ReleaseName string
ReleaseNamespace string
ChartPath string
}
type UpgradeResponse struct {
Status bool
ReleaseStatus string
}
type UpgradeHandler struct {
service Service
}
func Upgrade(service Service) http.Handler {
return UpgradeHandler{service: service}
}
func (h UpgradeHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "application/json")
defer req.Body.Close()
var request UpgradeRequest
decoder := json.NewDecoder(req.Body)
decoder.UseNumber()
if err := decoder.Decode(&request); err != nil {
fmt.Printf("error in request: %v", err)
return
}
request.RequestID = req.Header.Get("Request-Id")
request.ReleaseName = req.Header.Get("Release-Name")
request.ReleaseNamespace = req.Header.Get("Release-Namespace")
request.ChartPath = req.Header.Get("Chart-Path")
valueOpts := &values.Options{}
status, releaseStatus, err := h.UpgradeRelease(request.ReleaseName, request.ReleaseNamespace, request.ChartPath, valueOpts)
if err != nil {
fmt.Printf("error in request: %v", err)
return
}
response := UpgradeResponse{Status: status, ReleaseStatus: releaseStatus}
payload, err := json.Marshal(response)
if err != nil {
fmt.Printf("error parsing response %v", err)
return
}
res.Write(payload)
}
func (h UpgradeHandler) UpgradeRelease(releaseName, releaseNamespace, chartPath string, valueOpts *values.Options) (bool, string, error) {
upgrade := action.NewUpgrade(servercontext.App().ActionConfig)
upgrade.Namespace = releaseNamespace
vals, err := valueOpts.MergeValues(getter.All(servercontext.App().Config))
if err != nil {
return false, "", err
}
chartPath, err = upgrade.ChartPathOptions.LocateChart(chartPath, servercontext.App().Config)
if err != nil {
return false, "", err
}
if upgrade.Install {
history := action.NewHistory(servercontext.App().ActionConfig)
history.Max = 1
if _, err := history.Run(releaseName); err == driver.ErrReleaseNotFound {
fmt.Printf("Release %q does not exist. Installing it now.\n", releaseName)
//TODO: yet to accomodate namespace and releasename, just refactoring
release, err := h.service.Install(context.TODO(), chartPath, vals)
if err != nil {
fmt.Printf("error in request: %v", err)
return false, "", err
}
return true, release.status, nil
}
}
ch, err := loader.Load(chartPath)
if err != nil {
fmt.Printf("error in loading: %v", err)
return false, "", err
}
if req := ch.Metadata.Dependencies; req != nil {
if err := action.CheckDependencies(ch, req); err != nil {
fmt.Printf("error in dependencies: %v", err)
return false, "", err
}
}
if ch.Metadata.Deprecated {
fmt.Printf("WARNING: This chart is deprecated")
}
release, err := upgrade.Run(releaseName, ch, vals)
if err != nil {
fmt.Printf("error in installing chart: %v", err)
return false, "", err
}
return true, release.Info.Status.String(), nil
}

@ -1,13 +0,0 @@
package upgrade
type UpgradeRequest struct {
RequestID string
ReleaseName string
ReleaseNamespace string
ChartPath string
}
type UpgradeResponse struct {
Status bool
ReleaseStatus string
}

@ -1,105 +0,0 @@
package upgrade
import (
"encoding/json"
"fmt"
"net/http"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/cli/values"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/http/api/install"
"helm.sh/helm/v3/pkg/servercontext"
"helm.sh/helm/v3/pkg/storage/driver"
)
func Handler() http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "application/json")
defer req.Body.Close()
var request UpgradeRequest
decoder := json.NewDecoder(req.Body)
decoder.UseNumber()
if err := decoder.Decode(&request); err != nil {
fmt.Printf("error in request: %v", err)
return
}
request.RequestID = req.Header.Get("Request-Id")
request.ReleaseName = req.Header.Get("Release-Name")
request.ReleaseNamespace = req.Header.Get("Release-Namespace")
request.ChartPath = req.Header.Get("Chart-Path")
valueOpts := &values.Options{}
status, releaseStatus, err := UpgradeRelease(request.ReleaseName, request.ReleaseNamespace, request.ChartPath, valueOpts)
if err != nil {
fmt.Printf("error in request: %v", err)
return
}
response := UpgradeResponse{Status: status, ReleaseStatus: releaseStatus}
payload, err := json.Marshal(response)
if err != nil {
fmt.Printf("error parsing response %v", err)
return
}
res.Write(payload)
})
}
func UpgradeRelease(releaseName, releaseNamespace, chartPath string, valueOpts *values.Options) (bool, string, error) {
upgrade := action.NewUpgrade(servercontext.App().ActionConfig)
upgrade.Namespace = releaseNamespace
vals, err := valueOpts.MergeValues(getter.All(servercontext.App().Config))
if err != nil {
return false, "", err
}
chartPath, err = upgrade.ChartPathOptions.LocateChart(chartPath, servercontext.App().Config)
if err != nil {
return false, "", err
}
if upgrade.Install {
history := action.NewHistory(servercontext.App().ActionConfig)
history.Max = 1
if _, err := history.Run(releaseName); err == driver.ErrReleaseNotFound {
fmt.Printf("Release %q does not exist. Installing it now.\n", releaseName)
status, releaseStatus, err := install.InstallChart(releaseName, releaseNamespace, chartPath, valueOpts)
if err != nil {
fmt.Printf("error in request: %v", err)
return false, "", err
}
return status, releaseStatus, nil
}
}
ch, err := loader.Load(chartPath)
if err != nil {
fmt.Printf("error in loading: %v", err)
return false, "", err
}
if req := ch.Metadata.Dependencies; req != nil {
if err := action.CheckDependencies(ch, req); err != nil {
fmt.Printf("error in dependencies: %v", err)
return false, "", err
}
}
if ch.Metadata.Deprecated {
fmt.Printf("WARNING: This chart is deprecated")
}
release, err := upgrade.Run(releaseName, ch, vals)
if err != nil {
fmt.Printf("error in installing chart: %v", err)
return false, "", err
}
return true, release.Info.Status.String(), nil
}

@ -9,6 +9,7 @@ import (
"helm.sh/helm/v3/pkg/cli"
)
//TODO: rename package
var app Application
type Application struct {

Loading…
Cancel
Save