From 7cd75c182137c1c75796311d59c413ef3ce0e0e0 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:35:29 +0100 Subject: [PATCH 01/65] feat: add create k0s setup from config.yaml --- Makefile | 3 + cli/cmd/install_k0s.go | 112 +++++- cli/cmd/install_k0s_integration_test.go | 449 ++++++++++++++++++++++++ cli/cmd/mocks.go | 17 +- go.mod | 2 + go.sum | 4 + hack/lima-oms.yaml | 42 ++- internal/installer/k0s.go | 101 ++++++ internal/installer/k0s_config.go | 118 +++++++ internal/installer/k0s_config_test.go | 148 ++++++++ internal/installer/mocks.go | 241 +++++-------- internal/installer/node/node.go | 444 +++++++++++++++++++++++ internal/portal/mocks.go | 225 ++---------- internal/system/mocks.go | 44 +-- internal/util/filewriter.go | 5 + internal/util/mocks.go | 268 ++++++-------- 16 files changed, 1644 insertions(+), 579 deletions(-) create mode 100644 cli/cmd/install_k0s_integration_test.go create mode 100644 internal/installer/k0s_config.go create mode 100644 internal/installer/k0s_config_test.go create mode 100644 internal/installer/node/node.go diff --git a/Makefile b/Makefile index aac4d160..d4ff5082 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,9 @@ all: build-cli build-service build-cli: cd cli && go build -v && mv cli ../oms-cli +build-cli-linux: + GOOS=linux GOARCH=amd64 go build -C cli -o ../oms-cli + build-service: cd service && go build -v && mv service ../oms-service diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index a5139330..aed5d79d 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -5,12 +5,17 @@ package cmd import ( "fmt" + "log" + "os" + "path/filepath" packageio "github.com/codesphere-cloud/cs-go/pkg/io" "github.com/spf13/cobra" "github.com/codesphere-cloud/oms/internal/env" "github.com/codesphere-cloud/oms/internal/installer" + "github.com/codesphere-cloud/oms/internal/installer/files" + "github.com/codesphere-cloud/oms/internal/installer/node" "github.com/codesphere-cloud/oms/internal/portal" "github.com/codesphere-cloud/oms/internal/util" ) @@ -25,10 +30,14 @@ type InstallK0sCmd struct { type InstallK0sOpts struct { *GlobalOptions - Version string - Package string - Config string - Force bool + Version string + Package string + Config string + InstallConfig string + SSHKeyPath string + RemoteHost string + RemoteUser string + Force bool } func (c *InstallK0sCmd) RunE(_ *cobra.Command, args []string) error { @@ -37,6 +46,10 @@ func (c *InstallK0sCmd) RunE(_ *cobra.Command, args []string) error { pm := installer.NewPackage(env.GetOmsWorkdir(), c.Opts.Package) k0s := installer.NewK0s(hw, env, c.FileWriter) + if c.Opts.InstallConfig != "" { + return c.InstallK0sFromInstallConfig(pm, k0s) + } + err := c.InstallK0s(pm, k0s) if err != nil { return fmt.Errorf("failed to install k0s: %w", err) @@ -54,12 +67,19 @@ func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { This will either download the k0s binary directly to the OMS workdir, if not already present, and install it or load the k0s binary from the provided package file and install it. If no version is specified, the latest version will be downloaded. - If no install config is provided, k0s will be installed with the '--single' flag.`), + If no install config is provided, k0s will be installed with the '--single' flag. + + You can also install k0s from a Codesphere install-config file, which will: + - Generate a k0s configuration from the install-config + - Optionally install k0s on remote nodes via SSH`), Example: formatExamplesWithBinary("install k0s", []packageio.Example{ {Cmd: "", Desc: "Install k0s using the Go-native implementation"}, {Cmd: "--version ", Desc: "Version of k0s to install"}, {Cmd: "--package ", Desc: "Package file (e.g. codesphere-v1.2.3-installer.tar.gz) to load k0s from"}, {Cmd: "--k0s-config ", Desc: "Path to k0s configuration file, if not set k0s will be installed with the '--single' flag"}, + {Cmd: "--install-config ", Desc: "Path to Codesphere install-config file to generate k0s config from"}, + {Cmd: "--remote-host ", Desc: "Remote host IP to install k0s on (requires --ssh-key-path)"}, + {Cmd: "--ssh-key-path ", Desc: "SSH private key path for remote installation"}, {Cmd: "--force", Desc: "Force new download and installation even if k0s binary exists or is already installed"}, }, "oms-cli"), }, @@ -70,6 +90,10 @@ func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { k0s.cmd.Flags().StringVarP(&k0s.Opts.Version, "version", "v", "", "Version of k0s to install") k0s.cmd.Flags().StringVarP(&k0s.Opts.Package, "package", "p", "", "Package file (e.g. codesphere-v1.2.3-installer.tar.gz) to load k0s from") k0s.cmd.Flags().StringVar(&k0s.Opts.Config, "k0s-config", "", "Path to k0s configuration file") + k0s.cmd.Flags().StringVar(&k0s.Opts.InstallConfig, "install-config", "", "Path to Codesphere install-config file") + k0s.cmd.Flags().StringVar(&k0s.Opts.SSHKeyPath, "ssh-key-path", "", "SSH private key path for remote installation") + k0s.cmd.Flags().StringVar(&k0s.Opts.RemoteHost, "remote-host", "", "Remote host IP to install k0s on") + k0s.cmd.Flags().StringVar(&k0s.Opts.RemoteUser, "remote-user", "root", "Remote user for SSH connection") k0s.cmd.Flags().BoolVarP(&k0s.Opts.Force, "force", "f", false, "Force new download and installation") install.AddCommand(k0s.cmd) @@ -98,3 +122,81 @@ func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0 return nil } + +func (c *InstallK0sCmd) InstallK0sFromInstallConfig(pm installer.PackageManager, k0s installer.K0sManager) error { + icg := installer.NewInstallConfigManager() + if err := icg.LoadInstallConfigFromFile(c.Opts.InstallConfig); err != nil { + return fmt.Errorf("failed to load install-config: %w", err) + } + + config := icg.GetInstallConfig() + + if !config.Kubernetes.ManagedByCodesphere { + return fmt.Errorf("install-config specifies external Kubernetes, k0s installation is only supported for Codesphere-managed Kubernetes") + } + + log.Println("Generating k0s configuration from install-config...") + k0sConfig, err := installer.GenerateK0sConfig(config) + if err != nil { + return fmt.Errorf("failed to generate k0s config: %w", err) + } + + k0sConfigData, err := k0sConfig.Marshal() + if err != nil { + return fmt.Errorf("failed to marshal k0s config: %w", err) + } + + tmpK0sConfigPath := filepath.Join(os.TempDir(), "k0s-config.yaml") + if err := os.WriteFile(tmpK0sConfigPath, k0sConfigData, 0644); err != nil { + return fmt.Errorf("failed to write k0s config: %w", err) + } + defer os.Remove(tmpK0sConfigPath) + + log.Printf("Generated k0s configuration at %s", tmpK0sConfigPath) + + k0sPath := pm.GetDependencyPath(defaultK0sPath) + if c.Opts.Package == "" { + k0sPath, err = k0s.Download(c.Opts.Version, c.Opts.Force, false) + if err != nil { + return fmt.Errorf("failed to download k0s: %w", err) + } + } + + if c.Opts.RemoteHost != "" { + return c.InstallK0sRemote(config, k0sPath, tmpK0sConfigPath) + } + + err = k0s.Install(tmpK0sConfigPath, k0sPath, c.Opts.Force) + if err != nil { + return fmt.Errorf("failed to install k0s: %w", err) + } + + log.Println("k0s installed successfully using configuration from install-config") + return nil +} + +func (c *InstallK0sCmd) InstallK0sRemote(config *files.RootConfig, k0sBinaryPath string, k0sConfigPath string) error { + if c.Opts.SSHKeyPath == "" { + return fmt.Errorf("--ssh-key-path is required for remote installation") + } + + log.Printf("Installing k0s on remote host %s", c.Opts.RemoteHost) + + nm := &node.NodeManager{ + FileIO: c.FileWriter, + KeyPath: c.Opts.SSHKeyPath, + } + + remoteNode := &node.Node{ + ExternalIP: c.Opts.RemoteHost, + InternalIP: c.Opts.RemoteHost, + Name: "k0s-node", + } + + if err := remoteNode.InstallK0s(nm, k0sBinaryPath, k0sConfigPath, c.Opts.Force); err != nil { + return fmt.Errorf("failed to install k0s on remote host: %w", err) + } + + log.Printf("k0s successfully installed on remote host %s", c.Opts.RemoteHost) + return nil +} diff --git a/cli/cmd/install_k0s_integration_test.go b/cli/cmd/install_k0s_integration_test.go new file mode 100644 index 00000000..966315a1 --- /dev/null +++ b/cli/cmd/install_k0s_integration_test.go @@ -0,0 +1,449 @@ +//go:build integration +// +build integration + +package cmd_test + +import ( + "os" + "path/filepath" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "gopkg.in/yaml.v3" + + "github.com/codesphere-cloud/oms/internal/installer" + "github.com/codesphere-cloud/oms/internal/installer/files" +) + +var _ = Describe("K0s Install-Config Integration", func() { + var ( + tempDir string + configPath string + k0sConfigOut string + ) + + BeforeEach(func() { + var err error + tempDir, err = os.MkdirTemp("", "k0s-integration-test-*") + Expect(err).NotTo(HaveOccurred()) + + configPath = filepath.Join(tempDir, "install-config.yaml") + k0sConfigOut = filepath.Join(tempDir, "k0s-config.yaml") + }) + + AfterEach(func() { + if tempDir != "" { + os.RemoveAll(tempDir) + } + }) + + Describe("Config Generation Workflow", func() { + It("should generate valid k0s config from install-config", func() { + // Create a minimal install-config using RootConfig + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + City: "Test City", + CountryCode: "US", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + { + IPAddress: "192.168.1.100", + }, + }, + APIServerHost: "api.test.example.com", + }, + Codesphere: files.CodesphereConfig{ + Domain: "test.example.com", + PublicIP: "192.168.1.100", + DeployConfig: files.DeployConfig{ + Images: map[string]files.ImageConfig{}, + }, + Plans: files.PlansConfig{ + HostingPlans: map[int]files.HostingPlan{}, + WorkspacePlans: map[int]files.WorkspacePlan{}, + }, + }, + } + + // Write install-config to file + configData, err := yaml.Marshal(installConfig) + Expect(err).NotTo(HaveOccurred()) + err = os.WriteFile(configPath, configData, 0644) + Expect(err).NotTo(HaveOccurred()) + + // Load the config back using InstallConfigManager + icg := installer.NewInstallConfigManager() + err = icg.LoadInstallConfigFromFile(configPath) + Expect(err).NotTo(HaveOccurred()) + + loadedConfig := icg.GetInstallConfig() + Expect(loadedConfig).NotTo(BeNil()) + Expect(loadedConfig.Kubernetes.ManagedByCodesphere).To(BeTrue()) + + // Generate k0s config + k0sConfig, err := installer.GenerateK0sConfig(loadedConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(k0sConfig).NotTo(BeNil()) + + // Verify k0s config structure + Expect(k0sConfig.APIVersion).To(Equal("k0s.k0sproject.io/v1beta1")) + Expect(k0sConfig.Kind).To(Equal("ClusterConfig")) + Expect(k0sConfig.Metadata.Name).To(Equal("codesphere-test-dc")) + Expect(k0sConfig.Spec.API).NotTo(BeNil()) + Expect(k0sConfig.Spec.API.Address).To(Equal("192.168.1.100")) + Expect(k0sConfig.Spec.API.ExternalAddress).To(Equal("api.test.example.com")) + + // Write k0s config to file + k0sData, err := k0sConfig.Marshal() + Expect(err).NotTo(HaveOccurred()) + err = os.WriteFile(k0sConfigOut, k0sData, 0644) + Expect(err).NotTo(HaveOccurred()) + + // Verify file was created and is valid YAML + Expect(k0sConfigOut).To(BeAnExistingFile()) + data, err := os.ReadFile(k0sConfigOut) + Expect(err).NotTo(HaveOccurred()) + Expect(len(data)).To(BeNumerically(">", 0)) + + // Verify we can unmarshal it back + var verifyConfig installer.K0sConfig + err = yaml.Unmarshal(data, &verifyConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(verifyConfig.APIVersion).To(Equal("k0s.k0sproject.io/v1beta1")) + }) + + It("should handle multi-control-plane configuration", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "multi-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.0.10"}, + {IPAddress: "10.0.0.11"}, + {IPAddress: "10.0.0.12"}, + }, + APIServerHost: "api.cluster.test", + }, + Codesphere: files.CodesphereConfig{ + Domain: "cluster.test", + PublicIP: "10.0.0.10", + DeployConfig: files.DeployConfig{ + Images: map[string]files.ImageConfig{}, + }, + Plans: files.PlansConfig{ + HostingPlans: map[int]files.HostingPlan{}, + WorkspacePlans: map[int]files.WorkspacePlan{}, + }, + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + + // Verify primary IP is used + Expect(k0sConfig.Spec.API.Address).To(Equal("10.0.0.10")) + // Verify all IPs are in SANs + Expect(k0sConfig.Spec.API.SANs).To(ContainElement("10.0.0.10")) + Expect(k0sConfig.Spec.API.SANs).To(ContainElement("10.0.0.11")) + Expect(k0sConfig.Spec.API.SANs).To(ContainElement("10.0.0.12")) + Expect(k0sConfig.Spec.API.SANs).To(ContainElement("api.cluster.test")) + }) + + It("should preserve network configuration", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "network-test", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "192.168.1.100"}, + }, + PodCIDR: "10.244.0.0/16", + ServiceCIDR: "10.96.0.0/12", + }, + Codesphere: files.CodesphereConfig{ + Domain: "network.test", + PublicIP: "192.168.1.100", + DeployConfig: files.DeployConfig{ + Images: map[string]files.ImageConfig{}, + }, + Plans: files.PlansConfig{ + HostingPlans: map[int]files.HostingPlan{}, + WorkspacePlans: map[int]files.WorkspacePlan{}, + }, + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + + // Verify network settings + Expect(k0sConfig.Spec.Network).NotTo(BeNil()) + Expect(k0sConfig.Spec.Network.PodCIDR).To(Equal("10.244.0.0/16")) + Expect(k0sConfig.Spec.Network.ServiceCIDR).To(Equal("10.96.0.0/12")) + }) + + It("should handle storage configuration", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "storage-test", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "192.168.1.100"}, + }, + }, + Codesphere: files.CodesphereConfig{ + Domain: "storage.test", + PublicIP: "192.168.1.100", + DeployConfig: files.DeployConfig{ + Images: map[string]files.ImageConfig{}, + }, + Plans: files.PlansConfig{ + HostingPlans: map[int]files.HostingPlan{}, + WorkspacePlans: map[int]files.WorkspacePlan{}, + }, + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + + // Verify storage/etcd settings + Expect(k0sConfig.Spec.Storage).NotTo(BeNil()) + Expect(k0sConfig.Spec.Storage.Type).To(Equal("etcd")) + Expect(k0sConfig.Spec.Storage.Etcd).NotTo(BeNil()) + Expect(k0sConfig.Spec.Storage.Etcd.PeerAddress).To(Equal("192.168.1.100")) + }) + }) + + Describe("Error Handling", func() { + It("should fail gracefully on missing install-config file", func() { + nonExistentPath := filepath.Join(tempDir, "does-not-exist.yaml") + icg := installer.NewInstallConfigManager() + err := icg.LoadInstallConfigFromFile(nonExistentPath) + Expect(err).To(HaveOccurred()) + }) + + It("should handle nil config gracefully", func() { + _, err := installer.GenerateK0sConfig(nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("cannot be nil")) + }) + + It("should handle invalid YAML gracefully", func() { + invalidYAML := []byte("invalid: [unclosed bracket") + err := os.WriteFile(configPath, invalidYAML, 0644) + Expect(err).NotTo(HaveOccurred()) + + icg := installer.NewInstallConfigManager() + err = icg.LoadInstallConfigFromFile(configPath) + Expect(err).To(HaveOccurred()) + }) + + It("should fail for external Kubernetes", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "external-k8s", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: false, // External K8s + }, + Codesphere: files.CodesphereConfig{ + Domain: "external.test", + PublicIP: "10.0.0.1", + DeployConfig: files.DeployConfig{ + Images: map[string]files.ImageConfig{}, + }, + Plans: files.PlansConfig{ + HostingPlans: map[int]files.HostingPlan{}, + WorkspacePlans: map[int]files.WorkspacePlan{}, + }, + }, + } + + // GenerateK0sConfig should still work (doesn't validate ManagedByCodesphere) + // The validation happens in the CLI command + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(k0sConfig).NotTo(BeNil()) + }) + }) + + Describe("YAML Marshalling", func() { + It("should produce valid k0s YAML output", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "yaml-test", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.20.30.40"}, + }, + }, + Codesphere: files.CodesphereConfig{ + Domain: "yaml.test", + PublicIP: "10.20.30.40", + DeployConfig: files.DeployConfig{ + Images: map[string]files.ImageConfig{}, + }, + Plans: files.PlansConfig{ + HostingPlans: map[int]files.HostingPlan{}, + WorkspacePlans: map[int]files.WorkspacePlan{}, + }, + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + + yamlData, err := k0sConfig.Marshal() + Expect(err).NotTo(HaveOccurred()) + Expect(string(yamlData)).To(ContainSubstring("k0s.k0sproject.io/v1beta1")) + Expect(string(yamlData)).To(ContainSubstring("ClusterConfig")) + Expect(string(yamlData)).To(ContainSubstring("10.20.30.40")) + }) + + It("should round-trip marshal and unmarshal correctly", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "roundtrip-test", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "172.16.0.1"}, + }, + PodCIDR: "10.244.0.0/16", + ServiceCIDR: "10.96.0.0/12", + }, + Codesphere: files.CodesphereConfig{ + Domain: "roundtrip.test", + PublicIP: "172.16.0.1", + DeployConfig: files.DeployConfig{ + Images: map[string]files.ImageConfig{}, + }, + Plans: files.PlansConfig{ + HostingPlans: map[int]files.HostingPlan{}, + WorkspacePlans: map[int]files.WorkspacePlan{}, + }, + }, + } + + original, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + + // Marshal to YAML + yamlData, err := original.Marshal() + Expect(err).NotTo(HaveOccurred()) + + // Unmarshal back + var restored installer.K0sConfig + err = yaml.Unmarshal(yamlData, &restored) + Expect(err).NotTo(HaveOccurred()) + + // Verify they match + Expect(restored.APIVersion).To(Equal(original.APIVersion)) + Expect(restored.Kind).To(Equal(original.Kind)) + Expect(restored.Metadata.Name).To(Equal(original.Metadata.Name)) + Expect(restored.Spec.API.Address).To(Equal(original.Spec.API.Address)) + Expect(restored.Spec.Network.PodCIDR).To(Equal(original.Spec.Network.PodCIDR)) + Expect(restored.Spec.Network.ServiceCIDR).To(Equal(original.Spec.Network.ServiceCIDR)) + }) + }) + + Describe("Full Workflow Integration", func() { + It("should complete full config generation workflow", func() { + // Step 1: Create install-config + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "integration-dc", + City: "Integration City", + CountryCode: "US", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "203.0.113.10"}, + }, + APIServerHost: "api.integration.test", + PodCIDR: "10.244.0.0/16", + ServiceCIDR: "10.96.0.0/12", + }, + Codesphere: files.CodesphereConfig{ + Domain: "integration.test", + PublicIP: "203.0.113.10", + DeployConfig: files.DeployConfig{ + Images: map[string]files.ImageConfig{}, + }, + Plans: files.PlansConfig{ + HostingPlans: map[int]files.HostingPlan{}, + WorkspacePlans: map[int]files.WorkspacePlan{}, + }, + }, + } + + // Step 2: Write install-config + configData, err := yaml.Marshal(installConfig) + Expect(err).NotTo(HaveOccurred()) + err = os.WriteFile(configPath, configData, 0644) + Expect(err).NotTo(HaveOccurred()) + + // Step 3: Load install-config + icg := installer.NewInstallConfigManager() + err = icg.LoadInstallConfigFromFile(configPath) + Expect(err).NotTo(HaveOccurred()) + + // Step 4: Generate k0s config + loadedConfig := icg.GetInstallConfig() + k0sConfig, err := installer.GenerateK0sConfig(loadedConfig) + Expect(err).NotTo(HaveOccurred()) + + // Step 5: Marshal k0s config + k0sData, err := k0sConfig.Marshal() + Expect(err).NotTo(HaveOccurred()) + + // Step 6: Write k0s config + err = os.WriteFile(k0sConfigOut, k0sData, 0644) + Expect(err).NotTo(HaveOccurred()) + + // Step 7: Verify complete workflow + Expect(k0sConfigOut).To(BeAnExistingFile()) + + // Step 8: Load and verify k0s config + readData, err := os.ReadFile(k0sConfigOut) + Expect(err).NotTo(HaveOccurred()) + + var finalK0sConfig installer.K0sConfig + err = yaml.Unmarshal(readData, &finalK0sConfig) + Expect(err).NotTo(HaveOccurred()) + + // Step 9: Validate all fields + Expect(finalK0sConfig.APIVersion).To(Equal("k0s.k0sproject.io/v1beta1")) + Expect(finalK0sConfig.Kind).To(Equal("ClusterConfig")) + Expect(finalK0sConfig.Metadata.Name).To(Equal("codesphere-integration-dc")) + Expect(finalK0sConfig.Spec.API.Address).To(Equal("203.0.113.10")) + Expect(finalK0sConfig.Spec.API.ExternalAddress).To(Equal("api.integration.test")) + Expect(finalK0sConfig.Spec.Network.PodCIDR).To(Equal("10.244.0.0/16")) + Expect(finalK0sConfig.Spec.Network.ServiceCIDR).To(Equal("10.96.0.0/12")) + Expect(finalK0sConfig.Spec.Storage.Type).To(Equal("etcd")) + Expect(finalK0sConfig.Spec.Storage.Etcd.PeerAddress).To(Equal("203.0.113.10")) + }) + }) +}) diff --git a/cli/cmd/mocks.go b/cli/cmd/mocks.go index 2f86863c..a1ac35ea 100644 --- a/cli/cmd/mocks.go +++ b/cli/cmd/mocks.go @@ -74,26 +74,15 @@ type MockOMSUpdater_Update_Call struct { } // Update is a helper method to define mock.On call -// - v semver.Version -// - repo string +// - v +// - repo func (_e *MockOMSUpdater_Expecter) Update(v interface{}, repo interface{}) *MockOMSUpdater_Update_Call { return &MockOMSUpdater_Update_Call{Call: _e.mock.On("Update", v, repo)} } func (_c *MockOMSUpdater_Update_Call) Run(run func(v semver.Version, repo string)) *MockOMSUpdater_Update_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 semver.Version - if args[0] != nil { - arg0 = args[0].(semver.Version) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - run( - arg0, - arg1, - ) + run(args[0].(semver.Version), args[1].(string)) }) return _c } diff --git a/go.mod b/go.mod index fc94122a..2654bc52 100644 --- a/go.mod +++ b/go.mod @@ -329,6 +329,7 @@ require ( github.com/knadh/koanf/providers/posflag v0.1.0 // indirect github.com/knadh/koanf/providers/structs v0.1.0 // indirect github.com/knadh/koanf/v2 v2.3.0 // indirect + github.com/kr/fs v0.1.0 // indirect github.com/kulti/thelper v0.7.1 // indirect github.com/kunwardeep/paralleltest v1.0.15 // indirect github.com/kylelemons/godebug v1.1.0 // indirect @@ -390,6 +391,7 @@ require ( github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/sftp v1.13.10 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect diff --git a/go.sum b/go.sum index 1571e54f..bea566d5 100644 --- a/go.sum +++ b/go.sum @@ -853,6 +853,8 @@ github.com/knadh/koanf/providers/structs v0.1.0 h1:wJRteCNn1qvLtE5h8KQBvLJovidSd github.com/knadh/koanf/providers/structs v0.1.0/go.mod h1:sw2YZ3txUcqA3Z27gPlmmBzWn1h8Nt9O6EP/91MkcWE= github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM= github.com/knadh/koanf/v2 v2.3.0/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -1024,6 +1026,8 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjL github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU= +github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/hack/lima-oms.yaml b/hack/lima-oms.yaml index 8dabfd2b..631b7966 100644 --- a/hack/lima-oms.yaml +++ b/hack/lima-oms.yaml @@ -18,8 +18,8 @@ disk: "60GiB" # Mount your OMS project mounts: -- location: "." - mountPoint: "/home/user/oms" +- location: "~" + mountPoint: "/home/user/host-home" writable: true # Ports and SSH @@ -61,13 +61,12 @@ provision: set -eux -o pipefail # Install Docker in rootless mode - dockerd-rootless-setuptool.sh install + dockerd-rootless-setuptool.sh install || true - # Set up the OMS project - cd /home/user/oms - export PATH=$PATH:/usr/local/go/bin - go mod download - cd cli && go build -a -buildvcs=false && mv cli ../oms-cli + # Clone OMS repository locally for better build performance + if [ ! -d ~/oms ]; then + git clone https://github.com/codesphere-cloud/oms.git ~/oms + fi message: | Your OMS development environment is ready! @@ -75,13 +74,32 @@ message: | To access it: ------ limactl shell lima-oms - cd /home/user/oms + cd oms ./oms-cli --help ------ + + To build the CLI for Linux (from your Mac): + ------ + make build-cli-linux + ------ - To install Codesphere eg.: + To build the CLI inside lima: ------ - ./oms-cli install codesphere --package codesphere-v1.66.0-installer --config config.yaml --priv-key ./path-to-private-key + limactl shell lima-oms + cd oms + make build-cli ------ - Go 1.24 and Docker are installed and ready to use. + To install k0s (run inside lima): + ------ + limactl shell lima-oms + cd oms + ./oms-cli install k0s --k0s-config config.yaml --version v1.30.0+k0s.0 --force + ------ + + To install Codesphere (run inside lima): + ------ + limactl shell lima-oms + cd oms + ./oms-cli install codesphere --package codesphere-v1.66.0-installer --install-config config.yaml --priv-key ./path-to-private-key + ------ diff --git a/internal/installer/k0s.go b/internal/installer/k0s.go index 1c350b37..f497c1c9 100644 --- a/internal/installer/k0s.go +++ b/internal/installer/k0s.go @@ -14,12 +14,14 @@ import ( "github.com/codesphere-cloud/oms/internal/env" "github.com/codesphere-cloud/oms/internal/portal" "github.com/codesphere-cloud/oms/internal/util" + "gopkg.in/yaml.v3" ) type K0sManager interface { GetLatestVersion() (string, error) Download(version string, force bool, quiet bool) (string, error) Install(configPath string, k0sPath string, force bool) error + Reset(k0sPath string) error } type K0s struct { @@ -62,6 +64,12 @@ func (k *K0s) Download(version string, force bool, quiet bool) (string, error) { // Check if k0s binary already exists and create destination file workdir := k.Env.GetOmsWorkdir() + + // Ensure workdir exists + if err := os.MkdirAll(workdir, 0755); err != nil { + return "", fmt.Errorf("failed to create workdir: %w", err) + } + k0sPath := filepath.Join(workdir, "k0s") if k.FileWriter.Exists(k0sPath) && !force { return "", fmt.Errorf("k0s binary already exists at %s. Use --force to overwrite", k0sPath) @@ -102,8 +110,23 @@ func (k *K0s) Install(configPath string, k0sPath string, force bool) error { return fmt.Errorf("k0s binary does not exist in '%s', please download first", k0sPath) } + if force { + if err := k.Reset(k0sPath); err != nil { + log.Printf("Warning: failed to reset k0s: %v", err) + } + } + args := []string{k0sPath, "install", "controller"} + + // If config path is provided, filter it to only include k0s-compatible fields if configPath != "" { + filteredConfigPath, err := k.filterConfigForK0s(configPath) + if err != nil { + log.Printf("Warning: failed to filter config, using original: %v", err) + } else { + configPath = filteredConfigPath + defer os.Remove(filteredConfigPath) // Clean up temp file after use + } args = append(args, "--config", configPath) } else { args = append(args, "--single") @@ -128,3 +151,81 @@ func (k *K0s) Install(configPath string, k0sPath string, force bool) error { return nil } + +func (k *K0s) filterConfigForK0s(configPath string) (string, error) { + data, err := os.ReadFile(configPath) + if err != nil { + return "", fmt.Errorf("failed to read config: %w", err) + } + + var config map[string]interface{} + if err := yaml.Unmarshal(data, &config); err != nil { + return "", fmt.Errorf("failed to parse config: %w", err) + } + + keysToKeep := map[string]bool{ + "apiVersion": true, + "kind": true, + "metadata": true, + "spec": true, + } + + for key := range config { + if !keysToKeep[key] { + delete(config, key) + } + } + + if spec, ok := config["spec"].(map[string]interface{}); ok { + specKeysToKeep := map[string]bool{ + "api": true, + "controllerManager": true, + "scheduler": true, + "extensions": true, + "network": true, + "storage": true, + "telemetry": true, + "images": true, + "konnectivity": true, + } + + for key := range spec { + if !specKeysToKeep[key] { + delete(spec, key) + } + } + config["spec"] = spec + } + + filteredData, err := yaml.Marshal(config) + if err != nil { + return "", fmt.Errorf("failed to marshal filtered config: %w", err) + } + + tmpFile, err := os.CreateTemp("", "k0s-config-*.yaml") + if err != nil { + return "", fmt.Errorf("failed to create temp config: %w", err) + } + defer tmpFile.Close() + + if _, err := tmpFile.Write(filteredData); err != nil { + return "", fmt.Errorf("failed to write temp config: %w", err) + } + + return tmpFile.Name(), nil +} + +func (k *K0s) Reset(k0sPath string) error { + if !k.FileWriter.Exists(k0sPath) { + return nil + } + + log.Println("Resetting existing k0s installation...") + err := util.RunCommand("sudo", []string{k0sPath, "reset"}, "") + if err != nil { + return fmt.Errorf("failed to reset k0s: %w", err) + } + + log.Println("k0s reset completed successfully") + return nil +} diff --git a/internal/installer/k0s_config.go b/internal/installer/k0s_config.go new file mode 100644 index 00000000..738e0c88 --- /dev/null +++ b/internal/installer/k0s_config.go @@ -0,0 +1,118 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package installer + +import ( + "fmt" + + "github.com/codesphere-cloud/oms/internal/installer/files" + "gopkg.in/yaml.v3" +) + +type K0sConfig struct { + APIVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Metadata K0sMetadata `yaml:"metadata"` + Spec K0sSpec `yaml:"spec"` +} + +type K0sMetadata struct { + Name string `yaml:"name"` +} + +type K0sSpec struct { + API *K0sAPI `yaml:"api,omitempty"` + Network *K0sNetwork `yaml:"network,omitempty"` + Storage *K0sStorage `yaml:"storage,omitempty"` +} + +type K0sAPI struct { + Address string `yaml:"address,omitempty"` + ExternalAddress string `yaml:"externalAddress,omitempty"` + SANs []string `yaml:"sans,omitempty"` + Port int `yaml:"port,omitempty"` +} + +type K0sNetwork struct { + PodCIDR string `yaml:"podCIDR,omitempty"` + ServiceCIDR string `yaml:"serviceCIDR,omitempty"` + Provider string `yaml:"provider,omitempty"` +} + +type K0sStorage struct { + Type string `yaml:"type,omitempty"` + Etcd *K0sEtcd `yaml:"etcd,omitempty"` +} + +type K0sEtcd struct { + PeerAddress string `yaml:"peerAddress,omitempty"` +} + +func GenerateK0sConfig(installConfig *files.RootConfig) (*K0sConfig, error) { + if installConfig == nil { + return nil, fmt.Errorf("installConfig cannot be nil") + } + + k0sConfig := &K0sConfig{ + APIVersion: "k0s.k0sproject.io/v1beta1", + Kind: "ClusterConfig", + Metadata: K0sMetadata{ + Name: fmt.Sprintf("codesphere-%s", installConfig.Datacenter.Name), + }, + Spec: K0sSpec{}, + } + + if installConfig.Kubernetes.ManagedByCodesphere { + if len(installConfig.Kubernetes.ControlPlanes) > 0 { + firstControlPlane := installConfig.Kubernetes.ControlPlanes[0] + k0sConfig.Spec.API = &K0sAPI{ + Address: firstControlPlane.IPAddress, + Port: 6443, + } + + if installConfig.Kubernetes.APIServerHost != "" { + k0sConfig.Spec.API.ExternalAddress = installConfig.Kubernetes.APIServerHost + } + + sans := make([]string, 0, len(installConfig.Kubernetes.ControlPlanes)) + for _, cp := range installConfig.Kubernetes.ControlPlanes { + sans = append(sans, cp.IPAddress) + } + if installConfig.Kubernetes.APIServerHost != "" { + sans = append(sans, installConfig.Kubernetes.APIServerHost) + } + k0sConfig.Spec.API.SANs = sans + } + + k0sConfig.Spec.Network = &K0sNetwork{ + Provider: "kuberouter", + } + + if installConfig.Kubernetes.PodCIDR != "" { + k0sConfig.Spec.Network.PodCIDR = installConfig.Kubernetes.PodCIDR + } + if installConfig.Kubernetes.ServiceCIDR != "" { + k0sConfig.Spec.Network.ServiceCIDR = installConfig.Kubernetes.ServiceCIDR + } + + if len(installConfig.Kubernetes.ControlPlanes) > 0 { + k0sConfig.Spec.Storage = &K0sStorage{ + Type: "etcd", + Etcd: &K0sEtcd{ + PeerAddress: installConfig.Kubernetes.ControlPlanes[0].IPAddress, + }, + } + } + } + + return k0sConfig, nil +} + +func (c *K0sConfig) Marshal() ([]byte, error) { + return yaml.Marshal(c) +} + +func (c *K0sConfig) Unmarshal(data []byte) error { + return yaml.Unmarshal(data, c) +} diff --git a/internal/installer/k0s_config_test.go b/internal/installer/k0s_config_test.go new file mode 100644 index 00000000..673a886e --- /dev/null +++ b/internal/installer/k0s_config_test.go @@ -0,0 +1,148 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package installer_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "gopkg.in/yaml.v3" + + "github.com/codesphere-cloud/oms/internal/installer" + "github.com/codesphere-cloud/oms/internal/installer/files" +) + +var _ = Describe("K0sConfig", func() { + Describe("GenerateK0sConfig", func() { + Context("with valid install-config", func() { + It("should generate k0s config with control plane settings", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + APIServerHost: "k8s.example.com", + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, + {IPAddress: "10.0.1.11"}, + {IPAddress: "10.0.1.12"}, + }, + PodCIDR: "10.244.0.0/16", + ServiceCIDR: "10.96.0.0/12", + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).ToNot(HaveOccurred()) + Expect(k0sConfig).ToNot(BeNil()) + + // Check basic structure + Expect(k0sConfig.APIVersion).To(Equal("k0s.k0sproject.io/v1beta1")) + Expect(k0sConfig.Kind).To(Equal("ClusterConfig")) + Expect(k0sConfig.Metadata.Name).To(Equal("codesphere-test-dc")) + + // Check API configuration + Expect(k0sConfig.Spec.API).ToNot(BeNil()) + Expect(k0sConfig.Spec.API.Address).To(Equal("10.0.1.10")) + Expect(k0sConfig.Spec.API.ExternalAddress).To(Equal("k8s.example.com")) + Expect(k0sConfig.Spec.API.Port).To(Equal(6443)) + Expect(k0sConfig.Spec.API.SANs).To(ContainElements("10.0.1.10", "10.0.1.11", "10.0.1.12", "k8s.example.com")) + + // Check Network configuration + Expect(k0sConfig.Spec.Network).ToNot(BeNil()) + Expect(k0sConfig.Spec.Network.PodCIDR).To(Equal("10.244.0.0/16")) + Expect(k0sConfig.Spec.Network.ServiceCIDR).To(Equal("10.96.0.0/12")) + Expect(k0sConfig.Spec.Network.Provider).To(Equal("kuberouter")) + + // Check Storage configuration + Expect(k0sConfig.Spec.Storage).ToNot(BeNil()) + Expect(k0sConfig.Spec.Storage.Type).To(Equal("etcd")) + Expect(k0sConfig.Spec.Storage.Etcd).ToNot(BeNil()) + Expect(k0sConfig.Spec.Storage.Etcd.PeerAddress).To(Equal("10.0.1.10")) + }) + + It("should handle minimal configuration", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "minimal", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "192.168.1.100"}, + }, + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).ToNot(HaveOccurred()) + Expect(k0sConfig).ToNot(BeNil()) + Expect(k0sConfig.Metadata.Name).To(Equal("codesphere-minimal")) + }) + + It("should generate valid YAML", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, + }, + PodCIDR: "10.244.0.0/16", + ServiceCIDR: "10.96.0.0/12", + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).ToNot(HaveOccurred()) + + yamlData, err := k0sConfig.Marshal() + Expect(err).ToNot(HaveOccurred()) + Expect(yamlData).ToNot(BeEmpty()) + + // Verify it can be unmarshalled back + var parsedConfig installer.K0sConfig + err = yaml.Unmarshal(yamlData, &parsedConfig) + Expect(err).ToNot(HaveOccurred()) + Expect(parsedConfig.Metadata.Name).To(Equal("codesphere-test-dc")) + }) + }) + + Context("with invalid input", func() { + It("should return error for nil install-config", func() { + k0sConfig, err := installer.GenerateK0sConfig(nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("installConfig cannot be nil")) + Expect(k0sConfig).To(BeNil()) + }) + }) + + Context("with non-managed Kubernetes", func() { + It("should not configure k0s for external kubernetes", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "external", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: false, + PodCIDR: "10.244.0.0/16", + ServiceCIDR: "10.96.0.0/12", + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).ToNot(HaveOccurred()) + Expect(k0sConfig).ToNot(BeNil()) + // Should still have basic structure but no specific config + Expect(k0sConfig.Metadata.Name).To(Equal("codesphere-external")) + }) + }) + }) +}) diff --git a/internal/installer/mocks.go b/internal/installer/mocks.go index 56e8250d..fdfaf36b 100644 --- a/internal/installer/mocks.go +++ b/internal/installer/mocks.go @@ -69,20 +69,14 @@ type MockConfigManager_ParseConfigYaml_Call struct { } // ParseConfigYaml is a helper method to define mock.On call -// - configPath string +// - configPath func (_e *MockConfigManager_Expecter) ParseConfigYaml(configPath interface{}) *MockConfigManager_ParseConfigYaml_Call { return &MockConfigManager_ParseConfigYaml_Call{Call: _e.mock.On("ParseConfigYaml", configPath)} } func (_c *MockConfigManager_ParseConfigYaml_Call) Run(run func(configPath string)) *MockConfigManager_ParseConfigYaml_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -147,20 +141,14 @@ type MockInstallConfigManager_ApplyProfile_Call struct { } // ApplyProfile is a helper method to define mock.On call -// - profile string +// - profile func (_e *MockInstallConfigManager_Expecter) ApplyProfile(profile interface{}) *MockInstallConfigManager_ApplyProfile_Call { return &MockInstallConfigManager_ApplyProfile_Call{Call: _e.mock.On("ApplyProfile", profile)} } func (_c *MockInstallConfigManager_ApplyProfile_Call) Run(run func(profile string)) *MockInstallConfigManager_ApplyProfile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -332,20 +320,14 @@ type MockInstallConfigManager_LoadInstallConfigFromFile_Call struct { } // LoadInstallConfigFromFile is a helper method to define mock.On call -// - configPath string +// - configPath func (_e *MockInstallConfigManager_Expecter) LoadInstallConfigFromFile(configPath interface{}) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { return &MockInstallConfigManager_LoadInstallConfigFromFile_Call{Call: _e.mock.On("LoadInstallConfigFromFile", configPath)} } func (_c *MockInstallConfigManager_LoadInstallConfigFromFile_Call) Run(run func(configPath string)) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -383,20 +365,14 @@ type MockInstallConfigManager_LoadVaultFromFile_Call struct { } // LoadVaultFromFile is a helper method to define mock.On call -// - vaultPath string +// - vaultPath func (_e *MockInstallConfigManager_Expecter) LoadVaultFromFile(vaultPath interface{}) *MockInstallConfigManager_LoadVaultFromFile_Call { return &MockInstallConfigManager_LoadVaultFromFile_Call{Call: _e.mock.On("LoadVaultFromFile", vaultPath)} } func (_c *MockInstallConfigManager_LoadVaultFromFile_Call) Run(run func(vaultPath string)) *MockInstallConfigManager_LoadVaultFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -570,26 +546,15 @@ type MockInstallConfigManager_WriteInstallConfig_Call struct { } // WriteInstallConfig is a helper method to define mock.On call -// - configPath string -// - withComments bool +// - configPath +// - withComments func (_e *MockInstallConfigManager_Expecter) WriteInstallConfig(configPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteInstallConfig_Call { return &MockInstallConfigManager_WriteInstallConfig_Call{Call: _e.mock.On("WriteInstallConfig", configPath, withComments)} } func (_c *MockInstallConfigManager_WriteInstallConfig_Call) Run(run func(configPath string, withComments bool)) *MockInstallConfigManager_WriteInstallConfig_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -627,26 +592,15 @@ type MockInstallConfigManager_WriteVault_Call struct { } // WriteVault is a helper method to define mock.On call -// - vaultPath string -// - withComments bool +// - vaultPath +// - withComments func (_e *MockInstallConfigManager_Expecter) WriteVault(vaultPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteVault_Call { return &MockInstallConfigManager_WriteVault_Call{Call: _e.mock.On("WriteVault", vaultPath, withComments)} } func (_c *MockInstallConfigManager_WriteVault_Call) Run(run func(vaultPath string, withComments bool)) *MockInstallConfigManager_WriteVault_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -720,32 +674,16 @@ type MockK0sManager_Download_Call struct { } // Download is a helper method to define mock.On call -// - version string -// - force bool -// - quiet bool +// - version +// - force +// - quiet func (_e *MockK0sManager_Expecter) Download(version interface{}, force interface{}, quiet interface{}) *MockK0sManager_Download_Call { return &MockK0sManager_Download_Call{Call: _e.mock.On("Download", version, force, quiet)} } func (_c *MockK0sManager_Download_Call) Run(run func(version string, force bool, quiet bool)) *MockK0sManager_Download_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - var arg2 bool - if args[2] != nil { - arg2 = args[2].(bool) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(bool), args[2].(bool)) }) return _c } @@ -836,32 +774,16 @@ type MockK0sManager_Install_Call struct { } // Install is a helper method to define mock.On call -// - configPath string -// - k0sPath string -// - force bool +// - configPath +// - k0sPath +// - force func (_e *MockK0sManager_Expecter) Install(configPath interface{}, k0sPath interface{}, force interface{}) *MockK0sManager_Install_Call { return &MockK0sManager_Install_Call{Call: _e.mock.On("Install", configPath, k0sPath, force)} } func (_c *MockK0sManager_Install_Call) Run(run func(configPath string, k0sPath string, force bool)) *MockK0sManager_Install_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 bool - if args[2] != nil { - arg2 = args[2].(bool) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(string), args[2].(bool)) }) return _c } @@ -876,6 +798,51 @@ func (_c *MockK0sManager_Install_Call) RunAndReturn(run func(configPath string, return _c } +// Reset provides a mock function for the type MockK0sManager +func (_mock *MockK0sManager) Reset(k0sPath string) error { + ret := _mock.Called(k0sPath) + + if len(ret) == 0 { + panic("no return value specified for Reset") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(string) error); ok { + r0 = returnFunc(k0sPath) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockK0sManager_Reset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Reset' +type MockK0sManager_Reset_Call struct { + *mock.Call +} + +// Reset is a helper method to define mock.On call +// - k0sPath +func (_e *MockK0sManager_Expecter) Reset(k0sPath interface{}) *MockK0sManager_Reset_Call { + return &MockK0sManager_Reset_Call{Call: _e.mock.On("Reset", k0sPath)} +} + +func (_c *MockK0sManager_Reset_Call) Run(run func(k0sPath string)) *MockK0sManager_Reset_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockK0sManager_Reset_Call) Return(err error) *MockK0sManager_Reset_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockK0sManager_Reset_Call) RunAndReturn(run func(k0sPath string) error) *MockK0sManager_Reset_Call { + _c.Call.Return(run) + return _c +} + // NewMockPackageManager creates a new instance of MockPackageManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMockPackageManager(t interface { @@ -926,20 +893,14 @@ type MockPackageManager_Extract_Call struct { } // Extract is a helper method to define mock.On call -// - force bool +// - force func (_e *MockPackageManager_Expecter) Extract(force interface{}) *MockPackageManager_Extract_Call { return &MockPackageManager_Extract_Call{Call: _e.mock.On("Extract", force)} } func (_c *MockPackageManager_Extract_Call) Run(run func(force bool)) *MockPackageManager_Extract_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 bool - if args[0] != nil { - arg0 = args[0].(bool) - } - run( - arg0, - ) + run(args[0].(bool)) }) return _c } @@ -977,26 +938,15 @@ type MockPackageManager_ExtractDependency_Call struct { } // ExtractDependency is a helper method to define mock.On call -// - file string -// - force bool +// - file +// - force func (_e *MockPackageManager_Expecter) ExtractDependency(file interface{}, force interface{}) *MockPackageManager_ExtractDependency_Call { return &MockPackageManager_ExtractDependency_Call{Call: _e.mock.On("ExtractDependency", file, force)} } func (_c *MockPackageManager_ExtractDependency_Call) Run(run func(file string, force bool)) *MockPackageManager_ExtractDependency_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -1043,20 +993,14 @@ type MockPackageManager_ExtractOciImageIndex_Call struct { } // ExtractOciImageIndex is a helper method to define mock.On call -// - imagefile string +// - imagefile func (_e *MockPackageManager_Expecter) ExtractOciImageIndex(imagefile interface{}) *MockPackageManager_ExtractOciImageIndex_Call { return &MockPackageManager_ExtractOciImageIndex_Call{Call: _e.mock.On("ExtractOciImageIndex", imagefile)} } func (_c *MockPackageManager_ExtractOciImageIndex_Call) Run(run func(imagefile string)) *MockPackageManager_ExtractOciImageIndex_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -1149,20 +1093,14 @@ type MockPackageManager_GetBaseimageName_Call struct { } // GetBaseimageName is a helper method to define mock.On call -// - baseimage string +// - baseimage func (_e *MockPackageManager_Expecter) GetBaseimageName(baseimage interface{}) *MockPackageManager_GetBaseimageName_Call { return &MockPackageManager_GetBaseimageName_Call{Call: _e.mock.On("GetBaseimageName", baseimage)} } func (_c *MockPackageManager_GetBaseimageName_Call) Run(run func(baseimage string)) *MockPackageManager_GetBaseimageName_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -1209,26 +1147,15 @@ type MockPackageManager_GetBaseimagePath_Call struct { } // GetBaseimagePath is a helper method to define mock.On call -// - baseimage string -// - force bool +// - baseimage +// - force func (_e *MockPackageManager_Expecter) GetBaseimagePath(baseimage interface{}, force interface{}) *MockPackageManager_GetBaseimagePath_Call { return &MockPackageManager_GetBaseimagePath_Call{Call: _e.mock.On("GetBaseimagePath", baseimage, force)} } func (_c *MockPackageManager_GetBaseimagePath_Call) Run(run func(baseimage string, force bool)) *MockPackageManager_GetBaseimagePath_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -1319,20 +1246,14 @@ type MockPackageManager_GetDependencyPath_Call struct { } // GetDependencyPath is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockPackageManager_Expecter) GetDependencyPath(filename interface{}) *MockPackageManager_GetDependencyPath_Call { return &MockPackageManager_GetDependencyPath_Call{Call: _e.mock.On("GetDependencyPath", filename)} } func (_c *MockPackageManager_GetDependencyPath_Call) Run(run func(filename string)) *MockPackageManager_GetDependencyPath_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go new file mode 100644 index 00000000..20123606 --- /dev/null +++ b/internal/installer/node/node.go @@ -0,0 +1,444 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package node + +import ( + "fmt" + "log" + "net" + "os" + "path/filepath" + "syscall" + "time" + + "github.com/codesphere-cloud/oms/internal/util" + "github.com/pkg/sftp" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" + "golang.org/x/crypto/ssh/knownhosts" + "golang.org/x/term" +) + +type Node struct { + Name string `json:"name"` + ExternalIP string `json:"external_ip"` + InternalIP string `json:"internal_ip"` +} + +type NodeManager struct { + FileIO util.FileIO + KeyPath string +} + +func (n *NodeManager) getHostKeyCallback() (ssh.HostKeyCallback, error) { + homeDir, err := os.UserHomeDir() + if err != nil { + return nil, fmt.Errorf("failed to get user home directory: %w", err) + } + + knownHostsPath := filepath.Join(homeDir, ".ssh", "known_hosts") + hostKeyCallback, err := knownhosts.New(knownHostsPath) + if err != nil { + sshDir := filepath.Join(homeDir, ".ssh") + if err := os.MkdirAll(sshDir, 0700); err != nil { + return nil, fmt.Errorf("failed to create .ssh directory: %w", err) + } + if _, err := os.Create(knownHostsPath); err != nil { + return nil, fmt.Errorf("failed to create known_hosts file: %w", err) + } + hostKeyCallback, err = knownhosts.New(knownHostsPath) + if err != nil { + return nil, fmt.Errorf("failed to load known_hosts: %w", err) + } + } + + return hostKeyCallback, nil +} + +func (n *NodeManager) getAuthMethods() ([]ssh.AuthMethod, error) { + var authMethods []ssh.AuthMethod + + if authSocket := os.Getenv("SSH_AUTH_SOCK"); authSocket != "" { + conn, err := net.Dial("unix", authSocket) + if err == nil { + agentClient := agent.NewClient(conn) + authMethods = append(authMethods, ssh.PublicKeysCallback(agentClient.Signers)) + return authMethods, nil + } + fmt.Printf("Could not connect to SSH Agent (%s): %v\n", authSocket, err) + } + + if n.KeyPath != "" { + fmt.Println("Falling back to private key file authentication.") + + key, err := n.FileIO.ReadFile(n.KeyPath) + if err != nil { + return nil, fmt.Errorf("failed to read private key file %s: %v", n.KeyPath, err) + } + + signer, err := ssh.ParsePrivateKey(key) + if err == nil { + authMethods = append(authMethods, ssh.PublicKeys(signer)) + return authMethods, nil + } + if _, ok := err.(*ssh.PassphraseMissingError); ok { + fmt.Printf("Enter passphrase for key '%s': ", n.KeyPath) + passphraseBytes, err := term.ReadPassword(int(syscall.Stdin)) + fmt.Println() + + if err != nil { + return nil, fmt.Errorf("failed to read passphrase: %v", err) + } + + signer, err = ssh.ParsePrivateKeyWithPassphrase(key, passphraseBytes) + for i := range passphraseBytes { + passphraseBytes[i] = 0 + } + + if err != nil { + return nil, fmt.Errorf("failed to parse private key with passphrase: %v", err) + } + authMethods = append(authMethods, ssh.PublicKeys(signer)) + return authMethods, nil + } + return nil, fmt.Errorf("failed to parse private key: %v", err) + } + + if len(authMethods) == 0 { + return nil, fmt.Errorf("no valid authentication methods configured. Check SSH_AUTH_SOCK and private key path") + } + + return authMethods, nil +} + +func (n *NodeManager) connectToJumpbox(ip, username string) (*ssh.Client, error) { + authMethods, err := n.getAuthMethods() + if err != nil { + return nil, fmt.Errorf("jumpbox authentication setup failed: %v", err) + } + + hostKeyCallback, err := n.getHostKeyCallback() + if err != nil { + return nil, fmt.Errorf("failed to get host key callback: %w", err) + } + + config := &ssh.ClientConfig{ + User: username, + Auth: authMethods, + Timeout: 10 * time.Second, + HostKeyCallback: hostKeyCallback, + } + + addr := fmt.Sprintf("%s:22", ip) + jumpboxClient, err := ssh.Dial("tcp", addr, config) + if err != nil { + return nil, fmt.Errorf("failed to dial jumpbox %s: %v", addr, err) + } + + if err := n.forwardAgent(jumpboxClient, nil); err != nil { + fmt.Printf(" Warning: Agent forwarding setup failed on jumpbox: %v\n", err) + } + + return jumpboxClient, nil +} + +func (n *NodeManager) forwardAgent(client *ssh.Client, session *ssh.Session) error { + authSocket := os.Getenv("SSH_AUTH_SOCK") + if authSocket == "" { + log.Printf("SSH_AUTH_SOCK not set. Cannot perform agent forwarding") + } else { + conn, err := net.Dial("unix", authSocket) + if err != nil { + log.Printf("failed to dial SSH agent socket: %v", err) + } else { + ag := agent.NewClient(conn) + if err := agent.ForwardToAgent(client, ag); err != nil { + log.Printf("failed to forward agent to remote client: %v", err) + } + if session != nil { + if err := agent.RequestAgentForwarding(session); err != nil { + log.Printf("failed to request agent forwarding on session: %v", err) + } + } + } + + } + return nil +} + +func (n *NodeManager) RunSSHCommand(jumpboxIp string, ip string, username string, command string) error { + client, err := n.GetClient(jumpboxIp, ip, username) + if err != nil { + return fmt.Errorf("failed to get client: %w", err) + } + defer client.Close() + session, err := client.NewSession() + if err != nil { + return fmt.Errorf("failed to create session on jumpbox: %v", err) + } + defer session.Close() + + if err := n.forwardAgent(client, session); err != nil { + fmt.Printf(" Warning: Agent forwarding setup failed on session: %v\n", err) + } + + session.Stdout = os.Stdout + session.Stderr = os.Stderr + if err := session.Start(command); err != nil { + return fmt.Errorf("failed to start command: %v", err) + } + + if err := session.Wait(); err != nil { + return fmt.Errorf("command failed: %w", err) + } + + return nil +} + +func (n *NodeManager) GetClient(jumpboxIp string, ip string, username string) (*ssh.Client, error) { + + authMethods, err := n.getAuthMethods() + if err != nil { + return nil, fmt.Errorf("failed to get authentication methods: %w", err) + } + + hostKeyCallback, err := n.getHostKeyCallback() + if err != nil { + return nil, fmt.Errorf("failed to get host key callback: %w", err) + } + + if jumpboxIp != "" { + jbClient, err := n.connectToJumpbox(jumpboxIp, "ubuntu") + if err != nil { + return nil, fmt.Errorf("failed to connect to jumpbox: %v", err) + } + + finalTargetConfig := &ssh.ClientConfig{ + User: username, + Auth: authMethods, + Timeout: 10 * time.Second, + HostKeyCallback: hostKeyCallback, + } + + finalAddr := fmt.Sprintf("%s:22", ip) + jbConn, err := jbClient.Dial("tcp", finalAddr) + if err != nil { + return nil, fmt.Errorf("failed to create connection through jumpbox: %v", err) + } + finalClient, channels, requests, err := ssh.NewClientConn(jbConn, finalAddr, finalTargetConfig) + if err != nil { + return nil, fmt.Errorf("failed to perform SSH handshake through jumpbox: %v", err) + } + + return ssh.NewClient(finalClient, channels, requests), nil + } + + config := &ssh.ClientConfig{ + User: username, + Auth: authMethods, + Timeout: 10 * time.Second, + HostKeyCallback: hostKeyCallback, + } + + addr := fmt.Sprintf("%s:22", ip) + client, err := ssh.Dial("tcp", addr, config) + if err != nil { + return nil, fmt.Errorf("failed to dial: %v", err) + } + return client, nil +} + +func (n *NodeManager) GetSFTPClient(jumpboxIp string, ip string, username string) (*sftp.Client, error) { + client, err := n.GetClient(jumpboxIp, ip, username) + if err != nil { + return nil, fmt.Errorf("failed to get SSH client: %v", err) + } + sftpClient, err := sftp.NewClient(client) + if err != nil { + return nil, fmt.Errorf("failed to create SFTP client: %v", err) + } + return sftpClient, nil +} + +func (nm *NodeManager) EnsureDirectoryExists(ip string, username string, dir string) error { + cmd := fmt.Sprintf("mkdir -p '%s'", dir) + return nm.RunSSHCommand("", ip, username, cmd) +} + +func (n *NodeManager) CopyFile(jumpboxIp string, ip string, username string, src string, dst string) error { + client, err := n.GetSFTPClient(jumpboxIp, ip, username) + if err != nil { + return fmt.Errorf("failed to get SSH client: %v", err) + } + defer client.Close() + + srcFile, err := n.FileIO.Open(src) + if err != nil { + return fmt.Errorf("failed to open source file %s: %v", src, err) + } + defer srcFile.Close() + + dstFile, err := client.Create(dst) + if err != nil { + return fmt.Errorf("failed to create destination file %s: %v", dst, err) + } + defer dstFile.Close() + + _, err = dstFile.ReadFrom(srcFile) + if err != nil { + return fmt.Errorf("failed to copy data from %s to %s: %v", src, dst, err) + } + + return nil +} + +func (n *Node) HasCommand(nm *NodeManager, command string) bool { + checkCommand := fmt.Sprintf("command -v %s >/dev/null 2>&1", command) + err := nm.RunSSHCommand("", n.ExternalIP, "root", checkCommand) + if err != nil { + return false + } + return true +} + +func (n *Node) InstallOms(nm *NodeManager) error { + remoteCommands := []string{ + "wget -qO- 'https://api.github.com/repos/codesphere-cloud/oms/releases/latest' | jq -r '.assets[] | select(.name | match(\"oms-cli.*linux_amd64\")) | .browser_download_url' | xargs wget -O oms-cli", + "chmod +x oms-cli; sudo mv oms-cli /usr/local/bin/", + "curl -LO https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.amd64; sudo mv sops-v3.11.0.linux.amd64 /usr/local/bin/sops; sudo chmod +x /usr/local/bin/sops", + "wget https://dl.filippo.io/age/latest?for=linux/amd64 -O age.tar.gz; tar -xvf age.tar.gz; sudo mv age/age* /usr/local/bin/", + } + for _, cmd := range remoteCommands { + err := nm.RunSSHCommand("", n.ExternalIP, "root", cmd) + if err != nil { + return fmt.Errorf("failed to run remote command '%s': %w", cmd, err) + } + } + return nil +} + +func (n *Node) CopyFile(nm *NodeManager, src string, dst string) error { + err := nm.EnsureDirectoryExists(n.ExternalIP, "root", filepath.Dir(dst)) + if err != nil { + return fmt.Errorf("failed to ensure directory exists: %w", err) + } + return nm.CopyFile("", n.ExternalIP, "root", src, dst) +} + +func (n *Node) HasAcceptEnvConfigured(jumpbox *Node, nm *NodeManager) bool { + checkCommand := "sudo grep -E '^AcceptEnv OMS_PORTAL_API_KEY' /etc/ssh/sshd_config >/dev/null 2>&1" + err := n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommand) + if err != nil { + return false + } + return true +} + +func (n *Node) ConfigureAcceptEnv(jumpbox *Node, nm *NodeManager) error { + cmds := []string{ + "sudo sed -i 's/^#\\?AcceptEnv.*/AcceptEnv OMS_PORTAL_API_KEY/' /etc/ssh/sshd_config", + "sudo systemctl restart sshd", + } + for _, cmd := range cmds { + err := n.RunSSHCommand(jumpbox, nm, "ubuntu", cmd) + if err != nil { + return fmt.Errorf("failed to run command '%s': %w", cmd, err) + } + } + return nil +} + +func (n *Node) HasRootLoginEnabled(jumpbox *Node, nm *NodeManager) bool { + checkCommandPermit := "sudo grep -E '^PermitRootLogin yes' /etc/ssh/sshd_config >/dev/null 2>&1" + err := n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommandPermit) + if err != nil { + return false + } + checkCommandAuthorizedKeys := "sudo grep -E '^no-port-forwarding' /root/.ssh/authorized_keys >/dev/null 2>&1" + err = n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommandAuthorizedKeys) + if err == nil { + return false + } + return true +} + +func (n *Node) HasFile(jumpbox *Node, nm *NodeManager, filePath string) bool { + checkCommand := fmt.Sprintf("test -f '%s'", filePath) + err := n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommand) + if err != nil { + return false + } + return true +} + +func (n *Node) RunSSHCommand(jumpbox *Node, nm *NodeManager, username string, command string) error { + if jumpbox == nil { + return nm.RunSSHCommand("", n.ExternalIP, username, command) + } + + return nm.RunSSHCommand(jumpbox.ExternalIP, n.InternalIP, "ubuntu", command) +} + +func (n *Node) EnableRootLogin(jumpbox *Node, nm *NodeManager) error { + cmds := []string{ + "sudo sed-i 's/^#\\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config", + "sudo sed -i 's/no-port-forwarding.*$//g' /root/.ssh/authorized_keys", + "sudo systemctl restart sshd", + } + for _, cmd := range cmds { + err := n.RunSSHCommand(jumpbox, nm, "ubuntu", cmd) + if err != nil { + return fmt.Errorf("failed to run command '%s': %w", cmd, err) + } + } + return nil +} + +func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath string, force bool) error { + remoteK0sDir := "/usr/local/bin" + remoteK0sBinary := filepath.Join(remoteK0sDir, "k0s") + remoteConfigPath := "/etc/k0s/k0s.yaml" + + log.Printf("Copying k0s binary to %s:%s", n.ExternalIP, remoteK0sBinary) + if err := n.CopyFile(nm, k0sBinaryPath, remoteK0sBinary); err != nil { + return fmt.Errorf("failed to copy k0s binary: %w", err) + } + + log.Printf("Making k0s binary executable on %s", n.ExternalIP) + chmodCmd := fmt.Sprintf("chmod +x %s", remoteK0sBinary) + if err := nm.RunSSHCommand("", n.ExternalIP, "root", chmodCmd); err != nil { + return fmt.Errorf("failed to make k0s binary executable: %w", err) + } + + if k0sConfigPath != "" { + log.Printf("Copying k0s config to %s:%s", n.ExternalIP, remoteConfigPath) + if err := nm.EnsureDirectoryExists(n.ExternalIP, "root", "/etc/k0s"); err != nil { + return fmt.Errorf("failed to create /etc/k0s directory: %w", err) + } + if err := nm.CopyFile("", n.ExternalIP, "root", k0sConfigPath, remoteConfigPath); err != nil { + return fmt.Errorf("failed to copy k0s config: %w", err) + } + } + + installCmd := fmt.Sprintf("sudo %s install controller", remoteK0sBinary) + if k0sConfigPath != "" { + installCmd += fmt.Sprintf(" --config %s", remoteConfigPath) + } else { + installCmd += " --single" + } + if force { + installCmd += " --force" + } + + log.Printf("Installing k0s on %s", n.ExternalIP) + if err := nm.RunSSHCommand("", n.ExternalIP, "root", installCmd); err != nil { + return fmt.Errorf("failed to install k0s: %w", err) + } + + log.Printf("k0s successfully installed on %s", n.ExternalIP) + log.Printf("You can start it using: ssh root@%s 'sudo %s start'", n.ExternalIP, remoteK0sBinary) + log.Printf("You can check the status using: ssh root@%s 'sudo %s status'", n.ExternalIP, remoteK0sBinary) + + return nil +} diff --git a/internal/portal/mocks.go b/internal/portal/mocks.go index c162a3ee..83372369 100644 --- a/internal/portal/mocks.go +++ b/internal/portal/mocks.go @@ -61,32 +61,16 @@ type MockHttp_Download_Call struct { } // Download is a helper method to define mock.On call -// - url string -// - file io.Writer -// - quiet bool +// - url +// - file +// - quiet func (_e *MockHttp_Expecter) Download(url interface{}, file interface{}, quiet interface{}) *MockHttp_Download_Call { return &MockHttp_Download_Call{Call: _e.mock.On("Download", url, file, quiet)} } func (_c *MockHttp_Download_Call) Run(run func(url string, file io.Writer, quiet bool)) *MockHttp_Download_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 io.Writer - if args[1] != nil { - arg1 = args[1].(io.Writer) - } - var arg2 bool - if args[2] != nil { - arg2 = args[2].(bool) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(io.Writer), args[2].(bool)) }) return _c } @@ -135,20 +119,14 @@ type MockHttp_Get_Call struct { } // Get is a helper method to define mock.On call -// - url string +// - url func (_e *MockHttp_Expecter) Get(url interface{}) *MockHttp_Get_Call { return &MockHttp_Get_Call{Call: _e.mock.On("Get", url)} } func (_c *MockHttp_Get_Call) Run(run func(url string)) *MockHttp_Get_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -197,32 +175,16 @@ type MockHttp_Request_Call struct { } // Request is a helper method to define mock.On call -// - url string -// - method string -// - body io.Reader +// - url +// - method +// - body func (_e *MockHttp_Expecter) Request(url interface{}, method interface{}, body interface{}) *MockHttp_Request_Call { return &MockHttp_Request_Call{Call: _e.mock.On("Request", url, method, body)} } func (_c *MockHttp_Request_Call) Run(run func(url string, method string, body io.Reader)) *MockHttp_Request_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 io.Reader - if args[2] != nil { - arg2 = args[2].(io.Reader) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(string), args[2].(io.Reader)) }) return _c } @@ -287,44 +249,18 @@ type MockPortal_DownloadBuildArtifact_Call struct { } // DownloadBuildArtifact is a helper method to define mock.On call -// - product Product -// - build Build -// - file io.Writer -// - startByte int -// - quiet bool +// - product +// - build +// - file +// - startByte +// - quiet func (_e *MockPortal_Expecter) DownloadBuildArtifact(product interface{}, build interface{}, file interface{}, startByte interface{}, quiet interface{}) *MockPortal_DownloadBuildArtifact_Call { return &MockPortal_DownloadBuildArtifact_Call{Call: _e.mock.On("DownloadBuildArtifact", product, build, file, startByte, quiet)} } func (_c *MockPortal_DownloadBuildArtifact_Call) Run(run func(product Product, build Build, file io.Writer, startByte int, quiet bool)) *MockPortal_DownloadBuildArtifact_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 Product - if args[0] != nil { - arg0 = args[0].(Product) - } - var arg1 Build - if args[1] != nil { - arg1 = args[1].(Build) - } - var arg2 io.Writer - if args[2] != nil { - arg2 = args[2].(io.Writer) - } - var arg3 int - if args[3] != nil { - arg3 = args[3].(int) - } - var arg4 bool - if args[4] != nil { - arg4 = args[4].(bool) - } - run( - arg0, - arg1, - arg2, - arg3, - arg4, - ) + run(args[0].(Product), args[1].(Build), args[2].(io.Writer), args[3].(int), args[4].(bool)) }) return _c } @@ -371,20 +307,14 @@ type MockPortal_GetApiKeyId_Call struct { } // GetApiKeyId is a helper method to define mock.On call -// - oldKey string +// - oldKey func (_e *MockPortal_Expecter) GetApiKeyId(oldKey interface{}) *MockPortal_GetApiKeyId_Call { return &MockPortal_GetApiKeyId_Call{Call: _e.mock.On("GetApiKeyId", oldKey)} } func (_c *MockPortal_GetApiKeyId_Call) Run(run func(oldKey string)) *MockPortal_GetApiKeyId_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -431,32 +361,16 @@ type MockPortal_GetBuild_Call struct { } // GetBuild is a helper method to define mock.On call -// - product Product -// - version string -// - hash string +// - product +// - version +// - hash func (_e *MockPortal_Expecter) GetBuild(product interface{}, version interface{}, hash interface{}) *MockPortal_GetBuild_Call { return &MockPortal_GetBuild_Call{Call: _e.mock.On("GetBuild", product, version, hash)} } func (_c *MockPortal_GetBuild_Call) Run(run func(product Product, version string, hash string)) *MockPortal_GetBuild_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 Product - if args[0] != nil { - arg0 = args[0].(Product) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(Product), args[1].(string), args[2].(string)) }) return _c } @@ -558,20 +472,14 @@ type MockPortal_ListBuilds_Call struct { } // ListBuilds is a helper method to define mock.On call -// - product Product +// - product func (_e *MockPortal_Expecter) ListBuilds(product interface{}) *MockPortal_ListBuilds_Call { return &MockPortal_ListBuilds_Call{Call: _e.mock.On("ListBuilds", product)} } func (_c *MockPortal_ListBuilds_Call) Run(run func(product Product)) *MockPortal_ListBuilds_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 Product - if args[0] != nil { - arg0 = args[0].(Product) - } - run( - arg0, - ) + run(args[0].(Product)) }) return _c } @@ -620,38 +528,17 @@ type MockPortal_RegisterAPIKey_Call struct { } // RegisterAPIKey is a helper method to define mock.On call -// - owner string -// - organization string -// - role string -// - expiresAt time.Time +// - owner +// - organization +// - role +// - expiresAt func (_e *MockPortal_Expecter) RegisterAPIKey(owner interface{}, organization interface{}, role interface{}, expiresAt interface{}) *MockPortal_RegisterAPIKey_Call { return &MockPortal_RegisterAPIKey_Call{Call: _e.mock.On("RegisterAPIKey", owner, organization, role, expiresAt)} } func (_c *MockPortal_RegisterAPIKey_Call) Run(run func(owner string, organization string, role string, expiresAt time.Time)) *MockPortal_RegisterAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - var arg3 time.Time - if args[3] != nil { - arg3 = args[3].(time.Time) - } - run( - arg0, - arg1, - arg2, - arg3, - ) + run(args[0].(string), args[1].(string), args[2].(string), args[3].(time.Time)) }) return _c } @@ -689,20 +576,14 @@ type MockPortal_RevokeAPIKey_Call struct { } // RevokeAPIKey is a helper method to define mock.On call -// - key string +// - key func (_e *MockPortal_Expecter) RevokeAPIKey(key interface{}) *MockPortal_RevokeAPIKey_Call { return &MockPortal_RevokeAPIKey_Call{Call: _e.mock.On("RevokeAPIKey", key)} } func (_c *MockPortal_RevokeAPIKey_Call) Run(run func(key string)) *MockPortal_RevokeAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -740,26 +621,15 @@ type MockPortal_UpdateAPIKey_Call struct { } // UpdateAPIKey is a helper method to define mock.On call -// - key string -// - expiresAt time.Time +// - key +// - expiresAt func (_e *MockPortal_Expecter) UpdateAPIKey(key interface{}, expiresAt interface{}) *MockPortal_UpdateAPIKey_Call { return &MockPortal_UpdateAPIKey_Call{Call: _e.mock.On("UpdateAPIKey", key, expiresAt)} } func (_c *MockPortal_UpdateAPIKey_Call) Run(run func(key string, expiresAt time.Time)) *MockPortal_UpdateAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 time.Time - if args[1] != nil { - arg1 = args[1].(time.Time) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(time.Time)) }) return _c } @@ -797,26 +667,15 @@ type MockPortal_VerifyBuildArtifactDownload_Call struct { } // VerifyBuildArtifactDownload is a helper method to define mock.On call -// - file io.Reader -// - download Build +// - file +// - download func (_e *MockPortal_Expecter) VerifyBuildArtifactDownload(file interface{}, download interface{}) *MockPortal_VerifyBuildArtifactDownload_Call { return &MockPortal_VerifyBuildArtifactDownload_Call{Call: _e.mock.On("VerifyBuildArtifactDownload", file, download)} } func (_c *MockPortal_VerifyBuildArtifactDownload_Call) Run(run func(file io.Reader, download Build)) *MockPortal_VerifyBuildArtifactDownload_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 io.Reader - if args[0] != nil { - arg0 = args[0].(io.Reader) - } - var arg1 Build - if args[1] != nil { - arg1 = args[1].(Build) - } - run( - arg0, - arg1, - ) + run(args[0].(io.Reader), args[1].(Build)) }) return _c } @@ -892,20 +751,14 @@ type MockHttpClient_Do_Call struct { } // Do is a helper method to define mock.On call -// - request *http.Request +// - request func (_e *MockHttpClient_Expecter) Do(request interface{}) *MockHttpClient_Do_Call { return &MockHttpClient_Do_Call{Call: _e.mock.On("Do", request)} } func (_c *MockHttpClient_Do_Call) Run(run func(request *http.Request)) *MockHttpClient_Do_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 *http.Request - if args[0] != nil { - arg0 = args[0].(*http.Request) - } - run( - arg0, - ) + run(args[0].(*http.Request)) }) return _c } diff --git a/internal/system/mocks.go b/internal/system/mocks.go index ee80bc69..cf8e9292 100644 --- a/internal/system/mocks.go +++ b/internal/system/mocks.go @@ -58,32 +58,16 @@ type MockImageManager_BuildImage_Call struct { } // BuildImage is a helper method to define mock.On call -// - dockerfile string -// - tag string -// - buildContext string +// - dockerfile +// - tag +// - buildContext func (_e *MockImageManager_Expecter) BuildImage(dockerfile interface{}, tag interface{}, buildContext interface{}) *MockImageManager_BuildImage_Call { return &MockImageManager_BuildImage_Call{Call: _e.mock.On("BuildImage", dockerfile, tag, buildContext)} } func (_c *MockImageManager_BuildImage_Call) Run(run func(dockerfile string, tag string, buildContext string)) *MockImageManager_BuildImage_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(string), args[2].(string)) }) return _c } @@ -121,20 +105,14 @@ type MockImageManager_LoadImage_Call struct { } // LoadImage is a helper method to define mock.On call -// - imageTarPath string +// - imageTarPath func (_e *MockImageManager_Expecter) LoadImage(imageTarPath interface{}) *MockImageManager_LoadImage_Call { return &MockImageManager_LoadImage_Call{Call: _e.mock.On("LoadImage", imageTarPath)} } func (_c *MockImageManager_LoadImage_Call) Run(run func(imageTarPath string)) *MockImageManager_LoadImage_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -172,20 +150,14 @@ type MockImageManager_PushImage_Call struct { } // PushImage is a helper method to define mock.On call -// - tag string +// - tag func (_e *MockImageManager_Expecter) PushImage(tag interface{}) *MockImageManager_PushImage_Call { return &MockImageManager_PushImage_Call{Call: _e.mock.On("PushImage", tag)} } func (_c *MockImageManager_PushImage_Call) Run(run func(tag string)) *MockImageManager_PushImage_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } diff --git a/internal/util/filewriter.go b/internal/util/filewriter.go index 48938df8..8c2685fe 100644 --- a/internal/util/filewriter.go +++ b/internal/util/filewriter.go @@ -17,6 +17,7 @@ type FileIO interface { MkdirAll(path string, perm os.FileMode) error OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) WriteFile(filename string, data []byte, perm os.FileMode) error + ReadFile(filename string) ([]byte, error) ReadDir(dirname string) ([]os.DirEntry, error) CreateAndWrite(filePath string, data []byte, fileType string) error } @@ -83,6 +84,10 @@ func (fs *FilesystemWriter) WriteFile(filename string, data []byte, perm os.File return os.WriteFile(filename, data, perm) } +func (fs *FilesystemWriter) ReadFile(filename string) ([]byte, error) { + return os.ReadFile(filename) +} + func (fs *FilesystemWriter) ReadDir(dirname string) ([]os.DirEntry, error) { return os.ReadDir(dirname) } diff --git a/internal/util/mocks.go b/internal/util/mocks.go index 24e1f4b3..394f6b82 100644 --- a/internal/util/mocks.go +++ b/internal/util/mocks.go @@ -70,26 +70,15 @@ type MockDockerfileManager_UpdateFromStatement_Call struct { } // UpdateFromStatement is a helper method to define mock.On call -// - dockerfile io.Reader -// - baseImage string +// - dockerfile +// - baseImage func (_e *MockDockerfileManager_Expecter) UpdateFromStatement(dockerfile interface{}, baseImage interface{}) *MockDockerfileManager_UpdateFromStatement_Call { return &MockDockerfileManager_UpdateFromStatement_Call{Call: _e.mock.On("UpdateFromStatement", dockerfile, baseImage)} } func (_c *MockDockerfileManager_UpdateFromStatement_Call) Run(run func(dockerfile io.Reader, baseImage string)) *MockDockerfileManager_UpdateFromStatement_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 io.Reader - if args[0] != nil { - arg0 = args[0].(io.Reader) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - run( - arg0, - arg1, - ) + run(args[0].(io.Reader), args[1].(string)) }) return _c } @@ -165,20 +154,14 @@ type MockFileIO_Create_Call struct { } // Create is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) Create(filename interface{}) *MockFileIO_Create_Call { return &MockFileIO_Create_Call{Call: _e.mock.On("Create", filename)} } func (_c *MockFileIO_Create_Call) Run(run func(filename string)) *MockFileIO_Create_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -216,32 +199,16 @@ type MockFileIO_CreateAndWrite_Call struct { } // CreateAndWrite is a helper method to define mock.On call -// - filePath string -// - data []byte -// - fileType string +// - filePath +// - data +// - fileType func (_e *MockFileIO_Expecter) CreateAndWrite(filePath interface{}, data interface{}, fileType interface{}) *MockFileIO_CreateAndWrite_Call { return &MockFileIO_CreateAndWrite_Call{Call: _e.mock.On("CreateAndWrite", filePath, data, fileType)} } func (_c *MockFileIO_CreateAndWrite_Call) Run(run func(filePath string, data []byte, fileType string)) *MockFileIO_CreateAndWrite_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 []byte - if args[1] != nil { - arg1 = args[1].([]byte) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].([]byte), args[2].(string)) }) return _c } @@ -279,20 +246,14 @@ type MockFileIO_Exists_Call struct { } // Exists is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) Exists(filename interface{}) *MockFileIO_Exists_Call { return &MockFileIO_Exists_Call{Call: _e.mock.On("Exists", filename)} } func (_c *MockFileIO_Exists_Call) Run(run func(filename string)) *MockFileIO_Exists_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -339,20 +300,14 @@ type MockFileIO_IsDirectory_Call struct { } // IsDirectory is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) IsDirectory(filename interface{}) *MockFileIO_IsDirectory_Call { return &MockFileIO_IsDirectory_Call{Call: _e.mock.On("IsDirectory", filename)} } func (_c *MockFileIO_IsDirectory_Call) Run(run func(filename string)) *MockFileIO_IsDirectory_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -390,26 +345,15 @@ type MockFileIO_MkdirAll_Call struct { } // MkdirAll is a helper method to define mock.On call -// - path string -// - perm os.FileMode +// - path +// - perm func (_e *MockFileIO_Expecter) MkdirAll(path interface{}, perm interface{}) *MockFileIO_MkdirAll_Call { return &MockFileIO_MkdirAll_Call{Call: _e.mock.On("MkdirAll", path, perm)} } func (_c *MockFileIO_MkdirAll_Call) Run(run func(path string, perm os.FileMode)) *MockFileIO_MkdirAll_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 os.FileMode - if args[1] != nil { - arg1 = args[1].(os.FileMode) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(os.FileMode)) }) return _c } @@ -458,20 +402,14 @@ type MockFileIO_Open_Call struct { } // Open is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) Open(filename interface{}) *MockFileIO_Open_Call { return &MockFileIO_Open_Call{Call: _e.mock.On("Open", filename)} } func (_c *MockFileIO_Open_Call) Run(run func(filename string)) *MockFileIO_Open_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -520,20 +458,14 @@ type MockFileIO_OpenAppend_Call struct { } // OpenAppend is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) OpenAppend(filename interface{}) *MockFileIO_OpenAppend_Call { return &MockFileIO_OpenAppend_Call{Call: _e.mock.On("OpenAppend", filename)} } func (_c *MockFileIO_OpenAppend_Call) Run(run func(filename string)) *MockFileIO_OpenAppend_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -582,32 +514,16 @@ type MockFileIO_OpenFile_Call struct { } // OpenFile is a helper method to define mock.On call -// - name string -// - flag int -// - perm os.FileMode +// - name +// - flag +// - perm func (_e *MockFileIO_Expecter) OpenFile(name interface{}, flag interface{}, perm interface{}) *MockFileIO_OpenFile_Call { return &MockFileIO_OpenFile_Call{Call: _e.mock.On("OpenFile", name, flag, perm)} } func (_c *MockFileIO_OpenFile_Call) Run(run func(name string, flag int, perm os.FileMode)) *MockFileIO_OpenFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 int - if args[1] != nil { - arg1 = args[1].(int) - } - var arg2 os.FileMode - if args[2] != nil { - arg2 = args[2].(os.FileMode) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(int), args[2].(os.FileMode)) }) return _c } @@ -656,20 +572,14 @@ type MockFileIO_ReadDir_Call struct { } // ReadDir is a helper method to define mock.On call -// - dirname string +// - dirname func (_e *MockFileIO_Expecter) ReadDir(dirname interface{}) *MockFileIO_ReadDir_Call { return &MockFileIO_ReadDir_Call{Call: _e.mock.On("ReadDir", dirname)} } func (_c *MockFileIO_ReadDir_Call) Run(run func(dirname string)) *MockFileIO_ReadDir_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -684,6 +594,62 @@ func (_c *MockFileIO_ReadDir_Call) RunAndReturn(run func(dirname string) ([]os.D return _c } +// ReadFile provides a mock function for the type MockFileIO +func (_mock *MockFileIO) ReadFile(filename string) ([]byte, error) { + ret := _mock.Called(filename) + + if len(ret) == 0 { + panic("no return value specified for ReadFile") + } + + var r0 []byte + var r1 error + if returnFunc, ok := ret.Get(0).(func(string) ([]byte, error)); ok { + return returnFunc(filename) + } + if returnFunc, ok := ret.Get(0).(func(string) []byte); ok { + r0 = returnFunc(filename) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + if returnFunc, ok := ret.Get(1).(func(string) error); ok { + r1 = returnFunc(filename) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockFileIO_ReadFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadFile' +type MockFileIO_ReadFile_Call struct { + *mock.Call +} + +// ReadFile is a helper method to define mock.On call +// - filename +func (_e *MockFileIO_Expecter) ReadFile(filename interface{}) *MockFileIO_ReadFile_Call { + return &MockFileIO_ReadFile_Call{Call: _e.mock.On("ReadFile", filename)} +} + +func (_c *MockFileIO_ReadFile_Call) Run(run func(filename string)) *MockFileIO_ReadFile_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockFileIO_ReadFile_Call) Return(bytes []byte, err error) *MockFileIO_ReadFile_Call { + _c.Call.Return(bytes, err) + return _c +} + +func (_c *MockFileIO_ReadFile_Call) RunAndReturn(run func(filename string) ([]byte, error)) *MockFileIO_ReadFile_Call { + _c.Call.Return(run) + return _c +} + // WriteFile provides a mock function for the type MockFileIO func (_mock *MockFileIO) WriteFile(filename string, data []byte, perm os.FileMode) error { ret := _mock.Called(filename, data, perm) @@ -707,32 +673,16 @@ type MockFileIO_WriteFile_Call struct { } // WriteFile is a helper method to define mock.On call -// - filename string -// - data []byte -// - perm os.FileMode +// - filename +// - data +// - perm func (_e *MockFileIO_Expecter) WriteFile(filename interface{}, data interface{}, perm interface{}) *MockFileIO_WriteFile_Call { return &MockFileIO_WriteFile_Call{Call: _e.mock.On("WriteFile", filename, data, perm)} } func (_c *MockFileIO_WriteFile_Call) Run(run func(filename string, data []byte, perm os.FileMode)) *MockFileIO_WriteFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 []byte - if args[1] != nil { - arg1 = args[1].([]byte) - } - var arg2 os.FileMode - if args[2] != nil { - arg2 = args[2].(os.FileMode) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].([]byte), args[2].(os.FileMode)) }) return _c } @@ -862,8 +812,8 @@ type MockTableWriter_AppendHeader_Call struct { } // AppendHeader is a helper method to define mock.On call -// - row table.Row -// - configs ...table.RowConfig +// - row +// - configs func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...interface{}) *MockTableWriter_AppendHeader_Call { return &MockTableWriter_AppendHeader_Call{Call: _e.mock.On("AppendHeader", append([]interface{}{row}, configs...)...)} @@ -871,20 +821,13 @@ func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...int func (_c *MockTableWriter_AppendHeader_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendHeader_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 table.Row - if args[0] != nil { - arg0 = args[0].(table.Row) - } - var arg1 []table.RowConfig - var variadicArgs []table.RowConfig - if len(args) > 1 { - variadicArgs = args[1].([]table.RowConfig) + variadicArgs := make([]table.RowConfig, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(table.RowConfig) + } } - arg1 = variadicArgs - run( - arg0, - arg1..., - ) + run(args[0].(table.Row), variadicArgs...) }) return _c } @@ -916,8 +859,8 @@ type MockTableWriter_AppendRow_Call struct { } // AppendRow is a helper method to define mock.On call -// - row table.Row -// - configs ...table.RowConfig +// - row +// - configs func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interface{}) *MockTableWriter_AppendRow_Call { return &MockTableWriter_AppendRow_Call{Call: _e.mock.On("AppendRow", append([]interface{}{row}, configs...)...)} @@ -925,20 +868,13 @@ func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interf func (_c *MockTableWriter_AppendRow_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendRow_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 table.Row - if args[0] != nil { - arg0 = args[0].(table.Row) - } - var arg1 []table.RowConfig - var variadicArgs []table.RowConfig - if len(args) > 1 { - variadicArgs = args[1].([]table.RowConfig) + variadicArgs := make([]table.RowConfig, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(table.RowConfig) + } } - arg1 = variadicArgs - run( - arg0, - arg1..., - ) + run(args[0].(table.Row), variadicArgs...) }) return _c } From 5a907ab6f8bffd4d533777696dae37a598c3e309 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:55:08 +0000 Subject: [PATCH 02/65] chore(docs): Auto-update docs and licenses Signed-off-by: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> --- NOTICE | 40 ++++- cli/cmd/install_k0s_integration_test.go | 3 + cli/cmd/mocks.go | 17 +- docs/oms-cli_install_k0s.md | 27 ++- internal/installer/mocks.go | 206 ++++++++++++++++++---- internal/portal/mocks.go | 225 ++++++++++++++++++++---- internal/system/mocks.go | 44 ++++- internal/util/mocks.go | 222 ++++++++++++++++++----- 8 files changed, 635 insertions(+), 149 deletions(-) diff --git a/NOTICE b/NOTICE index d7e513d3..ed0df755 100644 --- a/NOTICE +++ b/NOTICE @@ -23,9 +23,9 @@ License URL: https://github.com/clipperhouse/uax29/blob/v2.3.0/LICENSE ---------- Module: github.com/codesphere-cloud/cs-go/pkg/io -Version: v0.14.1 +Version: v0.15.0 License: Apache-2.0 -License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.14.1/LICENSE +License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.15.0/LICENSE ---------- Module: github.com/codesphere-cloud/oms/internal/tmpl @@ -77,9 +77,15 @@ License URL: https://github.com/inconshreveable/go-update/blob/8152e7eb6ccf/inte ---------- Module: github.com/jedib0t/go-pretty/v6 -Version: v6.7.5 +Version: v6.7.7 License: MIT -License URL: https://github.com/jedib0t/go-pretty/blob/v6.7.5/LICENSE +License URL: https://github.com/jedib0t/go-pretty/blob/v6.7.7/LICENSE + +---------- +Module: github.com/kr/fs +Version: v0.1.0 +License: BSD-3-Clause +License URL: https://github.com/kr/fs/blob/v0.1.0/LICENSE ---------- Module: github.com/mattn/go-runewidth @@ -87,6 +93,12 @@ Version: v0.0.19 License: MIT License URL: https://github.com/mattn/go-runewidth/blob/v0.0.19/LICENSE +---------- +Module: github.com/pkg/sftp +Version: v1.13.10 +License: BSD-2-Clause +License URL: https://github.com/pkg/sftp/blob/v1.13.10/LICENSE + ---------- Module: github.com/pmezard/go-difflib/difflib Version: v1.0.1-0.20181226105442-5d4384ee4fb2 @@ -155,9 +167,9 @@ License URL: https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE ---------- Module: golang.org/x/crypto -Version: v0.45.0 +Version: v0.46.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/crypto/+/v0.45.0:LICENSE +License URL: https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE ---------- Module: golang.org/x/oauth2 @@ -165,11 +177,23 @@ Version: v0.33.0 License: BSD-3-Clause License URL: https://cs.opensource.google/go/x/oauth2/+/v0.33.0:LICENSE +---------- +Module: golang.org/x/sys/unix +Version: v0.39.0 +License: BSD-3-Clause +License URL: https://cs.opensource.google/go/x/sys/+/v0.39.0:LICENSE + +---------- +Module: golang.org/x/term +Version: v0.38.0 +License: BSD-3-Clause +License URL: https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE + ---------- Module: golang.org/x/text -Version: v0.31.0 +Version: v0.32.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/text/+/v0.31.0:LICENSE +License URL: https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE ---------- Module: gopkg.in/yaml.v3 diff --git a/cli/cmd/install_k0s_integration_test.go b/cli/cmd/install_k0s_integration_test.go index 966315a1..8823aa4c 100644 --- a/cli/cmd/install_k0s_integration_test.go +++ b/cli/cmd/install_k0s_integration_test.go @@ -1,3 +1,6 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + //go:build integration // +build integration diff --git a/cli/cmd/mocks.go b/cli/cmd/mocks.go index a1ac35ea..2f86863c 100644 --- a/cli/cmd/mocks.go +++ b/cli/cmd/mocks.go @@ -74,15 +74,26 @@ type MockOMSUpdater_Update_Call struct { } // Update is a helper method to define mock.On call -// - v -// - repo +// - v semver.Version +// - repo string func (_e *MockOMSUpdater_Expecter) Update(v interface{}, repo interface{}) *MockOMSUpdater_Update_Call { return &MockOMSUpdater_Update_Call{Call: _e.mock.On("Update", v, repo)} } func (_c *MockOMSUpdater_Update_Call) Run(run func(v semver.Version, repo string)) *MockOMSUpdater_Update_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(semver.Version), args[1].(string)) + var arg0 semver.Version + if args[0] != nil { + arg0 = args[0].(semver.Version) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } diff --git a/docs/oms-cli_install_k0s.md b/docs/oms-cli_install_k0s.md index e9c7fa06..08b17f30 100644 --- a/docs/oms-cli_install_k0s.md +++ b/docs/oms-cli_install_k0s.md @@ -10,6 +10,10 @@ or load the k0s binary from the provided package file and install it. If no version is specified, the latest version will be downloaded. If no install config is provided, k0s will be installed with the '--single' flag. +You can also install k0s from a Codesphere install-config file, which will: +- Generate a k0s configuration from the install-config +- Optionally install k0s on remote nodes via SSH + ``` oms-cli install k0s [flags] ``` @@ -29,6 +33,15 @@ $ oms-cli install k0s --package # Path to k0s configuration file, if not set k0s will be installed with the '--single' flag $ oms-cli install k0s --k0s-config +# Path to Codesphere install-config file to generate k0s config from +$ oms-cli install k0s --install-config + +# Remote host IP to install k0s on (requires --ssh-key-path) +$ oms-cli install k0s --remote-host + +# SSH private key path for remote installation +$ oms-cli install k0s --ssh-key-path + # Force new download and installation even if k0s binary exists or is already installed $ oms-cli install k0s --force @@ -37,11 +50,15 @@ $ oms-cli install k0s --force ### Options ``` - -f, --force Force new download and installation - -h, --help help for k0s - --k0s-config string Path to k0s configuration file - -p, --package string Package file (e.g. codesphere-v1.2.3-installer.tar.gz) to load k0s from - -v, --version string Version of k0s to install + -f, --force Force new download and installation + -h, --help help for k0s + --install-config string Path to Codesphere install-config file + --k0s-config string Path to k0s configuration file + -p, --package string Package file (e.g. codesphere-v1.2.3-installer.tar.gz) to load k0s from + --remote-host string Remote host IP to install k0s on + --remote-user string Remote user for SSH connection (default "root") + --ssh-key-path string SSH private key path for remote installation + -v, --version string Version of k0s to install ``` ### SEE ALSO diff --git a/internal/installer/mocks.go b/internal/installer/mocks.go index fdfaf36b..d22ab787 100644 --- a/internal/installer/mocks.go +++ b/internal/installer/mocks.go @@ -69,14 +69,20 @@ type MockConfigManager_ParseConfigYaml_Call struct { } // ParseConfigYaml is a helper method to define mock.On call -// - configPath +// - configPath string func (_e *MockConfigManager_Expecter) ParseConfigYaml(configPath interface{}) *MockConfigManager_ParseConfigYaml_Call { return &MockConfigManager_ParseConfigYaml_Call{Call: _e.mock.On("ParseConfigYaml", configPath)} } func (_c *MockConfigManager_ParseConfigYaml_Call) Run(run func(configPath string)) *MockConfigManager_ParseConfigYaml_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -141,14 +147,20 @@ type MockInstallConfigManager_ApplyProfile_Call struct { } // ApplyProfile is a helper method to define mock.On call -// - profile +// - profile string func (_e *MockInstallConfigManager_Expecter) ApplyProfile(profile interface{}) *MockInstallConfigManager_ApplyProfile_Call { return &MockInstallConfigManager_ApplyProfile_Call{Call: _e.mock.On("ApplyProfile", profile)} } func (_c *MockInstallConfigManager_ApplyProfile_Call) Run(run func(profile string)) *MockInstallConfigManager_ApplyProfile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -320,14 +332,20 @@ type MockInstallConfigManager_LoadInstallConfigFromFile_Call struct { } // LoadInstallConfigFromFile is a helper method to define mock.On call -// - configPath +// - configPath string func (_e *MockInstallConfigManager_Expecter) LoadInstallConfigFromFile(configPath interface{}) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { return &MockInstallConfigManager_LoadInstallConfigFromFile_Call{Call: _e.mock.On("LoadInstallConfigFromFile", configPath)} } func (_c *MockInstallConfigManager_LoadInstallConfigFromFile_Call) Run(run func(configPath string)) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -365,14 +383,20 @@ type MockInstallConfigManager_LoadVaultFromFile_Call struct { } // LoadVaultFromFile is a helper method to define mock.On call -// - vaultPath +// - vaultPath string func (_e *MockInstallConfigManager_Expecter) LoadVaultFromFile(vaultPath interface{}) *MockInstallConfigManager_LoadVaultFromFile_Call { return &MockInstallConfigManager_LoadVaultFromFile_Call{Call: _e.mock.On("LoadVaultFromFile", vaultPath)} } func (_c *MockInstallConfigManager_LoadVaultFromFile_Call) Run(run func(vaultPath string)) *MockInstallConfigManager_LoadVaultFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -546,15 +570,26 @@ type MockInstallConfigManager_WriteInstallConfig_Call struct { } // WriteInstallConfig is a helper method to define mock.On call -// - configPath -// - withComments +// - configPath string +// - withComments bool func (_e *MockInstallConfigManager_Expecter) WriteInstallConfig(configPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteInstallConfig_Call { return &MockInstallConfigManager_WriteInstallConfig_Call{Call: _e.mock.On("WriteInstallConfig", configPath, withComments)} } func (_c *MockInstallConfigManager_WriteInstallConfig_Call) Run(run func(configPath string, withComments bool)) *MockInstallConfigManager_WriteInstallConfig_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -592,15 +627,26 @@ type MockInstallConfigManager_WriteVault_Call struct { } // WriteVault is a helper method to define mock.On call -// - vaultPath -// - withComments +// - vaultPath string +// - withComments bool func (_e *MockInstallConfigManager_Expecter) WriteVault(vaultPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteVault_Call { return &MockInstallConfigManager_WriteVault_Call{Call: _e.mock.On("WriteVault", vaultPath, withComments)} } func (_c *MockInstallConfigManager_WriteVault_Call) Run(run func(vaultPath string, withComments bool)) *MockInstallConfigManager_WriteVault_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -674,16 +720,32 @@ type MockK0sManager_Download_Call struct { } // Download is a helper method to define mock.On call -// - version -// - force -// - quiet +// - version string +// - force bool +// - quiet bool func (_e *MockK0sManager_Expecter) Download(version interface{}, force interface{}, quiet interface{}) *MockK0sManager_Download_Call { return &MockK0sManager_Download_Call{Call: _e.mock.On("Download", version, force, quiet)} } func (_c *MockK0sManager_Download_Call) Run(run func(version string, force bool, quiet bool)) *MockK0sManager_Download_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool), args[2].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + var arg2 bool + if args[2] != nil { + arg2 = args[2].(bool) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -774,16 +836,32 @@ type MockK0sManager_Install_Call struct { } // Install is a helper method to define mock.On call -// - configPath -// - k0sPath -// - force +// - configPath string +// - k0sPath string +// - force bool func (_e *MockK0sManager_Expecter) Install(configPath interface{}, k0sPath interface{}, force interface{}) *MockK0sManager_Install_Call { return &MockK0sManager_Install_Call{Call: _e.mock.On("Install", configPath, k0sPath, force)} } func (_c *MockK0sManager_Install_Call) Run(run func(configPath string, k0sPath string, force bool)) *MockK0sManager_Install_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 bool + if args[2] != nil { + arg2 = args[2].(bool) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -821,14 +899,20 @@ type MockK0sManager_Reset_Call struct { } // Reset is a helper method to define mock.On call -// - k0sPath +// - k0sPath string func (_e *MockK0sManager_Expecter) Reset(k0sPath interface{}) *MockK0sManager_Reset_Call { return &MockK0sManager_Reset_Call{Call: _e.mock.On("Reset", k0sPath)} } func (_c *MockK0sManager_Reset_Call) Run(run func(k0sPath string)) *MockK0sManager_Reset_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -893,14 +977,20 @@ type MockPackageManager_Extract_Call struct { } // Extract is a helper method to define mock.On call -// - force +// - force bool func (_e *MockPackageManager_Expecter) Extract(force interface{}) *MockPackageManager_Extract_Call { return &MockPackageManager_Extract_Call{Call: _e.mock.On("Extract", force)} } func (_c *MockPackageManager_Extract_Call) Run(run func(force bool)) *MockPackageManager_Extract_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(bool)) + var arg0 bool + if args[0] != nil { + arg0 = args[0].(bool) + } + run( + arg0, + ) }) return _c } @@ -938,15 +1028,26 @@ type MockPackageManager_ExtractDependency_Call struct { } // ExtractDependency is a helper method to define mock.On call -// - file -// - force +// - file string +// - force bool func (_e *MockPackageManager_Expecter) ExtractDependency(file interface{}, force interface{}) *MockPackageManager_ExtractDependency_Call { return &MockPackageManager_ExtractDependency_Call{Call: _e.mock.On("ExtractDependency", file, force)} } func (_c *MockPackageManager_ExtractDependency_Call) Run(run func(file string, force bool)) *MockPackageManager_ExtractDependency_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -993,14 +1094,20 @@ type MockPackageManager_ExtractOciImageIndex_Call struct { } // ExtractOciImageIndex is a helper method to define mock.On call -// - imagefile +// - imagefile string func (_e *MockPackageManager_Expecter) ExtractOciImageIndex(imagefile interface{}) *MockPackageManager_ExtractOciImageIndex_Call { return &MockPackageManager_ExtractOciImageIndex_Call{Call: _e.mock.On("ExtractOciImageIndex", imagefile)} } func (_c *MockPackageManager_ExtractOciImageIndex_Call) Run(run func(imagefile string)) *MockPackageManager_ExtractOciImageIndex_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -1093,14 +1200,20 @@ type MockPackageManager_GetBaseimageName_Call struct { } // GetBaseimageName is a helper method to define mock.On call -// - baseimage +// - baseimage string func (_e *MockPackageManager_Expecter) GetBaseimageName(baseimage interface{}) *MockPackageManager_GetBaseimageName_Call { return &MockPackageManager_GetBaseimageName_Call{Call: _e.mock.On("GetBaseimageName", baseimage)} } func (_c *MockPackageManager_GetBaseimageName_Call) Run(run func(baseimage string)) *MockPackageManager_GetBaseimageName_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -1147,15 +1260,26 @@ type MockPackageManager_GetBaseimagePath_Call struct { } // GetBaseimagePath is a helper method to define mock.On call -// - baseimage -// - force +// - baseimage string +// - force bool func (_e *MockPackageManager_Expecter) GetBaseimagePath(baseimage interface{}, force interface{}) *MockPackageManager_GetBaseimagePath_Call { return &MockPackageManager_GetBaseimagePath_Call{Call: _e.mock.On("GetBaseimagePath", baseimage, force)} } func (_c *MockPackageManager_GetBaseimagePath_Call) Run(run func(baseimage string, force bool)) *MockPackageManager_GetBaseimagePath_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -1246,14 +1370,20 @@ type MockPackageManager_GetDependencyPath_Call struct { } // GetDependencyPath is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockPackageManager_Expecter) GetDependencyPath(filename interface{}) *MockPackageManager_GetDependencyPath_Call { return &MockPackageManager_GetDependencyPath_Call{Call: _e.mock.On("GetDependencyPath", filename)} } func (_c *MockPackageManager_GetDependencyPath_Call) Run(run func(filename string)) *MockPackageManager_GetDependencyPath_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } diff --git a/internal/portal/mocks.go b/internal/portal/mocks.go index 83372369..c162a3ee 100644 --- a/internal/portal/mocks.go +++ b/internal/portal/mocks.go @@ -61,16 +61,32 @@ type MockHttp_Download_Call struct { } // Download is a helper method to define mock.On call -// - url -// - file -// - quiet +// - url string +// - file io.Writer +// - quiet bool func (_e *MockHttp_Expecter) Download(url interface{}, file interface{}, quiet interface{}) *MockHttp_Download_Call { return &MockHttp_Download_Call{Call: _e.mock.On("Download", url, file, quiet)} } func (_c *MockHttp_Download_Call) Run(run func(url string, file io.Writer, quiet bool)) *MockHttp_Download_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(io.Writer), args[2].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 io.Writer + if args[1] != nil { + arg1 = args[1].(io.Writer) + } + var arg2 bool + if args[2] != nil { + arg2 = args[2].(bool) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -119,14 +135,20 @@ type MockHttp_Get_Call struct { } // Get is a helper method to define mock.On call -// - url +// - url string func (_e *MockHttp_Expecter) Get(url interface{}) *MockHttp_Get_Call { return &MockHttp_Get_Call{Call: _e.mock.On("Get", url)} } func (_c *MockHttp_Get_Call) Run(run func(url string)) *MockHttp_Get_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -175,16 +197,32 @@ type MockHttp_Request_Call struct { } // Request is a helper method to define mock.On call -// - url -// - method -// - body +// - url string +// - method string +// - body io.Reader func (_e *MockHttp_Expecter) Request(url interface{}, method interface{}, body interface{}) *MockHttp_Request_Call { return &MockHttp_Request_Call{Call: _e.mock.On("Request", url, method, body)} } func (_c *MockHttp_Request_Call) Run(run func(url string, method string, body io.Reader)) *MockHttp_Request_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(io.Reader)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 io.Reader + if args[2] != nil { + arg2 = args[2].(io.Reader) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -249,18 +287,44 @@ type MockPortal_DownloadBuildArtifact_Call struct { } // DownloadBuildArtifact is a helper method to define mock.On call -// - product -// - build -// - file -// - startByte -// - quiet +// - product Product +// - build Build +// - file io.Writer +// - startByte int +// - quiet bool func (_e *MockPortal_Expecter) DownloadBuildArtifact(product interface{}, build interface{}, file interface{}, startByte interface{}, quiet interface{}) *MockPortal_DownloadBuildArtifact_Call { return &MockPortal_DownloadBuildArtifact_Call{Call: _e.mock.On("DownloadBuildArtifact", product, build, file, startByte, quiet)} } func (_c *MockPortal_DownloadBuildArtifact_Call) Run(run func(product Product, build Build, file io.Writer, startByte int, quiet bool)) *MockPortal_DownloadBuildArtifact_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(Product), args[1].(Build), args[2].(io.Writer), args[3].(int), args[4].(bool)) + var arg0 Product + if args[0] != nil { + arg0 = args[0].(Product) + } + var arg1 Build + if args[1] != nil { + arg1 = args[1].(Build) + } + var arg2 io.Writer + if args[2] != nil { + arg2 = args[2].(io.Writer) + } + var arg3 int + if args[3] != nil { + arg3 = args[3].(int) + } + var arg4 bool + if args[4] != nil { + arg4 = args[4].(bool) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) }) return _c } @@ -307,14 +371,20 @@ type MockPortal_GetApiKeyId_Call struct { } // GetApiKeyId is a helper method to define mock.On call -// - oldKey +// - oldKey string func (_e *MockPortal_Expecter) GetApiKeyId(oldKey interface{}) *MockPortal_GetApiKeyId_Call { return &MockPortal_GetApiKeyId_Call{Call: _e.mock.On("GetApiKeyId", oldKey)} } func (_c *MockPortal_GetApiKeyId_Call) Run(run func(oldKey string)) *MockPortal_GetApiKeyId_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -361,16 +431,32 @@ type MockPortal_GetBuild_Call struct { } // GetBuild is a helper method to define mock.On call -// - product -// - version -// - hash +// - product Product +// - version string +// - hash string func (_e *MockPortal_Expecter) GetBuild(product interface{}, version interface{}, hash interface{}) *MockPortal_GetBuild_Call { return &MockPortal_GetBuild_Call{Call: _e.mock.On("GetBuild", product, version, hash)} } func (_c *MockPortal_GetBuild_Call) Run(run func(product Product, version string, hash string)) *MockPortal_GetBuild_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(Product), args[1].(string), args[2].(string)) + var arg0 Product + if args[0] != nil { + arg0 = args[0].(Product) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -472,14 +558,20 @@ type MockPortal_ListBuilds_Call struct { } // ListBuilds is a helper method to define mock.On call -// - product +// - product Product func (_e *MockPortal_Expecter) ListBuilds(product interface{}) *MockPortal_ListBuilds_Call { return &MockPortal_ListBuilds_Call{Call: _e.mock.On("ListBuilds", product)} } func (_c *MockPortal_ListBuilds_Call) Run(run func(product Product)) *MockPortal_ListBuilds_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(Product)) + var arg0 Product + if args[0] != nil { + arg0 = args[0].(Product) + } + run( + arg0, + ) }) return _c } @@ -528,17 +620,38 @@ type MockPortal_RegisterAPIKey_Call struct { } // RegisterAPIKey is a helper method to define mock.On call -// - owner -// - organization -// - role -// - expiresAt +// - owner string +// - organization string +// - role string +// - expiresAt time.Time func (_e *MockPortal_Expecter) RegisterAPIKey(owner interface{}, organization interface{}, role interface{}, expiresAt interface{}) *MockPortal_RegisterAPIKey_Call { return &MockPortal_RegisterAPIKey_Call{Call: _e.mock.On("RegisterAPIKey", owner, organization, role, expiresAt)} } func (_c *MockPortal_RegisterAPIKey_Call) Run(run func(owner string, organization string, role string, expiresAt time.Time)) *MockPortal_RegisterAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(string), args[3].(time.Time)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 time.Time + if args[3] != nil { + arg3 = args[3].(time.Time) + } + run( + arg0, + arg1, + arg2, + arg3, + ) }) return _c } @@ -576,14 +689,20 @@ type MockPortal_RevokeAPIKey_Call struct { } // RevokeAPIKey is a helper method to define mock.On call -// - key +// - key string func (_e *MockPortal_Expecter) RevokeAPIKey(key interface{}) *MockPortal_RevokeAPIKey_Call { return &MockPortal_RevokeAPIKey_Call{Call: _e.mock.On("RevokeAPIKey", key)} } func (_c *MockPortal_RevokeAPIKey_Call) Run(run func(key string)) *MockPortal_RevokeAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -621,15 +740,26 @@ type MockPortal_UpdateAPIKey_Call struct { } // UpdateAPIKey is a helper method to define mock.On call -// - key -// - expiresAt +// - key string +// - expiresAt time.Time func (_e *MockPortal_Expecter) UpdateAPIKey(key interface{}, expiresAt interface{}) *MockPortal_UpdateAPIKey_Call { return &MockPortal_UpdateAPIKey_Call{Call: _e.mock.On("UpdateAPIKey", key, expiresAt)} } func (_c *MockPortal_UpdateAPIKey_Call) Run(run func(key string, expiresAt time.Time)) *MockPortal_UpdateAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(time.Time)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 time.Time + if args[1] != nil { + arg1 = args[1].(time.Time) + } + run( + arg0, + arg1, + ) }) return _c } @@ -667,15 +797,26 @@ type MockPortal_VerifyBuildArtifactDownload_Call struct { } // VerifyBuildArtifactDownload is a helper method to define mock.On call -// - file -// - download +// - file io.Reader +// - download Build func (_e *MockPortal_Expecter) VerifyBuildArtifactDownload(file interface{}, download interface{}) *MockPortal_VerifyBuildArtifactDownload_Call { return &MockPortal_VerifyBuildArtifactDownload_Call{Call: _e.mock.On("VerifyBuildArtifactDownload", file, download)} } func (_c *MockPortal_VerifyBuildArtifactDownload_Call) Run(run func(file io.Reader, download Build)) *MockPortal_VerifyBuildArtifactDownload_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(io.Reader), args[1].(Build)) + var arg0 io.Reader + if args[0] != nil { + arg0 = args[0].(io.Reader) + } + var arg1 Build + if args[1] != nil { + arg1 = args[1].(Build) + } + run( + arg0, + arg1, + ) }) return _c } @@ -751,14 +892,20 @@ type MockHttpClient_Do_Call struct { } // Do is a helper method to define mock.On call -// - request +// - request *http.Request func (_e *MockHttpClient_Expecter) Do(request interface{}) *MockHttpClient_Do_Call { return &MockHttpClient_Do_Call{Call: _e.mock.On("Do", request)} } func (_c *MockHttpClient_Do_Call) Run(run func(request *http.Request)) *MockHttpClient_Do_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*http.Request)) + var arg0 *http.Request + if args[0] != nil { + arg0 = args[0].(*http.Request) + } + run( + arg0, + ) }) return _c } diff --git a/internal/system/mocks.go b/internal/system/mocks.go index cf8e9292..ee80bc69 100644 --- a/internal/system/mocks.go +++ b/internal/system/mocks.go @@ -58,16 +58,32 @@ type MockImageManager_BuildImage_Call struct { } // BuildImage is a helper method to define mock.On call -// - dockerfile -// - tag -// - buildContext +// - dockerfile string +// - tag string +// - buildContext string func (_e *MockImageManager_Expecter) BuildImage(dockerfile interface{}, tag interface{}, buildContext interface{}) *MockImageManager_BuildImage_Call { return &MockImageManager_BuildImage_Call{Call: _e.mock.On("BuildImage", dockerfile, tag, buildContext)} } func (_c *MockImageManager_BuildImage_Call) Run(run func(dockerfile string, tag string, buildContext string)) *MockImageManager_BuildImage_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -105,14 +121,20 @@ type MockImageManager_LoadImage_Call struct { } // LoadImage is a helper method to define mock.On call -// - imageTarPath +// - imageTarPath string func (_e *MockImageManager_Expecter) LoadImage(imageTarPath interface{}) *MockImageManager_LoadImage_Call { return &MockImageManager_LoadImage_Call{Call: _e.mock.On("LoadImage", imageTarPath)} } func (_c *MockImageManager_LoadImage_Call) Run(run func(imageTarPath string)) *MockImageManager_LoadImage_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -150,14 +172,20 @@ type MockImageManager_PushImage_Call struct { } // PushImage is a helper method to define mock.On call -// - tag +// - tag string func (_e *MockImageManager_Expecter) PushImage(tag interface{}) *MockImageManager_PushImage_Call { return &MockImageManager_PushImage_Call{Call: _e.mock.On("PushImage", tag)} } func (_c *MockImageManager_PushImage_Call) Run(run func(tag string)) *MockImageManager_PushImage_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } diff --git a/internal/util/mocks.go b/internal/util/mocks.go index 394f6b82..abfdd4f8 100644 --- a/internal/util/mocks.go +++ b/internal/util/mocks.go @@ -70,15 +70,26 @@ type MockDockerfileManager_UpdateFromStatement_Call struct { } // UpdateFromStatement is a helper method to define mock.On call -// - dockerfile -// - baseImage +// - dockerfile io.Reader +// - baseImage string func (_e *MockDockerfileManager_Expecter) UpdateFromStatement(dockerfile interface{}, baseImage interface{}) *MockDockerfileManager_UpdateFromStatement_Call { return &MockDockerfileManager_UpdateFromStatement_Call{Call: _e.mock.On("UpdateFromStatement", dockerfile, baseImage)} } func (_c *MockDockerfileManager_UpdateFromStatement_Call) Run(run func(dockerfile io.Reader, baseImage string)) *MockDockerfileManager_UpdateFromStatement_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(io.Reader), args[1].(string)) + var arg0 io.Reader + if args[0] != nil { + arg0 = args[0].(io.Reader) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } @@ -154,14 +165,20 @@ type MockFileIO_Create_Call struct { } // Create is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) Create(filename interface{}) *MockFileIO_Create_Call { return &MockFileIO_Create_Call{Call: _e.mock.On("Create", filename)} } func (_c *MockFileIO_Create_Call) Run(run func(filename string)) *MockFileIO_Create_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -199,16 +216,32 @@ type MockFileIO_CreateAndWrite_Call struct { } // CreateAndWrite is a helper method to define mock.On call -// - filePath -// - data -// - fileType +// - filePath string +// - data []byte +// - fileType string func (_e *MockFileIO_Expecter) CreateAndWrite(filePath interface{}, data interface{}, fileType interface{}) *MockFileIO_CreateAndWrite_Call { return &MockFileIO_CreateAndWrite_Call{Call: _e.mock.On("CreateAndWrite", filePath, data, fileType)} } func (_c *MockFileIO_CreateAndWrite_Call) Run(run func(filePath string, data []byte, fileType string)) *MockFileIO_CreateAndWrite_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].([]byte), args[2].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 []byte + if args[1] != nil { + arg1 = args[1].([]byte) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -246,14 +279,20 @@ type MockFileIO_Exists_Call struct { } // Exists is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) Exists(filename interface{}) *MockFileIO_Exists_Call { return &MockFileIO_Exists_Call{Call: _e.mock.On("Exists", filename)} } func (_c *MockFileIO_Exists_Call) Run(run func(filename string)) *MockFileIO_Exists_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -300,14 +339,20 @@ type MockFileIO_IsDirectory_Call struct { } // IsDirectory is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) IsDirectory(filename interface{}) *MockFileIO_IsDirectory_Call { return &MockFileIO_IsDirectory_Call{Call: _e.mock.On("IsDirectory", filename)} } func (_c *MockFileIO_IsDirectory_Call) Run(run func(filename string)) *MockFileIO_IsDirectory_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -345,15 +390,26 @@ type MockFileIO_MkdirAll_Call struct { } // MkdirAll is a helper method to define mock.On call -// - path -// - perm +// - path string +// - perm os.FileMode func (_e *MockFileIO_Expecter) MkdirAll(path interface{}, perm interface{}) *MockFileIO_MkdirAll_Call { return &MockFileIO_MkdirAll_Call{Call: _e.mock.On("MkdirAll", path, perm)} } func (_c *MockFileIO_MkdirAll_Call) Run(run func(path string, perm os.FileMode)) *MockFileIO_MkdirAll_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(os.FileMode)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 os.FileMode + if args[1] != nil { + arg1 = args[1].(os.FileMode) + } + run( + arg0, + arg1, + ) }) return _c } @@ -402,14 +458,20 @@ type MockFileIO_Open_Call struct { } // Open is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) Open(filename interface{}) *MockFileIO_Open_Call { return &MockFileIO_Open_Call{Call: _e.mock.On("Open", filename)} } func (_c *MockFileIO_Open_Call) Run(run func(filename string)) *MockFileIO_Open_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -458,14 +520,20 @@ type MockFileIO_OpenAppend_Call struct { } // OpenAppend is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) OpenAppend(filename interface{}) *MockFileIO_OpenAppend_Call { return &MockFileIO_OpenAppend_Call{Call: _e.mock.On("OpenAppend", filename)} } func (_c *MockFileIO_OpenAppend_Call) Run(run func(filename string)) *MockFileIO_OpenAppend_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -514,16 +582,32 @@ type MockFileIO_OpenFile_Call struct { } // OpenFile is a helper method to define mock.On call -// - name -// - flag -// - perm +// - name string +// - flag int +// - perm os.FileMode func (_e *MockFileIO_Expecter) OpenFile(name interface{}, flag interface{}, perm interface{}) *MockFileIO_OpenFile_Call { return &MockFileIO_OpenFile_Call{Call: _e.mock.On("OpenFile", name, flag, perm)} } func (_c *MockFileIO_OpenFile_Call) Run(run func(name string, flag int, perm os.FileMode)) *MockFileIO_OpenFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(int), args[2].(os.FileMode)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 int + if args[1] != nil { + arg1 = args[1].(int) + } + var arg2 os.FileMode + if args[2] != nil { + arg2 = args[2].(os.FileMode) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -572,14 +656,20 @@ type MockFileIO_ReadDir_Call struct { } // ReadDir is a helper method to define mock.On call -// - dirname +// - dirname string func (_e *MockFileIO_Expecter) ReadDir(dirname interface{}) *MockFileIO_ReadDir_Call { return &MockFileIO_ReadDir_Call{Call: _e.mock.On("ReadDir", dirname)} } func (_c *MockFileIO_ReadDir_Call) Run(run func(dirname string)) *MockFileIO_ReadDir_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -628,14 +718,20 @@ type MockFileIO_ReadFile_Call struct { } // ReadFile is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) ReadFile(filename interface{}) *MockFileIO_ReadFile_Call { return &MockFileIO_ReadFile_Call{Call: _e.mock.On("ReadFile", filename)} } func (_c *MockFileIO_ReadFile_Call) Run(run func(filename string)) *MockFileIO_ReadFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -673,16 +769,32 @@ type MockFileIO_WriteFile_Call struct { } // WriteFile is a helper method to define mock.On call -// - filename -// - data -// - perm +// - filename string +// - data []byte +// - perm os.FileMode func (_e *MockFileIO_Expecter) WriteFile(filename interface{}, data interface{}, perm interface{}) *MockFileIO_WriteFile_Call { return &MockFileIO_WriteFile_Call{Call: _e.mock.On("WriteFile", filename, data, perm)} } func (_c *MockFileIO_WriteFile_Call) Run(run func(filename string, data []byte, perm os.FileMode)) *MockFileIO_WriteFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].([]byte), args[2].(os.FileMode)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 []byte + if args[1] != nil { + arg1 = args[1].([]byte) + } + var arg2 os.FileMode + if args[2] != nil { + arg2 = args[2].(os.FileMode) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -812,8 +924,8 @@ type MockTableWriter_AppendHeader_Call struct { } // AppendHeader is a helper method to define mock.On call -// - row -// - configs +// - row table.Row +// - configs ...table.RowConfig func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...interface{}) *MockTableWriter_AppendHeader_Call { return &MockTableWriter_AppendHeader_Call{Call: _e.mock.On("AppendHeader", append([]interface{}{row}, configs...)...)} @@ -821,13 +933,20 @@ func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...int func (_c *MockTableWriter_AppendHeader_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendHeader_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]table.RowConfig, len(args)-1) - for i, a := range args[1:] { - if a != nil { - variadicArgs[i] = a.(table.RowConfig) - } + var arg0 table.Row + if args[0] != nil { + arg0 = args[0].(table.Row) } - run(args[0].(table.Row), variadicArgs...) + var arg1 []table.RowConfig + var variadicArgs []table.RowConfig + if len(args) > 1 { + variadicArgs = args[1].([]table.RowConfig) + } + arg1 = variadicArgs + run( + arg0, + arg1..., + ) }) return _c } @@ -859,8 +978,8 @@ type MockTableWriter_AppendRow_Call struct { } // AppendRow is a helper method to define mock.On call -// - row -// - configs +// - row table.Row +// - configs ...table.RowConfig func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interface{}) *MockTableWriter_AppendRow_Call { return &MockTableWriter_AppendRow_Call{Call: _e.mock.On("AppendRow", append([]interface{}{row}, configs...)...)} @@ -868,13 +987,20 @@ func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interf func (_c *MockTableWriter_AppendRow_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendRow_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]table.RowConfig, len(args)-1) - for i, a := range args[1:] { - if a != nil { - variadicArgs[i] = a.(table.RowConfig) - } + var arg0 table.Row + if args[0] != nil { + arg0 = args[0].(table.Row) + } + var arg1 []table.RowConfig + var variadicArgs []table.RowConfig + if len(args) > 1 { + variadicArgs = args[1].([]table.RowConfig) } - run(args[0].(table.Row), variadicArgs...) + arg1 = variadicArgs + run( + arg0, + arg1..., + ) }) return _c } From 3035990d6a48bb052f12d45ec3f156c3fb2ce50a Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:59:37 +0100 Subject: [PATCH 03/65] ref: use deferred functions for cleanup in k0s and node installers --- cli/cmd/install_k0s.go | 2 +- internal/installer/k0s.go | 4 ++-- internal/installer/node/node.go | 30 +++++++++--------------------- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index aed5d79d..220654ee 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -150,7 +150,7 @@ func (c *InstallK0sCmd) InstallK0sFromInstallConfig(pm installer.PackageManager, if err := os.WriteFile(tmpK0sConfigPath, k0sConfigData, 0644); err != nil { return fmt.Errorf("failed to write k0s config: %w", err) } - defer os.Remove(tmpK0sConfigPath) + defer func() { _ = os.Remove(tmpK0sConfigPath) }() log.Printf("Generated k0s configuration at %s", tmpK0sConfigPath) diff --git a/internal/installer/k0s.go b/internal/installer/k0s.go index f497c1c9..422268e5 100644 --- a/internal/installer/k0s.go +++ b/internal/installer/k0s.go @@ -125,7 +125,7 @@ func (k *K0s) Install(configPath string, k0sPath string, force bool) error { log.Printf("Warning: failed to filter config, using original: %v", err) } else { configPath = filteredConfigPath - defer os.Remove(filteredConfigPath) // Clean up temp file after use + defer func() { _ = os.Remove(filteredConfigPath) }() // Clean up temp file after use } args = append(args, "--config", configPath) } else { @@ -206,7 +206,7 @@ func (k *K0s) filterConfigForK0s(configPath string) (string, error) { if err != nil { return "", fmt.Errorf("failed to create temp config: %w", err) } - defer tmpFile.Close() + defer func() { _ = tmpFile.Close() }() if _, err := tmpFile.Write(filteredData); err != nil { return "", fmt.Errorf("failed to write temp config: %w", err) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 20123606..438d80b7 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -172,12 +172,12 @@ func (n *NodeManager) RunSSHCommand(jumpboxIp string, ip string, username string if err != nil { return fmt.Errorf("failed to get client: %w", err) } - defer client.Close() + defer func() { _ = client.Close() }() session, err := client.NewSession() if err != nil { return fmt.Errorf("failed to create session on jumpbox: %v", err) } - defer session.Close() + defer func() { _ = session.Close() }() if err := n.forwardAgent(client, session); err != nil { fmt.Printf(" Warning: Agent forwarding setup failed on session: %v\n", err) @@ -271,19 +271,19 @@ func (n *NodeManager) CopyFile(jumpboxIp string, ip string, username string, src if err != nil { return fmt.Errorf("failed to get SSH client: %v", err) } - defer client.Close() + defer func() { _ = client.Close() }() srcFile, err := n.FileIO.Open(src) if err != nil { return fmt.Errorf("failed to open source file %s: %v", src, err) } - defer srcFile.Close() + defer func() { _ = srcFile.Close() }() dstFile, err := client.Create(dst) if err != nil { return fmt.Errorf("failed to create destination file %s: %v", dst, err) } - defer dstFile.Close() + defer func() { _ = dstFile.Close() }() _, err = dstFile.ReadFrom(srcFile) if err != nil { @@ -296,10 +296,7 @@ func (n *NodeManager) CopyFile(jumpboxIp string, ip string, username string, src func (n *Node) HasCommand(nm *NodeManager, command string) bool { checkCommand := fmt.Sprintf("command -v %s >/dev/null 2>&1", command) err := nm.RunSSHCommand("", n.ExternalIP, "root", checkCommand) - if err != nil { - return false - } - return true + return err == nil } func (n *Node) InstallOms(nm *NodeManager) error { @@ -329,10 +326,7 @@ func (n *Node) CopyFile(nm *NodeManager, src string, dst string) error { func (n *Node) HasAcceptEnvConfigured(jumpbox *Node, nm *NodeManager) bool { checkCommand := "sudo grep -E '^AcceptEnv OMS_PORTAL_API_KEY' /etc/ssh/sshd_config >/dev/null 2>&1" err := n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommand) - if err != nil { - return false - } - return true + return err == nil } func (n *Node) ConfigureAcceptEnv(jumpbox *Node, nm *NodeManager) error { @@ -357,19 +351,13 @@ func (n *Node) HasRootLoginEnabled(jumpbox *Node, nm *NodeManager) bool { } checkCommandAuthorizedKeys := "sudo grep -E '^no-port-forwarding' /root/.ssh/authorized_keys >/dev/null 2>&1" err = n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommandAuthorizedKeys) - if err == nil { - return false - } - return true + return err != nil } func (n *Node) HasFile(jumpbox *Node, nm *NodeManager, filePath string) bool { checkCommand := fmt.Sprintf("test -f '%s'", filePath) err := n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommand) - if err != nil { - return false - } - return true + return err == nil } func (n *Node) RunSSHCommand(jumpbox *Node, nm *NodeManager, username string, command string) error { From ad86e8e0fdf8e1602849c0d1d67d905bbf1fa9b6 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:00:55 +0000 Subject: [PATCH 04/65] chore(docs): Auto-update docs and licenses Signed-off-by: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> --- internal/tmpl/NOTICE | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/internal/tmpl/NOTICE b/internal/tmpl/NOTICE index d7e513d3..ed0df755 100644 --- a/internal/tmpl/NOTICE +++ b/internal/tmpl/NOTICE @@ -23,9 +23,9 @@ License URL: https://github.com/clipperhouse/uax29/blob/v2.3.0/LICENSE ---------- Module: github.com/codesphere-cloud/cs-go/pkg/io -Version: v0.14.1 +Version: v0.15.0 License: Apache-2.0 -License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.14.1/LICENSE +License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.15.0/LICENSE ---------- Module: github.com/codesphere-cloud/oms/internal/tmpl @@ -77,9 +77,15 @@ License URL: https://github.com/inconshreveable/go-update/blob/8152e7eb6ccf/inte ---------- Module: github.com/jedib0t/go-pretty/v6 -Version: v6.7.5 +Version: v6.7.7 License: MIT -License URL: https://github.com/jedib0t/go-pretty/blob/v6.7.5/LICENSE +License URL: https://github.com/jedib0t/go-pretty/blob/v6.7.7/LICENSE + +---------- +Module: github.com/kr/fs +Version: v0.1.0 +License: BSD-3-Clause +License URL: https://github.com/kr/fs/blob/v0.1.0/LICENSE ---------- Module: github.com/mattn/go-runewidth @@ -87,6 +93,12 @@ Version: v0.0.19 License: MIT License URL: https://github.com/mattn/go-runewidth/blob/v0.0.19/LICENSE +---------- +Module: github.com/pkg/sftp +Version: v1.13.10 +License: BSD-2-Clause +License URL: https://github.com/pkg/sftp/blob/v1.13.10/LICENSE + ---------- Module: github.com/pmezard/go-difflib/difflib Version: v1.0.1-0.20181226105442-5d4384ee4fb2 @@ -155,9 +167,9 @@ License URL: https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE ---------- Module: golang.org/x/crypto -Version: v0.45.0 +Version: v0.46.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/crypto/+/v0.45.0:LICENSE +License URL: https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE ---------- Module: golang.org/x/oauth2 @@ -165,11 +177,23 @@ Version: v0.33.0 License: BSD-3-Clause License URL: https://cs.opensource.google/go/x/oauth2/+/v0.33.0:LICENSE +---------- +Module: golang.org/x/sys/unix +Version: v0.39.0 +License: BSD-3-Clause +License URL: https://cs.opensource.google/go/x/sys/+/v0.39.0:LICENSE + +---------- +Module: golang.org/x/term +Version: v0.38.0 +License: BSD-3-Clause +License URL: https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE + ---------- Module: golang.org/x/text -Version: v0.31.0 +Version: v0.32.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/text/+/v0.31.0:LICENSE +License URL: https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE ---------- Module: gopkg.in/yaml.v3 From c79fc3dd57207430c45737994ec776072d3fbd76 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Mon, 15 Dec 2025 10:09:09 +0100 Subject: [PATCH 05/65] ref: refactor install-config integration tests for improved clarity and structure --- cli/cmd/install_k0s_integration_test.go | 473 +++++++++++------------- 1 file changed, 215 insertions(+), 258 deletions(-) diff --git a/cli/cmd/install_k0s_integration_test.go b/cli/cmd/install_k0s_integration_test.go index 8823aa4c..28a5523c 100644 --- a/cli/cmd/install_k0s_integration_test.go +++ b/cli/cmd/install_k0s_integration_test.go @@ -7,6 +7,7 @@ package cmd_test import ( + "fmt" "os" "path/filepath" @@ -40,45 +41,45 @@ var _ = Describe("K0s Install-Config Integration", func() { } }) - Describe("Config Generation Workflow", func() { - It("should generate valid k0s config from install-config", func() { - // Create a minimal install-config using RootConfig - installConfig := &files.RootConfig{ - Datacenter: files.DatacenterConfig{ - ID: 1, - Name: "test-dc", - City: "Test City", - CountryCode: "US", + createBaseConfig := func(name string, ip string) *files.RootConfig { + return &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: name, + City: "Test City", + CountryCode: "US", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: ip}, }, - Kubernetes: files.KubernetesConfig{ - ManagedByCodesphere: true, - ControlPlanes: []files.K8sNode{ - { - IPAddress: "192.168.1.100", - }, - }, - APIServerHost: "api.test.example.com", + APIServerHost: "api.test.example.com", + }, + Codesphere: files.CodesphereConfig{ + Domain: "test.example.com", + PublicIP: ip, + DeployConfig: files.DeployConfig{ + Images: map[string]files.ImageConfig{}, }, - Codesphere: files.CodesphereConfig{ - Domain: "test.example.com", - PublicIP: "192.168.1.100", - DeployConfig: files.DeployConfig{ - Images: map[string]files.ImageConfig{}, - }, - Plans: files.PlansConfig{ - HostingPlans: map[int]files.HostingPlan{}, - WorkspacePlans: map[int]files.WorkspacePlan{}, - }, + Plans: files.PlansConfig{ + HostingPlans: map[int]files.HostingPlan{}, + WorkspacePlans: map[int]files.WorkspacePlan{}, }, - } + }, + } + } - // Write install-config to file + Describe("Complete Workflow", func() { + It("should generate valid k0s config from install-config file", func() { + installConfig := createBaseConfig("test-dc", "192.168.1.100") + + // Write and load install-config configData, err := yaml.Marshal(installConfig) Expect(err).NotTo(HaveOccurred()) err = os.WriteFile(configPath, configData, 0644) Expect(err).NotTo(HaveOccurred()) - // Load the config back using InstallConfigManager icg := installer.NewInstallConfigManager() err = icg.LoadInstallConfigFromFile(configPath) Expect(err).NotTo(HaveOccurred()) @@ -96,56 +97,36 @@ var _ = Describe("K0s Install-Config Integration", func() { Expect(k0sConfig.APIVersion).To(Equal("k0s.k0sproject.io/v1beta1")) Expect(k0sConfig.Kind).To(Equal("ClusterConfig")) Expect(k0sConfig.Metadata.Name).To(Equal("codesphere-test-dc")) - Expect(k0sConfig.Spec.API).NotTo(BeNil()) Expect(k0sConfig.Spec.API.Address).To(Equal("192.168.1.100")) Expect(k0sConfig.Spec.API.ExternalAddress).To(Equal("api.test.example.com")) - // Write k0s config to file + // Write k0s config to file and verify k0sData, err := k0sConfig.Marshal() Expect(err).NotTo(HaveOccurred()) err = os.WriteFile(k0sConfigOut, k0sData, 0644) Expect(err).NotTo(HaveOccurred()) - // Verify file was created and is valid YAML Expect(k0sConfigOut).To(BeAnExistingFile()) data, err := os.ReadFile(k0sConfigOut) Expect(err).NotTo(HaveOccurred()) - Expect(len(data)).To(BeNumerically(">", 0)) - // Verify we can unmarshal it back var verifyConfig installer.K0sConfig err = yaml.Unmarshal(data, &verifyConfig) Expect(err).NotTo(HaveOccurred()) Expect(verifyConfig.APIVersion).To(Equal("k0s.k0sproject.io/v1beta1")) + Expect(verifyConfig.Metadata.Name).To(Equal("codesphere-test-dc")) }) + }) + Describe("Configuration Features", func() { It("should handle multi-control-plane configuration", func() { - installConfig := &files.RootConfig{ - Datacenter: files.DatacenterConfig{ - ID: 1, - Name: "multi-dc", - }, - Kubernetes: files.KubernetesConfig{ - ManagedByCodesphere: true, - ControlPlanes: []files.K8sNode{ - {IPAddress: "10.0.0.10"}, - {IPAddress: "10.0.0.11"}, - {IPAddress: "10.0.0.12"}, - }, - APIServerHost: "api.cluster.test", - }, - Codesphere: files.CodesphereConfig{ - Domain: "cluster.test", - PublicIP: "10.0.0.10", - DeployConfig: files.DeployConfig{ - Images: map[string]files.ImageConfig{}, - }, - Plans: files.PlansConfig{ - HostingPlans: map[int]files.HostingPlan{}, - WorkspacePlans: map[int]files.WorkspacePlan{}, - }, - }, + installConfig := createBaseConfig("multi-dc", "10.0.0.10") + installConfig.Kubernetes.ControlPlanes = []files.K8sNode{ + {IPAddress: "10.0.0.10"}, + {IPAddress: "10.0.0.11"}, + {IPAddress: "10.0.0.12"}, } + installConfig.Kubernetes.APIServerHost = "api.cluster.test" k0sConfig, err := installer.GenerateK0sConfig(installConfig) Expect(err).NotTo(HaveOccurred()) @@ -159,93 +140,132 @@ var _ = Describe("K0s Install-Config Integration", func() { Expect(k0sConfig.Spec.API.SANs).To(ContainElement("api.cluster.test")) }) - It("should preserve network configuration", func() { - installConfig := &files.RootConfig{ - Datacenter: files.DatacenterConfig{ - ID: 1, - Name: "network-test", - }, - Kubernetes: files.KubernetesConfig{ - ManagedByCodesphere: true, - ControlPlanes: []files.K8sNode{ - {IPAddress: "192.168.1.100"}, - }, - PodCIDR: "10.244.0.0/16", - ServiceCIDR: "10.96.0.0/12", - }, - Codesphere: files.CodesphereConfig{ - Domain: "network.test", - PublicIP: "192.168.1.100", - DeployConfig: files.DeployConfig{ - Images: map[string]files.ImageConfig{}, - }, - Plans: files.PlansConfig{ - HostingPlans: map[int]files.HostingPlan{}, - WorkspacePlans: map[int]files.WorkspacePlan{}, - }, - }, - } + It("should preserve custom network configuration", func() { + installConfig := createBaseConfig("network-test", "192.168.1.100") + installConfig.Kubernetes.PodCIDR = "10.244.0.0/16" + installConfig.Kubernetes.ServiceCIDR = "10.96.0.0/12" k0sConfig, err := installer.GenerateK0sConfig(installConfig) Expect(err).NotTo(HaveOccurred()) - // Verify network settings Expect(k0sConfig.Spec.Network).NotTo(BeNil()) Expect(k0sConfig.Spec.Network.PodCIDR).To(Equal("10.244.0.0/16")) Expect(k0sConfig.Spec.Network.ServiceCIDR).To(Equal("10.96.0.0/12")) }) - It("should handle storage configuration", func() { - installConfig := &files.RootConfig{ - Datacenter: files.DatacenterConfig{ - ID: 1, - Name: "storage-test", - }, - Kubernetes: files.KubernetesConfig{ - ManagedByCodesphere: true, - ControlPlanes: []files.K8sNode{ - {IPAddress: "192.168.1.100"}, - }, - }, - Codesphere: files.CodesphereConfig{ - Domain: "storage.test", - PublicIP: "192.168.1.100", - DeployConfig: files.DeployConfig{ - Images: map[string]files.ImageConfig{}, - }, - Plans: files.PlansConfig{ - HostingPlans: map[int]files.HostingPlan{}, - WorkspacePlans: map[int]files.WorkspacePlan{}, - }, - }, - } + It("should configure etcd storage correctly", func() { + installConfig := createBaseConfig("storage-test", "192.168.1.100") k0sConfig, err := installer.GenerateK0sConfig(installConfig) Expect(err).NotTo(HaveOccurred()) - // Verify storage/etcd settings Expect(k0sConfig.Spec.Storage).NotTo(BeNil()) Expect(k0sConfig.Spec.Storage.Type).To(Equal("etcd")) Expect(k0sConfig.Spec.Storage.Etcd).NotTo(BeNil()) Expect(k0sConfig.Spec.Storage.Etcd.PeerAddress).To(Equal("192.168.1.100")) }) + + It("should generate correct cluster name from datacenter", func() { + installConfig := createBaseConfig("prod-us-east", "10.1.2.3") + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + + Expect(k0sConfig.Metadata.Name).To(Equal("codesphere-prod-us-east")) + }) + + It("should handle empty control plane list", func() { + installConfig := createBaseConfig("empty-cp", "10.0.0.1") + installConfig.Kubernetes.ControlPlanes = []files.K8sNode{} + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + // Should either handle gracefully or error + if err == nil { + Expect(k0sConfig).NotTo(BeNil()) + } else { + Expect(err).To(HaveOccurred()) + } + }) + + It("should use default network values when not specified", func() { + installConfig := createBaseConfig("defaults-test", "10.0.0.1") + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + + // Verify defaults are applied or fields are present + Expect(k0sConfig.Spec.Network).NotTo(BeNil()) + if k0sConfig.Spec.Network.PodCIDR != "" { + Expect(k0sConfig.Spec.Network.PodCIDR).To(MatchRegexp(`^\d+\.\d+\.\d+\.\d+/\d+$`)) + } + if k0sConfig.Spec.Network.ServiceCIDR != "" { + Expect(k0sConfig.Spec.Network.ServiceCIDR).To(MatchRegexp(`^\d+\.\d+\.\d+\.\d+/\d+$`)) + } + }) + + It("should handle special characters in datacenter names", func() { + testCases := []struct { + name string + expected string + }{ + {"test-dc-01", "codesphere-test-dc-01"}, + {"test_dc_02", "codesphere-test_dc_02"}, + {"TestDC03", "codesphere-TestDC03"}, + } + + for _, tc := range testCases { + installConfig := createBaseConfig(tc.name, "10.0.0.1") + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(k0sConfig.Metadata.Name).To(Equal(tc.expected)) + } + }) + + It("should handle large multi-control-plane setup", func() { + installConfig := createBaseConfig("large-cluster", "10.0.1.1") + controlPlanes := make([]files.K8sNode, 7) + for i := 0; i < 7; i++ { + controlPlanes[i] = files.K8sNode{ + IPAddress: fmt.Sprintf("10.0.1.%d", i+1), + } + } + installConfig.Kubernetes.ControlPlanes = controlPlanes + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(k0sConfig.Spec.API.Address).To(Equal("10.0.1.1")) + Expect(len(k0sConfig.Spec.API.SANs)).To(BeNumerically(">=", 7)) + }) + + It("should properly configure certificate SANs", func() { + installConfig := createBaseConfig("san-test", "192.168.100.50") + installConfig.Kubernetes.APIServerHost = "k8s.example.com" + installConfig.Codesphere.Domain = "app.example.com" + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + + Expect(k0sConfig.Spec.API.SANs).To(ContainElement("192.168.100.50")) + Expect(k0sConfig.Spec.API.SANs).To(ContainElement("k8s.example.com")) + Expect(len(k0sConfig.Spec.API.SANs)).To(BeNumerically(">=", 2)) + }) }) Describe("Error Handling", func() { - It("should fail gracefully on missing install-config file", func() { + It("should fail when loading non-existent file", func() { nonExistentPath := filepath.Join(tempDir, "does-not-exist.yaml") icg := installer.NewInstallConfigManager() err := icg.LoadInstallConfigFromFile(nonExistentPath) Expect(err).To(HaveOccurred()) }) - It("should handle nil config gracefully", func() { + It("should fail when generating config from nil", func() { _, err := installer.GenerateK0sConfig(nil) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("cannot be nil")) }) - It("should handle invalid YAML gracefully", func() { + It("should fail when loading invalid YAML", func() { invalidYAML := []byte("invalid: [unclosed bracket") err := os.WriteFile(configPath, invalidYAML, 0644) Expect(err).NotTo(HaveOccurred()) @@ -255,112 +275,87 @@ var _ = Describe("K0s Install-Config Integration", func() { Expect(err).To(HaveOccurred()) }) - It("should fail for external Kubernetes", func() { - installConfig := &files.RootConfig{ - Datacenter: files.DatacenterConfig{ - ID: 1, - Name: "external-k8s", - }, - Kubernetes: files.KubernetesConfig{ - ManagedByCodesphere: false, // External K8s - }, - Codesphere: files.CodesphereConfig{ - Domain: "external.test", - PublicIP: "10.0.0.1", - DeployConfig: files.DeployConfig{ - Images: map[string]files.ImageConfig{}, - }, - Plans: files.PlansConfig{ - HostingPlans: map[int]files.HostingPlan{}, - WorkspacePlans: map[int]files.WorkspacePlan{}, - }, - }, - } + It("should handle empty file gracefully", func() { + err := os.WriteFile(configPath, []byte{}, 0644) + Expect(err).NotTo(HaveOccurred()) + + icg := installer.NewInstallConfigManager() + err = icg.LoadInstallConfigFromFile(configPath) + // Empty file loads successfully but returns empty config + Expect(err).NotTo(HaveOccurred()) + config := icg.GetInstallConfig() + Expect(config).NotTo(BeNil()) + }) + + It("should handle external Kubernetes cluster config", func() { + installConfig := createBaseConfig("external-k8s", "10.0.0.1") + installConfig.Kubernetes.ManagedByCodesphere = false - // GenerateK0sConfig should still work (doesn't validate ManagedByCodesphere) - // The validation happens in the CLI command k0sConfig, err := installer.GenerateK0sConfig(installConfig) Expect(err).NotTo(HaveOccurred()) Expect(k0sConfig).NotTo(BeNil()) }) - }) - Describe("YAML Marshalling", func() { - It("should produce valid k0s YAML output", func() { - installConfig := &files.RootConfig{ - Datacenter: files.DatacenterConfig{ - ID: 1, - Name: "yaml-test", - }, - Kubernetes: files.KubernetesConfig{ - ManagedByCodesphere: true, - ControlPlanes: []files.K8sNode{ - {IPAddress: "10.20.30.40"}, - }, - }, - Codesphere: files.CodesphereConfig{ - Domain: "yaml.test", - PublicIP: "10.20.30.40", - DeployConfig: files.DeployConfig{ - Images: map[string]files.ImageConfig{}, - }, - Plans: files.PlansConfig{ - HostingPlans: map[int]files.HostingPlan{}, - WorkspacePlans: map[int]files.WorkspacePlan{}, - }, - }, + It("should handle missing APIServerHost", func() { + installConfig := createBaseConfig("missing-host", "10.0.0.1") + installConfig.Kubernetes.APIServerHost = "" + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + if err == nil { + Expect(k0sConfig).NotTo(BeNil()) + Expect(k0sConfig.Spec.API.Address).To(Equal("10.0.0.1")) + } else { + Expect(err).To(HaveOccurred()) } + }) + + It("should handle missing datacenter name", func() { + installConfig := createBaseConfig("", "10.0.0.1") k0sConfig, err := installer.GenerateK0sConfig(installConfig) + if err == nil { + Expect(k0sConfig).NotTo(BeNil()) + } else { + Expect(err).To(HaveOccurred()) + } + }) + + It("should fail when writing to read-only directory", func() { + readOnlyDir := filepath.Join(tempDir, "readonly") + err := os.Mkdir(readOnlyDir, 0444) Expect(err).NotTo(HaveOccurred()) - yamlData, err := k0sConfig.Marshal() + readOnlyPath := filepath.Join(readOnlyDir, "config.yaml") + installConfig := createBaseConfig("test", "10.0.0.1") + configData, err := yaml.Marshal(installConfig) Expect(err).NotTo(HaveOccurred()) - Expect(string(yamlData)).To(ContainSubstring("k0s.k0sproject.io/v1beta1")) - Expect(string(yamlData)).To(ContainSubstring("ClusterConfig")) - Expect(string(yamlData)).To(ContainSubstring("10.20.30.40")) + + err = os.WriteFile(readOnlyPath, configData, 0644) + Expect(err).To(HaveOccurred()) + + os.Chmod(readOnlyDir, 0755) }) + }) - It("should round-trip marshal and unmarshal correctly", func() { - installConfig := &files.RootConfig{ - Datacenter: files.DatacenterConfig{ - ID: 1, - Name: "roundtrip-test", - }, - Kubernetes: files.KubernetesConfig{ - ManagedByCodesphere: true, - ControlPlanes: []files.K8sNode{ - {IPAddress: "172.16.0.1"}, - }, - PodCIDR: "10.244.0.0/16", - ServiceCIDR: "10.96.0.0/12", - }, - Codesphere: files.CodesphereConfig{ - Domain: "roundtrip.test", - PublicIP: "172.16.0.1", - DeployConfig: files.DeployConfig{ - Images: map[string]files.ImageConfig{}, - }, - Plans: files.PlansConfig{ - HostingPlans: map[int]files.HostingPlan{}, - WorkspacePlans: map[int]files.WorkspacePlan{}, - }, - }, - } + Describe("YAML Serialization", func() { + It("should marshal and unmarshal k0s config correctly", func() { + installConfig := createBaseConfig("roundtrip-test", "172.16.0.1") + installConfig.Kubernetes.PodCIDR = "10.244.0.0/16" + installConfig.Kubernetes.ServiceCIDR = "10.96.0.0/12" original, err := installer.GenerateK0sConfig(installConfig) Expect(err).NotTo(HaveOccurred()) - // Marshal to YAML yamlData, err := original.Marshal() Expect(err).NotTo(HaveOccurred()) + Expect(string(yamlData)).To(ContainSubstring("k0s.k0sproject.io/v1beta1")) + Expect(string(yamlData)).To(ContainSubstring("ClusterConfig")) - // Unmarshal back var restored installer.K0sConfig err = yaml.Unmarshal(yamlData, &restored) Expect(err).NotTo(HaveOccurred()) - // Verify they match + // Verify critical fields match Expect(restored.APIVersion).To(Equal(original.APIVersion)) Expect(restored.Kind).To(Equal(original.Kind)) Expect(restored.Metadata.Name).To(Equal(original.Metadata.Name)) @@ -370,83 +365,45 @@ var _ = Describe("K0s Install-Config Integration", func() { }) }) - Describe("Full Workflow Integration", func() { - It("should complete full config generation workflow", func() { - // Step 1: Create install-config - installConfig := &files.RootConfig{ - Datacenter: files.DatacenterConfig{ - ID: 1, - Name: "integration-dc", - City: "Integration City", - CountryCode: "US", - }, - Kubernetes: files.KubernetesConfig{ - ManagedByCodesphere: true, - ControlPlanes: []files.K8sNode{ - {IPAddress: "203.0.113.10"}, - }, - APIServerHost: "api.integration.test", - PodCIDR: "10.244.0.0/16", - ServiceCIDR: "10.96.0.0/12", - }, - Codesphere: files.CodesphereConfig{ - Domain: "integration.test", - PublicIP: "203.0.113.10", - DeployConfig: files.DeployConfig{ - Images: map[string]files.ImageConfig{}, - }, - Plans: files.PlansConfig{ - HostingPlans: map[int]files.HostingPlan{}, - WorkspacePlans: map[int]files.WorkspacePlan{}, - }, - }, - } + Describe("Config Persistence", func() { + It("should persist and reload config correctly", func() { + originalConfig := createBaseConfig("persist-test", "172.20.30.40") + originalConfig.Kubernetes.PodCIDR = "10.100.0.0/16" + originalConfig.Kubernetes.ServiceCIDR = "10.200.0.0/16" - // Step 2: Write install-config - configData, err := yaml.Marshal(installConfig) + // Save install-config + configData, err := yaml.Marshal(originalConfig) Expect(err).NotTo(HaveOccurred()) err = os.WriteFile(configPath, configData, 0644) Expect(err).NotTo(HaveOccurred()) - // Step 3: Load install-config - icg := installer.NewInstallConfigManager() - err = icg.LoadInstallConfigFromFile(configPath) + // Generate and save k0s config + k0sConfig, err := installer.GenerateK0sConfig(originalConfig) Expect(err).NotTo(HaveOccurred()) - - // Step 4: Generate k0s config - loadedConfig := icg.GetInstallConfig() - k0sConfig, err := installer.GenerateK0sConfig(loadedConfig) - Expect(err).NotTo(HaveOccurred()) - - // Step 5: Marshal k0s config k0sData, err := k0sConfig.Marshal() Expect(err).NotTo(HaveOccurred()) - - // Step 6: Write k0s config err = os.WriteFile(k0sConfigOut, k0sData, 0644) Expect(err).NotTo(HaveOccurred()) - // Step 7: Verify complete workflow - Expect(k0sConfigOut).To(BeAnExistingFile()) - - // Step 8: Load and verify k0s config - readData, err := os.ReadFile(k0sConfigOut) + // Reload install-config + icg := installer.NewInstallConfigManager() + err = icg.LoadInstallConfigFromFile(configPath) Expect(err).NotTo(HaveOccurred()) + reloadedInstallConfig := icg.GetInstallConfig() - var finalK0sConfig installer.K0sConfig - err = yaml.Unmarshal(readData, &finalK0sConfig) + // Reload k0s config + reloadedK0sData, err := os.ReadFile(k0sConfigOut) + Expect(err).NotTo(HaveOccurred()) + var reloadedK0sConfig installer.K0sConfig + err = yaml.Unmarshal(reloadedK0sData, &reloadedK0sConfig) Expect(err).NotTo(HaveOccurred()) - // Step 9: Validate all fields - Expect(finalK0sConfig.APIVersion).To(Equal("k0s.k0sproject.io/v1beta1")) - Expect(finalK0sConfig.Kind).To(Equal("ClusterConfig")) - Expect(finalK0sConfig.Metadata.Name).To(Equal("codesphere-integration-dc")) - Expect(finalK0sConfig.Spec.API.Address).To(Equal("203.0.113.10")) - Expect(finalK0sConfig.Spec.API.ExternalAddress).To(Equal("api.integration.test")) - Expect(finalK0sConfig.Spec.Network.PodCIDR).To(Equal("10.244.0.0/16")) - Expect(finalK0sConfig.Spec.Network.ServiceCIDR).To(Equal("10.96.0.0/12")) - Expect(finalK0sConfig.Spec.Storage.Type).To(Equal("etcd")) - Expect(finalK0sConfig.Spec.Storage.Etcd.PeerAddress).To(Equal("203.0.113.10")) + // Verify both configs match original + Expect(reloadedInstallConfig.Datacenter.Name).To(Equal(originalConfig.Datacenter.Name)) + Expect(reloadedInstallConfig.Kubernetes.PodCIDR).To(Equal(originalConfig.Kubernetes.PodCIDR)) + Expect(reloadedK0sConfig.Metadata.Name).To(Equal(k0sConfig.Metadata.Name)) + Expect(reloadedK0sConfig.Spec.API.Address).To(Equal(k0sConfig.Spec.API.Address)) + Expect(reloadedK0sConfig.Spec.Network.PodCIDR).To(Equal(k0sConfig.Spec.Network.PodCIDR)) }) }) }) From 071eea949dc50f69a0aebd731876d27276c04ea3 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:04:30 +0100 Subject: [PATCH 06/65] ref: cleanup --- cli/cmd/install_k0s.go | 42 ++++------------------- cli/cmd/install_k0s_test.go | 66 ++++--------------------------------- 2 files changed, 13 insertions(+), 95 deletions(-) diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index 220654ee..b314347d 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -32,7 +32,6 @@ type InstallK0sOpts struct { *GlobalOptions Version string Package string - Config string InstallConfig string SSHKeyPath string RemoteHost string @@ -46,16 +45,11 @@ func (c *InstallK0sCmd) RunE(_ *cobra.Command, args []string) error { pm := installer.NewPackage(env.GetOmsWorkdir(), c.Opts.Package) k0s := installer.NewK0s(hw, env, c.FileWriter) - if c.Opts.InstallConfig != "" { - return c.InstallK0sFromInstallConfig(pm, k0s) + if c.Opts.InstallConfig == "" { + return fmt.Errorf("--install-config is required") } - err := c.InstallK0s(pm, k0s) - if err != nil { - return fmt.Errorf("failed to install k0s: %w", err) - } - - return nil + return c.InstallK0sFromInstallConfig(pm, k0s) } func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { @@ -67,17 +61,14 @@ func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { This will either download the k0s binary directly to the OMS workdir, if not already present, and install it or load the k0s binary from the provided package file and install it. If no version is specified, the latest version will be downloaded. - If no install config is provided, k0s will be installed with the '--single' flag. - You can also install k0s from a Codesphere install-config file, which will: + You must provide a Codesphere install-config file, which will: - Generate a k0s configuration from the install-config - Optionally install k0s on remote nodes via SSH`), Example: formatExamplesWithBinary("install k0s", []packageio.Example{ - {Cmd: "", Desc: "Install k0s using the Go-native implementation"}, + {Cmd: "--install-config ", Desc: "Path to Codesphere install-config file to generate k0s config from"}, {Cmd: "--version ", Desc: "Version of k0s to install"}, {Cmd: "--package ", Desc: "Package file (e.g. codesphere-v1.2.3-installer.tar.gz) to load k0s from"}, - {Cmd: "--k0s-config ", Desc: "Path to k0s configuration file, if not set k0s will be installed with the '--single' flag"}, - {Cmd: "--install-config ", Desc: "Path to Codesphere install-config file to generate k0s config from"}, {Cmd: "--remote-host ", Desc: "Remote host IP to install k0s on (requires --ssh-key-path)"}, {Cmd: "--ssh-key-path ", Desc: "SSH private key path for remote installation"}, {Cmd: "--force", Desc: "Force new download and installation even if k0s binary exists or is already installed"}, @@ -89,8 +80,7 @@ func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { } k0s.cmd.Flags().StringVarP(&k0s.Opts.Version, "version", "v", "", "Version of k0s to install") k0s.cmd.Flags().StringVarP(&k0s.Opts.Package, "package", "p", "", "Package file (e.g. codesphere-v1.2.3-installer.tar.gz) to load k0s from") - k0s.cmd.Flags().StringVar(&k0s.Opts.Config, "k0s-config", "", "Path to k0s configuration file") - k0s.cmd.Flags().StringVar(&k0s.Opts.InstallConfig, "install-config", "", "Path to Codesphere install-config file") + k0s.cmd.Flags().StringVar(&k0s.Opts.InstallConfig, "install-config", "", "Path to Codesphere install-config file (required)") k0s.cmd.Flags().StringVar(&k0s.Opts.SSHKeyPath, "ssh-key-path", "", "SSH private key path for remote installation") k0s.cmd.Flags().StringVar(&k0s.Opts.RemoteHost, "remote-host", "", "Remote host IP to install k0s on") k0s.cmd.Flags().StringVar(&k0s.Opts.RemoteUser, "remote-user", "root", "Remote user for SSH connection") @@ -103,26 +93,6 @@ func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { const defaultK0sPath = "kubernetes/files/k0s" -func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0sManager) error { - // Default dependency path for k0s binary within package - k0sPath := pm.GetDependencyPath(defaultK0sPath) - - var err error - if c.Opts.Package == "" { - k0sPath, err = k0s.Download(c.Opts.Version, c.Opts.Force, false) - if err != nil { - return fmt.Errorf("failed to download k0s: %w", err) - } - } - - err = k0s.Install(c.Opts.Config, k0sPath, c.Opts.Force) - if err != nil { - return fmt.Errorf("failed to install k0s: %w", err) - } - - return nil -} - func (c *InstallK0sCmd) InstallK0sFromInstallConfig(pm installer.PackageManager, k0s installer.K0sManager) error { icg := installer.NewInstallConfigManager() if err := icg.LoadInstallConfigFromFile(c.Opts.InstallConfig); err != nil { diff --git a/cli/cmd/install_k0s_test.go b/cli/cmd/install_k0s_test.go index 8c59b542..c6b3e207 100644 --- a/cli/cmd/install_k0s_test.go +++ b/cli/cmd/install_k0s_test.go @@ -4,14 +4,11 @@ package cmd_test import ( - "errors" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/codesphere-cloud/oms/cli/cmd" "github.com/codesphere-cloud/oms/internal/env" - "github.com/codesphere-cloud/oms/internal/installer" "github.com/codesphere-cloud/oms/internal/util" ) @@ -32,7 +29,7 @@ var _ = Describe("InstallK0sCmd", func() { GlobalOptions: globalOpts, Version: "", Package: "", - Config: "", + InstallConfig: "", Force: false, } c = cmd.InstallK0sCmd{ @@ -47,63 +44,14 @@ var _ = Describe("InstallK0sCmd", func() { mockFileWriter.AssertExpectations(GinkgoT()) }) - Context("InstallK0s method", func() { - It("fails when package is not specified and k0s download fails", func() { - mockPackageManager := installer.NewMockPackageManager(GinkgoT()) - mockK0sManager := installer.NewMockK0sManager(GinkgoT()) - - c.Opts.Package = "" // No package specified, should download - mockPackageManager.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/workdir/test-package/deps/kubernetes/files/k0s") - mockK0sManager.EXPECT().Download("", false, false).Return("", errors.New("download failed")) - - err := c.InstallK0s(mockPackageManager, mockK0sManager) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to download k0s")) - Expect(err.Error()).To(ContainSubstring("download failed")) - }) - - It("fails when k0s install fails", func() { - mockPackageManager := installer.NewMockPackageManager(GinkgoT()) - mockK0sManager := installer.NewMockK0sManager(GinkgoT()) + Context("RunE method", func() { + It("fails when install-config is not provided", func() { + c.Opts.InstallConfig = "" + mockEnv.EXPECT().GetOmsWorkdir().Return("/test/workdir") - c.Opts.Package = "" // No package specified, should download - c.Opts.Config = "/path/to/config.yaml" - c.Opts.Force = true - mockPackageManager.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/workdir/test-package/deps/kubernetes/files/k0s") - mockK0sManager.EXPECT().Download("", true, false).Return("/test/workdir/k0s", nil) - mockK0sManager.EXPECT().Install("/path/to/config.yaml", "/test/workdir/k0s", true).Return(errors.New("install failed")) - - err := c.InstallK0s(mockPackageManager, mockK0sManager) + err := c.RunE(nil, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to install k0s")) - Expect(err.Error()).To(ContainSubstring("install failed")) - }) - - It("succeeds when package is not specified and k0s download and install work", func() { - mockPackageManager := installer.NewMockPackageManager(GinkgoT()) - mockK0sManager := installer.NewMockK0sManager(GinkgoT()) - - c.Opts.Package = "" // No package specified, should download - c.Opts.Config = "" // No config, will use single mode - mockPackageManager.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/workdir/test-package/deps/kubernetes/files/k0s") - mockK0sManager.EXPECT().Download("", false, false).Return("/test/workdir/k0s", nil) - mockK0sManager.EXPECT().Install("", "/test/workdir/k0s", false).Return(nil) - - err := c.InstallK0s(mockPackageManager, mockK0sManager) - Expect(err).ToNot(HaveOccurred()) - }) - - It("succeeds when package is specified and k0s install works", func() { - mockPackageManager := installer.NewMockPackageManager(GinkgoT()) - mockK0sManager := installer.NewMockK0sManager(GinkgoT()) - - c.Opts.Package = "test-package.tar.gz" // Package specified, should use k0s from package - c.Opts.Config = "/path/to/config.yaml" - mockPackageManager.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/workdir/test-package/deps/kubernetes/files/k0s") - mockK0sManager.EXPECT().Install("/path/to/config.yaml", "/test/workdir/test-package/deps/kubernetes/files/k0s", false).Return(nil) - - err := c.InstallK0s(mockPackageManager, mockK0sManager) - Expect(err).ToNot(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("--install-config is required")) }) }) }) From 9de5965476fef6c1a1733208ecc053b3715060ad Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:05:25 +0000 Subject: [PATCH 07/65] chore(docs): Auto-update docs and licenses Signed-off-by: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> --- docs/oms-cli_install_k0s.md | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/docs/oms-cli_install_k0s.md b/docs/oms-cli_install_k0s.md index 08b17f30..e0084e7b 100644 --- a/docs/oms-cli_install_k0s.md +++ b/docs/oms-cli_install_k0s.md @@ -8,9 +8,8 @@ Install k0s either from the package or by downloading it. This will either download the k0s binary directly to the OMS workdir, if not already present, and install it or load the k0s binary from the provided package file and install it. If no version is specified, the latest version will be downloaded. -If no install config is provided, k0s will be installed with the '--single' flag. -You can also install k0s from a Codesphere install-config file, which will: +You must provide a Codesphere install-config file, which will: - Generate a k0s configuration from the install-config - Optionally install k0s on remote nodes via SSH @@ -21,8 +20,8 @@ oms-cli install k0s [flags] ### Examples ``` -# Install k0s using the Go-native implementation -$ oms-cli install k0s +# Path to Codesphere install-config file to generate k0s config from +$ oms-cli install k0s --install-config # Version of k0s to install $ oms-cli install k0s --version @@ -30,12 +29,6 @@ $ oms-cli install k0s --version # Package file (e.g. codesphere-v1.2.3-installer.tar.gz) to load k0s from $ oms-cli install k0s --package -# Path to k0s configuration file, if not set k0s will be installed with the '--single' flag -$ oms-cli install k0s --k0s-config - -# Path to Codesphere install-config file to generate k0s config from -$ oms-cli install k0s --install-config - # Remote host IP to install k0s on (requires --ssh-key-path) $ oms-cli install k0s --remote-host @@ -52,8 +45,7 @@ $ oms-cli install k0s --force ``` -f, --force Force new download and installation -h, --help help for k0s - --install-config string Path to Codesphere install-config file - --k0s-config string Path to k0s configuration file + --install-config string Path to Codesphere install-config file (required) -p, --package string Package file (e.g. codesphere-v1.2.3-installer.tar.gz) to load k0s from --remote-host string Remote host IP to install k0s on --remote-user string Remote user for SSH connection (default "root") From 01d67504bee81e4bbd4b23be549e3d2bf9287ece Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:05:53 +0100 Subject: [PATCH 08/65] Update internal/installer/node/node.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/installer/node/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 438d80b7..ab437eb0 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -175,7 +175,7 @@ func (n *NodeManager) RunSSHCommand(jumpboxIp string, ip string, username string defer func() { _ = client.Close() }() session, err := client.NewSession() if err != nil { - return fmt.Errorf("failed to create session on jumpbox: %v", err) + return fmt.Errorf("failed to create session on target node (%s): %v", ip, err) } defer func() { _ = session.Close() }() From b19ac3b7f79a9b5edaf31673e8dc076c4d134878 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:10:09 +0100 Subject: [PATCH 09/65] ref: enhance shell command safety --- internal/installer/node/node.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 438d80b7..558ab654 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -9,6 +9,7 @@ import ( "net" "os" "path/filepath" + "strings" "syscall" "time" @@ -31,6 +32,10 @@ type NodeManager struct { KeyPath string } +func shellEscape(s string) string { + return strings.ReplaceAll(s, "'", "'\\''") +} + func (n *NodeManager) getHostKeyCallback() (ssh.HostKeyCallback, error) { homeDir, err := os.UserHomeDir() if err != nil { @@ -262,7 +267,7 @@ func (n *NodeManager) GetSFTPClient(jumpboxIp string, ip string, username string } func (nm *NodeManager) EnsureDirectoryExists(ip string, username string, dir string) error { - cmd := fmt.Sprintf("mkdir -p '%s'", dir) + cmd := fmt.Sprintf("mkdir -p '%s'", shellEscape(dir)) return nm.RunSSHCommand("", ip, username, cmd) } @@ -294,7 +299,7 @@ func (n *NodeManager) CopyFile(jumpboxIp string, ip string, username string, src } func (n *Node) HasCommand(nm *NodeManager, command string) bool { - checkCommand := fmt.Sprintf("command -v %s >/dev/null 2>&1", command) + checkCommand := fmt.Sprintf("command -v '%s' >/dev/null 2>&1", shellEscape(command)) err := nm.RunSSHCommand("", n.ExternalIP, "root", checkCommand) return err == nil } @@ -355,7 +360,7 @@ func (n *Node) HasRootLoginEnabled(jumpbox *Node, nm *NodeManager) bool { } func (n *Node) HasFile(jumpbox *Node, nm *NodeManager, filePath string) bool { - checkCommand := fmt.Sprintf("test -f '%s'", filePath) + checkCommand := fmt.Sprintf("test -f '%s'", shellEscape(filePath)) err := n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommand) return err == nil } @@ -394,7 +399,7 @@ func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath s } log.Printf("Making k0s binary executable on %s", n.ExternalIP) - chmodCmd := fmt.Sprintf("chmod +x %s", remoteK0sBinary) + chmodCmd := fmt.Sprintf("chmod +x '%s'", shellEscape(remoteK0sBinary)) if err := nm.RunSSHCommand("", n.ExternalIP, "root", chmodCmd); err != nil { return fmt.Errorf("failed to make k0s binary executable: %w", err) } @@ -409,9 +414,9 @@ func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath s } } - installCmd := fmt.Sprintf("sudo %s install controller", remoteK0sBinary) + installCmd := fmt.Sprintf("sudo '%s' install controller", shellEscape(remoteK0sBinary)) if k0sConfigPath != "" { - installCmd += fmt.Sprintf(" --config %s", remoteConfigPath) + installCmd += fmt.Sprintf(" --config '%s'", shellEscape(remoteConfigPath)) } else { installCmd += " --single" } @@ -425,8 +430,8 @@ func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath s } log.Printf("k0s successfully installed on %s", n.ExternalIP) - log.Printf("You can start it using: ssh root@%s 'sudo %s start'", n.ExternalIP, remoteK0sBinary) - log.Printf("You can check the status using: ssh root@%s 'sudo %s status'", n.ExternalIP, remoteK0sBinary) + log.Printf("You can start it using: ssh root@%s 'sudo %s start'", n.ExternalIP, shellEscape(remoteK0sBinary)) + log.Printf("You can check the status using: ssh root@%s 'sudo %s status'", n.ExternalIP, shellEscape(remoteK0sBinary)) return nil } From d4d1b5f164eb789ef77d8b6aefcfebb281693782 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:41:38 +0100 Subject: [PATCH 10/65] test: add tests for NodeManager and Node methods --- internal/installer/node/node_test.go | 294 +++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 internal/installer/node/node_test.go diff --git a/internal/installer/node/node_test.go b/internal/installer/node/node_test.go new file mode 100644 index 00000000..1db8f100 --- /dev/null +++ b/internal/installer/node/node_test.go @@ -0,0 +1,294 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package node_test + +import ( + "errors" + "io" + "os" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/codesphere-cloud/oms/internal/installer/node" + "github.com/codesphere-cloud/oms/internal/util" +) + +func TestNode(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Node Suite") +} + +var _ = Describe("Node", func() { + Describe("NodeManager", func() { + var ( + nm *node.NodeManager + mockFileWriter *util.MockFileIO + ) + + BeforeEach(func() { + mockFileWriter = util.NewMockFileIO(GinkgoT()) + nm = &node.NodeManager{ + FileIO: mockFileWriter, + KeyPath: "", + } + }) + + AfterEach(func() { + mockFileWriter.AssertExpectations(GinkgoT()) + }) + + Context("authentication methods", func() { + It("should return error when no authentication method is available", func() { + originalAuthSock := os.Getenv("SSH_AUTH_SOCK") + defer func() { + if originalAuthSock != "" { + os.Setenv("SSH_AUTH_SOCK", originalAuthSock) + } else { + os.Unsetenv("SSH_AUTH_SOCK") + } + }() + os.Unsetenv("SSH_AUTH_SOCK") + + nm.KeyPath = "" + + client, err := nm.GetClient("", "10.0.0.1", "root") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("no valid authentication methods")) + Expect(client).To(BeNil()) + }) + + It("should return error when key file cannot be read", func() { + originalAuthSock := os.Getenv("SSH_AUTH_SOCK") + defer func() { + if originalAuthSock != "" { + os.Setenv("SSH_AUTH_SOCK", originalAuthSock) + } else { + os.Unsetenv("SSH_AUTH_SOCK") + } + }() + os.Unsetenv("SSH_AUTH_SOCK") + + nm.KeyPath = "/nonexistent/key" + mockFileWriter.EXPECT().ReadFile("/nonexistent/key").Return(nil, errors.New("file not found")) + + client, err := nm.GetClient("", "10.0.0.1", "root") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to read private key file")) + Expect(client).To(BeNil()) + }) + + It("should return error when key file is invalid", func() { + originalAuthSock := os.Getenv("SSH_AUTH_SOCK") + defer func() { + if originalAuthSock != "" { + os.Setenv("SSH_AUTH_SOCK", originalAuthSock) + } else { + os.Unsetenv("SSH_AUTH_SOCK") + } + }() + os.Unsetenv("SSH_AUTH_SOCK") + + invalidKey := []byte("not a valid ssh key") + nm.KeyPath = "/path/to/invalid/key" + mockFileWriter.EXPECT().ReadFile("/path/to/invalid/key").Return(invalidKey, nil) + + client, err := nm.GetClient("", "10.0.0.1", "root") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to parse private key")) + Expect(client).To(BeNil()) + }) + }) + + Context("SSH connection", func() { + It("should fail to connect to invalid host", func() { + privateKey := []byte(`-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDjKvZvwzXnCdFniXHDZdFPo4LFJ7KJJdBWrJjN1rO1ZQAAAJgNY3PmDWNz +5gAAAAtzc2gtZWQyNTUxOQAAACDjKvZvwzXnCdFniXHDZdFPo4LFJ7KJJdBWrJjN1rO1ZQ +AAAEDcZfnYLBVPEQT3qYDh6e5zMvKjN8x5k4l3n9qYLFJ7MOMq9m/DNecJ0WeJccNl0U+j +gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== +-----END OPENSSH PRIVATE KEY-----`) + + nm.KeyPath = "/path/to/key" + mockFileWriter.EXPECT().ReadFile("/path/to/key").Return(privateKey, nil).Maybe() + + client, err := nm.GetClient("", "192.0.2.1", "root") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to dial")) + Expect(client).To(BeNil()) + }) + + It("should fail to connect through invalid jumpbox", func() { + privateKey := []byte(`-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDjKvZvwzXnCdFniXHDZdFPo4LFJ7KJJdBWrJjN1rO1ZQAAAJgNY3PmDWNz +5gAAAAtzc2gtZWQyNTUxOQAAACDjKvZvwzXnCdFniXHDZdFPo4LFJ7KJJdBWrJjN1rO1ZQ +AAAEDcZfnYLBVPEQT3qYDh6e5zMvKjN8x5k4l3n9qYLFJ7MOMq9m/DNecJ0WeJccNl0U+j +gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== +-----END OPENSSH PRIVATE KEY-----`) + + nm.KeyPath = "/path/to/key" + mockFileWriter.EXPECT().ReadFile("/path/to/key").Return(privateKey, nil).Maybe() + + client, err := nm.GetClient("192.0.2.1", "192.0.2.2", "root") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to connect to jumpbox")) + Expect(client).To(BeNil()) + }) + }) + + Context("file operations", func() { + It("should handle directory creation errors", func() { + err := nm.EnsureDirectoryExists("192.0.2.1", "root", "/tmp/test") + Expect(err).To(HaveOccurred()) + }) + + It("should handle copy file errors when source doesn't exist", func() { + mockFileWriter.EXPECT().Open("/nonexistent/file").Return(nil, errors.New("file not found")).Maybe() + + err := nm.CopyFile("", "192.0.2.1", "root", "/nonexistent/file", "/tmp/dest") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to get SSH client")) + }) + }) + }) + + Describe("Node methods", func() { + var ( + n *node.Node + nm *node.NodeManager + mockFileWriter *util.MockFileIO + ) + + BeforeEach(func() { + mockFileWriter = util.NewMockFileIO(GinkgoT()) + nm = &node.NodeManager{ + FileIO: mockFileWriter, + KeyPath: "", + } + n = &node.Node{ + Name: "test-node", + ExternalIP: "10.0.0.1", + InternalIP: "192.168.1.1", + } + }) + + AfterEach(func() { + mockFileWriter.AssertExpectations(GinkgoT()) + }) + + Context("HasCommand", func() { + It("should return false when SSH connection fails", func() { + result := n.HasCommand(nm, "kubectl") + Expect(result).To(BeFalse()) + }) + + It("should handle commands with special characters safely", func() { + result := n.HasCommand(nm, "kubectl'; rm -rf /; echo '") + Expect(result).To(BeFalse()) + }) + }) + + Context("HasFile", func() { + It("should return false when SSH connection fails", func() { + result := n.HasFile(nil, nm, "/etc/k0s/k0s.yaml") + Expect(result).To(BeFalse()) + }) + + It("should handle paths with special characters safely", func() { + result := n.HasFile(nil, nm, "/path'; rm -rf /; echo '/file.txt") + Expect(result).To(BeFalse()) + }) + + It("should support jumpbox connections", func() { + jumpbox := &node.Node{ + ExternalIP: "10.0.0.2", + InternalIP: "10.0.0.2", + } + result := n.HasFile(jumpbox, nm, "/etc/k0s/k0s.yaml") + Expect(result).To(BeFalse()) + }) + }) + + Context("CopyFile", func() { + It("should fail when directory creation fails", func() { + err := n.CopyFile(nm, "/some/file", "/remote/path/dest.txt") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to ensure directory exists")) + }) + }) + + Context("RunSSHCommand", func() { + It("should handle direct connection without jumpbox", func() { + err := n.RunSSHCommand(nil, nm, "root", "echo test") + Expect(err).To(HaveOccurred()) + }) + + It("should handle connection through jumpbox", func() { + jumpbox := &node.Node{ + ExternalIP: "10.0.0.2", + InternalIP: "10.0.0.2", + } + err := n.RunSSHCommand(jumpbox, nm, "ubuntu", "echo test") + Expect(err).To(HaveOccurred()) + }) + }) + + Context("InstallK0s", func() { + It("should handle binary copy failure", func() { + k0sBinaryPath := "/path/to/k0s" + mockFileWriter.EXPECT().Open(k0sBinaryPath).Return(nil, errors.New("file not found")).Maybe() + + err := n.InstallK0s(nm, k0sBinaryPath, "", false) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to ensure directory exists")) + }) + + It("should handle paths with special characters safely", func() { + k0sBinaryPath := "/path/to/k0s'; echo 'injected" + + err := n.InstallK0s(nm, k0sBinaryPath, "", false) + Expect(err).To(HaveOccurred()) + }) + + It("should support force flag parameter", func() { + k0sBinaryPath := "/tmp/k0s" + + err := n.InstallK0s(nm, k0sBinaryPath, "", true) + Expect(err).To(HaveOccurred()) + // Will fail to connect, but tests that force flag is handled + }) + + It("should support config file parameter", func() { + k0sBinaryPath := "/tmp/k0s" + k0sConfigPath := "/tmp/k0s.yaml" + + err := n.InstallK0s(nm, k0sBinaryPath, k0sConfigPath, false) + Expect(err).To(HaveOccurred()) + // Will fail to connect, but tests that config path is handled + }) + }) + }) +}) + +// mockReadCloser implements io.ReadCloser for testing +type mockReadCloser struct { + content []byte + pos int +} + +func (m *mockReadCloser) Read(p []byte) (n int, err error) { + if m.pos >= len(m.content) { + return 0, io.EOF + } + n = copy(p, m.content[m.pos:]) + m.pos += n + return n, nil +} + +func (m *mockReadCloser) Close() error { + return nil +} From 97bb3b884e5f5f348f5564a1cc5b0e09ba846905 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 16 Dec 2025 12:01:15 +0100 Subject: [PATCH 11/65] fix: improve passphrase handling in getAuthMethods --- internal/installer/node/node.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index a5d35ce7..d2377e1e 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -96,11 +96,13 @@ func (n *NodeManager) getAuthMethods() ([]ssh.AuthMethod, error) { return nil, fmt.Errorf("failed to read passphrase: %v", err) } - signer, err = ssh.ParsePrivateKeyWithPassphrase(key, passphraseBytes) - for i := range passphraseBytes { - passphraseBytes[i] = 0 - } + defer func() { + for i := range passphraseBytes { + passphraseBytes[i] = 0 + } + }() + signer, err = ssh.ParsePrivateKeyWithPassphrase(key, passphraseBytes) if err != nil { return nil, fmt.Errorf("failed to parse private key with passphrase: %v", err) } From b07c0a80647b3db8d2806a6c96fd30a8593fdd0a Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 16 Dec 2025 12:10:37 +0100 Subject: [PATCH 12/65] fix: enforce required flags for remote installation and clean up test code --- cli/cmd/install_k0s.go | 6 ++--- internal/installer/node/node_test.go | 38 +++++++--------------------- 2 files changed, 11 insertions(+), 33 deletions(-) diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index b314347d..b5b09932 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -86,6 +86,8 @@ func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { k0s.cmd.Flags().StringVar(&k0s.Opts.RemoteUser, "remote-user", "root", "Remote user for SSH connection") k0s.cmd.Flags().BoolVarP(&k0s.Opts.Force, "force", "f", false, "Force new download and installation") + k0s.cmd.MarkFlagsRequiredTogether("remote-host", "ssh-key-path") + install.AddCommand(k0s.cmd) k0s.cmd.RunE = k0s.RunE @@ -146,10 +148,6 @@ func (c *InstallK0sCmd) InstallK0sFromInstallConfig(pm installer.PackageManager, } func (c *InstallK0sCmd) InstallK0sRemote(config *files.RootConfig, k0sBinaryPath string, k0sConfigPath string) error { - if c.Opts.SSHKeyPath == "" { - return fmt.Errorf("--ssh-key-path is required for remote installation") - } - log.Printf("Installing k0s on remote host %s", c.Opts.RemoteHost) nm := &node.NodeManager{ diff --git a/internal/installer/node/node_test.go b/internal/installer/node/node_test.go index 1db8f100..44d3a6df 100644 --- a/internal/installer/node/node_test.go +++ b/internal/installer/node/node_test.go @@ -5,7 +5,6 @@ package node_test import ( "errors" - "io" "os" "testing" @@ -45,12 +44,12 @@ var _ = Describe("Node", func() { originalAuthSock := os.Getenv("SSH_AUTH_SOCK") defer func() { if originalAuthSock != "" { - os.Setenv("SSH_AUTH_SOCK", originalAuthSock) + _ = os.Setenv("SSH_AUTH_SOCK", originalAuthSock) } else { - os.Unsetenv("SSH_AUTH_SOCK") + _ = os.Unsetenv("SSH_AUTH_SOCK") } }() - os.Unsetenv("SSH_AUTH_SOCK") + _ = os.Unsetenv("SSH_AUTH_SOCK") nm.KeyPath = "" @@ -64,12 +63,12 @@ var _ = Describe("Node", func() { originalAuthSock := os.Getenv("SSH_AUTH_SOCK") defer func() { if originalAuthSock != "" { - os.Setenv("SSH_AUTH_SOCK", originalAuthSock) + _ = os.Setenv("SSH_AUTH_SOCK", originalAuthSock) } else { - os.Unsetenv("SSH_AUTH_SOCK") + _ = os.Unsetenv("SSH_AUTH_SOCK") } }() - os.Unsetenv("SSH_AUTH_SOCK") + _ = os.Unsetenv("SSH_AUTH_SOCK") nm.KeyPath = "/nonexistent/key" mockFileWriter.EXPECT().ReadFile("/nonexistent/key").Return(nil, errors.New("file not found")) @@ -84,12 +83,12 @@ var _ = Describe("Node", func() { originalAuthSock := os.Getenv("SSH_AUTH_SOCK") defer func() { if originalAuthSock != "" { - os.Setenv("SSH_AUTH_SOCK", originalAuthSock) + _ = os.Setenv("SSH_AUTH_SOCK", originalAuthSock) } else { - os.Unsetenv("SSH_AUTH_SOCK") + _ = os.Unsetenv("SSH_AUTH_SOCK") } }() - os.Unsetenv("SSH_AUTH_SOCK") + _ = os.Unsetenv("SSH_AUTH_SOCK") invalidKey := []byte("not a valid ssh key") nm.KeyPath = "/path/to/invalid/key" @@ -273,22 +272,3 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== }) }) }) - -// mockReadCloser implements io.ReadCloser for testing -type mockReadCloser struct { - content []byte - pos int -} - -func (m *mockReadCloser) Read(p []byte) (n int, err error) { - if m.pos >= len(m.content) { - return 0, io.EOF - } - n = copy(p, m.content[m.pos:]) - m.pos += n - return n, nil -} - -func (m *mockReadCloser) Close() error { - return nil -} From 8f5647630191f9ff29b77e236d30139ae6ccf6d2 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 16 Dec 2025 12:22:02 +0100 Subject: [PATCH 13/65] fix: update EnsureDirectoryExists to accept jumpbox IP --- internal/installer/node/node.go | 8 ++++---- internal/installer/node/node_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index d2377e1e..2a5a26bd 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -268,9 +268,9 @@ func (n *NodeManager) GetSFTPClient(jumpboxIp string, ip string, username string return sftpClient, nil } -func (nm *NodeManager) EnsureDirectoryExists(ip string, username string, dir string) error { +func (nm *NodeManager) EnsureDirectoryExists(jumpboxIp string, ip string, username string, dir string) error { cmd := fmt.Sprintf("mkdir -p '%s'", shellEscape(dir)) - return nm.RunSSHCommand("", ip, username, cmd) + return nm.RunSSHCommand(jumpboxIp, ip, username, cmd) } func (n *NodeManager) CopyFile(jumpboxIp string, ip string, username string, src string, dst string) error { @@ -323,7 +323,7 @@ func (n *Node) InstallOms(nm *NodeManager) error { } func (n *Node) CopyFile(nm *NodeManager, src string, dst string) error { - err := nm.EnsureDirectoryExists(n.ExternalIP, "root", filepath.Dir(dst)) + err := nm.EnsureDirectoryExists("", n.ExternalIP, "root", filepath.Dir(dst)) if err != nil { return fmt.Errorf("failed to ensure directory exists: %w", err) } @@ -408,7 +408,7 @@ func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath s if k0sConfigPath != "" { log.Printf("Copying k0s config to %s:%s", n.ExternalIP, remoteConfigPath) - if err := nm.EnsureDirectoryExists(n.ExternalIP, "root", "/etc/k0s"); err != nil { + if err := nm.EnsureDirectoryExists("", n.ExternalIP, "root", "/etc/k0s"); err != nil { return fmt.Errorf("failed to create /etc/k0s directory: %w", err) } if err := nm.CopyFile("", n.ExternalIP, "root", k0sConfigPath, remoteConfigPath); err != nil { diff --git a/internal/installer/node/node_test.go b/internal/installer/node/node_test.go index 44d3a6df..8c7126f3 100644 --- a/internal/installer/node/node_test.go +++ b/internal/installer/node/node_test.go @@ -141,7 +141,7 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== Context("file operations", func() { It("should handle directory creation errors", func() { - err := nm.EnsureDirectoryExists("192.0.2.1", "root", "/tmp/test") + err := nm.EnsureDirectoryExists("", "192.0.2.1", "root", "/tmp/test") Expect(err).To(HaveOccurred()) }) From f674352ceb3ba6ceb14270cbfadcc862fffdb59f Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 16 Dec 2025 12:38:19 +0100 Subject: [PATCH 14/65] test: add tests for InstallK0sCmd --- cli/cmd/install_k0s_test.go | 212 ++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) diff --git a/cli/cmd/install_k0s_test.go b/cli/cmd/install_k0s_test.go index c6b3e207..b1f791b1 100644 --- a/cli/cmd/install_k0s_test.go +++ b/cli/cmd/install_k0s_test.go @@ -4,11 +4,18 @@ package cmd_test import ( + "os" + "path/filepath" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stretchr/testify/mock" + "gopkg.in/yaml.v3" "github.com/codesphere-cloud/oms/cli/cmd" "github.com/codesphere-cloud/oms/internal/env" + "github.com/codesphere-cloud/oms/internal/installer" + "github.com/codesphere-cloud/oms/internal/installer/files" "github.com/codesphere-cloud/oms/internal/util" ) @@ -54,4 +61,209 @@ var _ = Describe("InstallK0sCmd", func() { Expect(err.Error()).To(ContainSubstring("--install-config is required")) }) }) + + Context("InstallK0sFromInstallConfig method", func() { + var ( + mockPM *installer.MockPackageManager + mockK0s *installer.MockK0sManager + tempDir string + ) + + BeforeEach(func() { + mockPM = installer.NewMockPackageManager(GinkgoT()) + mockK0s = installer.NewMockK0sManager(GinkgoT()) + var err error + tempDir, err = os.MkdirTemp("", "install-k0s-test-*") + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + mockPM.AssertExpectations(GinkgoT()) + mockK0s.AssertExpectations(GinkgoT()) + if tempDir != "" { + _ = os.RemoveAll(tempDir) + } + }) + + createTestConfig := func(managedByCodesphere bool) *files.RootConfig { + return &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + City: "Test City", + CountryCode: "US", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: managedByCodesphere, + ControlPlanes: []files.K8sNode{ + {IPAddress: "192.168.1.100"}, + }, + APIServerHost: "api.test.example.com", + }, + Codesphere: files.CodesphereConfig{ + Domain: "test.example.com", + PublicIP: "192.168.1.100", + DeployConfig: files.DeployConfig{ + Images: map[string]files.ImageConfig{}, + }, + Plans: files.PlansConfig{ + HostingPlans: map[int]files.HostingPlan{}, + WorkspacePlans: map[int]files.WorkspacePlan{}, + }, + }, + } + } + + It("fails when install-config file does not exist", func() { + c.Opts.InstallConfig = "/nonexistent/install-config.yaml" + + err := c.InstallK0sFromInstallConfig(mockPM, mockK0s) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to load install-config")) + }) + + It("fails when install-config specifies external Kubernetes", func() { + config := createTestConfig(false) + configPath := filepath.Join(tempDir, "install-config.yaml") + configData, err := yaml.Marshal(config) + Expect(err).NotTo(HaveOccurred()) + err = os.WriteFile(configPath, configData, 0644) + Expect(err).NotTo(HaveOccurred()) + + c.Opts.InstallConfig = configPath + + err = c.InstallK0sFromInstallConfig(mockPM, mockK0s) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("external Kubernetes")) + }) + + It("successfully installs k0s locally with valid config", func() { + config := createTestConfig(true) + configPath := filepath.Join(tempDir, "install-config.yaml") + configData, err := yaml.Marshal(config) + Expect(err).NotTo(HaveOccurred()) + err = os.WriteFile(configPath, configData, 0644) + Expect(err).NotTo(HaveOccurred()) + + c.Opts.InstallConfig = configPath + c.Opts.Package = "test-package.tar.gz" + c.Opts.Force = true + + mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") + mockK0s.EXPECT().Install(mock.Anything, "/test/path/k0s", true).Return(nil) + + err = c.InstallK0sFromInstallConfig(mockPM, mockK0s) + Expect(err).NotTo(HaveOccurred()) + }) + + It("downloads k0s when package is not specified", func() { + config := createTestConfig(true) + configPath := filepath.Join(tempDir, "install-config.yaml") + configData, err := yaml.Marshal(config) + Expect(err).NotTo(HaveOccurred()) + err = os.WriteFile(configPath, configData, 0644) + Expect(err).NotTo(HaveOccurred()) + + c.Opts.InstallConfig = configPath + c.Opts.Package = "" + c.Opts.Version = "v1.29.0+k0s.0" + + mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") + mockK0s.EXPECT().Download("v1.29.0+k0s.0", false, false).Return("/downloaded/k0s", nil) + mockK0s.EXPECT().Install(mock.Anything, "/downloaded/k0s", false).Return(nil) + + err = c.InstallK0sFromInstallConfig(mockPM, mockK0s) + Expect(err).NotTo(HaveOccurred()) + }) + + It("fails when k0s download fails", func() { + config := createTestConfig(true) + configPath := filepath.Join(tempDir, "install-config.yaml") + configData, err := yaml.Marshal(config) + Expect(err).NotTo(HaveOccurred()) + err = os.WriteFile(configPath, configData, 0644) + Expect(err).NotTo(HaveOccurred()) + + c.Opts.InstallConfig = configPath + c.Opts.Package = "" + + mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") + mockK0s.EXPECT().Download("", false, false).Return("", os.ErrNotExist) + + err = c.InstallK0sFromInstallConfig(mockPM, mockK0s) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to download k0s")) + }) + + It("fails when k0s install fails", func() { + config := createTestConfig(true) + configPath := filepath.Join(tempDir, "install-config.yaml") + configData, err := yaml.Marshal(config) + Expect(err).NotTo(HaveOccurred()) + err = os.WriteFile(configPath, configData, 0644) + Expect(err).NotTo(HaveOccurred()) + + c.Opts.InstallConfig = configPath + c.Opts.Package = "test-package.tar.gz" + + mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") + mockK0s.EXPECT().Install(mock.Anything, "/test/path/k0s", false).Return(os.ErrPermission) + + err = c.InstallK0sFromInstallConfig(mockPM, mockK0s) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to install k0s")) + }) + + It("handles remote installation when remote-host is specified", func() { + config := createTestConfig(true) + configPath := filepath.Join(tempDir, "install-config.yaml") + configData, err := yaml.Marshal(config) + Expect(err).NotTo(HaveOccurred()) + err = os.WriteFile(configPath, configData, 0644) + Expect(err).NotTo(HaveOccurred()) + + c.Opts.InstallConfig = configPath + c.Opts.Package = "test-package.tar.gz" + c.Opts.RemoteHost = "192.168.1.50" + c.Opts.SSHKeyPath = "/path/to/key" + + mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") + + // Remote installation will fail because we can't actually connect, + // but we're testing that it attempts remote installation + err = c.InstallK0sFromInstallConfig(mockPM, mockK0s) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) + }) + }) + + Context("InstallK0sRemote method", func() { + var ( + config *files.RootConfig + ) + + BeforeEach(func() { + config = &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "192.168.1.100"}, + }, + }, + } + }) + + It("fails when SSH connection cannot be established", func() { + c.Opts.RemoteHost = "192.0.2.1" // TEST-NET-1, should fail to connect + c.Opts.SSHKeyPath = "/tmp/nonexistent-key" + + err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) + }) + }) }) From 4e610923120c2c7cf49c72f6f4965e8c680eac9c Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 16 Dec 2025 12:41:00 +0100 Subject: [PATCH 15/65] fix: correct sed command syntax for enabling root login --- internal/installer/node/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 2a5a26bd..355dc635 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -377,7 +377,7 @@ func (n *Node) RunSSHCommand(jumpbox *Node, nm *NodeManager, username string, co func (n *Node) EnableRootLogin(jumpbox *Node, nm *NodeManager) error { cmds := []string{ - "sudo sed-i 's/^#\\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config", + "sudo sed -i 's/^#\\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config", "sudo sed -i 's/no-port-forwarding.*$//g' /root/.ssh/authorized_keys", "sudo systemctl restart sshd", } From 7a407f0e3b3fc10ba938994696aae426424ba800 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:02:44 +0100 Subject: [PATCH 16/65] test: add mock expectations for SSH key file reading in InstallK0sCmd tests --- cli/cmd/install_k0s_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/cmd/install_k0s_test.go b/cli/cmd/install_k0s_test.go index b1f791b1..e82d03c6 100644 --- a/cli/cmd/install_k0s_test.go +++ b/cli/cmd/install_k0s_test.go @@ -228,6 +228,7 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.SSHKeyPath = "/path/to/key" mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") + mockFileWriter.EXPECT().ReadFile("/path/to/key").Return([]byte("invalid-key-data"), nil).Maybe() // Remote installation will fail because we can't actually connect, // but we're testing that it attempts remote installation @@ -261,6 +262,8 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.RemoteHost = "192.0.2.1" // TEST-NET-1, should fail to connect c.Opts.SSHKeyPath = "/tmp/nonexistent-key" + mockFileWriter.EXPECT().ReadFile("/tmp/nonexistent-key").Return([]byte("invalid-key-data"), nil).Maybe() + err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) From 52e86679d9e16ec282ddc2652479d6fd9a04a505 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:58:05 +0100 Subject: [PATCH 17/65] test: add tests for K0s configuration filtering --- internal/installer/k0s_internal_test.go | 339 ++++++++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 internal/installer/k0s_internal_test.go diff --git a/internal/installer/k0s_internal_test.go b/internal/installer/k0s_internal_test.go new file mode 100644 index 00000000..83ecc537 --- /dev/null +++ b/internal/installer/k0s_internal_test.go @@ -0,0 +1,339 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package installer + +import ( + "os" + "path/filepath" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "gopkg.in/yaml.v3" +) + +var _ = Describe("K0s Internal Methods", func() { + var ( + k0s *K0s + tempConfigDir string + ) + + BeforeEach(func() { + k0s = &K0s{} + var err error + tempConfigDir, err = os.MkdirTemp("", "k0s-config-test-*") + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + if tempConfigDir != "" { + _ = os.RemoveAll(tempConfigDir) + } + }) + + createConfigFile := func(content string) string { + configPath := filepath.Join(tempConfigDir, "test-config.yaml") + err := os.WriteFile(configPath, []byte(content), 0644) + Expect(err).NotTo(HaveOccurred()) + return configPath + } + + Describe("filterConfigForK0s", func() { + It("filters out non-k0s top-level fields", func() { + configContent := `apiVersion: k0s.k0sproject.io/v1beta1 +kind: ClusterConfig +metadata: + name: test-cluster +spec: + api: + address: 192.168.1.100 +extraField: should-be-removed +anotherExtra: also-removed +` + configPath := createConfigFile(configContent) + + filteredPath, err := k0s.filterConfigForK0s(configPath) + Expect(err).NotTo(HaveOccurred()) + Expect(filteredPath).NotTo(BeEmpty()) + defer func() { _ = os.Remove(filteredPath) }() + + // Read and verify filtered content + data, err := os.ReadFile(filteredPath) + Expect(err).NotTo(HaveOccurred()) + content := string(data) + + Expect(content).To(ContainSubstring("apiVersion")) + Expect(content).To(ContainSubstring("kind")) + Expect(content).To(ContainSubstring("metadata")) + Expect(content).To(ContainSubstring("spec")) + Expect(content).NotTo(ContainSubstring("extraField")) + Expect(content).NotTo(ContainSubstring("anotherExtra")) + }) + + It("preserves all expected k0s fields at top level", func() { + configContent := `apiVersion: k0s.k0sproject.io/v1beta1 +kind: ClusterConfig +metadata: + name: test-cluster +spec: + api: + address: 192.168.1.100 +` + configPath := createConfigFile(configContent) + + filteredPath, err := k0s.filterConfigForK0s(configPath) + Expect(err).NotTo(HaveOccurred()) + Expect(filteredPath).NotTo(BeEmpty()) + defer func() { _ = os.Remove(filteredPath) }() + + data, err := os.ReadFile(filteredPath) + Expect(err).NotTo(HaveOccurred()) + content := string(data) + + Expect(content).To(ContainSubstring("apiVersion: k0s.k0sproject.io/v1beta1")) + Expect(content).To(ContainSubstring("kind: ClusterConfig")) + Expect(content).To(ContainSubstring("metadata")) + Expect(content).To(ContainSubstring("spec")) + }) + + It("filters out non-k0s spec fields", func() { + configContent := `apiVersion: k0s.k0sproject.io/v1beta1 +kind: ClusterConfig +spec: + api: + address: 192.168.1.100 + network: + provider: calico + customField: should-be-removed + anotherCustom: also-removed +` + configPath := createConfigFile(configContent) + + filteredPath, err := k0s.filterConfigForK0s(configPath) + Expect(err).NotTo(HaveOccurred()) + Expect(filteredPath).NotTo(BeEmpty()) + defer func() { _ = os.Remove(filteredPath) }() + + data, err := os.ReadFile(filteredPath) + Expect(err).NotTo(HaveOccurred()) + content := string(data) + + Expect(content).To(ContainSubstring("api")) + Expect(content).To(ContainSubstring("network")) + Expect(content).NotTo(ContainSubstring("customField")) + Expect(content).NotTo(ContainSubstring("anotherCustom")) + }) + + It("preserves all expected k0s spec fields", func() { + configContent := `apiVersion: k0s.k0sproject.io/v1beta1 +kind: ClusterConfig +spec: + api: + address: 192.168.1.100 + controllerManager: + extraArgs: + - --cluster-cidr=10.244.0.0/16 + scheduler: + extraArgs: + - --bind-address=0.0.0.0 + extensions: + helm: + repositories: + - name: stable + network: + provider: calico + storage: + type: etcd + telemetry: + enabled: false + images: + default_pull_policy: IfNotPresent + konnectivity: + enabled: true +` + configPath := createConfigFile(configContent) + + filteredPath, err := k0s.filterConfigForK0s(configPath) + Expect(err).NotTo(HaveOccurred()) + Expect(filteredPath).NotTo(BeEmpty()) + defer func() { _ = os.Remove(filteredPath) }() + + data, err := os.ReadFile(filteredPath) + Expect(err).NotTo(HaveOccurred()) + content := string(data) + + // Verify all expected spec fields are preserved + Expect(content).To(ContainSubstring("api")) + Expect(content).To(ContainSubstring("controllerManager")) + Expect(content).To(ContainSubstring("scheduler")) + Expect(content).To(ContainSubstring("extensions")) + Expect(content).To(ContainSubstring("network")) + Expect(content).To(ContainSubstring("storage")) + Expect(content).To(ContainSubstring("telemetry")) + Expect(content).To(ContainSubstring("images")) + Expect(content).To(ContainSubstring("konnectivity")) + }) + + It("handles config with only required fields", func() { + configContent := `apiVersion: k0s.k0sproject.io/v1beta1 +kind: ClusterConfig +spec: + api: + address: 192.168.1.100 +` + configPath := createConfigFile(configContent) + + filteredPath, err := k0s.filterConfigForK0s(configPath) + Expect(err).NotTo(HaveOccurred()) + Expect(filteredPath).NotTo(BeEmpty()) + defer func() { _ = os.Remove(filteredPath) }() + + data, err := os.ReadFile(filteredPath) + Expect(err).NotTo(HaveOccurred()) + + Expect(string(data)).NotTo(BeEmpty()) + }) + + It("creates a temporary file with .yaml extension", func() { + configContent := `apiVersion: k0s.k0sproject.io/v1beta1 +kind: ClusterConfig +spec: + api: + address: 192.168.1.100 +` + configPath := createConfigFile(configContent) + + filteredPath, err := k0s.filterConfigForK0s(configPath) + Expect(err).NotTo(HaveOccurred()) + Expect(filteredPath).NotTo(BeEmpty()) + defer func() { _ = os.Remove(filteredPath) }() + + Expect(filteredPath).To(HaveSuffix(".yaml")) + Expect(filteredPath).To(ContainSubstring("k0s-config-")) + + // Verify file exists and is readable + _, err = os.Stat(filteredPath) + Expect(err).NotTo(HaveOccurred()) + }) + + It("fails when config file does not exist", func() { + _, err := k0s.filterConfigForK0s("/nonexistent/config.yaml") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to read config")) + }) + + It("fails when config contains invalid YAML", func() { + configPath := createConfigFile("invalid: yaml: content: [") + + _, err := k0s.filterConfigForK0s(configPath) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to parse config")) + }) + + It("handles complex nested structures correctly", func() { + configContent := `apiVersion: k0s.k0sproject.io/v1beta1 +kind: ClusterConfig +metadata: + name: production-cluster +spec: + api: + address: 192.168.1.100 + port: 6443 + sans: + - api.example.com + - 192.168.1.100 + network: + provider: calico + podCIDR: 10.244.0.0/16 + serviceCIDR: 10.96.0.0/12 + extensions: + helm: + repositories: + - name: stable + url: https://charts.helm.sh/stable + charts: + - name: metrics-server + namespace: kube-system +customTopLevel: should-be-filtered +` + configPath := createConfigFile(configContent) + + filteredPath, err := k0s.filterConfigForK0s(configPath) + Expect(err).NotTo(HaveOccurred()) + Expect(filteredPath).NotTo(BeEmpty()) + defer func() { _ = os.Remove(filteredPath) }() + + data, err := os.ReadFile(filteredPath) + Expect(err).NotTo(HaveOccurred()) + content := string(data) + + // Verify nested structures are preserved + Expect(content).To(ContainSubstring("api.example.com")) + Expect(content).To(ContainSubstring("10.244.0.0/16")) + Expect(content).To(ContainSubstring("metrics-server")) + + // Verify custom fields are filtered out + Expect(content).NotTo(ContainSubstring("customTopLevel")) + }) + + It("filters custom fields within spec", func() { + configContent := `apiVersion: k0s.k0sproject.io/v1beta1 +kind: ClusterConfig +spec: + api: + address: 192.168.1.100 + network: + provider: calico + customInSpec: should-be-filtered + anotherCustom: also-filtered +` + configPath := createConfigFile(configContent) + + filteredPath, err := k0s.filterConfigForK0s(configPath) + Expect(err).NotTo(HaveOccurred()) + Expect(filteredPath).NotTo(BeEmpty()) + defer func() { _ = os.Remove(filteredPath) }() + + data, err := os.ReadFile(filteredPath) + Expect(err).NotTo(HaveOccurred()) + content := string(data) + + Expect(content).To(ContainSubstring("api")) + Expect(content).To(ContainSubstring("network")) + Expect(content).NotTo(ContainSubstring("customInSpec")) + Expect(content).NotTo(ContainSubstring("anotherCustom")) + }) + + It("returns valid YAML that can be parsed", func() { + configContent := `apiVersion: k0s.k0sproject.io/v1beta1 +kind: ClusterConfig +spec: + api: + address: 192.168.1.100 + network: + provider: calico +extraField: removed +` + configPath := createConfigFile(configContent) + + filteredPath, err := k0s.filterConfigForK0s(configPath) + Expect(err).NotTo(HaveOccurred()) + Expect(filteredPath).NotTo(BeEmpty()) + defer func() { _ = os.Remove(filteredPath) }() + + // Verify the output is valid YAML by parsing it + data, err := os.ReadFile(filteredPath) + Expect(err).NotTo(HaveOccurred()) + + var result map[string]interface{} + err = yaml.Unmarshal(data, &result) + Expect(err).NotTo(HaveOccurred()) + + // Verify expected structure + Expect(result).To(HaveKey("apiVersion")) + Expect(result).To(HaveKey("kind")) + Expect(result).To(HaveKey("spec")) + Expect(result).NotTo(HaveKey("extraField")) + }) + }) +}) From ef0f96cfe2101d4a9ff535942a5a599bd1f66c4f Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 16 Dec 2025 14:10:54 +0100 Subject: [PATCH 18/65] test: add tests for Reset functionality in K0s --- internal/installer/k0s.go | 2 ++ internal/installer/k0s_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/internal/installer/k0s.go b/internal/installer/k0s.go index 422268e5..f5fd3c37 100644 --- a/internal/installer/k0s.go +++ b/internal/installer/k0s.go @@ -215,6 +215,8 @@ func (k *K0s) filterConfigForK0s(configPath string) (string, error) { return tmpFile.Name(), nil } +// Reset tears down an existing k0s installation by executing `k0s reset`. +// This command removes all k0s-related resources func (k *K0s) Reset(k0sPath string) error { if !k.FileWriter.Exists(k0sPath) { return nil diff --git a/internal/installer/k0s_test.go b/internal/installer/k0s_test.go index 83b9109f..ba863a5c 100644 --- a/internal/installer/k0s_test.go +++ b/internal/installer/k0s_test.go @@ -321,4 +321,32 @@ var _ = Describe("K0s", func() { }) }) }) + + Describe("Reset", func() { + BeforeEach(func() { + k0sImpl.Goos = "linux" + k0sImpl.Goarch = "amd64" + }) + + Context("when k0s binary does not exist", func() { + It("should return nil without attempting reset", func() { + mockFileWriter.EXPECT().Exists(k0sPath).Return(false) + + err := k0s.Reset(k0sPath) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("platform validation", func() { + It("should work regardless of platform for reset", func() { + k0sImpl.Goos = "darwin" + k0sImpl.Goarch = "arm64" + + mockFileWriter.EXPECT().Exists(k0sPath).Return(false) + + err := k0s.Reset(k0sPath) + Expect(err).NotTo(HaveOccurred()) + }) + }) + }) }) From 4b49bd603f7b9d3aef38d5c439d84e1ac607319b Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 16 Dec 2025 14:30:16 +0100 Subject: [PATCH 19/65] test: add tests for SSH key file handling in InstallK0sRemote method --- cli/cmd/install_k0s_test.go | 102 ++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/cli/cmd/install_k0s_test.go b/cli/cmd/install_k0s_test.go index e82d03c6..cbb70c87 100644 --- a/cli/cmd/install_k0s_test.go +++ b/cli/cmd/install_k0s_test.go @@ -268,5 +268,107 @@ var _ = Describe("InstallK0sCmd", func() { Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) }) + + It("fails when SSH key file does not exist", func() { + c.Opts.RemoteHost = "192.168.1.50" + c.Opts.SSHKeyPath = "/nonexistent/ssh/key" + + mockFileWriter.EXPECT().ReadFile("/nonexistent/ssh/key").Return(nil, os.ErrNotExist).Maybe() + + err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) + }) + + It("fails when SSH key file is invalid", func() { + c.Opts.RemoteHost = "192.168.1.50" + c.Opts.SSHKeyPath = "/path/to/invalid/key" + + mockFileWriter.EXPECT().ReadFile("/path/to/invalid/key").Return([]byte("not-a-valid-key"), nil).Maybe() + + err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) + }) + + It("uses correct remote host IP for node configuration", func() { + c.Opts.RemoteHost = "10.0.0.50" + c.Opts.SSHKeyPath = "/path/to/key" + + mockFileWriter.EXPECT().ReadFile("/path/to/key").Return([]byte("ssh-key-data"), nil).Maybe() + + err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) + }) + + It("passes correct paths to InstallK0s", func() { + c.Opts.RemoteHost = "192.168.1.60" + c.Opts.SSHKeyPath = "/custom/ssh/key" + c.Opts.Force = true + + mockFileWriter.EXPECT().ReadFile("/custom/ssh/key").Return([]byte("ssh-key-data"), nil).Maybe() + + err := c.InstallK0sRemote(config, "/custom/k0s/path", "/custom/config/path") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) + }) + + It("respects the force flag", func() { + c.Opts.RemoteHost = "192.168.1.70" + c.Opts.SSHKeyPath = "/path/to/key" + c.Opts.Force = true + + mockFileWriter.EXPECT().ReadFile("/path/to/key").Return([]byte("ssh-key-data"), nil).Maybe() + + err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) + }) + + It("uses remote user from options", func() { + c.Opts.RemoteHost = "192.168.1.80" + c.Opts.SSHKeyPath = "/path/to/key" + c.Opts.RemoteUser = "ubuntu" + + mockFileWriter.EXPECT().ReadFile("/path/to/key").Return([]byte("ssh-key-data"), nil).Maybe() + + err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) + }) + + It("handles empty remote host", func() { + c.Opts.RemoteHost = "" + c.Opts.SSHKeyPath = "/path/to/key" + + mockFileWriter.EXPECT().ReadFile("/path/to/key").Return([]byte("ssh-key-data"), nil).Maybe() + + err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) + }) + + It("handles timeout during SSH connection", func() { + c.Opts.RemoteHost = "192.0.2.1" // TEST-NET-1 address + c.Opts.SSHKeyPath = "/path/to/key" + + mockFileWriter.EXPECT().ReadFile("/path/to/key").Return([]byte("-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----"), nil).Maybe() + + err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) + }) + + It("wraps errors from InstallK0s with context", func() { + c.Opts.RemoteHost = "10.0.0.100" + c.Opts.SSHKeyPath = "/path/to/key" + + mockFileWriter.EXPECT().ReadFile("/path/to/key").Return([]byte("ssh-key-data"), nil).Maybe() + + err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) + }) }) }) From e3a9e188aad04f5210fa76d2d2b80ae2e3aefa31 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:06:24 +0100 Subject: [PATCH 20/65] fix: fix k0s installation path and config handling --- cli/cmd/install_k0s.go | 16 ++++++++++------ hack/lima-oms.yaml | 2 +- internal/installer/k0s.go | 11 +++++++++-- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index b5b09932..fb50ea20 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -118,13 +118,17 @@ func (c *InstallK0sCmd) InstallK0sFromInstallConfig(pm installer.PackageManager, return fmt.Errorf("failed to marshal k0s config: %w", err) } - tmpK0sConfigPath := filepath.Join(os.TempDir(), "k0s-config.yaml") - if err := os.WriteFile(tmpK0sConfigPath, k0sConfigData, 0644); err != nil { + k0sConfigPath := "/etc/k0s/k0s.yaml" + + if err := os.MkdirAll(filepath.Dir(k0sConfigPath), 0755); err != nil { + return fmt.Errorf("failed to create k0s config directory: %w", err) + } + + if err := os.WriteFile(k0sConfigPath, k0sConfigData, 0644); err != nil { return fmt.Errorf("failed to write k0s config: %w", err) } - defer func() { _ = os.Remove(tmpK0sConfigPath) }() - log.Printf("Generated k0s configuration at %s", tmpK0sConfigPath) + log.Printf("Generated k0s configuration at %s", k0sConfigPath) k0sPath := pm.GetDependencyPath(defaultK0sPath) if c.Opts.Package == "" { @@ -135,10 +139,10 @@ func (c *InstallK0sCmd) InstallK0sFromInstallConfig(pm installer.PackageManager, } if c.Opts.RemoteHost != "" { - return c.InstallK0sRemote(config, k0sPath, tmpK0sConfigPath) + return c.InstallK0sRemote(config, k0sPath, k0sConfigPath) } - err = k0s.Install(tmpK0sConfigPath, k0sPath, c.Opts.Force) + err = k0s.Install(k0sConfigPath, k0sPath, c.Opts.Force) if err != nil { return fmt.Errorf("failed to install k0s: %w", err) } diff --git a/hack/lima-oms.yaml b/hack/lima-oms.yaml index 631b7966..f744fd83 100644 --- a/hack/lima-oms.yaml +++ b/hack/lima-oms.yaml @@ -94,7 +94,7 @@ message: | ------ limactl shell lima-oms cd oms - ./oms-cli install k0s --k0s-config config.yaml --version v1.30.0+k0s.0 --force + ./oms-cli install k0s --install-config config.yaml --version v1.30.0+k0s.0 --force ------ To install Codesphere (run inside lima): diff --git a/internal/installer/k0s.go b/internal/installer/k0s.go index f5fd3c37..24e90032 100644 --- a/internal/installer/k0s.go +++ b/internal/installer/k0s.go @@ -124,8 +124,15 @@ func (k *K0s) Install(configPath string, k0sPath string, force bool) error { if err != nil { log.Printf("Warning: failed to filter config, using original: %v", err) } else { - configPath = filteredConfigPath - defer func() { _ = os.Remove(filteredConfigPath) }() // Clean up temp file after use + filteredData, err := os.ReadFile(filteredConfigPath) + if err != nil { + log.Printf("Warning: failed to read filtered config: %v", err) + } else { + if err := os.WriteFile(configPath, filteredData, 0644); err != nil { + log.Printf("Warning: failed to write filtered config back: %v", err) + } + } + _ = os.Remove(filteredConfigPath) } args = append(args, "--config", configPath) } else { From e2192fe74c9e570fef7b3a84138c6a7aa014245e Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:24:01 +0100 Subject: [PATCH 21/65] fix: k0s config handling to support temporary paths in tests --- cli/cmd/install_k0s.go | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index fb50ea20..8a6a329d 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -118,18 +118,31 @@ func (c *InstallK0sCmd) InstallK0sFromInstallConfig(pm installer.PackageManager, return fmt.Errorf("failed to marshal k0s config: %w", err) } + // Use /etc/k0s/k0s.yaml for production, but allow using temp directory in tests k0sConfigPath := "/etc/k0s/k0s.yaml" + usedTempPath := false if err := os.MkdirAll(filepath.Dir(k0sConfigPath), 0755); err != nil { - return fmt.Errorf("failed to create k0s config directory: %w", err) + // If we can't write to /etc/k0s (e.g., in tests without root), use temp directory + tmpK0sConfigPath := filepath.Join(os.TempDir(), "k0s-config.yaml") + if err := os.WriteFile(tmpK0sConfigPath, k0sConfigData, 0644); err != nil { + return fmt.Errorf("failed to write k0s config: %w", err) + } + k0sConfigPath = tmpK0sConfigPath + usedTempPath = true + log.Printf("Generated k0s configuration at %s (using temp path due to permissions)", k0sConfigPath) + } else { + if err := os.WriteFile(k0sConfigPath, k0sConfigData, 0644); err != nil { + return fmt.Errorf("failed to write k0s config: %w", err) + } + log.Printf("Generated k0s configuration at %s", k0sConfigPath) } - if err := os.WriteFile(k0sConfigPath, k0sConfigData, 0644); err != nil { - return fmt.Errorf("failed to write k0s config: %w", err) + // Clean up temp file if used (only for testing scenarios) + if usedTempPath { + defer func() { _ = os.Remove(k0sConfigPath) }() } - log.Printf("Generated k0s configuration at %s", k0sConfigPath) - k0sPath := pm.GetDependencyPath(defaultK0sPath) if c.Opts.Package == "" { k0sPath, err = k0s.Download(c.Opts.Version, c.Opts.Force, false) From bb39c0e4b2a0321b85d57414b15857350f35078f Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:17:18 +0100 Subject: [PATCH 22/65] feat: enhance remote k0s installation with user support and SSH instructions --- cli/cmd/install_k0s.go | 1 + hack/lima-oms.yaml | 18 +++++++++ internal/installer/node/node.go | 69 ++++++++++++++++++++++++--------- 3 files changed, 70 insertions(+), 18 deletions(-) diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index 8a6a329d..f50a9126 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -176,6 +176,7 @@ func (c *InstallK0sCmd) InstallK0sRemote(config *files.RootConfig, k0sBinaryPath ExternalIP: c.Opts.RemoteHost, InternalIP: c.Opts.RemoteHost, Name: "k0s-node", + User: c.Opts.RemoteUser, } if err := remoteNode.InstallK0s(nm, k0sBinaryPath, k0sConfigPath, c.Opts.Force); err != nil { diff --git a/hack/lima-oms.yaml b/hack/lima-oms.yaml index f744fd83..8297ad16 100644 --- a/hack/lima-oms.yaml +++ b/hack/lima-oms.yaml @@ -97,6 +97,24 @@ message: | ./oms-cli install k0s --install-config config.yaml --version v1.30.0+k0s.0 --force ------ + To test remote k0s installation (run inside lima): + ------ + limactl shell lima-oms + cd oms + # Setup SSH for testing + ssh-keygen -t rsa -b 4096 -f ~/.ssh/test_key -N "" + cat ~/.ssh/test_key.pub >> ~/.ssh/authorized_keys + chmod 600 ~/.ssh/authorized_keys + # Add host to known_hosts + VM_IP=$(hostname -I | awk '{print $1}') + ssh-keyscan $VM_IP >> ~/.ssh/known_hosts + # Test SSH connection + ssh -i ~/.ssh/test_key $VM_IP "echo 'SSH works'" + # Test remote installation (unit tests validate this feature) + ./oms-cli install k0s --install-config config.yaml --version v1.30.0+k0s.0 \ + --remote-host $VM_IP --remote-user $(whoami) --ssh-key-path ~/.ssh/test_key --force + ------ + To install Codesphere (run inside lima): ------ limactl shell lima-oms diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 355dc635..051fe540 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -25,6 +25,7 @@ type Node struct { Name string `json:"name"` ExternalIP string `json:"external_ip"` InternalIP string `json:"internal_ip"` + User string `json:"user,omitempty"` } type NodeManager struct { @@ -37,6 +38,12 @@ func shellEscape(s string) string { } func (n *NodeManager) getHostKeyCallback() (ssh.HostKeyCallback, error) { + // Required for testing/development via environment variable + if os.Getenv("OMS_SSH_INSECURE") == "true" { + fmt.Println("Warning: Using insecure host key checking (OMS_SSH_INSECURE=true)") + return ssh.InsecureIgnoreHostKey(), nil + } + homeDir, err := os.UserHomeDir() if err != nil { return nil, fmt.Errorf("failed to get user home directory: %w", err) @@ -75,18 +82,23 @@ func (n *NodeManager) getAuthMethods() ([]ssh.AuthMethod, error) { } if n.KeyPath != "" { - fmt.Println("Falling back to private key file authentication.") + fmt.Printf("Falling back to private key file authentication (key: %s).\n", n.KeyPath) key, err := n.FileIO.ReadFile(n.KeyPath) if err != nil { return nil, fmt.Errorf("failed to read private key file %s: %v", n.KeyPath, err) } + fmt.Printf("Successfully read %d bytes from key file\n", len(key)) + signer, err := ssh.ParsePrivateKey(key) if err == nil { + fmt.Printf("Successfully parsed private key (type: %s)\n", signer.PublicKey().Type()) authMethods = append(authMethods, ssh.PublicKeys(signer)) return authMethods, nil } + + fmt.Printf("Failed to parse private key: %v\n", err) if _, ok := err.(*ssh.PassphraseMissingError); ok { fmt.Printf("Enter passphrase for key '%s': ", n.KeyPath) passphraseBytes, err := term.ReadPassword(int(syscall.Stdin)) @@ -323,11 +335,16 @@ func (n *Node) InstallOms(nm *NodeManager) error { } func (n *Node) CopyFile(nm *NodeManager, src string, dst string) error { - err := nm.EnsureDirectoryExists("", n.ExternalIP, "root", filepath.Dir(dst)) + user := n.User + if user == "" { + user = "root" + } + + err := nm.EnsureDirectoryExists("", n.ExternalIP, user, filepath.Dir(dst)) if err != nil { return fmt.Errorf("failed to ensure directory exists: %w", err) } - return nm.CopyFile("", n.ExternalIP, "root", src, dst) + return nm.CopyFile("", n.ExternalIP, user, src, dst) } func (n *Node) HasAcceptEnvConfigured(jumpbox *Node, nm *NodeManager) bool { @@ -395,24 +412,40 @@ func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath s remoteK0sBinary := filepath.Join(remoteK0sDir, "k0s") remoteConfigPath := "/etc/k0s/k0s.yaml" - log.Printf("Copying k0s binary to %s:%s", n.ExternalIP, remoteK0sBinary) - if err := n.CopyFile(nm, k0sBinaryPath, remoteK0sBinary); err != nil { - return fmt.Errorf("failed to copy k0s binary: %w", err) + user := n.User + if user == "" { + user = "root" + } + + // Copy k0s binary to temp location first, then move with sudo + tmpK0sBinary := "/tmp/k0s" + log.Printf("Copying k0s binary to %s:%s", n.ExternalIP, tmpK0sBinary) + if err := nm.CopyFile("", n.ExternalIP, user, k0sBinaryPath, tmpK0sBinary); err != nil { + return fmt.Errorf("failed to copy k0s binary to temp: %w", err) } - log.Printf("Making k0s binary executable on %s", n.ExternalIP) - chmodCmd := fmt.Sprintf("chmod +x '%s'", shellEscape(remoteK0sBinary)) - if err := nm.RunSSHCommand("", n.ExternalIP, "root", chmodCmd); err != nil { - return fmt.Errorf("failed to make k0s binary executable: %w", err) + // Move to final location and make executable with sudo + log.Printf("Moving k0s binary to %s", remoteK0sBinary) + moveCmd := fmt.Sprintf("sudo mv '%s' '%s' && sudo chmod +x '%s'", + shellEscape(tmpK0sBinary), shellEscape(remoteK0sBinary), shellEscape(remoteK0sBinary)) + if err := nm.RunSSHCommand("", n.ExternalIP, user, moveCmd); err != nil { + return fmt.Errorf("failed to move and chmod k0s binary: %w", err) } if k0sConfigPath != "" { - log.Printf("Copying k0s config to %s:%s", n.ExternalIP, remoteConfigPath) - if err := nm.EnsureDirectoryExists("", n.ExternalIP, "root", "/etc/k0s"); err != nil { - return fmt.Errorf("failed to create /etc/k0s directory: %w", err) + // Copy config to temp location first + tmpConfigPath := "/tmp/k0s-config.yaml" + log.Printf("Copying k0s config to %s", tmpConfigPath) + if err := nm.CopyFile("", n.ExternalIP, user, k0sConfigPath, tmpConfigPath); err != nil { + return fmt.Errorf("failed to copy k0s config to temp: %w", err) } - if err := nm.CopyFile("", n.ExternalIP, "root", k0sConfigPath, remoteConfigPath); err != nil { - return fmt.Errorf("failed to copy k0s config: %w", err) + + // Create /etc/k0s directory and move config with sudo + log.Printf("Moving k0s config to %s", remoteConfigPath) + setupConfigCmd := fmt.Sprintf("sudo mkdir -p /etc/k0s && sudo mv '%s' '%s' && sudo chmod 644 '%s'", + shellEscape(tmpConfigPath), shellEscape(remoteConfigPath), shellEscape(remoteConfigPath)) + if err := nm.RunSSHCommand("", n.ExternalIP, user, setupConfigCmd); err != nil { + return fmt.Errorf("failed to setup k0s config: %w", err) } } @@ -427,13 +460,13 @@ func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath s } log.Printf("Installing k0s on %s", n.ExternalIP) - if err := nm.RunSSHCommand("", n.ExternalIP, "root", installCmd); err != nil { + if err := nm.RunSSHCommand("", n.ExternalIP, user, installCmd); err != nil { return fmt.Errorf("failed to install k0s: %w", err) } log.Printf("k0s successfully installed on %s", n.ExternalIP) - log.Printf("You can start it using: ssh root@%s 'sudo %s start'", n.ExternalIP, shellEscape(remoteK0sBinary)) - log.Printf("You can check the status using: ssh root@%s 'sudo %s status'", n.ExternalIP, shellEscape(remoteK0sBinary)) + log.Printf("You can start it using: ssh %s@%s 'sudo %s start'", user, n.ExternalIP, shellEscape(remoteK0sBinary)) + log.Printf("You can check the status using: ssh %s@%s 'sudo %s status'", user, n.ExternalIP, shellEscape(remoteK0sBinary)) return nil } From fdcb21444bc534cb3c6eca90dd79778663bc1fc8 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:33:28 +0100 Subject: [PATCH 23/65] ref: minor --- cli/cmd/install_k0s.go | 14 +++++--------- cli/cmd/install_k0s_test.go | 14 +++++++------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index f50a9126..2938eff0 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -45,11 +45,7 @@ func (c *InstallK0sCmd) RunE(_ *cobra.Command, args []string) error { pm := installer.NewPackage(env.GetOmsWorkdir(), c.Opts.Package) k0s := installer.NewK0s(hw, env, c.FileWriter) - if c.Opts.InstallConfig == "" { - return fmt.Errorf("--install-config is required") - } - - return c.InstallK0sFromInstallConfig(pm, k0s) + return c.InstallK0s(pm, k0s) } func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { @@ -86,6 +82,7 @@ func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { k0s.cmd.Flags().StringVar(&k0s.Opts.RemoteUser, "remote-user", "root", "Remote user for SSH connection") k0s.cmd.Flags().BoolVarP(&k0s.Opts.Force, "force", "f", false, "Force new download and installation") + k0s.cmd.MarkFlagRequired("install-config") k0s.cmd.MarkFlagsRequiredTogether("remote-host", "ssh-key-path") install.AddCommand(k0s.cmd) @@ -95,7 +92,7 @@ func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { const defaultK0sPath = "kubernetes/files/k0s" -func (c *InstallK0sCmd) InstallK0sFromInstallConfig(pm installer.PackageManager, k0s installer.K0sManager) error { +func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0sManager) error { icg := installer.NewInstallConfigManager() if err := icg.LoadInstallConfigFromFile(c.Opts.InstallConfig); err != nil { return fmt.Errorf("failed to load install-config: %w", err) @@ -118,12 +115,11 @@ func (c *InstallK0sCmd) InstallK0sFromInstallConfig(pm installer.PackageManager, return fmt.Errorf("failed to marshal k0s config: %w", err) } - // Use /etc/k0s/k0s.yaml for production, but allow using temp directory in tests + // allow temp directory in tests k0sConfigPath := "/etc/k0s/k0s.yaml" usedTempPath := false if err := os.MkdirAll(filepath.Dir(k0sConfigPath), 0755); err != nil { - // If we can't write to /etc/k0s (e.g., in tests without root), use temp directory tmpK0sConfigPath := filepath.Join(os.TempDir(), "k0s-config.yaml") if err := os.WriteFile(tmpK0sConfigPath, k0sConfigData, 0644); err != nil { return fmt.Errorf("failed to write k0s config: %w", err) @@ -138,7 +134,7 @@ func (c *InstallK0sCmd) InstallK0sFromInstallConfig(pm installer.PackageManager, log.Printf("Generated k0s configuration at %s", k0sConfigPath) } - // Clean up temp file if used (only for testing scenarios) + // Clean up temp file if used if usedTempPath { defer func() { _ = os.Remove(k0sConfigPath) }() } diff --git a/cli/cmd/install_k0s_test.go b/cli/cmd/install_k0s_test.go index cbb70c87..af9dffa9 100644 --- a/cli/cmd/install_k0s_test.go +++ b/cli/cmd/install_k0s_test.go @@ -117,7 +117,7 @@ var _ = Describe("InstallK0sCmd", func() { It("fails when install-config file does not exist", func() { c.Opts.InstallConfig = "/nonexistent/install-config.yaml" - err := c.InstallK0sFromInstallConfig(mockPM, mockK0s) + err := c.InstallK0s(mockPM, mockK0s) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to load install-config")) }) @@ -132,7 +132,7 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.InstallConfig = configPath - err = c.InstallK0sFromInstallConfig(mockPM, mockK0s) + err = c.InstallK0s(mockPM, mockK0s) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("external Kubernetes")) }) @@ -152,7 +152,7 @@ var _ = Describe("InstallK0sCmd", func() { mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") mockK0s.EXPECT().Install(mock.Anything, "/test/path/k0s", true).Return(nil) - err = c.InstallK0sFromInstallConfig(mockPM, mockK0s) + err = c.InstallK0s(mockPM, mockK0s) Expect(err).NotTo(HaveOccurred()) }) @@ -172,7 +172,7 @@ var _ = Describe("InstallK0sCmd", func() { mockK0s.EXPECT().Download("v1.29.0+k0s.0", false, false).Return("/downloaded/k0s", nil) mockK0s.EXPECT().Install(mock.Anything, "/downloaded/k0s", false).Return(nil) - err = c.InstallK0sFromInstallConfig(mockPM, mockK0s) + err = c.InstallK0s(mockPM, mockK0s) Expect(err).NotTo(HaveOccurred()) }) @@ -190,7 +190,7 @@ var _ = Describe("InstallK0sCmd", func() { mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") mockK0s.EXPECT().Download("", false, false).Return("", os.ErrNotExist) - err = c.InstallK0sFromInstallConfig(mockPM, mockK0s) + err = c.InstallK0s(mockPM, mockK0s) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to download k0s")) }) @@ -209,7 +209,7 @@ var _ = Describe("InstallK0sCmd", func() { mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") mockK0s.EXPECT().Install(mock.Anything, "/test/path/k0s", false).Return(os.ErrPermission) - err = c.InstallK0sFromInstallConfig(mockPM, mockK0s) + err = c.InstallK0s(mockPM, mockK0s) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to install k0s")) }) @@ -232,7 +232,7 @@ var _ = Describe("InstallK0sCmd", func() { // Remote installation will fail because we can't actually connect, // but we're testing that it attempts remote installation - err = c.InstallK0sFromInstallConfig(mockPM, mockK0s) + err = c.InstallK0s(mockPM, mockK0s) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) }) From 2f6fa295c718f21df584bfe353399d25224f39f2 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Thu, 18 Dec 2025 10:58:15 +0100 Subject: [PATCH 24/65] fix: error message for missing install-config in RunE method --- cli/cmd/install_k0s_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/cmd/install_k0s_test.go b/cli/cmd/install_k0s_test.go index af9dffa9..43573d54 100644 --- a/cli/cmd/install_k0s_test.go +++ b/cli/cmd/install_k0s_test.go @@ -58,7 +58,7 @@ var _ = Describe("InstallK0sCmd", func() { err := c.RunE(nil, nil) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("--install-config is required")) + Expect(err.Error()).To(ContainSubstring("install-config")) }) }) From 2ad025147dd7f630142b0a9adc5300e45e7845e9 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:03:54 +0100 Subject: [PATCH 25/65] fix: appease make lint --- cli/cmd/install_k0s.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index 2938eff0..4eb70434 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -82,7 +82,7 @@ func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { k0s.cmd.Flags().StringVar(&k0s.Opts.RemoteUser, "remote-user", "root", "Remote user for SSH connection") k0s.cmd.Flags().BoolVarP(&k0s.Opts.Force, "force", "f", false, "Force new download and installation") - k0s.cmd.MarkFlagRequired("install-config") + _ = k0s.cmd.MarkFlagRequired("install-config") k0s.cmd.MarkFlagsRequiredTogether("remote-host", "ssh-key-path") install.AddCommand(k0s.cmd) From 54ae71c7270441d82d6dff39dbfad2b21ff6d399 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:43:43 +0100 Subject: [PATCH 26/65] Update internal/installer/node/node.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/installer/node/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 051fe540..8f56eb5d 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -228,7 +228,7 @@ func (n *NodeManager) GetClient(jumpboxIp string, ip string, username string) (* } if jumpboxIp != "" { - jbClient, err := n.connectToJumpbox(jumpboxIp, "ubuntu") + jbClient, err := n.connectToJumpbox(jumpboxIp, username) if err != nil { return nil, fmt.Errorf("failed to connect to jumpbox: %v", err) } From a5faa2c5bd89f0c48ab431329331d806373abf69 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:21:23 +0100 Subject: [PATCH 27/65] fix: improve regex for enabling root login in SSH configuration --- internal/installer/node/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 8f56eb5d..9d0887c7 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -394,7 +394,7 @@ func (n *Node) RunSSHCommand(jumpbox *Node, nm *NodeManager, username string, co func (n *Node) EnableRootLogin(jumpbox *Node, nm *NodeManager) error { cmds := []string{ - "sudo sed -i 's/^#\\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config", + "sudo sed -i -E 's/^[[:space:]]*(#[[:space:]]*)?PermitRootLogin[[:space:]]+(yes|no|prohibit-password|without-password)[[:space:]]*$/PermitRootLogin yes/' /etc/ssh/sshd_config", "sudo sed -i 's/no-port-forwarding.*$//g' /root/.ssh/authorized_keys", "sudo systemctl restart sshd", } From d1204842fe1be37de36583f48610068d5286a090 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:22:16 +0100 Subject: [PATCH 28/65] Update internal/installer/node/node.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/installer/node/node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 9d0887c7..54569680 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -389,7 +389,7 @@ func (n *Node) RunSSHCommand(jumpbox *Node, nm *NodeManager, username string, co return nm.RunSSHCommand("", n.ExternalIP, username, command) } - return nm.RunSSHCommand(jumpbox.ExternalIP, n.InternalIP, "ubuntu", command) + return nm.RunSSHCommand(jumpbox.ExternalIP, n.InternalIP, username, command) } func (n *Node) EnableRootLogin(jumpbox *Node, nm *NodeManager) error { From 8e92d3a4eb61998b143d8a037dba9637efced68a Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:00:24 +0100 Subject: [PATCH 29/65] fix: update SSH installation notes and remove insecure host key warning --- hack/lima-oms.yaml | 3 ++- internal/installer/node/node.go | 6 ------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/hack/lima-oms.yaml b/hack/lima-oms.yaml index 8297ad16..83c11db7 100644 --- a/hack/lima-oms.yaml +++ b/hack/lima-oms.yaml @@ -110,7 +110,8 @@ message: | ssh-keyscan $VM_IP >> ~/.ssh/known_hosts # Test SSH connection ssh -i ~/.ssh/test_key $VM_IP "echo 'SSH works'" - # Test remote installation (unit tests validate this feature) + # Note: Remote installation requires proper SSH host key verification + # For testing, you can add the host to known_hosts (already done above with ssh-keyscan) ./oms-cli install k0s --install-config config.yaml --version v1.30.0+k0s.0 \ --remote-host $VM_IP --remote-user $(whoami) --ssh-key-path ~/.ssh/test_key --force ------ diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 9d0887c7..a8dac882 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -38,12 +38,6 @@ func shellEscape(s string) string { } func (n *NodeManager) getHostKeyCallback() (ssh.HostKeyCallback, error) { - // Required for testing/development via environment variable - if os.Getenv("OMS_SSH_INSECURE") == "true" { - fmt.Println("Warning: Using insecure host key checking (OMS_SSH_INSECURE=true)") - return ssh.InsecureIgnoreHostKey(), nil - } - homeDir, err := os.UserHomeDir() if err != nil { return nil, fmt.Errorf("failed to get user home directory: %w", err) From 6e80a788e15b1ced0df3e73ac59d5554f55135a2 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:42:07 +0100 Subject: [PATCH 30/65] refactor: standardize receiver names in NodeManager methods for consistency --- cli/cmd/install_k0s.go | 9 +-- internal/installer/node/node.go | 103 +++++++++++++++++++------------- 2 files changed, 65 insertions(+), 47 deletions(-) diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index 4eb70434..c2735304 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -117,7 +117,6 @@ func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0 // allow temp directory in tests k0sConfigPath := "/etc/k0s/k0s.yaml" - usedTempPath := false if err := os.MkdirAll(filepath.Dir(k0sConfigPath), 0755); err != nil { tmpK0sConfigPath := filepath.Join(os.TempDir(), "k0s-config.yaml") @@ -125,7 +124,8 @@ func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0 return fmt.Errorf("failed to write k0s config: %w", err) } k0sConfigPath = tmpK0sConfigPath - usedTempPath = true + // Clean up temp file on all exit paths + defer func() { _ = os.Remove(k0sConfigPath) }() log.Printf("Generated k0s configuration at %s (using temp path due to permissions)", k0sConfigPath) } else { if err := os.WriteFile(k0sConfigPath, k0sConfigData, 0644); err != nil { @@ -134,11 +134,6 @@ func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0 log.Printf("Generated k0s configuration at %s", k0sConfigPath) } - // Clean up temp file if used - if usedTempPath { - defer func() { _ = os.Remove(k0sConfigPath) }() - } - k0sPath := pm.GetDependencyPath(defaultK0sPath) if c.Opts.Package == "" { k0sPath, err = k0s.Download(c.Opts.Version, c.Opts.Force, false) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index b382bd07..d5892b63 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -37,7 +37,7 @@ func shellEscape(s string) string { return strings.ReplaceAll(s, "'", "'\\''") } -func (n *NodeManager) getHostKeyCallback() (ssh.HostKeyCallback, error) { +func (nm *NodeManager) getHostKeyCallback() (ssh.HostKeyCallback, error) { homeDir, err := os.UserHomeDir() if err != nil { return nil, fmt.Errorf("failed to get user home directory: %w", err) @@ -62,7 +62,7 @@ func (n *NodeManager) getHostKeyCallback() (ssh.HostKeyCallback, error) { return hostKeyCallback, nil } -func (n *NodeManager) getAuthMethods() ([]ssh.AuthMethod, error) { +func (nm *NodeManager) getAuthMethods() ([]ssh.AuthMethod, error) { var authMethods []ssh.AuthMethod if authSocket := os.Getenv("SSH_AUTH_SOCK"); authSocket != "" { @@ -75,45 +75,68 @@ func (n *NodeManager) getAuthMethods() ([]ssh.AuthMethod, error) { fmt.Printf("Could not connect to SSH Agent (%s): %v\n", authSocket, err) } - if n.KeyPath != "" { - fmt.Printf("Falling back to private key file authentication (key: %s).\n", n.KeyPath) + if nm.KeyPath != "" { + fmt.Printf("Falling back to private key file authentication (key: %s).\n", nm.KeyPath) - key, err := n.FileIO.ReadFile(n.KeyPath) + key, err := nm.FileIO.ReadFile(nm.KeyPath) if err != nil { - return nil, fmt.Errorf("failed to read private key file %s: %v", n.KeyPath, err) + return nil, fmt.Errorf("failed to read private key file %s: %v", nm.KeyPath, err) } - fmt.Printf("Successfully read %d bytes from key file\n", len(key)) + fmt.Printf("Successfully read %d bytes from key file\\n", len(key)) signer, err := ssh.ParsePrivateKey(key) if err == nil { - fmt.Printf("Successfully parsed private key (type: %s)\n", signer.PublicKey().Type()) + fmt.Printf("Successfully parsed private key (type: %s)\\n", signer.PublicKey().Type()) authMethods = append(authMethods, ssh.PublicKeys(signer)) return authMethods, nil } - fmt.Printf("Failed to parse private key: %v\n", err) + fmt.Printf("Failed to parse private key: %v\\n", err) if _, ok := err.(*ssh.PassphraseMissingError); ok { - fmt.Printf("Enter passphrase for key '%s': ", n.KeyPath) - passphraseBytes, err := term.ReadPassword(int(syscall.Stdin)) - fmt.Println() + // Check if we're in an interactive terminal + if !term.IsTerminal(int(syscall.Stdin)) { + return nil, fmt.Errorf("passphrase-protected key requires interactive terminal. Use ssh-agent or an unencrypted key for automated scenarios") + } + + fmt.Printf("Enter passphrase for key '%s': ", nm.KeyPath) - if err != nil { - return nil, fmt.Errorf("failed to read passphrase: %v", err) + // Read passphrase with a timeout using a channel + type result struct { + password []byte + err error } + resultChan := make(chan result, 1) + go func() { + passphraseBytes, err := term.ReadPassword(int(syscall.Stdin)) + resultChan <- result{password: passphraseBytes, err: err} + }() - defer func() { - for i := range passphraseBytes { - passphraseBytes[i] = 0 + // Wait for passphrase input with 30 second timeout + select { + case res := <-resultChan: + fmt.Println() + if res.err != nil { + return nil, fmt.Errorf("failed to read passphrase: %v", res.err) } - }() - signer, err = ssh.ParsePrivateKeyWithPassphrase(key, passphraseBytes) - if err != nil { - return nil, fmt.Errorf("failed to parse private key with passphrase: %v", err) + defer func() { + for i := range res.password { + res.password[i] = 0 + } + }() + + signer, err = ssh.ParsePrivateKeyWithPassphrase(key, res.password) + if err != nil { + return nil, fmt.Errorf("failed to parse private key with passphrase: %v", err) + } + authMethods = append(authMethods, ssh.PublicKeys(signer)) + return authMethods, nil + + case <-time.After(30 * time.Second): + fmt.Println() + return nil, fmt.Errorf("passphrase input timeout after 30 seconds") } - authMethods = append(authMethods, ssh.PublicKeys(signer)) - return authMethods, nil } return nil, fmt.Errorf("failed to parse private key: %v", err) } @@ -125,13 +148,13 @@ func (n *NodeManager) getAuthMethods() ([]ssh.AuthMethod, error) { return authMethods, nil } -func (n *NodeManager) connectToJumpbox(ip, username string) (*ssh.Client, error) { - authMethods, err := n.getAuthMethods() +func (nm *NodeManager) connectToJumpbox(ip, username string) (*ssh.Client, error) { + authMethods, err := nm.getAuthMethods() if err != nil { return nil, fmt.Errorf("jumpbox authentication setup failed: %v", err) } - hostKeyCallback, err := n.getHostKeyCallback() + hostKeyCallback, err := nm.getHostKeyCallback() if err != nil { return nil, fmt.Errorf("failed to get host key callback: %w", err) } @@ -149,14 +172,14 @@ func (n *NodeManager) connectToJumpbox(ip, username string) (*ssh.Client, error) return nil, fmt.Errorf("failed to dial jumpbox %s: %v", addr, err) } - if err := n.forwardAgent(jumpboxClient, nil); err != nil { + if err := nm.forwardAgent(jumpboxClient, nil); err != nil { fmt.Printf(" Warning: Agent forwarding setup failed on jumpbox: %v\n", err) } return jumpboxClient, nil } -func (n *NodeManager) forwardAgent(client *ssh.Client, session *ssh.Session) error { +func (nm *NodeManager) forwardAgent(client *ssh.Client, session *ssh.Session) error { authSocket := os.Getenv("SSH_AUTH_SOCK") if authSocket == "" { log.Printf("SSH_AUTH_SOCK not set. Cannot perform agent forwarding") @@ -180,8 +203,8 @@ func (n *NodeManager) forwardAgent(client *ssh.Client, session *ssh.Session) err return nil } -func (n *NodeManager) RunSSHCommand(jumpboxIp string, ip string, username string, command string) error { - client, err := n.GetClient(jumpboxIp, ip, username) +func (nm *NodeManager) RunSSHCommand(jumpboxIp string, ip string, username string, command string) error { + client, err := nm.GetClient(jumpboxIp, ip, username) if err != nil { return fmt.Errorf("failed to get client: %w", err) } @@ -192,7 +215,7 @@ func (n *NodeManager) RunSSHCommand(jumpboxIp string, ip string, username string } defer func() { _ = session.Close() }() - if err := n.forwardAgent(client, session); err != nil { + if err := nm.forwardAgent(client, session); err != nil { fmt.Printf(" Warning: Agent forwarding setup failed on session: %v\n", err) } @@ -209,20 +232,20 @@ func (n *NodeManager) RunSSHCommand(jumpboxIp string, ip string, username string return nil } -func (n *NodeManager) GetClient(jumpboxIp string, ip string, username string) (*ssh.Client, error) { +func (nm *NodeManager) GetClient(jumpboxIp string, ip string, username string) (*ssh.Client, error) { - authMethods, err := n.getAuthMethods() + authMethods, err := nm.getAuthMethods() if err != nil { return nil, fmt.Errorf("failed to get authentication methods: %w", err) } - hostKeyCallback, err := n.getHostKeyCallback() + hostKeyCallback, err := nm.getHostKeyCallback() if err != nil { return nil, fmt.Errorf("failed to get host key callback: %w", err) } if jumpboxIp != "" { - jbClient, err := n.connectToJumpbox(jumpboxIp, username) + jbClient, err := nm.connectToJumpbox(jumpboxIp, username) if err != nil { return nil, fmt.Errorf("failed to connect to jumpbox: %v", err) } @@ -262,8 +285,8 @@ func (n *NodeManager) GetClient(jumpboxIp string, ip string, username string) (* return client, nil } -func (n *NodeManager) GetSFTPClient(jumpboxIp string, ip string, username string) (*sftp.Client, error) { - client, err := n.GetClient(jumpboxIp, ip, username) +func (nm *NodeManager) GetSFTPClient(jumpboxIp string, ip string, username string) (*sftp.Client, error) { + client, err := nm.GetClient(jumpboxIp, ip, username) if err != nil { return nil, fmt.Errorf("failed to get SSH client: %v", err) } @@ -279,14 +302,14 @@ func (nm *NodeManager) EnsureDirectoryExists(jumpboxIp string, ip string, userna return nm.RunSSHCommand(jumpboxIp, ip, username, cmd) } -func (n *NodeManager) CopyFile(jumpboxIp string, ip string, username string, src string, dst string) error { - client, err := n.GetSFTPClient(jumpboxIp, ip, username) +func (nm *NodeManager) CopyFile(jumpboxIp string, ip string, username string, src string, dst string) error { + client, err := nm.GetSFTPClient(jumpboxIp, ip, username) if err != nil { return fmt.Errorf("failed to get SSH client: %v", err) } defer func() { _ = client.Close() }() - srcFile, err := n.FileIO.Open(src) + srcFile, err := nm.FileIO.Open(src) if err != nil { return fmt.Errorf("failed to open source file %s: %v", src, err) } From 921e47c745fd74f0dcdb9909afa9d5a079e59659 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:28:51 +0100 Subject: [PATCH 31/65] fix: enhance k0s reset process by stopping service before reset and update error message in tests --- internal/installer/k0s.go | 6 +++ internal/installer/node/node.go | 62 ---------------------------- internal/installer/node/node_test.go | 2 +- 3 files changed, 7 insertions(+), 63 deletions(-) diff --git a/internal/installer/k0s.go b/internal/installer/k0s.go index 24e90032..17b8792c 100644 --- a/internal/installer/k0s.go +++ b/internal/installer/k0s.go @@ -230,6 +230,12 @@ func (k *K0s) Reset(k0sPath string) error { } log.Println("Resetting existing k0s installation...") + + log.Println("Stopping k0s service if running...") + if err := util.RunCommand("sudo", []string{k0sPath, "stop"}, ""); err != nil { + log.Printf("Note: k0s stop returned error, try to continue the reset: %v", err) + } + err := util.RunCommand("sudo", []string{k0sPath, "reset"}, "") if err != nil { return fmt.Errorf("failed to reset k0s: %w", err) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index d5892b63..b4b11d0a 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -335,22 +335,6 @@ func (n *Node) HasCommand(nm *NodeManager, command string) bool { return err == nil } -func (n *Node) InstallOms(nm *NodeManager) error { - remoteCommands := []string{ - "wget -qO- 'https://api.github.com/repos/codesphere-cloud/oms/releases/latest' | jq -r '.assets[] | select(.name | match(\"oms-cli.*linux_amd64\")) | .browser_download_url' | xargs wget -O oms-cli", - "chmod +x oms-cli; sudo mv oms-cli /usr/local/bin/", - "curl -LO https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.amd64; sudo mv sops-v3.11.0.linux.amd64 /usr/local/bin/sops; sudo chmod +x /usr/local/bin/sops", - "wget https://dl.filippo.io/age/latest?for=linux/amd64 -O age.tar.gz; tar -xvf age.tar.gz; sudo mv age/age* /usr/local/bin/", - } - for _, cmd := range remoteCommands { - err := nm.RunSSHCommand("", n.ExternalIP, "root", cmd) - if err != nil { - return fmt.Errorf("failed to run remote command '%s': %w", cmd, err) - } - } - return nil -} - func (n *Node) CopyFile(nm *NodeManager, src string, dst string) error { user := n.User if user == "" { @@ -364,37 +348,6 @@ func (n *Node) CopyFile(nm *NodeManager, src string, dst string) error { return nm.CopyFile("", n.ExternalIP, user, src, dst) } -func (n *Node) HasAcceptEnvConfigured(jumpbox *Node, nm *NodeManager) bool { - checkCommand := "sudo grep -E '^AcceptEnv OMS_PORTAL_API_KEY' /etc/ssh/sshd_config >/dev/null 2>&1" - err := n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommand) - return err == nil -} - -func (n *Node) ConfigureAcceptEnv(jumpbox *Node, nm *NodeManager) error { - cmds := []string{ - "sudo sed -i 's/^#\\?AcceptEnv.*/AcceptEnv OMS_PORTAL_API_KEY/' /etc/ssh/sshd_config", - "sudo systemctl restart sshd", - } - for _, cmd := range cmds { - err := n.RunSSHCommand(jumpbox, nm, "ubuntu", cmd) - if err != nil { - return fmt.Errorf("failed to run command '%s': %w", cmd, err) - } - } - return nil -} - -func (n *Node) HasRootLoginEnabled(jumpbox *Node, nm *NodeManager) bool { - checkCommandPermit := "sudo grep -E '^PermitRootLogin yes' /etc/ssh/sshd_config >/dev/null 2>&1" - err := n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommandPermit) - if err != nil { - return false - } - checkCommandAuthorizedKeys := "sudo grep -E '^no-port-forwarding' /root/.ssh/authorized_keys >/dev/null 2>&1" - err = n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommandAuthorizedKeys) - return err != nil -} - func (n *Node) HasFile(jumpbox *Node, nm *NodeManager, filePath string) bool { checkCommand := fmt.Sprintf("test -f '%s'", shellEscape(filePath)) err := n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommand) @@ -409,21 +362,6 @@ func (n *Node) RunSSHCommand(jumpbox *Node, nm *NodeManager, username string, co return nm.RunSSHCommand(jumpbox.ExternalIP, n.InternalIP, username, command) } -func (n *Node) EnableRootLogin(jumpbox *Node, nm *NodeManager) error { - cmds := []string{ - "sudo sed -i -E 's/^[[:space:]]*(#[[:space:]]*)?PermitRootLogin[[:space:]]+(yes|no|prohibit-password|without-password)[[:space:]]*$/PermitRootLogin yes/' /etc/ssh/sshd_config", - "sudo sed -i 's/no-port-forwarding.*$//g' /root/.ssh/authorized_keys", - "sudo systemctl restart sshd", - } - for _, cmd := range cmds { - err := n.RunSSHCommand(jumpbox, nm, "ubuntu", cmd) - if err != nil { - return fmt.Errorf("failed to run command '%s': %w", cmd, err) - } - } - return nil -} - func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath string, force bool) error { remoteK0sDir := "/usr/local/bin" remoteK0sBinary := filepath.Join(remoteK0sDir, "k0s") diff --git a/internal/installer/node/node_test.go b/internal/installer/node/node_test.go index 8c7126f3..e59530aa 100644 --- a/internal/installer/node/node_test.go +++ b/internal/installer/node/node_test.go @@ -243,7 +243,7 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== err := n.InstallK0s(nm, k0sBinaryPath, "", false) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to ensure directory exists")) + Expect(err.Error()).To(ContainSubstring("failed to copy k0s binary to temp")) }) It("should handle paths with special characters safely", func() { From dfc9debf914907000a3757fd0906a6ae7c36af3c Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Fri, 19 Dec 2025 11:26:26 +0100 Subject: [PATCH 32/65] ref: extract k0s ClusterConfig keys into constants for improved maintainability --- internal/installer/k0s.go | 46 ++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/internal/installer/k0s.go b/internal/installer/k0s.go index 17b8792c..9b5627f3 100644 --- a/internal/installer/k0s.go +++ b/internal/installer/k0s.go @@ -32,6 +32,31 @@ type K0s struct { Goarch string } +// valid top-level fields in a k0s ClusterConfig. +// Reference: https://docs.k0sproject.io/stable/configuration/ +var K0sConfigTopLevelKeys = []string{ + "apiVersion", + "kind", + "metadata", + "spec", +} + +// valid fields in the spec section of a k0s ClusterConfig. +// Reference: https://docs.k0sproject.io/stable/configuration/ +var K0sConfigSpecKeys = []string{ + "api", + "controllerManager", + "scheduler", + "extensions", + "network", + "storage", + "telemetry", + "images", + "konnectivity", + "installConfig", + "featureGates", +} + func NewK0s(hw portal.Http, env env.Env, fw util.FileIO) K0sManager { return &K0s{ Env: env, @@ -170,11 +195,9 @@ func (k *K0s) filterConfigForK0s(configPath string) (string, error) { return "", fmt.Errorf("failed to parse config: %w", err) } - keysToKeep := map[string]bool{ - "apiVersion": true, - "kind": true, - "metadata": true, - "spec": true, + keysToKeep := make(map[string]bool, len(K0sConfigTopLevelKeys)) + for _, key := range K0sConfigTopLevelKeys { + keysToKeep[key] = true } for key := range config { @@ -184,16 +207,9 @@ func (k *K0s) filterConfigForK0s(configPath string) (string, error) { } if spec, ok := config["spec"].(map[string]interface{}); ok { - specKeysToKeep := map[string]bool{ - "api": true, - "controllerManager": true, - "scheduler": true, - "extensions": true, - "network": true, - "storage": true, - "telemetry": true, - "images": true, - "konnectivity": true, + specKeysToKeep := make(map[string]bool, len(K0sConfigSpecKeys)) + for _, key := range K0sConfigSpecKeys { + specKeysToKeep[key] = true } for key := range spec { From a79cd1aae72e52eb3eaabdb5b93f0ffa014f25c8 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Thu, 8 Jan 2026 17:41:49 +0100 Subject: [PATCH 33/65] feat: enhance node Installation and testing --- cli/cmd/install_k0s.go | 13 +- cli/cmd/install_k0s_test.go | 6 +- cli/cmd/mocks.go | 17 +- internal/installer/k0s.go | 44 ++++- internal/installer/k0s_config.go | 30 +++- internal/installer/k0s_config_test.go | 205 ++++++++++++++++++++++- internal/installer/k0s_nodeip_test.go | 137 ++++++++++++++++ internal/installer/k0s_test.go | 6 +- internal/installer/mocks.go | 223 ++++++------------------- internal/installer/node/node.go | 5 + internal/installer/node/node_test.go | 115 +++++++++++++ internal/portal/mocks.go | 225 +++++--------------------- internal/system/mocks.go | 44 +---- internal/util/mocks.go | 222 ++++++------------------- 14 files changed, 687 insertions(+), 605 deletions(-) create mode 100644 internal/installer/k0s_nodeip_test.go diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index c2735304..586e2d3d 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -115,7 +115,6 @@ func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0 return fmt.Errorf("failed to marshal k0s config: %w", err) } - // allow temp directory in tests k0sConfigPath := "/etc/k0s/k0s.yaml" if err := os.MkdirAll(filepath.Dir(k0sConfigPath), 0755); err != nil { @@ -146,7 +145,17 @@ func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0 return c.InstallK0sRemote(config, k0sPath, k0sConfigPath) } - err = k0s.Install(k0sConfigPath, k0sPath, c.Opts.Force) + controlPlaneIPs := make([]string, 0, len(config.Kubernetes.ControlPlanes)) + for _, cp := range config.Kubernetes.ControlPlanes { + controlPlaneIPs = append(controlPlaneIPs, cp.IPAddress) + } + nodeIP, err := installer.GetNodeIPAddress(controlPlaneIPs) + if err != nil { + log.Printf("Warning: could not determine node IP: %v. Installing without --kubelet-extra-args", err) + nodeIP = "" + } + + err = k0s.Install(k0sConfigPath, k0sPath, c.Opts.Force, nodeIP) if err != nil { return fmt.Errorf("failed to install k0s: %w", err) } diff --git a/cli/cmd/install_k0s_test.go b/cli/cmd/install_k0s_test.go index 43573d54..a65aa1ba 100644 --- a/cli/cmd/install_k0s_test.go +++ b/cli/cmd/install_k0s_test.go @@ -150,7 +150,7 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.Force = true mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") - mockK0s.EXPECT().Install(mock.Anything, "/test/path/k0s", true).Return(nil) + mockK0s.EXPECT().Install(mock.Anything, "/test/path/k0s", true, mock.Anything).Return(nil) err = c.InstallK0s(mockPM, mockK0s) Expect(err).NotTo(HaveOccurred()) @@ -170,7 +170,7 @@ var _ = Describe("InstallK0sCmd", func() { mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") mockK0s.EXPECT().Download("v1.29.0+k0s.0", false, false).Return("/downloaded/k0s", nil) - mockK0s.EXPECT().Install(mock.Anything, "/downloaded/k0s", false).Return(nil) + mockK0s.EXPECT().Install(mock.Anything, "/downloaded/k0s", false, mock.Anything).Return(nil) err = c.InstallK0s(mockPM, mockK0s) Expect(err).NotTo(HaveOccurred()) @@ -207,7 +207,7 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.Package = "test-package.tar.gz" mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") - mockK0s.EXPECT().Install(mock.Anything, "/test/path/k0s", false).Return(os.ErrPermission) + mockK0s.EXPECT().Install(mock.Anything, "/test/path/k0s", false, mock.Anything).Return(os.ErrPermission) err = c.InstallK0s(mockPM, mockK0s) Expect(err).To(HaveOccurred()) diff --git a/cli/cmd/mocks.go b/cli/cmd/mocks.go index 2f86863c..a1ac35ea 100644 --- a/cli/cmd/mocks.go +++ b/cli/cmd/mocks.go @@ -74,26 +74,15 @@ type MockOMSUpdater_Update_Call struct { } // Update is a helper method to define mock.On call -// - v semver.Version -// - repo string +// - v +// - repo func (_e *MockOMSUpdater_Expecter) Update(v interface{}, repo interface{}) *MockOMSUpdater_Update_Call { return &MockOMSUpdater_Update_Call{Call: _e.mock.On("Update", v, repo)} } func (_c *MockOMSUpdater_Update_Call) Run(run func(v semver.Version, repo string)) *MockOMSUpdater_Update_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 semver.Version - if args[0] != nil { - arg0 = args[0].(semver.Version) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - run( - arg0, - arg1, - ) + run(args[0].(semver.Version), args[1].(string)) }) return _c } diff --git a/internal/installer/k0s.go b/internal/installer/k0s.go index 9b5627f3..e2306f11 100644 --- a/internal/installer/k0s.go +++ b/internal/installer/k0s.go @@ -6,6 +6,7 @@ package installer import ( "fmt" "log" + "net" "os" "path/filepath" "runtime" @@ -20,7 +21,7 @@ import ( type K0sManager interface { GetLatestVersion() (string, error) Download(version string, force bool, quiet bool) (string, error) - Install(configPath string, k0sPath string, force bool) error + Install(configPath string, k0sPath string, force bool, nodeIP string) error Reset(k0sPath string) error } @@ -126,7 +127,7 @@ func (k *K0s) Download(version string, force bool, quiet bool) (string, error) { return k0sPath, nil } -func (k *K0s) Install(configPath string, k0sPath string, force bool) error { +func (k *K0s) Install(configPath string, k0sPath string, force bool, nodeIP string) error { if k.Goos != "linux" || k.Goarch != "amd64" { return fmt.Errorf("k0s installation is only supported on Linux amd64. Current platform: %s/%s", k.Goos, k.Goarch) } @@ -164,6 +165,13 @@ func (k *K0s) Install(configPath string, k0sPath string, force bool) error { args = append(args, "--single") } + args = append(args, "--enable-worker") + args = append(args, "--no-taints") + + if nodeIP != "" { + args = append(args, "--kubelet-extra-args", fmt.Sprintf("--node-ip=%s", nodeIP)) + } + if force { args = append(args, "--force") } @@ -238,6 +246,38 @@ func (k *K0s) filterConfigForK0s(configPath string) (string, error) { return tmpFile.Name(), nil } +// GetNodeIPAddress finds the IP address of the current node by matching +// against the control plane IPs in the config +func GetNodeIPAddress(controlPlanes []string) (string, error) { + addrs, err := net.InterfaceAddrs() + if err != nil { + return "", fmt.Errorf("failed to get network interfaces: %w", err) + } + + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + ip := ipnet.IP.String() + for _, cpIP := range controlPlanes { + if ip == cpIP { + return ip, nil + } + } + } + } + } + + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + return ipnet.IP.String(), nil + } + } + } + + return "", fmt.Errorf("no suitable IP address found") +} + // Reset tears down an existing k0s installation by executing `k0s reset`. // This command removes all k0s-related resources func (k *K0s) Reset(k0sPath string) error { diff --git a/internal/installer/k0s_config.go b/internal/installer/k0s_config.go index 738e0c88..a4ac23c2 100644 --- a/internal/installer/k0s_config.go +++ b/internal/installer/k0s_config.go @@ -22,9 +22,11 @@ type K0sMetadata struct { } type K0sSpec struct { - API *K0sAPI `yaml:"api,omitempty"` - Network *K0sNetwork `yaml:"network,omitempty"` - Storage *K0sStorage `yaml:"storage,omitempty"` + API *K0sAPI `yaml:"api,omitempty"` + Network *K0sNetwork `yaml:"network,omitempty"` + Storage *K0sStorage `yaml:"storage,omitempty"` + Images *K0sImages `yaml:"images,omitempty"` + Telemetry *K0sTelemetry `yaml:"telemetry,omitempty"` } type K0sAPI struct { @@ -49,6 +51,14 @@ type K0sEtcd struct { PeerAddress string `yaml:"peerAddress,omitempty"` } +type K0sImages struct { + DefaultPullPolicy string `yaml:"default_pull_policy,omitempty"` +} + +type K0sTelemetry struct { + Enabled bool `yaml:"enabled"` +} + func GenerateK0sConfig(installConfig *files.RootConfig) (*K0sConfig, error) { if installConfig == nil { return nil, fmt.Errorf("installConfig cannot be nil") @@ -86,14 +96,26 @@ func GenerateK0sConfig(installConfig *files.RootConfig) (*K0sConfig, error) { } k0sConfig.Spec.Network = &K0sNetwork{ - Provider: "kuberouter", + Provider: "calico", } if installConfig.Kubernetes.PodCIDR != "" { k0sConfig.Spec.Network.PodCIDR = installConfig.Kubernetes.PodCIDR + } else { + k0sConfig.Spec.Network.PodCIDR = "100.96.0.0/11" } if installConfig.Kubernetes.ServiceCIDR != "" { k0sConfig.Spec.Network.ServiceCIDR = installConfig.Kubernetes.ServiceCIDR + } else { + k0sConfig.Spec.Network.ServiceCIDR = "100.64.0.0/13" + } + + k0sConfig.Spec.Images = &K0sImages{ + DefaultPullPolicy: "Never", + } + + k0sConfig.Spec.Telemetry = &K0sTelemetry{ + Enabled: false, } if len(installConfig.Kubernetes.ControlPlanes) > 0 { diff --git a/internal/installer/k0s_config_test.go b/internal/installer/k0s_config_test.go index 673a886e..7bc17128 100644 --- a/internal/installer/k0s_config_test.go +++ b/internal/installer/k0s_config_test.go @@ -54,11 +54,7 @@ var _ = Describe("K0sConfig", func() { Expect(k0sConfig.Spec.Network).ToNot(BeNil()) Expect(k0sConfig.Spec.Network.PodCIDR).To(Equal("10.244.0.0/16")) Expect(k0sConfig.Spec.Network.ServiceCIDR).To(Equal("10.96.0.0/12")) - Expect(k0sConfig.Spec.Network.Provider).To(Equal("kuberouter")) - - // Check Storage configuration - Expect(k0sConfig.Spec.Storage).ToNot(BeNil()) - Expect(k0sConfig.Spec.Storage.Type).To(Equal("etcd")) + Expect(k0sConfig.Spec.Network.Provider).To(Equal("calico")) Expect(k0sConfig.Spec.Storage.Etcd).ToNot(BeNil()) Expect(k0sConfig.Spec.Storage.Etcd.PeerAddress).To(Equal("10.0.1.10")) }) @@ -144,5 +140,204 @@ var _ = Describe("K0sConfig", func() { Expect(k0sConfig.Metadata.Name).To(Equal("codesphere-external")) }) }) + + Context("edge cases and validation", func() { + It("should handle empty datacenter name", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.0.1"}, + }, + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(k0sConfig.Metadata.Name).To(Equal("codesphere-")) + }) + + It("should handle empty control plane list", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{}, + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + // Should have basic structure but no API/Storage config + Expect(k0sConfig.Spec.API).To(BeNil()) + Expect(k0sConfig.Spec.Storage).To(BeNil()) + }) + + It("should handle nil control plane addresses", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: nil, + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(k0sConfig.Spec.API).To(BeNil()) + }) + + It("should handle missing APIServerHost", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.0.1"}, + }, + APIServerHost: "", + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(k0sConfig.Spec.API.ExternalAddress).To(BeEmpty()) + Expect(k0sConfig.Spec.API.SANs).To(ConsistOf("10.0.0.1")) + }) + + It("should handle missing network CIDRs", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.0.1"}, + }, + PodCIDR: "", + ServiceCIDR: "", + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(k0sConfig.Spec.Network).NotTo(BeNil()) + Expect(k0sConfig.Spec.Network.Provider).To(Equal("calico")) + }) + + It("should use default network provider", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.0.1"}, + }, + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(k0sConfig.Spec.Network.Provider).To(Equal("calico")) + }) + + It("should generate correct SANs with single control plane", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.0.1"}, + }, + APIServerHost: "api.example.com", + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(k0sConfig.Spec.API.SANs).To(HaveLen(2)) + Expect(k0sConfig.Spec.API.SANs).To(ContainElements("10.0.0.1", "api.example.com")) + }) + + It("should handle special characters in datacenter name", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc_01.prod", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.0.1"}, + }, + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(k0sConfig.Metadata.Name).To(Equal("codesphere-test-dc_01.prod")) + }) + + It("should set correct API port", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.0.1"}, + }, + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(k0sConfig.Spec.API.Port).To(Equal(6443)) + }) + + It("should configure etcd with first control plane IP", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.0.1"}, + {IPAddress: "10.0.0.2"}, + }, + }, + } + + k0sConfig, err := installer.GenerateK0sConfig(installConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(k0sConfig.Spec.Storage.Type).To(Equal("etcd")) + Expect(k0sConfig.Spec.Storage.Etcd.PeerAddress).To(Equal("10.0.0.1")) + }) + }) }) }) diff --git a/internal/installer/k0s_nodeip_test.go b/internal/installer/k0s_nodeip_test.go new file mode 100644 index 00000000..06a3bcd2 --- /dev/null +++ b/internal/installer/k0s_nodeip_test.go @@ -0,0 +1,137 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package installer + +import ( + "net" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("GetNodeIPAddress", func() { + Context("with valid network setup", func() { + It("should return a valid IPv4 address", func() { + ip, err := GetNodeIPAddress([]string{}) + + if err != nil { + Skip("No non-loopback network interfaces available on this system") + } + + Expect(ip).NotTo(BeEmpty()) + parsedIP := net.ParseIP(ip) + Expect(parsedIP).NotTo(BeNil()) + Expect(parsedIP.To4()).NotTo(BeNil()) + }) + + It("should not return loopback address", func() { + ip, err := GetNodeIPAddress([]string{}) + + if err != nil { + Skip("No non-loopback network interfaces available") + } + + Expect(ip).NotTo(Equal("127.0.0.1")) + Expect(ip).NotTo(HavePrefix("127.")) + }) + }) + + Context("with control plane addresses provided", func() { + It("should prioritize control plane IP if available", func() { + interfaces, err := net.Interfaces() + if err != nil { + Skip("Cannot list network interfaces") + } + + var testIP string + for _, iface := range interfaces { + if iface.Flags&net.FlagLoopback != 0 { + continue + } + + addrs, err := iface.Addrs() + if err != nil { + continue + } + + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + + if ip == nil || ip.IsLoopback() { + continue + } + + if ip.To4() != nil { + testIP = ip.String() + break + } + } + + if testIP != "" { + break + } + } + + if testIP == "" { + Skip("No suitable test IP found") + } + + result, err := GetNodeIPAddress([]string{testIP, "10.0.0.1"}) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal(testIP)) + }) + + It("should fallback if control plane IPs don't match", func() { + result, err := GetNodeIPAddress([]string{"10.254.254.1", "192.168.254.254"}) + + if err == nil { + Expect(result).NotTo(BeEmpty()) + parsedIP := net.ParseIP(result) + Expect(parsedIP).NotTo(BeNil()) + Expect(parsedIP.To4()).NotTo(BeNil()) + } + }) + }) + + Context("with edge cases", func() { + It("should handle empty control plane list", func() { + ip, err := GetNodeIPAddress([]string{}) + + if err == nil { + Expect(ip).NotTo(BeEmpty()) + } else { + Expect(err.Error()).To(ContainSubstring("no suitable")) + } + }) + + It("should handle nil control plane list", func() { + ip, err := GetNodeIPAddress(nil) + + if err == nil { + Expect(ip).NotTo(BeEmpty()) + } else { + Expect(err.Error()).To(ContainSubstring("no suitable")) + } + }) + + It("should return error when no interfaces are available", func() { + ip, err := GetNodeIPAddress([]string{"invalid"}) + + if err != nil { + Expect(err.Error()).To(Or( + ContainSubstring("no suitable"), + ContainSubstring("network"), + )) + } else { + Expect(ip).NotTo(BeEmpty()) + } + }) + }) +}) diff --git a/internal/installer/k0s_test.go b/internal/installer/k0s_test.go index ba863a5c..e7878488 100644 --- a/internal/installer/k0s_test.go +++ b/internal/installer/k0s_test.go @@ -288,7 +288,7 @@ var _ = Describe("K0s", func() { k0sImpl.Goos = "windows" k0sImpl.Goarch = "amd64" - err := k0s.Install("", k0sPath, false) + err := k0s.Install("", k0sPath, false, "") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("k0s installation is only supported on Linux amd64")) Expect(err.Error()).To(ContainSubstring("windows/amd64")) @@ -298,7 +298,7 @@ var _ = Describe("K0s", func() { k0sImpl.Goos = "linux" k0sImpl.Goarch = "arm64" - err := k0s.Install("", k0sPath, false) + err := k0s.Install("", k0sPath, false, "") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("k0s installation is only supported on Linux amd64")) Expect(err.Error()).To(ContainSubstring("linux/arm64")) @@ -314,7 +314,7 @@ var _ = Describe("K0s", func() { It("should fail when k0s binary doesn't exist", func() { mockFileWriter.EXPECT().Exists(k0sPath).Return(false) - err := k0s.Install("", k0sPath, false) + err := k0s.Install("", k0sPath, false, "") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("k0s binary does not exist")) Expect(err.Error()).To(ContainSubstring("please download first")) diff --git a/internal/installer/mocks.go b/internal/installer/mocks.go index d22ab787..9bf75cac 100644 --- a/internal/installer/mocks.go +++ b/internal/installer/mocks.go @@ -69,20 +69,14 @@ type MockConfigManager_ParseConfigYaml_Call struct { } // ParseConfigYaml is a helper method to define mock.On call -// - configPath string +// - configPath func (_e *MockConfigManager_Expecter) ParseConfigYaml(configPath interface{}) *MockConfigManager_ParseConfigYaml_Call { return &MockConfigManager_ParseConfigYaml_Call{Call: _e.mock.On("ParseConfigYaml", configPath)} } func (_c *MockConfigManager_ParseConfigYaml_Call) Run(run func(configPath string)) *MockConfigManager_ParseConfigYaml_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -147,20 +141,14 @@ type MockInstallConfigManager_ApplyProfile_Call struct { } // ApplyProfile is a helper method to define mock.On call -// - profile string +// - profile func (_e *MockInstallConfigManager_Expecter) ApplyProfile(profile interface{}) *MockInstallConfigManager_ApplyProfile_Call { return &MockInstallConfigManager_ApplyProfile_Call{Call: _e.mock.On("ApplyProfile", profile)} } func (_c *MockInstallConfigManager_ApplyProfile_Call) Run(run func(profile string)) *MockInstallConfigManager_ApplyProfile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -332,20 +320,14 @@ type MockInstallConfigManager_LoadInstallConfigFromFile_Call struct { } // LoadInstallConfigFromFile is a helper method to define mock.On call -// - configPath string +// - configPath func (_e *MockInstallConfigManager_Expecter) LoadInstallConfigFromFile(configPath interface{}) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { return &MockInstallConfigManager_LoadInstallConfigFromFile_Call{Call: _e.mock.On("LoadInstallConfigFromFile", configPath)} } func (_c *MockInstallConfigManager_LoadInstallConfigFromFile_Call) Run(run func(configPath string)) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -383,20 +365,14 @@ type MockInstallConfigManager_LoadVaultFromFile_Call struct { } // LoadVaultFromFile is a helper method to define mock.On call -// - vaultPath string +// - vaultPath func (_e *MockInstallConfigManager_Expecter) LoadVaultFromFile(vaultPath interface{}) *MockInstallConfigManager_LoadVaultFromFile_Call { return &MockInstallConfigManager_LoadVaultFromFile_Call{Call: _e.mock.On("LoadVaultFromFile", vaultPath)} } func (_c *MockInstallConfigManager_LoadVaultFromFile_Call) Run(run func(vaultPath string)) *MockInstallConfigManager_LoadVaultFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -570,26 +546,15 @@ type MockInstallConfigManager_WriteInstallConfig_Call struct { } // WriteInstallConfig is a helper method to define mock.On call -// - configPath string -// - withComments bool +// - configPath +// - withComments func (_e *MockInstallConfigManager_Expecter) WriteInstallConfig(configPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteInstallConfig_Call { return &MockInstallConfigManager_WriteInstallConfig_Call{Call: _e.mock.On("WriteInstallConfig", configPath, withComments)} } func (_c *MockInstallConfigManager_WriteInstallConfig_Call) Run(run func(configPath string, withComments bool)) *MockInstallConfigManager_WriteInstallConfig_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -627,26 +592,15 @@ type MockInstallConfigManager_WriteVault_Call struct { } // WriteVault is a helper method to define mock.On call -// - vaultPath string -// - withComments bool +// - vaultPath +// - withComments func (_e *MockInstallConfigManager_Expecter) WriteVault(vaultPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteVault_Call { return &MockInstallConfigManager_WriteVault_Call{Call: _e.mock.On("WriteVault", vaultPath, withComments)} } func (_c *MockInstallConfigManager_WriteVault_Call) Run(run func(vaultPath string, withComments bool)) *MockInstallConfigManager_WriteVault_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -720,32 +674,16 @@ type MockK0sManager_Download_Call struct { } // Download is a helper method to define mock.On call -// - version string -// - force bool -// - quiet bool +// - version +// - force +// - quiet func (_e *MockK0sManager_Expecter) Download(version interface{}, force interface{}, quiet interface{}) *MockK0sManager_Download_Call { return &MockK0sManager_Download_Call{Call: _e.mock.On("Download", version, force, quiet)} } func (_c *MockK0sManager_Download_Call) Run(run func(version string, force bool, quiet bool)) *MockK0sManager_Download_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - var arg2 bool - if args[2] != nil { - arg2 = args[2].(bool) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(bool), args[2].(bool)) }) return _c } @@ -814,16 +752,16 @@ func (_c *MockK0sManager_GetLatestVersion_Call) RunAndReturn(run func() (string, } // Install provides a mock function for the type MockK0sManager -func (_mock *MockK0sManager) Install(configPath string, k0sPath string, force bool) error { - ret := _mock.Called(configPath, k0sPath, force) +func (_mock *MockK0sManager) Install(configPath string, k0sPath string, force bool, nodeIP string) error { + ret := _mock.Called(configPath, k0sPath, force, nodeIP) if len(ret) == 0 { panic("no return value specified for Install") } var r0 error - if returnFunc, ok := ret.Get(0).(func(string, string, bool) error); ok { - r0 = returnFunc(configPath, k0sPath, force) + if returnFunc, ok := ret.Get(0).(func(string, string, bool, string) error); ok { + r0 = returnFunc(configPath, k0sPath, force, nodeIP) } else { r0 = ret.Error(0) } @@ -836,32 +774,17 @@ type MockK0sManager_Install_Call struct { } // Install is a helper method to define mock.On call -// - configPath string -// - k0sPath string -// - force bool -func (_e *MockK0sManager_Expecter) Install(configPath interface{}, k0sPath interface{}, force interface{}) *MockK0sManager_Install_Call { - return &MockK0sManager_Install_Call{Call: _e.mock.On("Install", configPath, k0sPath, force)} +// - configPath +// - k0sPath +// - force +// - nodeIP +func (_e *MockK0sManager_Expecter) Install(configPath interface{}, k0sPath interface{}, force interface{}, nodeIP interface{}) *MockK0sManager_Install_Call { + return &MockK0sManager_Install_Call{Call: _e.mock.On("Install", configPath, k0sPath, force, nodeIP)} } -func (_c *MockK0sManager_Install_Call) Run(run func(configPath string, k0sPath string, force bool)) *MockK0sManager_Install_Call { +func (_c *MockK0sManager_Install_Call) Run(run func(configPath string, k0sPath string, force bool, nodeIP string)) *MockK0sManager_Install_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 bool - if args[2] != nil { - arg2 = args[2].(bool) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(string), args[2].(bool), args[3].(string)) }) return _c } @@ -871,7 +794,7 @@ func (_c *MockK0sManager_Install_Call) Return(err error) *MockK0sManager_Install return _c } -func (_c *MockK0sManager_Install_Call) RunAndReturn(run func(configPath string, k0sPath string, force bool) error) *MockK0sManager_Install_Call { +func (_c *MockK0sManager_Install_Call) RunAndReturn(run func(configPath string, k0sPath string, force bool, nodeIP string) error) *MockK0sManager_Install_Call { _c.Call.Return(run) return _c } @@ -899,20 +822,14 @@ type MockK0sManager_Reset_Call struct { } // Reset is a helper method to define mock.On call -// - k0sPath string +// - k0sPath func (_e *MockK0sManager_Expecter) Reset(k0sPath interface{}) *MockK0sManager_Reset_Call { return &MockK0sManager_Reset_Call{Call: _e.mock.On("Reset", k0sPath)} } func (_c *MockK0sManager_Reset_Call) Run(run func(k0sPath string)) *MockK0sManager_Reset_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -977,20 +894,14 @@ type MockPackageManager_Extract_Call struct { } // Extract is a helper method to define mock.On call -// - force bool +// - force func (_e *MockPackageManager_Expecter) Extract(force interface{}) *MockPackageManager_Extract_Call { return &MockPackageManager_Extract_Call{Call: _e.mock.On("Extract", force)} } func (_c *MockPackageManager_Extract_Call) Run(run func(force bool)) *MockPackageManager_Extract_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 bool - if args[0] != nil { - arg0 = args[0].(bool) - } - run( - arg0, - ) + run(args[0].(bool)) }) return _c } @@ -1028,26 +939,15 @@ type MockPackageManager_ExtractDependency_Call struct { } // ExtractDependency is a helper method to define mock.On call -// - file string -// - force bool +// - file +// - force func (_e *MockPackageManager_Expecter) ExtractDependency(file interface{}, force interface{}) *MockPackageManager_ExtractDependency_Call { return &MockPackageManager_ExtractDependency_Call{Call: _e.mock.On("ExtractDependency", file, force)} } func (_c *MockPackageManager_ExtractDependency_Call) Run(run func(file string, force bool)) *MockPackageManager_ExtractDependency_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -1094,20 +994,14 @@ type MockPackageManager_ExtractOciImageIndex_Call struct { } // ExtractOciImageIndex is a helper method to define mock.On call -// - imagefile string +// - imagefile func (_e *MockPackageManager_Expecter) ExtractOciImageIndex(imagefile interface{}) *MockPackageManager_ExtractOciImageIndex_Call { return &MockPackageManager_ExtractOciImageIndex_Call{Call: _e.mock.On("ExtractOciImageIndex", imagefile)} } func (_c *MockPackageManager_ExtractOciImageIndex_Call) Run(run func(imagefile string)) *MockPackageManager_ExtractOciImageIndex_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -1200,20 +1094,14 @@ type MockPackageManager_GetBaseimageName_Call struct { } // GetBaseimageName is a helper method to define mock.On call -// - baseimage string +// - baseimage func (_e *MockPackageManager_Expecter) GetBaseimageName(baseimage interface{}) *MockPackageManager_GetBaseimageName_Call { return &MockPackageManager_GetBaseimageName_Call{Call: _e.mock.On("GetBaseimageName", baseimage)} } func (_c *MockPackageManager_GetBaseimageName_Call) Run(run func(baseimage string)) *MockPackageManager_GetBaseimageName_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -1260,26 +1148,15 @@ type MockPackageManager_GetBaseimagePath_Call struct { } // GetBaseimagePath is a helper method to define mock.On call -// - baseimage string -// - force bool +// - baseimage +// - force func (_e *MockPackageManager_Expecter) GetBaseimagePath(baseimage interface{}, force interface{}) *MockPackageManager_GetBaseimagePath_Call { return &MockPackageManager_GetBaseimagePath_Call{Call: _e.mock.On("GetBaseimagePath", baseimage, force)} } func (_c *MockPackageManager_GetBaseimagePath_Call) Run(run func(baseimage string, force bool)) *MockPackageManager_GetBaseimagePath_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -1370,20 +1247,14 @@ type MockPackageManager_GetDependencyPath_Call struct { } // GetDependencyPath is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockPackageManager_Expecter) GetDependencyPath(filename interface{}) *MockPackageManager_GetDependencyPath_Call { return &MockPackageManager_GetDependencyPath_Call{Call: _e.mock.On("GetDependencyPath", filename)} } func (_c *MockPackageManager_GetDependencyPath_Call) Run(run func(filename string)) *MockPackageManager_GetDependencyPath_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index b4b11d0a..8f689bdb 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -410,6 +410,11 @@ func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath s } else { installCmd += " --single" } + + installCmd += " --enable-worker" + installCmd += " --no-taints" + installCmd += fmt.Sprintf(" --kubelet-extra-args='--node-ip=%s'", shellEscape(n.ExternalIP)) + if force { installCmd += " --force" } diff --git a/internal/installer/node/node_test.go b/internal/installer/node/node_test.go index e59530aa..b589b92e 100644 --- a/internal/installer/node/node_test.go +++ b/internal/installer/node/node_test.go @@ -21,6 +21,121 @@ func TestNode(t *testing.T) { } var _ = Describe("Node", func() { + Describe("shellEscape function", func() { + Context("security and injection prevention", func() { + It("should handle single quotes correctly", func() { + testNode := &node.Node{ + ExternalIP: "192.168.1.100", + User: "root", + } + mockFileWriter := util.NewMockFileIO(GinkgoT()) + nm := &node.NodeManager{ + FileIO: mockFileWriter, + KeyPath: "", + } + + result := testNode.HasCommand(nm, "test'; echo 'injected") + Expect(result).To(BeFalse()) + }) + + It("should handle special shell characters safely", func() { + testNode := &node.Node{ + ExternalIP: "192.168.1.100", + User: "root", + } + mockFileWriter := util.NewMockFileIO(GinkgoT()) + nm := &node.NodeManager{ + FileIO: mockFileWriter, + KeyPath: "", + } + + // Test various injection attempts + injectionAttempts := []string{ + "cmd; rm -rf /", + "cmd && malicious", + "cmd | grep password", + "cmd`backdoor`", + "cmd$(malicious)", + "cmd\nrm -rf /", + } + + for _, attempt := range injectionAttempts { + result := testNode.HasCommand(nm, attempt) + Expect(result).To(BeFalse()) + } + }) + + It("should preserve normal commands", func() { + testNode := &node.Node{ + ExternalIP: "192.168.1.100", + User: "root", + } + mockFileWriter := util.NewMockFileIO(GinkgoT()) + nm := &node.NodeManager{ + FileIO: mockFileWriter, + KeyPath: "", + } + + normalCommands := []string{ + "kubectl", + "ls", + "cat /etc/hosts", + "echo test", + } + + for _, cmd := range normalCommands { + result := testNode.HasCommand(nm, cmd) + Expect(result).To(BeFalse()) + } + }) + + It("should handle Unicode characters", func() { + testNode := &node.Node{ + ExternalIP: "192.168.1.100", + User: "root", + } + mockFileWriter := util.NewMockFileIO(GinkgoT()) + nm := &node.NodeManager{ + FileIO: mockFileWriter, + KeyPath: "", + } + + result := testNode.HasCommand(nm, "test-ꖇ件-αβγ") + Expect(result).To(BeFalse()) + }) + + It("should handle empty strings", func() { + testNode := &node.Node{ + ExternalIP: "192.168.1.100", + User: "root", + } + mockFileWriter := util.NewMockFileIO(GinkgoT()) + nm := &node.NodeManager{ + FileIO: mockFileWriter, + KeyPath: "", + } + + result := testNode.HasCommand(nm, "") + Expect(result).To(BeFalse()) + }) + + It("should handle nested quotes", func() { + testNode := &node.Node{ + ExternalIP: "192.168.1.100", + User: "root", + } + mockFileWriter := util.NewMockFileIO(GinkgoT()) + nm := &node.NodeManager{ + FileIO: mockFileWriter, + KeyPath: "", + } + + result := testNode.HasCommand(nm, "echo 'test \"nested\" quotes'") + Expect(result).To(BeFalse()) + }) + }) + }) + Describe("NodeManager", func() { var ( nm *node.NodeManager diff --git a/internal/portal/mocks.go b/internal/portal/mocks.go index c162a3ee..83372369 100644 --- a/internal/portal/mocks.go +++ b/internal/portal/mocks.go @@ -61,32 +61,16 @@ type MockHttp_Download_Call struct { } // Download is a helper method to define mock.On call -// - url string -// - file io.Writer -// - quiet bool +// - url +// - file +// - quiet func (_e *MockHttp_Expecter) Download(url interface{}, file interface{}, quiet interface{}) *MockHttp_Download_Call { return &MockHttp_Download_Call{Call: _e.mock.On("Download", url, file, quiet)} } func (_c *MockHttp_Download_Call) Run(run func(url string, file io.Writer, quiet bool)) *MockHttp_Download_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 io.Writer - if args[1] != nil { - arg1 = args[1].(io.Writer) - } - var arg2 bool - if args[2] != nil { - arg2 = args[2].(bool) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(io.Writer), args[2].(bool)) }) return _c } @@ -135,20 +119,14 @@ type MockHttp_Get_Call struct { } // Get is a helper method to define mock.On call -// - url string +// - url func (_e *MockHttp_Expecter) Get(url interface{}) *MockHttp_Get_Call { return &MockHttp_Get_Call{Call: _e.mock.On("Get", url)} } func (_c *MockHttp_Get_Call) Run(run func(url string)) *MockHttp_Get_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -197,32 +175,16 @@ type MockHttp_Request_Call struct { } // Request is a helper method to define mock.On call -// - url string -// - method string -// - body io.Reader +// - url +// - method +// - body func (_e *MockHttp_Expecter) Request(url interface{}, method interface{}, body interface{}) *MockHttp_Request_Call { return &MockHttp_Request_Call{Call: _e.mock.On("Request", url, method, body)} } func (_c *MockHttp_Request_Call) Run(run func(url string, method string, body io.Reader)) *MockHttp_Request_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 io.Reader - if args[2] != nil { - arg2 = args[2].(io.Reader) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(string), args[2].(io.Reader)) }) return _c } @@ -287,44 +249,18 @@ type MockPortal_DownloadBuildArtifact_Call struct { } // DownloadBuildArtifact is a helper method to define mock.On call -// - product Product -// - build Build -// - file io.Writer -// - startByte int -// - quiet bool +// - product +// - build +// - file +// - startByte +// - quiet func (_e *MockPortal_Expecter) DownloadBuildArtifact(product interface{}, build interface{}, file interface{}, startByte interface{}, quiet interface{}) *MockPortal_DownloadBuildArtifact_Call { return &MockPortal_DownloadBuildArtifact_Call{Call: _e.mock.On("DownloadBuildArtifact", product, build, file, startByte, quiet)} } func (_c *MockPortal_DownloadBuildArtifact_Call) Run(run func(product Product, build Build, file io.Writer, startByte int, quiet bool)) *MockPortal_DownloadBuildArtifact_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 Product - if args[0] != nil { - arg0 = args[0].(Product) - } - var arg1 Build - if args[1] != nil { - arg1 = args[1].(Build) - } - var arg2 io.Writer - if args[2] != nil { - arg2 = args[2].(io.Writer) - } - var arg3 int - if args[3] != nil { - arg3 = args[3].(int) - } - var arg4 bool - if args[4] != nil { - arg4 = args[4].(bool) - } - run( - arg0, - arg1, - arg2, - arg3, - arg4, - ) + run(args[0].(Product), args[1].(Build), args[2].(io.Writer), args[3].(int), args[4].(bool)) }) return _c } @@ -371,20 +307,14 @@ type MockPortal_GetApiKeyId_Call struct { } // GetApiKeyId is a helper method to define mock.On call -// - oldKey string +// - oldKey func (_e *MockPortal_Expecter) GetApiKeyId(oldKey interface{}) *MockPortal_GetApiKeyId_Call { return &MockPortal_GetApiKeyId_Call{Call: _e.mock.On("GetApiKeyId", oldKey)} } func (_c *MockPortal_GetApiKeyId_Call) Run(run func(oldKey string)) *MockPortal_GetApiKeyId_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -431,32 +361,16 @@ type MockPortal_GetBuild_Call struct { } // GetBuild is a helper method to define mock.On call -// - product Product -// - version string -// - hash string +// - product +// - version +// - hash func (_e *MockPortal_Expecter) GetBuild(product interface{}, version interface{}, hash interface{}) *MockPortal_GetBuild_Call { return &MockPortal_GetBuild_Call{Call: _e.mock.On("GetBuild", product, version, hash)} } func (_c *MockPortal_GetBuild_Call) Run(run func(product Product, version string, hash string)) *MockPortal_GetBuild_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 Product - if args[0] != nil { - arg0 = args[0].(Product) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(Product), args[1].(string), args[2].(string)) }) return _c } @@ -558,20 +472,14 @@ type MockPortal_ListBuilds_Call struct { } // ListBuilds is a helper method to define mock.On call -// - product Product +// - product func (_e *MockPortal_Expecter) ListBuilds(product interface{}) *MockPortal_ListBuilds_Call { return &MockPortal_ListBuilds_Call{Call: _e.mock.On("ListBuilds", product)} } func (_c *MockPortal_ListBuilds_Call) Run(run func(product Product)) *MockPortal_ListBuilds_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 Product - if args[0] != nil { - arg0 = args[0].(Product) - } - run( - arg0, - ) + run(args[0].(Product)) }) return _c } @@ -620,38 +528,17 @@ type MockPortal_RegisterAPIKey_Call struct { } // RegisterAPIKey is a helper method to define mock.On call -// - owner string -// - organization string -// - role string -// - expiresAt time.Time +// - owner +// - organization +// - role +// - expiresAt func (_e *MockPortal_Expecter) RegisterAPIKey(owner interface{}, organization interface{}, role interface{}, expiresAt interface{}) *MockPortal_RegisterAPIKey_Call { return &MockPortal_RegisterAPIKey_Call{Call: _e.mock.On("RegisterAPIKey", owner, organization, role, expiresAt)} } func (_c *MockPortal_RegisterAPIKey_Call) Run(run func(owner string, organization string, role string, expiresAt time.Time)) *MockPortal_RegisterAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - var arg3 time.Time - if args[3] != nil { - arg3 = args[3].(time.Time) - } - run( - arg0, - arg1, - arg2, - arg3, - ) + run(args[0].(string), args[1].(string), args[2].(string), args[3].(time.Time)) }) return _c } @@ -689,20 +576,14 @@ type MockPortal_RevokeAPIKey_Call struct { } // RevokeAPIKey is a helper method to define mock.On call -// - key string +// - key func (_e *MockPortal_Expecter) RevokeAPIKey(key interface{}) *MockPortal_RevokeAPIKey_Call { return &MockPortal_RevokeAPIKey_Call{Call: _e.mock.On("RevokeAPIKey", key)} } func (_c *MockPortal_RevokeAPIKey_Call) Run(run func(key string)) *MockPortal_RevokeAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -740,26 +621,15 @@ type MockPortal_UpdateAPIKey_Call struct { } // UpdateAPIKey is a helper method to define mock.On call -// - key string -// - expiresAt time.Time +// - key +// - expiresAt func (_e *MockPortal_Expecter) UpdateAPIKey(key interface{}, expiresAt interface{}) *MockPortal_UpdateAPIKey_Call { return &MockPortal_UpdateAPIKey_Call{Call: _e.mock.On("UpdateAPIKey", key, expiresAt)} } func (_c *MockPortal_UpdateAPIKey_Call) Run(run func(key string, expiresAt time.Time)) *MockPortal_UpdateAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 time.Time - if args[1] != nil { - arg1 = args[1].(time.Time) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(time.Time)) }) return _c } @@ -797,26 +667,15 @@ type MockPortal_VerifyBuildArtifactDownload_Call struct { } // VerifyBuildArtifactDownload is a helper method to define mock.On call -// - file io.Reader -// - download Build +// - file +// - download func (_e *MockPortal_Expecter) VerifyBuildArtifactDownload(file interface{}, download interface{}) *MockPortal_VerifyBuildArtifactDownload_Call { return &MockPortal_VerifyBuildArtifactDownload_Call{Call: _e.mock.On("VerifyBuildArtifactDownload", file, download)} } func (_c *MockPortal_VerifyBuildArtifactDownload_Call) Run(run func(file io.Reader, download Build)) *MockPortal_VerifyBuildArtifactDownload_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 io.Reader - if args[0] != nil { - arg0 = args[0].(io.Reader) - } - var arg1 Build - if args[1] != nil { - arg1 = args[1].(Build) - } - run( - arg0, - arg1, - ) + run(args[0].(io.Reader), args[1].(Build)) }) return _c } @@ -892,20 +751,14 @@ type MockHttpClient_Do_Call struct { } // Do is a helper method to define mock.On call -// - request *http.Request +// - request func (_e *MockHttpClient_Expecter) Do(request interface{}) *MockHttpClient_Do_Call { return &MockHttpClient_Do_Call{Call: _e.mock.On("Do", request)} } func (_c *MockHttpClient_Do_Call) Run(run func(request *http.Request)) *MockHttpClient_Do_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 *http.Request - if args[0] != nil { - arg0 = args[0].(*http.Request) - } - run( - arg0, - ) + run(args[0].(*http.Request)) }) return _c } diff --git a/internal/system/mocks.go b/internal/system/mocks.go index ee80bc69..cf8e9292 100644 --- a/internal/system/mocks.go +++ b/internal/system/mocks.go @@ -58,32 +58,16 @@ type MockImageManager_BuildImage_Call struct { } // BuildImage is a helper method to define mock.On call -// - dockerfile string -// - tag string -// - buildContext string +// - dockerfile +// - tag +// - buildContext func (_e *MockImageManager_Expecter) BuildImage(dockerfile interface{}, tag interface{}, buildContext interface{}) *MockImageManager_BuildImage_Call { return &MockImageManager_BuildImage_Call{Call: _e.mock.On("BuildImage", dockerfile, tag, buildContext)} } func (_c *MockImageManager_BuildImage_Call) Run(run func(dockerfile string, tag string, buildContext string)) *MockImageManager_BuildImage_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(string), args[2].(string)) }) return _c } @@ -121,20 +105,14 @@ type MockImageManager_LoadImage_Call struct { } // LoadImage is a helper method to define mock.On call -// - imageTarPath string +// - imageTarPath func (_e *MockImageManager_Expecter) LoadImage(imageTarPath interface{}) *MockImageManager_LoadImage_Call { return &MockImageManager_LoadImage_Call{Call: _e.mock.On("LoadImage", imageTarPath)} } func (_c *MockImageManager_LoadImage_Call) Run(run func(imageTarPath string)) *MockImageManager_LoadImage_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -172,20 +150,14 @@ type MockImageManager_PushImage_Call struct { } // PushImage is a helper method to define mock.On call -// - tag string +// - tag func (_e *MockImageManager_Expecter) PushImage(tag interface{}) *MockImageManager_PushImage_Call { return &MockImageManager_PushImage_Call{Call: _e.mock.On("PushImage", tag)} } func (_c *MockImageManager_PushImage_Call) Run(run func(tag string)) *MockImageManager_PushImage_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } diff --git a/internal/util/mocks.go b/internal/util/mocks.go index abfdd4f8..394f6b82 100644 --- a/internal/util/mocks.go +++ b/internal/util/mocks.go @@ -70,26 +70,15 @@ type MockDockerfileManager_UpdateFromStatement_Call struct { } // UpdateFromStatement is a helper method to define mock.On call -// - dockerfile io.Reader -// - baseImage string +// - dockerfile +// - baseImage func (_e *MockDockerfileManager_Expecter) UpdateFromStatement(dockerfile interface{}, baseImage interface{}) *MockDockerfileManager_UpdateFromStatement_Call { return &MockDockerfileManager_UpdateFromStatement_Call{Call: _e.mock.On("UpdateFromStatement", dockerfile, baseImage)} } func (_c *MockDockerfileManager_UpdateFromStatement_Call) Run(run func(dockerfile io.Reader, baseImage string)) *MockDockerfileManager_UpdateFromStatement_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 io.Reader - if args[0] != nil { - arg0 = args[0].(io.Reader) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - run( - arg0, - arg1, - ) + run(args[0].(io.Reader), args[1].(string)) }) return _c } @@ -165,20 +154,14 @@ type MockFileIO_Create_Call struct { } // Create is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) Create(filename interface{}) *MockFileIO_Create_Call { return &MockFileIO_Create_Call{Call: _e.mock.On("Create", filename)} } func (_c *MockFileIO_Create_Call) Run(run func(filename string)) *MockFileIO_Create_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -216,32 +199,16 @@ type MockFileIO_CreateAndWrite_Call struct { } // CreateAndWrite is a helper method to define mock.On call -// - filePath string -// - data []byte -// - fileType string +// - filePath +// - data +// - fileType func (_e *MockFileIO_Expecter) CreateAndWrite(filePath interface{}, data interface{}, fileType interface{}) *MockFileIO_CreateAndWrite_Call { return &MockFileIO_CreateAndWrite_Call{Call: _e.mock.On("CreateAndWrite", filePath, data, fileType)} } func (_c *MockFileIO_CreateAndWrite_Call) Run(run func(filePath string, data []byte, fileType string)) *MockFileIO_CreateAndWrite_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 []byte - if args[1] != nil { - arg1 = args[1].([]byte) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].([]byte), args[2].(string)) }) return _c } @@ -279,20 +246,14 @@ type MockFileIO_Exists_Call struct { } // Exists is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) Exists(filename interface{}) *MockFileIO_Exists_Call { return &MockFileIO_Exists_Call{Call: _e.mock.On("Exists", filename)} } func (_c *MockFileIO_Exists_Call) Run(run func(filename string)) *MockFileIO_Exists_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -339,20 +300,14 @@ type MockFileIO_IsDirectory_Call struct { } // IsDirectory is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) IsDirectory(filename interface{}) *MockFileIO_IsDirectory_Call { return &MockFileIO_IsDirectory_Call{Call: _e.mock.On("IsDirectory", filename)} } func (_c *MockFileIO_IsDirectory_Call) Run(run func(filename string)) *MockFileIO_IsDirectory_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -390,26 +345,15 @@ type MockFileIO_MkdirAll_Call struct { } // MkdirAll is a helper method to define mock.On call -// - path string -// - perm os.FileMode +// - path +// - perm func (_e *MockFileIO_Expecter) MkdirAll(path interface{}, perm interface{}) *MockFileIO_MkdirAll_Call { return &MockFileIO_MkdirAll_Call{Call: _e.mock.On("MkdirAll", path, perm)} } func (_c *MockFileIO_MkdirAll_Call) Run(run func(path string, perm os.FileMode)) *MockFileIO_MkdirAll_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 os.FileMode - if args[1] != nil { - arg1 = args[1].(os.FileMode) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(os.FileMode)) }) return _c } @@ -458,20 +402,14 @@ type MockFileIO_Open_Call struct { } // Open is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) Open(filename interface{}) *MockFileIO_Open_Call { return &MockFileIO_Open_Call{Call: _e.mock.On("Open", filename)} } func (_c *MockFileIO_Open_Call) Run(run func(filename string)) *MockFileIO_Open_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -520,20 +458,14 @@ type MockFileIO_OpenAppend_Call struct { } // OpenAppend is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) OpenAppend(filename interface{}) *MockFileIO_OpenAppend_Call { return &MockFileIO_OpenAppend_Call{Call: _e.mock.On("OpenAppend", filename)} } func (_c *MockFileIO_OpenAppend_Call) Run(run func(filename string)) *MockFileIO_OpenAppend_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -582,32 +514,16 @@ type MockFileIO_OpenFile_Call struct { } // OpenFile is a helper method to define mock.On call -// - name string -// - flag int -// - perm os.FileMode +// - name +// - flag +// - perm func (_e *MockFileIO_Expecter) OpenFile(name interface{}, flag interface{}, perm interface{}) *MockFileIO_OpenFile_Call { return &MockFileIO_OpenFile_Call{Call: _e.mock.On("OpenFile", name, flag, perm)} } func (_c *MockFileIO_OpenFile_Call) Run(run func(name string, flag int, perm os.FileMode)) *MockFileIO_OpenFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 int - if args[1] != nil { - arg1 = args[1].(int) - } - var arg2 os.FileMode - if args[2] != nil { - arg2 = args[2].(os.FileMode) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(int), args[2].(os.FileMode)) }) return _c } @@ -656,20 +572,14 @@ type MockFileIO_ReadDir_Call struct { } // ReadDir is a helper method to define mock.On call -// - dirname string +// - dirname func (_e *MockFileIO_Expecter) ReadDir(dirname interface{}) *MockFileIO_ReadDir_Call { return &MockFileIO_ReadDir_Call{Call: _e.mock.On("ReadDir", dirname)} } func (_c *MockFileIO_ReadDir_Call) Run(run func(dirname string)) *MockFileIO_ReadDir_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -718,20 +628,14 @@ type MockFileIO_ReadFile_Call struct { } // ReadFile is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) ReadFile(filename interface{}) *MockFileIO_ReadFile_Call { return &MockFileIO_ReadFile_Call{Call: _e.mock.On("ReadFile", filename)} } func (_c *MockFileIO_ReadFile_Call) Run(run func(filename string)) *MockFileIO_ReadFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -769,32 +673,16 @@ type MockFileIO_WriteFile_Call struct { } // WriteFile is a helper method to define mock.On call -// - filename string -// - data []byte -// - perm os.FileMode +// - filename +// - data +// - perm func (_e *MockFileIO_Expecter) WriteFile(filename interface{}, data interface{}, perm interface{}) *MockFileIO_WriteFile_Call { return &MockFileIO_WriteFile_Call{Call: _e.mock.On("WriteFile", filename, data, perm)} } func (_c *MockFileIO_WriteFile_Call) Run(run func(filename string, data []byte, perm os.FileMode)) *MockFileIO_WriteFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 []byte - if args[1] != nil { - arg1 = args[1].([]byte) - } - var arg2 os.FileMode - if args[2] != nil { - arg2 = args[2].(os.FileMode) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].([]byte), args[2].(os.FileMode)) }) return _c } @@ -924,8 +812,8 @@ type MockTableWriter_AppendHeader_Call struct { } // AppendHeader is a helper method to define mock.On call -// - row table.Row -// - configs ...table.RowConfig +// - row +// - configs func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...interface{}) *MockTableWriter_AppendHeader_Call { return &MockTableWriter_AppendHeader_Call{Call: _e.mock.On("AppendHeader", append([]interface{}{row}, configs...)...)} @@ -933,20 +821,13 @@ func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...int func (_c *MockTableWriter_AppendHeader_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendHeader_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 table.Row - if args[0] != nil { - arg0 = args[0].(table.Row) + variadicArgs := make([]table.RowConfig, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(table.RowConfig) + } } - var arg1 []table.RowConfig - var variadicArgs []table.RowConfig - if len(args) > 1 { - variadicArgs = args[1].([]table.RowConfig) - } - arg1 = variadicArgs - run( - arg0, - arg1..., - ) + run(args[0].(table.Row), variadicArgs...) }) return _c } @@ -978,8 +859,8 @@ type MockTableWriter_AppendRow_Call struct { } // AppendRow is a helper method to define mock.On call -// - row table.Row -// - configs ...table.RowConfig +// - row +// - configs func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interface{}) *MockTableWriter_AppendRow_Call { return &MockTableWriter_AppendRow_Call{Call: _e.mock.On("AppendRow", append([]interface{}{row}, configs...)...)} @@ -987,20 +868,13 @@ func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interf func (_c *MockTableWriter_AppendRow_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendRow_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 table.Row - if args[0] != nil { - arg0 = args[0].(table.Row) - } - var arg1 []table.RowConfig - var variadicArgs []table.RowConfig - if len(args) > 1 { - variadicArgs = args[1].([]table.RowConfig) + variadicArgs := make([]table.RowConfig, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(table.RowConfig) + } } - arg1 = variadicArgs - run( - arg0, - arg1..., - ) + run(args[0].(table.Row), variadicArgs...) }) return _c } From 590ad32e9002ef7464e056e66b1e02579238342d Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Thu, 8 Jan 2026 18:17:34 +0100 Subject: [PATCH 34/65] fix: merge errors --- internal/installer/node/node.go | 135 +++++++++++++++++++++++++-- internal/installer/node/node_test.go | 2 +- internal/util/filewriter.go | 5 - 3 files changed, 127 insertions(+), 15 deletions(-) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 8f689bdb..74c0686c 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -33,6 +33,8 @@ type NodeManager struct { KeyPath string } +const jumpboxUser = "ubuntu" + func shellEscape(s string) string { return strings.ReplaceAll(s, "'", "'\\''") } @@ -208,12 +210,14 @@ func (nm *NodeManager) RunSSHCommand(jumpboxIp string, ip string, username strin if err != nil { return fmt.Errorf("failed to get client: %w", err) } - defer func() { _ = client.Close() }() + defer util.IgnoreError(client.Close) session, err := client.NewSession() if err != nil { return fmt.Errorf("failed to create session on target node (%s): %v", ip, err) } - defer func() { _ = session.Close() }() + defer util.IgnoreError(session.Close) + + _ = session.Setenv("OMS_PORTAL_API_KEY", os.Getenv("OMS_PORTAL_API_KEY")) if err := nm.forwardAgent(client, session); err != nil { fmt.Printf(" Warning: Agent forwarding setup failed on session: %v\n", err) @@ -245,7 +249,7 @@ func (nm *NodeManager) GetClient(jumpboxIp string, ip string, username string) ( } if jumpboxIp != "" { - jbClient, err := nm.connectToJumpbox(jumpboxIp, username) + jbClient, err := nm.connectToJumpbox(jumpboxIp, jumpboxUser) if err != nil { return nil, fmt.Errorf("failed to connect to jumpbox: %v", err) } @@ -307,19 +311,19 @@ func (nm *NodeManager) CopyFile(jumpboxIp string, ip string, username string, sr if err != nil { return fmt.Errorf("failed to get SSH client: %v", err) } - defer func() { _ = client.Close() }() + defer util.IgnoreError(client.Close) srcFile, err := nm.FileIO.Open(src) if err != nil { return fmt.Errorf("failed to open source file %s: %v", src, err) } - defer func() { _ = srcFile.Close() }() + defer util.IgnoreError(srcFile.Close) dstFile, err := client.Create(dst) if err != nil { return fmt.Errorf("failed to create destination file %s: %v", dst, err) } - defer func() { _ = dstFile.Close() }() + defer util.IgnoreError(dstFile.Close) _, err = dstFile.ReadFrom(srcFile) if err != nil { @@ -335,17 +339,74 @@ func (n *Node) HasCommand(nm *NodeManager, command string) bool { return err == nil } -func (n *Node) CopyFile(nm *NodeManager, src string, dst string) error { +func (n *Node) InstallOms(nm *NodeManager) error { + remoteCommands := []string{ + "wget -qO- 'https://api.github.com/repos/codesphere-cloud/oms/releases/latest' | jq -r '.assets[] | select(.name | match(\"oms-cli.*linux_amd64\")) | .browser_download_url' | xargs wget -O oms-cli", + "chmod +x oms-cli; sudo mv oms-cli /usr/local/bin/", + "curl -LO https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.amd64; sudo mv sops-v3.11.0.linux.amd64 /usr/local/bin/sops; sudo chmod +x /usr/local/bin/sops", + "wget https://dl.filippo.io/age/latest?for=linux/amd64 -O age.tar.gz; tar -xvf age.tar.gz; sudo mv age/age* /usr/local/bin/", + } + for _, cmd := range remoteCommands { + err := nm.RunSSHCommand("", n.ExternalIP, "root", cmd) + if err != nil { + return fmt.Errorf("failed to run remote command '%s': %w", cmd, err) + } + } + return nil +} + +func (n *Node) CopyFile(jumpbox *Node, nm *NodeManager, src string, dst string) error { user := n.User if user == "" { user = "root" } - err := nm.EnsureDirectoryExists("", n.ExternalIP, user, filepath.Dir(dst)) + if jumpbox == nil { + err := nm.EnsureDirectoryExists("", n.ExternalIP, user, filepath.Dir(dst)) + if err != nil { + return fmt.Errorf("failed to ensure directory exists: %w", err) + } + return nm.CopyFile("", n.ExternalIP, user, src, dst) + } + err := nm.EnsureDirectoryExists(jumpbox.ExternalIP, n.InternalIP, user, filepath.Dir(dst)) if err != nil { return fmt.Errorf("failed to ensure directory exists: %w", err) } - return nm.CopyFile("", n.ExternalIP, user, src, dst) + return nm.CopyFile(jumpbox.ExternalIP, n.InternalIP, user, src, dst) +} + +func (n *Node) HasAcceptEnvConfigured(jumpbox *Node, nm *NodeManager) bool { + checkCommand := "sudo grep -E '^AcceptEnv OMS_PORTAL_API_KEY' /etc/ssh/sshd_config >/dev/null 2>&1" + err := n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommand) + return err == nil +} + +func (n *Node) ConfigureAcceptEnv(jumpbox *Node, nm *NodeManager) error { + cmds := []string{ + "sudo sed -i 's/^#\\?AcceptEnv.*/AcceptEnv OMS_PORTAL_API_KEY/' /etc/ssh/sshd_config", + "sudo systemctl restart sshd", + } + for _, cmd := range cmds { + err := n.RunSSHCommand(jumpbox, nm, "ubuntu", cmd) + if err != nil { + return fmt.Errorf("failed to run command '%s': %w", cmd, err) + } + } + return nil +} + +func (n *Node) HasRootLoginEnabled(jumpbox *Node, nm *NodeManager) bool { + checkCommandPermit := "sudo grep -E '^PermitRootLogin yes' /etc/ssh/sshd_config >/dev/null 2>&1" + err := n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommandPermit) + if err != nil { + return false + } + checkCommandAuthorizedKeys := "sudo grep -E '^no-port-forwarding' /root/.ssh/authorized_keys >/dev/null 2>&1" + err = n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommandAuthorizedKeys) + if err == nil { + return false + } + return true } func (n *Node) HasFile(jumpbox *Node, nm *NodeManager, filePath string) bool { @@ -362,6 +423,62 @@ func (n *Node) RunSSHCommand(jumpbox *Node, nm *NodeManager, username string, co return nm.RunSSHCommand(jumpbox.ExternalIP, n.InternalIP, username, command) } +func (n *Node) EnableRootLogin(jumpbox *Node, nm *NodeManager) error { + cmds := []string{ + "sudo sed -i 's/^#\\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config", + "sudo sed -i 's/no-port-forwarding.*$//g' /root/.ssh/authorized_keys", + "sudo systemctl restart sshd", + } + for _, cmd := range cmds { + err := n.RunSSHCommand(jumpbox, nm, "ubuntu", cmd) + if err != nil { + return fmt.Errorf("failed to run command '%s': %w", cmd, err) + } + } + return nil +} + +func (n *Node) WaitForSSH(jumpbox *Node, nm *NodeManager, timeout time.Duration) error { + start := time.Now() + jumpboxIp := "" + nodeIp := n.ExternalIP + if jumpbox != nil { + jumpboxIp = jumpbox.ExternalIP + nodeIp = n.InternalIP + } + for { + client, err := nm.GetClient(jumpboxIp, nodeIp, jumpboxUser) + if err == nil { + _ = client.Close() + return nil + } + if time.Since(start) > timeout { + return fmt.Errorf("timeout waiting for SSH on node %s (%s)", n.Name, n.ExternalIP) + } + time.Sleep(5 * time.Second) + } +} + +func (n *Node) HasInotifyWatchesConfigured(jumpbox *Node, nm *NodeManager) bool { + checkCommand := "sudo grep -E '^fs.inotify.max_user_watches=1048576' /etc/sysctl.conf >/dev/null 2>&1" + err := n.RunSSHCommand(jumpbox, nm, "root", checkCommand) + return err == nil +} + +func (n *Node) ConfigureInotifyWatches(jumpbox *Node, nm *NodeManager) error { + cmds := []string{ + "echo 'fs.inotify.max_user_watches=1048576' | sudo tee -a /etc/sysctl.conf", + "sudo sysctl -p", + } + for _, cmd := range cmds { + err := n.RunSSHCommand(jumpbox, nm, "root", cmd) + if err != nil { + return fmt.Errorf("failed to run command '%s': %w", cmd, err) + } + } + return nil +} + func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath string, force bool) error { remoteK0sDir := "/usr/local/bin" remoteK0sBinary := filepath.Join(remoteK0sDir, "k0s") diff --git a/internal/installer/node/node_test.go b/internal/installer/node/node_test.go index b589b92e..1d7d1e43 100644 --- a/internal/installer/node/node_test.go +++ b/internal/installer/node/node_test.go @@ -329,7 +329,7 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== Context("CopyFile", func() { It("should fail when directory creation fails", func() { - err := n.CopyFile(nm, "/some/file", "/remote/path/dest.txt") + err := n.CopyFile(nil, nm, "/some/file", "/remote/path/dest.txt") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to ensure directory exists")) }) diff --git a/internal/util/filewriter.go b/internal/util/filewriter.go index ad5385ad..2f9552c9 100644 --- a/internal/util/filewriter.go +++ b/internal/util/filewriter.go @@ -17,7 +17,6 @@ type FileIO interface { MkdirAll(path string, perm os.FileMode) error OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) WriteFile(filename string, data []byte, perm os.FileMode) error - ReadFile(filename string) ([]byte, error) ReadDir(dirname string) ([]os.DirEntry, error) ReadFile(filename string) ([]byte, error) CreateAndWrite(filePath string, data []byte, fileType string) error @@ -85,10 +84,6 @@ func (fs *FilesystemWriter) WriteFile(filename string, data []byte, perm os.File return os.WriteFile(filename, data, perm) } -func (fs *FilesystemWriter) ReadFile(filename string) ([]byte, error) { - return os.ReadFile(filename) -} - func (fs *FilesystemWriter) ReadDir(dirname string) ([]os.DirEntry, error) { return os.ReadDir(dirname) } From 88d365b9b2e2b86bba20334f1b7b9bd4ea34ea3a Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Thu, 8 Jan 2026 17:19:05 +0000 Subject: [PATCH 35/65] chore(docs): Auto-update docs and licenses Signed-off-by: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> --- NOTICE | 70 +++++++++-- cli/cmd/mocks.go | 17 ++- internal/installer/mocks.go | 213 +++++++++++++++++++++++++++------- internal/portal/mocks.go | 225 +++++++++++++++++++++++++++++------- internal/system/mocks.go | 44 +++++-- internal/tmpl/NOTICE | 40 +++++-- internal/util/mocks.go | 222 +++++++++++++++++++++++++++-------- 7 files changed, 675 insertions(+), 156 deletions(-) diff --git a/NOTICE b/NOTICE index 2fc3e2ae..0478b096 100644 --- a/NOTICE +++ b/NOTICE @@ -185,9 +185,9 @@ License URL: https://github.com/inconshreveable/go-update/blob/8152e7eb6ccf/inte ---------- Module: github.com/jedib0t/go-pretty/v6 -Version: v6.7.7 +Version: v6.7.8 License: MIT -License URL: https://github.com/jedib0t/go-pretty/blob/v6.7.7/LICENSE +License URL: https://github.com/jedib0t/go-pretty/blob/v6.7.8/LICENSE ---------- Module: github.com/kr/fs @@ -195,6 +195,12 @@ Version: v0.1.0 License: BSD-3-Clause License URL: https://github.com/kr/fs/blob/v0.1.0/LICENSE +---------- +Module: github.com/lithammer/shortuuid +Version: v3.0.0 +License: MIT +License URL: https://github.com/lithammer/shortuuid/blob/v3.0.0/LICENSE + ---------- Module: github.com/mattn/go-runewidth Version: v0.0.19 @@ -315,6 +321,12 @@ Version: v0.46.0 License: BSD-3-Clause License URL: https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE +---------- +Module: golang.org/x/net +Version: v0.48.0 +License: BSD-3-Clause +License URL: https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE + ---------- Module: golang.org/x/oauth2 Version: v0.34.0 @@ -340,22 +352,58 @@ License: BSD-3-Clause License URL: https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE ---------- -Module: golang.org/x/sys/unix -Version: v0.39.0 +Module: golang.org/x/text +Version: v0.32.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/sys/+/v0.39.0:LICENSE +License URL: https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE ---------- -Module: golang.org/x/term -Version: v0.38.0 +Module: golang.org/x/time/rate +Version: v0.14.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE +License URL: https://cs.opensource.google/go/x/time/+/v0.14.0:LICENSE ---------- -Module: golang.org/x/text -Version: v0.32.0 +Module: google.golang.org/api +Version: v0.247.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE +License URL: https://github.com/googleapis/google-api-go-client/blob/v0.247.0/LICENSE + +---------- +Module: google.golang.org/api/internal/third_party/uritemplates +Version: v0.247.0 +License: BSD-3-Clause +License URL: https://github.com/googleapis/google-api-go-client/blob/v0.247.0/internal/third_party/uritemplates/LICENSE + +---------- +Module: google.golang.org/genproto/googleapis +Version: v0.0.0-20250715232539-7130f93afb79 +License: Apache-2.0 +License URL: https://github.com/googleapis/go-genproto/blob/7130f93afb79/LICENSE + +---------- +Module: google.golang.org/genproto/googleapis/api +Version: v0.0.0-20250825161204-c5933d9347a5 +License: Apache-2.0 +License URL: https://github.com/googleapis/go-genproto/blob/c5933d9347a5/googleapis/api/LICENSE + +---------- +Module: google.golang.org/genproto/googleapis/rpc +Version: v0.0.0-20250825161204-c5933d9347a5 +License: Apache-2.0 +License URL: https://github.com/googleapis/go-genproto/blob/c5933d9347a5/googleapis/rpc/LICENSE + +---------- +Module: google.golang.org/grpc +Version: v1.75.0 +License: Apache-2.0 +License URL: https://github.com/grpc/grpc-go/blob/v1.75.0/LICENSE + +---------- +Module: google.golang.org/protobuf +Version: v1.36.11 +License: BSD-3-Clause +License URL: https://github.com/protocolbuffers/protobuf-go/blob/v1.36.11/LICENSE ---------- Module: gopkg.in/yaml.v3 diff --git a/cli/cmd/mocks.go b/cli/cmd/mocks.go index a1ac35ea..2f86863c 100644 --- a/cli/cmd/mocks.go +++ b/cli/cmd/mocks.go @@ -74,15 +74,26 @@ type MockOMSUpdater_Update_Call struct { } // Update is a helper method to define mock.On call -// - v -// - repo +// - v semver.Version +// - repo string func (_e *MockOMSUpdater_Expecter) Update(v interface{}, repo interface{}) *MockOMSUpdater_Update_Call { return &MockOMSUpdater_Update_Call{Call: _e.mock.On("Update", v, repo)} } func (_c *MockOMSUpdater_Update_Call) Run(run func(v semver.Version, repo string)) *MockOMSUpdater_Update_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(semver.Version), args[1].(string)) + var arg0 semver.Version + if args[0] != nil { + arg0 = args[0].(semver.Version) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } diff --git a/internal/installer/mocks.go b/internal/installer/mocks.go index ab2f322f..8474882b 100644 --- a/internal/installer/mocks.go +++ b/internal/installer/mocks.go @@ -69,14 +69,20 @@ type MockConfigManager_ParseConfigYaml_Call struct { } // ParseConfigYaml is a helper method to define mock.On call -// - configPath +// - configPath string func (_e *MockConfigManager_Expecter) ParseConfigYaml(configPath interface{}) *MockConfigManager_ParseConfigYaml_Call { return &MockConfigManager_ParseConfigYaml_Call{Call: _e.mock.On("ParseConfigYaml", configPath)} } func (_c *MockConfigManager_ParseConfigYaml_Call) Run(run func(configPath string)) *MockConfigManager_ParseConfigYaml_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -141,14 +147,20 @@ type MockInstallConfigManager_ApplyProfile_Call struct { } // ApplyProfile is a helper method to define mock.On call -// - profile +// - profile string func (_e *MockInstallConfigManager_Expecter) ApplyProfile(profile interface{}) *MockInstallConfigManager_ApplyProfile_Call { return &MockInstallConfigManager_ApplyProfile_Call{Call: _e.mock.On("ApplyProfile", profile)} } func (_c *MockInstallConfigManager_ApplyProfile_Call) Run(run func(profile string)) *MockInstallConfigManager_ApplyProfile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -366,14 +378,20 @@ type MockInstallConfigManager_LoadInstallConfigFromFile_Call struct { } // LoadInstallConfigFromFile is a helper method to define mock.On call -// - configPath +// - configPath string func (_e *MockInstallConfigManager_Expecter) LoadInstallConfigFromFile(configPath interface{}) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { return &MockInstallConfigManager_LoadInstallConfigFromFile_Call{Call: _e.mock.On("LoadInstallConfigFromFile", configPath)} } func (_c *MockInstallConfigManager_LoadInstallConfigFromFile_Call) Run(run func(configPath string)) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -411,14 +429,20 @@ type MockInstallConfigManager_LoadVaultFromFile_Call struct { } // LoadVaultFromFile is a helper method to define mock.On call -// - vaultPath +// - vaultPath string func (_e *MockInstallConfigManager_Expecter) LoadVaultFromFile(vaultPath interface{}) *MockInstallConfigManager_LoadVaultFromFile_Call { return &MockInstallConfigManager_LoadVaultFromFile_Call{Call: _e.mock.On("LoadVaultFromFile", vaultPath)} } func (_c *MockInstallConfigManager_LoadVaultFromFile_Call) Run(run func(vaultPath string)) *MockInstallConfigManager_LoadVaultFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -592,15 +616,26 @@ type MockInstallConfigManager_WriteInstallConfig_Call struct { } // WriteInstallConfig is a helper method to define mock.On call -// - configPath -// - withComments +// - configPath string +// - withComments bool func (_e *MockInstallConfigManager_Expecter) WriteInstallConfig(configPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteInstallConfig_Call { return &MockInstallConfigManager_WriteInstallConfig_Call{Call: _e.mock.On("WriteInstallConfig", configPath, withComments)} } func (_c *MockInstallConfigManager_WriteInstallConfig_Call) Run(run func(configPath string, withComments bool)) *MockInstallConfigManager_WriteInstallConfig_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -638,15 +673,26 @@ type MockInstallConfigManager_WriteVault_Call struct { } // WriteVault is a helper method to define mock.On call -// - vaultPath -// - withComments +// - vaultPath string +// - withComments bool func (_e *MockInstallConfigManager_Expecter) WriteVault(vaultPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteVault_Call { return &MockInstallConfigManager_WriteVault_Call{Call: _e.mock.On("WriteVault", vaultPath, withComments)} } func (_c *MockInstallConfigManager_WriteVault_Call) Run(run func(vaultPath string, withComments bool)) *MockInstallConfigManager_WriteVault_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -720,16 +766,32 @@ type MockK0sManager_Download_Call struct { } // Download is a helper method to define mock.On call -// - version -// - force -// - quiet +// - version string +// - force bool +// - quiet bool func (_e *MockK0sManager_Expecter) Download(version interface{}, force interface{}, quiet interface{}) *MockK0sManager_Download_Call { return &MockK0sManager_Download_Call{Call: _e.mock.On("Download", version, force, quiet)} } func (_c *MockK0sManager_Download_Call) Run(run func(version string, force bool, quiet bool)) *MockK0sManager_Download_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool), args[2].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + var arg2 bool + if args[2] != nil { + arg2 = args[2].(bool) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -820,17 +882,38 @@ type MockK0sManager_Install_Call struct { } // Install is a helper method to define mock.On call -// - configPath -// - k0sPath -// - force -// - nodeIP +// - configPath string +// - k0sPath string +// - force bool +// - nodeIP string func (_e *MockK0sManager_Expecter) Install(configPath interface{}, k0sPath interface{}, force interface{}, nodeIP interface{}) *MockK0sManager_Install_Call { return &MockK0sManager_Install_Call{Call: _e.mock.On("Install", configPath, k0sPath, force, nodeIP)} } func (_c *MockK0sManager_Install_Call) Run(run func(configPath string, k0sPath string, force bool, nodeIP string)) *MockK0sManager_Install_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(bool), args[3].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 bool + if args[2] != nil { + arg2 = args[2].(bool) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + ) }) return _c } @@ -868,14 +951,20 @@ type MockK0sManager_Reset_Call struct { } // Reset is a helper method to define mock.On call -// - k0sPath +// - k0sPath string func (_e *MockK0sManager_Expecter) Reset(k0sPath interface{}) *MockK0sManager_Reset_Call { return &MockK0sManager_Reset_Call{Call: _e.mock.On("Reset", k0sPath)} } func (_c *MockK0sManager_Reset_Call) Run(run func(k0sPath string)) *MockK0sManager_Reset_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -940,14 +1029,20 @@ type MockPackageManager_Extract_Call struct { } // Extract is a helper method to define mock.On call -// - force +// - force bool func (_e *MockPackageManager_Expecter) Extract(force interface{}) *MockPackageManager_Extract_Call { return &MockPackageManager_Extract_Call{Call: _e.mock.On("Extract", force)} } func (_c *MockPackageManager_Extract_Call) Run(run func(force bool)) *MockPackageManager_Extract_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(bool)) + var arg0 bool + if args[0] != nil { + arg0 = args[0].(bool) + } + run( + arg0, + ) }) return _c } @@ -985,15 +1080,26 @@ type MockPackageManager_ExtractDependency_Call struct { } // ExtractDependency is a helper method to define mock.On call -// - file -// - force +// - file string +// - force bool func (_e *MockPackageManager_Expecter) ExtractDependency(file interface{}, force interface{}) *MockPackageManager_ExtractDependency_Call { return &MockPackageManager_ExtractDependency_Call{Call: _e.mock.On("ExtractDependency", file, force)} } func (_c *MockPackageManager_ExtractDependency_Call) Run(run func(file string, force bool)) *MockPackageManager_ExtractDependency_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -1040,14 +1146,20 @@ type MockPackageManager_ExtractOciImageIndex_Call struct { } // ExtractOciImageIndex is a helper method to define mock.On call -// - imagefile +// - imagefile string func (_e *MockPackageManager_Expecter) ExtractOciImageIndex(imagefile interface{}) *MockPackageManager_ExtractOciImageIndex_Call { return &MockPackageManager_ExtractOciImageIndex_Call{Call: _e.mock.On("ExtractOciImageIndex", imagefile)} } func (_c *MockPackageManager_ExtractOciImageIndex_Call) Run(run func(imagefile string)) *MockPackageManager_ExtractOciImageIndex_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -1140,14 +1252,20 @@ type MockPackageManager_GetBaseimageName_Call struct { } // GetBaseimageName is a helper method to define mock.On call -// - baseimage +// - baseimage string func (_e *MockPackageManager_Expecter) GetBaseimageName(baseimage interface{}) *MockPackageManager_GetBaseimageName_Call { return &MockPackageManager_GetBaseimageName_Call{Call: _e.mock.On("GetBaseimageName", baseimage)} } func (_c *MockPackageManager_GetBaseimageName_Call) Run(run func(baseimage string)) *MockPackageManager_GetBaseimageName_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -1194,15 +1312,26 @@ type MockPackageManager_GetBaseimagePath_Call struct { } // GetBaseimagePath is a helper method to define mock.On call -// - baseimage -// - force +// - baseimage string +// - force bool func (_e *MockPackageManager_Expecter) GetBaseimagePath(baseimage interface{}, force interface{}) *MockPackageManager_GetBaseimagePath_Call { return &MockPackageManager_GetBaseimagePath_Call{Call: _e.mock.On("GetBaseimagePath", baseimage, force)} } func (_c *MockPackageManager_GetBaseimagePath_Call) Run(run func(baseimage string, force bool)) *MockPackageManager_GetBaseimagePath_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -1293,14 +1422,20 @@ type MockPackageManager_GetDependencyPath_Call struct { } // GetDependencyPath is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockPackageManager_Expecter) GetDependencyPath(filename interface{}) *MockPackageManager_GetDependencyPath_Call { return &MockPackageManager_GetDependencyPath_Call{Call: _e.mock.On("GetDependencyPath", filename)} } func (_c *MockPackageManager_GetDependencyPath_Call) Run(run func(filename string)) *MockPackageManager_GetDependencyPath_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } diff --git a/internal/portal/mocks.go b/internal/portal/mocks.go index 83372369..c162a3ee 100644 --- a/internal/portal/mocks.go +++ b/internal/portal/mocks.go @@ -61,16 +61,32 @@ type MockHttp_Download_Call struct { } // Download is a helper method to define mock.On call -// - url -// - file -// - quiet +// - url string +// - file io.Writer +// - quiet bool func (_e *MockHttp_Expecter) Download(url interface{}, file interface{}, quiet interface{}) *MockHttp_Download_Call { return &MockHttp_Download_Call{Call: _e.mock.On("Download", url, file, quiet)} } func (_c *MockHttp_Download_Call) Run(run func(url string, file io.Writer, quiet bool)) *MockHttp_Download_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(io.Writer), args[2].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 io.Writer + if args[1] != nil { + arg1 = args[1].(io.Writer) + } + var arg2 bool + if args[2] != nil { + arg2 = args[2].(bool) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -119,14 +135,20 @@ type MockHttp_Get_Call struct { } // Get is a helper method to define mock.On call -// - url +// - url string func (_e *MockHttp_Expecter) Get(url interface{}) *MockHttp_Get_Call { return &MockHttp_Get_Call{Call: _e.mock.On("Get", url)} } func (_c *MockHttp_Get_Call) Run(run func(url string)) *MockHttp_Get_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -175,16 +197,32 @@ type MockHttp_Request_Call struct { } // Request is a helper method to define mock.On call -// - url -// - method -// - body +// - url string +// - method string +// - body io.Reader func (_e *MockHttp_Expecter) Request(url interface{}, method interface{}, body interface{}) *MockHttp_Request_Call { return &MockHttp_Request_Call{Call: _e.mock.On("Request", url, method, body)} } func (_c *MockHttp_Request_Call) Run(run func(url string, method string, body io.Reader)) *MockHttp_Request_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(io.Reader)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 io.Reader + if args[2] != nil { + arg2 = args[2].(io.Reader) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -249,18 +287,44 @@ type MockPortal_DownloadBuildArtifact_Call struct { } // DownloadBuildArtifact is a helper method to define mock.On call -// - product -// - build -// - file -// - startByte -// - quiet +// - product Product +// - build Build +// - file io.Writer +// - startByte int +// - quiet bool func (_e *MockPortal_Expecter) DownloadBuildArtifact(product interface{}, build interface{}, file interface{}, startByte interface{}, quiet interface{}) *MockPortal_DownloadBuildArtifact_Call { return &MockPortal_DownloadBuildArtifact_Call{Call: _e.mock.On("DownloadBuildArtifact", product, build, file, startByte, quiet)} } func (_c *MockPortal_DownloadBuildArtifact_Call) Run(run func(product Product, build Build, file io.Writer, startByte int, quiet bool)) *MockPortal_DownloadBuildArtifact_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(Product), args[1].(Build), args[2].(io.Writer), args[3].(int), args[4].(bool)) + var arg0 Product + if args[0] != nil { + arg0 = args[0].(Product) + } + var arg1 Build + if args[1] != nil { + arg1 = args[1].(Build) + } + var arg2 io.Writer + if args[2] != nil { + arg2 = args[2].(io.Writer) + } + var arg3 int + if args[3] != nil { + arg3 = args[3].(int) + } + var arg4 bool + if args[4] != nil { + arg4 = args[4].(bool) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) }) return _c } @@ -307,14 +371,20 @@ type MockPortal_GetApiKeyId_Call struct { } // GetApiKeyId is a helper method to define mock.On call -// - oldKey +// - oldKey string func (_e *MockPortal_Expecter) GetApiKeyId(oldKey interface{}) *MockPortal_GetApiKeyId_Call { return &MockPortal_GetApiKeyId_Call{Call: _e.mock.On("GetApiKeyId", oldKey)} } func (_c *MockPortal_GetApiKeyId_Call) Run(run func(oldKey string)) *MockPortal_GetApiKeyId_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -361,16 +431,32 @@ type MockPortal_GetBuild_Call struct { } // GetBuild is a helper method to define mock.On call -// - product -// - version -// - hash +// - product Product +// - version string +// - hash string func (_e *MockPortal_Expecter) GetBuild(product interface{}, version interface{}, hash interface{}) *MockPortal_GetBuild_Call { return &MockPortal_GetBuild_Call{Call: _e.mock.On("GetBuild", product, version, hash)} } func (_c *MockPortal_GetBuild_Call) Run(run func(product Product, version string, hash string)) *MockPortal_GetBuild_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(Product), args[1].(string), args[2].(string)) + var arg0 Product + if args[0] != nil { + arg0 = args[0].(Product) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -472,14 +558,20 @@ type MockPortal_ListBuilds_Call struct { } // ListBuilds is a helper method to define mock.On call -// - product +// - product Product func (_e *MockPortal_Expecter) ListBuilds(product interface{}) *MockPortal_ListBuilds_Call { return &MockPortal_ListBuilds_Call{Call: _e.mock.On("ListBuilds", product)} } func (_c *MockPortal_ListBuilds_Call) Run(run func(product Product)) *MockPortal_ListBuilds_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(Product)) + var arg0 Product + if args[0] != nil { + arg0 = args[0].(Product) + } + run( + arg0, + ) }) return _c } @@ -528,17 +620,38 @@ type MockPortal_RegisterAPIKey_Call struct { } // RegisterAPIKey is a helper method to define mock.On call -// - owner -// - organization -// - role -// - expiresAt +// - owner string +// - organization string +// - role string +// - expiresAt time.Time func (_e *MockPortal_Expecter) RegisterAPIKey(owner interface{}, organization interface{}, role interface{}, expiresAt interface{}) *MockPortal_RegisterAPIKey_Call { return &MockPortal_RegisterAPIKey_Call{Call: _e.mock.On("RegisterAPIKey", owner, organization, role, expiresAt)} } func (_c *MockPortal_RegisterAPIKey_Call) Run(run func(owner string, organization string, role string, expiresAt time.Time)) *MockPortal_RegisterAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(string), args[3].(time.Time)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 time.Time + if args[3] != nil { + arg3 = args[3].(time.Time) + } + run( + arg0, + arg1, + arg2, + arg3, + ) }) return _c } @@ -576,14 +689,20 @@ type MockPortal_RevokeAPIKey_Call struct { } // RevokeAPIKey is a helper method to define mock.On call -// - key +// - key string func (_e *MockPortal_Expecter) RevokeAPIKey(key interface{}) *MockPortal_RevokeAPIKey_Call { return &MockPortal_RevokeAPIKey_Call{Call: _e.mock.On("RevokeAPIKey", key)} } func (_c *MockPortal_RevokeAPIKey_Call) Run(run func(key string)) *MockPortal_RevokeAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -621,15 +740,26 @@ type MockPortal_UpdateAPIKey_Call struct { } // UpdateAPIKey is a helper method to define mock.On call -// - key -// - expiresAt +// - key string +// - expiresAt time.Time func (_e *MockPortal_Expecter) UpdateAPIKey(key interface{}, expiresAt interface{}) *MockPortal_UpdateAPIKey_Call { return &MockPortal_UpdateAPIKey_Call{Call: _e.mock.On("UpdateAPIKey", key, expiresAt)} } func (_c *MockPortal_UpdateAPIKey_Call) Run(run func(key string, expiresAt time.Time)) *MockPortal_UpdateAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(time.Time)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 time.Time + if args[1] != nil { + arg1 = args[1].(time.Time) + } + run( + arg0, + arg1, + ) }) return _c } @@ -667,15 +797,26 @@ type MockPortal_VerifyBuildArtifactDownload_Call struct { } // VerifyBuildArtifactDownload is a helper method to define mock.On call -// - file -// - download +// - file io.Reader +// - download Build func (_e *MockPortal_Expecter) VerifyBuildArtifactDownload(file interface{}, download interface{}) *MockPortal_VerifyBuildArtifactDownload_Call { return &MockPortal_VerifyBuildArtifactDownload_Call{Call: _e.mock.On("VerifyBuildArtifactDownload", file, download)} } func (_c *MockPortal_VerifyBuildArtifactDownload_Call) Run(run func(file io.Reader, download Build)) *MockPortal_VerifyBuildArtifactDownload_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(io.Reader), args[1].(Build)) + var arg0 io.Reader + if args[0] != nil { + arg0 = args[0].(io.Reader) + } + var arg1 Build + if args[1] != nil { + arg1 = args[1].(Build) + } + run( + arg0, + arg1, + ) }) return _c } @@ -751,14 +892,20 @@ type MockHttpClient_Do_Call struct { } // Do is a helper method to define mock.On call -// - request +// - request *http.Request func (_e *MockHttpClient_Expecter) Do(request interface{}) *MockHttpClient_Do_Call { return &MockHttpClient_Do_Call{Call: _e.mock.On("Do", request)} } func (_c *MockHttpClient_Do_Call) Run(run func(request *http.Request)) *MockHttpClient_Do_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*http.Request)) + var arg0 *http.Request + if args[0] != nil { + arg0 = args[0].(*http.Request) + } + run( + arg0, + ) }) return _c } diff --git a/internal/system/mocks.go b/internal/system/mocks.go index cf8e9292..ee80bc69 100644 --- a/internal/system/mocks.go +++ b/internal/system/mocks.go @@ -58,16 +58,32 @@ type MockImageManager_BuildImage_Call struct { } // BuildImage is a helper method to define mock.On call -// - dockerfile -// - tag -// - buildContext +// - dockerfile string +// - tag string +// - buildContext string func (_e *MockImageManager_Expecter) BuildImage(dockerfile interface{}, tag interface{}, buildContext interface{}) *MockImageManager_BuildImage_Call { return &MockImageManager_BuildImage_Call{Call: _e.mock.On("BuildImage", dockerfile, tag, buildContext)} } func (_c *MockImageManager_BuildImage_Call) Run(run func(dockerfile string, tag string, buildContext string)) *MockImageManager_BuildImage_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -105,14 +121,20 @@ type MockImageManager_LoadImage_Call struct { } // LoadImage is a helper method to define mock.On call -// - imageTarPath +// - imageTarPath string func (_e *MockImageManager_Expecter) LoadImage(imageTarPath interface{}) *MockImageManager_LoadImage_Call { return &MockImageManager_LoadImage_Call{Call: _e.mock.On("LoadImage", imageTarPath)} } func (_c *MockImageManager_LoadImage_Call) Run(run func(imageTarPath string)) *MockImageManager_LoadImage_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -150,14 +172,20 @@ type MockImageManager_PushImage_Call struct { } // PushImage is a helper method to define mock.On call -// - tag +// - tag string func (_e *MockImageManager_Expecter) PushImage(tag interface{}) *MockImageManager_PushImage_Call { return &MockImageManager_PushImage_Call{Call: _e.mock.On("PushImage", tag)} } func (_c *MockImageManager_PushImage_Call) Run(run func(tag string)) *MockImageManager_PushImage_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } diff --git a/internal/tmpl/NOTICE b/internal/tmpl/NOTICE index 16065007..2fc3e2ae 100644 --- a/internal/tmpl/NOTICE +++ b/internal/tmpl/NOTICE @@ -69,6 +69,12 @@ Version: v3.5.1 License: MIT License URL: https://github.com/blang/semver/blob/v3.5.1/LICENSE +---------- +Module: github.com/cespare/xxhash/v2 +Version: v2.3.0 +License: MIT +License URL: https://github.com/cespare/xxhash/blob/v2.3.0/LICENSE.txt + ---------- Module: github.com/clipperhouse/stringish Version: v0.1.1 @@ -281,21 +287,21 @@ License URL: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/ins ---------- Module: go.opentelemetry.io/otel -Version: v1.38.0 +Version: v1.39.0 License: Apache-2.0 -License URL: https://github.com/open-telemetry/opentelemetry-go/blob/v1.38.0/LICENSE +License URL: https://github.com/open-telemetry/opentelemetry-go/blob/v1.39.0/LICENSE ---------- Module: go.opentelemetry.io/otel/metric -Version: v1.38.0 +Version: v1.39.0 License: Apache-2.0 -License URL: https://github.com/open-telemetry/opentelemetry-go/blob/metric/v1.38.0/metric/LICENSE +License URL: https://github.com/open-telemetry/opentelemetry-go/blob/metric/v1.39.0/metric/LICENSE ---------- Module: go.opentelemetry.io/otel/trace -Version: v1.38.0 +Version: v1.39.0 License: Apache-2.0 -License URL: https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.38.0/trace/LICENSE +License URL: https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.39.0/trace/LICENSE ---------- Module: go.yaml.in/yaml/v3 @@ -311,9 +317,27 @@ License URL: https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE ---------- Module: golang.org/x/oauth2 -Version: v0.33.0 +Version: v0.34.0 +License: BSD-3-Clause +License URL: https://cs.opensource.google/go/x/oauth2/+/v0.34.0:LICENSE + +---------- +Module: golang.org/x/sync/semaphore +Version: v0.19.0 +License: BSD-3-Clause +License URL: https://cs.opensource.google/go/x/sync/+/v0.19.0:LICENSE + +---------- +Module: golang.org/x/sys +Version: v0.39.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/oauth2/+/v0.33.0:LICENSE +License URL: https://cs.opensource.google/go/x/sys/+/v0.39.0:LICENSE + +---------- +Module: golang.org/x/term +Version: v0.38.0 +License: BSD-3-Clause +License URL: https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE ---------- Module: golang.org/x/sys/unix diff --git a/internal/util/mocks.go b/internal/util/mocks.go index 394f6b82..abfdd4f8 100644 --- a/internal/util/mocks.go +++ b/internal/util/mocks.go @@ -70,15 +70,26 @@ type MockDockerfileManager_UpdateFromStatement_Call struct { } // UpdateFromStatement is a helper method to define mock.On call -// - dockerfile -// - baseImage +// - dockerfile io.Reader +// - baseImage string func (_e *MockDockerfileManager_Expecter) UpdateFromStatement(dockerfile interface{}, baseImage interface{}) *MockDockerfileManager_UpdateFromStatement_Call { return &MockDockerfileManager_UpdateFromStatement_Call{Call: _e.mock.On("UpdateFromStatement", dockerfile, baseImage)} } func (_c *MockDockerfileManager_UpdateFromStatement_Call) Run(run func(dockerfile io.Reader, baseImage string)) *MockDockerfileManager_UpdateFromStatement_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(io.Reader), args[1].(string)) + var arg0 io.Reader + if args[0] != nil { + arg0 = args[0].(io.Reader) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } @@ -154,14 +165,20 @@ type MockFileIO_Create_Call struct { } // Create is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) Create(filename interface{}) *MockFileIO_Create_Call { return &MockFileIO_Create_Call{Call: _e.mock.On("Create", filename)} } func (_c *MockFileIO_Create_Call) Run(run func(filename string)) *MockFileIO_Create_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -199,16 +216,32 @@ type MockFileIO_CreateAndWrite_Call struct { } // CreateAndWrite is a helper method to define mock.On call -// - filePath -// - data -// - fileType +// - filePath string +// - data []byte +// - fileType string func (_e *MockFileIO_Expecter) CreateAndWrite(filePath interface{}, data interface{}, fileType interface{}) *MockFileIO_CreateAndWrite_Call { return &MockFileIO_CreateAndWrite_Call{Call: _e.mock.On("CreateAndWrite", filePath, data, fileType)} } func (_c *MockFileIO_CreateAndWrite_Call) Run(run func(filePath string, data []byte, fileType string)) *MockFileIO_CreateAndWrite_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].([]byte), args[2].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 []byte + if args[1] != nil { + arg1 = args[1].([]byte) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -246,14 +279,20 @@ type MockFileIO_Exists_Call struct { } // Exists is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) Exists(filename interface{}) *MockFileIO_Exists_Call { return &MockFileIO_Exists_Call{Call: _e.mock.On("Exists", filename)} } func (_c *MockFileIO_Exists_Call) Run(run func(filename string)) *MockFileIO_Exists_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -300,14 +339,20 @@ type MockFileIO_IsDirectory_Call struct { } // IsDirectory is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) IsDirectory(filename interface{}) *MockFileIO_IsDirectory_Call { return &MockFileIO_IsDirectory_Call{Call: _e.mock.On("IsDirectory", filename)} } func (_c *MockFileIO_IsDirectory_Call) Run(run func(filename string)) *MockFileIO_IsDirectory_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -345,15 +390,26 @@ type MockFileIO_MkdirAll_Call struct { } // MkdirAll is a helper method to define mock.On call -// - path -// - perm +// - path string +// - perm os.FileMode func (_e *MockFileIO_Expecter) MkdirAll(path interface{}, perm interface{}) *MockFileIO_MkdirAll_Call { return &MockFileIO_MkdirAll_Call{Call: _e.mock.On("MkdirAll", path, perm)} } func (_c *MockFileIO_MkdirAll_Call) Run(run func(path string, perm os.FileMode)) *MockFileIO_MkdirAll_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(os.FileMode)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 os.FileMode + if args[1] != nil { + arg1 = args[1].(os.FileMode) + } + run( + arg0, + arg1, + ) }) return _c } @@ -402,14 +458,20 @@ type MockFileIO_Open_Call struct { } // Open is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) Open(filename interface{}) *MockFileIO_Open_Call { return &MockFileIO_Open_Call{Call: _e.mock.On("Open", filename)} } func (_c *MockFileIO_Open_Call) Run(run func(filename string)) *MockFileIO_Open_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -458,14 +520,20 @@ type MockFileIO_OpenAppend_Call struct { } // OpenAppend is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) OpenAppend(filename interface{}) *MockFileIO_OpenAppend_Call { return &MockFileIO_OpenAppend_Call{Call: _e.mock.On("OpenAppend", filename)} } func (_c *MockFileIO_OpenAppend_Call) Run(run func(filename string)) *MockFileIO_OpenAppend_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -514,16 +582,32 @@ type MockFileIO_OpenFile_Call struct { } // OpenFile is a helper method to define mock.On call -// - name -// - flag -// - perm +// - name string +// - flag int +// - perm os.FileMode func (_e *MockFileIO_Expecter) OpenFile(name interface{}, flag interface{}, perm interface{}) *MockFileIO_OpenFile_Call { return &MockFileIO_OpenFile_Call{Call: _e.mock.On("OpenFile", name, flag, perm)} } func (_c *MockFileIO_OpenFile_Call) Run(run func(name string, flag int, perm os.FileMode)) *MockFileIO_OpenFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(int), args[2].(os.FileMode)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 int + if args[1] != nil { + arg1 = args[1].(int) + } + var arg2 os.FileMode + if args[2] != nil { + arg2 = args[2].(os.FileMode) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -572,14 +656,20 @@ type MockFileIO_ReadDir_Call struct { } // ReadDir is a helper method to define mock.On call -// - dirname +// - dirname string func (_e *MockFileIO_Expecter) ReadDir(dirname interface{}) *MockFileIO_ReadDir_Call { return &MockFileIO_ReadDir_Call{Call: _e.mock.On("ReadDir", dirname)} } func (_c *MockFileIO_ReadDir_Call) Run(run func(dirname string)) *MockFileIO_ReadDir_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -628,14 +718,20 @@ type MockFileIO_ReadFile_Call struct { } // ReadFile is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) ReadFile(filename interface{}) *MockFileIO_ReadFile_Call { return &MockFileIO_ReadFile_Call{Call: _e.mock.On("ReadFile", filename)} } func (_c *MockFileIO_ReadFile_Call) Run(run func(filename string)) *MockFileIO_ReadFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -673,16 +769,32 @@ type MockFileIO_WriteFile_Call struct { } // WriteFile is a helper method to define mock.On call -// - filename -// - data -// - perm +// - filename string +// - data []byte +// - perm os.FileMode func (_e *MockFileIO_Expecter) WriteFile(filename interface{}, data interface{}, perm interface{}) *MockFileIO_WriteFile_Call { return &MockFileIO_WriteFile_Call{Call: _e.mock.On("WriteFile", filename, data, perm)} } func (_c *MockFileIO_WriteFile_Call) Run(run func(filename string, data []byte, perm os.FileMode)) *MockFileIO_WriteFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].([]byte), args[2].(os.FileMode)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 []byte + if args[1] != nil { + arg1 = args[1].([]byte) + } + var arg2 os.FileMode + if args[2] != nil { + arg2 = args[2].(os.FileMode) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -812,8 +924,8 @@ type MockTableWriter_AppendHeader_Call struct { } // AppendHeader is a helper method to define mock.On call -// - row -// - configs +// - row table.Row +// - configs ...table.RowConfig func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...interface{}) *MockTableWriter_AppendHeader_Call { return &MockTableWriter_AppendHeader_Call{Call: _e.mock.On("AppendHeader", append([]interface{}{row}, configs...)...)} @@ -821,13 +933,20 @@ func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...int func (_c *MockTableWriter_AppendHeader_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendHeader_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]table.RowConfig, len(args)-1) - for i, a := range args[1:] { - if a != nil { - variadicArgs[i] = a.(table.RowConfig) - } + var arg0 table.Row + if args[0] != nil { + arg0 = args[0].(table.Row) } - run(args[0].(table.Row), variadicArgs...) + var arg1 []table.RowConfig + var variadicArgs []table.RowConfig + if len(args) > 1 { + variadicArgs = args[1].([]table.RowConfig) + } + arg1 = variadicArgs + run( + arg0, + arg1..., + ) }) return _c } @@ -859,8 +978,8 @@ type MockTableWriter_AppendRow_Call struct { } // AppendRow is a helper method to define mock.On call -// - row -// - configs +// - row table.Row +// - configs ...table.RowConfig func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interface{}) *MockTableWriter_AppendRow_Call { return &MockTableWriter_AppendRow_Call{Call: _e.mock.On("AppendRow", append([]interface{}{row}, configs...)...)} @@ -868,13 +987,20 @@ func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interf func (_c *MockTableWriter_AppendRow_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendRow_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]table.RowConfig, len(args)-1) - for i, a := range args[1:] { - if a != nil { - variadicArgs[i] = a.(table.RowConfig) - } + var arg0 table.Row + if args[0] != nil { + arg0 = args[0].(table.Row) + } + var arg1 []table.RowConfig + var variadicArgs []table.RowConfig + if len(args) > 1 { + variadicArgs = args[1].([]table.RowConfig) } - run(args[0].(table.Row), variadicArgs...) + arg1 = variadicArgs + run( + arg0, + arg1..., + ) }) return _c } From 6a88ac1caecc976e6df244d20b7c59aacc52df02 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Fri, 9 Jan 2026 09:46:03 +0100 Subject: [PATCH 36/65] fix: simplify root login check logic --- internal/installer/node/node.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 74c0686c..cab8aab1 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -403,10 +403,7 @@ func (n *Node) HasRootLoginEnabled(jumpbox *Node, nm *NodeManager) bool { } checkCommandAuthorizedKeys := "sudo grep -E '^no-port-forwarding' /root/.ssh/authorized_keys >/dev/null 2>&1" err = n.RunSSHCommand(jumpbox, nm, "ubuntu", checkCommandAuthorizedKeys) - if err == nil { - return false - } - return true + return err != nil } func (n *Node) HasFile(jumpbox *Node, nm *NodeManager, filePath string) bool { From f2be13a53dabea45d00677da867922e2cde72c0a Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Fri, 9 Jan 2026 08:47:47 +0000 Subject: [PATCH 37/65] chore(docs): Auto-update docs and licenses Signed-off-by: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> --- internal/tmpl/NOTICE | 70 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/internal/tmpl/NOTICE b/internal/tmpl/NOTICE index 2fc3e2ae..0478b096 100644 --- a/internal/tmpl/NOTICE +++ b/internal/tmpl/NOTICE @@ -185,9 +185,9 @@ License URL: https://github.com/inconshreveable/go-update/blob/8152e7eb6ccf/inte ---------- Module: github.com/jedib0t/go-pretty/v6 -Version: v6.7.7 +Version: v6.7.8 License: MIT -License URL: https://github.com/jedib0t/go-pretty/blob/v6.7.7/LICENSE +License URL: https://github.com/jedib0t/go-pretty/blob/v6.7.8/LICENSE ---------- Module: github.com/kr/fs @@ -195,6 +195,12 @@ Version: v0.1.0 License: BSD-3-Clause License URL: https://github.com/kr/fs/blob/v0.1.0/LICENSE +---------- +Module: github.com/lithammer/shortuuid +Version: v3.0.0 +License: MIT +License URL: https://github.com/lithammer/shortuuid/blob/v3.0.0/LICENSE + ---------- Module: github.com/mattn/go-runewidth Version: v0.0.19 @@ -315,6 +321,12 @@ Version: v0.46.0 License: BSD-3-Clause License URL: https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE +---------- +Module: golang.org/x/net +Version: v0.48.0 +License: BSD-3-Clause +License URL: https://cs.opensource.google/go/x/net/+/v0.48.0:LICENSE + ---------- Module: golang.org/x/oauth2 Version: v0.34.0 @@ -340,22 +352,58 @@ License: BSD-3-Clause License URL: https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE ---------- -Module: golang.org/x/sys/unix -Version: v0.39.0 +Module: golang.org/x/text +Version: v0.32.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/sys/+/v0.39.0:LICENSE +License URL: https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE ---------- -Module: golang.org/x/term -Version: v0.38.0 +Module: golang.org/x/time/rate +Version: v0.14.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/term/+/v0.38.0:LICENSE +License URL: https://cs.opensource.google/go/x/time/+/v0.14.0:LICENSE ---------- -Module: golang.org/x/text -Version: v0.32.0 +Module: google.golang.org/api +Version: v0.247.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE +License URL: https://github.com/googleapis/google-api-go-client/blob/v0.247.0/LICENSE + +---------- +Module: google.golang.org/api/internal/third_party/uritemplates +Version: v0.247.0 +License: BSD-3-Clause +License URL: https://github.com/googleapis/google-api-go-client/blob/v0.247.0/internal/third_party/uritemplates/LICENSE + +---------- +Module: google.golang.org/genproto/googleapis +Version: v0.0.0-20250715232539-7130f93afb79 +License: Apache-2.0 +License URL: https://github.com/googleapis/go-genproto/blob/7130f93afb79/LICENSE + +---------- +Module: google.golang.org/genproto/googleapis/api +Version: v0.0.0-20250825161204-c5933d9347a5 +License: Apache-2.0 +License URL: https://github.com/googleapis/go-genproto/blob/c5933d9347a5/googleapis/api/LICENSE + +---------- +Module: google.golang.org/genproto/googleapis/rpc +Version: v0.0.0-20250825161204-c5933d9347a5 +License: Apache-2.0 +License URL: https://github.com/googleapis/go-genproto/blob/c5933d9347a5/googleapis/rpc/LICENSE + +---------- +Module: google.golang.org/grpc +Version: v1.75.0 +License: Apache-2.0 +License URL: https://github.com/grpc/grpc-go/blob/v1.75.0/LICENSE + +---------- +Module: google.golang.org/protobuf +Version: v1.36.11 +License: BSD-3-Clause +License URL: https://github.com/protocolbuffers/protobuf-go/blob/v1.36.11/LICENSE ---------- Module: gopkg.in/yaml.v3 From aa4222e86d85a4fde2f65a8b83bd3702395b292e Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 13 Jan 2026 14:07:55 +0100 Subject: [PATCH 38/65] ref: enhance k0s installation process and test configuration --- cli/cmd/install_k0s.go | 61 +++++++++++----- cli/cmd/install_k0s_test.go | 2 +- hack/lima-oms.yaml | 105 +++++++++++++++++++-------- internal/installer/k0s.go | 13 +--- internal/installer/node/node.go | 29 ++++---- internal/installer/node/node_test.go | 8 +- 6 files changed, 143 insertions(+), 75 deletions(-) diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index 586e2d3d..71d7de90 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -90,7 +90,31 @@ func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { k0s.cmd.RunE = k0s.RunE } -const defaultK0sPath = "kubernetes/files/k0s" +const ( + defaultK0sPath = "kubernetes/files/k0s" + k0sConfigDir = "/etc/k0s" + k0sConfigFile = "k0s.yaml" +) + +// writeK0sConfigFile writes the k0s config to the specified path +func writeK0sConfigFile(preferredPath string, data []byte) (string, func(), error) { + if err := os.MkdirAll(filepath.Dir(preferredPath), 0755); err != nil { + // Fall back to temp file + tmpPath := filepath.Join(os.TempDir(), "k0s-config.yaml") + if err := os.WriteFile(tmpPath, data, 0644); err != nil { + return "", nil, err + } + log.Printf("Generated k0s configuration at %s (using temp path due to permissions)", tmpPath) + cleanup := func() { _ = os.Remove(tmpPath) } + return tmpPath, cleanup, nil + } + + if err := os.WriteFile(preferredPath, data, 0644); err != nil { + return "", nil, err + } + log.Printf("Generated k0s configuration at %s", preferredPath) + return preferredPath, nil, nil +} func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0sManager) error { icg := installer.NewInstallConfigManager() @@ -115,22 +139,13 @@ func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0 return fmt.Errorf("failed to marshal k0s config: %w", err) } - k0sConfigPath := "/etc/k0s/k0s.yaml" - - if err := os.MkdirAll(filepath.Dir(k0sConfigPath), 0755); err != nil { - tmpK0sConfigPath := filepath.Join(os.TempDir(), "k0s-config.yaml") - if err := os.WriteFile(tmpK0sConfigPath, k0sConfigData, 0644); err != nil { - return fmt.Errorf("failed to write k0s config: %w", err) - } - k0sConfigPath = tmpK0sConfigPath - // Clean up temp file on all exit paths - defer func() { _ = os.Remove(k0sConfigPath) }() - log.Printf("Generated k0s configuration at %s (using temp path due to permissions)", k0sConfigPath) - } else { - if err := os.WriteFile(k0sConfigPath, k0sConfigData, 0644); err != nil { - return fmt.Errorf("failed to write k0s config: %w", err) - } - log.Printf("Generated k0s configuration at %s", k0sConfigPath) + k0sConfigPath := filepath.Join(k0sConfigDir, k0sConfigFile) + k0sConfigPath, cleanup, err := writeK0sConfigFile(k0sConfigPath, k0sConfigData) + if err != nil { + return fmt.Errorf("failed to write k0s config: %w", err) + } + if cleanup != nil { + defer cleanup() } k0sPath := pm.GetDependencyPath(defaultK0sPath) @@ -179,7 +194,17 @@ func (c *InstallK0sCmd) InstallK0sRemote(config *files.RootConfig, k0sBinaryPath User: c.Opts.RemoteUser, } - if err := remoteNode.InstallK0s(nm, k0sBinaryPath, k0sConfigPath, c.Opts.Force); err != nil { + controlPlaneIPs := make([]string, 0, len(config.Kubernetes.ControlPlanes)) + for _, cp := range config.Kubernetes.ControlPlanes { + controlPlaneIPs = append(controlPlaneIPs, cp.IPAddress) + } + nodeIP, err := installer.GetNodeIPAddress(controlPlaneIPs) + if err != nil { + log.Printf("Warning: could not determine node IP from control planes: %v. Using remote host IP: %s", err, c.Opts.RemoteHost) + nodeIP = c.Opts.RemoteHost + } + + if err := remoteNode.InstallK0s(nm, k0sBinaryPath, k0sConfigPath, c.Opts.Force, nodeIP); err != nil { return fmt.Errorf("failed to install k0s on remote host: %w", err) } diff --git a/cli/cmd/install_k0s_test.go b/cli/cmd/install_k0s_test.go index a65aa1ba..4bbf2e18 100644 --- a/cli/cmd/install_k0s_test.go +++ b/cli/cmd/install_k0s_test.go @@ -62,7 +62,7 @@ var _ = Describe("InstallK0sCmd", func() { }) }) - Context("InstallK0sFromInstallConfig method", func() { + Context("InstallK0s method", func() { var ( mockPM *installer.MockPackageManager mockK0s *installer.MockK0sManager diff --git a/hack/lima-oms.yaml b/hack/lima-oms.yaml index 83c11db7..67261b71 100644 --- a/hack/lima-oms.yaml +++ b/hack/lima-oms.yaml @@ -67,58 +67,105 @@ provision: if [ ! -d ~/oms ]; then git clone https://github.com/codesphere-cloud/oms.git ~/oms fi + + # Create a test install-config for k0s testing + VM_IP=$(hostname -I | awk '{print $1}') + cat > ~/oms/test-install-config.yaml << 'EOFCONFIG' + datacenter: + id: 1 + name: test-dc + city: Test City + countryCode: US + kubernetes: + managedByCodesphere: true + controlPlanes: + - ipAddress: ${VM_IP} + apiServerHost: api.test.local + codesphere: + domain: test.local + publicIP: ${VM_IP} + deployConfig: + images: {} + plans: + hostingPlans: {} + workspacePlans: {} + EOFCONFIG message: | Your OMS development environment is ready! + VM IP: Run 'hostname -I | awk '{print $1}'' to get your VM's IP address - To access it: + Quick Start: ------ limactl shell lima-oms cd oms - ./oms-cli --help + make build-cli ------ - To build the CLI for Linux (from your Mac): - ------ - make build-cli-linux - ------ - - To build the CLI inside lima: + Test k0s installation locally: ------ limactl shell lima-oms cd oms + + # Build the CLI make build-cli + + # Install k0s (uses test-install-config.yaml which is auto-generated) + sudo ./oms-cli install k0s --install-config test-install-config.yaml --version v1.30.0+k0s.0 --force + + # Check k0s installation + sudo systemctl status k0scontroller + sudo oms-workdir/k0s kubectl get nodes + + # Stop and cleanup k0s + sudo systemctl stop k0scontroller + sudo systemctl disable k0scontroller + sudo oms-workdir/k0s reset ------ - - To install k0s (run inside lima): + + Check k0s config that was generated: + ------ + sudo cat /etc/k0s/k0s.yaml ------ + + Test with package instead of download: + ------ + # First, create a package (run on your host machine with access to real install-config) + ./oms-cli download package --version v1.x.x + + # Then in Lima: limactl shell lima-oms cd oms - ./oms-cli install k0s --install-config config.yaml --version v1.30.0+k0s.0 --force + sudo ./oms-cli install k0s --install-config test-install-config.yaml \ + --package codesphere-vX.X.X-installer.tar.gz --force ------ - - To test remote k0s installation (run inside lima): + + Run tests: ------ limactl shell lima-oms cd oms - # Setup SSH for testing - ssh-keygen -t rsa -b 4096 -f ~/.ssh/test_key -N "" - cat ~/.ssh/test_key.pub >> ~/.ssh/authorized_keys - chmod 600 ~/.ssh/authorized_keys - # Add host to known_hosts - VM_IP=$(hostname -I | awk '{print $1}') - ssh-keyscan $VM_IP >> ~/.ssh/known_hosts - # Test SSH connection - ssh -i ~/.ssh/test_key $VM_IP "echo 'SSH works'" - # Note: Remote installation requires proper SSH host key verification - # For testing, you can add the host to known_hosts (already done above with ssh-keyscan) - ./oms-cli install k0s --install-config config.yaml --version v1.30.0+k0s.0 \ - --remote-host $VM_IP --remote-user $(whoami) --ssh-key-path ~/.ssh/test_key --force + + # Run all tests + go test ./... + + # Run specific k0s tests + go test ./cli/cmd -ginkgo.v -ginkgo.focus "InstallK0s" + go test ./internal/installer -ginkgo.v -ginkgo.focus "k0s" + go test ./internal/installer/node -ginkgo.v -ginkgo.focus "InstallK0s" ------ - - To install Codesphere (run inside lima): + + Build for different platforms: ------ + # From your Mac (cross-compile to Linux) + make build-cli-linux + + # Inside Lima (native Linux build) limactl shell lima-oms cd oms - ./oms-cli install codesphere --package codesphere-v1.66.0-installer --install-config config.yaml --priv-key ./path-to-private-key + make build-cli + ------ + + Notes: + - test-install-config.yaml is automatically created in ~/oms with your VM's IP + - Remote installation requires proper SSH setup ------ diff --git a/internal/installer/k0s.go b/internal/installer/k0s.go index e2306f11..46e5cbdf 100644 --- a/internal/installer/k0s.go +++ b/internal/installer/k0s.go @@ -149,18 +149,11 @@ func (k *K0s) Install(configPath string, k0sPath string, force bool, nodeIP stri filteredConfigPath, err := k.filterConfigForK0s(configPath) if err != nil { log.Printf("Warning: failed to filter config, using original: %v", err) + args = append(args, "--config", configPath) } else { - filteredData, err := os.ReadFile(filteredConfigPath) - if err != nil { - log.Printf("Warning: failed to read filtered config: %v", err) - } else { - if err := os.WriteFile(configPath, filteredData, 0644); err != nil { - log.Printf("Warning: failed to write filtered config back: %v", err) - } - } - _ = os.Remove(filteredConfigPath) + args = append(args, "--config", filteredConfigPath) + defer func() { _ = os.Remove(filteredConfigPath) }() } - args = append(args, "--config", configPath) } else { args = append(args, "--single") } diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index cab8aab1..333eb56d 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -33,7 +33,13 @@ type NodeManager struct { KeyPath string } -const jumpboxUser = "ubuntu" +const ( + jumpboxUser = "ubuntu" + remoteK0sDir = "/usr/local/bin" + remoteK0sConfig = "/etc/k0s/k0s.yaml" + tmpK0sBinary = "/tmp/k0s" + tmpK0sConfig = "/tmp/k0s-config.yaml" +) func shellEscape(s string) string { return strings.ReplaceAll(s, "'", "'\\''") @@ -85,16 +91,16 @@ func (nm *NodeManager) getAuthMethods() ([]ssh.AuthMethod, error) { return nil, fmt.Errorf("failed to read private key file %s: %v", nm.KeyPath, err) } - fmt.Printf("Successfully read %d bytes from key file\\n", len(key)) + log.Printf("Successfully read %d bytes from key file", len(key)) signer, err := ssh.ParsePrivateKey(key) if err == nil { - fmt.Printf("Successfully parsed private key (type: %s)\\n", signer.PublicKey().Type()) + log.Printf("Successfully parsed private key (type: %s)", signer.PublicKey().Type()) authMethods = append(authMethods, ssh.PublicKeys(signer)) return authMethods, nil } - fmt.Printf("Failed to parse private key: %v\\n", err) + log.Printf("Failed to parse private key: %v", err) if _, ok := err.(*ssh.PassphraseMissingError); ok { // Check if we're in an interactive terminal if !term.IsTerminal(int(syscall.Stdin)) { @@ -476,10 +482,9 @@ func (n *Node) ConfigureInotifyWatches(jumpbox *Node, nm *NodeManager) error { return nil } -func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath string, force bool) error { - remoteK0sDir := "/usr/local/bin" +func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath string, force bool, nodeIP string) error { remoteK0sBinary := filepath.Join(remoteK0sDir, "k0s") - remoteConfigPath := "/etc/k0s/k0s.yaml" + remoteConfigPath := remoteK0sConfig user := n.User if user == "" { @@ -487,7 +492,6 @@ func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath s } // Copy k0s binary to temp location first, then move with sudo - tmpK0sBinary := "/tmp/k0s" log.Printf("Copying k0s binary to %s:%s", n.ExternalIP, tmpK0sBinary) if err := nm.CopyFile("", n.ExternalIP, user, k0sBinaryPath, tmpK0sBinary); err != nil { return fmt.Errorf("failed to copy k0s binary to temp: %w", err) @@ -503,16 +507,15 @@ func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath s if k0sConfigPath != "" { // Copy config to temp location first - tmpConfigPath := "/tmp/k0s-config.yaml" - log.Printf("Copying k0s config to %s", tmpConfigPath) - if err := nm.CopyFile("", n.ExternalIP, user, k0sConfigPath, tmpConfigPath); err != nil { + log.Printf("Copying k0s config to %s", tmpK0sConfig) + if err := nm.CopyFile("", n.ExternalIP, user, k0sConfigPath, tmpK0sConfig); err != nil { return fmt.Errorf("failed to copy k0s config to temp: %w", err) } // Create /etc/k0s directory and move config with sudo log.Printf("Moving k0s config to %s", remoteConfigPath) setupConfigCmd := fmt.Sprintf("sudo mkdir -p /etc/k0s && sudo mv '%s' '%s' && sudo chmod 644 '%s'", - shellEscape(tmpConfigPath), shellEscape(remoteConfigPath), shellEscape(remoteConfigPath)) + shellEscape(tmpK0sConfig), shellEscape(remoteConfigPath), shellEscape(remoteConfigPath)) if err := nm.RunSSHCommand("", n.ExternalIP, user, setupConfigCmd); err != nil { return fmt.Errorf("failed to setup k0s config: %w", err) } @@ -527,7 +530,7 @@ func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath s installCmd += " --enable-worker" installCmd += " --no-taints" - installCmd += fmt.Sprintf(" --kubelet-extra-args='--node-ip=%s'", shellEscape(n.ExternalIP)) + installCmd += fmt.Sprintf(" --kubelet-extra-args='--node-ip=%s'", shellEscape(nodeIP)) if force { installCmd += " --force" diff --git a/internal/installer/node/node_test.go b/internal/installer/node/node_test.go index 1d7d1e43..bfc2a25f 100644 --- a/internal/installer/node/node_test.go +++ b/internal/installer/node/node_test.go @@ -356,7 +356,7 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== k0sBinaryPath := "/path/to/k0s" mockFileWriter.EXPECT().Open(k0sBinaryPath).Return(nil, errors.New("file not found")).Maybe() - err := n.InstallK0s(nm, k0sBinaryPath, "", false) + err := n.InstallK0s(nm, k0sBinaryPath, "", false, "192.168.1.100") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to copy k0s binary to temp")) }) @@ -364,14 +364,14 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== It("should handle paths with special characters safely", func() { k0sBinaryPath := "/path/to/k0s'; echo 'injected" - err := n.InstallK0s(nm, k0sBinaryPath, "", false) + err := n.InstallK0s(nm, k0sBinaryPath, "", false, "192.168.1.100") Expect(err).To(HaveOccurred()) }) It("should support force flag parameter", func() { k0sBinaryPath := "/tmp/k0s" - err := n.InstallK0s(nm, k0sBinaryPath, "", true) + err := n.InstallK0s(nm, k0sBinaryPath, "", true, "192.168.1.100") Expect(err).To(HaveOccurred()) // Will fail to connect, but tests that force flag is handled }) @@ -380,7 +380,7 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== k0sBinaryPath := "/tmp/k0s" k0sConfigPath := "/tmp/k0s.yaml" - err := n.InstallK0s(nm, k0sBinaryPath, k0sConfigPath, false) + err := n.InstallK0s(nm, k0sBinaryPath, k0sConfigPath, false, "192.168.1.100") Expect(err).To(HaveOccurred()) // Will fail to connect, but tests that config path is handled }) From 7f3d823aae46b46b7c940aaaee8a0a463640da27 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 13 Jan 2026 13:09:49 +0000 Subject: [PATCH 39/65] chore(docs): Auto-update docs and licenses Signed-off-by: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> --- internal/tmpl/NOTICE | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/tmpl/NOTICE b/internal/tmpl/NOTICE index 153c93cc..5ff6900d 100644 --- a/internal/tmpl/NOTICE +++ b/internal/tmpl/NOTICE @@ -365,39 +365,39 @@ License URL: https://cs.opensource.google/go/x/time/+/v0.14.0:LICENSE ---------- Module: google.golang.org/api -Version: v0.247.0 +Version: v0.259.0 License: BSD-3-Clause -License URL: https://github.com/googleapis/google-api-go-client/blob/v0.247.0/LICENSE +License URL: https://github.com/googleapis/google-api-go-client/blob/v0.259.0/LICENSE ---------- Module: google.golang.org/api/internal/third_party/uritemplates -Version: v0.247.0 +Version: v0.259.0 License: BSD-3-Clause -License URL: https://github.com/googleapis/google-api-go-client/blob/v0.247.0/internal/third_party/uritemplates/LICENSE +License URL: https://github.com/googleapis/google-api-go-client/blob/v0.259.0/internal/third_party/uritemplates/LICENSE ---------- Module: google.golang.org/genproto/googleapis -Version: v0.0.0-20250715232539-7130f93afb79 +Version: v0.0.0-20251202230838-ff82c1b0f217 License: Apache-2.0 -License URL: https://github.com/googleapis/go-genproto/blob/7130f93afb79/LICENSE +License URL: https://github.com/googleapis/go-genproto/blob/ff82c1b0f217/LICENSE ---------- Module: google.golang.org/genproto/googleapis/api -Version: v0.0.0-20250825161204-c5933d9347a5 +Version: v0.0.0-20251202230838-ff82c1b0f217 License: Apache-2.0 -License URL: https://github.com/googleapis/go-genproto/blob/c5933d9347a5/googleapis/api/LICENSE +License URL: https://github.com/googleapis/go-genproto/blob/ff82c1b0f217/googleapis/api/LICENSE ---------- Module: google.golang.org/genproto/googleapis/rpc -Version: v0.0.0-20250825161204-c5933d9347a5 +Version: v0.0.0-20251222181119-0a764e51fe1b License: Apache-2.0 -License URL: https://github.com/googleapis/go-genproto/blob/c5933d9347a5/googleapis/rpc/LICENSE +License URL: https://github.com/googleapis/go-genproto/blob/0a764e51fe1b/googleapis/rpc/LICENSE ---------- Module: google.golang.org/grpc -Version: v1.75.0 +Version: v1.78.0 License: Apache-2.0 -License URL: https://github.com/grpc/grpc-go/blob/v1.75.0/LICENSE +License URL: https://github.com/grpc/grpc-go/blob/v1.78.0/LICENSE ---------- Module: google.golang.org/protobuf From 5da46eca41f9a1cf0858ad7a33fc890802ce6dff Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Tue, 13 Jan 2026 14:17:57 +0100 Subject: [PATCH 40/65] fix: enhance error handling in NodeManager client connection tests --- internal/installer/node/node_test.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/installer/node/node_test.go b/internal/installer/node/node_test.go index bfc2a25f..6200d18b 100644 --- a/internal/installer/node/node_test.go +++ b/internal/installer/node/node_test.go @@ -231,7 +231,12 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== client, err := nm.GetClient("", "192.0.2.1", "root") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to dial")) + // Accept either authentication failure or connection failure + Expect(err.Error()).To(Or( + ContainSubstring("failed to dial"), + ContainSubstring("failed to get authentication methods"), + ContainSubstring("failed to parse private key"), + )) Expect(client).To(BeNil()) }) @@ -249,7 +254,12 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== client, err := nm.GetClient("192.0.2.1", "192.0.2.2", "root") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to connect to jumpbox")) + // Accept either authentication failure or connection failure + Expect(err.Error()).To(Or( + ContainSubstring("failed to connect to jumpbox"), + ContainSubstring("failed to get authentication methods"), + ContainSubstring("failed to parse private key"), + )) Expect(client).To(BeNil()) }) }) From 949bbedbe0099334adf552b1015806d716f51868 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Thu, 22 Jan 2026 16:43:46 +0100 Subject: [PATCH 41/65] fix: merge error --- internal/installer/node/node.go | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 6cfdce57..0be5c9e1 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -473,14 +473,30 @@ func (n *Node) WaitForSSH(jumpbox *Node, nm *NodeManager, timeout time.Duration) } func (n *Node) HasInotifyWatchesConfigured(jumpbox *Node, nm *NodeManager) bool { - checkCommand := "sudo grep -E '^fs.inotify.max_user_watches=1048576' /etc/sysctl.conf >/dev/null 2>&1" + return n.HasSysctlLine(jumpbox, "fs.inotify.max_user_watches=1048576", nm) +} + +func (n *Node) ConfigureInotifyWatches(jumpbox *Node, nm *NodeManager) error { + return n.ConfigureSysctlLine(jumpbox, "fs.inotify.max_user_watches=1048576", nm) +} + +func (n *Node) HasMemoryMapConfigured(jumpbox *Node, nm *NodeManager) bool { + return n.HasSysctlLine(jumpbox, "vm.max_map_count=262144", nm) +} + +func (n *Node) ConfigureMemoryMap(jumpbox *Node, nm *NodeManager) error { + return n.ConfigureSysctlLine(jumpbox, "vm.max_map_count=262144", nm) +} + +func (n *Node) HasSysctlLine(jumpbox *Node, line string, nm *NodeManager) bool { + checkCommand := fmt.Sprintf("sudo grep -E '^%s' /etc/sysctl.conf >/dev/null 2>&1", line) err := n.RunSSHCommand(jumpbox, nm, "root", checkCommand) return err == nil } -func (n *Node) ConfigureInotifyWatches(jumpbox *Node, nm *NodeManager) error { +func (n *Node) ConfigureSysctlLine(jumpbox *Node, line string, nm *NodeManager) error { cmds := []string{ - "echo 'fs.inotify.max_user_watches=1048576' | sudo tee -a /etc/sysctl.conf", + fmt.Sprintf("echo '%s' | sudo tee -a /etc/sysctl.conf", line), "sudo sysctl -p", } for _, cmd := range cmds { From 8b47dfd29604fd6b218b1ec7970dbab37e988e1c Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:06:08 +0100 Subject: [PATCH 42/65] fix: node tests --- internal/installer/node/node_test.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/installer/node/node_test.go b/internal/installer/node/node_test.go index 6200d18b..118997bb 100644 --- a/internal/installer/node/node_test.go +++ b/internal/installer/node/node_test.go @@ -190,7 +190,8 @@ var _ = Describe("Node", func() { client, err := nm.GetClient("", "10.0.0.1", "root") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to read private key file")) + // With caching, key loading failures log warnings and result in no valid auth methods + Expect(err.Error()).To(ContainSubstring("no valid authentication methods")) Expect(client).To(BeNil()) }) @@ -211,7 +212,8 @@ var _ = Describe("Node", func() { client, err := nm.GetClient("", "10.0.0.1", "root") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to parse private key")) + // With caching, key parsing failures log warnings and result in no valid auth methods + Expect(err.Error()).To(ContainSubstring("no valid authentication methods")) Expect(client).To(BeNil()) }) }) @@ -228,6 +230,8 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== nm.KeyPath = "/path/to/key" mockFileWriter.EXPECT().ReadFile("/path/to/key").Return(privateKey, nil).Maybe() + // Mock the .pub file read for deduplication check + mockFileWriter.EXPECT().ReadFile("/path/to/key.pub").Return(nil, errors.New("file not found")).Maybe() client, err := nm.GetClient("", "192.0.2.1", "root") Expect(err).To(HaveOccurred()) @@ -251,6 +255,8 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== nm.KeyPath = "/path/to/key" mockFileWriter.EXPECT().ReadFile("/path/to/key").Return(privateKey, nil).Maybe() + // Mock the .pub file read for deduplication check + mockFileWriter.EXPECT().ReadFile("/path/to/key.pub").Return(nil, errors.New("file not found")).Maybe() client, err := nm.GetClient("192.0.2.1", "192.0.2.2", "root") Expect(err).To(HaveOccurred()) From 0086e601feb56cd0bc39e6547db6015c221cfc07 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 28 Jan 2026 11:23:44 +0100 Subject: [PATCH 43/65] ref: start to update k0s to k0sctl --- cli/cmd/install_k0s.go | 164 ++++------ cli/cmd/install_k0s_test.go | 190 ++--------- cli/cmd/mocks.go | 24 +- hack/lima-oms.yaml | 1 - internal/installer/k0s.go | 163 ---------- internal/installer/k0s_internal_test.go | 339 -------------------- internal/installer/k0s_test.go | 68 ---- internal/installer/k0sctl.go | 175 +++++++++++ internal/installer/k0sctl_config.go | 203 ++++++++++++ internal/installer/mocks.go | 401 ++++++++++++------------ internal/installer/node/node.go | 72 +---- internal/installer/node/node_test.go | 34 -- internal/portal/mocks.go | 225 +++---------- internal/system/mocks.go | 44 +-- internal/util/mocks.go | 222 +++---------- 15 files changed, 771 insertions(+), 1554 deletions(-) delete mode 100644 internal/installer/k0s_internal_test.go create mode 100644 internal/installer/k0sctl.go create mode 100644 internal/installer/k0sctl_config.go diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index 71d7de90..fe610e71 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -14,8 +14,6 @@ import ( "github.com/codesphere-cloud/oms/internal/env" "github.com/codesphere-cloud/oms/internal/installer" - "github.com/codesphere-cloud/oms/internal/installer/files" - "github.com/codesphere-cloud/oms/internal/installer/node" "github.com/codesphere-cloud/oms/internal/portal" "github.com/codesphere-cloud/oms/internal/util" ) @@ -31,12 +29,12 @@ type InstallK0sCmd struct { type InstallK0sOpts struct { *GlobalOptions Version string + K0sctlVersion string Package string InstallConfig string SSHKeyPath string - RemoteHost string - RemoteUser string Force bool + NoDownload bool } func (c *InstallK0sCmd) RunE(_ *cobra.Command, args []string) error { @@ -44,8 +42,9 @@ func (c *InstallK0sCmd) RunE(_ *cobra.Command, args []string) error { env := c.Env pm := installer.NewPackage(env.GetOmsWorkdir(), c.Opts.Package) k0s := installer.NewK0s(hw, env, c.FileWriter) + k0sctl := installer.NewK0sctl(hw, env, c.FileWriter) - return c.InstallK0s(pm, k0s) + return c.InstallK0s(pm, k0s, k0sctl) } func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { @@ -54,20 +53,20 @@ func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { Use: "k0s", Short: "Install k0s Kubernetes distribution", Long: packageio.Long(`Install k0s either from the package or by downloading it. - This will either download the k0s binary directly to the OMS workdir, if not already present, and install it - or load the k0s binary from the provided package file and install it. - If no version is specified, the latest version will be downloaded. + This command uses k0sctl to deploy k0s clusters from a Codesphere install-config. You must provide a Codesphere install-config file, which will: - Generate a k0s configuration from the install-config - - Optionally install k0s on remote nodes via SSH`), + - Generate a k0sctl configuration for cluster deployment + - Deploy k0s to all nodes defined in the install-config using k0sctl`), Example: formatExamplesWithBinary("install k0s", []packageio.Example{ {Cmd: "--install-config ", Desc: "Path to Codesphere install-config file to generate k0s config from"}, - {Cmd: "--version ", Desc: "Version of k0s to install"}, + {Cmd: "--version ", Desc: "Version of k0s to install (e.g., v1.30.0+k0s.0)"}, + {Cmd: "--k0sctl-version ", Desc: "Version of k0sctl to use (e.g., v0.17.4)"}, {Cmd: "--package ", Desc: "Package file (e.g. codesphere-v1.2.3-installer.tar.gz) to load k0s from"}, - {Cmd: "--remote-host ", Desc: "Remote host IP to install k0s on (requires --ssh-key-path)"}, {Cmd: "--ssh-key-path ", Desc: "SSH private key path for remote installation"}, - {Cmd: "--force", Desc: "Force new download and installation even if k0s binary exists or is already installed"}, + {Cmd: "--force", Desc: "Force new download and installation"}, + {Cmd: "--no-download", Desc: "Skip downloading k0s binary (expects it to be on remote nodes)"}, }, "oms-cli"), }, Opts: InstallK0sOpts{GlobalOptions: opts}, @@ -75,15 +74,14 @@ func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { FileWriter: util.NewFilesystemWriter(), } k0s.cmd.Flags().StringVarP(&k0s.Opts.Version, "version", "v", "", "Version of k0s to install") + k0s.cmd.Flags().StringVar(&k0s.Opts.K0sctlVersion, "k0sctl-version", "", "Version of k0sctl to use") k0s.cmd.Flags().StringVarP(&k0s.Opts.Package, "package", "p", "", "Package file (e.g. codesphere-v1.2.3-installer.tar.gz) to load k0s from") k0s.cmd.Flags().StringVar(&k0s.Opts.InstallConfig, "install-config", "", "Path to Codesphere install-config file (required)") k0s.cmd.Flags().StringVar(&k0s.Opts.SSHKeyPath, "ssh-key-path", "", "SSH private key path for remote installation") - k0s.cmd.Flags().StringVar(&k0s.Opts.RemoteHost, "remote-host", "", "Remote host IP to install k0s on") - k0s.cmd.Flags().StringVar(&k0s.Opts.RemoteUser, "remote-user", "root", "Remote user for SSH connection") k0s.cmd.Flags().BoolVarP(&k0s.Opts.Force, "force", "f", false, "Force new download and installation") + k0s.cmd.Flags().BoolVar(&k0s.Opts.NoDownload, "no-download", false, "Skip downloading k0s binary") _ = k0s.cmd.MarkFlagRequired("install-config") - k0s.cmd.MarkFlagsRequiredTogether("remote-host", "ssh-key-path") install.AddCommand(k0s.cmd) @@ -91,32 +89,13 @@ func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { } const ( - defaultK0sPath = "kubernetes/files/k0s" - k0sConfigDir = "/etc/k0s" - k0sConfigFile = "k0s.yaml" + defaultK0sPath = "kubernetes/files/k0s" + k0sctlConfigDir = "/tmp" + k0sctlConfigFile = "k0sctl-config.yaml" ) -// writeK0sConfigFile writes the k0s config to the specified path -func writeK0sConfigFile(preferredPath string, data []byte) (string, func(), error) { - if err := os.MkdirAll(filepath.Dir(preferredPath), 0755); err != nil { - // Fall back to temp file - tmpPath := filepath.Join(os.TempDir(), "k0s-config.yaml") - if err := os.WriteFile(tmpPath, data, 0644); err != nil { - return "", nil, err - } - log.Printf("Generated k0s configuration at %s (using temp path due to permissions)", tmpPath) - cleanup := func() { _ = os.Remove(tmpPath) } - return tmpPath, cleanup, nil - } - - if err := os.WriteFile(preferredPath, data, 0644); err != nil { - return "", nil, err - } - log.Printf("Generated k0s configuration at %s", preferredPath) - return preferredPath, nil, nil -} - -func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0sManager) error { +func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0sManager, k0sctl installer.K0sctlManager) error { + // Load install-config icg := installer.NewInstallConfigManager() if err := icg.LoadInstallConfigFromFile(c.Opts.InstallConfig); err != nil { return fmt.Errorf("failed to load install-config: %w", err) @@ -128,86 +107,67 @@ func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0 return fmt.Errorf("install-config specifies external Kubernetes, k0s installation is only supported for Codesphere-managed Kubernetes") } - log.Println("Generating k0s configuration from install-config...") - k0sConfig, err := installer.GenerateK0sConfig(config) - if err != nil { - return fmt.Errorf("failed to generate k0s config: %w", err) - } - - k0sConfigData, err := k0sConfig.Marshal() - if err != nil { - return fmt.Errorf("failed to marshal k0s config: %w", err) - } - - k0sConfigPath := filepath.Join(k0sConfigDir, k0sConfigFile) - k0sConfigPath, cleanup, err := writeK0sConfigFile(k0sConfigPath, k0sConfigData) - if err != nil { - return fmt.Errorf("failed to write k0s config: %w", err) - } - if cleanup != nil { - defer cleanup() - } - - k0sPath := pm.GetDependencyPath(defaultK0sPath) - if c.Opts.Package == "" { - k0sPath, err = k0s.Download(c.Opts.Version, c.Opts.Force, false) + // Determine k0s version + k0sVersion := c.Opts.Version + if k0sVersion == "" { + var err error + k0sVersion, err = k0s.GetLatestVersion() if err != nil { - return fmt.Errorf("failed to download k0s: %w", err) + return fmt.Errorf("failed to get latest k0s version: %w", err) + } + log.Printf("Using latest k0s version: %s", k0sVersion) + } + + // Download or get k0s binary path + var k0sBinaryPath string + if !c.Opts.NoDownload { + if c.Opts.Package != "" { + k0sBinaryPath = pm.GetDependencyPath(defaultK0sPath) + } else { + var err error + k0sBinaryPath, err = k0s.Download(k0sVersion, c.Opts.Force, false) + if err != nil { + return fmt.Errorf("failed to download k0s: %w", err) + } } } - if c.Opts.RemoteHost != "" { - return c.InstallK0sRemote(config, k0sPath, k0sConfigPath) + // Download k0sctl + log.Println("Downloading k0sctl...") + k0sctlPath, err := k0sctl.Download(c.Opts.K0sctlVersion, c.Opts.Force, false) + if err != nil { + return fmt.Errorf("failed to download k0sctl: %w", err) } - controlPlaneIPs := make([]string, 0, len(config.Kubernetes.ControlPlanes)) - for _, cp := range config.Kubernetes.ControlPlanes { - controlPlaneIPs = append(controlPlaneIPs, cp.IPAddress) - } - nodeIP, err := installer.GetNodeIPAddress(controlPlaneIPs) + // Generate k0sctl configuration + log.Println("Generating k0sctl configuration from install-config...") + k0sctlConfig, err := installer.GenerateK0sctlConfig(config, k0sVersion, c.Opts.SSHKeyPath, k0sBinaryPath) if err != nil { - log.Printf("Warning: could not determine node IP: %v. Installing without --kubelet-extra-args", err) - nodeIP = "" + return fmt.Errorf("failed to generate k0sctl config: %w", err) } - err = k0s.Install(k0sConfigPath, k0sPath, c.Opts.Force, nodeIP) + // Write k0sctl config to file + k0sctlConfigData, err := k0sctlConfig.Marshal() if err != nil { - return fmt.Errorf("failed to install k0s: %w", err) + return fmt.Errorf("failed to marshal k0sctl config: %w", err) } - log.Println("k0s installed successfully using configuration from install-config") - return nil -} - -func (c *InstallK0sCmd) InstallK0sRemote(config *files.RootConfig, k0sBinaryPath string, k0sConfigPath string) error { - log.Printf("Installing k0s on remote host %s", c.Opts.RemoteHost) - - nm := &node.NodeManager{ - FileIO: c.FileWriter, - KeyPath: c.Opts.SSHKeyPath, + k0sctlConfigPath := filepath.Join(k0sctlConfigDir, k0sctlConfigFile) + if err := os.WriteFile(k0sctlConfigPath, k0sctlConfigData, 0644); err != nil { + return fmt.Errorf("failed to write k0sctl config: %w", err) } + defer func() { _ = os.Remove(k0sctlConfigPath) }() - remoteNode := &node.Node{ - ExternalIP: c.Opts.RemoteHost, - InternalIP: c.Opts.RemoteHost, - Name: "k0s-node", - User: c.Opts.RemoteUser, - } + log.Printf("Generated k0sctl configuration at %s", k0sctlConfigPath) - controlPlaneIPs := make([]string, 0, len(config.Kubernetes.ControlPlanes)) - for _, cp := range config.Kubernetes.ControlPlanes { - controlPlaneIPs = append(controlPlaneIPs, cp.IPAddress) - } - nodeIP, err := installer.GetNodeIPAddress(controlPlaneIPs) - if err != nil { - log.Printf("Warning: could not determine node IP from control planes: %v. Using remote host IP: %s", err, c.Opts.RemoteHost) - nodeIP = c.Opts.RemoteHost + // Apply k0sctl configuration + log.Println("Applying k0sctl configuration to deploy k0s cluster...") + if err := k0sctl.Apply(k0sctlConfigPath, k0sctlPath, c.Opts.Force); err != nil { + return fmt.Errorf("failed to apply k0sctl config: %w", err) } - if err := remoteNode.InstallK0s(nm, k0sBinaryPath, k0sConfigPath, c.Opts.Force, nodeIP); err != nil { - return fmt.Errorf("failed to install k0s on remote host: %w", err) - } + log.Println("k0s cluster deployed successfully!") + log.Printf("To manage your cluster, use: %s kubeconfig --config %s", k0sctlPath, k0sctlConfigPath) - log.Printf("k0s successfully installed on remote host %s", c.Opts.RemoteHost) return nil } diff --git a/cli/cmd/install_k0s_test.go b/cli/cmd/install_k0s_test.go index 4bbf2e18..4b829a79 100644 --- a/cli/cmd/install_k0s_test.go +++ b/cli/cmd/install_k0s_test.go @@ -64,14 +64,16 @@ var _ = Describe("InstallK0sCmd", func() { Context("InstallK0s method", func() { var ( - mockPM *installer.MockPackageManager - mockK0s *installer.MockK0sManager - tempDir string + mockPM *installer.MockPackageManager + mockK0s *installer.MockK0sManager + mockK0sctl *installer.MockK0sctlManager + tempDir string ) BeforeEach(func() { mockPM = installer.NewMockPackageManager(GinkgoT()) mockK0s = installer.NewMockK0sManager(GinkgoT()) + mockK0sctl = installer.NewMockK0sctlManager(GinkgoT()) var err error tempDir, err = os.MkdirTemp("", "install-k0s-test-*") Expect(err).NotTo(HaveOccurred()) @@ -80,6 +82,7 @@ var _ = Describe("InstallK0sCmd", func() { AfterEach(func() { mockPM.AssertExpectations(GinkgoT()) mockK0s.AssertExpectations(GinkgoT()) + mockK0sctl.AssertExpectations(GinkgoT()) if tempDir != "" { _ = os.RemoveAll(tempDir) } @@ -117,7 +120,7 @@ var _ = Describe("InstallK0sCmd", func() { It("fails when install-config file does not exist", func() { c.Opts.InstallConfig = "/nonexistent/install-config.yaml" - err := c.InstallK0s(mockPM, mockK0s) + err := c.InstallK0s(mockPM, mockK0s, mockK0sctl) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to load install-config")) }) @@ -132,12 +135,12 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.InstallConfig = configPath - err = c.InstallK0s(mockPM, mockK0s) + err = c.InstallK0s(mockPM, mockK0s, mockK0sctl) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("external Kubernetes")) }) - It("successfully installs k0s locally with valid config", func() { + It("successfully installs k0s with valid config using k0sctl", func() { config := createTestConfig(true) configPath := filepath.Join(tempDir, "install-config.yaml") configData, err := yaml.Marshal(config) @@ -147,12 +150,14 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.InstallConfig = configPath c.Opts.Package = "test-package.tar.gz" + c.Opts.Version = "v1.30.0+k0s.0" c.Opts.Force = true mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") - mockK0s.EXPECT().Install(mock.Anything, "/test/path/k0s", true, mock.Anything).Return(nil) + mockK0sctl.EXPECT().Download("", true, false).Return("/tmp/k0sctl", nil) + mockK0sctl.EXPECT().Apply(mock.Anything, "/tmp/k0sctl", true).Return(nil) - err = c.InstallK0s(mockPM, mockK0s) + err = c.InstallK0s(mockPM, mockK0s, mockK0sctl) Expect(err).NotTo(HaveOccurred()) }) @@ -168,11 +173,11 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.Package = "" c.Opts.Version = "v1.29.0+k0s.0" - mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") mockK0s.EXPECT().Download("v1.29.0+k0s.0", false, false).Return("/downloaded/k0s", nil) - mockK0s.EXPECT().Install(mock.Anything, "/downloaded/k0s", false, mock.Anything).Return(nil) + mockK0sctl.EXPECT().Download("", false, false).Return("/tmp/k0sctl", nil) + mockK0sctl.EXPECT().Apply(mock.Anything, "/tmp/k0sctl", false).Return(nil) - err = c.InstallK0s(mockPM, mockK0s) + err = c.InstallK0s(mockPM, mockK0s, mockK0sctl) Expect(err).NotTo(HaveOccurred()) }) @@ -187,15 +192,15 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.InstallConfig = configPath c.Opts.Package = "" - mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") - mockK0s.EXPECT().Download("", false, false).Return("", os.ErrNotExist) + mockK0s.EXPECT().GetLatestVersion().Return("v1.30.0+k0s.0", nil) + mockK0s.EXPECT().Download("v1.30.0+k0s.0", false, false).Return("", os.ErrNotExist) - err = c.InstallK0s(mockPM, mockK0s) + err = c.InstallK0s(mockPM, mockK0s, mockK0sctl) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to download k0s")) }) - It("fails when k0s install fails", func() { + It("fails when k0sctl download fails", func() { config := createTestConfig(true) configPath := filepath.Join(tempDir, "install-config.yaml") configData, err := yaml.Marshal(config) @@ -205,16 +210,17 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.InstallConfig = configPath c.Opts.Package = "test-package.tar.gz" + c.Opts.Version = "v1.30.0+k0s.0" mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") - mockK0s.EXPECT().Install(mock.Anything, "/test/path/k0s", false, mock.Anything).Return(os.ErrPermission) + mockK0sctl.EXPECT().Download("", false, false).Return("", os.ErrPermission) - err = c.InstallK0s(mockPM, mockK0s) + err = c.InstallK0s(mockPM, mockK0s, mockK0sctl) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to install k0s")) + Expect(err.Error()).To(ContainSubstring("failed to download k0sctl")) }) - It("handles remote installation when remote-host is specified", func() { + It("fails when k0sctl apply fails", func() { config := createTestConfig(true) configPath := filepath.Join(tempDir, "install-config.yaml") configData, err := yaml.Marshal(config) @@ -224,151 +230,15 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.InstallConfig = configPath c.Opts.Package = "test-package.tar.gz" - c.Opts.RemoteHost = "192.168.1.50" - c.Opts.SSHKeyPath = "/path/to/key" + c.Opts.Version = "v1.30.0+k0s.0" mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") - mockFileWriter.EXPECT().ReadFile("/path/to/key").Return([]byte("invalid-key-data"), nil).Maybe() - - // Remote installation will fail because we can't actually connect, - // but we're testing that it attempts remote installation - err = c.InstallK0s(mockPM, mockK0s) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) - }) - }) - - Context("InstallK0sRemote method", func() { - var ( - config *files.RootConfig - ) - - BeforeEach(func() { - config = &files.RootConfig{ - Datacenter: files.DatacenterConfig{ - ID: 1, - Name: "test-dc", - }, - Kubernetes: files.KubernetesConfig{ - ManagedByCodesphere: true, - ControlPlanes: []files.K8sNode{ - {IPAddress: "192.168.1.100"}, - }, - }, - } - }) - - It("fails when SSH connection cannot be established", func() { - c.Opts.RemoteHost = "192.0.2.1" // TEST-NET-1, should fail to connect - c.Opts.SSHKeyPath = "/tmp/nonexistent-key" - - mockFileWriter.EXPECT().ReadFile("/tmp/nonexistent-key").Return([]byte("invalid-key-data"), nil).Maybe() - - err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) - }) - - It("fails when SSH key file does not exist", func() { - c.Opts.RemoteHost = "192.168.1.50" - c.Opts.SSHKeyPath = "/nonexistent/ssh/key" - - mockFileWriter.EXPECT().ReadFile("/nonexistent/ssh/key").Return(nil, os.ErrNotExist).Maybe() - - err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) - }) - - It("fails when SSH key file is invalid", func() { - c.Opts.RemoteHost = "192.168.1.50" - c.Opts.SSHKeyPath = "/path/to/invalid/key" - - mockFileWriter.EXPECT().ReadFile("/path/to/invalid/key").Return([]byte("not-a-valid-key"), nil).Maybe() - - err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) - }) - - It("uses correct remote host IP for node configuration", func() { - c.Opts.RemoteHost = "10.0.0.50" - c.Opts.SSHKeyPath = "/path/to/key" - - mockFileWriter.EXPECT().ReadFile("/path/to/key").Return([]byte("ssh-key-data"), nil).Maybe() - - err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) - }) - - It("passes correct paths to InstallK0s", func() { - c.Opts.RemoteHost = "192.168.1.60" - c.Opts.SSHKeyPath = "/custom/ssh/key" - c.Opts.Force = true - - mockFileWriter.EXPECT().ReadFile("/custom/ssh/key").Return([]byte("ssh-key-data"), nil).Maybe() - - err := c.InstallK0sRemote(config, "/custom/k0s/path", "/custom/config/path") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) - }) - - It("respects the force flag", func() { - c.Opts.RemoteHost = "192.168.1.70" - c.Opts.SSHKeyPath = "/path/to/key" - c.Opts.Force = true - - mockFileWriter.EXPECT().ReadFile("/path/to/key").Return([]byte("ssh-key-data"), nil).Maybe() - - err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) - }) - - It("uses remote user from options", func() { - c.Opts.RemoteHost = "192.168.1.80" - c.Opts.SSHKeyPath = "/path/to/key" - c.Opts.RemoteUser = "ubuntu" - - mockFileWriter.EXPECT().ReadFile("/path/to/key").Return([]byte("ssh-key-data"), nil).Maybe() - - err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) - }) - - It("handles empty remote host", func() { - c.Opts.RemoteHost = "" - c.Opts.SSHKeyPath = "/path/to/key" - - mockFileWriter.EXPECT().ReadFile("/path/to/key").Return([]byte("ssh-key-data"), nil).Maybe() - - err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) - }) - - It("handles timeout during SSH connection", func() { - c.Opts.RemoteHost = "192.0.2.1" // TEST-NET-1 address - c.Opts.SSHKeyPath = "/path/to/key" - - mockFileWriter.EXPECT().ReadFile("/path/to/key").Return([]byte("-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----"), nil).Maybe() - - err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) - }) - - It("wraps errors from InstallK0s with context", func() { - c.Opts.RemoteHost = "10.0.0.100" - c.Opts.SSHKeyPath = "/path/to/key" - - mockFileWriter.EXPECT().ReadFile("/path/to/key").Return([]byte("ssh-key-data"), nil).Maybe() + mockK0sctl.EXPECT().Download("", false, false).Return("/tmp/k0sctl", nil) + mockK0sctl.EXPECT().Apply(mock.Anything, "/tmp/k0sctl", false).Return(os.ErrPermission) - err := c.InstallK0sRemote(config, "/path/to/k0s", "/path/to/config") + err = c.InstallK0s(mockPM, mockK0s, mockK0sctl) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to install k0s on remote host")) + Expect(err.Error()).To(ContainSubstring("failed to apply k0sctl config")) }) }) }) diff --git a/cli/cmd/mocks.go b/cli/cmd/mocks.go index 26b37df5..60357284 100644 --- a/cli/cmd/mocks.go +++ b/cli/cmd/mocks.go @@ -75,32 +75,16 @@ type MockOMSUpdater_Update_Call struct { } // Update is a helper method to define mock.On call -// - ctx context.Context -// - current string -// - repo selfupdate.Repository +// - ctx +// - current +// - repo func (_e *MockOMSUpdater_Expecter) Update(ctx interface{}, current interface{}, repo interface{}) *MockOMSUpdater_Update_Call { return &MockOMSUpdater_Update_Call{Call: _e.mock.On("Update", ctx, current, repo)} } func (_c *MockOMSUpdater_Update_Call) Run(run func(ctx context.Context, current string, repo selfupdate.Repository)) *MockOMSUpdater_Update_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 selfupdate.Repository - if args[2] != nil { - arg2 = args[2].(selfupdate.Repository) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(context.Context), args[1].(string), args[2].(selfupdate.Repository)) }) return _c } diff --git a/hack/lima-oms.yaml b/hack/lima-oms.yaml index 67261b71..d7724b12 100644 --- a/hack/lima-oms.yaml +++ b/hack/lima-oms.yaml @@ -151,7 +151,6 @@ message: | # Run specific k0s tests go test ./cli/cmd -ginkgo.v -ginkgo.focus "InstallK0s" go test ./internal/installer -ginkgo.v -ginkgo.focus "k0s" - go test ./internal/installer/node -ginkgo.v -ginkgo.focus "InstallK0s" ------ Build for different platforms: diff --git a/internal/installer/k0s.go b/internal/installer/k0s.go index 46e5cbdf..da17c14a 100644 --- a/internal/installer/k0s.go +++ b/internal/installer/k0s.go @@ -15,14 +15,11 @@ import ( "github.com/codesphere-cloud/oms/internal/env" "github.com/codesphere-cloud/oms/internal/portal" "github.com/codesphere-cloud/oms/internal/util" - "gopkg.in/yaml.v3" ) type K0sManager interface { GetLatestVersion() (string, error) Download(version string, force bool, quiet bool) (string, error) - Install(configPath string, k0sPath string, force bool, nodeIP string) error - Reset(k0sPath string) error } type K0s struct { @@ -33,31 +30,6 @@ type K0s struct { Goarch string } -// valid top-level fields in a k0s ClusterConfig. -// Reference: https://docs.k0sproject.io/stable/configuration/ -var K0sConfigTopLevelKeys = []string{ - "apiVersion", - "kind", - "metadata", - "spec", -} - -// valid fields in the spec section of a k0s ClusterConfig. -// Reference: https://docs.k0sproject.io/stable/configuration/ -var K0sConfigSpecKeys = []string{ - "api", - "controllerManager", - "scheduler", - "extensions", - "network", - "storage", - "telemetry", - "images", - "konnectivity", - "installConfig", - "featureGates", -} - func NewK0s(hw portal.Http, env env.Env, fw util.FileIO) K0sManager { return &K0s{ Env: env, @@ -127,118 +99,6 @@ func (k *K0s) Download(version string, force bool, quiet bool) (string, error) { return k0sPath, nil } -func (k *K0s) Install(configPath string, k0sPath string, force bool, nodeIP string) error { - if k.Goos != "linux" || k.Goarch != "amd64" { - return fmt.Errorf("k0s installation is only supported on Linux amd64. Current platform: %s/%s", k.Goos, k.Goarch) - } - - if !k.FileWriter.Exists(k0sPath) { - return fmt.Errorf("k0s binary does not exist in '%s', please download first", k0sPath) - } - - if force { - if err := k.Reset(k0sPath); err != nil { - log.Printf("Warning: failed to reset k0s: %v", err) - } - } - - args := []string{k0sPath, "install", "controller"} - - // If config path is provided, filter it to only include k0s-compatible fields - if configPath != "" { - filteredConfigPath, err := k.filterConfigForK0s(configPath) - if err != nil { - log.Printf("Warning: failed to filter config, using original: %v", err) - args = append(args, "--config", configPath) - } else { - args = append(args, "--config", filteredConfigPath) - defer func() { _ = os.Remove(filteredConfigPath) }() - } - } else { - args = append(args, "--single") - } - - args = append(args, "--enable-worker") - args = append(args, "--no-taints") - - if nodeIP != "" { - args = append(args, "--kubelet-extra-args", fmt.Sprintf("--node-ip=%s", nodeIP)) - } - - if force { - args = append(args, "--force") - } - - err := util.RunCommand("sudo", args, "") - if err != nil { - return fmt.Errorf("failed to install k0s: %w", err) - } - - if configPath != "" { - log.Println("k0s installed successfully with provided configuration.") - } else { - log.Println("k0s installed successfully in single-node mode.") - } - log.Printf("You can start it using 'sudo %v start'", k0sPath) - log.Printf("You can check the status using 'sudo %v status'", k0sPath) - - return nil -} - -func (k *K0s) filterConfigForK0s(configPath string) (string, error) { - data, err := os.ReadFile(configPath) - if err != nil { - return "", fmt.Errorf("failed to read config: %w", err) - } - - var config map[string]interface{} - if err := yaml.Unmarshal(data, &config); err != nil { - return "", fmt.Errorf("failed to parse config: %w", err) - } - - keysToKeep := make(map[string]bool, len(K0sConfigTopLevelKeys)) - for _, key := range K0sConfigTopLevelKeys { - keysToKeep[key] = true - } - - for key := range config { - if !keysToKeep[key] { - delete(config, key) - } - } - - if spec, ok := config["spec"].(map[string]interface{}); ok { - specKeysToKeep := make(map[string]bool, len(K0sConfigSpecKeys)) - for _, key := range K0sConfigSpecKeys { - specKeysToKeep[key] = true - } - - for key := range spec { - if !specKeysToKeep[key] { - delete(spec, key) - } - } - config["spec"] = spec - } - - filteredData, err := yaml.Marshal(config) - if err != nil { - return "", fmt.Errorf("failed to marshal filtered config: %w", err) - } - - tmpFile, err := os.CreateTemp("", "k0s-config-*.yaml") - if err != nil { - return "", fmt.Errorf("failed to create temp config: %w", err) - } - defer func() { _ = tmpFile.Close() }() - - if _, err := tmpFile.Write(filteredData); err != nil { - return "", fmt.Errorf("failed to write temp config: %w", err) - } - - return tmpFile.Name(), nil -} - // GetNodeIPAddress finds the IP address of the current node by matching // against the control plane IPs in the config func GetNodeIPAddress(controlPlanes []string) (string, error) { @@ -270,26 +130,3 @@ func GetNodeIPAddress(controlPlanes []string) (string, error) { return "", fmt.Errorf("no suitable IP address found") } - -// Reset tears down an existing k0s installation by executing `k0s reset`. -// This command removes all k0s-related resources -func (k *K0s) Reset(k0sPath string) error { - if !k.FileWriter.Exists(k0sPath) { - return nil - } - - log.Println("Resetting existing k0s installation...") - - log.Println("Stopping k0s service if running...") - if err := util.RunCommand("sudo", []string{k0sPath, "stop"}, ""); err != nil { - log.Printf("Note: k0s stop returned error, try to continue the reset: %v", err) - } - - err := util.RunCommand("sudo", []string{k0sPath, "reset"}, "") - if err != nil { - return fmt.Errorf("failed to reset k0s: %w", err) - } - - log.Println("k0s reset completed successfully") - return nil -} diff --git a/internal/installer/k0s_internal_test.go b/internal/installer/k0s_internal_test.go deleted file mode 100644 index 83ecc537..00000000 --- a/internal/installer/k0s_internal_test.go +++ /dev/null @@ -1,339 +0,0 @@ -// Copyright (c) Codesphere Inc. -// SPDX-License-Identifier: Apache-2.0 - -package installer - -import ( - "os" - "path/filepath" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "gopkg.in/yaml.v3" -) - -var _ = Describe("K0s Internal Methods", func() { - var ( - k0s *K0s - tempConfigDir string - ) - - BeforeEach(func() { - k0s = &K0s{} - var err error - tempConfigDir, err = os.MkdirTemp("", "k0s-config-test-*") - Expect(err).NotTo(HaveOccurred()) - }) - - AfterEach(func() { - if tempConfigDir != "" { - _ = os.RemoveAll(tempConfigDir) - } - }) - - createConfigFile := func(content string) string { - configPath := filepath.Join(tempConfigDir, "test-config.yaml") - err := os.WriteFile(configPath, []byte(content), 0644) - Expect(err).NotTo(HaveOccurred()) - return configPath - } - - Describe("filterConfigForK0s", func() { - It("filters out non-k0s top-level fields", func() { - configContent := `apiVersion: k0s.k0sproject.io/v1beta1 -kind: ClusterConfig -metadata: - name: test-cluster -spec: - api: - address: 192.168.1.100 -extraField: should-be-removed -anotherExtra: also-removed -` - configPath := createConfigFile(configContent) - - filteredPath, err := k0s.filterConfigForK0s(configPath) - Expect(err).NotTo(HaveOccurred()) - Expect(filteredPath).NotTo(BeEmpty()) - defer func() { _ = os.Remove(filteredPath) }() - - // Read and verify filtered content - data, err := os.ReadFile(filteredPath) - Expect(err).NotTo(HaveOccurred()) - content := string(data) - - Expect(content).To(ContainSubstring("apiVersion")) - Expect(content).To(ContainSubstring("kind")) - Expect(content).To(ContainSubstring("metadata")) - Expect(content).To(ContainSubstring("spec")) - Expect(content).NotTo(ContainSubstring("extraField")) - Expect(content).NotTo(ContainSubstring("anotherExtra")) - }) - - It("preserves all expected k0s fields at top level", func() { - configContent := `apiVersion: k0s.k0sproject.io/v1beta1 -kind: ClusterConfig -metadata: - name: test-cluster -spec: - api: - address: 192.168.1.100 -` - configPath := createConfigFile(configContent) - - filteredPath, err := k0s.filterConfigForK0s(configPath) - Expect(err).NotTo(HaveOccurred()) - Expect(filteredPath).NotTo(BeEmpty()) - defer func() { _ = os.Remove(filteredPath) }() - - data, err := os.ReadFile(filteredPath) - Expect(err).NotTo(HaveOccurred()) - content := string(data) - - Expect(content).To(ContainSubstring("apiVersion: k0s.k0sproject.io/v1beta1")) - Expect(content).To(ContainSubstring("kind: ClusterConfig")) - Expect(content).To(ContainSubstring("metadata")) - Expect(content).To(ContainSubstring("spec")) - }) - - It("filters out non-k0s spec fields", func() { - configContent := `apiVersion: k0s.k0sproject.io/v1beta1 -kind: ClusterConfig -spec: - api: - address: 192.168.1.100 - network: - provider: calico - customField: should-be-removed - anotherCustom: also-removed -` - configPath := createConfigFile(configContent) - - filteredPath, err := k0s.filterConfigForK0s(configPath) - Expect(err).NotTo(HaveOccurred()) - Expect(filteredPath).NotTo(BeEmpty()) - defer func() { _ = os.Remove(filteredPath) }() - - data, err := os.ReadFile(filteredPath) - Expect(err).NotTo(HaveOccurred()) - content := string(data) - - Expect(content).To(ContainSubstring("api")) - Expect(content).To(ContainSubstring("network")) - Expect(content).NotTo(ContainSubstring("customField")) - Expect(content).NotTo(ContainSubstring("anotherCustom")) - }) - - It("preserves all expected k0s spec fields", func() { - configContent := `apiVersion: k0s.k0sproject.io/v1beta1 -kind: ClusterConfig -spec: - api: - address: 192.168.1.100 - controllerManager: - extraArgs: - - --cluster-cidr=10.244.0.0/16 - scheduler: - extraArgs: - - --bind-address=0.0.0.0 - extensions: - helm: - repositories: - - name: stable - network: - provider: calico - storage: - type: etcd - telemetry: - enabled: false - images: - default_pull_policy: IfNotPresent - konnectivity: - enabled: true -` - configPath := createConfigFile(configContent) - - filteredPath, err := k0s.filterConfigForK0s(configPath) - Expect(err).NotTo(HaveOccurred()) - Expect(filteredPath).NotTo(BeEmpty()) - defer func() { _ = os.Remove(filteredPath) }() - - data, err := os.ReadFile(filteredPath) - Expect(err).NotTo(HaveOccurred()) - content := string(data) - - // Verify all expected spec fields are preserved - Expect(content).To(ContainSubstring("api")) - Expect(content).To(ContainSubstring("controllerManager")) - Expect(content).To(ContainSubstring("scheduler")) - Expect(content).To(ContainSubstring("extensions")) - Expect(content).To(ContainSubstring("network")) - Expect(content).To(ContainSubstring("storage")) - Expect(content).To(ContainSubstring("telemetry")) - Expect(content).To(ContainSubstring("images")) - Expect(content).To(ContainSubstring("konnectivity")) - }) - - It("handles config with only required fields", func() { - configContent := `apiVersion: k0s.k0sproject.io/v1beta1 -kind: ClusterConfig -spec: - api: - address: 192.168.1.100 -` - configPath := createConfigFile(configContent) - - filteredPath, err := k0s.filterConfigForK0s(configPath) - Expect(err).NotTo(HaveOccurred()) - Expect(filteredPath).NotTo(BeEmpty()) - defer func() { _ = os.Remove(filteredPath) }() - - data, err := os.ReadFile(filteredPath) - Expect(err).NotTo(HaveOccurred()) - - Expect(string(data)).NotTo(BeEmpty()) - }) - - It("creates a temporary file with .yaml extension", func() { - configContent := `apiVersion: k0s.k0sproject.io/v1beta1 -kind: ClusterConfig -spec: - api: - address: 192.168.1.100 -` - configPath := createConfigFile(configContent) - - filteredPath, err := k0s.filterConfigForK0s(configPath) - Expect(err).NotTo(HaveOccurred()) - Expect(filteredPath).NotTo(BeEmpty()) - defer func() { _ = os.Remove(filteredPath) }() - - Expect(filteredPath).To(HaveSuffix(".yaml")) - Expect(filteredPath).To(ContainSubstring("k0s-config-")) - - // Verify file exists and is readable - _, err = os.Stat(filteredPath) - Expect(err).NotTo(HaveOccurred()) - }) - - It("fails when config file does not exist", func() { - _, err := k0s.filterConfigForK0s("/nonexistent/config.yaml") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to read config")) - }) - - It("fails when config contains invalid YAML", func() { - configPath := createConfigFile("invalid: yaml: content: [") - - _, err := k0s.filterConfigForK0s(configPath) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to parse config")) - }) - - It("handles complex nested structures correctly", func() { - configContent := `apiVersion: k0s.k0sproject.io/v1beta1 -kind: ClusterConfig -metadata: - name: production-cluster -spec: - api: - address: 192.168.1.100 - port: 6443 - sans: - - api.example.com - - 192.168.1.100 - network: - provider: calico - podCIDR: 10.244.0.0/16 - serviceCIDR: 10.96.0.0/12 - extensions: - helm: - repositories: - - name: stable - url: https://charts.helm.sh/stable - charts: - - name: metrics-server - namespace: kube-system -customTopLevel: should-be-filtered -` - configPath := createConfigFile(configContent) - - filteredPath, err := k0s.filterConfigForK0s(configPath) - Expect(err).NotTo(HaveOccurred()) - Expect(filteredPath).NotTo(BeEmpty()) - defer func() { _ = os.Remove(filteredPath) }() - - data, err := os.ReadFile(filteredPath) - Expect(err).NotTo(HaveOccurred()) - content := string(data) - - // Verify nested structures are preserved - Expect(content).To(ContainSubstring("api.example.com")) - Expect(content).To(ContainSubstring("10.244.0.0/16")) - Expect(content).To(ContainSubstring("metrics-server")) - - // Verify custom fields are filtered out - Expect(content).NotTo(ContainSubstring("customTopLevel")) - }) - - It("filters custom fields within spec", func() { - configContent := `apiVersion: k0s.k0sproject.io/v1beta1 -kind: ClusterConfig -spec: - api: - address: 192.168.1.100 - network: - provider: calico - customInSpec: should-be-filtered - anotherCustom: also-filtered -` - configPath := createConfigFile(configContent) - - filteredPath, err := k0s.filterConfigForK0s(configPath) - Expect(err).NotTo(HaveOccurred()) - Expect(filteredPath).NotTo(BeEmpty()) - defer func() { _ = os.Remove(filteredPath) }() - - data, err := os.ReadFile(filteredPath) - Expect(err).NotTo(HaveOccurred()) - content := string(data) - - Expect(content).To(ContainSubstring("api")) - Expect(content).To(ContainSubstring("network")) - Expect(content).NotTo(ContainSubstring("customInSpec")) - Expect(content).NotTo(ContainSubstring("anotherCustom")) - }) - - It("returns valid YAML that can be parsed", func() { - configContent := `apiVersion: k0s.k0sproject.io/v1beta1 -kind: ClusterConfig -spec: - api: - address: 192.168.1.100 - network: - provider: calico -extraField: removed -` - configPath := createConfigFile(configContent) - - filteredPath, err := k0s.filterConfigForK0s(configPath) - Expect(err).NotTo(HaveOccurred()) - Expect(filteredPath).NotTo(BeEmpty()) - defer func() { _ = os.Remove(filteredPath) }() - - // Verify the output is valid YAML by parsing it - data, err := os.ReadFile(filteredPath) - Expect(err).NotTo(HaveOccurred()) - - var result map[string]interface{} - err = yaml.Unmarshal(data, &result) - Expect(err).NotTo(HaveOccurred()) - - // Verify expected structure - Expect(result).To(HaveKey("apiVersion")) - Expect(result).To(HaveKey("kind")) - Expect(result).To(HaveKey("spec")) - Expect(result).NotTo(HaveKey("extraField")) - }) - }) -}) diff --git a/internal/installer/k0s_test.go b/internal/installer/k0s_test.go index e7878488..dbd36d80 100644 --- a/internal/installer/k0s_test.go +++ b/internal/installer/k0s_test.go @@ -281,72 +281,4 @@ var _ = Describe("K0s", func() { }) }) }) - - Describe("Install", func() { - Context("Platform support", func() { - It("should fail on non-Linux platforms", func() { - k0sImpl.Goos = "windows" - k0sImpl.Goarch = "amd64" - - err := k0s.Install("", k0sPath, false, "") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("k0s installation is only supported on Linux amd64")) - Expect(err.Error()).To(ContainSubstring("windows/amd64")) - }) - - It("should fail on non-amd64 architectures", func() { - k0sImpl.Goos = "linux" - k0sImpl.Goarch = "arm64" - - err := k0s.Install("", k0sPath, false, "") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("k0s installation is only supported on Linux amd64")) - Expect(err.Error()).To(ContainSubstring("linux/arm64")) - }) - }) - - Context("Binary existence checks", func() { - BeforeEach(func() { - k0sImpl.Goos = "linux" - k0sImpl.Goarch = "amd64" - }) - - It("should fail when k0s binary doesn't exist", func() { - mockFileWriter.EXPECT().Exists(k0sPath).Return(false) - - err := k0s.Install("", k0sPath, false, "") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("k0s binary does not exist")) - Expect(err.Error()).To(ContainSubstring("please download first")) - }) - }) - }) - - Describe("Reset", func() { - BeforeEach(func() { - k0sImpl.Goos = "linux" - k0sImpl.Goarch = "amd64" - }) - - Context("when k0s binary does not exist", func() { - It("should return nil without attempting reset", func() { - mockFileWriter.EXPECT().Exists(k0sPath).Return(false) - - err := k0s.Reset(k0sPath) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - Context("platform validation", func() { - It("should work regardless of platform for reset", func() { - k0sImpl.Goos = "darwin" - k0sImpl.Goarch = "arm64" - - mockFileWriter.EXPECT().Exists(k0sPath).Return(false) - - err := k0s.Reset(k0sPath) - Expect(err).NotTo(HaveOccurred()) - }) - }) - }) }) diff --git a/internal/installer/k0sctl.go b/internal/installer/k0sctl.go new file mode 100644 index 00000000..9237c4d1 --- /dev/null +++ b/internal/installer/k0sctl.go @@ -0,0 +1,175 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package installer + +import ( + "fmt" + "log" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/codesphere-cloud/oms/internal/env" + "github.com/codesphere-cloud/oms/internal/portal" + "github.com/codesphere-cloud/oms/internal/util" +) + +type K0sctlManager interface { + GetLatestVersion() (string, error) + Download(version string, force bool, quiet bool) (string, error) + Apply(configPath string, k0sctlPath string, force bool) error + Reset(configPath string, k0sctlPath string) error +} + +type K0sctl struct { + Env env.Env + Http portal.Http + FileWriter util.FileIO + Goos string + Goarch string +} + +func NewK0sctl(hw portal.Http, env env.Env, fw util.FileIO) K0sctlManager { + return &K0sctl{ + Env: env, + Http: hw, + FileWriter: fw, + Goos: runtime.GOOS, + Goarch: runtime.GOARCH, + } +} + +func (k *K0sctl) GetLatestVersion() (string, error) { + // k0sctl uses GitHub releases - fetch latest from API + releaseURL := "https://api.github.com/repos/k0sproject/k0sctl/releases/latest" + responseBody, err := k.Http.Get(releaseURL) + if err != nil { + return "", fmt.Errorf("failed to fetch latest k0sctl release: %w", err) + } + + // Simple parsing - just extract tag_name + tagStart := string(responseBody) + if idx := strings.Index(tagStart, `"tag_name"`); idx != -1 { + tagStart = tagStart[idx:] + if start := strings.Index(tagStart, `"`); start != -1 { + tagStart = tagStart[start+len(`"tag_name":"`)+1:] + if end := strings.Index(tagStart, `"`); end != -1 { + version := tagStart[:end] + return version, nil + } + } + } + + return "", fmt.Errorf("failed to parse version from GitHub API response") +} + +func (k *K0sctl) Download(version string, force bool, quiet bool) (string, error) { + if version == "" { + var err error + version, err = k.GetLatestVersion() + if err != nil { + return "", fmt.Errorf("failed to get latest version: %w", err) + } + if !quiet { + log.Printf("Using latest k0sctl version: %s", version) + } + } + + // Ensure workdir exists + workdir := k.Env.GetOmsWorkdir() + if err := os.MkdirAll(workdir, 0755); err != nil { + return "", fmt.Errorf("failed to create workdir: %w", err) + } + + k0sctlPath := filepath.Join(workdir, "k0sctl") + if k.FileWriter.Exists(k0sctlPath) && !force { + return "", fmt.Errorf("k0sctl binary already exists at %s. Use --force to overwrite", k0sctlPath) + } + + // Construct download URL + // Format: https://github.com/k0sproject/k0sctl/releases/download/v0.17.4/k0sctl-linux-amd64 + binaryName := fmt.Sprintf("k0sctl-%s-%s", k.Goos, k.Goarch) + // Ensure version has v prefix for GitHub URL + if !strings.HasPrefix(version, "v") { + version = "v" + version + } + downloadURL := fmt.Sprintf("https://github.com/k0sproject/k0sctl/releases/download/%s/%s", version, binaryName) + + if !quiet { + log.Printf("Downloading k0sctl %s from %s", version, downloadURL) + } + + dstFile, err := k.FileWriter.Create(k0sctlPath) + if err != nil { + return "", fmt.Errorf("failed to create destination file: %w", err) + } + defer util.IgnoreError(dstFile.Close) + + if err := k.Http.Download(downloadURL, dstFile, quiet); err != nil { + return "", fmt.Errorf("failed to download k0sctl: %w", err) + } + + // Make binary executable + if err := os.Chmod(k0sctlPath, 0755); err != nil { + return "", fmt.Errorf("failed to make k0sctl executable: %w", err) + } + + if !quiet { + log.Printf("k0sctl downloaded successfully to %s", k0sctlPath) + } + + return k0sctlPath, nil +} + +func (k *K0sctl) Apply(configPath string, k0sctlPath string, force bool) error { + if !k.FileWriter.Exists(k0sctlPath) { + return fmt.Errorf("k0sctl binary does not exist at '%s', please download first", k0sctlPath) + } + + if !k.FileWriter.Exists(configPath) { + return fmt.Errorf("k0sctl config does not exist at '%s'", configPath) + } + + args := []string{k0sctlPath, "apply", "--config", configPath} + + if force { + args = append(args, "--force") + } + + // Add debug flag for more verbose output + args = append(args, "--debug") + + log.Printf("Running k0sctl apply with config: %s", configPath) + + err := util.RunCommand("sudo", args, "") + if err != nil { + return fmt.Errorf("k0sctl apply failed: %w", err) + } + + log.Println("k0sctl apply completed successfully") + return nil +} + +func (k *K0sctl) Reset(configPath string, k0sctlPath string) error { + if !k.FileWriter.Exists(k0sctlPath) { + return nil + } + + if !k.FileWriter.Exists(configPath) { + return fmt.Errorf("k0sctl config does not exist at '%s'", configPath) + } + + log.Println("Resetting k0s cluster using k0sctl...") + + args := []string{k0sctlPath, "reset", "--config", configPath, "--force"} + + err := util.RunCommand("sudo", args, "") + if err != nil { + return fmt.Errorf("k0sctl reset failed: %w", err) + } + + log.Println("k0sctl reset completed successfully") + return nil +} diff --git a/internal/installer/k0sctl_config.go b/internal/installer/k0sctl_config.go new file mode 100644 index 00000000..337978dc --- /dev/null +++ b/internal/installer/k0sctl_config.go @@ -0,0 +1,203 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package installer + +import ( + "fmt" + + "github.com/codesphere-cloud/oms/internal/installer/files" + "gopkg.in/yaml.v3" +) + +// K0sctlConfig represents the k0sctl configuration file structure +type K0sctlConfig struct { + APIVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Metadata K0sctlMeta `yaml:"metadata,omitempty"` + Spec K0sctlSpec `yaml:"spec"` +} + +type K0sctlMeta struct { + Name string `yaml:"name"` +} + +type K0sctlSpec struct { + Hosts []K0sctlHost `yaml:"hosts"` + K0s K0sctlK0s `yaml:"k0s"` +} + +type K0sctlHost struct { + Role string `yaml:"role"` + SSH K0sctlSSH `yaml:"ssh"` + InstallFlags []string `yaml:"installFlags,omitempty"` + PrivateInterface string `yaml:"privateInterface,omitempty"` + PrivateAddress string `yaml:"privateAddress,omitempty"` + Environment map[string]string `yaml:"environment,omitempty"` + UploadBinary bool `yaml:"uploadBinary,omitempty"` + K0sBinaryPath string `yaml:"k0sBinaryPath,omitempty"` + Hooks *K0sctlHooks `yaml:"hooks,omitempty"` +} + +type K0sctlSSH struct { + Address string `yaml:"address"` + User string `yaml:"user"` + Port int `yaml:"port"` + KeyPath string `yaml:"keyPath,omitempty"` + Bastion *K0sctlBastion `yaml:"bastion,omitempty"` +} + +type K0sctlBastion struct { + Address string `yaml:"address"` + User string `yaml:"user"` + Port int `yaml:"port"` + KeyPath string `yaml:"keyPath,omitempty"` +} + +type K0sctlK0s struct { + Version string `yaml:"version"` + Config map[string]interface{} `yaml:"config,omitempty"` +} + +type K0sctlHooks struct { + Apply *K0sctlApplyHooks `yaml:"apply,omitempty"` +} + +type K0sctlApplyHooks struct { + Before []string `yaml:"before,omitempty"` + After []string `yaml:"after,omitempty"` +} + +// GenerateK0sctlConfig generates a k0sctl configuration from a Codesphere install-config +func GenerateK0sctlConfig(installConfig *files.RootConfig, k0sVersion string, sshKeyPath string, k0sBinaryPath string) (*K0sctlConfig, error) { + if installConfig == nil { + return nil, fmt.Errorf("installConfig cannot be nil") + } + + if !installConfig.Kubernetes.ManagedByCodesphere { + return nil, fmt.Errorf("k0sctl is only supported for Codesphere-managed Kubernetes") + } + + // Generate k0s config that will be embedded in k0sctl config + k0sConfig, err := GenerateK0sConfig(installConfig) + if err != nil { + return nil, fmt.Errorf("failed to generate k0s config: %w", err) + } + + // Convert K0sConfig struct to map for k0sctl + k0sConfigYAML, err := k0sConfig.Marshal() + if err != nil { + return nil, fmt.Errorf("failed to marshal k0s config: %w", err) + } + + var k0sConfigMap map[string]interface{} + if err := yaml.Unmarshal(k0sConfigYAML, &k0sConfigMap); err != nil { + return nil, fmt.Errorf("failed to unmarshal k0s config to map: %w", err) + } + + k0sctlConfig := &K0sctlConfig{ + APIVersion: "k0sctl.k0sproject.io/v1beta1", + Kind: "Cluster", + Metadata: K0sctlMeta{ + Name: fmt.Sprintf("codesphere-%s", installConfig.Datacenter.Name), + }, + Spec: K0sctlSpec{ + Hosts: []K0sctlHost{}, + K0s: K0sctlK0s{ + Version: k0sVersion, + Config: k0sConfigMap, + }, + }, + } + + // Track added IPs to avoid duplicates + addedIPs := make(map[string]bool) + + // Add controller+worker nodes from control planes + for i, cp := range installConfig.Kubernetes.ControlPlanes { + host := K0sctlHost{ + Role: "controller+worker", + SSH: K0sctlSSH{ + Address: cp.IPAddress, + User: "root", + Port: 22, + }, + InstallFlags: []string{ + "--enable-worker", + "--no-taints", + }, + PrivateAddress: cp.IPAddress, + } + + // Add SSH key path if provided + if sshKeyPath != "" { + host.SSH.KeyPath = sshKeyPath + } + + // Add k0s binary path if provided + if k0sBinaryPath != "" { + host.UploadBinary = true + host.K0sBinaryPath = k0sBinaryPath + } + + // Set node-ip in kubelet extra args + host.Environment = map[string]string{ + "KUBELET_EXTRA_ARGS": fmt.Sprintf("--node-ip=%s", cp.IPAddress), + } + + // Name hosts for clarity + if len(installConfig.Kubernetes.ControlPlanes) > 1 { + host.SSH.Address = fmt.Sprintf("%s # controller-%d", cp.IPAddress, i+1) + } + + k0sctlConfig.Spec.Hosts = append(k0sctlConfig.Spec.Hosts, host) + addedIPs[cp.IPAddress] = true + } + + // Add dedicated worker nodes if present + for i, worker := range installConfig.Kubernetes.Workers { + if addedIPs[worker.IPAddress] { + continue + } + host := K0sctlHost{ + Role: "worker", + SSH: K0sctlSSH{ + Address: worker.IPAddress, + User: "root", + Port: 22, + }, + PrivateAddress: worker.IPAddress, + } + + if sshKeyPath != "" { + host.SSH.KeyPath = sshKeyPath + } + + if k0sBinaryPath != "" { + host.UploadBinary = true + host.K0sBinaryPath = k0sBinaryPath + } + + host.Environment = map[string]string{ + "KUBELET_EXTRA_ARGS": fmt.Sprintf("--node-ip=%s", worker.IPAddress), + } + + if len(installConfig.Kubernetes.Workers) > 1 { + host.SSH.Address = fmt.Sprintf("%s # worker-%d", worker.IPAddress, i+1) + } + + k0sctlConfig.Spec.Hosts = append(k0sctlConfig.Spec.Hosts, host) + } + + return k0sctlConfig, nil +} + +// Marshal serializes the k0sctl config to YAML +func (c *K0sctlConfig) Marshal() ([]byte, error) { + return yaml.Marshal(c) +} + +// Unmarshal deserializes YAML to a k0sctl config +func (c *K0sctlConfig) Unmarshal(data []byte) error { + return yaml.Unmarshal(data, c) +} diff --git a/internal/installer/mocks.go b/internal/installer/mocks.go index 8474882b..cf9131fc 100644 --- a/internal/installer/mocks.go +++ b/internal/installer/mocks.go @@ -69,20 +69,14 @@ type MockConfigManager_ParseConfigYaml_Call struct { } // ParseConfigYaml is a helper method to define mock.On call -// - configPath string +// - configPath func (_e *MockConfigManager_Expecter) ParseConfigYaml(configPath interface{}) *MockConfigManager_ParseConfigYaml_Call { return &MockConfigManager_ParseConfigYaml_Call{Call: _e.mock.On("ParseConfigYaml", configPath)} } func (_c *MockConfigManager_ParseConfigYaml_Call) Run(run func(configPath string)) *MockConfigManager_ParseConfigYaml_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -147,20 +141,14 @@ type MockInstallConfigManager_ApplyProfile_Call struct { } // ApplyProfile is a helper method to define mock.On call -// - profile string +// - profile func (_e *MockInstallConfigManager_Expecter) ApplyProfile(profile interface{}) *MockInstallConfigManager_ApplyProfile_Call { return &MockInstallConfigManager_ApplyProfile_Call{Call: _e.mock.On("ApplyProfile", profile)} } func (_c *MockInstallConfigManager_ApplyProfile_Call) Run(run func(profile string)) *MockInstallConfigManager_ApplyProfile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -378,20 +366,14 @@ type MockInstallConfigManager_LoadInstallConfigFromFile_Call struct { } // LoadInstallConfigFromFile is a helper method to define mock.On call -// - configPath string +// - configPath func (_e *MockInstallConfigManager_Expecter) LoadInstallConfigFromFile(configPath interface{}) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { return &MockInstallConfigManager_LoadInstallConfigFromFile_Call{Call: _e.mock.On("LoadInstallConfigFromFile", configPath)} } func (_c *MockInstallConfigManager_LoadInstallConfigFromFile_Call) Run(run func(configPath string)) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -429,20 +411,14 @@ type MockInstallConfigManager_LoadVaultFromFile_Call struct { } // LoadVaultFromFile is a helper method to define mock.On call -// - vaultPath string +// - vaultPath func (_e *MockInstallConfigManager_Expecter) LoadVaultFromFile(vaultPath interface{}) *MockInstallConfigManager_LoadVaultFromFile_Call { return &MockInstallConfigManager_LoadVaultFromFile_Call{Call: _e.mock.On("LoadVaultFromFile", vaultPath)} } func (_c *MockInstallConfigManager_LoadVaultFromFile_Call) Run(run func(vaultPath string)) *MockInstallConfigManager_LoadVaultFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -616,26 +592,15 @@ type MockInstallConfigManager_WriteInstallConfig_Call struct { } // WriteInstallConfig is a helper method to define mock.On call -// - configPath string -// - withComments bool +// - configPath +// - withComments func (_e *MockInstallConfigManager_Expecter) WriteInstallConfig(configPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteInstallConfig_Call { return &MockInstallConfigManager_WriteInstallConfig_Call{Call: _e.mock.On("WriteInstallConfig", configPath, withComments)} } func (_c *MockInstallConfigManager_WriteInstallConfig_Call) Run(run func(configPath string, withComments bool)) *MockInstallConfigManager_WriteInstallConfig_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -673,26 +638,15 @@ type MockInstallConfigManager_WriteVault_Call struct { } // WriteVault is a helper method to define mock.On call -// - vaultPath string -// - withComments bool +// - vaultPath +// - withComments func (_e *MockInstallConfigManager_Expecter) WriteVault(vaultPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteVault_Call { return &MockInstallConfigManager_WriteVault_Call{Call: _e.mock.On("WriteVault", vaultPath, withComments)} } func (_c *MockInstallConfigManager_WriteVault_Call) Run(run func(vaultPath string, withComments bool)) *MockInstallConfigManager_WriteVault_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -766,32 +720,16 @@ type MockK0sManager_Download_Call struct { } // Download is a helper method to define mock.On call -// - version string -// - force bool -// - quiet bool +// - version +// - force +// - quiet func (_e *MockK0sManager_Expecter) Download(version interface{}, force interface{}, quiet interface{}) *MockK0sManager_Download_Call { return &MockK0sManager_Download_Call{Call: _e.mock.On("Download", version, force, quiet)} } func (_c *MockK0sManager_Download_Call) Run(run func(version string, force bool, quiet bool)) *MockK0sManager_Download_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - var arg2 bool - if args[2] != nil { - arg2 = args[2].(bool) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(bool), args[2].(bool)) }) return _c } @@ -859,122 +797,231 @@ func (_c *MockK0sManager_GetLatestVersion_Call) RunAndReturn(run func() (string, return _c } -// Install provides a mock function for the type MockK0sManager -func (_mock *MockK0sManager) Install(configPath string, k0sPath string, force bool, nodeIP string) error { - ret := _mock.Called(configPath, k0sPath, force, nodeIP) +// NewMockK0sctlManager creates a new instance of MockK0sctlManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockK0sctlManager(t interface { + mock.TestingT + Cleanup(func()) +}) *MockK0sctlManager { + mock := &MockK0sctlManager{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockK0sctlManager is an autogenerated mock type for the K0sctlManager type +type MockK0sctlManager struct { + mock.Mock +} + +type MockK0sctlManager_Expecter struct { + mock *mock.Mock +} + +func (_m *MockK0sctlManager) EXPECT() *MockK0sctlManager_Expecter { + return &MockK0sctlManager_Expecter{mock: &_m.Mock} +} + +// Apply provides a mock function for the type MockK0sctlManager +func (_mock *MockK0sctlManager) Apply(configPath string, k0sctlPath string, force bool) error { + ret := _mock.Called(configPath, k0sctlPath, force) if len(ret) == 0 { - panic("no return value specified for Install") + panic("no return value specified for Apply") } var r0 error - if returnFunc, ok := ret.Get(0).(func(string, string, bool, string) error); ok { - r0 = returnFunc(configPath, k0sPath, force, nodeIP) + if returnFunc, ok := ret.Get(0).(func(string, string, bool) error); ok { + r0 = returnFunc(configPath, k0sctlPath, force) } else { r0 = ret.Error(0) } return r0 } -// MockK0sManager_Install_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Install' -type MockK0sManager_Install_Call struct { +// MockK0sctlManager_Apply_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Apply' +type MockK0sctlManager_Apply_Call struct { *mock.Call } -// Install is a helper method to define mock.On call -// - configPath string -// - k0sPath string -// - force bool -// - nodeIP string -func (_e *MockK0sManager_Expecter) Install(configPath interface{}, k0sPath interface{}, force interface{}, nodeIP interface{}) *MockK0sManager_Install_Call { - return &MockK0sManager_Install_Call{Call: _e.mock.On("Install", configPath, k0sPath, force, nodeIP)} +// Apply is a helper method to define mock.On call +// - configPath +// - k0sctlPath +// - force +func (_e *MockK0sctlManager_Expecter) Apply(configPath interface{}, k0sctlPath interface{}, force interface{}) *MockK0sctlManager_Apply_Call { + return &MockK0sctlManager_Apply_Call{Call: _e.mock.On("Apply", configPath, k0sctlPath, force)} } -func (_c *MockK0sManager_Install_Call) Run(run func(configPath string, k0sPath string, force bool, nodeIP string)) *MockK0sManager_Install_Call { +func (_c *MockK0sctlManager_Apply_Call) Run(run func(configPath string, k0sctlPath string, force bool)) *MockK0sctlManager_Apply_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 bool - if args[2] != nil { - arg2 = args[2].(bool) - } - var arg3 string - if args[3] != nil { - arg3 = args[3].(string) - } - run( - arg0, - arg1, - arg2, - arg3, - ) + run(args[0].(string), args[1].(string), args[2].(bool)) }) return _c } -func (_c *MockK0sManager_Install_Call) Return(err error) *MockK0sManager_Install_Call { +func (_c *MockK0sctlManager_Apply_Call) Return(err error) *MockK0sctlManager_Apply_Call { _c.Call.Return(err) return _c } -func (_c *MockK0sManager_Install_Call) RunAndReturn(run func(configPath string, k0sPath string, force bool, nodeIP string) error) *MockK0sManager_Install_Call { +func (_c *MockK0sctlManager_Apply_Call) RunAndReturn(run func(configPath string, k0sctlPath string, force bool) error) *MockK0sctlManager_Apply_Call { + _c.Call.Return(run) + return _c +} + +// Download provides a mock function for the type MockK0sctlManager +func (_mock *MockK0sctlManager) Download(version string, force bool, quiet bool) (string, error) { + ret := _mock.Called(version, force, quiet) + + if len(ret) == 0 { + panic("no return value specified for Download") + } + + var r0 string + var r1 error + if returnFunc, ok := ret.Get(0).(func(string, bool, bool) (string, error)); ok { + return returnFunc(version, force, quiet) + } + if returnFunc, ok := ret.Get(0).(func(string, bool, bool) string); ok { + r0 = returnFunc(version, force, quiet) + } else { + r0 = ret.Get(0).(string) + } + if returnFunc, ok := ret.Get(1).(func(string, bool, bool) error); ok { + r1 = returnFunc(version, force, quiet) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockK0sctlManager_Download_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Download' +type MockK0sctlManager_Download_Call struct { + *mock.Call +} + +// Download is a helper method to define mock.On call +// - version +// - force +// - quiet +func (_e *MockK0sctlManager_Expecter) Download(version interface{}, force interface{}, quiet interface{}) *MockK0sctlManager_Download_Call { + return &MockK0sctlManager_Download_Call{Call: _e.mock.On("Download", version, force, quiet)} +} + +func (_c *MockK0sctlManager_Download_Call) Run(run func(version string, force bool, quiet bool)) *MockK0sctlManager_Download_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(bool), args[2].(bool)) + }) + return _c +} + +func (_c *MockK0sctlManager_Download_Call) Return(s string, err error) *MockK0sctlManager_Download_Call { + _c.Call.Return(s, err) + return _c +} + +func (_c *MockK0sctlManager_Download_Call) RunAndReturn(run func(version string, force bool, quiet bool) (string, error)) *MockK0sctlManager_Download_Call { _c.Call.Return(run) return _c } -// Reset provides a mock function for the type MockK0sManager -func (_mock *MockK0sManager) Reset(k0sPath string) error { - ret := _mock.Called(k0sPath) +// GetLatestVersion provides a mock function for the type MockK0sctlManager +func (_mock *MockK0sctlManager) GetLatestVersion() (string, error) { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for GetLatestVersion") + } + + var r0 string + var r1 error + if returnFunc, ok := ret.Get(0).(func() (string, error)); ok { + return returnFunc() + } + if returnFunc, ok := ret.Get(0).(func() string); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(string) + } + if returnFunc, ok := ret.Get(1).(func() error); ok { + r1 = returnFunc() + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockK0sctlManager_GetLatestVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestVersion' +type MockK0sctlManager_GetLatestVersion_Call struct { + *mock.Call +} + +// GetLatestVersion is a helper method to define mock.On call +func (_e *MockK0sctlManager_Expecter) GetLatestVersion() *MockK0sctlManager_GetLatestVersion_Call { + return &MockK0sctlManager_GetLatestVersion_Call{Call: _e.mock.On("GetLatestVersion")} +} + +func (_c *MockK0sctlManager_GetLatestVersion_Call) Run(run func()) *MockK0sctlManager_GetLatestVersion_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockK0sctlManager_GetLatestVersion_Call) Return(s string, err error) *MockK0sctlManager_GetLatestVersion_Call { + _c.Call.Return(s, err) + return _c +} + +func (_c *MockK0sctlManager_GetLatestVersion_Call) RunAndReturn(run func() (string, error)) *MockK0sctlManager_GetLatestVersion_Call { + _c.Call.Return(run) + return _c +} + +// Reset provides a mock function for the type MockK0sctlManager +func (_mock *MockK0sctlManager) Reset(configPath string, k0sctlPath string) error { + ret := _mock.Called(configPath, k0sctlPath) if len(ret) == 0 { panic("no return value specified for Reset") } var r0 error - if returnFunc, ok := ret.Get(0).(func(string) error); ok { - r0 = returnFunc(k0sPath) + if returnFunc, ok := ret.Get(0).(func(string, string) error); ok { + r0 = returnFunc(configPath, k0sctlPath) } else { r0 = ret.Error(0) } return r0 } -// MockK0sManager_Reset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Reset' -type MockK0sManager_Reset_Call struct { +// MockK0sctlManager_Reset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Reset' +type MockK0sctlManager_Reset_Call struct { *mock.Call } // Reset is a helper method to define mock.On call -// - k0sPath string -func (_e *MockK0sManager_Expecter) Reset(k0sPath interface{}) *MockK0sManager_Reset_Call { - return &MockK0sManager_Reset_Call{Call: _e.mock.On("Reset", k0sPath)} +// - configPath +// - k0sctlPath +func (_e *MockK0sctlManager_Expecter) Reset(configPath interface{}, k0sctlPath interface{}) *MockK0sctlManager_Reset_Call { + return &MockK0sctlManager_Reset_Call{Call: _e.mock.On("Reset", configPath, k0sctlPath)} } -func (_c *MockK0sManager_Reset_Call) Run(run func(k0sPath string)) *MockK0sManager_Reset_Call { +func (_c *MockK0sctlManager_Reset_Call) Run(run func(configPath string, k0sctlPath string)) *MockK0sctlManager_Reset_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string), args[1].(string)) }) return _c } -func (_c *MockK0sManager_Reset_Call) Return(err error) *MockK0sManager_Reset_Call { +func (_c *MockK0sctlManager_Reset_Call) Return(err error) *MockK0sctlManager_Reset_Call { _c.Call.Return(err) return _c } -func (_c *MockK0sManager_Reset_Call) RunAndReturn(run func(k0sPath string) error) *MockK0sManager_Reset_Call { +func (_c *MockK0sctlManager_Reset_Call) RunAndReturn(run func(configPath string, k0sctlPath string) error) *MockK0sctlManager_Reset_Call { _c.Call.Return(run) return _c } @@ -1029,20 +1076,14 @@ type MockPackageManager_Extract_Call struct { } // Extract is a helper method to define mock.On call -// - force bool +// - force func (_e *MockPackageManager_Expecter) Extract(force interface{}) *MockPackageManager_Extract_Call { return &MockPackageManager_Extract_Call{Call: _e.mock.On("Extract", force)} } func (_c *MockPackageManager_Extract_Call) Run(run func(force bool)) *MockPackageManager_Extract_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 bool - if args[0] != nil { - arg0 = args[0].(bool) - } - run( - arg0, - ) + run(args[0].(bool)) }) return _c } @@ -1080,26 +1121,15 @@ type MockPackageManager_ExtractDependency_Call struct { } // ExtractDependency is a helper method to define mock.On call -// - file string -// - force bool +// - file +// - force func (_e *MockPackageManager_Expecter) ExtractDependency(file interface{}, force interface{}) *MockPackageManager_ExtractDependency_Call { return &MockPackageManager_ExtractDependency_Call{Call: _e.mock.On("ExtractDependency", file, force)} } func (_c *MockPackageManager_ExtractDependency_Call) Run(run func(file string, force bool)) *MockPackageManager_ExtractDependency_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -1146,20 +1176,14 @@ type MockPackageManager_ExtractOciImageIndex_Call struct { } // ExtractOciImageIndex is a helper method to define mock.On call -// - imagefile string +// - imagefile func (_e *MockPackageManager_Expecter) ExtractOciImageIndex(imagefile interface{}) *MockPackageManager_ExtractOciImageIndex_Call { return &MockPackageManager_ExtractOciImageIndex_Call{Call: _e.mock.On("ExtractOciImageIndex", imagefile)} } func (_c *MockPackageManager_ExtractOciImageIndex_Call) Run(run func(imagefile string)) *MockPackageManager_ExtractOciImageIndex_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -1252,20 +1276,14 @@ type MockPackageManager_GetBaseimageName_Call struct { } // GetBaseimageName is a helper method to define mock.On call -// - baseimage string +// - baseimage func (_e *MockPackageManager_Expecter) GetBaseimageName(baseimage interface{}) *MockPackageManager_GetBaseimageName_Call { return &MockPackageManager_GetBaseimageName_Call{Call: _e.mock.On("GetBaseimageName", baseimage)} } func (_c *MockPackageManager_GetBaseimageName_Call) Run(run func(baseimage string)) *MockPackageManager_GetBaseimageName_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -1312,26 +1330,15 @@ type MockPackageManager_GetBaseimagePath_Call struct { } // GetBaseimagePath is a helper method to define mock.On call -// - baseimage string -// - force bool +// - baseimage +// - force func (_e *MockPackageManager_Expecter) GetBaseimagePath(baseimage interface{}, force interface{}) *MockPackageManager_GetBaseimagePath_Call { return &MockPackageManager_GetBaseimagePath_Call{Call: _e.mock.On("GetBaseimagePath", baseimage, force)} } func (_c *MockPackageManager_GetBaseimagePath_Call) Run(run func(baseimage string, force bool)) *MockPackageManager_GetBaseimagePath_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -1422,20 +1429,14 @@ type MockPackageManager_GetDependencyPath_Call struct { } // GetDependencyPath is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockPackageManager_Expecter) GetDependencyPath(filename interface{}) *MockPackageManager_GetDependencyPath_Call { return &MockPackageManager_GetDependencyPath_Call{Call: _e.mock.On("GetDependencyPath", filename)} } func (_c *MockPackageManager_GetDependencyPath_Call) Run(run func(filename string)) *MockPackageManager_GetDependencyPath_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 0be5c9e1..cd4dc72d 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -35,11 +35,7 @@ type NodeManager struct { } const ( - jumpboxUser = "ubuntu" - remoteK0sDir = "/usr/local/bin" - remoteK0sConfig = "/etc/k0s/k0s.yaml" - tmpK0sBinary = "/tmp/k0s" - tmpK0sConfig = "/tmp/k0s-config.yaml" + jumpboxUser = "ubuntu" ) func shellEscape(s string) string { @@ -507,69 +503,3 @@ func (n *Node) ConfigureSysctlLine(jumpbox *Node, line string, nm *NodeManager) } return nil } - -func (n *Node) InstallK0s(nm *NodeManager, k0sBinaryPath string, k0sConfigPath string, force bool, nodeIP string) error { - remoteK0sBinary := filepath.Join(remoteK0sDir, "k0s") - remoteConfigPath := remoteK0sConfig - - user := n.User - if user == "" { - user = "root" - } - - // Copy k0s binary to temp location first, then move with sudo - log.Printf("Copying k0s binary to %s:%s", n.ExternalIP, tmpK0sBinary) - if err := nm.CopyFile("", n.ExternalIP, user, k0sBinaryPath, tmpK0sBinary); err != nil { - return fmt.Errorf("failed to copy k0s binary to temp: %w", err) - } - - // Move to final location and make executable with sudo - log.Printf("Moving k0s binary to %s", remoteK0sBinary) - moveCmd := fmt.Sprintf("sudo mv '%s' '%s' && sudo chmod +x '%s'", - shellEscape(tmpK0sBinary), shellEscape(remoteK0sBinary), shellEscape(remoteK0sBinary)) - if err := nm.RunSSHCommand("", n.ExternalIP, user, moveCmd); err != nil { - return fmt.Errorf("failed to move and chmod k0s binary: %w", err) - } - - if k0sConfigPath != "" { - // Copy config to temp location first - log.Printf("Copying k0s config to %s", tmpK0sConfig) - if err := nm.CopyFile("", n.ExternalIP, user, k0sConfigPath, tmpK0sConfig); err != nil { - return fmt.Errorf("failed to copy k0s config to temp: %w", err) - } - - // Create /etc/k0s directory and move config with sudo - log.Printf("Moving k0s config to %s", remoteConfigPath) - setupConfigCmd := fmt.Sprintf("sudo mkdir -p /etc/k0s && sudo mv '%s' '%s' && sudo chmod 644 '%s'", - shellEscape(tmpK0sConfig), shellEscape(remoteConfigPath), shellEscape(remoteConfigPath)) - if err := nm.RunSSHCommand("", n.ExternalIP, user, setupConfigCmd); err != nil { - return fmt.Errorf("failed to setup k0s config: %w", err) - } - } - - installCmd := fmt.Sprintf("sudo '%s' install controller", shellEscape(remoteK0sBinary)) - if k0sConfigPath != "" { - installCmd += fmt.Sprintf(" --config '%s'", shellEscape(remoteConfigPath)) - } else { - installCmd += " --single" - } - - installCmd += " --enable-worker" - installCmd += " --no-taints" - installCmd += fmt.Sprintf(" --kubelet-extra-args='--node-ip=%s'", shellEscape(nodeIP)) - - if force { - installCmd += " --force" - } - - log.Printf("Installing k0s on %s", n.ExternalIP) - if err := nm.RunSSHCommand("", n.ExternalIP, user, installCmd); err != nil { - return fmt.Errorf("failed to install k0s: %w", err) - } - - log.Printf("k0s successfully installed on %s", n.ExternalIP) - log.Printf("You can start it using: ssh %s@%s 'sudo %s start'", user, n.ExternalIP, shellEscape(remoteK0sBinary)) - log.Printf("You can check the status using: ssh %s@%s 'sudo %s status'", user, n.ExternalIP, shellEscape(remoteK0sBinary)) - - return nil -} diff --git a/internal/installer/node/node_test.go b/internal/installer/node/node_test.go index 118997bb..ef1004a3 100644 --- a/internal/installer/node/node_test.go +++ b/internal/installer/node/node_test.go @@ -367,39 +367,5 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== }) }) - Context("InstallK0s", func() { - It("should handle binary copy failure", func() { - k0sBinaryPath := "/path/to/k0s" - mockFileWriter.EXPECT().Open(k0sBinaryPath).Return(nil, errors.New("file not found")).Maybe() - - err := n.InstallK0s(nm, k0sBinaryPath, "", false, "192.168.1.100") - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failed to copy k0s binary to temp")) - }) - - It("should handle paths with special characters safely", func() { - k0sBinaryPath := "/path/to/k0s'; echo 'injected" - - err := n.InstallK0s(nm, k0sBinaryPath, "", false, "192.168.1.100") - Expect(err).To(HaveOccurred()) - }) - - It("should support force flag parameter", func() { - k0sBinaryPath := "/tmp/k0s" - - err := n.InstallK0s(nm, k0sBinaryPath, "", true, "192.168.1.100") - Expect(err).To(HaveOccurred()) - // Will fail to connect, but tests that force flag is handled - }) - - It("should support config file parameter", func() { - k0sBinaryPath := "/tmp/k0s" - k0sConfigPath := "/tmp/k0s.yaml" - - err := n.InstallK0s(nm, k0sBinaryPath, k0sConfigPath, false, "192.168.1.100") - Expect(err).To(HaveOccurred()) - // Will fail to connect, but tests that config path is handled - }) - }) }) }) diff --git a/internal/portal/mocks.go b/internal/portal/mocks.go index c162a3ee..83372369 100644 --- a/internal/portal/mocks.go +++ b/internal/portal/mocks.go @@ -61,32 +61,16 @@ type MockHttp_Download_Call struct { } // Download is a helper method to define mock.On call -// - url string -// - file io.Writer -// - quiet bool +// - url +// - file +// - quiet func (_e *MockHttp_Expecter) Download(url interface{}, file interface{}, quiet interface{}) *MockHttp_Download_Call { return &MockHttp_Download_Call{Call: _e.mock.On("Download", url, file, quiet)} } func (_c *MockHttp_Download_Call) Run(run func(url string, file io.Writer, quiet bool)) *MockHttp_Download_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 io.Writer - if args[1] != nil { - arg1 = args[1].(io.Writer) - } - var arg2 bool - if args[2] != nil { - arg2 = args[2].(bool) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(io.Writer), args[2].(bool)) }) return _c } @@ -135,20 +119,14 @@ type MockHttp_Get_Call struct { } // Get is a helper method to define mock.On call -// - url string +// - url func (_e *MockHttp_Expecter) Get(url interface{}) *MockHttp_Get_Call { return &MockHttp_Get_Call{Call: _e.mock.On("Get", url)} } func (_c *MockHttp_Get_Call) Run(run func(url string)) *MockHttp_Get_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -197,32 +175,16 @@ type MockHttp_Request_Call struct { } // Request is a helper method to define mock.On call -// - url string -// - method string -// - body io.Reader +// - url +// - method +// - body func (_e *MockHttp_Expecter) Request(url interface{}, method interface{}, body interface{}) *MockHttp_Request_Call { return &MockHttp_Request_Call{Call: _e.mock.On("Request", url, method, body)} } func (_c *MockHttp_Request_Call) Run(run func(url string, method string, body io.Reader)) *MockHttp_Request_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 io.Reader - if args[2] != nil { - arg2 = args[2].(io.Reader) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(string), args[2].(io.Reader)) }) return _c } @@ -287,44 +249,18 @@ type MockPortal_DownloadBuildArtifact_Call struct { } // DownloadBuildArtifact is a helper method to define mock.On call -// - product Product -// - build Build -// - file io.Writer -// - startByte int -// - quiet bool +// - product +// - build +// - file +// - startByte +// - quiet func (_e *MockPortal_Expecter) DownloadBuildArtifact(product interface{}, build interface{}, file interface{}, startByte interface{}, quiet interface{}) *MockPortal_DownloadBuildArtifact_Call { return &MockPortal_DownloadBuildArtifact_Call{Call: _e.mock.On("DownloadBuildArtifact", product, build, file, startByte, quiet)} } func (_c *MockPortal_DownloadBuildArtifact_Call) Run(run func(product Product, build Build, file io.Writer, startByte int, quiet bool)) *MockPortal_DownloadBuildArtifact_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 Product - if args[0] != nil { - arg0 = args[0].(Product) - } - var arg1 Build - if args[1] != nil { - arg1 = args[1].(Build) - } - var arg2 io.Writer - if args[2] != nil { - arg2 = args[2].(io.Writer) - } - var arg3 int - if args[3] != nil { - arg3 = args[3].(int) - } - var arg4 bool - if args[4] != nil { - arg4 = args[4].(bool) - } - run( - arg0, - arg1, - arg2, - arg3, - arg4, - ) + run(args[0].(Product), args[1].(Build), args[2].(io.Writer), args[3].(int), args[4].(bool)) }) return _c } @@ -371,20 +307,14 @@ type MockPortal_GetApiKeyId_Call struct { } // GetApiKeyId is a helper method to define mock.On call -// - oldKey string +// - oldKey func (_e *MockPortal_Expecter) GetApiKeyId(oldKey interface{}) *MockPortal_GetApiKeyId_Call { return &MockPortal_GetApiKeyId_Call{Call: _e.mock.On("GetApiKeyId", oldKey)} } func (_c *MockPortal_GetApiKeyId_Call) Run(run func(oldKey string)) *MockPortal_GetApiKeyId_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -431,32 +361,16 @@ type MockPortal_GetBuild_Call struct { } // GetBuild is a helper method to define mock.On call -// - product Product -// - version string -// - hash string +// - product +// - version +// - hash func (_e *MockPortal_Expecter) GetBuild(product interface{}, version interface{}, hash interface{}) *MockPortal_GetBuild_Call { return &MockPortal_GetBuild_Call{Call: _e.mock.On("GetBuild", product, version, hash)} } func (_c *MockPortal_GetBuild_Call) Run(run func(product Product, version string, hash string)) *MockPortal_GetBuild_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 Product - if args[0] != nil { - arg0 = args[0].(Product) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(Product), args[1].(string), args[2].(string)) }) return _c } @@ -558,20 +472,14 @@ type MockPortal_ListBuilds_Call struct { } // ListBuilds is a helper method to define mock.On call -// - product Product +// - product func (_e *MockPortal_Expecter) ListBuilds(product interface{}) *MockPortal_ListBuilds_Call { return &MockPortal_ListBuilds_Call{Call: _e.mock.On("ListBuilds", product)} } func (_c *MockPortal_ListBuilds_Call) Run(run func(product Product)) *MockPortal_ListBuilds_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 Product - if args[0] != nil { - arg0 = args[0].(Product) - } - run( - arg0, - ) + run(args[0].(Product)) }) return _c } @@ -620,38 +528,17 @@ type MockPortal_RegisterAPIKey_Call struct { } // RegisterAPIKey is a helper method to define mock.On call -// - owner string -// - organization string -// - role string -// - expiresAt time.Time +// - owner +// - organization +// - role +// - expiresAt func (_e *MockPortal_Expecter) RegisterAPIKey(owner interface{}, organization interface{}, role interface{}, expiresAt interface{}) *MockPortal_RegisterAPIKey_Call { return &MockPortal_RegisterAPIKey_Call{Call: _e.mock.On("RegisterAPIKey", owner, organization, role, expiresAt)} } func (_c *MockPortal_RegisterAPIKey_Call) Run(run func(owner string, organization string, role string, expiresAt time.Time)) *MockPortal_RegisterAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - var arg3 time.Time - if args[3] != nil { - arg3 = args[3].(time.Time) - } - run( - arg0, - arg1, - arg2, - arg3, - ) + run(args[0].(string), args[1].(string), args[2].(string), args[3].(time.Time)) }) return _c } @@ -689,20 +576,14 @@ type MockPortal_RevokeAPIKey_Call struct { } // RevokeAPIKey is a helper method to define mock.On call -// - key string +// - key func (_e *MockPortal_Expecter) RevokeAPIKey(key interface{}) *MockPortal_RevokeAPIKey_Call { return &MockPortal_RevokeAPIKey_Call{Call: _e.mock.On("RevokeAPIKey", key)} } func (_c *MockPortal_RevokeAPIKey_Call) Run(run func(key string)) *MockPortal_RevokeAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -740,26 +621,15 @@ type MockPortal_UpdateAPIKey_Call struct { } // UpdateAPIKey is a helper method to define mock.On call -// - key string -// - expiresAt time.Time +// - key +// - expiresAt func (_e *MockPortal_Expecter) UpdateAPIKey(key interface{}, expiresAt interface{}) *MockPortal_UpdateAPIKey_Call { return &MockPortal_UpdateAPIKey_Call{Call: _e.mock.On("UpdateAPIKey", key, expiresAt)} } func (_c *MockPortal_UpdateAPIKey_Call) Run(run func(key string, expiresAt time.Time)) *MockPortal_UpdateAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 time.Time - if args[1] != nil { - arg1 = args[1].(time.Time) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(time.Time)) }) return _c } @@ -797,26 +667,15 @@ type MockPortal_VerifyBuildArtifactDownload_Call struct { } // VerifyBuildArtifactDownload is a helper method to define mock.On call -// - file io.Reader -// - download Build +// - file +// - download func (_e *MockPortal_Expecter) VerifyBuildArtifactDownload(file interface{}, download interface{}) *MockPortal_VerifyBuildArtifactDownload_Call { return &MockPortal_VerifyBuildArtifactDownload_Call{Call: _e.mock.On("VerifyBuildArtifactDownload", file, download)} } func (_c *MockPortal_VerifyBuildArtifactDownload_Call) Run(run func(file io.Reader, download Build)) *MockPortal_VerifyBuildArtifactDownload_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 io.Reader - if args[0] != nil { - arg0 = args[0].(io.Reader) - } - var arg1 Build - if args[1] != nil { - arg1 = args[1].(Build) - } - run( - arg0, - arg1, - ) + run(args[0].(io.Reader), args[1].(Build)) }) return _c } @@ -892,20 +751,14 @@ type MockHttpClient_Do_Call struct { } // Do is a helper method to define mock.On call -// - request *http.Request +// - request func (_e *MockHttpClient_Expecter) Do(request interface{}) *MockHttpClient_Do_Call { return &MockHttpClient_Do_Call{Call: _e.mock.On("Do", request)} } func (_c *MockHttpClient_Do_Call) Run(run func(request *http.Request)) *MockHttpClient_Do_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 *http.Request - if args[0] != nil { - arg0 = args[0].(*http.Request) - } - run( - arg0, - ) + run(args[0].(*http.Request)) }) return _c } diff --git a/internal/system/mocks.go b/internal/system/mocks.go index ee80bc69..cf8e9292 100644 --- a/internal/system/mocks.go +++ b/internal/system/mocks.go @@ -58,32 +58,16 @@ type MockImageManager_BuildImage_Call struct { } // BuildImage is a helper method to define mock.On call -// - dockerfile string -// - tag string -// - buildContext string +// - dockerfile +// - tag +// - buildContext func (_e *MockImageManager_Expecter) BuildImage(dockerfile interface{}, tag interface{}, buildContext interface{}) *MockImageManager_BuildImage_Call { return &MockImageManager_BuildImage_Call{Call: _e.mock.On("BuildImage", dockerfile, tag, buildContext)} } func (_c *MockImageManager_BuildImage_Call) Run(run func(dockerfile string, tag string, buildContext string)) *MockImageManager_BuildImage_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(string), args[2].(string)) }) return _c } @@ -121,20 +105,14 @@ type MockImageManager_LoadImage_Call struct { } // LoadImage is a helper method to define mock.On call -// - imageTarPath string +// - imageTarPath func (_e *MockImageManager_Expecter) LoadImage(imageTarPath interface{}) *MockImageManager_LoadImage_Call { return &MockImageManager_LoadImage_Call{Call: _e.mock.On("LoadImage", imageTarPath)} } func (_c *MockImageManager_LoadImage_Call) Run(run func(imageTarPath string)) *MockImageManager_LoadImage_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -172,20 +150,14 @@ type MockImageManager_PushImage_Call struct { } // PushImage is a helper method to define mock.On call -// - tag string +// - tag func (_e *MockImageManager_Expecter) PushImage(tag interface{}) *MockImageManager_PushImage_Call { return &MockImageManager_PushImage_Call{Call: _e.mock.On("PushImage", tag)} } func (_c *MockImageManager_PushImage_Call) Run(run func(tag string)) *MockImageManager_PushImage_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } diff --git a/internal/util/mocks.go b/internal/util/mocks.go index abfdd4f8..394f6b82 100644 --- a/internal/util/mocks.go +++ b/internal/util/mocks.go @@ -70,26 +70,15 @@ type MockDockerfileManager_UpdateFromStatement_Call struct { } // UpdateFromStatement is a helper method to define mock.On call -// - dockerfile io.Reader -// - baseImage string +// - dockerfile +// - baseImage func (_e *MockDockerfileManager_Expecter) UpdateFromStatement(dockerfile interface{}, baseImage interface{}) *MockDockerfileManager_UpdateFromStatement_Call { return &MockDockerfileManager_UpdateFromStatement_Call{Call: _e.mock.On("UpdateFromStatement", dockerfile, baseImage)} } func (_c *MockDockerfileManager_UpdateFromStatement_Call) Run(run func(dockerfile io.Reader, baseImage string)) *MockDockerfileManager_UpdateFromStatement_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 io.Reader - if args[0] != nil { - arg0 = args[0].(io.Reader) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - run( - arg0, - arg1, - ) + run(args[0].(io.Reader), args[1].(string)) }) return _c } @@ -165,20 +154,14 @@ type MockFileIO_Create_Call struct { } // Create is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) Create(filename interface{}) *MockFileIO_Create_Call { return &MockFileIO_Create_Call{Call: _e.mock.On("Create", filename)} } func (_c *MockFileIO_Create_Call) Run(run func(filename string)) *MockFileIO_Create_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -216,32 +199,16 @@ type MockFileIO_CreateAndWrite_Call struct { } // CreateAndWrite is a helper method to define mock.On call -// - filePath string -// - data []byte -// - fileType string +// - filePath +// - data +// - fileType func (_e *MockFileIO_Expecter) CreateAndWrite(filePath interface{}, data interface{}, fileType interface{}) *MockFileIO_CreateAndWrite_Call { return &MockFileIO_CreateAndWrite_Call{Call: _e.mock.On("CreateAndWrite", filePath, data, fileType)} } func (_c *MockFileIO_CreateAndWrite_Call) Run(run func(filePath string, data []byte, fileType string)) *MockFileIO_CreateAndWrite_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 []byte - if args[1] != nil { - arg1 = args[1].([]byte) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].([]byte), args[2].(string)) }) return _c } @@ -279,20 +246,14 @@ type MockFileIO_Exists_Call struct { } // Exists is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) Exists(filename interface{}) *MockFileIO_Exists_Call { return &MockFileIO_Exists_Call{Call: _e.mock.On("Exists", filename)} } func (_c *MockFileIO_Exists_Call) Run(run func(filename string)) *MockFileIO_Exists_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -339,20 +300,14 @@ type MockFileIO_IsDirectory_Call struct { } // IsDirectory is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) IsDirectory(filename interface{}) *MockFileIO_IsDirectory_Call { return &MockFileIO_IsDirectory_Call{Call: _e.mock.On("IsDirectory", filename)} } func (_c *MockFileIO_IsDirectory_Call) Run(run func(filename string)) *MockFileIO_IsDirectory_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -390,26 +345,15 @@ type MockFileIO_MkdirAll_Call struct { } // MkdirAll is a helper method to define mock.On call -// - path string -// - perm os.FileMode +// - path +// - perm func (_e *MockFileIO_Expecter) MkdirAll(path interface{}, perm interface{}) *MockFileIO_MkdirAll_Call { return &MockFileIO_MkdirAll_Call{Call: _e.mock.On("MkdirAll", path, perm)} } func (_c *MockFileIO_MkdirAll_Call) Run(run func(path string, perm os.FileMode)) *MockFileIO_MkdirAll_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 os.FileMode - if args[1] != nil { - arg1 = args[1].(os.FileMode) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(os.FileMode)) }) return _c } @@ -458,20 +402,14 @@ type MockFileIO_Open_Call struct { } // Open is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) Open(filename interface{}) *MockFileIO_Open_Call { return &MockFileIO_Open_Call{Call: _e.mock.On("Open", filename)} } func (_c *MockFileIO_Open_Call) Run(run func(filename string)) *MockFileIO_Open_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -520,20 +458,14 @@ type MockFileIO_OpenAppend_Call struct { } // OpenAppend is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) OpenAppend(filename interface{}) *MockFileIO_OpenAppend_Call { return &MockFileIO_OpenAppend_Call{Call: _e.mock.On("OpenAppend", filename)} } func (_c *MockFileIO_OpenAppend_Call) Run(run func(filename string)) *MockFileIO_OpenAppend_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -582,32 +514,16 @@ type MockFileIO_OpenFile_Call struct { } // OpenFile is a helper method to define mock.On call -// - name string -// - flag int -// - perm os.FileMode +// - name +// - flag +// - perm func (_e *MockFileIO_Expecter) OpenFile(name interface{}, flag interface{}, perm interface{}) *MockFileIO_OpenFile_Call { return &MockFileIO_OpenFile_Call{Call: _e.mock.On("OpenFile", name, flag, perm)} } func (_c *MockFileIO_OpenFile_Call) Run(run func(name string, flag int, perm os.FileMode)) *MockFileIO_OpenFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 int - if args[1] != nil { - arg1 = args[1].(int) - } - var arg2 os.FileMode - if args[2] != nil { - arg2 = args[2].(os.FileMode) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(int), args[2].(os.FileMode)) }) return _c } @@ -656,20 +572,14 @@ type MockFileIO_ReadDir_Call struct { } // ReadDir is a helper method to define mock.On call -// - dirname string +// - dirname func (_e *MockFileIO_Expecter) ReadDir(dirname interface{}) *MockFileIO_ReadDir_Call { return &MockFileIO_ReadDir_Call{Call: _e.mock.On("ReadDir", dirname)} } func (_c *MockFileIO_ReadDir_Call) Run(run func(dirname string)) *MockFileIO_ReadDir_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -718,20 +628,14 @@ type MockFileIO_ReadFile_Call struct { } // ReadFile is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) ReadFile(filename interface{}) *MockFileIO_ReadFile_Call { return &MockFileIO_ReadFile_Call{Call: _e.mock.On("ReadFile", filename)} } func (_c *MockFileIO_ReadFile_Call) Run(run func(filename string)) *MockFileIO_ReadFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -769,32 +673,16 @@ type MockFileIO_WriteFile_Call struct { } // WriteFile is a helper method to define mock.On call -// - filename string -// - data []byte -// - perm os.FileMode +// - filename +// - data +// - perm func (_e *MockFileIO_Expecter) WriteFile(filename interface{}, data interface{}, perm interface{}) *MockFileIO_WriteFile_Call { return &MockFileIO_WriteFile_Call{Call: _e.mock.On("WriteFile", filename, data, perm)} } func (_c *MockFileIO_WriteFile_Call) Run(run func(filename string, data []byte, perm os.FileMode)) *MockFileIO_WriteFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 []byte - if args[1] != nil { - arg1 = args[1].([]byte) - } - var arg2 os.FileMode - if args[2] != nil { - arg2 = args[2].(os.FileMode) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].([]byte), args[2].(os.FileMode)) }) return _c } @@ -924,8 +812,8 @@ type MockTableWriter_AppendHeader_Call struct { } // AppendHeader is a helper method to define mock.On call -// - row table.Row -// - configs ...table.RowConfig +// - row +// - configs func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...interface{}) *MockTableWriter_AppendHeader_Call { return &MockTableWriter_AppendHeader_Call{Call: _e.mock.On("AppendHeader", append([]interface{}{row}, configs...)...)} @@ -933,20 +821,13 @@ func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...int func (_c *MockTableWriter_AppendHeader_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendHeader_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 table.Row - if args[0] != nil { - arg0 = args[0].(table.Row) + variadicArgs := make([]table.RowConfig, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(table.RowConfig) + } } - var arg1 []table.RowConfig - var variadicArgs []table.RowConfig - if len(args) > 1 { - variadicArgs = args[1].([]table.RowConfig) - } - arg1 = variadicArgs - run( - arg0, - arg1..., - ) + run(args[0].(table.Row), variadicArgs...) }) return _c } @@ -978,8 +859,8 @@ type MockTableWriter_AppendRow_Call struct { } // AppendRow is a helper method to define mock.On call -// - row table.Row -// - configs ...table.RowConfig +// - row +// - configs func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interface{}) *MockTableWriter_AppendRow_Call { return &MockTableWriter_AppendRow_Call{Call: _e.mock.On("AppendRow", append([]interface{}{row}, configs...)...)} @@ -987,20 +868,13 @@ func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interf func (_c *MockTableWriter_AppendRow_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendRow_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 table.Row - if args[0] != nil { - arg0 = args[0].(table.Row) - } - var arg1 []table.RowConfig - var variadicArgs []table.RowConfig - if len(args) > 1 { - variadicArgs = args[1].([]table.RowConfig) + variadicArgs := make([]table.RowConfig, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(table.RowConfig) + } } - arg1 = variadicArgs - run( - arg0, - arg1..., - ) + run(args[0].(table.Row), variadicArgs...) }) return _c } From 8cb070924ac302fcce20766e774426cee7192a82 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:24:41 +0000 Subject: [PATCH 44/65] chore(docs): Auto-update docs and licenses Signed-off-by: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> --- cli/cmd/mocks.go | 24 +++- docs/oms-cli_install_k0s.md | 24 ++-- internal/installer/mocks.go | 237 +++++++++++++++++++++++++++++------- internal/portal/mocks.go | 225 ++++++++++++++++++++++++++++------ internal/system/mocks.go | 44 +++++-- internal/util/mocks.go | 222 +++++++++++++++++++++++++-------- 6 files changed, 623 insertions(+), 153 deletions(-) diff --git a/cli/cmd/mocks.go b/cli/cmd/mocks.go index 60357284..26b37df5 100644 --- a/cli/cmd/mocks.go +++ b/cli/cmd/mocks.go @@ -75,16 +75,32 @@ type MockOMSUpdater_Update_Call struct { } // Update is a helper method to define mock.On call -// - ctx -// - current -// - repo +// - ctx context.Context +// - current string +// - repo selfupdate.Repository func (_e *MockOMSUpdater_Expecter) Update(ctx interface{}, current interface{}, repo interface{}) *MockOMSUpdater_Update_Call { return &MockOMSUpdater_Update_Call{Call: _e.mock.On("Update", ctx, current, repo)} } func (_c *MockOMSUpdater_Update_Call) Run(run func(ctx context.Context, current string, repo selfupdate.Repository)) *MockOMSUpdater_Update_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(selfupdate.Repository)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 selfupdate.Repository + if args[2] != nil { + arg2 = args[2].(selfupdate.Repository) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } diff --git a/docs/oms-cli_install_k0s.md b/docs/oms-cli_install_k0s.md index b9a44593..023d66da 100644 --- a/docs/oms-cli_install_k0s.md +++ b/docs/oms-cli_install_k0s.md @@ -5,13 +5,12 @@ Install k0s Kubernetes distribution ### Synopsis Install k0s either from the package or by downloading it. -This will either download the k0s binary directly to the OMS workdir, if not already present, and install it -or load the k0s binary from the provided package file and install it. -If no version is specified, the latest version will be downloaded. +This command uses k0sctl to deploy k0s clusters from a Codesphere install-config. You must provide a Codesphere install-config file, which will: - Generate a k0s configuration from the install-config -- Optionally install k0s on remote nodes via SSH +- Generate a k0sctl configuration for cluster deployment +- Deploy k0s to all nodes defined in the install-config using k0sctl ``` oms-cli install k0s [flags] @@ -23,21 +22,24 @@ oms-cli install k0s [flags] # Path to Codesphere install-config file to generate k0s config from $ oms-cli install k0s --install-config -# Version of k0s to install +# Version of k0s to install (e.g., v1.30.0+k0s.0) $ oms-cli install k0s --version +# Version of k0sctl to use (e.g., v0.17.4) +$ oms-cli install k0s --k0sctl-version + # Package file (e.g. codesphere-v1.2.3-installer.tar.gz) to load k0s from $ oms-cli install k0s --package -# Remote host IP to install k0s on (requires --ssh-key-path) -$ oms-cli install k0s --remote-host - # SSH private key path for remote installation $ oms-cli install k0s --ssh-key-path -# Force new download and installation even if k0s binary exists or is already installed +# Force new download and installation $ oms-cli install k0s --force +# Skip downloading k0s binary (expects it to be on remote nodes) +$ oms-cli install k0s --no-download + ``` ### Options @@ -46,9 +48,9 @@ $ oms-cli install k0s --force -f, --force Force new download and installation -h, --help help for k0s --install-config string Path to Codesphere install-config file (required) + --k0sctl-version string Version of k0sctl to use + --no-download Skip downloading k0s binary -p, --package string Package file (e.g. codesphere-v1.2.3-installer.tar.gz) to load k0s from - --remote-host string Remote host IP to install k0s on - --remote-user string Remote user for SSH connection (default "root") --ssh-key-path string SSH private key path for remote installation -v, --version string Version of k0s to install ``` diff --git a/internal/installer/mocks.go b/internal/installer/mocks.go index cf9131fc..0bf9fda6 100644 --- a/internal/installer/mocks.go +++ b/internal/installer/mocks.go @@ -69,14 +69,20 @@ type MockConfigManager_ParseConfigYaml_Call struct { } // ParseConfigYaml is a helper method to define mock.On call -// - configPath +// - configPath string func (_e *MockConfigManager_Expecter) ParseConfigYaml(configPath interface{}) *MockConfigManager_ParseConfigYaml_Call { return &MockConfigManager_ParseConfigYaml_Call{Call: _e.mock.On("ParseConfigYaml", configPath)} } func (_c *MockConfigManager_ParseConfigYaml_Call) Run(run func(configPath string)) *MockConfigManager_ParseConfigYaml_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -141,14 +147,20 @@ type MockInstallConfigManager_ApplyProfile_Call struct { } // ApplyProfile is a helper method to define mock.On call -// - profile +// - profile string func (_e *MockInstallConfigManager_Expecter) ApplyProfile(profile interface{}) *MockInstallConfigManager_ApplyProfile_Call { return &MockInstallConfigManager_ApplyProfile_Call{Call: _e.mock.On("ApplyProfile", profile)} } func (_c *MockInstallConfigManager_ApplyProfile_Call) Run(run func(profile string)) *MockInstallConfigManager_ApplyProfile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -366,14 +378,20 @@ type MockInstallConfigManager_LoadInstallConfigFromFile_Call struct { } // LoadInstallConfigFromFile is a helper method to define mock.On call -// - configPath +// - configPath string func (_e *MockInstallConfigManager_Expecter) LoadInstallConfigFromFile(configPath interface{}) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { return &MockInstallConfigManager_LoadInstallConfigFromFile_Call{Call: _e.mock.On("LoadInstallConfigFromFile", configPath)} } func (_c *MockInstallConfigManager_LoadInstallConfigFromFile_Call) Run(run func(configPath string)) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -411,14 +429,20 @@ type MockInstallConfigManager_LoadVaultFromFile_Call struct { } // LoadVaultFromFile is a helper method to define mock.On call -// - vaultPath +// - vaultPath string func (_e *MockInstallConfigManager_Expecter) LoadVaultFromFile(vaultPath interface{}) *MockInstallConfigManager_LoadVaultFromFile_Call { return &MockInstallConfigManager_LoadVaultFromFile_Call{Call: _e.mock.On("LoadVaultFromFile", vaultPath)} } func (_c *MockInstallConfigManager_LoadVaultFromFile_Call) Run(run func(vaultPath string)) *MockInstallConfigManager_LoadVaultFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -592,15 +616,26 @@ type MockInstallConfigManager_WriteInstallConfig_Call struct { } // WriteInstallConfig is a helper method to define mock.On call -// - configPath -// - withComments +// - configPath string +// - withComments bool func (_e *MockInstallConfigManager_Expecter) WriteInstallConfig(configPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteInstallConfig_Call { return &MockInstallConfigManager_WriteInstallConfig_Call{Call: _e.mock.On("WriteInstallConfig", configPath, withComments)} } func (_c *MockInstallConfigManager_WriteInstallConfig_Call) Run(run func(configPath string, withComments bool)) *MockInstallConfigManager_WriteInstallConfig_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -638,15 +673,26 @@ type MockInstallConfigManager_WriteVault_Call struct { } // WriteVault is a helper method to define mock.On call -// - vaultPath -// - withComments +// - vaultPath string +// - withComments bool func (_e *MockInstallConfigManager_Expecter) WriteVault(vaultPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteVault_Call { return &MockInstallConfigManager_WriteVault_Call{Call: _e.mock.On("WriteVault", vaultPath, withComments)} } func (_c *MockInstallConfigManager_WriteVault_Call) Run(run func(vaultPath string, withComments bool)) *MockInstallConfigManager_WriteVault_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -720,16 +766,32 @@ type MockK0sManager_Download_Call struct { } // Download is a helper method to define mock.On call -// - version -// - force -// - quiet +// - version string +// - force bool +// - quiet bool func (_e *MockK0sManager_Expecter) Download(version interface{}, force interface{}, quiet interface{}) *MockK0sManager_Download_Call { return &MockK0sManager_Download_Call{Call: _e.mock.On("Download", version, force, quiet)} } func (_c *MockK0sManager_Download_Call) Run(run func(version string, force bool, quiet bool)) *MockK0sManager_Download_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool), args[2].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + var arg2 bool + if args[2] != nil { + arg2 = args[2].(bool) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -847,16 +909,32 @@ type MockK0sctlManager_Apply_Call struct { } // Apply is a helper method to define mock.On call -// - configPath -// - k0sctlPath -// - force +// - configPath string +// - k0sctlPath string +// - force bool func (_e *MockK0sctlManager_Expecter) Apply(configPath interface{}, k0sctlPath interface{}, force interface{}) *MockK0sctlManager_Apply_Call { return &MockK0sctlManager_Apply_Call{Call: _e.mock.On("Apply", configPath, k0sctlPath, force)} } func (_c *MockK0sctlManager_Apply_Call) Run(run func(configPath string, k0sctlPath string, force bool)) *MockK0sctlManager_Apply_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 bool + if args[2] != nil { + arg2 = args[2].(bool) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -903,16 +981,32 @@ type MockK0sctlManager_Download_Call struct { } // Download is a helper method to define mock.On call -// - version -// - force -// - quiet +// - version string +// - force bool +// - quiet bool func (_e *MockK0sctlManager_Expecter) Download(version interface{}, force interface{}, quiet interface{}) *MockK0sctlManager_Download_Call { return &MockK0sctlManager_Download_Call{Call: _e.mock.On("Download", version, force, quiet)} } func (_c *MockK0sctlManager_Download_Call) Run(run func(version string, force bool, quiet bool)) *MockK0sctlManager_Download_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool), args[2].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + var arg2 bool + if args[2] != nil { + arg2 = args[2].(bool) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -1003,15 +1097,26 @@ type MockK0sctlManager_Reset_Call struct { } // Reset is a helper method to define mock.On call -// - configPath -// - k0sctlPath +// - configPath string +// - k0sctlPath string func (_e *MockK0sctlManager_Expecter) Reset(configPath interface{}, k0sctlPath interface{}) *MockK0sctlManager_Reset_Call { return &MockK0sctlManager_Reset_Call{Call: _e.mock.On("Reset", configPath, k0sctlPath)} } func (_c *MockK0sctlManager_Reset_Call) Run(run func(configPath string, k0sctlPath string)) *MockK0sctlManager_Reset_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } @@ -1076,14 +1181,20 @@ type MockPackageManager_Extract_Call struct { } // Extract is a helper method to define mock.On call -// - force +// - force bool func (_e *MockPackageManager_Expecter) Extract(force interface{}) *MockPackageManager_Extract_Call { return &MockPackageManager_Extract_Call{Call: _e.mock.On("Extract", force)} } func (_c *MockPackageManager_Extract_Call) Run(run func(force bool)) *MockPackageManager_Extract_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(bool)) + var arg0 bool + if args[0] != nil { + arg0 = args[0].(bool) + } + run( + arg0, + ) }) return _c } @@ -1121,15 +1232,26 @@ type MockPackageManager_ExtractDependency_Call struct { } // ExtractDependency is a helper method to define mock.On call -// - file -// - force +// - file string +// - force bool func (_e *MockPackageManager_Expecter) ExtractDependency(file interface{}, force interface{}) *MockPackageManager_ExtractDependency_Call { return &MockPackageManager_ExtractDependency_Call{Call: _e.mock.On("ExtractDependency", file, force)} } func (_c *MockPackageManager_ExtractDependency_Call) Run(run func(file string, force bool)) *MockPackageManager_ExtractDependency_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -1176,14 +1298,20 @@ type MockPackageManager_ExtractOciImageIndex_Call struct { } // ExtractOciImageIndex is a helper method to define mock.On call -// - imagefile +// - imagefile string func (_e *MockPackageManager_Expecter) ExtractOciImageIndex(imagefile interface{}) *MockPackageManager_ExtractOciImageIndex_Call { return &MockPackageManager_ExtractOciImageIndex_Call{Call: _e.mock.On("ExtractOciImageIndex", imagefile)} } func (_c *MockPackageManager_ExtractOciImageIndex_Call) Run(run func(imagefile string)) *MockPackageManager_ExtractOciImageIndex_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -1276,14 +1404,20 @@ type MockPackageManager_GetBaseimageName_Call struct { } // GetBaseimageName is a helper method to define mock.On call -// - baseimage +// - baseimage string func (_e *MockPackageManager_Expecter) GetBaseimageName(baseimage interface{}) *MockPackageManager_GetBaseimageName_Call { return &MockPackageManager_GetBaseimageName_Call{Call: _e.mock.On("GetBaseimageName", baseimage)} } func (_c *MockPackageManager_GetBaseimageName_Call) Run(run func(baseimage string)) *MockPackageManager_GetBaseimageName_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -1330,15 +1464,26 @@ type MockPackageManager_GetBaseimagePath_Call struct { } // GetBaseimagePath is a helper method to define mock.On call -// - baseimage -// - force +// - baseimage string +// - force bool func (_e *MockPackageManager_Expecter) GetBaseimagePath(baseimage interface{}, force interface{}) *MockPackageManager_GetBaseimagePath_Call { return &MockPackageManager_GetBaseimagePath_Call{Call: _e.mock.On("GetBaseimagePath", baseimage, force)} } func (_c *MockPackageManager_GetBaseimagePath_Call) Run(run func(baseimage string, force bool)) *MockPackageManager_GetBaseimagePath_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -1429,14 +1574,20 @@ type MockPackageManager_GetDependencyPath_Call struct { } // GetDependencyPath is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockPackageManager_Expecter) GetDependencyPath(filename interface{}) *MockPackageManager_GetDependencyPath_Call { return &MockPackageManager_GetDependencyPath_Call{Call: _e.mock.On("GetDependencyPath", filename)} } func (_c *MockPackageManager_GetDependencyPath_Call) Run(run func(filename string)) *MockPackageManager_GetDependencyPath_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } diff --git a/internal/portal/mocks.go b/internal/portal/mocks.go index 83372369..c162a3ee 100644 --- a/internal/portal/mocks.go +++ b/internal/portal/mocks.go @@ -61,16 +61,32 @@ type MockHttp_Download_Call struct { } // Download is a helper method to define mock.On call -// - url -// - file -// - quiet +// - url string +// - file io.Writer +// - quiet bool func (_e *MockHttp_Expecter) Download(url interface{}, file interface{}, quiet interface{}) *MockHttp_Download_Call { return &MockHttp_Download_Call{Call: _e.mock.On("Download", url, file, quiet)} } func (_c *MockHttp_Download_Call) Run(run func(url string, file io.Writer, quiet bool)) *MockHttp_Download_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(io.Writer), args[2].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 io.Writer + if args[1] != nil { + arg1 = args[1].(io.Writer) + } + var arg2 bool + if args[2] != nil { + arg2 = args[2].(bool) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -119,14 +135,20 @@ type MockHttp_Get_Call struct { } // Get is a helper method to define mock.On call -// - url +// - url string func (_e *MockHttp_Expecter) Get(url interface{}) *MockHttp_Get_Call { return &MockHttp_Get_Call{Call: _e.mock.On("Get", url)} } func (_c *MockHttp_Get_Call) Run(run func(url string)) *MockHttp_Get_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -175,16 +197,32 @@ type MockHttp_Request_Call struct { } // Request is a helper method to define mock.On call -// - url -// - method -// - body +// - url string +// - method string +// - body io.Reader func (_e *MockHttp_Expecter) Request(url interface{}, method interface{}, body interface{}) *MockHttp_Request_Call { return &MockHttp_Request_Call{Call: _e.mock.On("Request", url, method, body)} } func (_c *MockHttp_Request_Call) Run(run func(url string, method string, body io.Reader)) *MockHttp_Request_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(io.Reader)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 io.Reader + if args[2] != nil { + arg2 = args[2].(io.Reader) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -249,18 +287,44 @@ type MockPortal_DownloadBuildArtifact_Call struct { } // DownloadBuildArtifact is a helper method to define mock.On call -// - product -// - build -// - file -// - startByte -// - quiet +// - product Product +// - build Build +// - file io.Writer +// - startByte int +// - quiet bool func (_e *MockPortal_Expecter) DownloadBuildArtifact(product interface{}, build interface{}, file interface{}, startByte interface{}, quiet interface{}) *MockPortal_DownloadBuildArtifact_Call { return &MockPortal_DownloadBuildArtifact_Call{Call: _e.mock.On("DownloadBuildArtifact", product, build, file, startByte, quiet)} } func (_c *MockPortal_DownloadBuildArtifact_Call) Run(run func(product Product, build Build, file io.Writer, startByte int, quiet bool)) *MockPortal_DownloadBuildArtifact_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(Product), args[1].(Build), args[2].(io.Writer), args[3].(int), args[4].(bool)) + var arg0 Product + if args[0] != nil { + arg0 = args[0].(Product) + } + var arg1 Build + if args[1] != nil { + arg1 = args[1].(Build) + } + var arg2 io.Writer + if args[2] != nil { + arg2 = args[2].(io.Writer) + } + var arg3 int + if args[3] != nil { + arg3 = args[3].(int) + } + var arg4 bool + if args[4] != nil { + arg4 = args[4].(bool) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) }) return _c } @@ -307,14 +371,20 @@ type MockPortal_GetApiKeyId_Call struct { } // GetApiKeyId is a helper method to define mock.On call -// - oldKey +// - oldKey string func (_e *MockPortal_Expecter) GetApiKeyId(oldKey interface{}) *MockPortal_GetApiKeyId_Call { return &MockPortal_GetApiKeyId_Call{Call: _e.mock.On("GetApiKeyId", oldKey)} } func (_c *MockPortal_GetApiKeyId_Call) Run(run func(oldKey string)) *MockPortal_GetApiKeyId_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -361,16 +431,32 @@ type MockPortal_GetBuild_Call struct { } // GetBuild is a helper method to define mock.On call -// - product -// - version -// - hash +// - product Product +// - version string +// - hash string func (_e *MockPortal_Expecter) GetBuild(product interface{}, version interface{}, hash interface{}) *MockPortal_GetBuild_Call { return &MockPortal_GetBuild_Call{Call: _e.mock.On("GetBuild", product, version, hash)} } func (_c *MockPortal_GetBuild_Call) Run(run func(product Product, version string, hash string)) *MockPortal_GetBuild_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(Product), args[1].(string), args[2].(string)) + var arg0 Product + if args[0] != nil { + arg0 = args[0].(Product) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -472,14 +558,20 @@ type MockPortal_ListBuilds_Call struct { } // ListBuilds is a helper method to define mock.On call -// - product +// - product Product func (_e *MockPortal_Expecter) ListBuilds(product interface{}) *MockPortal_ListBuilds_Call { return &MockPortal_ListBuilds_Call{Call: _e.mock.On("ListBuilds", product)} } func (_c *MockPortal_ListBuilds_Call) Run(run func(product Product)) *MockPortal_ListBuilds_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(Product)) + var arg0 Product + if args[0] != nil { + arg0 = args[0].(Product) + } + run( + arg0, + ) }) return _c } @@ -528,17 +620,38 @@ type MockPortal_RegisterAPIKey_Call struct { } // RegisterAPIKey is a helper method to define mock.On call -// - owner -// - organization -// - role -// - expiresAt +// - owner string +// - organization string +// - role string +// - expiresAt time.Time func (_e *MockPortal_Expecter) RegisterAPIKey(owner interface{}, organization interface{}, role interface{}, expiresAt interface{}) *MockPortal_RegisterAPIKey_Call { return &MockPortal_RegisterAPIKey_Call{Call: _e.mock.On("RegisterAPIKey", owner, organization, role, expiresAt)} } func (_c *MockPortal_RegisterAPIKey_Call) Run(run func(owner string, organization string, role string, expiresAt time.Time)) *MockPortal_RegisterAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(string), args[3].(time.Time)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 time.Time + if args[3] != nil { + arg3 = args[3].(time.Time) + } + run( + arg0, + arg1, + arg2, + arg3, + ) }) return _c } @@ -576,14 +689,20 @@ type MockPortal_RevokeAPIKey_Call struct { } // RevokeAPIKey is a helper method to define mock.On call -// - key +// - key string func (_e *MockPortal_Expecter) RevokeAPIKey(key interface{}) *MockPortal_RevokeAPIKey_Call { return &MockPortal_RevokeAPIKey_Call{Call: _e.mock.On("RevokeAPIKey", key)} } func (_c *MockPortal_RevokeAPIKey_Call) Run(run func(key string)) *MockPortal_RevokeAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -621,15 +740,26 @@ type MockPortal_UpdateAPIKey_Call struct { } // UpdateAPIKey is a helper method to define mock.On call -// - key -// - expiresAt +// - key string +// - expiresAt time.Time func (_e *MockPortal_Expecter) UpdateAPIKey(key interface{}, expiresAt interface{}) *MockPortal_UpdateAPIKey_Call { return &MockPortal_UpdateAPIKey_Call{Call: _e.mock.On("UpdateAPIKey", key, expiresAt)} } func (_c *MockPortal_UpdateAPIKey_Call) Run(run func(key string, expiresAt time.Time)) *MockPortal_UpdateAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(time.Time)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 time.Time + if args[1] != nil { + arg1 = args[1].(time.Time) + } + run( + arg0, + arg1, + ) }) return _c } @@ -667,15 +797,26 @@ type MockPortal_VerifyBuildArtifactDownload_Call struct { } // VerifyBuildArtifactDownload is a helper method to define mock.On call -// - file -// - download +// - file io.Reader +// - download Build func (_e *MockPortal_Expecter) VerifyBuildArtifactDownload(file interface{}, download interface{}) *MockPortal_VerifyBuildArtifactDownload_Call { return &MockPortal_VerifyBuildArtifactDownload_Call{Call: _e.mock.On("VerifyBuildArtifactDownload", file, download)} } func (_c *MockPortal_VerifyBuildArtifactDownload_Call) Run(run func(file io.Reader, download Build)) *MockPortal_VerifyBuildArtifactDownload_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(io.Reader), args[1].(Build)) + var arg0 io.Reader + if args[0] != nil { + arg0 = args[0].(io.Reader) + } + var arg1 Build + if args[1] != nil { + arg1 = args[1].(Build) + } + run( + arg0, + arg1, + ) }) return _c } @@ -751,14 +892,20 @@ type MockHttpClient_Do_Call struct { } // Do is a helper method to define mock.On call -// - request +// - request *http.Request func (_e *MockHttpClient_Expecter) Do(request interface{}) *MockHttpClient_Do_Call { return &MockHttpClient_Do_Call{Call: _e.mock.On("Do", request)} } func (_c *MockHttpClient_Do_Call) Run(run func(request *http.Request)) *MockHttpClient_Do_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*http.Request)) + var arg0 *http.Request + if args[0] != nil { + arg0 = args[0].(*http.Request) + } + run( + arg0, + ) }) return _c } diff --git a/internal/system/mocks.go b/internal/system/mocks.go index cf8e9292..ee80bc69 100644 --- a/internal/system/mocks.go +++ b/internal/system/mocks.go @@ -58,16 +58,32 @@ type MockImageManager_BuildImage_Call struct { } // BuildImage is a helper method to define mock.On call -// - dockerfile -// - tag -// - buildContext +// - dockerfile string +// - tag string +// - buildContext string func (_e *MockImageManager_Expecter) BuildImage(dockerfile interface{}, tag interface{}, buildContext interface{}) *MockImageManager_BuildImage_Call { return &MockImageManager_BuildImage_Call{Call: _e.mock.On("BuildImage", dockerfile, tag, buildContext)} } func (_c *MockImageManager_BuildImage_Call) Run(run func(dockerfile string, tag string, buildContext string)) *MockImageManager_BuildImage_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -105,14 +121,20 @@ type MockImageManager_LoadImage_Call struct { } // LoadImage is a helper method to define mock.On call -// - imageTarPath +// - imageTarPath string func (_e *MockImageManager_Expecter) LoadImage(imageTarPath interface{}) *MockImageManager_LoadImage_Call { return &MockImageManager_LoadImage_Call{Call: _e.mock.On("LoadImage", imageTarPath)} } func (_c *MockImageManager_LoadImage_Call) Run(run func(imageTarPath string)) *MockImageManager_LoadImage_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -150,14 +172,20 @@ type MockImageManager_PushImage_Call struct { } // PushImage is a helper method to define mock.On call -// - tag +// - tag string func (_e *MockImageManager_Expecter) PushImage(tag interface{}) *MockImageManager_PushImage_Call { return &MockImageManager_PushImage_Call{Call: _e.mock.On("PushImage", tag)} } func (_c *MockImageManager_PushImage_Call) Run(run func(tag string)) *MockImageManager_PushImage_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } diff --git a/internal/util/mocks.go b/internal/util/mocks.go index 394f6b82..abfdd4f8 100644 --- a/internal/util/mocks.go +++ b/internal/util/mocks.go @@ -70,15 +70,26 @@ type MockDockerfileManager_UpdateFromStatement_Call struct { } // UpdateFromStatement is a helper method to define mock.On call -// - dockerfile -// - baseImage +// - dockerfile io.Reader +// - baseImage string func (_e *MockDockerfileManager_Expecter) UpdateFromStatement(dockerfile interface{}, baseImage interface{}) *MockDockerfileManager_UpdateFromStatement_Call { return &MockDockerfileManager_UpdateFromStatement_Call{Call: _e.mock.On("UpdateFromStatement", dockerfile, baseImage)} } func (_c *MockDockerfileManager_UpdateFromStatement_Call) Run(run func(dockerfile io.Reader, baseImage string)) *MockDockerfileManager_UpdateFromStatement_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(io.Reader), args[1].(string)) + var arg0 io.Reader + if args[0] != nil { + arg0 = args[0].(io.Reader) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } @@ -154,14 +165,20 @@ type MockFileIO_Create_Call struct { } // Create is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) Create(filename interface{}) *MockFileIO_Create_Call { return &MockFileIO_Create_Call{Call: _e.mock.On("Create", filename)} } func (_c *MockFileIO_Create_Call) Run(run func(filename string)) *MockFileIO_Create_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -199,16 +216,32 @@ type MockFileIO_CreateAndWrite_Call struct { } // CreateAndWrite is a helper method to define mock.On call -// - filePath -// - data -// - fileType +// - filePath string +// - data []byte +// - fileType string func (_e *MockFileIO_Expecter) CreateAndWrite(filePath interface{}, data interface{}, fileType interface{}) *MockFileIO_CreateAndWrite_Call { return &MockFileIO_CreateAndWrite_Call{Call: _e.mock.On("CreateAndWrite", filePath, data, fileType)} } func (_c *MockFileIO_CreateAndWrite_Call) Run(run func(filePath string, data []byte, fileType string)) *MockFileIO_CreateAndWrite_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].([]byte), args[2].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 []byte + if args[1] != nil { + arg1 = args[1].([]byte) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -246,14 +279,20 @@ type MockFileIO_Exists_Call struct { } // Exists is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) Exists(filename interface{}) *MockFileIO_Exists_Call { return &MockFileIO_Exists_Call{Call: _e.mock.On("Exists", filename)} } func (_c *MockFileIO_Exists_Call) Run(run func(filename string)) *MockFileIO_Exists_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -300,14 +339,20 @@ type MockFileIO_IsDirectory_Call struct { } // IsDirectory is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) IsDirectory(filename interface{}) *MockFileIO_IsDirectory_Call { return &MockFileIO_IsDirectory_Call{Call: _e.mock.On("IsDirectory", filename)} } func (_c *MockFileIO_IsDirectory_Call) Run(run func(filename string)) *MockFileIO_IsDirectory_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -345,15 +390,26 @@ type MockFileIO_MkdirAll_Call struct { } // MkdirAll is a helper method to define mock.On call -// - path -// - perm +// - path string +// - perm os.FileMode func (_e *MockFileIO_Expecter) MkdirAll(path interface{}, perm interface{}) *MockFileIO_MkdirAll_Call { return &MockFileIO_MkdirAll_Call{Call: _e.mock.On("MkdirAll", path, perm)} } func (_c *MockFileIO_MkdirAll_Call) Run(run func(path string, perm os.FileMode)) *MockFileIO_MkdirAll_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(os.FileMode)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 os.FileMode + if args[1] != nil { + arg1 = args[1].(os.FileMode) + } + run( + arg0, + arg1, + ) }) return _c } @@ -402,14 +458,20 @@ type MockFileIO_Open_Call struct { } // Open is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) Open(filename interface{}) *MockFileIO_Open_Call { return &MockFileIO_Open_Call{Call: _e.mock.On("Open", filename)} } func (_c *MockFileIO_Open_Call) Run(run func(filename string)) *MockFileIO_Open_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -458,14 +520,20 @@ type MockFileIO_OpenAppend_Call struct { } // OpenAppend is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) OpenAppend(filename interface{}) *MockFileIO_OpenAppend_Call { return &MockFileIO_OpenAppend_Call{Call: _e.mock.On("OpenAppend", filename)} } func (_c *MockFileIO_OpenAppend_Call) Run(run func(filename string)) *MockFileIO_OpenAppend_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -514,16 +582,32 @@ type MockFileIO_OpenFile_Call struct { } // OpenFile is a helper method to define mock.On call -// - name -// - flag -// - perm +// - name string +// - flag int +// - perm os.FileMode func (_e *MockFileIO_Expecter) OpenFile(name interface{}, flag interface{}, perm interface{}) *MockFileIO_OpenFile_Call { return &MockFileIO_OpenFile_Call{Call: _e.mock.On("OpenFile", name, flag, perm)} } func (_c *MockFileIO_OpenFile_Call) Run(run func(name string, flag int, perm os.FileMode)) *MockFileIO_OpenFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(int), args[2].(os.FileMode)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 int + if args[1] != nil { + arg1 = args[1].(int) + } + var arg2 os.FileMode + if args[2] != nil { + arg2 = args[2].(os.FileMode) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -572,14 +656,20 @@ type MockFileIO_ReadDir_Call struct { } // ReadDir is a helper method to define mock.On call -// - dirname +// - dirname string func (_e *MockFileIO_Expecter) ReadDir(dirname interface{}) *MockFileIO_ReadDir_Call { return &MockFileIO_ReadDir_Call{Call: _e.mock.On("ReadDir", dirname)} } func (_c *MockFileIO_ReadDir_Call) Run(run func(dirname string)) *MockFileIO_ReadDir_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -628,14 +718,20 @@ type MockFileIO_ReadFile_Call struct { } // ReadFile is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) ReadFile(filename interface{}) *MockFileIO_ReadFile_Call { return &MockFileIO_ReadFile_Call{Call: _e.mock.On("ReadFile", filename)} } func (_c *MockFileIO_ReadFile_Call) Run(run func(filename string)) *MockFileIO_ReadFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -673,16 +769,32 @@ type MockFileIO_WriteFile_Call struct { } // WriteFile is a helper method to define mock.On call -// - filename -// - data -// - perm +// - filename string +// - data []byte +// - perm os.FileMode func (_e *MockFileIO_Expecter) WriteFile(filename interface{}, data interface{}, perm interface{}) *MockFileIO_WriteFile_Call { return &MockFileIO_WriteFile_Call{Call: _e.mock.On("WriteFile", filename, data, perm)} } func (_c *MockFileIO_WriteFile_Call) Run(run func(filename string, data []byte, perm os.FileMode)) *MockFileIO_WriteFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].([]byte), args[2].(os.FileMode)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 []byte + if args[1] != nil { + arg1 = args[1].([]byte) + } + var arg2 os.FileMode + if args[2] != nil { + arg2 = args[2].(os.FileMode) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -812,8 +924,8 @@ type MockTableWriter_AppendHeader_Call struct { } // AppendHeader is a helper method to define mock.On call -// - row -// - configs +// - row table.Row +// - configs ...table.RowConfig func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...interface{}) *MockTableWriter_AppendHeader_Call { return &MockTableWriter_AppendHeader_Call{Call: _e.mock.On("AppendHeader", append([]interface{}{row}, configs...)...)} @@ -821,13 +933,20 @@ func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...int func (_c *MockTableWriter_AppendHeader_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendHeader_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]table.RowConfig, len(args)-1) - for i, a := range args[1:] { - if a != nil { - variadicArgs[i] = a.(table.RowConfig) - } + var arg0 table.Row + if args[0] != nil { + arg0 = args[0].(table.Row) } - run(args[0].(table.Row), variadicArgs...) + var arg1 []table.RowConfig + var variadicArgs []table.RowConfig + if len(args) > 1 { + variadicArgs = args[1].([]table.RowConfig) + } + arg1 = variadicArgs + run( + arg0, + arg1..., + ) }) return _c } @@ -859,8 +978,8 @@ type MockTableWriter_AppendRow_Call struct { } // AppendRow is a helper method to define mock.On call -// - row -// - configs +// - row table.Row +// - configs ...table.RowConfig func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interface{}) *MockTableWriter_AppendRow_Call { return &MockTableWriter_AppendRow_Call{Call: _e.mock.On("AppendRow", append([]interface{}{row}, configs...)...)} @@ -868,13 +987,20 @@ func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interf func (_c *MockTableWriter_AppendRow_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendRow_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]table.RowConfig, len(args)-1) - for i, a := range args[1:] { - if a != nil { - variadicArgs[i] = a.(table.RowConfig) - } + var arg0 table.Row + if args[0] != nil { + arg0 = args[0].(table.Row) + } + var arg1 []table.RowConfig + var variadicArgs []table.RowConfig + if len(args) > 1 { + variadicArgs = args[1].([]table.RowConfig) } - run(args[0].(table.Row), variadicArgs...) + arg1 = variadicArgs + run( + arg0, + arg1..., + ) }) return _c } From ffa3084b03251c9d5c60e44b19c64870117203b4 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 28 Jan 2026 15:13:47 +0000 Subject: [PATCH 45/65] chore(docs): Auto-update docs and licenses Signed-off-by: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> --- NOTICE | 24 ++++++++++++------------ internal/tmpl/NOTICE | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/NOTICE b/NOTICE index ab64d489..7bbe52e5 100644 --- a/NOTICE +++ b/NOTICE @@ -11,9 +11,9 @@ License URL: https://github.com/googleapis/google-cloud-go/blob/artifactregistry ---------- Module: cloud.google.com/go/auth -Version: v0.18.0 +Version: v0.18.1 License: Apache-2.0 -License URL: https://github.com/googleapis/google-cloud-go/blob/auth/v0.18.0/auth/LICENSE +License URL: https://github.com/googleapis/google-cloud-go/blob/auth/v0.18.1/auth/LICENSE ---------- Module: cloud.google.com/go/auth/oauth2adapt @@ -23,9 +23,9 @@ License URL: https://github.com/googleapis/google-cloud-go/blob/auth/oauth2adapt ---------- Module: cloud.google.com/go/compute -Version: v1.53.0 +Version: v1.54.0 License: Apache-2.0 -License URL: https://github.com/googleapis/google-cloud-go/blob/compute/v1.53.0/compute/LICENSE +License URL: https://github.com/googleapis/google-cloud-go/blob/compute/v1.54.0/compute/LICENSE ---------- Module: cloud.google.com/go/compute/metadata @@ -95,9 +95,9 @@ License URL: https://github.com/clipperhouse/uax29/blob/v2.3.0/LICENSE ---------- Module: github.com/codesphere-cloud/cs-go/pkg/io -Version: v0.15.0 +Version: v0.16.1 License: Apache-2.0 -License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.15.0/LICENSE +License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.16.1/LICENSE ---------- Module: github.com/codesphere-cloud/oms/internal/tmpl @@ -401,15 +401,15 @@ License URL: https://cs.opensource.google/go/x/time/+/v0.14.0:LICENSE ---------- Module: google.golang.org/api -Version: v0.261.0 +Version: v0.263.0 License: BSD-3-Clause -License URL: https://github.com/googleapis/google-api-go-client/blob/v0.261.0/LICENSE +License URL: https://github.com/googleapis/google-api-go-client/blob/v0.263.0/LICENSE ---------- Module: google.golang.org/api/internal/third_party/uritemplates -Version: v0.261.0 +Version: v0.263.0 License: BSD-3-Clause -License URL: https://github.com/googleapis/google-api-go-client/blob/v0.261.0/internal/third_party/uritemplates/LICENSE +License URL: https://github.com/googleapis/google-api-go-client/blob/v0.263.0/internal/third_party/uritemplates/LICENSE ---------- Module: google.golang.org/genproto/googleapis @@ -425,9 +425,9 @@ License URL: https://github.com/googleapis/go-genproto/blob/0a764e51fe1b/googlea ---------- Module: google.golang.org/genproto/googleapis/rpc -Version: v0.0.0-20260120174246-409b4a993575 +Version: v0.0.0-20260122232226-8e98ce8d340d License: Apache-2.0 -License URL: https://github.com/googleapis/go-genproto/blob/409b4a993575/googleapis/rpc/LICENSE +License URL: https://github.com/googleapis/go-genproto/blob/8e98ce8d340d/googleapis/rpc/LICENSE ---------- Module: google.golang.org/grpc diff --git a/internal/tmpl/NOTICE b/internal/tmpl/NOTICE index ab64d489..7bbe52e5 100644 --- a/internal/tmpl/NOTICE +++ b/internal/tmpl/NOTICE @@ -11,9 +11,9 @@ License URL: https://github.com/googleapis/google-cloud-go/blob/artifactregistry ---------- Module: cloud.google.com/go/auth -Version: v0.18.0 +Version: v0.18.1 License: Apache-2.0 -License URL: https://github.com/googleapis/google-cloud-go/blob/auth/v0.18.0/auth/LICENSE +License URL: https://github.com/googleapis/google-cloud-go/blob/auth/v0.18.1/auth/LICENSE ---------- Module: cloud.google.com/go/auth/oauth2adapt @@ -23,9 +23,9 @@ License URL: https://github.com/googleapis/google-cloud-go/blob/auth/oauth2adapt ---------- Module: cloud.google.com/go/compute -Version: v1.53.0 +Version: v1.54.0 License: Apache-2.0 -License URL: https://github.com/googleapis/google-cloud-go/blob/compute/v1.53.0/compute/LICENSE +License URL: https://github.com/googleapis/google-cloud-go/blob/compute/v1.54.0/compute/LICENSE ---------- Module: cloud.google.com/go/compute/metadata @@ -95,9 +95,9 @@ License URL: https://github.com/clipperhouse/uax29/blob/v2.3.0/LICENSE ---------- Module: github.com/codesphere-cloud/cs-go/pkg/io -Version: v0.15.0 +Version: v0.16.1 License: Apache-2.0 -License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.15.0/LICENSE +License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.16.1/LICENSE ---------- Module: github.com/codesphere-cloud/oms/internal/tmpl @@ -401,15 +401,15 @@ License URL: https://cs.opensource.google/go/x/time/+/v0.14.0:LICENSE ---------- Module: google.golang.org/api -Version: v0.261.0 +Version: v0.263.0 License: BSD-3-Clause -License URL: https://github.com/googleapis/google-api-go-client/blob/v0.261.0/LICENSE +License URL: https://github.com/googleapis/google-api-go-client/blob/v0.263.0/LICENSE ---------- Module: google.golang.org/api/internal/third_party/uritemplates -Version: v0.261.0 +Version: v0.263.0 License: BSD-3-Clause -License URL: https://github.com/googleapis/google-api-go-client/blob/v0.261.0/internal/third_party/uritemplates/LICENSE +License URL: https://github.com/googleapis/google-api-go-client/blob/v0.263.0/internal/third_party/uritemplates/LICENSE ---------- Module: google.golang.org/genproto/googleapis @@ -425,9 +425,9 @@ License URL: https://github.com/googleapis/go-genproto/blob/0a764e51fe1b/googlea ---------- Module: google.golang.org/genproto/googleapis/rpc -Version: v0.0.0-20260120174246-409b4a993575 +Version: v0.0.0-20260122232226-8e98ce8d340d License: Apache-2.0 -License URL: https://github.com/googleapis/go-genproto/blob/409b4a993575/googleapis/rpc/LICENSE +License URL: https://github.com/googleapis/go-genproto/blob/8e98ce8d340d/googleapis/rpc/LICENSE ---------- Module: google.golang.org/grpc From d30c11755430c1de840dfcf43e94d56d9484a20d Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:42:12 +0100 Subject: [PATCH 46/65] ref: mock SSH client and enhance tests for SSH connection failures --- cli/cmd/mocks.go | 24 +-- internal/bootstrap/gcp.go | 5 +- internal/installer/mocks.go | 237 ++++----------------- internal/installer/node/mock_ssh_client.go | 71 ++++++ internal/installer/node/node.go | 41 +++- internal/installer/node/node_test.go | 122 +++++++++-- internal/portal/mocks.go | 225 ++++--------------- internal/system/mocks.go | 44 +--- internal/util/mocks.go | 222 +++++-------------- 9 files changed, 360 insertions(+), 631 deletions(-) create mode 100644 internal/installer/node/mock_ssh_client.go diff --git a/cli/cmd/mocks.go b/cli/cmd/mocks.go index 26b37df5..60357284 100644 --- a/cli/cmd/mocks.go +++ b/cli/cmd/mocks.go @@ -75,32 +75,16 @@ type MockOMSUpdater_Update_Call struct { } // Update is a helper method to define mock.On call -// - ctx context.Context -// - current string -// - repo selfupdate.Repository +// - ctx +// - current +// - repo func (_e *MockOMSUpdater_Expecter) Update(ctx interface{}, current interface{}, repo interface{}) *MockOMSUpdater_Update_Call { return &MockOMSUpdater_Update_Call{Call: _e.mock.On("Update", ctx, current, repo)} } func (_c *MockOMSUpdater_Update_Call) Run(run func(ctx context.Context, current string, repo selfupdate.Repository)) *MockOMSUpdater_Update_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 selfupdate.Repository - if args[2] != nil { - arg2 = args[2].(selfupdate.Repository) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(context.Context), args[1].(string), args[2].(selfupdate.Repository)) }) return _c } diff --git a/internal/bootstrap/gcp.go b/internal/bootstrap/gcp.go index 802a60e9..0650378a 100644 --- a/internal/bootstrap/gcp.go +++ b/internal/bootstrap/gcp.go @@ -83,10 +83,7 @@ func NewGCPBootstrapper(env env.Env, CodesphereEnv *CodesphereEnvironment, gcpCl ctx := context.Background() fw := util.NewFilesystemWriter() icg := installer.NewInstallConfigManager() - nm := &node.NodeManager{ - FileIO: fw, - KeyPath: expandPath(CodesphereEnv.SSHPrivateKeyPath), - } + nm := node.NewNodeManager(fw, expandPath(CodesphereEnv.SSHPrivateKeyPath)) if fw.Exists(CodesphereEnv.InstallConfig) { log.Printf("Reading install config file: %s", CodesphereEnv.InstallConfig) diff --git a/internal/installer/mocks.go b/internal/installer/mocks.go index 0bf9fda6..cf9131fc 100644 --- a/internal/installer/mocks.go +++ b/internal/installer/mocks.go @@ -69,20 +69,14 @@ type MockConfigManager_ParseConfigYaml_Call struct { } // ParseConfigYaml is a helper method to define mock.On call -// - configPath string +// - configPath func (_e *MockConfigManager_Expecter) ParseConfigYaml(configPath interface{}) *MockConfigManager_ParseConfigYaml_Call { return &MockConfigManager_ParseConfigYaml_Call{Call: _e.mock.On("ParseConfigYaml", configPath)} } func (_c *MockConfigManager_ParseConfigYaml_Call) Run(run func(configPath string)) *MockConfigManager_ParseConfigYaml_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -147,20 +141,14 @@ type MockInstallConfigManager_ApplyProfile_Call struct { } // ApplyProfile is a helper method to define mock.On call -// - profile string +// - profile func (_e *MockInstallConfigManager_Expecter) ApplyProfile(profile interface{}) *MockInstallConfigManager_ApplyProfile_Call { return &MockInstallConfigManager_ApplyProfile_Call{Call: _e.mock.On("ApplyProfile", profile)} } func (_c *MockInstallConfigManager_ApplyProfile_Call) Run(run func(profile string)) *MockInstallConfigManager_ApplyProfile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -378,20 +366,14 @@ type MockInstallConfigManager_LoadInstallConfigFromFile_Call struct { } // LoadInstallConfigFromFile is a helper method to define mock.On call -// - configPath string +// - configPath func (_e *MockInstallConfigManager_Expecter) LoadInstallConfigFromFile(configPath interface{}) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { return &MockInstallConfigManager_LoadInstallConfigFromFile_Call{Call: _e.mock.On("LoadInstallConfigFromFile", configPath)} } func (_c *MockInstallConfigManager_LoadInstallConfigFromFile_Call) Run(run func(configPath string)) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -429,20 +411,14 @@ type MockInstallConfigManager_LoadVaultFromFile_Call struct { } // LoadVaultFromFile is a helper method to define mock.On call -// - vaultPath string +// - vaultPath func (_e *MockInstallConfigManager_Expecter) LoadVaultFromFile(vaultPath interface{}) *MockInstallConfigManager_LoadVaultFromFile_Call { return &MockInstallConfigManager_LoadVaultFromFile_Call{Call: _e.mock.On("LoadVaultFromFile", vaultPath)} } func (_c *MockInstallConfigManager_LoadVaultFromFile_Call) Run(run func(vaultPath string)) *MockInstallConfigManager_LoadVaultFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -616,26 +592,15 @@ type MockInstallConfigManager_WriteInstallConfig_Call struct { } // WriteInstallConfig is a helper method to define mock.On call -// - configPath string -// - withComments bool +// - configPath +// - withComments func (_e *MockInstallConfigManager_Expecter) WriteInstallConfig(configPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteInstallConfig_Call { return &MockInstallConfigManager_WriteInstallConfig_Call{Call: _e.mock.On("WriteInstallConfig", configPath, withComments)} } func (_c *MockInstallConfigManager_WriteInstallConfig_Call) Run(run func(configPath string, withComments bool)) *MockInstallConfigManager_WriteInstallConfig_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -673,26 +638,15 @@ type MockInstallConfigManager_WriteVault_Call struct { } // WriteVault is a helper method to define mock.On call -// - vaultPath string -// - withComments bool +// - vaultPath +// - withComments func (_e *MockInstallConfigManager_Expecter) WriteVault(vaultPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteVault_Call { return &MockInstallConfigManager_WriteVault_Call{Call: _e.mock.On("WriteVault", vaultPath, withComments)} } func (_c *MockInstallConfigManager_WriteVault_Call) Run(run func(vaultPath string, withComments bool)) *MockInstallConfigManager_WriteVault_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -766,32 +720,16 @@ type MockK0sManager_Download_Call struct { } // Download is a helper method to define mock.On call -// - version string -// - force bool -// - quiet bool +// - version +// - force +// - quiet func (_e *MockK0sManager_Expecter) Download(version interface{}, force interface{}, quiet interface{}) *MockK0sManager_Download_Call { return &MockK0sManager_Download_Call{Call: _e.mock.On("Download", version, force, quiet)} } func (_c *MockK0sManager_Download_Call) Run(run func(version string, force bool, quiet bool)) *MockK0sManager_Download_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - var arg2 bool - if args[2] != nil { - arg2 = args[2].(bool) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(bool), args[2].(bool)) }) return _c } @@ -909,32 +847,16 @@ type MockK0sctlManager_Apply_Call struct { } // Apply is a helper method to define mock.On call -// - configPath string -// - k0sctlPath string -// - force bool +// - configPath +// - k0sctlPath +// - force func (_e *MockK0sctlManager_Expecter) Apply(configPath interface{}, k0sctlPath interface{}, force interface{}) *MockK0sctlManager_Apply_Call { return &MockK0sctlManager_Apply_Call{Call: _e.mock.On("Apply", configPath, k0sctlPath, force)} } func (_c *MockK0sctlManager_Apply_Call) Run(run func(configPath string, k0sctlPath string, force bool)) *MockK0sctlManager_Apply_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 bool - if args[2] != nil { - arg2 = args[2].(bool) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(string), args[2].(bool)) }) return _c } @@ -981,32 +903,16 @@ type MockK0sctlManager_Download_Call struct { } // Download is a helper method to define mock.On call -// - version string -// - force bool -// - quiet bool +// - version +// - force +// - quiet func (_e *MockK0sctlManager_Expecter) Download(version interface{}, force interface{}, quiet interface{}) *MockK0sctlManager_Download_Call { return &MockK0sctlManager_Download_Call{Call: _e.mock.On("Download", version, force, quiet)} } func (_c *MockK0sctlManager_Download_Call) Run(run func(version string, force bool, quiet bool)) *MockK0sctlManager_Download_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - var arg2 bool - if args[2] != nil { - arg2 = args[2].(bool) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(bool), args[2].(bool)) }) return _c } @@ -1097,26 +1003,15 @@ type MockK0sctlManager_Reset_Call struct { } // Reset is a helper method to define mock.On call -// - configPath string -// - k0sctlPath string +// - configPath +// - k0sctlPath func (_e *MockK0sctlManager_Expecter) Reset(configPath interface{}, k0sctlPath interface{}) *MockK0sctlManager_Reset_Call { return &MockK0sctlManager_Reset_Call{Call: _e.mock.On("Reset", configPath, k0sctlPath)} } func (_c *MockK0sctlManager_Reset_Call) Run(run func(configPath string, k0sctlPath string)) *MockK0sctlManager_Reset_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(string)) }) return _c } @@ -1181,20 +1076,14 @@ type MockPackageManager_Extract_Call struct { } // Extract is a helper method to define mock.On call -// - force bool +// - force func (_e *MockPackageManager_Expecter) Extract(force interface{}) *MockPackageManager_Extract_Call { return &MockPackageManager_Extract_Call{Call: _e.mock.On("Extract", force)} } func (_c *MockPackageManager_Extract_Call) Run(run func(force bool)) *MockPackageManager_Extract_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 bool - if args[0] != nil { - arg0 = args[0].(bool) - } - run( - arg0, - ) + run(args[0].(bool)) }) return _c } @@ -1232,26 +1121,15 @@ type MockPackageManager_ExtractDependency_Call struct { } // ExtractDependency is a helper method to define mock.On call -// - file string -// - force bool +// - file +// - force func (_e *MockPackageManager_Expecter) ExtractDependency(file interface{}, force interface{}) *MockPackageManager_ExtractDependency_Call { return &MockPackageManager_ExtractDependency_Call{Call: _e.mock.On("ExtractDependency", file, force)} } func (_c *MockPackageManager_ExtractDependency_Call) Run(run func(file string, force bool)) *MockPackageManager_ExtractDependency_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -1298,20 +1176,14 @@ type MockPackageManager_ExtractOciImageIndex_Call struct { } // ExtractOciImageIndex is a helper method to define mock.On call -// - imagefile string +// - imagefile func (_e *MockPackageManager_Expecter) ExtractOciImageIndex(imagefile interface{}) *MockPackageManager_ExtractOciImageIndex_Call { return &MockPackageManager_ExtractOciImageIndex_Call{Call: _e.mock.On("ExtractOciImageIndex", imagefile)} } func (_c *MockPackageManager_ExtractOciImageIndex_Call) Run(run func(imagefile string)) *MockPackageManager_ExtractOciImageIndex_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -1404,20 +1276,14 @@ type MockPackageManager_GetBaseimageName_Call struct { } // GetBaseimageName is a helper method to define mock.On call -// - baseimage string +// - baseimage func (_e *MockPackageManager_Expecter) GetBaseimageName(baseimage interface{}) *MockPackageManager_GetBaseimageName_Call { return &MockPackageManager_GetBaseimageName_Call{Call: _e.mock.On("GetBaseimageName", baseimage)} } func (_c *MockPackageManager_GetBaseimageName_Call) Run(run func(baseimage string)) *MockPackageManager_GetBaseimageName_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -1464,26 +1330,15 @@ type MockPackageManager_GetBaseimagePath_Call struct { } // GetBaseimagePath is a helper method to define mock.On call -// - baseimage string -// - force bool +// - baseimage +// - force func (_e *MockPackageManager_Expecter) GetBaseimagePath(baseimage interface{}, force interface{}) *MockPackageManager_GetBaseimagePath_Call { return &MockPackageManager_GetBaseimagePath_Call{Call: _e.mock.On("GetBaseimagePath", baseimage, force)} } func (_c *MockPackageManager_GetBaseimagePath_Call) Run(run func(baseimage string, force bool)) *MockPackageManager_GetBaseimagePath_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 bool - if args[1] != nil { - arg1 = args[1].(bool) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(bool)) }) return _c } @@ -1574,20 +1429,14 @@ type MockPackageManager_GetDependencyPath_Call struct { } // GetDependencyPath is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockPackageManager_Expecter) GetDependencyPath(filename interface{}) *MockPackageManager_GetDependencyPath_Call { return &MockPackageManager_GetDependencyPath_Call{Call: _e.mock.On("GetDependencyPath", filename)} } func (_c *MockPackageManager_GetDependencyPath_Call) Run(run func(filename string)) *MockPackageManager_GetDependencyPath_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } diff --git a/internal/installer/node/mock_ssh_client.go b/internal/installer/node/mock_ssh_client.go new file mode 100644 index 00000000..7d76529c --- /dev/null +++ b/internal/installer/node/mock_ssh_client.go @@ -0,0 +1,71 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package node + +import ( + "errors" + "io" + + "golang.org/x/crypto/ssh" +) + +// MockSSHClientFactory is a test implementation of SSHClientFactory. +type MockSSHClientFactory struct { + DialFunc func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) +} + +func (m *MockSSHClientFactory) Dial(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + if m.DialFunc != nil { + return m.DialFunc(network, addr, config) + } + return nil, errors.New("mock SSH client factory not configured") +} + +// MockSSHClient wraps an SSH client for testing purposes. +type MockSSHClient struct { + client *ssh.Client +} + +// NewMockSSHClient creates a mock SSH client that returns errors for all operations. +func NewMockSSHClient() *MockSSHClient { + return &MockSSHClient{} +} + +// MockSSHSession is a mock SSH session for testing. +type MockSSHSession struct { + StartFunc func(cmd string) error + WaitFunc func() error + CloseFunc func() error + SetenvFunc func(name, value string) error + Stdout io.Writer + Stderr io.Writer +} + +func (m *MockSSHSession) Start(cmd string) error { + if m.StartFunc != nil { + return m.StartFunc(cmd) + } + return nil +} + +func (m *MockSSHSession) Wait() error { + if m.WaitFunc != nil { + return m.WaitFunc() + } + return nil +} + +func (m *MockSSHSession) Close() error { + if m.CloseFunc != nil { + return m.CloseFunc() + } + return nil +} + +func (m *MockSSHSession) Setenv(name, value string) error { + if m.SetenvFunc != nil { + return m.SetenvFunc(name, value) + } + return nil +} diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index cd4dc72d..405a705b 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -21,6 +21,12 @@ import ( "golang.org/x/term" ) +// SSHClientFactory is an interface for creating SSH clients, allowing for mocking in tests. +type SSHClientFactory interface { + // Dial creates an SSH connection to the specified network address. + Dial(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) +} + type Node struct { Name string `json:"name"` ExternalIP string `json:"external_ip"` @@ -29,9 +35,26 @@ type Node struct { } type NodeManager struct { - FileIO util.FileIO - KeyPath string - cachedSigner ssh.Signer // cached signer to avoid repeated passphrase prompts + FileIO util.FileIO + KeyPath string + cachedSigner ssh.Signer // cached signer to avoid repeated passphrase prompts + ClientFactory SSHClientFactory +} + +// defaultSSHClientFactory is the real SSH client factory that makes actual network calls. +type defaultSSHClientFactory struct{} + +func (d *defaultSSHClientFactory) Dial(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return ssh.Dial(network, addr, config) +} + +// NewNodeManager creates a NodeManager with the default SSH client factory. +func NewNodeManager(fileIO util.FileIO, keyPath string) *NodeManager { + return &NodeManager{ + FileIO: fileIO, + KeyPath: keyPath, + ClientFactory: &defaultSSHClientFactory{}, + } } const ( @@ -179,7 +202,11 @@ func (nm *NodeManager) connectToJumpbox(ip, username string) (*ssh.Client, error } addr := fmt.Sprintf("%s:22", ip) - jumpboxClient, err := ssh.Dial("tcp", addr, config) + factory := nm.ClientFactory + if factory == nil { + factory = &defaultSSHClientFactory{} + } + jumpboxClient, err := factory.Dial("tcp", addr, config) if err != nil { return nil, fmt.Errorf("failed to dial jumpbox %s: %v", addr, err) } @@ -294,7 +321,11 @@ func (n *NodeManager) GetClient(jumpboxIp string, ip string, username string) (* } addr := fmt.Sprintf("%s:22", ip) - client, err := ssh.Dial("tcp", addr, config) + factory := n.ClientFactory + if factory == nil { + factory = &defaultSSHClientFactory{} + } + client, err := factory.Dial("tcp", addr, config) if err != nil { return nil, fmt.Errorf("failed to dial: %v", err) } diff --git a/internal/installer/node/node_test.go b/internal/installer/node/node_test.go index ef1004a3..8cea4a38 100644 --- a/internal/installer/node/node_test.go +++ b/internal/installer/node/node_test.go @@ -10,6 +10,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "golang.org/x/crypto/ssh" "github.com/codesphere-cloud/oms/internal/installer/node" "github.com/codesphere-cloud/oms/internal/util" @@ -32,6 +33,11 @@ var _ = Describe("Node", func() { nm := &node.NodeManager{ FileIO: mockFileWriter, KeyPath: "", + ClientFactory: &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + }, } result := testNode.HasCommand(nm, "test'; echo 'injected") @@ -47,6 +53,11 @@ var _ = Describe("Node", func() { nm := &node.NodeManager{ FileIO: mockFileWriter, KeyPath: "", + ClientFactory: &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + }, } // Test various injection attempts @@ -74,6 +85,11 @@ var _ = Describe("Node", func() { nm := &node.NodeManager{ FileIO: mockFileWriter, KeyPath: "", + ClientFactory: &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + }, } normalCommands := []string{ @@ -98,6 +114,11 @@ var _ = Describe("Node", func() { nm := &node.NodeManager{ FileIO: mockFileWriter, KeyPath: "", + ClientFactory: &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + }, } result := testNode.HasCommand(nm, "test-ꖇ件-αβγ") @@ -113,6 +134,11 @@ var _ = Describe("Node", func() { nm := &node.NodeManager{ FileIO: mockFileWriter, KeyPath: "", + ClientFactory: &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + }, } result := testNode.HasCommand(nm, "") @@ -128,6 +154,11 @@ var _ = Describe("Node", func() { nm := &node.NodeManager{ FileIO: mockFileWriter, KeyPath: "", + ClientFactory: &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + }, } result := testNode.HasCommand(nm, "echo 'test \"nested\" quotes'") @@ -167,6 +198,11 @@ var _ = Describe("Node", func() { _ = os.Unsetenv("SSH_AUTH_SOCK") nm.KeyPath = "" + nm.ClientFactory = &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("mock dial error") + }, + } client, err := nm.GetClient("", "10.0.0.1", "root") Expect(err).To(HaveOccurred()) @@ -187,6 +223,11 @@ var _ = Describe("Node", func() { nm.KeyPath = "/nonexistent/key" mockFileWriter.EXPECT().ReadFile("/nonexistent/key").Return(nil, errors.New("file not found")) + nm.ClientFactory = &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("mock dial error") + }, + } client, err := nm.GetClient("", "10.0.0.1", "root") Expect(err).To(HaveOccurred()) @@ -209,6 +250,11 @@ var _ = Describe("Node", func() { invalidKey := []byte("not a valid ssh key") nm.KeyPath = "/path/to/invalid/key" mockFileWriter.EXPECT().ReadFile("/path/to/invalid/key").Return(invalidKey, nil) + nm.ClientFactory = &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("mock dial error") + }, + } client, err := nm.GetClient("", "10.0.0.1", "root") Expect(err).To(HaveOccurred()) @@ -232,15 +278,16 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== mockFileWriter.EXPECT().ReadFile("/path/to/key").Return(privateKey, nil).Maybe() // Mock the .pub file read for deduplication check mockFileWriter.EXPECT().ReadFile("/path/to/key.pub").Return(nil, errors.New("file not found")).Maybe() + // Mock the SSH client factory to avoid real network calls + nm.ClientFactory = &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("failed to dial: connection refused") + }, + } client, err := nm.GetClient("", "192.0.2.1", "root") Expect(err).To(HaveOccurred()) - // Accept either authentication failure or connection failure - Expect(err.Error()).To(Or( - ContainSubstring("failed to dial"), - ContainSubstring("failed to get authentication methods"), - ContainSubstring("failed to parse private key"), - )) + Expect(err.Error()).To(ContainSubstring("failed to dial")) Expect(client).To(BeNil()) }) @@ -257,27 +304,38 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== mockFileWriter.EXPECT().ReadFile("/path/to/key").Return(privateKey, nil).Maybe() // Mock the .pub file read for deduplication check mockFileWriter.EXPECT().ReadFile("/path/to/key.pub").Return(nil, errors.New("file not found")).Maybe() + // Mock the SSH client factory to avoid real network calls + nm.ClientFactory = &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("failed to dial jumpbox") + }, + } client, err := nm.GetClient("192.0.2.1", "192.0.2.2", "root") Expect(err).To(HaveOccurred()) - // Accept either authentication failure or connection failure - Expect(err.Error()).To(Or( - ContainSubstring("failed to connect to jumpbox"), - ContainSubstring("failed to get authentication methods"), - ContainSubstring("failed to parse private key"), - )) + Expect(err.Error()).To(ContainSubstring("failed to connect to jumpbox")) Expect(client).To(BeNil()) }) }) Context("file operations", func() { It("should handle directory creation errors", func() { + nm.ClientFactory = &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + } err := nm.EnsureDirectoryExists("", "192.0.2.1", "root", "/tmp/test") Expect(err).To(HaveOccurred()) }) It("should handle copy file errors when source doesn't exist", func() { mockFileWriter.EXPECT().Open("/nonexistent/file").Return(nil, errors.New("file not found")).Maybe() + nm.ClientFactory = &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + } err := nm.CopyFile("", "192.0.2.1", "root", "/nonexistent/file", "/tmp/dest") Expect(err).To(HaveOccurred()) @@ -312,11 +370,21 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== Context("HasCommand", func() { It("should return false when SSH connection fails", func() { + nm.ClientFactory = &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + } result := n.HasCommand(nm, "kubectl") Expect(result).To(BeFalse()) }) It("should handle commands with special characters safely", func() { + nm.ClientFactory = &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + } result := n.HasCommand(nm, "kubectl'; rm -rf /; echo '") Expect(result).To(BeFalse()) }) @@ -324,11 +392,21 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== Context("HasFile", func() { It("should return false when SSH connection fails", func() { + nm.ClientFactory = &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + } result := n.HasFile(nil, nm, "/etc/k0s/k0s.yaml") Expect(result).To(BeFalse()) }) It("should handle paths with special characters safely", func() { + nm.ClientFactory = &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + } result := n.HasFile(nil, nm, "/path'; rm -rf /; echo '/file.txt") Expect(result).To(BeFalse()) }) @@ -338,6 +416,11 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== ExternalIP: "10.0.0.2", InternalIP: "10.0.0.2", } + nm.ClientFactory = &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + } result := n.HasFile(jumpbox, nm, "/etc/k0s/k0s.yaml") Expect(result).To(BeFalse()) }) @@ -345,6 +428,11 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== Context("CopyFile", func() { It("should fail when directory creation fails", func() { + nm.ClientFactory = &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + } err := n.CopyFile(nil, nm, "/some/file", "/remote/path/dest.txt") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to ensure directory exists")) @@ -353,6 +441,11 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== Context("RunSSHCommand", func() { It("should handle direct connection without jumpbox", func() { + nm.ClientFactory = &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + } err := n.RunSSHCommand(nil, nm, "root", "echo test") Expect(err).To(HaveOccurred()) }) @@ -362,6 +455,11 @@ gsUnsokl0FasmM3Ws7VlAAAADnRlc3RAZXhhbXBsZS5jb20BAgMEBQ== ExternalIP: "10.0.0.2", InternalIP: "10.0.0.2", } + nm.ClientFactory = &node.MockSSHClientFactory{ + DialFunc: func(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return nil, errors.New("connection failed") + }, + } err := n.RunSSHCommand(jumpbox, nm, "ubuntu", "echo test") Expect(err).To(HaveOccurred()) }) diff --git a/internal/portal/mocks.go b/internal/portal/mocks.go index c162a3ee..83372369 100644 --- a/internal/portal/mocks.go +++ b/internal/portal/mocks.go @@ -61,32 +61,16 @@ type MockHttp_Download_Call struct { } // Download is a helper method to define mock.On call -// - url string -// - file io.Writer -// - quiet bool +// - url +// - file +// - quiet func (_e *MockHttp_Expecter) Download(url interface{}, file interface{}, quiet interface{}) *MockHttp_Download_Call { return &MockHttp_Download_Call{Call: _e.mock.On("Download", url, file, quiet)} } func (_c *MockHttp_Download_Call) Run(run func(url string, file io.Writer, quiet bool)) *MockHttp_Download_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 io.Writer - if args[1] != nil { - arg1 = args[1].(io.Writer) - } - var arg2 bool - if args[2] != nil { - arg2 = args[2].(bool) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(io.Writer), args[2].(bool)) }) return _c } @@ -135,20 +119,14 @@ type MockHttp_Get_Call struct { } // Get is a helper method to define mock.On call -// - url string +// - url func (_e *MockHttp_Expecter) Get(url interface{}) *MockHttp_Get_Call { return &MockHttp_Get_Call{Call: _e.mock.On("Get", url)} } func (_c *MockHttp_Get_Call) Run(run func(url string)) *MockHttp_Get_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -197,32 +175,16 @@ type MockHttp_Request_Call struct { } // Request is a helper method to define mock.On call -// - url string -// - method string -// - body io.Reader +// - url +// - method +// - body func (_e *MockHttp_Expecter) Request(url interface{}, method interface{}, body interface{}) *MockHttp_Request_Call { return &MockHttp_Request_Call{Call: _e.mock.On("Request", url, method, body)} } func (_c *MockHttp_Request_Call) Run(run func(url string, method string, body io.Reader)) *MockHttp_Request_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 io.Reader - if args[2] != nil { - arg2 = args[2].(io.Reader) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(string), args[2].(io.Reader)) }) return _c } @@ -287,44 +249,18 @@ type MockPortal_DownloadBuildArtifact_Call struct { } // DownloadBuildArtifact is a helper method to define mock.On call -// - product Product -// - build Build -// - file io.Writer -// - startByte int -// - quiet bool +// - product +// - build +// - file +// - startByte +// - quiet func (_e *MockPortal_Expecter) DownloadBuildArtifact(product interface{}, build interface{}, file interface{}, startByte interface{}, quiet interface{}) *MockPortal_DownloadBuildArtifact_Call { return &MockPortal_DownloadBuildArtifact_Call{Call: _e.mock.On("DownloadBuildArtifact", product, build, file, startByte, quiet)} } func (_c *MockPortal_DownloadBuildArtifact_Call) Run(run func(product Product, build Build, file io.Writer, startByte int, quiet bool)) *MockPortal_DownloadBuildArtifact_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 Product - if args[0] != nil { - arg0 = args[0].(Product) - } - var arg1 Build - if args[1] != nil { - arg1 = args[1].(Build) - } - var arg2 io.Writer - if args[2] != nil { - arg2 = args[2].(io.Writer) - } - var arg3 int - if args[3] != nil { - arg3 = args[3].(int) - } - var arg4 bool - if args[4] != nil { - arg4 = args[4].(bool) - } - run( - arg0, - arg1, - arg2, - arg3, - arg4, - ) + run(args[0].(Product), args[1].(Build), args[2].(io.Writer), args[3].(int), args[4].(bool)) }) return _c } @@ -371,20 +307,14 @@ type MockPortal_GetApiKeyId_Call struct { } // GetApiKeyId is a helper method to define mock.On call -// - oldKey string +// - oldKey func (_e *MockPortal_Expecter) GetApiKeyId(oldKey interface{}) *MockPortal_GetApiKeyId_Call { return &MockPortal_GetApiKeyId_Call{Call: _e.mock.On("GetApiKeyId", oldKey)} } func (_c *MockPortal_GetApiKeyId_Call) Run(run func(oldKey string)) *MockPortal_GetApiKeyId_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -431,32 +361,16 @@ type MockPortal_GetBuild_Call struct { } // GetBuild is a helper method to define mock.On call -// - product Product -// - version string -// - hash string +// - product +// - version +// - hash func (_e *MockPortal_Expecter) GetBuild(product interface{}, version interface{}, hash interface{}) *MockPortal_GetBuild_Call { return &MockPortal_GetBuild_Call{Call: _e.mock.On("GetBuild", product, version, hash)} } func (_c *MockPortal_GetBuild_Call) Run(run func(product Product, version string, hash string)) *MockPortal_GetBuild_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 Product - if args[0] != nil { - arg0 = args[0].(Product) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(Product), args[1].(string), args[2].(string)) }) return _c } @@ -558,20 +472,14 @@ type MockPortal_ListBuilds_Call struct { } // ListBuilds is a helper method to define mock.On call -// - product Product +// - product func (_e *MockPortal_Expecter) ListBuilds(product interface{}) *MockPortal_ListBuilds_Call { return &MockPortal_ListBuilds_Call{Call: _e.mock.On("ListBuilds", product)} } func (_c *MockPortal_ListBuilds_Call) Run(run func(product Product)) *MockPortal_ListBuilds_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 Product - if args[0] != nil { - arg0 = args[0].(Product) - } - run( - arg0, - ) + run(args[0].(Product)) }) return _c } @@ -620,38 +528,17 @@ type MockPortal_RegisterAPIKey_Call struct { } // RegisterAPIKey is a helper method to define mock.On call -// - owner string -// - organization string -// - role string -// - expiresAt time.Time +// - owner +// - organization +// - role +// - expiresAt func (_e *MockPortal_Expecter) RegisterAPIKey(owner interface{}, organization interface{}, role interface{}, expiresAt interface{}) *MockPortal_RegisterAPIKey_Call { return &MockPortal_RegisterAPIKey_Call{Call: _e.mock.On("RegisterAPIKey", owner, organization, role, expiresAt)} } func (_c *MockPortal_RegisterAPIKey_Call) Run(run func(owner string, organization string, role string, expiresAt time.Time)) *MockPortal_RegisterAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - var arg3 time.Time - if args[3] != nil { - arg3 = args[3].(time.Time) - } - run( - arg0, - arg1, - arg2, - arg3, - ) + run(args[0].(string), args[1].(string), args[2].(string), args[3].(time.Time)) }) return _c } @@ -689,20 +576,14 @@ type MockPortal_RevokeAPIKey_Call struct { } // RevokeAPIKey is a helper method to define mock.On call -// - key string +// - key func (_e *MockPortal_Expecter) RevokeAPIKey(key interface{}) *MockPortal_RevokeAPIKey_Call { return &MockPortal_RevokeAPIKey_Call{Call: _e.mock.On("RevokeAPIKey", key)} } func (_c *MockPortal_RevokeAPIKey_Call) Run(run func(key string)) *MockPortal_RevokeAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -740,26 +621,15 @@ type MockPortal_UpdateAPIKey_Call struct { } // UpdateAPIKey is a helper method to define mock.On call -// - key string -// - expiresAt time.Time +// - key +// - expiresAt func (_e *MockPortal_Expecter) UpdateAPIKey(key interface{}, expiresAt interface{}) *MockPortal_UpdateAPIKey_Call { return &MockPortal_UpdateAPIKey_Call{Call: _e.mock.On("UpdateAPIKey", key, expiresAt)} } func (_c *MockPortal_UpdateAPIKey_Call) Run(run func(key string, expiresAt time.Time)) *MockPortal_UpdateAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 time.Time - if args[1] != nil { - arg1 = args[1].(time.Time) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(time.Time)) }) return _c } @@ -797,26 +667,15 @@ type MockPortal_VerifyBuildArtifactDownload_Call struct { } // VerifyBuildArtifactDownload is a helper method to define mock.On call -// - file io.Reader -// - download Build +// - file +// - download func (_e *MockPortal_Expecter) VerifyBuildArtifactDownload(file interface{}, download interface{}) *MockPortal_VerifyBuildArtifactDownload_Call { return &MockPortal_VerifyBuildArtifactDownload_Call{Call: _e.mock.On("VerifyBuildArtifactDownload", file, download)} } func (_c *MockPortal_VerifyBuildArtifactDownload_Call) Run(run func(file io.Reader, download Build)) *MockPortal_VerifyBuildArtifactDownload_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 io.Reader - if args[0] != nil { - arg0 = args[0].(io.Reader) - } - var arg1 Build - if args[1] != nil { - arg1 = args[1].(Build) - } - run( - arg0, - arg1, - ) + run(args[0].(io.Reader), args[1].(Build)) }) return _c } @@ -892,20 +751,14 @@ type MockHttpClient_Do_Call struct { } // Do is a helper method to define mock.On call -// - request *http.Request +// - request func (_e *MockHttpClient_Expecter) Do(request interface{}) *MockHttpClient_Do_Call { return &MockHttpClient_Do_Call{Call: _e.mock.On("Do", request)} } func (_c *MockHttpClient_Do_Call) Run(run func(request *http.Request)) *MockHttpClient_Do_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 *http.Request - if args[0] != nil { - arg0 = args[0].(*http.Request) - } - run( - arg0, - ) + run(args[0].(*http.Request)) }) return _c } diff --git a/internal/system/mocks.go b/internal/system/mocks.go index ee80bc69..cf8e9292 100644 --- a/internal/system/mocks.go +++ b/internal/system/mocks.go @@ -58,32 +58,16 @@ type MockImageManager_BuildImage_Call struct { } // BuildImage is a helper method to define mock.On call -// - dockerfile string -// - tag string -// - buildContext string +// - dockerfile +// - tag +// - buildContext func (_e *MockImageManager_Expecter) BuildImage(dockerfile interface{}, tag interface{}, buildContext interface{}) *MockImageManager_BuildImage_Call { return &MockImageManager_BuildImage_Call{Call: _e.mock.On("BuildImage", dockerfile, tag, buildContext)} } func (_c *MockImageManager_BuildImage_Call) Run(run func(dockerfile string, tag string, buildContext string)) *MockImageManager_BuildImage_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(string), args[2].(string)) }) return _c } @@ -121,20 +105,14 @@ type MockImageManager_LoadImage_Call struct { } // LoadImage is a helper method to define mock.On call -// - imageTarPath string +// - imageTarPath func (_e *MockImageManager_Expecter) LoadImage(imageTarPath interface{}) *MockImageManager_LoadImage_Call { return &MockImageManager_LoadImage_Call{Call: _e.mock.On("LoadImage", imageTarPath)} } func (_c *MockImageManager_LoadImage_Call) Run(run func(imageTarPath string)) *MockImageManager_LoadImage_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -172,20 +150,14 @@ type MockImageManager_PushImage_Call struct { } // PushImage is a helper method to define mock.On call -// - tag string +// - tag func (_e *MockImageManager_Expecter) PushImage(tag interface{}) *MockImageManager_PushImage_Call { return &MockImageManager_PushImage_Call{Call: _e.mock.On("PushImage", tag)} } func (_c *MockImageManager_PushImage_Call) Run(run func(tag string)) *MockImageManager_PushImage_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } diff --git a/internal/util/mocks.go b/internal/util/mocks.go index abfdd4f8..394f6b82 100644 --- a/internal/util/mocks.go +++ b/internal/util/mocks.go @@ -70,26 +70,15 @@ type MockDockerfileManager_UpdateFromStatement_Call struct { } // UpdateFromStatement is a helper method to define mock.On call -// - dockerfile io.Reader -// - baseImage string +// - dockerfile +// - baseImage func (_e *MockDockerfileManager_Expecter) UpdateFromStatement(dockerfile interface{}, baseImage interface{}) *MockDockerfileManager_UpdateFromStatement_Call { return &MockDockerfileManager_UpdateFromStatement_Call{Call: _e.mock.On("UpdateFromStatement", dockerfile, baseImage)} } func (_c *MockDockerfileManager_UpdateFromStatement_Call) Run(run func(dockerfile io.Reader, baseImage string)) *MockDockerfileManager_UpdateFromStatement_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 io.Reader - if args[0] != nil { - arg0 = args[0].(io.Reader) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - run( - arg0, - arg1, - ) + run(args[0].(io.Reader), args[1].(string)) }) return _c } @@ -165,20 +154,14 @@ type MockFileIO_Create_Call struct { } // Create is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) Create(filename interface{}) *MockFileIO_Create_Call { return &MockFileIO_Create_Call{Call: _e.mock.On("Create", filename)} } func (_c *MockFileIO_Create_Call) Run(run func(filename string)) *MockFileIO_Create_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -216,32 +199,16 @@ type MockFileIO_CreateAndWrite_Call struct { } // CreateAndWrite is a helper method to define mock.On call -// - filePath string -// - data []byte -// - fileType string +// - filePath +// - data +// - fileType func (_e *MockFileIO_Expecter) CreateAndWrite(filePath interface{}, data interface{}, fileType interface{}) *MockFileIO_CreateAndWrite_Call { return &MockFileIO_CreateAndWrite_Call{Call: _e.mock.On("CreateAndWrite", filePath, data, fileType)} } func (_c *MockFileIO_CreateAndWrite_Call) Run(run func(filePath string, data []byte, fileType string)) *MockFileIO_CreateAndWrite_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 []byte - if args[1] != nil { - arg1 = args[1].([]byte) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].([]byte), args[2].(string)) }) return _c } @@ -279,20 +246,14 @@ type MockFileIO_Exists_Call struct { } // Exists is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) Exists(filename interface{}) *MockFileIO_Exists_Call { return &MockFileIO_Exists_Call{Call: _e.mock.On("Exists", filename)} } func (_c *MockFileIO_Exists_Call) Run(run func(filename string)) *MockFileIO_Exists_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -339,20 +300,14 @@ type MockFileIO_IsDirectory_Call struct { } // IsDirectory is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) IsDirectory(filename interface{}) *MockFileIO_IsDirectory_Call { return &MockFileIO_IsDirectory_Call{Call: _e.mock.On("IsDirectory", filename)} } func (_c *MockFileIO_IsDirectory_Call) Run(run func(filename string)) *MockFileIO_IsDirectory_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -390,26 +345,15 @@ type MockFileIO_MkdirAll_Call struct { } // MkdirAll is a helper method to define mock.On call -// - path string -// - perm os.FileMode +// - path +// - perm func (_e *MockFileIO_Expecter) MkdirAll(path interface{}, perm interface{}) *MockFileIO_MkdirAll_Call { return &MockFileIO_MkdirAll_Call{Call: _e.mock.On("MkdirAll", path, perm)} } func (_c *MockFileIO_MkdirAll_Call) Run(run func(path string, perm os.FileMode)) *MockFileIO_MkdirAll_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 os.FileMode - if args[1] != nil { - arg1 = args[1].(os.FileMode) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(os.FileMode)) }) return _c } @@ -458,20 +402,14 @@ type MockFileIO_Open_Call struct { } // Open is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) Open(filename interface{}) *MockFileIO_Open_Call { return &MockFileIO_Open_Call{Call: _e.mock.On("Open", filename)} } func (_c *MockFileIO_Open_Call) Run(run func(filename string)) *MockFileIO_Open_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -520,20 +458,14 @@ type MockFileIO_OpenAppend_Call struct { } // OpenAppend is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) OpenAppend(filename interface{}) *MockFileIO_OpenAppend_Call { return &MockFileIO_OpenAppend_Call{Call: _e.mock.On("OpenAppend", filename)} } func (_c *MockFileIO_OpenAppend_Call) Run(run func(filename string)) *MockFileIO_OpenAppend_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -582,32 +514,16 @@ type MockFileIO_OpenFile_Call struct { } // OpenFile is a helper method to define mock.On call -// - name string -// - flag int -// - perm os.FileMode +// - name +// - flag +// - perm func (_e *MockFileIO_Expecter) OpenFile(name interface{}, flag interface{}, perm interface{}) *MockFileIO_OpenFile_Call { return &MockFileIO_OpenFile_Call{Call: _e.mock.On("OpenFile", name, flag, perm)} } func (_c *MockFileIO_OpenFile_Call) Run(run func(name string, flag int, perm os.FileMode)) *MockFileIO_OpenFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 int - if args[1] != nil { - arg1 = args[1].(int) - } - var arg2 os.FileMode - if args[2] != nil { - arg2 = args[2].(os.FileMode) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].(int), args[2].(os.FileMode)) }) return _c } @@ -656,20 +572,14 @@ type MockFileIO_ReadDir_Call struct { } // ReadDir is a helper method to define mock.On call -// - dirname string +// - dirname func (_e *MockFileIO_Expecter) ReadDir(dirname interface{}) *MockFileIO_ReadDir_Call { return &MockFileIO_ReadDir_Call{Call: _e.mock.On("ReadDir", dirname)} } func (_c *MockFileIO_ReadDir_Call) Run(run func(dirname string)) *MockFileIO_ReadDir_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -718,20 +628,14 @@ type MockFileIO_ReadFile_Call struct { } // ReadFile is a helper method to define mock.On call -// - filename string +// - filename func (_e *MockFileIO_Expecter) ReadFile(filename interface{}) *MockFileIO_ReadFile_Call { return &MockFileIO_ReadFile_Call{Call: _e.mock.On("ReadFile", filename)} } func (_c *MockFileIO_ReadFile_Call) Run(run func(filename string)) *MockFileIO_ReadFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -769,32 +673,16 @@ type MockFileIO_WriteFile_Call struct { } // WriteFile is a helper method to define mock.On call -// - filename string -// - data []byte -// - perm os.FileMode +// - filename +// - data +// - perm func (_e *MockFileIO_Expecter) WriteFile(filename interface{}, data interface{}, perm interface{}) *MockFileIO_WriteFile_Call { return &MockFileIO_WriteFile_Call{Call: _e.mock.On("WriteFile", filename, data, perm)} } func (_c *MockFileIO_WriteFile_Call) Run(run func(filename string, data []byte, perm os.FileMode)) *MockFileIO_WriteFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 []byte - if args[1] != nil { - arg1 = args[1].([]byte) - } - var arg2 os.FileMode - if args[2] != nil { - arg2 = args[2].(os.FileMode) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(string), args[1].([]byte), args[2].(os.FileMode)) }) return _c } @@ -924,8 +812,8 @@ type MockTableWriter_AppendHeader_Call struct { } // AppendHeader is a helper method to define mock.On call -// - row table.Row -// - configs ...table.RowConfig +// - row +// - configs func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...interface{}) *MockTableWriter_AppendHeader_Call { return &MockTableWriter_AppendHeader_Call{Call: _e.mock.On("AppendHeader", append([]interface{}{row}, configs...)...)} @@ -933,20 +821,13 @@ func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...int func (_c *MockTableWriter_AppendHeader_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendHeader_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 table.Row - if args[0] != nil { - arg0 = args[0].(table.Row) + variadicArgs := make([]table.RowConfig, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(table.RowConfig) + } } - var arg1 []table.RowConfig - var variadicArgs []table.RowConfig - if len(args) > 1 { - variadicArgs = args[1].([]table.RowConfig) - } - arg1 = variadicArgs - run( - arg0, - arg1..., - ) + run(args[0].(table.Row), variadicArgs...) }) return _c } @@ -978,8 +859,8 @@ type MockTableWriter_AppendRow_Call struct { } // AppendRow is a helper method to define mock.On call -// - row table.Row -// - configs ...table.RowConfig +// - row +// - configs func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interface{}) *MockTableWriter_AppendRow_Call { return &MockTableWriter_AppendRow_Call{Call: _e.mock.On("AppendRow", append([]interface{}{row}, configs...)...)} @@ -987,20 +868,13 @@ func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interf func (_c *MockTableWriter_AppendRow_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendRow_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 table.Row - if args[0] != nil { - arg0 = args[0].(table.Row) - } - var arg1 []table.RowConfig - var variadicArgs []table.RowConfig - if len(args) > 1 { - variadicArgs = args[1].([]table.RowConfig) + variadicArgs := make([]table.RowConfig, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(table.RowConfig) + } } - arg1 = variadicArgs - run( - arg0, - arg1..., - ) + run(args[0].(table.Row), variadicArgs...) }) return _c } From 095c55d416291248ca3a915f501c3fb1a6184d0b Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 28 Jan 2026 15:42:54 +0000 Subject: [PATCH 47/65] chore(docs): Auto-update docs and licenses Signed-off-by: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> --- cli/cmd/mocks.go | 24 +++- internal/installer/mocks.go | 237 +++++++++++++++++++++++++++++------- internal/portal/mocks.go | 225 ++++++++++++++++++++++++++++------ internal/system/mocks.go | 44 +++++-- internal/util/mocks.go | 222 +++++++++++++++++++++++++-------- 5 files changed, 610 insertions(+), 142 deletions(-) diff --git a/cli/cmd/mocks.go b/cli/cmd/mocks.go index 60357284..26b37df5 100644 --- a/cli/cmd/mocks.go +++ b/cli/cmd/mocks.go @@ -75,16 +75,32 @@ type MockOMSUpdater_Update_Call struct { } // Update is a helper method to define mock.On call -// - ctx -// - current -// - repo +// - ctx context.Context +// - current string +// - repo selfupdate.Repository func (_e *MockOMSUpdater_Expecter) Update(ctx interface{}, current interface{}, repo interface{}) *MockOMSUpdater_Update_Call { return &MockOMSUpdater_Update_Call{Call: _e.mock.On("Update", ctx, current, repo)} } func (_c *MockOMSUpdater_Update_Call) Run(run func(ctx context.Context, current string, repo selfupdate.Repository)) *MockOMSUpdater_Update_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(selfupdate.Repository)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 selfupdate.Repository + if args[2] != nil { + arg2 = args[2].(selfupdate.Repository) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } diff --git a/internal/installer/mocks.go b/internal/installer/mocks.go index cf9131fc..0bf9fda6 100644 --- a/internal/installer/mocks.go +++ b/internal/installer/mocks.go @@ -69,14 +69,20 @@ type MockConfigManager_ParseConfigYaml_Call struct { } // ParseConfigYaml is a helper method to define mock.On call -// - configPath +// - configPath string func (_e *MockConfigManager_Expecter) ParseConfigYaml(configPath interface{}) *MockConfigManager_ParseConfigYaml_Call { return &MockConfigManager_ParseConfigYaml_Call{Call: _e.mock.On("ParseConfigYaml", configPath)} } func (_c *MockConfigManager_ParseConfigYaml_Call) Run(run func(configPath string)) *MockConfigManager_ParseConfigYaml_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -141,14 +147,20 @@ type MockInstallConfigManager_ApplyProfile_Call struct { } // ApplyProfile is a helper method to define mock.On call -// - profile +// - profile string func (_e *MockInstallConfigManager_Expecter) ApplyProfile(profile interface{}) *MockInstallConfigManager_ApplyProfile_Call { return &MockInstallConfigManager_ApplyProfile_Call{Call: _e.mock.On("ApplyProfile", profile)} } func (_c *MockInstallConfigManager_ApplyProfile_Call) Run(run func(profile string)) *MockInstallConfigManager_ApplyProfile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -366,14 +378,20 @@ type MockInstallConfigManager_LoadInstallConfigFromFile_Call struct { } // LoadInstallConfigFromFile is a helper method to define mock.On call -// - configPath +// - configPath string func (_e *MockInstallConfigManager_Expecter) LoadInstallConfigFromFile(configPath interface{}) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { return &MockInstallConfigManager_LoadInstallConfigFromFile_Call{Call: _e.mock.On("LoadInstallConfigFromFile", configPath)} } func (_c *MockInstallConfigManager_LoadInstallConfigFromFile_Call) Run(run func(configPath string)) *MockInstallConfigManager_LoadInstallConfigFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -411,14 +429,20 @@ type MockInstallConfigManager_LoadVaultFromFile_Call struct { } // LoadVaultFromFile is a helper method to define mock.On call -// - vaultPath +// - vaultPath string func (_e *MockInstallConfigManager_Expecter) LoadVaultFromFile(vaultPath interface{}) *MockInstallConfigManager_LoadVaultFromFile_Call { return &MockInstallConfigManager_LoadVaultFromFile_Call{Call: _e.mock.On("LoadVaultFromFile", vaultPath)} } func (_c *MockInstallConfigManager_LoadVaultFromFile_Call) Run(run func(vaultPath string)) *MockInstallConfigManager_LoadVaultFromFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -592,15 +616,26 @@ type MockInstallConfigManager_WriteInstallConfig_Call struct { } // WriteInstallConfig is a helper method to define mock.On call -// - configPath -// - withComments +// - configPath string +// - withComments bool func (_e *MockInstallConfigManager_Expecter) WriteInstallConfig(configPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteInstallConfig_Call { return &MockInstallConfigManager_WriteInstallConfig_Call{Call: _e.mock.On("WriteInstallConfig", configPath, withComments)} } func (_c *MockInstallConfigManager_WriteInstallConfig_Call) Run(run func(configPath string, withComments bool)) *MockInstallConfigManager_WriteInstallConfig_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -638,15 +673,26 @@ type MockInstallConfigManager_WriteVault_Call struct { } // WriteVault is a helper method to define mock.On call -// - vaultPath -// - withComments +// - vaultPath string +// - withComments bool func (_e *MockInstallConfigManager_Expecter) WriteVault(vaultPath interface{}, withComments interface{}) *MockInstallConfigManager_WriteVault_Call { return &MockInstallConfigManager_WriteVault_Call{Call: _e.mock.On("WriteVault", vaultPath, withComments)} } func (_c *MockInstallConfigManager_WriteVault_Call) Run(run func(vaultPath string, withComments bool)) *MockInstallConfigManager_WriteVault_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -720,16 +766,32 @@ type MockK0sManager_Download_Call struct { } // Download is a helper method to define mock.On call -// - version -// - force -// - quiet +// - version string +// - force bool +// - quiet bool func (_e *MockK0sManager_Expecter) Download(version interface{}, force interface{}, quiet interface{}) *MockK0sManager_Download_Call { return &MockK0sManager_Download_Call{Call: _e.mock.On("Download", version, force, quiet)} } func (_c *MockK0sManager_Download_Call) Run(run func(version string, force bool, quiet bool)) *MockK0sManager_Download_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool), args[2].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + var arg2 bool + if args[2] != nil { + arg2 = args[2].(bool) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -847,16 +909,32 @@ type MockK0sctlManager_Apply_Call struct { } // Apply is a helper method to define mock.On call -// - configPath -// - k0sctlPath -// - force +// - configPath string +// - k0sctlPath string +// - force bool func (_e *MockK0sctlManager_Expecter) Apply(configPath interface{}, k0sctlPath interface{}, force interface{}) *MockK0sctlManager_Apply_Call { return &MockK0sctlManager_Apply_Call{Call: _e.mock.On("Apply", configPath, k0sctlPath, force)} } func (_c *MockK0sctlManager_Apply_Call) Run(run func(configPath string, k0sctlPath string, force bool)) *MockK0sctlManager_Apply_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 bool + if args[2] != nil { + arg2 = args[2].(bool) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -903,16 +981,32 @@ type MockK0sctlManager_Download_Call struct { } // Download is a helper method to define mock.On call -// - version -// - force -// - quiet +// - version string +// - force bool +// - quiet bool func (_e *MockK0sctlManager_Expecter) Download(version interface{}, force interface{}, quiet interface{}) *MockK0sctlManager_Download_Call { return &MockK0sctlManager_Download_Call{Call: _e.mock.On("Download", version, force, quiet)} } func (_c *MockK0sctlManager_Download_Call) Run(run func(version string, force bool, quiet bool)) *MockK0sctlManager_Download_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool), args[2].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + var arg2 bool + if args[2] != nil { + arg2 = args[2].(bool) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -1003,15 +1097,26 @@ type MockK0sctlManager_Reset_Call struct { } // Reset is a helper method to define mock.On call -// - configPath -// - k0sctlPath +// - configPath string +// - k0sctlPath string func (_e *MockK0sctlManager_Expecter) Reset(configPath interface{}, k0sctlPath interface{}) *MockK0sctlManager_Reset_Call { return &MockK0sctlManager_Reset_Call{Call: _e.mock.On("Reset", configPath, k0sctlPath)} } func (_c *MockK0sctlManager_Reset_Call) Run(run func(configPath string, k0sctlPath string)) *MockK0sctlManager_Reset_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } @@ -1076,14 +1181,20 @@ type MockPackageManager_Extract_Call struct { } // Extract is a helper method to define mock.On call -// - force +// - force bool func (_e *MockPackageManager_Expecter) Extract(force interface{}) *MockPackageManager_Extract_Call { return &MockPackageManager_Extract_Call{Call: _e.mock.On("Extract", force)} } func (_c *MockPackageManager_Extract_Call) Run(run func(force bool)) *MockPackageManager_Extract_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(bool)) + var arg0 bool + if args[0] != nil { + arg0 = args[0].(bool) + } + run( + arg0, + ) }) return _c } @@ -1121,15 +1232,26 @@ type MockPackageManager_ExtractDependency_Call struct { } // ExtractDependency is a helper method to define mock.On call -// - file -// - force +// - file string +// - force bool func (_e *MockPackageManager_Expecter) ExtractDependency(file interface{}, force interface{}) *MockPackageManager_ExtractDependency_Call { return &MockPackageManager_ExtractDependency_Call{Call: _e.mock.On("ExtractDependency", file, force)} } func (_c *MockPackageManager_ExtractDependency_Call) Run(run func(file string, force bool)) *MockPackageManager_ExtractDependency_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -1176,14 +1298,20 @@ type MockPackageManager_ExtractOciImageIndex_Call struct { } // ExtractOciImageIndex is a helper method to define mock.On call -// - imagefile +// - imagefile string func (_e *MockPackageManager_Expecter) ExtractOciImageIndex(imagefile interface{}) *MockPackageManager_ExtractOciImageIndex_Call { return &MockPackageManager_ExtractOciImageIndex_Call{Call: _e.mock.On("ExtractOciImageIndex", imagefile)} } func (_c *MockPackageManager_ExtractOciImageIndex_Call) Run(run func(imagefile string)) *MockPackageManager_ExtractOciImageIndex_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -1276,14 +1404,20 @@ type MockPackageManager_GetBaseimageName_Call struct { } // GetBaseimageName is a helper method to define mock.On call -// - baseimage +// - baseimage string func (_e *MockPackageManager_Expecter) GetBaseimageName(baseimage interface{}) *MockPackageManager_GetBaseimageName_Call { return &MockPackageManager_GetBaseimageName_Call{Call: _e.mock.On("GetBaseimageName", baseimage)} } func (_c *MockPackageManager_GetBaseimageName_Call) Run(run func(baseimage string)) *MockPackageManager_GetBaseimageName_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -1330,15 +1464,26 @@ type MockPackageManager_GetBaseimagePath_Call struct { } // GetBaseimagePath is a helper method to define mock.On call -// - baseimage -// - force +// - baseimage string +// - force bool func (_e *MockPackageManager_Expecter) GetBaseimagePath(baseimage interface{}, force interface{}) *MockPackageManager_GetBaseimagePath_Call { return &MockPackageManager_GetBaseimagePath_Call{Call: _e.mock.On("GetBaseimagePath", baseimage, force)} } func (_c *MockPackageManager_GetBaseimagePath_Call) Run(run func(baseimage string, force bool)) *MockPackageManager_GetBaseimagePath_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 bool + if args[1] != nil { + arg1 = args[1].(bool) + } + run( + arg0, + arg1, + ) }) return _c } @@ -1429,14 +1574,20 @@ type MockPackageManager_GetDependencyPath_Call struct { } // GetDependencyPath is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockPackageManager_Expecter) GetDependencyPath(filename interface{}) *MockPackageManager_GetDependencyPath_Call { return &MockPackageManager_GetDependencyPath_Call{Call: _e.mock.On("GetDependencyPath", filename)} } func (_c *MockPackageManager_GetDependencyPath_Call) Run(run func(filename string)) *MockPackageManager_GetDependencyPath_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } diff --git a/internal/portal/mocks.go b/internal/portal/mocks.go index 83372369..c162a3ee 100644 --- a/internal/portal/mocks.go +++ b/internal/portal/mocks.go @@ -61,16 +61,32 @@ type MockHttp_Download_Call struct { } // Download is a helper method to define mock.On call -// - url -// - file -// - quiet +// - url string +// - file io.Writer +// - quiet bool func (_e *MockHttp_Expecter) Download(url interface{}, file interface{}, quiet interface{}) *MockHttp_Download_Call { return &MockHttp_Download_Call{Call: _e.mock.On("Download", url, file, quiet)} } func (_c *MockHttp_Download_Call) Run(run func(url string, file io.Writer, quiet bool)) *MockHttp_Download_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(io.Writer), args[2].(bool)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 io.Writer + if args[1] != nil { + arg1 = args[1].(io.Writer) + } + var arg2 bool + if args[2] != nil { + arg2 = args[2].(bool) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -119,14 +135,20 @@ type MockHttp_Get_Call struct { } // Get is a helper method to define mock.On call -// - url +// - url string func (_e *MockHttp_Expecter) Get(url interface{}) *MockHttp_Get_Call { return &MockHttp_Get_Call{Call: _e.mock.On("Get", url)} } func (_c *MockHttp_Get_Call) Run(run func(url string)) *MockHttp_Get_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -175,16 +197,32 @@ type MockHttp_Request_Call struct { } // Request is a helper method to define mock.On call -// - url -// - method -// - body +// - url string +// - method string +// - body io.Reader func (_e *MockHttp_Expecter) Request(url interface{}, method interface{}, body interface{}) *MockHttp_Request_Call { return &MockHttp_Request_Call{Call: _e.mock.On("Request", url, method, body)} } func (_c *MockHttp_Request_Call) Run(run func(url string, method string, body io.Reader)) *MockHttp_Request_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(io.Reader)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 io.Reader + if args[2] != nil { + arg2 = args[2].(io.Reader) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -249,18 +287,44 @@ type MockPortal_DownloadBuildArtifact_Call struct { } // DownloadBuildArtifact is a helper method to define mock.On call -// - product -// - build -// - file -// - startByte -// - quiet +// - product Product +// - build Build +// - file io.Writer +// - startByte int +// - quiet bool func (_e *MockPortal_Expecter) DownloadBuildArtifact(product interface{}, build interface{}, file interface{}, startByte interface{}, quiet interface{}) *MockPortal_DownloadBuildArtifact_Call { return &MockPortal_DownloadBuildArtifact_Call{Call: _e.mock.On("DownloadBuildArtifact", product, build, file, startByte, quiet)} } func (_c *MockPortal_DownloadBuildArtifact_Call) Run(run func(product Product, build Build, file io.Writer, startByte int, quiet bool)) *MockPortal_DownloadBuildArtifact_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(Product), args[1].(Build), args[2].(io.Writer), args[3].(int), args[4].(bool)) + var arg0 Product + if args[0] != nil { + arg0 = args[0].(Product) + } + var arg1 Build + if args[1] != nil { + arg1 = args[1].(Build) + } + var arg2 io.Writer + if args[2] != nil { + arg2 = args[2].(io.Writer) + } + var arg3 int + if args[3] != nil { + arg3 = args[3].(int) + } + var arg4 bool + if args[4] != nil { + arg4 = args[4].(bool) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) }) return _c } @@ -307,14 +371,20 @@ type MockPortal_GetApiKeyId_Call struct { } // GetApiKeyId is a helper method to define mock.On call -// - oldKey +// - oldKey string func (_e *MockPortal_Expecter) GetApiKeyId(oldKey interface{}) *MockPortal_GetApiKeyId_Call { return &MockPortal_GetApiKeyId_Call{Call: _e.mock.On("GetApiKeyId", oldKey)} } func (_c *MockPortal_GetApiKeyId_Call) Run(run func(oldKey string)) *MockPortal_GetApiKeyId_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -361,16 +431,32 @@ type MockPortal_GetBuild_Call struct { } // GetBuild is a helper method to define mock.On call -// - product -// - version -// - hash +// - product Product +// - version string +// - hash string func (_e *MockPortal_Expecter) GetBuild(product interface{}, version interface{}, hash interface{}) *MockPortal_GetBuild_Call { return &MockPortal_GetBuild_Call{Call: _e.mock.On("GetBuild", product, version, hash)} } func (_c *MockPortal_GetBuild_Call) Run(run func(product Product, version string, hash string)) *MockPortal_GetBuild_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(Product), args[1].(string), args[2].(string)) + var arg0 Product + if args[0] != nil { + arg0 = args[0].(Product) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -472,14 +558,20 @@ type MockPortal_ListBuilds_Call struct { } // ListBuilds is a helper method to define mock.On call -// - product +// - product Product func (_e *MockPortal_Expecter) ListBuilds(product interface{}) *MockPortal_ListBuilds_Call { return &MockPortal_ListBuilds_Call{Call: _e.mock.On("ListBuilds", product)} } func (_c *MockPortal_ListBuilds_Call) Run(run func(product Product)) *MockPortal_ListBuilds_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(Product)) + var arg0 Product + if args[0] != nil { + arg0 = args[0].(Product) + } + run( + arg0, + ) }) return _c } @@ -528,17 +620,38 @@ type MockPortal_RegisterAPIKey_Call struct { } // RegisterAPIKey is a helper method to define mock.On call -// - owner -// - organization -// - role -// - expiresAt +// - owner string +// - organization string +// - role string +// - expiresAt time.Time func (_e *MockPortal_Expecter) RegisterAPIKey(owner interface{}, organization interface{}, role interface{}, expiresAt interface{}) *MockPortal_RegisterAPIKey_Call { return &MockPortal_RegisterAPIKey_Call{Call: _e.mock.On("RegisterAPIKey", owner, organization, role, expiresAt)} } func (_c *MockPortal_RegisterAPIKey_Call) Run(run func(owner string, organization string, role string, expiresAt time.Time)) *MockPortal_RegisterAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(string), args[3].(time.Time)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 time.Time + if args[3] != nil { + arg3 = args[3].(time.Time) + } + run( + arg0, + arg1, + arg2, + arg3, + ) }) return _c } @@ -576,14 +689,20 @@ type MockPortal_RevokeAPIKey_Call struct { } // RevokeAPIKey is a helper method to define mock.On call -// - key +// - key string func (_e *MockPortal_Expecter) RevokeAPIKey(key interface{}) *MockPortal_RevokeAPIKey_Call { return &MockPortal_RevokeAPIKey_Call{Call: _e.mock.On("RevokeAPIKey", key)} } func (_c *MockPortal_RevokeAPIKey_Call) Run(run func(key string)) *MockPortal_RevokeAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -621,15 +740,26 @@ type MockPortal_UpdateAPIKey_Call struct { } // UpdateAPIKey is a helper method to define mock.On call -// - key -// - expiresAt +// - key string +// - expiresAt time.Time func (_e *MockPortal_Expecter) UpdateAPIKey(key interface{}, expiresAt interface{}) *MockPortal_UpdateAPIKey_Call { return &MockPortal_UpdateAPIKey_Call{Call: _e.mock.On("UpdateAPIKey", key, expiresAt)} } func (_c *MockPortal_UpdateAPIKey_Call) Run(run func(key string, expiresAt time.Time)) *MockPortal_UpdateAPIKey_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(time.Time)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 time.Time + if args[1] != nil { + arg1 = args[1].(time.Time) + } + run( + arg0, + arg1, + ) }) return _c } @@ -667,15 +797,26 @@ type MockPortal_VerifyBuildArtifactDownload_Call struct { } // VerifyBuildArtifactDownload is a helper method to define mock.On call -// - file -// - download +// - file io.Reader +// - download Build func (_e *MockPortal_Expecter) VerifyBuildArtifactDownload(file interface{}, download interface{}) *MockPortal_VerifyBuildArtifactDownload_Call { return &MockPortal_VerifyBuildArtifactDownload_Call{Call: _e.mock.On("VerifyBuildArtifactDownload", file, download)} } func (_c *MockPortal_VerifyBuildArtifactDownload_Call) Run(run func(file io.Reader, download Build)) *MockPortal_VerifyBuildArtifactDownload_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(io.Reader), args[1].(Build)) + var arg0 io.Reader + if args[0] != nil { + arg0 = args[0].(io.Reader) + } + var arg1 Build + if args[1] != nil { + arg1 = args[1].(Build) + } + run( + arg0, + arg1, + ) }) return _c } @@ -751,14 +892,20 @@ type MockHttpClient_Do_Call struct { } // Do is a helper method to define mock.On call -// - request +// - request *http.Request func (_e *MockHttpClient_Expecter) Do(request interface{}) *MockHttpClient_Do_Call { return &MockHttpClient_Do_Call{Call: _e.mock.On("Do", request)} } func (_c *MockHttpClient_Do_Call) Run(run func(request *http.Request)) *MockHttpClient_Do_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*http.Request)) + var arg0 *http.Request + if args[0] != nil { + arg0 = args[0].(*http.Request) + } + run( + arg0, + ) }) return _c } diff --git a/internal/system/mocks.go b/internal/system/mocks.go index cf8e9292..ee80bc69 100644 --- a/internal/system/mocks.go +++ b/internal/system/mocks.go @@ -58,16 +58,32 @@ type MockImageManager_BuildImage_Call struct { } // BuildImage is a helper method to define mock.On call -// - dockerfile -// - tag -// - buildContext +// - dockerfile string +// - tag string +// - buildContext string func (_e *MockImageManager_Expecter) BuildImage(dockerfile interface{}, tag interface{}, buildContext interface{}) *MockImageManager_BuildImage_Call { return &MockImageManager_BuildImage_Call{Call: _e.mock.On("BuildImage", dockerfile, tag, buildContext)} } func (_c *MockImageManager_BuildImage_Call) Run(run func(dockerfile string, tag string, buildContext string)) *MockImageManager_BuildImage_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -105,14 +121,20 @@ type MockImageManager_LoadImage_Call struct { } // LoadImage is a helper method to define mock.On call -// - imageTarPath +// - imageTarPath string func (_e *MockImageManager_Expecter) LoadImage(imageTarPath interface{}) *MockImageManager_LoadImage_Call { return &MockImageManager_LoadImage_Call{Call: _e.mock.On("LoadImage", imageTarPath)} } func (_c *MockImageManager_LoadImage_Call) Run(run func(imageTarPath string)) *MockImageManager_LoadImage_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -150,14 +172,20 @@ type MockImageManager_PushImage_Call struct { } // PushImage is a helper method to define mock.On call -// - tag +// - tag string func (_e *MockImageManager_Expecter) PushImage(tag interface{}) *MockImageManager_PushImage_Call { return &MockImageManager_PushImage_Call{Call: _e.mock.On("PushImage", tag)} } func (_c *MockImageManager_PushImage_Call) Run(run func(tag string)) *MockImageManager_PushImage_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } diff --git a/internal/util/mocks.go b/internal/util/mocks.go index 394f6b82..abfdd4f8 100644 --- a/internal/util/mocks.go +++ b/internal/util/mocks.go @@ -70,15 +70,26 @@ type MockDockerfileManager_UpdateFromStatement_Call struct { } // UpdateFromStatement is a helper method to define mock.On call -// - dockerfile -// - baseImage +// - dockerfile io.Reader +// - baseImage string func (_e *MockDockerfileManager_Expecter) UpdateFromStatement(dockerfile interface{}, baseImage interface{}) *MockDockerfileManager_UpdateFromStatement_Call { return &MockDockerfileManager_UpdateFromStatement_Call{Call: _e.mock.On("UpdateFromStatement", dockerfile, baseImage)} } func (_c *MockDockerfileManager_UpdateFromStatement_Call) Run(run func(dockerfile io.Reader, baseImage string)) *MockDockerfileManager_UpdateFromStatement_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(io.Reader), args[1].(string)) + var arg0 io.Reader + if args[0] != nil { + arg0 = args[0].(io.Reader) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } @@ -154,14 +165,20 @@ type MockFileIO_Create_Call struct { } // Create is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) Create(filename interface{}) *MockFileIO_Create_Call { return &MockFileIO_Create_Call{Call: _e.mock.On("Create", filename)} } func (_c *MockFileIO_Create_Call) Run(run func(filename string)) *MockFileIO_Create_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -199,16 +216,32 @@ type MockFileIO_CreateAndWrite_Call struct { } // CreateAndWrite is a helper method to define mock.On call -// - filePath -// - data -// - fileType +// - filePath string +// - data []byte +// - fileType string func (_e *MockFileIO_Expecter) CreateAndWrite(filePath interface{}, data interface{}, fileType interface{}) *MockFileIO_CreateAndWrite_Call { return &MockFileIO_CreateAndWrite_Call{Call: _e.mock.On("CreateAndWrite", filePath, data, fileType)} } func (_c *MockFileIO_CreateAndWrite_Call) Run(run func(filePath string, data []byte, fileType string)) *MockFileIO_CreateAndWrite_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].([]byte), args[2].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 []byte + if args[1] != nil { + arg1 = args[1].([]byte) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -246,14 +279,20 @@ type MockFileIO_Exists_Call struct { } // Exists is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) Exists(filename interface{}) *MockFileIO_Exists_Call { return &MockFileIO_Exists_Call{Call: _e.mock.On("Exists", filename)} } func (_c *MockFileIO_Exists_Call) Run(run func(filename string)) *MockFileIO_Exists_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -300,14 +339,20 @@ type MockFileIO_IsDirectory_Call struct { } // IsDirectory is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) IsDirectory(filename interface{}) *MockFileIO_IsDirectory_Call { return &MockFileIO_IsDirectory_Call{Call: _e.mock.On("IsDirectory", filename)} } func (_c *MockFileIO_IsDirectory_Call) Run(run func(filename string)) *MockFileIO_IsDirectory_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -345,15 +390,26 @@ type MockFileIO_MkdirAll_Call struct { } // MkdirAll is a helper method to define mock.On call -// - path -// - perm +// - path string +// - perm os.FileMode func (_e *MockFileIO_Expecter) MkdirAll(path interface{}, perm interface{}) *MockFileIO_MkdirAll_Call { return &MockFileIO_MkdirAll_Call{Call: _e.mock.On("MkdirAll", path, perm)} } func (_c *MockFileIO_MkdirAll_Call) Run(run func(path string, perm os.FileMode)) *MockFileIO_MkdirAll_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(os.FileMode)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 os.FileMode + if args[1] != nil { + arg1 = args[1].(os.FileMode) + } + run( + arg0, + arg1, + ) }) return _c } @@ -402,14 +458,20 @@ type MockFileIO_Open_Call struct { } // Open is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) Open(filename interface{}) *MockFileIO_Open_Call { return &MockFileIO_Open_Call{Call: _e.mock.On("Open", filename)} } func (_c *MockFileIO_Open_Call) Run(run func(filename string)) *MockFileIO_Open_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -458,14 +520,20 @@ type MockFileIO_OpenAppend_Call struct { } // OpenAppend is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) OpenAppend(filename interface{}) *MockFileIO_OpenAppend_Call { return &MockFileIO_OpenAppend_Call{Call: _e.mock.On("OpenAppend", filename)} } func (_c *MockFileIO_OpenAppend_Call) Run(run func(filename string)) *MockFileIO_OpenAppend_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -514,16 +582,32 @@ type MockFileIO_OpenFile_Call struct { } // OpenFile is a helper method to define mock.On call -// - name -// - flag -// - perm +// - name string +// - flag int +// - perm os.FileMode func (_e *MockFileIO_Expecter) OpenFile(name interface{}, flag interface{}, perm interface{}) *MockFileIO_OpenFile_Call { return &MockFileIO_OpenFile_Call{Call: _e.mock.On("OpenFile", name, flag, perm)} } func (_c *MockFileIO_OpenFile_Call) Run(run func(name string, flag int, perm os.FileMode)) *MockFileIO_OpenFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(int), args[2].(os.FileMode)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 int + if args[1] != nil { + arg1 = args[1].(int) + } + var arg2 os.FileMode + if args[2] != nil { + arg2 = args[2].(os.FileMode) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -572,14 +656,20 @@ type MockFileIO_ReadDir_Call struct { } // ReadDir is a helper method to define mock.On call -// - dirname +// - dirname string func (_e *MockFileIO_Expecter) ReadDir(dirname interface{}) *MockFileIO_ReadDir_Call { return &MockFileIO_ReadDir_Call{Call: _e.mock.On("ReadDir", dirname)} } func (_c *MockFileIO_ReadDir_Call) Run(run func(dirname string)) *MockFileIO_ReadDir_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -628,14 +718,20 @@ type MockFileIO_ReadFile_Call struct { } // ReadFile is a helper method to define mock.On call -// - filename +// - filename string func (_e *MockFileIO_Expecter) ReadFile(filename interface{}) *MockFileIO_ReadFile_Call { return &MockFileIO_ReadFile_Call{Call: _e.mock.On("ReadFile", filename)} } func (_c *MockFileIO_ReadFile_Call) Run(run func(filename string)) *MockFileIO_ReadFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -673,16 +769,32 @@ type MockFileIO_WriteFile_Call struct { } // WriteFile is a helper method to define mock.On call -// - filename -// - data -// - perm +// - filename string +// - data []byte +// - perm os.FileMode func (_e *MockFileIO_Expecter) WriteFile(filename interface{}, data interface{}, perm interface{}) *MockFileIO_WriteFile_Call { return &MockFileIO_WriteFile_Call{Call: _e.mock.On("WriteFile", filename, data, perm)} } func (_c *MockFileIO_WriteFile_Call) Run(run func(filename string, data []byte, perm os.FileMode)) *MockFileIO_WriteFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].([]byte), args[2].(os.FileMode)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 []byte + if args[1] != nil { + arg1 = args[1].([]byte) + } + var arg2 os.FileMode + if args[2] != nil { + arg2 = args[2].(os.FileMode) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -812,8 +924,8 @@ type MockTableWriter_AppendHeader_Call struct { } // AppendHeader is a helper method to define mock.On call -// - row -// - configs +// - row table.Row +// - configs ...table.RowConfig func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...interface{}) *MockTableWriter_AppendHeader_Call { return &MockTableWriter_AppendHeader_Call{Call: _e.mock.On("AppendHeader", append([]interface{}{row}, configs...)...)} @@ -821,13 +933,20 @@ func (_e *MockTableWriter_Expecter) AppendHeader(row interface{}, configs ...int func (_c *MockTableWriter_AppendHeader_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendHeader_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]table.RowConfig, len(args)-1) - for i, a := range args[1:] { - if a != nil { - variadicArgs[i] = a.(table.RowConfig) - } + var arg0 table.Row + if args[0] != nil { + arg0 = args[0].(table.Row) } - run(args[0].(table.Row), variadicArgs...) + var arg1 []table.RowConfig + var variadicArgs []table.RowConfig + if len(args) > 1 { + variadicArgs = args[1].([]table.RowConfig) + } + arg1 = variadicArgs + run( + arg0, + arg1..., + ) }) return _c } @@ -859,8 +978,8 @@ type MockTableWriter_AppendRow_Call struct { } // AppendRow is a helper method to define mock.On call -// - row -// - configs +// - row table.Row +// - configs ...table.RowConfig func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interface{}) *MockTableWriter_AppendRow_Call { return &MockTableWriter_AppendRow_Call{Call: _e.mock.On("AppendRow", append([]interface{}{row}, configs...)...)} @@ -868,13 +987,20 @@ func (_e *MockTableWriter_Expecter) AppendRow(row interface{}, configs ...interf func (_c *MockTableWriter_AppendRow_Call) Run(run func(row table.Row, configs ...table.RowConfig)) *MockTableWriter_AppendRow_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]table.RowConfig, len(args)-1) - for i, a := range args[1:] { - if a != nil { - variadicArgs[i] = a.(table.RowConfig) - } + var arg0 table.Row + if args[0] != nil { + arg0 = args[0].(table.Row) + } + var arg1 []table.RowConfig + var variadicArgs []table.RowConfig + if len(args) > 1 { + variadicArgs = args[1].([]table.RowConfig) } - run(args[0].(table.Row), variadicArgs...) + arg1 = variadicArgs + run( + arg0, + arg1..., + ) }) return _c } From 767a0055bdfaaa455009f1dfdbbf7da0cac70491 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:44:36 +0100 Subject: [PATCH 48/65] Update internal/installer/node/node.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/installer/node/node.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 405a705b..f09eb2db 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -78,9 +78,13 @@ func (nm *NodeManager) getHostKeyCallback() (ssh.HostKeyCallback, error) { if err := os.MkdirAll(sshDir, 0700); err != nil { return nil, fmt.Errorf("failed to create .ssh directory: %w", err) } - if _, err := os.Create(knownHostsPath); err != nil { + f, err := os.Create(knownHostsPath) + if err != nil { return nil, fmt.Errorf("failed to create known_hosts file: %w", err) } + if err := f.Close(); err != nil { + return nil, fmt.Errorf("failed to close known_hosts file: %w", err) + } hostKeyCallback, err = knownhosts.New(knownHostsPath) if err != nil { return nil, fmt.Errorf("failed to load known_hosts: %w", err) From d0d3adb2db739631320242a2625efd4d163a4e54 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:45:43 +0100 Subject: [PATCH 49/65] Update hack/lima-oms.yaml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- hack/lima-oms.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/lima-oms.yaml b/hack/lima-oms.yaml index d7724b12..f3f90113 100644 --- a/hack/lima-oms.yaml +++ b/hack/lima-oms.yaml @@ -70,7 +70,7 @@ provision: # Create a test install-config for k0s testing VM_IP=$(hostname -I | awk '{print $1}') - cat > ~/oms/test-install-config.yaml << 'EOFCONFIG' + cat > ~/oms/test-install-config.yaml << EOFCONFIG datacenter: id: 1 name: test-dc From 7c6466b5e6eaa1d83c2bb090e5cd92762326a5d8 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:47:04 +0100 Subject: [PATCH 50/65] fix: update go mod --- go.mod | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 35554e0f..13ec2ac2 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( golang.org/x/term v0.39.0 google.golang.org/api v0.263.0 google.golang.org/grpc v1.78.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -392,7 +393,6 @@ require ( github.com/pjbgf/sha1cd v0.5.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pkg/sftp v1.13.10 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect github.com/prometheus/client_golang v1.23.2 // indirect @@ -539,7 +539,6 @@ require ( golang.org/x/text v0.33.0 // indirect golang.org/x/tools v0.40.0 // indirect google.golang.org/protobuf v1.36.11 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) tool ( From 205bca4805b0300276e318835049020732a036fd Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:53:42 +0100 Subject: [PATCH 51/65] ref: simplify host address assignment in k0sctl config generation --- internal/installer/k0sctl_config.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/internal/installer/k0sctl_config.go b/internal/installer/k0sctl_config.go index 337978dc..c4bc5bce 100644 --- a/internal/installer/k0sctl_config.go +++ b/internal/installer/k0sctl_config.go @@ -114,7 +114,7 @@ func GenerateK0sctlConfig(installConfig *files.RootConfig, k0sVersion string, ss addedIPs := make(map[string]bool) // Add controller+worker nodes from control planes - for i, cp := range installConfig.Kubernetes.ControlPlanes { + for _, cp := range installConfig.Kubernetes.ControlPlanes { host := K0sctlHost{ Role: "controller+worker", SSH: K0sctlSSH{ @@ -145,17 +145,12 @@ func GenerateK0sctlConfig(installConfig *files.RootConfig, k0sVersion string, ss "KUBELET_EXTRA_ARGS": fmt.Sprintf("--node-ip=%s", cp.IPAddress), } - // Name hosts for clarity - if len(installConfig.Kubernetes.ControlPlanes) > 1 { - host.SSH.Address = fmt.Sprintf("%s # controller-%d", cp.IPAddress, i+1) - } - k0sctlConfig.Spec.Hosts = append(k0sctlConfig.Spec.Hosts, host) addedIPs[cp.IPAddress] = true } // Add dedicated worker nodes if present - for i, worker := range installConfig.Kubernetes.Workers { + for _, worker := range installConfig.Kubernetes.Workers { if addedIPs[worker.IPAddress] { continue } @@ -182,10 +177,6 @@ func GenerateK0sctlConfig(installConfig *files.RootConfig, k0sVersion string, ss "KUBELET_EXTRA_ARGS": fmt.Sprintf("--node-ip=%s", worker.IPAddress), } - if len(installConfig.Kubernetes.Workers) > 1 { - host.SSH.Address = fmt.Sprintf("%s # worker-%d", worker.IPAddress, i+1) - } - k0sctlConfig.Spec.Hosts = append(k0sctlConfig.Spec.Hosts, host) } From 836abc7ab5149591e4d64035264cc1b79d17d6e8 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:55:23 +0100 Subject: [PATCH 52/65] ref: remove unused MockSSHClient and related functions --- internal/installer/node/mock_ssh_client.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/internal/installer/node/mock_ssh_client.go b/internal/installer/node/mock_ssh_client.go index 7d76529c..6afa1aa9 100644 --- a/internal/installer/node/mock_ssh_client.go +++ b/internal/installer/node/mock_ssh_client.go @@ -22,16 +22,6 @@ func (m *MockSSHClientFactory) Dial(network, addr string, config *ssh.ClientConf return nil, errors.New("mock SSH client factory not configured") } -// MockSSHClient wraps an SSH client for testing purposes. -type MockSSHClient struct { - client *ssh.Client -} - -// NewMockSSHClient creates a mock SSH client that returns errors for all operations. -func NewMockSSHClient() *MockSSHClient { - return &MockSSHClient{} -} - // MockSSHSession is a mock SSH session for testing. type MockSSHSession struct { StartFunc func(cmd string) error From 41f5eace51a31b8f1e84c04af6ccb1f098094af2 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:12:12 +0100 Subject: [PATCH 53/65] ref: enhance host key verification to auto-accept new hosts during provisioning --- internal/installer/node/node.go | 62 +++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index f09eb2db..7d369e88 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -72,12 +72,15 @@ func (nm *NodeManager) getHostKeyCallback() (ssh.HostKeyCallback, error) { } knownHostsPath := filepath.Join(homeDir, ".ssh", "known_hosts") - hostKeyCallback, err := knownhosts.New(knownHostsPath) - if err != nil { - sshDir := filepath.Join(homeDir, ".ssh") - if err := os.MkdirAll(sshDir, 0700); err != nil { - return nil, fmt.Errorf("failed to create .ssh directory: %w", err) - } + + // Ensure known_hosts file exists + sshDir := filepath.Join(homeDir, ".ssh") + if err := os.MkdirAll(sshDir, 0700); err != nil { + return nil, fmt.Errorf("failed to create .ssh directory: %w", err) + } + + // Create file if it doesn't exist + if _, err := os.Stat(knownHostsPath); os.IsNotExist(err) { f, err := os.Create(knownHostsPath) if err != nil { return nil, fmt.Errorf("failed to create known_hosts file: %w", err) @@ -85,13 +88,50 @@ func (nm *NodeManager) getHostKeyCallback() (ssh.HostKeyCallback, error) { if err := f.Close(); err != nil { return nil, fmt.Errorf("failed to close known_hosts file: %w", err) } - hostKeyCallback, err = knownhosts.New(knownHostsPath) - if err != nil { - return nil, fmt.Errorf("failed to load known_hosts: %w", err) - } } - return hostKeyCallback, nil + hostKeyCallback, err := knownhosts.New(knownHostsPath) + if err != nil { + return nil, fmt.Errorf("failed to load known_hosts: %w", err) + } + + // Wrap the callback to auto-accept new hosts for provisioning + return func(hostname string, remote net.Addr, key ssh.PublicKey) error { + err := hostKeyCallback(hostname, remote, key) + if err == nil { + // Host key is already known and valid + return nil + } + + // Check if this is a "host key not found" error (new host) + var keyErr *knownhosts.KeyError + if errors, ok := err.(*knownhosts.KeyError); ok { + keyErr = errors + } + + if keyErr != nil && len(keyErr.Want) == 0 { + // Host key not in known_hosts - auto-accept for provisioning + log.Printf("Warning: Adding new host %s to known_hosts (first connection)", hostname) + + // Append the new host key to known_hosts + f, err := os.OpenFile(knownHostsPath, os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + return fmt.Errorf("failed to open known_hosts for writing: %w", err) + } + defer f.Close() + + // Format: hostname ssh-keytype base64-encoded-key + line := knownhosts.Line([]string{hostname}, key) + if _, err := f.WriteString(line + "\n"); err != nil { + return fmt.Errorf("failed to write to known_hosts: %w", err) + } + + return nil + } + + // Host key mismatch (potential MITM attack) - reject + return fmt.Errorf("host key verification failed: %w", err) + }, nil } // getAuthMethods constructs a slice of ssh.AuthMethod, prioritizing the SSH agent. From 344d3de49576f5597ecc5ea1a2be75979022c777 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Fri, 30 Jan 2026 15:03:51 +0100 Subject: [PATCH 54/65] fix: k0s bugs --- internal/installer/files/config_yaml.go | 4 +++- internal/installer/k0s_config.go | 21 +++++++++++++++----- internal/installer/k0s_config_test.go | 5 +++++ internal/installer/k0sctl.go | 4 ++-- internal/installer/k0sctl_config.go | 26 +++++++++++++++++++++---- 5 files changed, 48 insertions(+), 12 deletions(-) diff --git a/internal/installer/files/config_yaml.go b/internal/installer/files/config_yaml.go index 85cffa4e..e4fffa52 100644 --- a/internal/installer/files/config_yaml.go +++ b/internal/installer/files/config_yaml.go @@ -160,7 +160,9 @@ type KubernetesConfig struct { } type K8sNode struct { - IPAddress string `yaml:"ipAddress"` + IPAddress string `yaml:"ipAddress"` + SSHAddress string `yaml:"sshAddress,omitempty"` + SSHPort int `yaml:"sshPort,omitempty"` } type ClusterConfig struct { diff --git a/internal/installer/k0s_config.go b/internal/installer/k0s_config.go index a4ac23c2..0d9db153 100644 --- a/internal/installer/k0s_config.go +++ b/internal/installer/k0s_config.go @@ -22,11 +22,12 @@ type K0sMetadata struct { } type K0sSpec struct { - API *K0sAPI `yaml:"api,omitempty"` - Network *K0sNetwork `yaml:"network,omitempty"` - Storage *K0sStorage `yaml:"storage,omitempty"` - Images *K0sImages `yaml:"images,omitempty"` - Telemetry *K0sTelemetry `yaml:"telemetry,omitempty"` + API *K0sAPI `yaml:"api,omitempty"` + Network *K0sNetwork `yaml:"network,omitempty"` + Storage *K0sStorage `yaml:"storage,omitempty"` + Images *K0sImages `yaml:"images,omitempty"` + Telemetry *K0sTelemetry `yaml:"telemetry,omitempty"` + Konnectivity *K0sKonnectivity `yaml:"konnectivity,omitempty"` } type K0sAPI struct { @@ -59,6 +60,11 @@ type K0sTelemetry struct { Enabled bool `yaml:"enabled"` } +type K0sKonnectivity struct { + AdminPort int `yaml:"adminPort,omitempty"` + AgentPort int `yaml:"agentPort,omitempty"` +} + func GenerateK0sConfig(installConfig *files.RootConfig) (*K0sConfig, error) { if installConfig == nil { return nil, fmt.Errorf("installConfig cannot be nil") @@ -118,6 +124,11 @@ func GenerateK0sConfig(installConfig *files.RootConfig) (*K0sConfig, error) { Enabled: false, } + k0sConfig.Spec.Konnectivity = &K0sKonnectivity{ + AdminPort: 8133, + AgentPort: 8132, + } + if len(installConfig.Kubernetes.ControlPlanes) > 0 { k0sConfig.Spec.Storage = &K0sStorage{ Type: "etcd", diff --git a/internal/installer/k0s_config_test.go b/internal/installer/k0s_config_test.go index 7bc17128..89efeaed 100644 --- a/internal/installer/k0s_config_test.go +++ b/internal/installer/k0s_config_test.go @@ -57,6 +57,11 @@ var _ = Describe("K0sConfig", func() { Expect(k0sConfig.Spec.Network.Provider).To(Equal("calico")) Expect(k0sConfig.Spec.Storage.Etcd).ToNot(BeNil()) Expect(k0sConfig.Spec.Storage.Etcd.PeerAddress).To(Equal("10.0.1.10")) + + // Check Konnectivity configuration (prevents webhook failures) + Expect(k0sConfig.Spec.Konnectivity).ToNot(BeNil()) + Expect(k0sConfig.Spec.Konnectivity.AdminPort).To(Equal(8133)) + Expect(k0sConfig.Spec.Konnectivity.AgentPort).To(Equal(8132)) }) It("should handle minimal configuration", func() { diff --git a/internal/installer/k0sctl.go b/internal/installer/k0sctl.go index 9237c4d1..0ffe4e97 100644 --- a/internal/installer/k0sctl.go +++ b/internal/installer/k0sctl.go @@ -132,7 +132,7 @@ func (k *K0sctl) Apply(configPath string, k0sctlPath string, force bool) error { return fmt.Errorf("k0sctl config does not exist at '%s'", configPath) } - args := []string{k0sctlPath, "apply", "--config", configPath} + args := []string{"env", "-u", "SSH_AUTH_SOCK", k0sctlPath, "apply", "--config", configPath} if force { args = append(args, "--force") @@ -163,7 +163,7 @@ func (k *K0sctl) Reset(configPath string, k0sctlPath string) error { log.Println("Resetting k0s cluster using k0sctl...") - args := []string{k0sctlPath, "reset", "--config", configPath, "--force"} + args := []string{"env", "-u", "SSH_AUTH_SOCK", k0sctlPath, "reset", "--config", configPath, "--force"} err := util.RunCommand("sudo", args, "") if err != nil { diff --git a/internal/installer/k0sctl_config.go b/internal/installer/k0sctl_config.go index c4bc5bce..f07c6781 100644 --- a/internal/installer/k0sctl_config.go +++ b/internal/installer/k0sctl_config.go @@ -115,12 +115,21 @@ func GenerateK0sctlConfig(installConfig *files.RootConfig, k0sVersion string, ss // Add controller+worker nodes from control planes for _, cp := range installConfig.Kubernetes.ControlPlanes { + sshPort := cp.SSHPort + if sshPort == 0 { + sshPort = 22 + } + // Use SSHAddress if provided, otherwise fall back to IPAddress + sshAddress := cp.SSHAddress + if sshAddress == "" { + sshAddress = cp.IPAddress + } host := K0sctlHost{ Role: "controller+worker", SSH: K0sctlSSH{ - Address: cp.IPAddress, + Address: sshAddress, User: "root", - Port: 22, + Port: sshPort, }, InstallFlags: []string{ "--enable-worker", @@ -154,12 +163,21 @@ func GenerateK0sctlConfig(installConfig *files.RootConfig, k0sVersion string, ss if addedIPs[worker.IPAddress] { continue } + sshPort := worker.SSHPort + if sshPort == 0 { + sshPort = 22 + } + // Use SSHAddress if provided, otherwise fall back to IPAddress + sshAddress := worker.SSHAddress + if sshAddress == "" { + sshAddress = worker.IPAddress + } host := K0sctlHost{ Role: "worker", SSH: K0sctlSSH{ - Address: worker.IPAddress, + Address: sshAddress, User: "root", - Port: 22, + Port: sshPort, }, PrivateAddress: worker.IPAddress, } From 41678828d5699f12dff04b15975f7d2f43ccc9c6 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Fri, 30 Jan 2026 15:27:43 +0100 Subject: [PATCH 55/65] ref: update k0sctl config path to use OmsWorkdir and improve error handling --- cli/cmd/install_k0s.go | 4 +--- cli/cmd/install_k0s_test.go | 3 +++ internal/installer/k0sctl.go | 26 ++++++++++++++------------ internal/installer/node/node.go | 8 ++++++-- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index fe610e71..d237035b 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -90,7 +90,6 @@ func AddInstallK0sCmd(install *cobra.Command, opts *GlobalOptions) { const ( defaultK0sPath = "kubernetes/files/k0s" - k0sctlConfigDir = "/tmp" k0sctlConfigFile = "k0sctl-config.yaml" ) @@ -152,11 +151,10 @@ func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0 return fmt.Errorf("failed to marshal k0sctl config: %w", err) } - k0sctlConfigPath := filepath.Join(k0sctlConfigDir, k0sctlConfigFile) + k0sctlConfigPath := filepath.Join(c.Env.GetOmsWorkdir(), k0sctlConfigFile) if err := os.WriteFile(k0sctlConfigPath, k0sctlConfigData, 0644); err != nil { return fmt.Errorf("failed to write k0sctl config: %w", err) } - defer func() { _ = os.Remove(k0sctlConfigPath) }() log.Printf("Generated k0sctl configuration at %s", k0sctlConfigPath) diff --git a/cli/cmd/install_k0s_test.go b/cli/cmd/install_k0s_test.go index 4b829a79..b4386924 100644 --- a/cli/cmd/install_k0s_test.go +++ b/cli/cmd/install_k0s_test.go @@ -153,6 +153,7 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.Version = "v1.30.0+k0s.0" c.Opts.Force = true + mockEnv.EXPECT().GetOmsWorkdir().Return(tempDir) mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") mockK0sctl.EXPECT().Download("", true, false).Return("/tmp/k0sctl", nil) mockK0sctl.EXPECT().Apply(mock.Anything, "/tmp/k0sctl", true).Return(nil) @@ -173,6 +174,7 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.Package = "" c.Opts.Version = "v1.29.0+k0s.0" + mockEnv.EXPECT().GetOmsWorkdir().Return(tempDir) mockK0s.EXPECT().Download("v1.29.0+k0s.0", false, false).Return("/downloaded/k0s", nil) mockK0sctl.EXPECT().Download("", false, false).Return("/tmp/k0sctl", nil) mockK0sctl.EXPECT().Apply(mock.Anything, "/tmp/k0sctl", false).Return(nil) @@ -232,6 +234,7 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.Package = "test-package.tar.gz" c.Opts.Version = "v1.30.0+k0s.0" + mockEnv.EXPECT().GetOmsWorkdir().Return(tempDir) mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") mockK0sctl.EXPECT().Download("", false, false).Return("/tmp/k0sctl", nil) mockK0sctl.EXPECT().Apply(mock.Anything, "/tmp/k0sctl", false).Return(os.ErrPermission) diff --git a/internal/installer/k0sctl.go b/internal/installer/k0sctl.go index 0ffe4e97..9eaafd97 100644 --- a/internal/installer/k0sctl.go +++ b/internal/installer/k0sctl.go @@ -4,6 +4,7 @@ package installer import ( + "encoding/json" "fmt" "log" "os" @@ -41,6 +42,11 @@ func NewK0sctl(hw portal.Http, env env.Env, fw util.FileIO) K0sctlManager { } } +// githubRelease represents the minimal GitHub release API response +type githubRelease struct { + TagName string `json:"tag_name"` +} + func (k *K0sctl) GetLatestVersion() (string, error) { // k0sctl uses GitHub releases - fetch latest from API releaseURL := "https://api.github.com/repos/k0sproject/k0sctl/releases/latest" @@ -49,20 +55,16 @@ func (k *K0sctl) GetLatestVersion() (string, error) { return "", fmt.Errorf("failed to fetch latest k0sctl release: %w", err) } - // Simple parsing - just extract tag_name - tagStart := string(responseBody) - if idx := strings.Index(tagStart, `"tag_name"`); idx != -1 { - tagStart = tagStart[idx:] - if start := strings.Index(tagStart, `"`); start != -1 { - tagStart = tagStart[start+len(`"tag_name":"`)+1:] - if end := strings.Index(tagStart, `"`); end != -1 { - version := tagStart[:end] - return version, nil - } - } + var release githubRelease + if err := json.Unmarshal(responseBody, &release); err != nil { + return "", fmt.Errorf("failed to parse GitHub API response: %w", err) + } + + if release.TagName == "" { + return "", fmt.Errorf("no tag_name found in GitHub API response") } - return "", fmt.Errorf("failed to parse version from GitHub API response") + return release.TagName, nil } func (k *K0sctl) Download(version string, force bool, quiet bool) (string, error) { diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 7d369e88..4958d842 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -118,7 +118,11 @@ func (nm *NodeManager) getHostKeyCallback() (ssh.HostKeyCallback, error) { if err != nil { return fmt.Errorf("failed to open known_hosts for writing: %w", err) } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + log.Printf("Warning: failed to close known_hosts file: %v", err) + } + }() // Format: hostname ssh-keytype base64-encoded-key line := knownhosts.Line([]string{hostname}, key) @@ -272,6 +276,7 @@ func (nm *NodeManager) forwardAgent(client *ssh.Client, session *ssh.Session) er if err != nil { log.Printf("failed to dial SSH agent socket: %v", err) } else { + defer util.IgnoreError(conn.Close) ag := agent.NewClient(conn) if err := agent.ForwardToAgent(client, ag); err != nil { log.Printf("failed to forward agent to remote client: %v", err) @@ -282,7 +287,6 @@ func (nm *NodeManager) forwardAgent(client *ssh.Client, session *ssh.Session) er } } } - } return nil } From 456ec52ac99838f3decacd31fa31c4b3b4fb59bd Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Fri, 30 Jan 2026 15:29:42 +0100 Subject: [PATCH 56/65] Update internal/installer/node/node.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/installer/node/node.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 4958d842..2695ada9 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -425,8 +425,21 @@ func (nm *NodeManager) CopyFile(jumpboxIp string, ip string, username string, sr } func (n *Node) HasCommand(nm *NodeManager, command string) bool { - checkCommand := fmt.Sprintf("command -v '%s' >/dev/null 2>&1", shellEscape(command)) - err := nm.RunSSHCommand("", n.ExternalIP, "root", checkCommand) + // Normalize to a bare executable name (ignore any arguments) + fields := strings.Fields(command) + if len(fields) == 0 { + return false + } + executable := fields[0] + + checkCommand := fmt.Sprintf("command -v '%s' >/dev/null 2>&1", shellEscape(executable)) + + user := n.User + if user == "" { + user = "root" + } + + err := nm.RunSSHCommand("", n.ExternalIP, user, checkCommand) return err == nil } From 8a7ffc18b816a4e964e95389c19e54d8c633cf65 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Fri, 30 Jan 2026 15:52:30 +0100 Subject: [PATCH 57/65] ref: cleanup --- cli/cmd/bootstrap_gcp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/cmd/bootstrap_gcp.go b/cli/cmd/bootstrap_gcp.go index 301b461a..8569f482 100644 --- a/cli/cmd/bootstrap_gcp.go +++ b/cli/cmd/bootstrap_gcp.go @@ -114,7 +114,7 @@ func (c *BootstrapGcpCmd) BootstrapGcp() error { return fmt.Errorf("failed to bootstrap GCP: %w, env: %s", err, envString) } - log.Println("\nšŸŽ‰šŸŽ‰šŸŽ‰ GCP infrastructure bootstrapped successfully!") + log.Println("\nGCP infrastructure bootstrapped successfully!") log.Println(envString) log.Printf("Start the Codesphere installation using OMS from the jumpbox host:\nssh-add $SSH_KEY_PATH; ssh -o StrictHostKeyChecking=no -o ForwardAgent=yes -o SendEnv=OMS_PORTAL_API_KEY root@%s", bs.Env.Jumpbox.GetExternalIP()) log.Printf("When the installation is done, run the k0s configuration script generated at the k0s-1 host %s /root/configure-k0s.sh.", bs.Env.ControlPlaneNodes[0].GetInternalIP()) From 1d44afc7dacd8c17f7c36b200558eedc43265784 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Mon, 2 Feb 2026 12:15:26 +0100 Subject: [PATCH 58/65] ref: enhance GetNodeIPAddress logic and simplify GenerateK0sConfig with default values --- internal/installer/k0s.go | 38 +++++----- internal/installer/k0s_config.go | 22 +++--- internal/installer/k0sctl_config.go | 108 ++++++++++------------------ 3 files changed, 69 insertions(+), 99 deletions(-) diff --git a/internal/installer/k0s.go b/internal/installer/k0s.go index da17c14a..0ef8b83a 100644 --- a/internal/installer/k0s.go +++ b/internal/installer/k0s.go @@ -100,32 +100,38 @@ func (k *K0s) Download(version string, force bool, quiet bool) (string, error) { } // GetNodeIPAddress finds the IP address of the current node by matching -// against the control plane IPs in the config +// against the control plane IPs in the config. Returns matching control plane IP +// if found, otherwise returns the first non-loopback IPv4 address. func GetNodeIPAddress(controlPlanes []string) (string, error) { addrs, err := net.InterfaceAddrs() if err != nil { return "", fmt.Errorf("failed to get network interfaces: %w", err) } - for _, addr := range addrs { - if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - ip := ipnet.IP.String() - for _, cpIP := range controlPlanes { - if ip == cpIP { - return ip, nil - } - } - } - } + // Build a set of control plane IPs for O(1) lookup + cpSet := make(map[string]bool, len(controlPlanes)) + for _, ip := range controlPlanes { + cpSet[ip] = true } + var fallbackIP string for _, addr := range addrs { - if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - return ipnet.IP.String(), nil - } + ipnet, ok := addr.(*net.IPNet) + if !ok || ipnet.IP.IsLoopback() || ipnet.IP.To4() == nil { + continue + } + + ip := ipnet.IP.String() + if cpSet[ip] { + return ip, nil } + if fallbackIP == "" { + fallbackIP = ip + } + } + + if fallbackIP != "" { + return fallbackIP, nil } return "", fmt.Errorf("no suitable IP address found") diff --git a/internal/installer/k0s_config.go b/internal/installer/k0s_config.go index 0d9db153..109fcfd9 100644 --- a/internal/installer/k0s_config.go +++ b/internal/installer/k0s_config.go @@ -102,18 +102,9 @@ func GenerateK0sConfig(installConfig *files.RootConfig) (*K0sConfig, error) { } k0sConfig.Spec.Network = &K0sNetwork{ - Provider: "calico", - } - - if installConfig.Kubernetes.PodCIDR != "" { - k0sConfig.Spec.Network.PodCIDR = installConfig.Kubernetes.PodCIDR - } else { - k0sConfig.Spec.Network.PodCIDR = "100.96.0.0/11" - } - if installConfig.Kubernetes.ServiceCIDR != "" { - k0sConfig.Spec.Network.ServiceCIDR = installConfig.Kubernetes.ServiceCIDR - } else { - k0sConfig.Spec.Network.ServiceCIDR = "100.64.0.0/13" + Provider: "calico", + PodCIDR: defaultIfEmpty(installConfig.Kubernetes.PodCIDR, "100.96.0.0/11"), + ServiceCIDR: defaultIfEmpty(installConfig.Kubernetes.ServiceCIDR, "100.64.0.0/13"), } k0sConfig.Spec.Images = &K0sImages{ @@ -142,6 +133,13 @@ func GenerateK0sConfig(installConfig *files.RootConfig) (*K0sConfig, error) { return k0sConfig, nil } +func defaultIfEmpty(value, defaultValue string) string { + if value != "" { + return value + } + return defaultValue +} + func (c *K0sConfig) Marshal() ([]byte, error) { return yaml.Marshal(c) } diff --git a/internal/installer/k0sctl_config.go b/internal/installer/k0sctl_config.go index f07c6781..9603a535 100644 --- a/internal/installer/k0sctl_config.go +++ b/internal/installer/k0sctl_config.go @@ -68,6 +68,40 @@ type K0sctlApplyHooks struct { After []string `yaml:"after,omitempty"` } +func createK0sctlHost(node files.K8sNode, role string, installFlags []string, sshKeyPath string, k0sBinaryPath string) K0sctlHost { + sshPort := node.SSHPort + if sshPort == 0 { + sshPort = 22 + } + + sshAddress := node.SSHAddress + if sshAddress == "" { + sshAddress = node.IPAddress + } + + host := K0sctlHost{ + Role: role, + SSH: K0sctlSSH{ + Address: sshAddress, + User: "root", + Port: sshPort, + KeyPath: sshKeyPath, + }, + InstallFlags: installFlags, + PrivateAddress: node.IPAddress, + Environment: map[string]string{ + "KUBELET_EXTRA_ARGS": fmt.Sprintf("--node-ip=%s", node.IPAddress), + }, + } + + if k0sBinaryPath != "" { + host.UploadBinary = true + host.K0sBinaryPath = k0sBinaryPath + } + + return host +} + // GenerateK0sctlConfig generates a k0sctl configuration from a Codesphere install-config func GenerateK0sctlConfig(installConfig *files.RootConfig, k0sVersion string, sshKeyPath string, k0sBinaryPath string) (*K0sctlConfig, error) { if installConfig == nil { @@ -114,46 +148,9 @@ func GenerateK0sctlConfig(installConfig *files.RootConfig, k0sVersion string, ss addedIPs := make(map[string]bool) // Add controller+worker nodes from control planes + controllerFlags := []string{"--enable-worker", "--no-taints"} for _, cp := range installConfig.Kubernetes.ControlPlanes { - sshPort := cp.SSHPort - if sshPort == 0 { - sshPort = 22 - } - // Use SSHAddress if provided, otherwise fall back to IPAddress - sshAddress := cp.SSHAddress - if sshAddress == "" { - sshAddress = cp.IPAddress - } - host := K0sctlHost{ - Role: "controller+worker", - SSH: K0sctlSSH{ - Address: sshAddress, - User: "root", - Port: sshPort, - }, - InstallFlags: []string{ - "--enable-worker", - "--no-taints", - }, - PrivateAddress: cp.IPAddress, - } - - // Add SSH key path if provided - if sshKeyPath != "" { - host.SSH.KeyPath = sshKeyPath - } - - // Add k0s binary path if provided - if k0sBinaryPath != "" { - host.UploadBinary = true - host.K0sBinaryPath = k0sBinaryPath - } - - // Set node-ip in kubelet extra args - host.Environment = map[string]string{ - "KUBELET_EXTRA_ARGS": fmt.Sprintf("--node-ip=%s", cp.IPAddress), - } - + host := createK0sctlHost(cp, "controller+worker", controllerFlags, sshKeyPath, k0sBinaryPath) k0sctlConfig.Spec.Hosts = append(k0sctlConfig.Spec.Hosts, host) addedIPs[cp.IPAddress] = true } @@ -163,38 +160,7 @@ func GenerateK0sctlConfig(installConfig *files.RootConfig, k0sVersion string, ss if addedIPs[worker.IPAddress] { continue } - sshPort := worker.SSHPort - if sshPort == 0 { - sshPort = 22 - } - // Use SSHAddress if provided, otherwise fall back to IPAddress - sshAddress := worker.SSHAddress - if sshAddress == "" { - sshAddress = worker.IPAddress - } - host := K0sctlHost{ - Role: "worker", - SSH: K0sctlSSH{ - Address: sshAddress, - User: "root", - Port: sshPort, - }, - PrivateAddress: worker.IPAddress, - } - - if sshKeyPath != "" { - host.SSH.KeyPath = sshKeyPath - } - - if k0sBinaryPath != "" { - host.UploadBinary = true - host.K0sBinaryPath = k0sBinaryPath - } - - host.Environment = map[string]string{ - "KUBELET_EXTRA_ARGS": fmt.Sprintf("--node-ip=%s", worker.IPAddress), - } - + host := createK0sctlHost(worker, "worker", nil, sshKeyPath, k0sBinaryPath) k0sctlConfig.Spec.Hosts = append(k0sctlConfig.Spec.Hosts, host) } From c9316f7e8a696e23b9ba82a9e5042bd258e107ef Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Mon, 2 Feb 2026 12:23:50 +0100 Subject: [PATCH 59/65] ref: simplify k0s config --- internal/installer/k0s_config.go | 36 ++++++++++++++------------------ 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/internal/installer/k0s_config.go b/internal/installer/k0s_config.go index 109fcfd9..29b99b01 100644 --- a/internal/installer/k0s_config.go +++ b/internal/installer/k0s_config.go @@ -81,24 +81,29 @@ func GenerateK0sConfig(installConfig *files.RootConfig) (*K0sConfig, error) { if installConfig.Kubernetes.ManagedByCodesphere { if len(installConfig.Kubernetes.ControlPlanes) > 0 { - firstControlPlane := installConfig.Kubernetes.ControlPlanes[0] - k0sConfig.Spec.API = &K0sAPI{ - Address: firstControlPlane.IPAddress, - Port: 6443, - } - - if installConfig.Kubernetes.APIServerHost != "" { - k0sConfig.Spec.API.ExternalAddress = installConfig.Kubernetes.APIServerHost - } + firstControlPlaneIP := installConfig.Kubernetes.ControlPlanes[0].IPAddress - sans := make([]string, 0, len(installConfig.Kubernetes.ControlPlanes)) + sans := make([]string, 0, len(installConfig.Kubernetes.ControlPlanes)+1) for _, cp := range installConfig.Kubernetes.ControlPlanes { sans = append(sans, cp.IPAddress) } if installConfig.Kubernetes.APIServerHost != "" { sans = append(sans, installConfig.Kubernetes.APIServerHost) } - k0sConfig.Spec.API.SANs = sans + + k0sConfig.Spec.API = &K0sAPI{ + Address: firstControlPlaneIP, + ExternalAddress: installConfig.Kubernetes.APIServerHost, + SANs: sans, + Port: 6443, + } + + k0sConfig.Spec.Storage = &K0sStorage{ + Type: "etcd", + Etcd: &K0sEtcd{ + PeerAddress: firstControlPlaneIP, + }, + } } k0sConfig.Spec.Network = &K0sNetwork{ @@ -119,15 +124,6 @@ func GenerateK0sConfig(installConfig *files.RootConfig) (*K0sConfig, error) { AdminPort: 8133, AgentPort: 8132, } - - if len(installConfig.Kubernetes.ControlPlanes) > 0 { - k0sConfig.Spec.Storage = &K0sStorage{ - Type: "etcd", - Etcd: &K0sEtcd{ - PeerAddress: installConfig.Kubernetes.ControlPlanes[0].IPAddress, - }, - } - } } return k0sConfig, nil From a97522f39eeccb137d08085b87bc8e4215e9c4d4 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Mon, 2 Feb 2026 12:47:58 +0100 Subject: [PATCH 60/65] Update internal/installer/k0sctl_config.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/installer/k0sctl_config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/installer/k0sctl_config.go b/internal/installer/k0sctl_config.go index 9603a535..19c37571 100644 --- a/internal/installer/k0sctl_config.go +++ b/internal/installer/k0sctl_config.go @@ -162,6 +162,7 @@ func GenerateK0sctlConfig(installConfig *files.RootConfig, k0sVersion string, ss } host := createK0sctlHost(worker, "worker", nil, sshKeyPath, k0sBinaryPath) k0sctlConfig.Spec.Hosts = append(k0sctlConfig.Spec.Hosts, host) + addedIPs[worker.IPAddress] = true } return k0sctlConfig, nil From ada698325818d751456169f6de4466eeb28f4898 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Mon, 2 Feb 2026 12:49:52 +0100 Subject: [PATCH 61/65] Update internal/installer/node/node.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/installer/node/node.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index 54c62476..dd960f07 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -178,7 +178,11 @@ func (n *Node) getHostKeyCallback() (ssh.HostKeyCallback, error) { }() // Format: hostname ssh-keytype base64-encoded-key - line := knownhosts.Line([]string{hostname}, key) + normalizedHosts := []string{hostname} + if host, port, splitErr := net.SplitHostPort(hostname); splitErr == nil { + normalizedHosts = []string{net.JoinHostPort(host, port)} + } + line := knownhosts.Line(normalizedHosts, key) if _, err := f.WriteString(line + "\n"); err != nil { return fmt.Errorf("failed to write to known_hosts: %w", err) } From 990571697d9cec43aabc3a5c1037dd4a8cb7dcd5 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Mon, 2 Feb 2026 12:55:47 +0100 Subject: [PATCH 62/65] ref: implement review suggestions --- cli/cmd/install_k0s.go | 3 +- internal/installer/k0sctl_config_test.go | 485 +++++++++++++++++++++++ internal/installer/node/node.go | 2 +- 3 files changed, 487 insertions(+), 3 deletions(-) create mode 100644 internal/installer/k0sctl_config_test.go diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index d237035b..062fc7b4 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -6,7 +6,6 @@ package cmd import ( "fmt" "log" - "os" "path/filepath" packageio "github.com/codesphere-cloud/cs-go/pkg/io" @@ -152,7 +151,7 @@ func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0 } k0sctlConfigPath := filepath.Join(c.Env.GetOmsWorkdir(), k0sctlConfigFile) - if err := os.WriteFile(k0sctlConfigPath, k0sctlConfigData, 0644); err != nil { + if err := c.FileWriter.WriteFile(k0sctlConfigPath, k0sctlConfigData, 0644); err != nil { return fmt.Errorf("failed to write k0sctl config: %w", err) } diff --git a/internal/installer/k0sctl_config_test.go b/internal/installer/k0sctl_config_test.go new file mode 100644 index 00000000..5b1a9c8e --- /dev/null +++ b/internal/installer/k0sctl_config_test.go @@ -0,0 +1,485 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package installer_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "gopkg.in/yaml.v3" + + "github.com/codesphere-cloud/oms/internal/installer" + "github.com/codesphere-cloud/oms/internal/installer/files" +) + +var _ = Describe("K0sctlConfig", func() { + Describe("GenerateK0sctlConfig", func() { + Context("with valid install-config", func() { + It("should generate k0sctl config with control plane nodes", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + APIServerHost: "k8s.example.com", + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, + {IPAddress: "10.0.1.11"}, + {IPAddress: "10.0.1.12"}, + }, + PodCIDR: "10.244.0.0/16", + ServiceCIDR: "10.96.0.0/12", + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "/path/to/k0s") + Expect(err).ToNot(HaveOccurred()) + Expect(k0sctlConfig).ToNot(BeNil()) + + // Check basic structure + Expect(k0sctlConfig.APIVersion).To(Equal("k0sctl.k0sproject.io/v1beta1")) + Expect(k0sctlConfig.Kind).To(Equal("Cluster")) + Expect(k0sctlConfig.Metadata.Name).To(Equal("codesphere-test-dc")) + + // Check k0s version + Expect(k0sctlConfig.Spec.K0s.Version).To(Equal("v1.30.0+k0s.0")) + + // Check hosts count matches control planes + Expect(k0sctlConfig.Spec.Hosts).To(HaveLen(3)) + }) + + It("should assign controller+worker role to control plane nodes", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, + }, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).ToNot(HaveOccurred()) + + Expect(k0sctlConfig.Spec.Hosts).To(HaveLen(1)) + Expect(k0sctlConfig.Spec.Hosts[0].Role).To(Equal("controller+worker")) + Expect(k0sctlConfig.Spec.Hosts[0].InstallFlags).To(ContainElements("--enable-worker", "--no-taints")) + }) + + It("should assign worker role to dedicated worker nodes", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, + }, + Workers: []files.K8sNode{ + {IPAddress: "10.0.2.10"}, + {IPAddress: "10.0.2.11"}, + }, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).ToNot(HaveOccurred()) + + Expect(k0sctlConfig.Spec.Hosts).To(HaveLen(3)) + + // First host should be controller+worker + Expect(k0sctlConfig.Spec.Hosts[0].Role).To(Equal("controller+worker")) + + // Worker nodes should have worker role with no install flags + Expect(k0sctlConfig.Spec.Hosts[1].Role).To(Equal("worker")) + Expect(k0sctlConfig.Spec.Hosts[1].InstallFlags).To(BeNil()) + Expect(k0sctlConfig.Spec.Hosts[2].Role).To(Equal("worker")) + }) + + It("should use SSHAddress when specified", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + { + IPAddress: "10.0.1.10", + SSHAddress: "ssh.example.com", + }, + }, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).ToNot(HaveOccurred()) + + Expect(k0sctlConfig.Spec.Hosts[0].SSH.Address).To(Equal("ssh.example.com")) + Expect(k0sctlConfig.Spec.Hosts[0].PrivateAddress).To(Equal("10.0.1.10")) + }) + + It("should default SSHAddress to IPAddress when not specified", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, + }, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).ToNot(HaveOccurred()) + + Expect(k0sctlConfig.Spec.Hosts[0].SSH.Address).To(Equal("10.0.1.10")) + }) + + It("should use SSHPort when specified", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + { + IPAddress: "10.0.1.10", + SSHPort: 2222, + }, + }, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).ToNot(HaveOccurred()) + + Expect(k0sctlConfig.Spec.Hosts[0].SSH.Port).To(Equal(2222)) + }) + + It("should default SSHPort to 22 when not specified", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, + }, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).ToNot(HaveOccurred()) + + Expect(k0sctlConfig.Spec.Hosts[0].SSH.Port).To(Equal(22)) + }) + + It("should skip duplicate IPs between control planes and workers", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, + }, + Workers: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, // Duplicate + {IPAddress: "10.0.2.10"}, // Unique + }, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).ToNot(HaveOccurred()) + + // Should only have 2 hosts: 1 control plane + 1 unique worker + Expect(k0sctlConfig.Spec.Hosts).To(HaveLen(2)) + Expect(k0sctlConfig.Spec.Hosts[0].SSH.Address).To(Equal("10.0.1.10")) + Expect(k0sctlConfig.Spec.Hosts[0].Role).To(Equal("controller+worker")) + Expect(k0sctlConfig.Spec.Hosts[1].SSH.Address).To(Equal("10.0.2.10")) + Expect(k0sctlConfig.Spec.Hosts[1].Role).To(Equal("worker")) + }) + + It("should enable UploadBinary when k0sBinaryPath is provided", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, + }, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "/path/to/k0s") + Expect(err).ToNot(HaveOccurred()) + + Expect(k0sctlConfig.Spec.Hosts[0].UploadBinary).To(BeTrue()) + Expect(k0sctlConfig.Spec.Hosts[0].K0sBinaryPath).To(Equal("/path/to/k0s")) + }) + + It("should not enable UploadBinary when k0sBinaryPath is empty", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, + }, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).ToNot(HaveOccurred()) + + Expect(k0sctlConfig.Spec.Hosts[0].UploadBinary).To(BeFalse()) + Expect(k0sctlConfig.Spec.Hosts[0].K0sBinaryPath).To(BeEmpty()) + }) + + It("should set SSH key path correctly", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, + }, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/home/user/.ssh/id_rsa", "") + Expect(err).ToNot(HaveOccurred()) + + Expect(k0sctlConfig.Spec.Hosts[0].SSH.KeyPath).To(Equal("/home/user/.ssh/id_rsa")) + }) + + It("should set SSH user to root", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, + }, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).ToNot(HaveOccurred()) + + Expect(k0sctlConfig.Spec.Hosts[0].SSH.User).To(Equal("root")) + }) + + It("should set KUBELET_EXTRA_ARGS environment variable", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, + }, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).ToNot(HaveOccurred()) + + Expect(k0sctlConfig.Spec.Hosts[0].Environment).To(HaveKeyWithValue("KUBELET_EXTRA_ARGS", "--node-ip=10.0.1.10")) + }) + + It("should generate valid YAML", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, + }, + PodCIDR: "10.244.0.0/16", + ServiceCIDR: "10.96.0.0/12", + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).ToNot(HaveOccurred()) + + yamlData, err := k0sctlConfig.Marshal() + Expect(err).ToNot(HaveOccurred()) + Expect(yamlData).ToNot(BeEmpty()) + + // Verify it can be unmarshalled back + var parsedConfig installer.K0sctlConfig + err = yaml.Unmarshal(yamlData, &parsedConfig) + Expect(err).ToNot(HaveOccurred()) + Expect(parsedConfig.Metadata.Name).To(Equal("codesphere-test-dc")) + }) + }) + + Context("with invalid input", func() { + It("should return error for nil install-config", func() { + k0sctlConfig, err := installer.GenerateK0sctlConfig(nil, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("installConfig cannot be nil")) + Expect(k0sctlConfig).To(BeNil()) + }) + + It("should return error for non-managed Kubernetes", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: false, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("k0sctl is only supported for Codesphere-managed Kubernetes")) + Expect(k0sctlConfig).To(BeNil()) + }) + }) + + Context("edge cases", func() { + It("should handle empty control plane list", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{}, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).ToNot(HaveOccurred()) + Expect(k0sctlConfig.Spec.Hosts).To(BeEmpty()) + }) + + It("should handle nil control plane list", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: nil, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).ToNot(HaveOccurred()) + Expect(k0sctlConfig.Spec.Hosts).To(BeEmpty()) + }) + + It("should handle only worker nodes (no control planes)", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{}, + Workers: []files.K8sNode{ + {IPAddress: "10.0.2.10"}, + }, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).ToNot(HaveOccurred()) + // Workers should still be added even without control planes + Expect(k0sctlConfig.Spec.Hosts).To(HaveLen(1)) + Expect(k0sctlConfig.Spec.Hosts[0].Role).To(Equal("worker")) + }) + + It("should handle empty SSH key path", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10"}, + }, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "", "") + Expect(err).ToNot(HaveOccurred()) + + Expect(k0sctlConfig.Spec.Hosts[0].SSH.KeyPath).To(BeEmpty()) + }) + + It("should set PrivateAddress for all host types", func() { + installConfig := &files.RootConfig{ + Datacenter: files.DatacenterConfig{ + ID: 1, + Name: "test-dc", + }, + Kubernetes: files.KubernetesConfig{ + ManagedByCodesphere: true, + ControlPlanes: []files.K8sNode{ + {IPAddress: "10.0.1.10", SSHAddress: "public1.example.com"}, + }, + Workers: []files.K8sNode{ + {IPAddress: "10.0.2.10", SSHAddress: "public2.example.com"}, + }, + }, + } + + k0sctlConfig, err := installer.GenerateK0sctlConfig(installConfig, "v1.30.0+k0s.0", "/path/to/key", "") + Expect(err).ToNot(HaveOccurred()) + + // Both hosts should have PrivateAddress set to the internal IP + Expect(k0sctlConfig.Spec.Hosts[0].PrivateAddress).To(Equal("10.0.1.10")) + Expect(k0sctlConfig.Spec.Hosts[1].PrivateAddress).To(Equal("10.0.2.10")) + }) + }) + }) +}) diff --git a/internal/installer/node/node.go b/internal/installer/node/node.go index dd960f07..c8897554 100644 --- a/internal/installer/node/node.go +++ b/internal/installer/node/node.go @@ -134,7 +134,7 @@ func (n *Node) getHostKeyCallback() (ssh.HostKeyCallback, error) { // Create file if it doesn't exist if _, err := os.Stat(knownHostsPath); os.IsNotExist(err) { - f, err := os.Create(knownHostsPath) + f, err := os.OpenFile(knownHostsPath, os.O_CREATE|os.O_RDONLY, 0600) if err != nil { return nil, fmt.Errorf("failed to create known_hosts file: %w", err) } From fc90a080a805dc1d0b79f23ba1b01da6aa55d73c Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Mon, 2 Feb 2026 13:03:52 +0100 Subject: [PATCH 63/65] fix: new tests --- cli/cmd/install_k0s_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/cmd/install_k0s_test.go b/cli/cmd/install_k0s_test.go index b4386924..94c9f698 100644 --- a/cli/cmd/install_k0s_test.go +++ b/cli/cmd/install_k0s_test.go @@ -156,6 +156,7 @@ var _ = Describe("InstallK0sCmd", func() { mockEnv.EXPECT().GetOmsWorkdir().Return(tempDir) mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") mockK0sctl.EXPECT().Download("", true, false).Return("/tmp/k0sctl", nil) + mockFileWriter.EXPECT().WriteFile(mock.Anything, mock.Anything, mock.Anything).Return(nil) mockK0sctl.EXPECT().Apply(mock.Anything, "/tmp/k0sctl", true).Return(nil) err = c.InstallK0s(mockPM, mockK0s, mockK0sctl) @@ -177,6 +178,7 @@ var _ = Describe("InstallK0sCmd", func() { mockEnv.EXPECT().GetOmsWorkdir().Return(tempDir) mockK0s.EXPECT().Download("v1.29.0+k0s.0", false, false).Return("/downloaded/k0s", nil) mockK0sctl.EXPECT().Download("", false, false).Return("/tmp/k0sctl", nil) + mockFileWriter.EXPECT().WriteFile(mock.Anything, mock.Anything, mock.Anything).Return(nil) mockK0sctl.EXPECT().Apply(mock.Anything, "/tmp/k0sctl", false).Return(nil) err = c.InstallK0s(mockPM, mockK0s, mockK0sctl) @@ -237,6 +239,7 @@ var _ = Describe("InstallK0sCmd", func() { mockEnv.EXPECT().GetOmsWorkdir().Return(tempDir) mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") mockK0sctl.EXPECT().Download("", false, false).Return("/tmp/k0sctl", nil) + mockFileWriter.EXPECT().WriteFile(mock.Anything, mock.Anything, mock.Anything).Return(nil) mockK0sctl.EXPECT().Apply(mock.Anything, "/tmp/k0sctl", false).Return(os.ErrPermission) err = c.InstallK0s(mockPM, mockK0s, mockK0sctl) From ff7bfff803580217f534f9b87758080a2c4eb1d2 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Mon, 2 Feb 2026 13:27:19 +0100 Subject: [PATCH 64/65] Update hack/lima-oms.yaml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- hack/lima-oms.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/lima-oms.yaml b/hack/lima-oms.yaml index f3f90113..e0748fdd 100644 --- a/hack/lima-oms.yaml +++ b/hack/lima-oms.yaml @@ -93,7 +93,7 @@ provision: message: | Your OMS development environment is ready! - VM IP: Run 'hostname -I | awk '{print $1}'' to get your VM's IP address + VM IP: Run "hostname -I | awk '{print $1}'" to get your VM's IP address Quick Start: ------ From 36f193eb6e2f13dd19ca0d313b8879d8eabcf43b Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Mon, 2 Feb 2026 13:30:46 +0100 Subject: [PATCH 65/65] ref: implement review suggestions --- cli/cmd/install_k0s.go | 4 ++++ cli/cmd/install_k0s_test.go | 3 +++ internal/installer/k0sctl.go | 8 ++++---- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cli/cmd/install_k0s.go b/cli/cmd/install_k0s.go index 062fc7b4..da845a74 100644 --- a/cli/cmd/install_k0s.go +++ b/cli/cmd/install_k0s.go @@ -120,6 +120,10 @@ func (c *InstallK0sCmd) InstallK0s(pm installer.PackageManager, k0s installer.K0 var k0sBinaryPath string if !c.Opts.NoDownload { if c.Opts.Package != "" { + // Extract the k0s binary from the package first + if err := pm.ExtractDependency(defaultK0sPath, c.Opts.Force); err != nil { + return fmt.Errorf("failed to extract k0s from package: %w", err) + } k0sBinaryPath = pm.GetDependencyPath(defaultK0sPath) } else { var err error diff --git a/cli/cmd/install_k0s_test.go b/cli/cmd/install_k0s_test.go index 94c9f698..32de387a 100644 --- a/cli/cmd/install_k0s_test.go +++ b/cli/cmd/install_k0s_test.go @@ -154,6 +154,7 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.Force = true mockEnv.EXPECT().GetOmsWorkdir().Return(tempDir) + mockPM.EXPECT().ExtractDependency("kubernetes/files/k0s", true).Return(nil) mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") mockK0sctl.EXPECT().Download("", true, false).Return("/tmp/k0sctl", nil) mockFileWriter.EXPECT().WriteFile(mock.Anything, mock.Anything, mock.Anything).Return(nil) @@ -216,6 +217,7 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.Package = "test-package.tar.gz" c.Opts.Version = "v1.30.0+k0s.0" + mockPM.EXPECT().ExtractDependency("kubernetes/files/k0s", false).Return(nil) mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") mockK0sctl.EXPECT().Download("", false, false).Return("", os.ErrPermission) @@ -237,6 +239,7 @@ var _ = Describe("InstallK0sCmd", func() { c.Opts.Version = "v1.30.0+k0s.0" mockEnv.EXPECT().GetOmsWorkdir().Return(tempDir) + mockPM.EXPECT().ExtractDependency("kubernetes/files/k0s", false).Return(nil) mockPM.EXPECT().GetDependencyPath("kubernetes/files/k0s").Return("/test/path/k0s") mockK0sctl.EXPECT().Download("", false, false).Return("/tmp/k0sctl", nil) mockFileWriter.EXPECT().WriteFile(mock.Anything, mock.Anything, mock.Anything).Return(nil) diff --git a/internal/installer/k0sctl.go b/internal/installer/k0sctl.go index 9eaafd97..ca9b5de7 100644 --- a/internal/installer/k0sctl.go +++ b/internal/installer/k0sctl.go @@ -134,7 +134,7 @@ func (k *K0sctl) Apply(configPath string, k0sctlPath string, force bool) error { return fmt.Errorf("k0sctl config does not exist at '%s'", configPath) } - args := []string{"env", "-u", "SSH_AUTH_SOCK", k0sctlPath, "apply", "--config", configPath} + args := []string{"apply", "--config", configPath} if force { args = append(args, "--force") @@ -145,7 +145,7 @@ func (k *K0sctl) Apply(configPath string, k0sctlPath string, force bool) error { log.Printf("Running k0sctl apply with config: %s", configPath) - err := util.RunCommand("sudo", args, "") + err := util.RunCommand(k0sctlPath, args, "") if err != nil { return fmt.Errorf("k0sctl apply failed: %w", err) } @@ -165,9 +165,9 @@ func (k *K0sctl) Reset(configPath string, k0sctlPath string) error { log.Println("Resetting k0s cluster using k0sctl...") - args := []string{"env", "-u", "SSH_AUTH_SOCK", k0sctlPath, "reset", "--config", configPath, "--force"} + args := []string{"reset", "--config", configPath, "--force"} - err := util.RunCommand("sudo", args, "") + err := util.RunCommand(k0sctlPath, args, "") if err != nil { return fmt.Errorf("k0sctl reset failed: %w", err) }