Skip to content

Commit

Permalink
Read config from multidoc/multiple YAMLs
Browse files Browse the repository at this point in the history
Signed-off-by: Kimmo Lehto <klehto@mirantis.com>
  • Loading branch information
kke committed Jan 15, 2025
1 parent 082a528 commit 1d181af
Show file tree
Hide file tree
Showing 16 changed files with 564 additions and 50 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,23 @@ jobs:
env:
LINUX_IMAGE: ${{ matrix.image }}
run: make smoke-basic-openssh

smoke-multidoc:
strategy:
matrix:
image:
- quay.io/k0sproject/bootloose-alpine3.18
name: Basic 1+1 smoke using multidoc yamls
needs: build
runs-on: ubuntu-20.04

steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/smoke-test-cache
- name: Run smoke tests
env:
LINUX_IMAGE: ${{ matrix.image }}
run: make smoke-multidoc

smoke-files:
strategy:
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ build-all: $(addprefix bin/,$(bins)) bin/checksums.md
clean:
rm -rf bin/ k0sctl

smoketests := smoke-basic smoke-basic-rootless smoke-files smoke-upgrade smoke-reset smoke-os-override smoke-init smoke-backup-restore smoke-dynamic smoke-basic-openssh smoke-dryrun smoke-downloadurl smoke-controller-swap smoke-reinstall
smoketests := smoke-basic smoke-basic-rootless smoke-files smoke-upgrade smoke-reset smoke-os-override smoke-init smoke-backup-restore smoke-dynamic smoke-basic-openssh smoke-dryrun smoke-downloadurl smoke-controller-swap smoke-reinstall smoke-multidoc
.PHONY: $(smoketests)
$(smoketests): k0sctl
$(MAKE) -C smoke-test $@
Expand Down
12 changes: 7 additions & 5 deletions action/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ type ApplyOptions struct {
KubeconfigUser string
// KubeconfigCluster is the cluster name to use in the kubeconfig
KubeconfigCluster string
// ConfigPath is the path to the configuration file (used for kubeconfig command tip on success)
ConfigPath string
// ConfigPaths is the list of paths to the configuration files (used for kubeconfig command tip on success)
ConfigPaths []string
}

