Skip to content

Commit

Permalink
wip: save config api for upgrade service
Browse files Browse the repository at this point in the history
  • Loading branch information
Craig O'Donnell committed Jun 13, 2024
1 parent bb1c47c commit f86b6a3
Show file tree
Hide file tree
Showing 7 changed files with 596 additions and 24 deletions.
26 changes: 2 additions & 24 deletions pkg/handlers/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ func updateAppConfig(updateApp *apptypes.App, sequence int64, configGroups []kot
return updateAppConfigResponse, err
}

requiredItems, requiredItemsTitles := getMissingRequiredConfig(configGroups)
requiredItems, requiredItemsTitles := kotsadmconfig.GetMissingRequiredConfig(configGroups)

// not having all the required items is only a failure for the version that the user intended to edit
if len(requiredItems) > 0 && isPrimaryVersion {
Expand All @@ -624,7 +624,7 @@ func updateAppConfig(updateApp *apptypes.App, sequence int64, configGroups []kot
// so we don't need the complex logic in kots, we can just write
if kotsKinds.ConfigValues != nil {
values := kotsKinds.ConfigValues.Spec.Values
kotsKinds.ConfigValues.Spec.Values = updateAppConfigValues(values, configGroups)
kotsKinds.ConfigValues.Spec.Values = kotsadmconfig.UpdateAppConfigValues(values, configGroups)

configValuesSpec, err := kotsKinds.Marshal("kots.io", "v1beta1", "ConfigValues")
if err != nil {
Expand Down Expand Up @@ -761,28 +761,6 @@ func updateAppConfig(updateApp *apptypes.App, sequence int64, configGroups []kot
return updateAppConfigResponse, nil
}

func getMissingRequiredConfig(configGroups []kotsv1beta1.ConfigGroup) ([]string, []string) {
requiredItems := make([]string, 0, 0)
requiredItemsTitles := make([]string, 0, 0)
for _, group := range configGroups {
if group.When == "false" {
continue
}
for _, item := range group.Items {
if kotsadmconfig.IsRequiredItem(item) && kotsadmconfig.IsUnsetItem(item) {
requiredItems = append(requiredItems, item.Name)
if item.Title != "" {
requiredItemsTitles = append(requiredItemsTitles, item.Title)
} else {
requiredItemsTitles = append(requiredItemsTitles, item.Name)
}
}
}
}

return requiredItems, requiredItemsTitles
}

func updateAppConfigValues(values map[string]kotsv1beta1.ConfigValue, configGroups []kotsv1beta1.ConfigGroup) map[string]kotsv1beta1.ConfigValue {
for _, group := range configGroups {
for _, item := range group.Items {
Expand Down
75 changes: 75 additions & 0 deletions pkg/kotsadmconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@ package kotsadmconfig

import (
"context"
"encoding/base64"
"fmt"
"os"
"strconv"

"github.com/pkg/errors"
kotsconfig "github.com/replicatedhq/kots/pkg/config"
"github.com/replicatedhq/kots/pkg/crypto"
"github.com/replicatedhq/kots/pkg/k8sutil"
"github.com/replicatedhq/kots/pkg/kotsutil"
"github.com/replicatedhq/kots/pkg/logger"
registrytypes "github.com/replicatedhq/kots/pkg/registry/types"
"github.com/replicatedhq/kots/pkg/template"
"github.com/replicatedhq/kots/pkg/util"
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
"github.com/replicatedhq/kotskinds/multitype"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand Down Expand Up @@ -91,6 +96,76 @@ func NeedsConfiguration(appSlug string, sequence int64, isAirgap bool, kotsKinds
return false, nil
}

func GetMissingRequiredConfig(configGroups []kotsv1beta1.ConfigGroup) ([]string, []string) {
requiredItems := make([]string, 0, 0)
requiredItemsTitles := make([]string, 0, 0)
for _, group := range configGroups {
if group.When == "false" {
continue
}
for _, item := range group.Items {
if IsRequiredItem(item) && IsUnsetItem(item) {
requiredItems = append(requiredItems, item.Name)
if item.Title != "" {
requiredItemsTitles = append(requiredItemsTitles, item.Title)
} else {
requiredItemsTitles = append(requiredItemsTitles, item.Name)
}
}
}
}

return requiredItems, requiredItemsTitles
}

func UpdateAppConfigValues(values map[string]kotsv1beta1.ConfigValue, configGroups []kotsv1beta1.ConfigGroup) map[string]kotsv1beta1.ConfigValue {
for _, group := range configGroups {
for _, item := range group.Items {
if item.Type == "file" {
v := values[item.Name]
v.Filename = item.Filename
values[item.Name] = v
}
if item.Value.Type == multitype.Bool {
updatedValue := item.Value.BoolVal
v := values[item.Name]
v.Value = strconv.FormatBool(updatedValue)
values[item.Name] = v
} else if item.Value.Type == multitype.String {
updatedValue := item.Value.String()
if item.Type == "password" {
// encrypt using the key
// if the decryption succeeds, don't encrypt again
_, err := util.DecryptConfigValue(updatedValue)
if err != nil {
updatedValue = base64.StdEncoding.EncodeToString(crypto.Encrypt([]byte(updatedValue)))
}
}

v := values[item.Name]
v.Value = updatedValue
values[item.Name] = v
}
for _, repeatableValues := range item.ValuesByGroup {
// clear out all variadic values for this group first
for name, value := range values {
if value.RepeatableItem == item.Name {
delete(values, name)
}
}
// add variadic groups back in declaratively
for itemName, valueItem := range repeatableValues {
v := values[itemName]
v.Value = fmt.Sprintf("%v", valueItem)
v.RepeatableItem = item.Name
values[itemName] = v
}
}
}
}
return values
}

func ReadConfigValuesFromInClusterSecret() (string, error) {
log := logger.NewCLILogger(os.Stdout)

Expand Down
140 changes: 140 additions & 0 deletions pkg/upgradeservice/handlers/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,28 @@ package handlers

import (
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/gorilla/mux"
"github.com/pkg/errors"
downstreamtypes "github.com/replicatedhq/kots/pkg/api/downstream/types"
apptypes "github.com/replicatedhq/kots/pkg/app/types"
"github.com/replicatedhq/kots/pkg/config"
kotsconfig "github.com/replicatedhq/kots/pkg/config"
"github.com/replicatedhq/kots/pkg/kotsadmconfig"
configtypes "github.com/replicatedhq/kots/pkg/kotsadmconfig/types"
configvalidation "github.com/replicatedhq/kots/pkg/kotsadmconfig/validation"
"github.com/replicatedhq/kots/pkg/kotsutil"
"github.com/replicatedhq/kots/pkg/logger"
registrytypes "github.com/replicatedhq/kots/pkg/registry/types"
"github.com/replicatedhq/kots/pkg/render"
rendertypes "github.com/replicatedhq/kots/pkg/render/types"
"github.com/replicatedhq/kots/pkg/template"
upgradepreflight "github.com/replicatedhq/kots/pkg/upgradeservice/preflight"
"github.com/replicatedhq/kots/pkg/util"
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
"github.com/replicatedhq/kotskinds/multitype"
Expand All @@ -38,6 +47,19 @@ type LiveAppConfigResponse struct {
ValidationErrors []configtypes.ConfigGroupValidationError `json:"validationErrors,omitempty"`
}

type SaveAppConfigRequest struct {
Sequence int64 `json:"sequence"`
CreateNewVersion bool `json:"createNewVersion"`
ConfigGroups []kotsv1beta1.ConfigGroup `json:"configGroups"`
}

type SaveAppConfigResponse struct {
Success bool `json:"success"`
Error string `json:"error,omitempty"`
RequiredItems []string `json:"requiredItems,omitempty"`
ValidationErrors []configtypes.ConfigGroupValidationError `json:"validationErrors,omitempty"`
}

func (h *Handler) CurrentAppConfig(w http.ResponseWriter, r *http.Request) {
currentAppConfigResponse := CurrentAppConfigResponse{
Success: false,
Expand Down Expand Up @@ -254,3 +276,121 @@ func configValuesFromConfigGroups(configGroups []kotsv1beta1.ConfigGroup) map[st

return configValues
}

func (h *Handler) SaveAppConfig(w http.ResponseWriter, r *http.Request) {
saveAppConfigResponse := SaveAppConfigResponse{
Success: false,
}

params := GetContextParams(r)
appSlug := mux.Vars(r)["appSlug"]

if params.AppSlug != appSlug {
saveAppConfigResponse.Error = "app slug does not match"
JSON(w, http.StatusForbidden, saveAppConfigResponse)
return
}

saveAppConfigRequest := SaveAppConfigRequest{}
if err := json.NewDecoder(r.Body).Decode(&saveAppConfigRequest); err != nil {
logger.Error(err)
saveAppConfigResponse.Error = "failed to decode request body"
JSON(w, http.StatusBadRequest, saveAppConfigResponse)
return
}

validationErrors, err := configvalidation.ValidateConfigSpec(kotsv1beta1.ConfigSpec{Groups: saveAppConfigRequest.ConfigGroups})
if err != nil {
saveAppConfigResponse.Error = "failed to validate config spec."
logger.Error(errors.Wrap(err, saveAppConfigResponse.Error))
JSON(w, http.StatusInternalServerError, saveAppConfigResponse)
return
}

if len(validationErrors) > 0 {
saveAppConfigResponse.Error = "invalid config values"
saveAppConfigResponse.ValidationErrors = validationErrors
logger.Errorf("%v, validation errors: %+v", saveAppConfigResponse.Error, validationErrors)
JSON(w, http.StatusBadRequest, saveAppConfigResponse)
return
}

requiredItems, requiredItemsTitles := kotsadmconfig.GetMissingRequiredConfig(saveAppConfigRequest.ConfigGroups)
if len(requiredItems) > 0 {
saveAppConfigResponse.RequiredItems = requiredItems
saveAppConfigResponse.Error = fmt.Sprintf("The following fields are required: %s", strings.Join(requiredItemsTitles, ", "))
logger.Errorf("%v, required items: %+v", saveAppConfigResponse.Error, requiredItems)
JSON(w, http.StatusBadRequest, saveAppConfigResponse)
return
}

localRegistry := registrytypes.RegistrySettings{
Hostname: params.RegistryEndpoint,
Username: params.RegistryUsername,
Password: params.RegistryPassword,
Namespace: params.RegistryNamespace,
IsReadOnly: params.RegistryIsReadOnly,
}

app := &apptypes.App{
ID: params.AppID,
Slug: params.AppSlug,
IsAirgap: params.AppIsAirgap,
IsGitOps: params.AppIsGitOps,
}

if err := saveAppConfig(app, params.BaseArchive, params.NextSequence, saveAppConfigRequest.ConfigGroups, localRegistry, params.PreflightResultsDir); err != nil {
saveAppConfigResponse.Error = "failed to save app config"
logger.Error(errors.Wrap(err, saveAppConfigResponse.Error))
JSON(w, http.StatusInternalServerError, saveAppConfigResponse)
return
}

saveAppConfigResponse.Success = true
JSON(w, http.StatusOK, saveAppConfigResponse)
}

func saveAppConfig(app *apptypes.App, archiveDir string, sequence int64, configGroups []kotsv1beta1.ConfigGroup, registrySettings registrytypes.RegistrySettings, preflightResultsDir string) error {
kotsKinds, err := kotsutil.LoadKotsKinds(archiveDir)
if err != nil {
return errors.Wrap(err, "failed to load kots kinds")
}

if kotsKinds.ConfigValues == nil {
return errors.New("config values not found")
}

values := kotsKinds.ConfigValues.Spec.Values
kotsKinds.ConfigValues.Spec.Values = kotsadmconfig.UpdateAppConfigValues(values, configGroups)

configValuesSpec, err := kotsKinds.Marshal("kots.io", "v1beta1", "ConfigValues")
if err != nil {
return errors.Wrap(err, "failed to marshal config values")
}

if err := os.WriteFile(filepath.Join(archiveDir, "upstream", "userdata", "config.yaml"), []byte(configValuesSpec), 0644); err != nil {
return errors.Wrap(err, "failed to write config values")
}

err = render.RenderDir(rendertypes.RenderDirOptions{
ArchiveDir: archiveDir,
App: app,
Downstreams: []downstreamtypes.Downstream{{Name: "this-cluster"}}, // TODO NOW: is this correct?
RegistrySettings: registrySettings,
Sequence: sequence,
})
if err != nil {
cause := errors.Cause(err)
if _, ok := cause.(util.ActionableError); ok {
return err
} else {
return errors.Wrap(err, "failed to render archive directory")
}
}

if err := upgradepreflight.Run(app, archiveDir, int64(sequence), registrySettings, false, preflightResultsDir); err != nil {
return errors.Wrap(err, "failed to run preflights")
}

return nil
}
1 change: 1 addition & 0 deletions pkg/upgradeservice/handlers/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ type UpgradeServiceHandler interface {

CurrentAppConfig(w http.ResponseWriter, r *http.Request)
LiveAppConfig(w http.ResponseWriter, r *http.Request)
SaveAppConfig(w http.ResponseWriter, r *http.Request)
}
Loading

0 comments on commit f86b6a3

Please sign in to comment.