Skip to content

Commit

Permalink
feat(cli): add value config flags
Browse files Browse the repository at this point in the history
Signed-off-by: Jakob Steiner <[email protected]>
  • Loading branch information
kosmoz authored and christophenne committed Apr 15, 2024
1 parent ba0cd32 commit 9be6b1f
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 55 deletions.
66 changes: 23 additions & 43 deletions cmd/glasskube/cmd/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,22 @@ package cmd
import (
"fmt"
"os"
"strings"

"github.com/fatih/color"
"github.com/glasskube/glasskube/api/v1alpha1"
clientadapter "github.com/glasskube/glasskube/internal/adapter/goclient"
"github.com/glasskube/glasskube/internal/cliutils"
"github.com/glasskube/glasskube/internal/manifestvalues"
"github.com/glasskube/glasskube/internal/manifestvalues/cli"
"github.com/glasskube/glasskube/internal/manifestvalues/flags"
"github.com/glasskube/glasskube/pkg/client"
"github.com/glasskube/glasskube/pkg/manifest"
"github.com/spf13/cobra"
"k8s.io/client-go/kubernetes"
)

var configureCmdOptions = struct {
values []string
keepOld bool
}{
keepOld: true,
var configureCmdOptions = struct{ flags.ValuesOptions }{
ValuesOptions: flags.NewOptions(flags.WithKeepOldValuesFlag),
}

var configureCmd = &cobra.Command{
Expand All @@ -44,34 +41,29 @@ func runConfigure(cmd *cobra.Command, args []string) {
)
pkgName := args[0]
var pkg v1alpha1.Package
var pkgManifest *v1alpha1.PackageManifest

if err := pkgClient.Packages().Get(ctx, pkgName, &pkg); err != nil {
fmt.Fprintf(os.Stderr, "❌ error getting package: %v\n", err)
os.Exit(1)
} else if pkgManifest, err = manifest.GetInstalledManifestForPackage(ctx, pkg); err != nil {
fmt.Fprintf(os.Stderr, "❌ error getting installed manifest: %v\n", err)
os.Exit(1)
}

if configureCmdOptions.keepOld {
if len(configureCmdOptions.values) > 0 {
for name, value := range parseValuesFlag(configureCmdOptions.values) {
if pkg.Spec.Values == nil {
pkg.Spec.Values = make(map[string]v1alpha1.ValueConfiguration)
}
pkg.Spec.Values[name] = value
}
if configureCmdOptions.IsValuesSet() {
if values, err := configureCmdOptions.ParseValues(pkg.Spec.Values); err != nil {
fmt.Fprintf(os.Stderr, "❌ invalid values in command line flags: %v\n", err)
os.Exit(1)
} else {
if values, err := cli.Configure(*pkgManifest, pkg.Spec.Values); err != nil {
fmt.Fprintf(os.Stderr, "❌ error during configure: %v\n", err)
os.Exit(1)
} else {
pkg.Spec.Values = values
}
pkg.Spec.Values = values
}
} else {
pkg.Spec.Values = parseValuesFlag(configureCmdOptions.values)
if pkgManifest, err := manifest.GetInstalledManifestForPackage(ctx, pkg); err != nil {
fmt.Fprintf(os.Stderr, "❌ error getting installed manifest: %v\n", err)
os.Exit(1)
} else if values, err := cli.Configure(*pkgManifest, pkg.Spec.Values); err != nil {
fmt.Fprintf(os.Stderr, "❌ error during configure: %v\n", err)
os.Exit(1)
} else {
pkg.Spec.Values = values
}
}

fmt.Fprintln(os.Stderr, bold("Configuration:"))
Expand All @@ -84,6 +76,11 @@ func runConfigure(cmd *cobra.Command, args []string) {
cancel()
}

if err := pkgClient.Packages().Get(ctx, pkgName, &pkg); err != nil {
// Don't exit, we can still try to call update ...
fmt.Fprintf(os.Stderr, "⚠️ error fetching package: %v\n", err)
}

if err := pkgClient.Packages().Update(ctx, &pkg); err != nil {
fmt.Fprintf(os.Stderr, "❌ error updating package: %v\n", err)
os.Exit(1)
Expand All @@ -92,24 +89,7 @@ func runConfigure(cmd *cobra.Command, args []string) {
}
}

func parseValuesFlag(str []string) map[string]v1alpha1.ValueConfiguration {
result := make(map[string]v1alpha1.ValueConfiguration)
for _, s := range str {
split := strings.SplitN(s, "=", 2)
var key, value string
key = split[0]
if len(split) > 1 {
value = split[1]
}
result[key] = v1alpha1.ValueConfiguration{Value: &value}
}
return result
}

func init() {
configureCmd.Flags().StringArrayVar(&configureCmdOptions.values, "value", configureCmdOptions.values,
"set a value via flag (can be used multiple times)")
configureCmd.Flags().BoolVar(&configureCmdOptions.keepOld, "keep-old", configureCmdOptions.keepOld,
"set this to false erase any values not specified via --value")
configureCmdOptions.ValuesOptions.AddFlagsToCommand(configureCmd)
RootCmd.AddCommand(configureCmd)
}
41 changes: 29 additions & 12 deletions cmd/glasskube/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/glasskube/glasskube/internal/dependency"
"github.com/glasskube/glasskube/internal/manifestvalues"
"github.com/glasskube/glasskube/internal/manifestvalues/cli"
"github.com/glasskube/glasskube/internal/manifestvalues/flags"
"github.com/glasskube/glasskube/internal/repo"
"github.com/glasskube/glasskube/pkg/client"
"github.com/glasskube/glasskube/pkg/condition"
Expand All @@ -22,11 +23,14 @@ import (
)

var installCmdOptions = struct {
flags.ValuesOptions
Version string
EnableAutoUpdates bool
NoWait bool
Yes bool
}{}
}{
ValuesOptions: flags.NewOptions(),
}

var installCmd = &cobra.Command{
Use: "install [package-name]",
Expand Down Expand Up @@ -81,17 +85,19 @@ var installCmd = &cobra.Command{
os.Exit(1)
} else if len(validationResult.Requirements) > 0 {
installationPlan = append(installationPlan, validationResult.Requirements...)
} else if values, err := cli.Configure(manifest, nil); err != nil {
cancel()
} else if installCmdOptions.IsValuesSet() {
if values, err := installCmdOptions.ParseValues(nil); err != nil {
fmt.Fprintf(os.Stderr, "❌ invalid values in command line flags: %v\n", err)
os.Exit(1)
} else {
pkgBuilder.WithValues(values)
}
} else {
if len(values) > 0 {
fmt.Fprintln(os.Stderr, bold("Configuration:"))
printValueConfigurations(os.Stderr, values)
if _, err := valueResolver.Resolve(ctx, values); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Some values can not be resolved: %v\n", err)
}
if values, err := cli.Configure(manifest, nil); err != nil {
cancel()
} else {
pkgBuilder.WithValues(values)
}
pkgBuilder.WithValues(values)
}

if !installCmdOptions.EnableAutoUpdates && !installCmdOptions.Yes {
Expand All @@ -102,6 +108,8 @@ var installCmd = &cobra.Command{

pkgBuilder.WithAutoUpdates(installCmdOptions.EnableAutoUpdates)

pkg := pkgBuilder.Build()

fmt.Fprintln(os.Stderr, bold("Summary:"))
fmt.Fprintf(os.Stderr, " * The following packages will be installed in your cluster (%v):\n", config.CurrentContext)
for i, p := range installationPlan {
Expand All @@ -113,12 +121,20 @@ var installCmd = &cobra.Command{
fmt.Fprintln(os.Stderr, " * Automatic updates will be", bold("not enabled"))
}

if len(pkg.Spec.Values) > 0 {
fmt.Fprintln(os.Stderr, bold("Configuration:"))
printValueConfigurations(os.Stderr, pkg.Spec.Values)
if _, err := valueResolver.Resolve(ctx, pkg.Spec.Values); err != nil {
fmt.Fprintf(os.Stderr, "⚠️ Some values can not be resolved: %v\n", err)
}
}

if !installCmdOptions.Yes && !cliutils.YesNoPrompt("Continue?", true) {
cancel()
}

if installCmdOptions.NoWait {
if err := installer.Install(ctx, pkgBuilder.Build()); err != nil {
if err := installer.Install(ctx, pkg); err != nil {
fmt.Fprintf(os.Stderr, "An error occurred during installation:\n\n%v\n", err)
os.Exit(1)
}
Expand All @@ -127,7 +143,7 @@ var installCmd = &cobra.Command{
"💡 Run \"glasskube describe %v\" to get the current status",
packageName, packageName)
} else {
status, err := installer.InstallBlocking(ctx, pkgBuilder.Build())
status, err := installer.InstallBlocking(ctx, pkg)
if err != nil {
fmt.Fprintf(os.Stderr, "An error occurred during installation:\n\n%v\n", err)
os.Exit(1)
Expand Down Expand Up @@ -207,5 +223,6 @@ func init() {
installCmd.PersistentFlags().BoolVar(&installCmdOptions.NoWait, "no-wait", false, "perform non-blocking install")
installCmd.PersistentFlags().BoolVarP(&installCmdOptions.Yes, "yes", "y", false, "do not ask for any confirmation")
installCmd.MarkFlagsMutuallyExclusive("version", "enable-auto-updates")
installCmdOptions.ValuesOptions.AddFlagsToCommand(installCmd)
RootCmd.AddCommand(installCmd)
}
116 changes: 116 additions & 0 deletions internal/manifestvalues/flags/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package flags

import (
"fmt"
"maps"
"strings"

"github.com/glasskube/glasskube/api/v1alpha1"
"github.com/spf13/cobra"
)

type valuesOptionsConfigurer = func(opts *ValuesOptions)

type ValuesOptions struct {
Values []string
KeepOldValues bool
KeepOldValuesDefault *bool
}

func NewOptions(conf ...valuesOptionsConfigurer) ValuesOptions {
var opt ValuesOptions
for _, fn := range conf {
fn(&opt)
}
return opt
}

var WithKeepOldValuesFlag valuesOptionsConfigurer = func(opts *ValuesOptions) {
tmp := true
opts.KeepOldValuesDefault = &tmp
}

func (opts *ValuesOptions) IsValuesSet() bool {
return (opts.KeepOldValuesDefault != nil && opts.KeepOldValues != *opts.KeepOldValuesDefault) || len(opts.Values) > 0
}

func (opts *ValuesOptions) AddFlagsToCommand(cmd *cobra.Command) {
flags := cmd.Flags()
flags.StringArrayVar(&opts.Values, "value", opts.Values,
"set a value via flag (can be used multiple times).\n"+
"You can create values referencing data in other resources using the following syntax: "+
"$<ReferenceKind>$[specifier...].\n"+
"For example:\n"+
" * Reference a ConfigMap key: --value \"name=$ConfigMapRef$namespace,name,key\"\n"+
" * Reference a Secret key: --value \"name=$SecretRef$namespace,name,key\"\n"+
" * Reference another Package value: --value \"name=$PackageRef$name,value\"\n")
if opts.KeepOldValuesDefault != nil {
flags.BoolVar(&opts.KeepOldValues, "keep-old-values", *opts.KeepOldValuesDefault,
"set this to false in order to erase any values not specified via --value")
}
}

func (opts *ValuesOptions) ParseValues(
oldValues map[string]v1alpha1.ValueConfiguration,
) (map[string]v1alpha1.ValueConfiguration, error) {
newValues := make(map[string]v1alpha1.ValueConfiguration)
if opts.KeepOldValues {
maps.Copy(newValues, oldValues)
}
for _, s := range opts.Values {
split := strings.SplitN(s, "=", 2)
if len(split) != 2 {
return nil, fmt.Errorf("invalid value format: %v", s)
}
key, value := split[0], split[1]

var valueConfiguration v1alpha1.ValueConfiguration
if strings.HasPrefix(value, "$ConfigMapRef$") {
if source, err := parseObjectKeyValueSource(value, "$ConfigMapRef$"); err != nil {
return nil, fmt.Errorf("value %v is invalid: %v", key, err)
} else {
valueConfiguration.ValueFrom = &v1alpha1.ValueReference{ConfigMapRef: source}
}
} else if strings.HasPrefix(value, "$SecretRef$") {
if source, err := parseObjectKeyValueSource(value, "$SecretRef$"); err != nil {
return nil, fmt.Errorf("value %v is invalid: %v", key, err)
} else {
valueConfiguration.ValueFrom = &v1alpha1.ValueReference{SecretRef: source}
}
} else if strings.HasPrefix(value, "$PackageRef$") {
if source, err := parsePackageValueSource(value); err != nil {
return nil, fmt.Errorf("value %v is invalid: %v", key, err)
} else {
valueConfiguration.ValueFrom = &v1alpha1.ValueReference{PackageRef: source}
}
} else {
valueConfiguration.Value = &value
}
newValues[key] = valueConfiguration
}
return newValues, nil
}

func parseObjectKeyValueSource(value, prefix string) (*v1alpha1.ObjectKeyValueSource, error) {
if parts, err := parseSourceParts(value, prefix, 3); err != nil {
return nil, err
} else {
return &v1alpha1.ObjectKeyValueSource{Namespace: parts[0], Name: parts[1], Key: parts[2]}, nil
}
}

func parsePackageValueSource(value string) (*v1alpha1.PackageValueSource, error) {
if parts, err := parseSourceParts(value, "$PackageRef$", 2); err != nil {
return nil, err
} else {
return &v1alpha1.PackageValueSource{Name: parts[0], Value: parts[1]}, nil
}
}

func parseSourceParts(value, prefix string, n int) ([]string, error) {
if parts := strings.SplitN(strings.TrimPrefix(value, prefix), ",", n); len(parts) != n {
return nil, fmt.Errorf("%v requires %v parameters, got %v", prefix, n, len(parts))
} else {
return parts, nil
}
}
13 changes: 13 additions & 0 deletions internal/manifestvalues/flags/flags_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package flags

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestDependency(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "flags Suite")
}
Loading

0 comments on commit 9be6b1f

Please sign in to comment.