type Apply struct {
Expand Down Expand Up @@ -158,9 +158,11 @@ func (a Apply) Run() error {
cmd.WriteString(executable)
cmd.WriteString(" kubeconfig")

if a.ConfigPath != "" && a.ConfigPath != "-" && a.ConfigPath != "k0sctl.yaml" {
cmd.WriteString(" --config ")
cmd.WriteString(a.ConfigPath)
if len(a.ConfigPaths) > 0 && (len(a.ConfigPaths) != 1 && a.ConfigPaths[0] != "-" && a.ConfigPaths[0] != "k0sctl.yaml") {
for _, path := range a.ConfigPaths {
cmd.WriteString(" --config ")
cmd.WriteString(path)
}
}

log.Info("Tip: To access the cluster you can now fetch the admin kubeconfig using:")
Expand Down
2 changes: 1 addition & 1 deletion cmd/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ var applyCommand = &cli.Command{
NoDrain: ctx.Bool("no-drain"),
DisableDowngradeCheck: ctx.Bool("disable-downgrade-check"),
RestoreFrom: ctx.String("restore-from"),
ConfigPath: ctx.String("config"),
ConfigPaths: ctx.StringSlice("config"),
}

applyAction := action.NewApply(applyOpts)
Expand Down
2 changes: 1 addition & 1 deletion cmd/config_edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var configEditCommand = &cli.Command{
Before: actions(initLogging, initConfig),
Action: func(ctx *cli.Context) error {
configEditAction := action.ConfigEdit{
Config: ctx.Context.Value(ctxConfigKey{}).(*v1beta1.Cluster),
Config: ctx.Context.Value(ctxConfigsKey{}).(*v1beta1.Cluster),
Stdout: ctx.App.Writer,
Stderr: ctx.App.ErrWriter,
Stdin: ctx.App.Reader,
Expand Down
8 changes: 6 additions & 2 deletions cmd/config_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"github.com/k0sproject/k0sctl/action"
"github.com/k0sproject/k0sctl/pkg/apis/k0sctl.k0sproject.io/v1beta1"

"github.com/urfave/cli/v2"
)
Expand All @@ -23,8 +22,13 @@ var configStatusCommand = &cli.Command{
},
Before: actions(initLogging, initConfig),
Action: func(ctx *cli.Context) error {
cfg, err := readConfig(ctx)
if err != nil {
return err
}

configStatusAction := action.ConfigStatus{
Config: ctx.Context.Value(ctxConfigKey{}).(*v1beta1.Cluster),
Config: cfg,
Format: ctx.String("output"),
Writer: ctx.App.Writer,
}
Expand Down
146 changes: 113 additions & 33 deletions cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ import (

"github.com/a8m/envsubst"
"github.com/adrg/xdg"
glob "github.com/bmatcuk/doublestar/v4"
"github.com/k0sproject/dig"
"github.com/k0sproject/k0sctl/phase"
"github.com/k0sproject/k0sctl/pkg/apis/k0sctl.k0sproject.io/v1beta1"
"github.com/k0sproject/k0sctl/pkg/manifest"
"github.com/k0sproject/k0sctl/pkg/retry"
k0sctl "github.com/k0sproject/k0sctl/version"
"github.com/k0sproject/rig"
Expand All @@ -22,11 +25,10 @@ import (
"github.com/shiena/ansicolor"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v2"
)

type (
ctxConfigKey struct{}
ctxConfigsKey struct{}
ctxManagerKey struct{}
ctxLogFileKey struct{}
)
Expand Down Expand Up @@ -58,11 +60,11 @@ var (
Value: false,
}

configFlag = &cli.StringFlag{
configFlag = &cli.StringSliceFlag{
Name: "config",
Usage: "Path to cluster config yaml. Use '-' to read from stdin.",
Usage: "Path or glob to config yaml. Can be given multiple times. Use '-' to read from stdin.",
Aliases: []string{"c"},
Value: "k0sctl.yaml",
Value: cli.NewStringSlice("k0sctl.yaml"),
TakesFile: true,
}

Expand Down Expand Up @@ -115,44 +117,73 @@ func actions(funcs ...func(*cli.Context) error) func(*cli.Context) error {

// initConfig takes the config flag, does some magic and replaces the value with the file contents
func initConfig(ctx *cli.Context) error {
f := ctx.String("config")
if f == "" {
f := ctx.StringSlice("config")
if len(f) == 0 || f[0] == "" {
return nil
}

file, err := configReader(f)
if err != nil {
return err
var configs []string
// detect globs and expand
for _, p := range f {
if p == "-" || p == "k0sctl.yaml" {
configs = append(configs, p)
continue
}
stat, err := os.Stat(p)
if err == nil {
if stat.IsDir() {
p = path.Join(p, "**/*.{yml,yaml}")
}
}
base, pattern := glob.SplitPattern(p)
fsys := os.DirFS(base)
matches, err := glob.Glob(fsys, pattern)
if err != nil {
return err
}
log.Debugf("glob %s expanded to %v", p, matches)
for _, m := range matches {
configs = append(configs, path.Join(base, m))
}
}
defer file.Close()

content, err := io.ReadAll(file)
if err != nil {
return err
if len(configs) == 0 {
return fmt.Errorf("no configuration files found")
}

subst, err := envsubst.Bytes(content)
if err != nil {
return err
}
log.Debugf("%d potential configuration files found", len(configs))

log.Debugf("Loaded configuration:\n%s", subst)
manifestReader := &manifest.Reader{}

c := &v1beta1.Cluster{}
if err := yaml.UnmarshalStrict(subst, c); err != nil {
return err
}
for _, f := range configs {
file, err := configReader(f)
if err != nil {
return err
}
defer file.Close()

m, err := yaml.Marshal(c)
if err == nil {
log.Tracef("unmarshaled configuration:\n%s", m)
content, err := io.ReadAll(file)
if err != nil {
return err
}

subst, err := envsubst.Bytes(content)
if err != nil {
return err
}

log.Debugf("Loaded configuration from %s:\n%s", f, subst)

if err := manifestReader.ParseBytes(subst); err != nil {
return fmt.Errorf("failed to parse config: %w", err)
}
}

if err := c.Validate(); err != nil {
return fmt.Errorf("configuration validation failed: %w", err)
if manifestReader.Len() == 0 {
return fmt.Errorf("no resource definition manifests found in configuration files")
}

ctx.Context = context.WithValue(ctx.Context, ctxConfigKey{}, c)
ctx.Context = context.WithValue(ctx.Context, ctxConfigsKey{}, manifestReader)

return nil
}
Expand Down Expand Up @@ -181,13 +212,47 @@ func warnOldCache(_ *cli.Context) error {
return nil
}

func readConfig(ctx *cli.Context) (*v1beta1.Cluster, error) {
mr, err := ManifestReader(ctx.Context)
if err != nil {
return nil, fmt.Errorf("failed to get manifest reader: %w", err)
}
ctlConfigs, err := mr.GetResources(v1beta1.APIVersion, "Cluster")
if err != nil {
return nil, fmt.Errorf("failed to get cluster resources: %w", err)
}
if len(ctlConfigs) != 1 {
return nil, fmt.Errorf("expected exactly one cluster config, got %d", len(ctlConfigs))
}
cfg := &v1beta1.Cluster{}
if err := ctlConfigs[0].Unmarshal(cfg); err != nil {
return nil, fmt.Errorf("failed to unmarshal cluster config: %w", err)
}
if k0sConfigs, err := mr.GetResources("k0s.k0sproject.io/v1beta1", "ClusterConfig"); err == nil && len(k0sConfigs) > 0 {
for _, k0sConfig := range k0sConfigs {
k0s := make(dig.Mapping)
log.Debugf("unmarshalling %d bytes of config from %v", len(k0sConfig.Raw), k0sConfig.Filename())
if err := k0sConfig.Unmarshal(&k0s); err != nil {
return nil, fmt.Errorf("failed to unmarshal k0s config: %w", err)
}
log.Debugf("merging in k0s config from %v", k0sConfig.Filename())
cfg.Spec.K0s.Config.Merge(k0s)
}
}

if err := cfg.Validate(); err != nil {
return nil, fmt.Errorf("cluster config validation failed: %w", err)
}
return cfg, nil
}

func initManager(ctx *cli.Context) error {
c, ok := ctx.Context.Value(ctxConfigKey{}).(*v1beta1.Cluster)
if c == nil || !ok {
return fmt.Errorf("cluster config not available in context")
cfg, err := readConfig(ctx)
if err != nil {
return err
}

manager, err := phase.NewManager(c)
manager, err := phase.NewManager(cfg)
if err != nil {
return fmt.Errorf("failed to initialize phase manager: %w", err)
}
Expand Down Expand Up @@ -382,3 +447,18 @@ func displayLogo(_ *cli.Context) error {
fmt.Print(logo)
return nil
}

// ManifestReader returns a manifest reader from context
func ManifestReader(ctx context.Context) (*manifest.Reader, error) {
if ctx == nil {
return nil, fmt.Errorf("context is nil")
}
v := ctx.Value(ctxConfigsKey{})
if v == nil {
return nil, fmt.Errorf("config reader not found in context")
}
if r, ok := v.(*manifest.Reader); ok {
return r, nil
}
return nil, fmt.Errorf("config reader in context is not of the correct type")
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ require (
github.com/creasty/defaults v1.8.0
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/k0sproject/dig v0.3.1
github.com/k0sproject/dig v0.4.0
github.com/k0sproject/rig v0.19.0
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/k0sproject/dig v0.3.1 h1:/QK40lXQ/HEE3LMT3r/kST1ANhMVZiajNDXI+spbL9o=
github.com/k0sproject/dig v0.3.1/go.mod h1:rlZ7N7ZEcB4Fi96TPXkZ4dqyAiDWOGLapyL9YpZ7Qz4=
github.com/k0sproject/dig v0.4.0 h1:yBxFUUxNXAMGBg6b7c6ypxdx/o3RmhoI5v5ABOw5tn0=
github.com/k0sproject/dig v0.4.0/go.mod h1:rlZ7N7ZEcB4Fi96TPXkZ4dqyAiDWOGLapyL9YpZ7Qz4=
github.com/k0sproject/rig v0.19.0 h1:aF/wJDfK45Ho2Z75Uap+u4Q4jHgr/1WfrHcOg2U9/n0=
github.com/k0sproject/rig v0.19.0/go.mod h1:SNa9+xeVA6zQVYx+SINaa4ZihFPWrmo/6crHcdvJRFI=
github.com/k0sproject/version v0.6.0 h1:Wi8wu9j+H36+okIQA47o/YHbzNpKeIYj8IjGdJOdqsI=
Expand Down
8 changes: 4 additions & 4 deletions pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ const APIVersion = "k0sctl.k0sproject.io/v1beta1"

// ClusterMetadata defines cluster metadata
type ClusterMetadata struct {
Name string `yaml:"name" validate:"required" default:"k0s-cluster"`
User string `yaml:"user" default:"admin"`
Kubeconfig string `yaml:"-"`
EtcdMembers []string `yaml:"-"`
Name string `yaml:"name" validate:"required" default:"k0s-cluster"`
User string `yaml:"user" default:"admin"`
Kubeconfig string `yaml:"-"`
EtcdMembers []string `yaml:"-"`
}

// Cluster describes launchpad.yaml configuration
Expand Down
Loading

0 comments on commit 1d181af

Please sign in to comment.