From 058fc19ec237776b3f2fcd4892117d74cbbc940f Mon Sep 17 00:00:00 2001 From: Kimmo Lehto Date: Tue, 31 Dec 2024 11:36:09 +0200 Subject: [PATCH] Read config from multidoc/multiple YAMLs Signed-off-by: Kimmo Lehto --- action/apply.go | 1 + cmd/flags.go | 18 +++++ phase/apply_manifests.go | 70 +++++++++++++++++++ .../k0sctl.k0sproject.io/v1beta1/cluster.go | 1 + smoke-test/smoke-multidoc.sh | 9 +++ 5 files changed, 99 insertions(+) create mode 100644 phase/apply_manifests.go diff --git a/action/apply.go b/action/apply.go index f9b54b91..9fcb156e 100644 --- a/action/apply.go +++ b/action/apply.go @@ -90,6 +90,7 @@ func NewApply(opts ApplyOptions) *Apply { &phase.ResetWorkers{NoDrain: opts.NoDrain}, &phase.ResetControllers{NoDrain: opts.NoDrain}, &phase.RunHooks{Stage: "after", Action: "apply"}, + &phase.ApplyManifests{}, unlockPhase, &phase.Disconnect{}, }, diff --git a/cmd/flags.go b/cmd/flags.go index f331bdec..f418a50d 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -8,6 +8,7 @@ import ( "path" "path/filepath" "runtime" + "strings" "time" "github.com/a8m/envsubst" @@ -239,6 +240,23 @@ func readConfig(ctx *cli.Context) (*v1beta1.Cluster, error) { cfg.Spec.K0s.Config.Merge(k0s) } } + otherConfigs := mr.FilterResources(func(rd *manifest.ResourceDefinition) bool { + if strings.EqualFold(rd.APIVersion, v1beta1.APIVersion) && strings.EqualFold(rd.Kind, "cluster") { + return false + } + if strings.EqualFold(rd.APIVersion, "k0s.k0sproject.io/v1beta1") && strings.EqualFold(rd.Kind, "clusterconfig") { + return false + } + return true + }) + if len(otherConfigs) > 0 { + cfg.Metadata.Manifests = make(map[string][]byte) + log.Debugf("found %d additional resources in the configuration", len(otherConfigs)) + for _, otherConfig := range otherConfigs { + log.Debugf("found resource: %s (%d bytes)", otherConfig.Filename(), len(otherConfig.Raw)) + cfg.Metadata.Manifests[otherConfig.Filename()] = otherConfig.Raw + } + } if err := cfg.Validate(); err != nil { return nil, fmt.Errorf("cluster config validation failed: %w", err) diff --git a/phase/apply_manifests.go b/phase/apply_manifests.go new file mode 100644 index 00000000..ad879b4b --- /dev/null +++ b/phase/apply_manifests.go @@ -0,0 +1,70 @@ +package phase + +import ( + "bytes" + "fmt" + "io" + + "github.com/k0sproject/k0sctl/pkg/apis/k0sctl.k0sproject.io/v1beta1" + "github.com/k0sproject/k0sctl/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster" + "github.com/k0sproject/rig/exec" + log "github.com/sirupsen/logrus" +) + +// ApplyManifests is a phase that applies additional manifests to the cluster +type ApplyManifests struct { + GenericPhase + leader *cluster.Host +} + +// Title for the phase +func (p *ApplyManifests) Title() string { + return "Apply additional manifests" +} + +// Prepare the phase +func (p *ApplyManifests) Prepare(config *v1beta1.Cluster) error { + p.Config = config + p.leader = p.Config.Spec.K0sLeader() + + return nil +} + +// ShouldRun is true when there are additional manifests to apply +func (p *ApplyManifests) ShouldRun() bool { + return len(p.Config.Metadata.Manifests) > 0 +} + +// Run the phase +func (p *ApplyManifests) Run() error { + for name, content := range p.Config.Metadata.Manifests { + if err := p.apply(name, content); err != nil { + return err + } + } + + return nil +} + +func (p *ApplyManifests) apply(name string, content []byte) error { + if !p.IsWet() { + p.DryMsgf(p.leader, "apply manifest %s (%d bytes)", name, len(content)) + return nil + } + + log.Infof("%s: apply manifest %s (%d bytes)", p.leader, name, len(content)) + kubectlCmd := p.leader.Configurer.KubectlCmdf(p.leader, p.leader.K0sDataDir(), "apply -f -") + var stdout, stderr bytes.Buffer + + cmd, err := p.leader.ExecStreams(kubectlCmd, io.NopCloser(bytes.NewReader(content)), &stdout, &stderr, exec.Sudo(p.leader)) + if err != nil { + return fmt.Errorf("failed to run apply for manifest %s: %w", name, err) + } + if err := cmd.Wait(); err != nil { + log.Errorf("kubectl apply failed for manifest %s", name) + log.Errorf("kubectl apply stderr: %s", stderr.String()) + return fmt.Errorf("failed to apply manifest %s: %w", name, err) + } + log.Infof("kubectl apply: %s", stdout.String()) + return nil +} diff --git a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go index 6824c77a..0d95903d 100644 --- a/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go +++ b/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster.go @@ -18,6 +18,7 @@ type ClusterMetadata struct { User string `yaml:"user" default:"admin"` Kubeconfig string `yaml:"-"` EtcdMembers []string `yaml:"-"` + Manifests map[string][]byte `yaml:"-"` } // Cluster describes launchpad.yaml configuration diff --git a/smoke-test/smoke-multidoc.sh b/smoke-test/smoke-multidoc.sh index 5f4ac910..da5c93a5 100755 --- a/smoke-test/smoke-multidoc.sh +++ b/smoke-test/smoke-multidoc.sh @@ -21,6 +21,15 @@ echo "* Starting apply" ../k0sctl apply --config multidoc/ --kubeconfig-out applykubeconfig --debug echo "* Apply OK" +echo "* Downloading kubectl for local test" +downloadKubectl + +echo "*Waiting until the test pod is running" +KUBECONFIG=applykubeconfig ./kubectl wait --for=condition=Ready pod/hello --timeout=120s + +echo "* Using kubectl to verify the test pod works" +KUBECONFIG=applykubeconfig ./kubectl exec -it pod/hello -- curl http://localhost/ | grep -q "Welcome to nginx!" + remoteCommand root@manager0 "cat /etc/k0s/k0s.yaml" > k0syaml echo Resulting k0s.yaml: cat k0syaml