From c6a57a46e0017d3c0b23ae1fac2b408a66f646d3 Mon Sep 17 00:00:00 2001 From: Ethan Mosbaugh Date: Fri, 20 Sep 2024 14:07:34 -0500 Subject: [PATCH] feat: ability to override admin console and lam host ports (#1201) * feat: ability to override admin console and lam host ports * kots overrides * f * Revert "kots overrides" This reverts commit edbded188d318ac53aa23afb57464cfe66be74e7. * update kots --- Makefile | 3 +- cmd/embedded-cluster/flags.go | 58 +++++++++ cmd/embedded-cluster/install.go | 58 ++++++++- cmd/embedded-cluster/join.go | 15 ++- cmd/embedded-cluster/restore.go | 96 ++++++++++++++- cmd/embedded-cluster/util.go | 7 +- cmd/local-artifact-mirror/serve.go | 26 +++- dev/dockerfiles/operator/Dockerfile.ttlsh | 1 + e2e/install_test.go | 6 +- e2e/local-artifact-mirror_test.go | 8 +- e2e/scripts/default-install.sh | 12 +- .../restore-multi-node-airgap-phase1.exp | 2 +- .../controllers/installation_controller.go | 7 +- operator/pkg/artifacts/upgrade.go | 7 +- operator/pkg/charts/charts.go | 112 ++++++++++-------- pkg/addons/adminconsole/adminconsole.go | 39 ++++-- pkg/addons/adminconsole/static/metadata.yaml | 14 +-- pkg/addons/applier.go | 64 +++++++--- .../embeddedclusteroperator.go | 58 +++++---- pkg/addons/options.go | 14 +++ pkg/defaults/defaults.go | 8 +- pkg/helm/values.go | 41 +++++++ 22 files changed, 522 insertions(+), 134 deletions(-) create mode 100644 cmd/embedded-cluster/flags.go create mode 100644 pkg/helm/values.go diff --git a/Makefile b/Makefile index 52b8431e1..b75c87eb7 100644 --- a/Makefile +++ b/Makefile @@ -269,6 +269,7 @@ list-distros: .PHONY: create-node% create-node%: DISTRO = debian-bookworm +create-node%: NODE_PORT = 30000 create-node%: @if ! docker images | grep -q ec-$(DISTRO); then \ $(MAKE) -C dev/distros build-$(DISTRO); \ @@ -282,7 +283,7 @@ create-node%: -v /var/lib/k0s \ -v $(shell pwd):/replicatedhq/embedded-cluster \ -v $(shell dirname $(shell pwd))/kots:/replicatedhq/kots \ - $(if $(filter node0,node$*),-p 30000:30000) \ + $(if $(filter node0,node$*),-p $(NODE_PORT):$(NODE_PORT)) \ ec-$(DISTRO) @$(MAKE) ssh-node$* diff --git a/cmd/embedded-cluster/flags.go b/cmd/embedded-cluster/flags.go new file mode 100644 index 000000000..cdd2ed7db --- /dev/null +++ b/cmd/embedded-cluster/flags.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "strconv" + + "github.com/replicatedhq/embedded-cluster/pkg/defaults" + "github.com/urfave/cli/v2" + k8snet "k8s.io/utils/net" +) + +func getAdminColsolePortFlag() cli.Flag { + return &cli.StringFlag{ + Name: "admin-console-port", + Usage: "Port on which the Admin Console will be served", + Value: strconv.Itoa(defaults.AdminConsolePort), + Hidden: false, + } +} + +func getAdminConsolePortFromFlag(c *cli.Context) (int, error) { + portStr := c.String("admin-console-port") + if portStr == "" { + return defaults.AdminConsolePort, nil + } + // TODO: add first class support for service node port range and validate the port + port, err := k8snet.ParsePort(portStr, false) + if err != nil { + return 0, fmt.Errorf("invalid admin console port: %w", err) + } + return port, nil +} + +func getLocalArtifactMirrorPortFlag() cli.Flag { + return &cli.StringFlag{ + Name: "local-artifact-mirror-port", + Usage: "Port on which the Local Artifact Mirror will be served", + Value: strconv.Itoa(defaults.LocalArtifactMirrorPort), + Hidden: false, + } +} + +func getLocalArtifactMirrorPortFromFlag(c *cli.Context) (int, error) { + portStr := c.String("local-artifact-mirror-port") + if portStr == "" { + return defaults.LocalArtifactMirrorPort, nil + } + // TODO: add first class support for service node port range and validate the port does not + // conflict with this range + port, err := k8snet.ParsePort(portStr, false) + if err != nil { + return 0, fmt.Errorf("invalid local artifact mirror port: %w", err) + } + if portStr == c.String("admin-console-port") { + return 0, fmt.Errorf("local artifact mirror port cannot be the same as admin console port") + } + return port, nil +} diff --git a/cmd/embedded-cluster/install.go b/cmd/embedded-cluster/install.go index 06065b604..c702305c9 100644 --- a/cmd/embedded-cluster/install.go +++ b/cmd/embedded-cluster/install.go @@ -37,10 +37,13 @@ var ErrNothingElseToAdd = fmt.Errorf("") // installAndEnableLocalArtifactMirror installs and enables the local artifact mirror. This // service is responsible for serving on localhost, through http, all files that are used // during a cluster upgrade. -func installAndEnableLocalArtifactMirror() error { +func installAndEnableLocalArtifactMirror(port int) error { if err := goods.MaterializeLocalArtifactMirrorUnitFile(); err != nil { return fmt.Errorf("failed to materialize artifact mirror unit: %w", err) } + if err := writeLocalArtifactMirrorEnvironmentFile(port); err != nil { + return fmt.Errorf("failed to write local artifact mirror environment file: %w", err) + } if _, err := helpers.RunCommand("systemctl", "daemon-reload"); err != nil { return fmt.Errorf("unable to get reload systemctl daemon: %w", err) } @@ -48,7 +51,41 @@ func installAndEnableLocalArtifactMirror() error { return fmt.Errorf("unable to start the local artifact mirror: %w", err) } if _, err := helpers.RunCommand("systemctl", "enable", "local-artifact-mirror"); err != nil { - return fmt.Errorf("unable to start the local artifact mirror: %w", err) + return fmt.Errorf("unable to start the local artifact mirror service: %w", err) + } + return nil +} + +// updateLocalArtifactMirrorService updates the port on which the local artifact mirror is served. +func updateLocalArtifactMirrorService(port int) error { + if err := writeLocalArtifactMirrorEnvironmentFile(port); err != nil { + return fmt.Errorf("failed to write local artifact mirror environment file: %w", err) + } + if _, err := helpers.RunCommand("systemctl", "daemon-reload"); err != nil { + return fmt.Errorf("unable to get reload systemctl daemon: %w", err) + } + if _, err := helpers.RunCommand("systemctl", "restart", "local-artifact-mirror"); err != nil { + return fmt.Errorf("unable to restart the local artifact mirror service: %w", err) + } + return nil +} + +const ( + localArtifactMirrorSystemdConfFile = "/etc/systemd/system/local-artifact-mirror.service.d/embedded-cluster.conf" + localArtifactMirrorEnvironmentFileContents = `[Service] +Environment="LOCAL_ARTIFACT_MIRROR_PORT=%d"` +) + +func writeLocalArtifactMirrorEnvironmentFile(port int) error { + dir := filepath.Dir(localArtifactMirrorSystemdConfFile) + err := os.MkdirAll(dir, 0755) + if err != nil { + return fmt.Errorf("create directory: %w", err) + } + contents := fmt.Sprintf(localArtifactMirrorEnvironmentFileContents, port) + err = os.WriteFile(localArtifactMirrorSystemdConfFile, []byte(contents), 0644) + if err != nil { + return fmt.Errorf("write file: %w", err) } return nil } @@ -463,7 +500,7 @@ func installAndWaitForK0s(c *cli.Context, applier *addons.Applier, proxy *ecv1be return nil, err } logrus.Debugf("creating systemd unit files") - if err := createSystemdUnitFiles(false, proxy); err != nil { + if err := createSystemdUnitFiles(false, proxy, applier.GetLocalArtifactMirrorPort()); err != nil { err := fmt.Errorf("unable to create systemd unit files: %w", err) metrics.ReportApplyFinished(c, err) return nil, err @@ -591,6 +628,8 @@ var installCommand = &cli.Command{ Name: "private-ca", Usage: "Path to a trusted private CA certificate file", }, + getAdminColsolePortFlag(), + getLocalArtifactMirrorPortFlag(), }, )), Action: func(c *cli.Context) error { @@ -709,6 +748,19 @@ func getAddonsApplier(c *cli.Context, adminConsolePwd string, proxy *ecv1beta1.P } opts = append(opts, addons.WithPrivateCAs(privateCAs)) } + + adminConsolePort, err := getAdminConsolePortFromFlag(c) + if err != nil { + return nil, err + } + opts = append(opts, addons.WithAdminConsolePort(adminConsolePort)) + + localArtifactMirrorPort, err := getLocalArtifactMirrorPortFromFlag(c) + if err != nil { + return nil, err + } + opts = append(opts, addons.WithLocalArtifactMirrorPort(localArtifactMirrorPort)) + if adminConsolePwd != "" { opts = append(opts, addons.WithAdminConsolePassword(adminConsolePwd)) } diff --git a/cmd/embedded-cluster/join.go b/cmd/embedded-cluster/join.go index afa2f8f66..b3bb3dfad 100644 --- a/cmd/embedded-cluster/join.go +++ b/cmd/embedded-cluster/join.go @@ -41,10 +41,11 @@ type JoinCommandResponse struct { K0sUnsupportedOverrides string `json:"k0sUnsupportedOverrides"` EndUserK0sConfigOverrides string `json:"endUserK0sConfigOverrides"` // MetricsBaseURL is the https://replicated.app endpoint url - MetricsBaseURL string `json:"metricsBaseURL"` - AirgapRegistryAddress string `json:"airgapRegistryAddress"` - Proxy *ecv1beta1.ProxySpec `json:"proxy"` - Network *ecv1beta1.NetworkSpec `json:"network"` + MetricsBaseURL string `json:"metricsBaseURL"` + AirgapRegistryAddress string `json:"airgapRegistryAddress"` + Proxy *ecv1beta1.ProxySpec `json:"proxy"` + Network *ecv1beta1.NetworkSpec `json:"network"` + LocalArtifactMirrorPort int `json:"localArtifactMirrorPort,omitempty"` } // extractK0sConfigOverridePatch parses the provided override and returns a dig.Mapping that @@ -265,8 +266,12 @@ var joinCommand = &cli.Command{ } logrus.Debugf("creating systemd unit files") + localArtifactMirrorPort := defaults.LocalArtifactMirrorPort + if jcmd.LocalArtifactMirrorPort > 0 { + localArtifactMirrorPort = jcmd.LocalArtifactMirrorPort + } // both controller and worker nodes will have 'worker' in the join command - if err := createSystemdUnitFiles(!strings.Contains(jcmd.K0sJoinCommand, "controller"), jcmd.Proxy); err != nil { + if err := createSystemdUnitFiles(!strings.Contains(jcmd.K0sJoinCommand, "controller"), jcmd.Proxy, localArtifactMirrorPort); err != nil { err := fmt.Errorf("unable to create systemd unit files: %w", err) metrics.ReportJoinFailed(c.Context, jcmd.MetricsBaseURL, jcmd.ClusterID, err) return err diff --git a/cmd/embedded-cluster/restore.go b/cmd/embedded-cluster/restore.go index ba8bba5f2..8cc1a0e32 100644 --- a/cmd/embedded-cluster/restore.go +++ b/cmd/embedded-cluster/restore.go @@ -39,6 +39,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + k8snet "k8s.io/utils/net" "k8s.io/utils/ptr" k8sconfig "sigs.k8s.io/controller-runtime/pkg/client/config" k8syaml "sigs.k8s.io/yaml" @@ -261,7 +262,7 @@ func newS3BackupStore() *s3BackupStore { } store.region = prompts.New().Input("Region:", "", true) store.bucket = prompts.New().Input("Bucket:", "", true) - store.prefix = prompts.New().Input("Prefix (press Enter to skip):", "", false) + store.prefix = strings.TrimPrefix(prompts.New().Input("Prefix (press Enter to skip):", "", false), "/") store.accessKeyID = prompts.New().Input("Access key ID:", "", true) store.secretAccessKey = prompts.New().Password("Secret access key:") logrus.Info("") @@ -811,10 +812,19 @@ func waitForAdditionalNodes(ctx context.Context, highAvailability bool, networkI return fmt.Errorf("unable to create kube client: %w", err) } + in, err := kubeutils.GetLatestInstallation(ctx, kcli) + if err != nil { + return fmt.Errorf("unable to get latest installation: %w", err) + } + adminConsolePort := defaults.AdminConsolePort + if in.Spec.AdminConsole != nil && in.Spec.AdminConsole.Port > 0 { + adminConsolePort = in.Spec.AdminConsole.Port + } + successColor := "\033[32m" colorReset := "\033[0m" joinNodesMsg := fmt.Sprintf("\nVisit the Admin Console if you need to add nodes to the cluster: %s%s%s\n", - successColor, adminconsole.GetURL(networkInterface), colorReset, + successColor, adminconsole.GetURL(networkInterface, adminConsolePort), colorReset, ) logrus.Info(joinNodesMsg) @@ -860,7 +870,7 @@ func installAndWaitForRestoredK0sNode(c *cli.Context, applier *addons.Applier) ( } proxy := getProxySpecFromFlags(c) logrus.Debugf("creating systemd unit files") - if err := createSystemdUnitFiles(false, proxy); err != nil { + if err := createSystemdUnitFiles(false, proxy, applier.GetLocalArtifactMirrorPort()); err != nil { return nil, fmt.Errorf("unable to create systemd unit files: %w", err) } logrus.Debugf("installing k0s") @@ -907,6 +917,12 @@ var restoreCommand = &cli.Command{ Value: false, Hidden: true, }, + &cli.StringFlag{ + Name: "local-artifact-mirror-port", + Usage: "Port on which the Local Artifact Mirror will be served. If left empty, the port will be retrieved from the snapshot.", + // DefaultText: strconv.Itoa(defaults.LocalArtifactMirrorPort), + Hidden: false, + }, }, )), Before: func(c *cli.Context) error { @@ -1063,7 +1079,11 @@ var restoreCommand = &cli.Command{ } logrus.Debugf("restoring embedded cluster installation from backup %q", backupToRestore.Name) if err := restoreFromBackup(c.Context, backupToRestore, disasterRecoveryComponentECInstall); err != nil { - return err + return fmt.Errorf("unable to restore from backup: %w", err) + } + logrus.Debugf("updating local artifact mirror port %q", backupToRestore.Name) + if err := restoreReconcileLocalArtifactMirrorPort(c, backupToRestore); err != nil { + return fmt.Errorf("unable to update local artifact mirror port: %w", err) } fallthrough @@ -1165,3 +1185,71 @@ var restoreCommand = &cli.Command{ return nil }, } + +// restoreReconcileLocalArtifactMirrorPort will set the local artifact mirror port in the +// installation if it was explicitly set using a flag, otherwise it will update the service to use +// the port from the installation. +func restoreReconcileLocalArtifactMirrorPort(c *cli.Context, backup *velerov1.Backup) error { + if c.IsSet("local-artifact-mirror-port") { + logrus.Debugf("updating local artifact mirror port from flag %q", backup.Name) + err := restoreReconcileLocalArtifactMirrorPortFromFlag(c) + if err != nil { + return fmt.Errorf("unable to update local artifact mirror port from flag: %w", err) + } + return nil + } + + logrus.Debugf("updating local artifact mirror port from backup %q", backup.Name) + err := restoreReconcileLocalArtifactMirrorPortFromBackup(backup) + if err != nil { + return fmt.Errorf("unable to update local artifact mirror port from backup: %w", err) + } + return nil +} + +// restoreReconcileLocalArtifactMirrorPortFromFlag will set the local artifact mirror port in the +// installation from the flag. +func restoreReconcileLocalArtifactMirrorPortFromFlag(c *cli.Context) error { + kcli, err := kubeutils.KubeClient() + if err != nil { + return fmt.Errorf("create kube client: %w", err) + } + in, err := kubeutils.GetLatestInstallation(c.Context, kcli) + if err != nil { + return fmt.Errorf("get latest installation: %w", err) + } + if in.Spec.LocalArtifactMirror == nil { + in.Spec.LocalArtifactMirror = &ecv1beta1.LocalArtifactMirrorSpec{} + } + port, err := getLocalArtifactMirrorPortFromFlag(c) + if err != nil { + return fmt.Errorf("get local artifact mirror port: %w", err) + } + if in.Spec.LocalArtifactMirror.Port == port { + return nil + } + logrus.Debugf("updating local artifact mirror port from flag to %d on installation %q", port, in.Name) + in.Spec.LocalArtifactMirror.Port = port + if err := kcli.Update(c.Context, in); err != nil { + return fmt.Errorf("update installation: %w", err) + } + return nil +} + +// restoreReconcileLocalArtifactMirrorPortFromBackup will update the service to use the port from +// the installation. +func restoreReconcileLocalArtifactMirrorPortFromBackup(backup *velerov1.Backup) error { + portStr := backup.Annotations["kots.io/embedded-cluster-local-artifact-mirror-port"] + if portStr == "" { + return nil + } + port, err := k8snet.ParsePort(portStr, false) + if err != nil { + return fmt.Errorf("unable to parse local artifact mirror port from backup: %w", err) + } + logrus.Debugf("updating local artifact mirror port from backup to %d", port) + if err := updateLocalArtifactMirrorService(port); err != nil { + return fmt.Errorf("unable to update local artifact mirror service: %w", err) + } + return nil +} diff --git a/cmd/embedded-cluster/util.go b/cmd/embedded-cluster/util.go index 49fe700b6..f3ef286e6 100644 --- a/cmd/embedded-cluster/util.go +++ b/cmd/embedded-cluster/util.go @@ -12,7 +12,7 @@ import ( // createSystemdUnitFiles links the k0s systemd unit file. this also creates a new // systemd unit file for the local artifact mirror service. -func createSystemdUnitFiles(isWorker bool, proxy *ecv1beta1.ProxySpec) error { +func createSystemdUnitFiles(isWorker bool, proxy *ecv1beta1.ProxySpec, localArtifactMirrorPort int) error { dst := systemdUnitFileName() if _, err := os.Lstat(dst); err == nil { if err := os.Remove(dst); err != nil { @@ -36,7 +36,10 @@ func createSystemdUnitFiles(isWorker bool, proxy *ecv1beta1.ProxySpec) error { if _, err := helpers.RunCommand("systemctl", "daemon-reload"); err != nil { return fmt.Errorf("unable to get reload systemctl daemon: %w", err) } - return installAndEnableLocalArtifactMirror() + if err := installAndEnableLocalArtifactMirror(localArtifactMirrorPort); err != nil { + return fmt.Errorf("unable to install and enable local artifact mirror: %w", err) + } + return nil } // ensureProxyConfig creates a new http-proxy.conf configuration file. The file is saved in the diff --git a/cmd/local-artifact-mirror/serve.go b/cmd/local-artifact-mirror/serve.go index ebd98c242..d48143c86 100644 --- a/cmd/local-artifact-mirror/serve.go +++ b/cmd/local-artifact-mirror/serve.go @@ -3,15 +3,18 @@ package main import ( "context" "fmt" + "net" "net/http" "os" "os/signal" + "strconv" "strings" "syscall" "time" "github.com/replicatedhq/embedded-cluster/pkg/defaults" "github.com/urfave/cli/v2" + k8snet "k8s.io/utils/net" ) // serveCommand starts a http server that serves files from the /var/lib/embedded-cluster @@ -20,6 +23,14 @@ import ( var serveCommand = &cli.Command{ Name: "serve", Usage: "Serve /var/lib/embedded-cluster files over HTTP", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "port", + Usage: "Port to listen on", + Value: strconv.Itoa(defaults.LocalArtifactMirrorPort), + EnvVars: []string{"LOCAL_ARTIFACT_MIRROR_PORT"}, + }, + }, Before: func(c *cli.Context) error { if os.Getuid() != 0 { return fmt.Errorf("serve command must be run as root") @@ -40,9 +51,20 @@ var serveCommand = &cli.Command{ panic(err) } - server := &http.Server{Addr: "127.0.0.1:50000"} + port := defaults.LocalArtifactMirrorPort + portStr := c.String("port") + if portStr != "" { + var err error + port, err = k8snet.ParsePort(portStr, false) + if err != nil { + panic(fmt.Errorf("unable to parse port: %w", err)) + } + } + + addr := net.JoinHostPort("127.0.0.1", strconv.Itoa(port)) + server := &http.Server{Addr: addr} go func() { - fmt.Println("Starting server on 127.0.0.1:50000") + fmt.Printf("Starting server on %s\n", addr) if err := server.ListenAndServe(); err != nil { if err != http.ErrServerClosed { panic(err) diff --git a/dev/dockerfiles/operator/Dockerfile.ttlsh b/dev/dockerfiles/operator/Dockerfile.ttlsh index 13f7516ec..eaa5a5508 100644 --- a/dev/dockerfiles/operator/Dockerfile.ttlsh +++ b/dev/dockerfiles/operator/Dockerfile.ttlsh @@ -9,6 +9,7 @@ RUN --mount=type=cache,target="/go/pkg/mod" go mod download COPY common.mk common.mk COPY operator/ operator/ +COPY pkg/ pkg/ COPY kinds/ kinds/ COPY utils/ utils/ diff --git a/e2e/install_test.go b/e2e/install_test.go index db5d9b503..dd4263084 100644 --- a/e2e/install_test.go +++ b/e2e/install_test.go @@ -31,7 +31,7 @@ func TestSingleNodeInstallation(t *testing.T) { defer cleanupCluster(t, tc) t.Logf("%s: installing embedded-cluster on node 0", time.Now().Format(time.RFC3339)) - line := []string{"single-node-install.sh", "ui"} + line := []string{"single-node-install.sh", "ui", "--admin-console-port", "30002"} if _, _, err := RunCommandOnNode(t, tc, 0, line); err != nil { t.Fatalf("fail to install embedded-cluster on node %s: %v", tc.Nodes[0], err) } @@ -806,7 +806,7 @@ func TestSingleNodeAirgapUpgrade(t *testing.T) { } t.Logf("%s: installing embedded-cluster on node 0", time.Now().Format(time.RFC3339)) - line = []string{"single-node-airgap-install.sh"} + line = []string{"single-node-airgap-install.sh", "--local-artifact-mirror-port", "50001"} // choose an alternate lam port if _, _, err := RunCommandOnNode(t, tc, 0, line); err != nil { t.Fatalf("fail to install embedded-cluster on node %s: %v", tc.Nodes[0], err) } @@ -1257,7 +1257,7 @@ func TestMultiNodeAirgapUpgrade(t *testing.T) { } t.Logf("%s: installing embedded-cluster on node 0", time.Now().Format(time.RFC3339)) - line = []string{"single-node-airgap-install.sh"} + line = []string{"single-node-airgap-install.sh", "--local-artifact-mirror-port", "50001"} // choose an alternate lam port if _, _, err := RunCommandOnNode(t, tc, 0, line); err != nil { t.Fatalf("fail to install embedded-cluster on node %s: %v", tc.Nodes[0], err) } diff --git a/e2e/local-artifact-mirror_test.go b/e2e/local-artifact-mirror_test.go index 0e5562773..58bf89488 100644 --- a/e2e/local-artifact-mirror_test.go +++ b/e2e/local-artifact-mirror_test.go @@ -23,7 +23,7 @@ func TestLocalArtifactMirror(t *testing.T) { defer cleanupCluster(t, tc) t.Logf("%s: installing embedded-cluster on node 0", time.Now().Format(time.RFC3339)) - line := []string{"default-install.sh"} + line := []string{"default-install.sh", "--local-artifact-mirror-port", "50001"} if _, _, err := RunCommandOnNode(t, tc, 0, line); err != nil { t.Fatalf("fail to install embedded-cluster on node %s: %v", tc.Nodes[0], err) } @@ -34,7 +34,7 @@ func TestLocalArtifactMirror(t *testing.T) { {"systemctl", "stop", "local-artifact-mirror"}, {"systemctl", "start", "local-artifact-mirror"}, {"systemctl", "status", "local-artifact-mirror"}, - {"curl", "-o", "/tmp/kubectl-test", "127.0.0.1:50000/bin/kubectl"}, + {"curl", "-o", "/tmp/kubectl-test", "127.0.0.1:50001/bin/kubectl"}, {"chmod", "755", "/tmp/kubectl-test"}, {"/tmp/kubectl-test", "version", "--client"}, } @@ -47,13 +47,13 @@ func TestLocalArtifactMirror(t *testing.T) { t.Fatalf("fail to copy file: %v", err) } - command = []string{"curl", "-O", "--fail", "127.0.0.1:50000/logs/passwd"} + command = []string{"curl", "-O", "--fail", "127.0.0.1:50001/logs/passwd"} t.Logf("running %v", command) if _, _, err := RunCommandOnNode(t, tc, 0, command); err == nil { t.Fatalf("we should not be able to fetch logs from local artifact mirror") } - command = []string{"curl", "-O", "--fail", "127.0.0.1:50000/../../../etc/passwd"} + command = []string{"curl", "-O", "--fail", "127.0.0.1:50001/../../../etc/passwd"} t.Logf("running %v", command) if _, _, err := RunCommandOnNode(t, tc, 0, command); err == nil { t.Fatalf("we should not be able to fetch paths with ../") diff --git a/e2e/scripts/default-install.sh b/e2e/scripts/default-install.sh index 31f39d296..e8e0e594e 100755 --- a/e2e/scripts/default-install.sh +++ b/e2e/scripts/default-install.sh @@ -14,12 +14,18 @@ check_openebs_storage_class() { } main() { - if embedded-cluster install --no-prompt --skip-host-preflights --license /assets/license.yaml 2>&1 | tee /tmp/log ; then + local additional_args= + if [ -n "${1:-}" ]; then + additional_args="$*" + echo "Running install with additional args: $additional_args" + fi + + if embedded-cluster install --no-prompt --skip-host-preflights --license /assets/license.yaml $additional_args 2>&1 | tee /tmp/log ; then echo "Expected installation to fail with a license provided" exit 1 fi - if ! embedded-cluster install --no-prompt --skip-host-preflights 2>&1 | tee /tmp/log ; then + if ! embedded-cluster install --no-prompt --skip-host-preflights $additional_args 2>&1 | tee /tmp/log ; then cat /etc/os-release echo "Failed to install embedded-cluster" exit 1 @@ -49,4 +55,4 @@ main() { export EMBEDDED_CLUSTER_METRICS_BASEURL="https://staging.replicated.app" export KUBECONFIG=/var/lib/k0s/pki/admin.conf export PATH=$PATH:/var/lib/embedded-cluster/bin -main +main "$@" diff --git a/e2e/scripts/restore-multi-node-airgap-phase1.exp b/e2e/scripts/restore-multi-node-airgap-phase1.exp index 300d17344..9058acefb 100755 --- a/e2e/scripts/restore-multi-node-airgap-phase1.exp +++ b/e2e/scripts/restore-multi-node-airgap-phase1.exp @@ -12,7 +12,7 @@ set dr_aws_s3_prefix [lindex $argv 3] set dr_aws_access_key_id [lindex $argv 4] set dr_aws_secret_access_key [lindex $argv 5] -spawn embedded-cluster restore --airgap-bundle /assets/release.airgap --proxy +spawn embedded-cluster restore --airgap-bundle /assets/release.airgap --proxy --local-artifact-mirror-port 50001 expect { "Enter information to configure access to your backup storage location." {} diff --git a/operator/controllers/installation_controller.go b/operator/controllers/installation_controller.go index dff55df15..0efa020a7 100644 --- a/operator/controllers/installation_controller.go +++ b/operator/controllers/installation_controller.go @@ -31,6 +31,7 @@ import ( apcore "github.com/k0sproject/k0s/pkg/autopilot/controller/plans/core" "github.com/k0sproject/version" ectypes "github.com/replicatedhq/embedded-cluster/kinds/types" + "github.com/replicatedhq/embedded-cluster/pkg/defaults" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -753,7 +754,11 @@ func (r *InstallationReconciler) StartAutopilotUpgrade(ctx context.Context, in * // if we are running in an airgap environment all assets are already present in the // node and are served by the local-artifact-mirror binary listening on localhost // port 50000. we just need to get autopilot to fetch the k0s binary from there. - k0surl = "http://127.0.0.1:50000/bin/k0s-upgrade" + port := defaults.LocalArtifactMirrorPort + if in.Spec.LocalArtifactMirror != nil && in.Spec.LocalArtifactMirror.Port > 0 { + port = in.Spec.LocalArtifactMirror.Port + } + k0surl = fmt.Sprintf("http://127.0.0.1:%d/bin/k0s-upgrade", port) } else { artifact := meta.Artifacts["k0s"] if strings.HasPrefix(artifact, "https://") || strings.HasPrefix(artifact, "http://") { diff --git a/operator/pkg/artifacts/upgrade.go b/operator/pkg/artifacts/upgrade.go index 9d656fc67..44a277ae9 100644 --- a/operator/pkg/artifacts/upgrade.go +++ b/operator/pkg/artifacts/upgrade.go @@ -12,6 +12,7 @@ import ( "github.com/replicatedhq/embedded-cluster/operator/pkg/k8sutil" "github.com/replicatedhq/embedded-cluster/operator/pkg/release" "github.com/replicatedhq/embedded-cluster/operator/pkg/util" + "github.com/replicatedhq/embedded-cluster/pkg/defaults" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -252,7 +253,11 @@ func CreateAutopilotAirgapPlanCommand(ctx context.Context, cli client.Client, in allNodes = append(allNodes, node.Name) } - imageURL := fmt.Sprintf("http://127.0.0.1:50000/images/images-amd64-%s.tar", in.Name) + port := defaults.LocalArtifactMirrorPort + if in.Spec.LocalArtifactMirror != nil && in.Spec.LocalArtifactMirror.Port > 0 { + port = in.Spec.LocalArtifactMirror.Port + } + imageURL := fmt.Sprintf("http://127.0.0.1:%d/images/images-amd64-%s.tar", port, in.Name) return &autopilotv1beta2.PlanCommand{ AirgapUpdate: &autopilotv1beta2.PlanCommandAirgapUpdate{ diff --git a/operator/pkg/charts/charts.go b/operator/pkg/charts/charts.go index eb1c8a21d..3ce5c7ae0 100644 --- a/operator/pkg/charts/charts.go +++ b/operator/pkg/charts/charts.go @@ -5,10 +5,7 @@ import ( "fmt" "path/filepath" - "github.com/k0sproject/dig" k0sv1beta1 "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1" - "github.com/ohler55/ojg/jp" - "gopkg.in/yaml.v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/replicatedhq/embedded-cluster/kinds/apis/v1beta1" @@ -17,6 +14,7 @@ import ( "github.com/replicatedhq/embedded-cluster/operator/pkg/k8sutil" "github.com/replicatedhq/embedded-cluster/operator/pkg/registry" "github.com/replicatedhq/embedded-cluster/operator/pkg/util" + "github.com/replicatedhq/embedded-cluster/pkg/helm" ) const ( @@ -129,59 +127,87 @@ func mergeHelmConfigs(ctx context.Context, meta *ectypes.ReleaseMetadata, in *cl func updateInfraChartsFromInstall(in *v1beta1.Installation, clusterConfig *k0sv1beta1.ClusterConfig, charts []v1beta1.Chart) ([]v1beta1.Chart, error) { for i, chart := range charts { if chart.Name == "admin-console" { + newVals, err := helm.UnmarshalValues(chart.Values) + if err != nil { + return nil, fmt.Errorf("unmarshal admin-console.values: %w", err) + } + // admin-console has "embeddedClusterID" and "isAirgap" as dynamic values - newVals, err := setHelmValue(chart.Values, "embeddedClusterID", in.Spec.ClusterID) + newVals, err = helm.SetValue(newVals, "embeddedClusterID", in.Spec.ClusterID) if err != nil { return nil, fmt.Errorf("set helm values admin-console.embeddedClusterID: %w", err) } - newVals, err = setHelmValue(newVals, "isAirgap", fmt.Sprintf("%t", in.Spec.AirGap)) + newVals, err = helm.SetValue(newVals, "isAirgap", fmt.Sprintf("%t", in.Spec.AirGap)) if err != nil { return nil, fmt.Errorf("set helm values admin-console.isAirgap: %w", err) } - newVals, err = setHelmValue(newVals, "isHA", in.Spec.HighAvailability) + newVals, err = helm.SetValue(newVals, "isHA", in.Spec.HighAvailability) if err != nil { return nil, fmt.Errorf("set helm values admin-console.isHA: %w", err) } if in.Spec.Proxy != nil { extraEnv := getExtraEnvFromProxy(in.Spec.Proxy.HTTPProxy, in.Spec.Proxy.HTTPSProxy, in.Spec.Proxy.NoProxy) - newVals, err = setHelmValue(newVals, "extraEnv", extraEnv) + newVals, err = helm.SetValue(newVals, "extraEnv", extraEnv) if err != nil { return nil, fmt.Errorf("set helm values admin-console.extraEnv: %w", err) } } - charts[i].Values = newVals + if in.Spec.AdminConsole != nil && in.Spec.AdminConsole.Port > 0 { + newVals, err = helm.SetValue(newVals, "kurlProxy.nodePort", in.Spec.AdminConsole.Port) + if err != nil { + return nil, fmt.Errorf("set helm values admin-console.kurlProxy.nodePort: %w", err) + } + } + + charts[i].Values, err = helm.MarshalValues(newVals) + if err != nil { + return nil, fmt.Errorf("marshal admin-console.values: %w", err) + } } if chart.Name == "embedded-cluster-operator" { + newVals, err := helm.UnmarshalValues(chart.Values) + if err != nil { + return nil, fmt.Errorf("unmarshal admin-console.values: %w", err) + } + // embedded-cluster-operator has "embeddedBinaryName" and "embeddedClusterID" as dynamic values - newVals, err := setHelmValue(chart.Values, "embeddedBinaryName", in.Spec.BinaryName) + newVals, err = helm.SetValue(newVals, "embeddedBinaryName", in.Spec.BinaryName) if err != nil { return nil, fmt.Errorf("set helm values embedded-cluster-operator.embeddedBinaryName: %w", err) } - newVals, err = setHelmValue(newVals, "embeddedClusterID", in.Spec.ClusterID) + newVals, err = helm.SetValue(newVals, "embeddedClusterID", in.Spec.ClusterID) if err != nil { return nil, fmt.Errorf("set helm values embedded-cluster-operator.embeddedClusterID: %w", err) } if in.Spec.Proxy != nil { extraEnv := getExtraEnvFromProxy(in.Spec.Proxy.HTTPProxy, in.Spec.Proxy.HTTPSProxy, in.Spec.Proxy.NoProxy) - newVals, err = setHelmValue(newVals, "extraEnv", extraEnv) + newVals, err = helm.SetValue(newVals, "extraEnv", extraEnv) if err != nil { return nil, fmt.Errorf("set helm values embedded-cluster-operator.extraEnv: %w", err) } } - charts[i].Values = newVals + charts[i].Values, err = helm.MarshalValues(newVals) + if err != nil { + return nil, fmt.Errorf("marshal admin-console.values: %w", err) + } } if chart.Name == "docker-registry" { if !in.Spec.AirGap { continue } + newVals, err := helm.UnmarshalValues(chart.Values) + if err != nil { + return nil, fmt.Errorf("unmarshal admin-console.values: %w", err) + } + // handle the registry IP, which will always be present in airgap serviceCIDR := util.ClusterServiceCIDR(*clusterConfig, in) registryEndpoint, err := registry.GetRegistryServiceIP(serviceCIDR) @@ -189,31 +215,36 @@ func updateInfraChartsFromInstall(in *v1beta1.Installation, clusterConfig *k0sv1 return nil, fmt.Errorf("get registry service IP: %w", err) } - newVals, err := setHelmValue(chart.Values, "service.clusterIP", registryEndpoint) + newVals, err = helm.SetValue(newVals, "service.clusterIP", registryEndpoint) if err != nil { return nil, fmt.Errorf("set helm values docker-registry.service.clusterIP: %w", err) } - charts[i].Values = newVals - if !in.Spec.HighAvailability { - continue - } + if in.Spec.HighAvailability { + // handle the seaweedFS endpoint, which will only be present in HA airgap + seaweedfsS3Endpoint, err := registry.GetSeaweedfsS3Endpoint(serviceCIDR) + if err != nil { + return nil, fmt.Errorf("get seaweedfs s3 endpoint: %w", err) + } - // handle the seaweedFS endpoint, which will only be present in HA airgap - seaweedfsS3Endpoint, err := registry.GetSeaweedfsS3Endpoint(serviceCIDR) - if err != nil { - return nil, fmt.Errorf("get seaweedfs s3 endpoint: %w", err) + newVals, err = helm.SetValue(newVals, "s3.regionEndpoint", seaweedfsS3Endpoint) + if err != nil { + return nil, fmt.Errorf("set helm values docker-registry.s3.regionEndpoint: %w", err) + } } - newVals, err = setHelmValue(newVals, "s3.regionEndpoint", seaweedfsS3Endpoint) + charts[i].Values, err = helm.MarshalValues(newVals) if err != nil { - return nil, fmt.Errorf("set helm values docker-registry.s3.regionEndpoint: %w", err) + return nil, fmt.Errorf("marshal admin-console.values: %w", err) } - - charts[i].Values = newVals } if chart.Name == "velero" { if in.Spec.Proxy != nil { + newVals, err := helm.UnmarshalValues(chart.Values) + if err != nil { + return nil, fmt.Errorf("unmarshal admin-console.values: %w", err) + } + extraEnvVars := map[string]interface{}{ "extraEnvVars": map[string]string{ "HTTP_PROXY": in.Spec.Proxy.HTTPProxy, @@ -222,11 +253,15 @@ func updateInfraChartsFromInstall(in *v1beta1.Installation, clusterConfig *k0sv1 }, } - newVals, err := setHelmValue(chart.Values, "configuration", extraEnvVars) + newVals, err = helm.SetValue(newVals, "configuration", extraEnvVars) if err != nil { return nil, fmt.Errorf("set helm values velero.configuration: %w", err) } - charts[i].Values = newVals + + charts[i].Values, err = helm.MarshalValues(newVals) + if err != nil { + return nil, fmt.Errorf("marshal admin-console.values: %w", err) + } } } } @@ -265,29 +300,6 @@ func patchExtensionsForAirGap(config *v1beta1.Helm) *v1beta1.Helm { return config } -func setHelmValue(valuesYaml string, path string, newValue interface{}) (string, error) { - newValuesMap := dig.Mapping{} - if err := yaml.Unmarshal([]byte(valuesYaml), &newValuesMap); err != nil { - return "", fmt.Errorf("unmarshal initial values: %w", err) - } - - x, err := jp.ParseString(path) - if err != nil { - return "", fmt.Errorf("parse json path %q: %w", path, err) - } - - err = x.Set(newValuesMap, newValue) - if err != nil { - return "", fmt.Errorf("set json path %q to %q: %w", path, newValue, err) - } - - newValuesYaml, err := yaml.Marshal(newValuesMap) - if err != nil { - return "", fmt.Errorf("marshal updated values: %w", err) - } - return string(newValuesYaml), nil -} - func getExtraEnvFromProxy(httpProxy string, httpsProxy string, noProxy string) []map[string]interface{} { extraEnv := []map[string]interface{}{} extraEnv = append(extraEnv, map[string]interface{}{ diff --git a/pkg/addons/adminconsole/adminconsole.go b/pkg/addons/adminconsole/adminconsole.go index df8025c68..028efc179 100644 --- a/pkg/addons/adminconsole/adminconsole.go +++ b/pkg/addons/adminconsole/adminconsole.go @@ -23,6 +23,7 @@ import ( "github.com/replicatedhq/embedded-cluster/pkg/addons/registry" "github.com/replicatedhq/embedded-cluster/pkg/defaults" + "github.com/replicatedhq/embedded-cluster/pkg/helm" "github.com/replicatedhq/embedded-cluster/pkg/helpers" "github.com/replicatedhq/embedded-cluster/pkg/kotscli" "github.com/replicatedhq/embedded-cluster/pkg/kubeutils" @@ -34,8 +35,7 @@ import ( ) const ( - ReleaseName = "admin-console" - DefaultAdminConsoleNodePort = 30000 + ReleaseName = "admin-console" ) var ( @@ -89,6 +89,7 @@ type AdminConsole struct { airgapBundle string proxyEnv map[string]string privateCAs map[string]string + port int } // Version returns the embedded admin console version. @@ -132,7 +133,14 @@ func (a *AdminConsole) GenerateHelmConfig(k0sCfg *k0sv1beta1.ClusterConfig, only helmValues["extraEnv"] = extraEnv } } - values, err := yaml.Marshal(helmValues) + + var err error + helmValues, err = helm.SetValue(helmValues, "kurlProxy.nodePort", a.port) + if err != nil { + return nil, nil, fmt.Errorf("set helm values admin-console.kurlProxy.nodePort: %w", err) + } + + values, err := helm.MarshalValues(helmValues) if err != nil { return nil, nil, fmt.Errorf("unable to marshal helm values: %w", err) } @@ -212,14 +220,23 @@ func (a *AdminConsole) Outro(ctx context.Context, cli client.Client, k0sCfg *k0s } // New creates a new AdminConsole object. -func New(ns, password string, licenseFile string, airgapBundle string, proxyEnv map[string]string, privateCAs map[string]string) (*AdminConsole, error) { +func New( + namespace string, + password string, + licenseFile string, + airgapBundle string, + proxyEnv map[string]string, + privateCAs map[string]string, + port int, +) (*AdminConsole, error) { return &AdminConsole{ - namespace: ns, + namespace: namespace, password: password, licenseFile: licenseFile, airgapBundle: airgapBundle, proxyEnv: proxyEnv, privateCAs: privateCAs, + port: GetPort(port), }, nil } @@ -259,7 +276,7 @@ func WaitForReady(ctx context.Context, cli client.Client, ns string, writer *spi } // GetURL returns the URL to the admin console. -func GetURL(networkInterface string) string { +func GetURL(networkInterface string, port int) string { ipaddr := defaults.TryDiscoverPublicIP() if ipaddr == "" { var err error @@ -269,7 +286,15 @@ func GetURL(networkInterface string) string { ipaddr = "NODE-IP-ADDRESS" } } - return fmt.Sprintf("http://%s:%v", ipaddr, DefaultAdminConsoleNodePort) + return fmt.Sprintf("http://%s:%v", ipaddr, GetPort(port)) +} + +// GetURL returns the URL to the admin console. +func GetPort(port int) int { + if port <= 0 { + return defaults.AdminConsolePort + } + return port } func createRegistrySecret(ctx context.Context, cli client.Client, namespace string) error { diff --git a/pkg/addons/adminconsole/static/metadata.yaml b/pkg/addons/adminconsole/static/metadata.yaml index fe1482ac6..6ba05ab8e 100644 --- a/pkg/addons/adminconsole/static/metadata.yaml +++ b/pkg/addons/adminconsole/static/metadata.yaml @@ -5,24 +5,24 @@ # $ make buildtools # $ output/bin/buildtools update addon # -version: 1.117.1 +version: 1.117.2 location: oci://proxy.replicated.com/anonymous/registry.replicated.com/library/admin-console images: kotsadm: repo: proxy.replicated.com/anonymous/kotsadm/kotsadm tag: - amd64: v1.117.1-amd64@sha256:cf22f8f8ad9358035d8d5d5e4d9d3adeb4686b77483f5e319fc7246160f075f8 - arm64: v1.117.1-arm64@sha256:b4d473b08c25d17c22e0d2795950feae86e723c19eda44407bc0d6e33e377bd5 + amd64: v1.117.2-amd64@sha256:eb36a5a9af101164598f1e2f7f0745aad59bdb8d8870e09e2702d8c75c753380 + arm64: v1.117.2-arm64@sha256:a60cf01f935a785b1a5e693d65a18bd6f4e8d7aac7be30a3311c4c17f3b8222f kotsadm-migrations: repo: proxy.replicated.com/anonymous/kotsadm/kotsadm-migrations tag: - amd64: v1.117.1-amd64@sha256:10dd44ba52da9cd8dc89eac92ec6a6dc310c63acb9350d34359aff365c624ea6 - arm64: v1.117.1-arm64@sha256:fbde81bd9a08de901013fafb70f783ed6e17d3e741ff4aae92bb87ef6cf3a8d5 + amd64: v1.117.2-amd64@sha256:afb070977dfc16d3b85d2ae9e3cf68fc317e17e3ab05e6ad08d353201f1682ef + arm64: v1.117.2-arm64@sha256:cff8c43da1094d7969ef689eca3fd11c17aae936f45d1584fd00e25085a6112b kurl-proxy: repo: proxy.replicated.com/anonymous/kotsadm/kurl-proxy tag: - amd64: v1.117.1-amd64@sha256:d70f3ef0ee64984e5a9b08c7a0f997f1ed3a732854315a1c4f6709980fb65a2a - arm64: v1.117.1-arm64@sha256:866f343f2c5a71d0817339605d94754209ac002126e40e302b3714544b809c25 + amd64: v1.117.2-amd64@sha256:b2e4a7ee3ca4cedd1028fbac0678fcab091b1e0a81fd39d6b8d5d20f509b2304 + arm64: v1.117.2-arm64@sha256:056b640c03ff824553adb2fc95fb4930e4cdea18c89b656ed81c1937d8ab3c73 rqlite: repo: proxy.replicated.com/anonymous/kotsadm/rqlite tag: diff --git a/pkg/addons/applier.go b/pkg/addons/applier.go index 04e5671e1..244360d4f 100644 --- a/pkg/addons/applier.go +++ b/pkg/addons/applier.go @@ -41,15 +41,17 @@ type AddOn interface { // Applier is an entity that applies (installs and updates) addons in the cluster. type Applier struct { - prompt bool - verbose bool - adminConsolePwd string // admin console password - licenseFile string - onlyDefaults bool - endUserConfig *ecv1beta1.Config - airgapBundle string - proxyEnv map[string]string - privateCAs map[string]string + prompt bool + verbose bool + adminConsolePwd string // admin console password + licenseFile string + onlyDefaults bool + endUserConfig *ecv1beta1.Config + airgapBundle string + proxyEnv map[string]string + privateCAs map[string]string + adminConsolePort int + localArtifactMirrorPort int } // Outro runs the outro in all enabled add-ons. @@ -79,7 +81,7 @@ func (a *Applier) Outro(ctx context.Context, k0sCfg *k0sv1beta1.ClusterConfig, e if err := spinForInstallation(ctx, kcli); err != nil { return err } - if err := printKotsadmLinkMessage(a.licenseFile, networkInterface); err != nil { + if err := printKotsadmLinkMessage(a.licenseFile, networkInterface, a.GetAdminConsolePort()); err != nil { return fmt.Errorf("unable to print success message: %w", err) } return nil @@ -250,6 +252,20 @@ func (a *Applier) HostPreflightsForRestore() (*v1beta2.HostPreflightSpec, error) return a.hostPreflights(addons) } +func (a *Applier) GetAdminConsolePort() int { + if a.adminConsolePort <= 0 { + return defaults.AdminConsolePort + } + return a.adminConsolePort +} + +func (a *Applier) GetLocalArtifactMirrorPort() int { + if a.localArtifactMirrorPort <= 0 { + return defaults.LocalArtifactMirrorPort + } + return a.localArtifactMirrorPort +} + func (a *Applier) hostPreflights(addons []AddOn) (*v1beta2.HostPreflightSpec, error) { allpf := &v1beta2.HostPreflightSpec{} for _, addon := range addons { @@ -280,7 +296,15 @@ func (a *Applier) load() ([]AddOn, error) { } addons = append(addons, reg) - embedoperator, err := embeddedclusteroperator.New(a.endUserConfig, a.licenseFile, a.airgapBundle != "", a.proxyEnv, a.privateCAs) + embedoperator, err := embeddedclusteroperator.New( + a.endUserConfig, + a.licenseFile, + a.airgapBundle != "", + a.proxyEnv, + a.privateCAs, + a.GetAdminConsolePort(), + a.GetLocalArtifactMirrorPort(), + ) if err != nil { return nil, fmt.Errorf("unable to create embedded cluster operator addon: %w", err) } @@ -296,7 +320,15 @@ func (a *Applier) load() ([]AddOn, error) { } addons = append(addons, vel) - aconsole, err := adminconsole.New(defaults.KotsadmNamespace, a.adminConsolePwd, a.licenseFile, a.airgapBundle, a.proxyEnv, a.privateCAs) + aconsole, err := adminconsole.New( + defaults.KotsadmNamespace, + a.adminConsolePwd, + a.licenseFile, + a.airgapBundle, + a.proxyEnv, + a.privateCAs, + a.GetAdminConsolePort(), + ) if err != nil { return nil, fmt.Errorf("unable to create admin console addon: %w", err) } @@ -391,7 +423,7 @@ func spinForInstallation(ctx context.Context, cli client.Client) error { } // printKotsadmLinkMessage prints the success message when the admin console is online. -func printKotsadmLinkMessage(licenseFile string, networkInterface string) error { +func printKotsadmLinkMessage(licenseFile string, networkInterface string, adminConsolePort int) error { var err error license := &kotsv1beta1.License{} if licenseFile != "" { @@ -401,16 +433,18 @@ func printKotsadmLinkMessage(licenseFile string, networkInterface string) error } } + adminConsoleURL := adminconsole.GetURL(networkInterface, adminConsolePort) + successColor := "\033[32m" colorReset := "\033[0m" var successMessage string if license != nil { successMessage = fmt.Sprintf("Visit the Admin Console to configure and install %s: %s%s%s", - license.Spec.AppSlug, successColor, adminconsole.GetURL(networkInterface), colorReset, + license.Spec.AppSlug, successColor, adminConsoleURL, colorReset, ) } else { successMessage = fmt.Sprintf("Visit the Admin Console to configure and install your application: %s%s%s", - successColor, adminconsole.GetURL(networkInterface), colorReset, + successColor, adminConsoleURL, colorReset, ) } logrus.Info(successMessage) diff --git a/pkg/addons/embeddedclusteroperator/embeddedclusteroperator.go b/pkg/addons/embeddedclusteroperator/embeddedclusteroperator.go index 6cb442876..b7bd95f75 100644 --- a/pkg/addons/embeddedclusteroperator/embeddedclusteroperator.go +++ b/pkg/addons/embeddedclusteroperator/embeddedclusteroperator.go @@ -62,13 +62,15 @@ func init() { // EmbeddedClusterOperator manages the installation of the embedded cluster operator // helm chart. type EmbeddedClusterOperator struct { - namespace string - deployName string - endUserConfig *ecv1beta1.Config - licenseFile string - airgap bool - proxyEnv map[string]string - privateCAs map[string]string + namespace string + deployName string + endUserConfig *ecv1beta1.Config + licenseFile string + airgap bool + proxyEnv map[string]string + privateCAs map[string]string + adminConsolePort int + localArtifactMirrorPort int } // Version returns the version of the embedded cluster operator chart. @@ -259,11 +261,17 @@ func (e *EmbeddedClusterOperator) Outro(ctx context.Context, cli client.Client, }, }, Spec: ecv1beta1.InstallationSpec{ - ClusterID: metrics.ClusterID().String(), - MetricsBaseURL: metrics.BaseURL(license), - AirGap: e.airgap, - Proxy: proxySpec, - Network: k0sConfigToNetworkSpec(k0sCfg), + ClusterID: metrics.ClusterID().String(), + MetricsBaseURL: metrics.BaseURL(license), + AirGap: e.airgap, + Proxy: proxySpec, + Network: k0sConfigToNetworkSpec(k0sCfg), + AdminConsole: &ecv1beta1.AdminConsoleSpec{ + Port: e.adminConsolePort, + }, + LocalArtifactMirror: &ecv1beta1.LocalArtifactMirrorSpec{ + Port: e.localArtifactMirrorPort, + }, Config: cfgspec, EndUserK0sConfigOverrides: euOverrides, BinaryName: defaults.BinaryName(), @@ -279,15 +287,25 @@ func (e *EmbeddedClusterOperator) Outro(ctx context.Context, cli client.Client, } // New creates a new EmbeddedClusterOperator addon. -func New(endUserConfig *ecv1beta1.Config, licenseFile string, airgapEnabled bool, proxyEnv map[string]string, privateCAs map[string]string) (*EmbeddedClusterOperator, error) { +func New( + endUserConfig *ecv1beta1.Config, + licenseFile string, + airgapEnabled bool, + proxyEnv map[string]string, + privateCAs map[string]string, + adminConsolePort int, + localArtifactMirrorPort int, +) (*EmbeddedClusterOperator, error) { return &EmbeddedClusterOperator{ - namespace: "embedded-cluster", - deployName: "embedded-cluster-operator", - endUserConfig: endUserConfig, - licenseFile: licenseFile, - airgap: airgapEnabled, - proxyEnv: proxyEnv, - privateCAs: privateCAs, + namespace: "embedded-cluster", + deployName: "embedded-cluster-operator", + endUserConfig: endUserConfig, + licenseFile: licenseFile, + airgap: airgapEnabled, + proxyEnv: proxyEnv, + privateCAs: privateCAs, + adminConsolePort: adminConsolePort, + localArtifactMirrorPort: localArtifactMirrorPort, }, nil } diff --git a/pkg/addons/options.go b/pkg/addons/options.go index 2345f7d1a..6eeef9564 100644 --- a/pkg/addons/options.go +++ b/pkg/addons/options.go @@ -21,6 +21,20 @@ func WithPrivateCAs(privateCAs map[string]string) Option { } } +// WithAdminConsolePort sets the port on which the admin console will be served. +func WithAdminConsolePort(port int) Option { + return func(a *Applier) { + a.adminConsolePort = port + } +} + +// WithLocalArtifactMirrorPort sets the port on which the local artifact mirror will be served. +func WithLocalArtifactMirrorPort(port int) Option { + return func(a *Applier) { + a.localArtifactMirrorPort = port + } +} + // Quiet disables logging for addons. func Quiet() Option { return func(a *Applier) { diff --git a/pkg/defaults/defaults.go b/pkg/defaults/defaults.go index 1e7088d48..99a0347ef 100644 --- a/pkg/defaults/defaults.go +++ b/pkg/defaults/defaults.go @@ -8,11 +8,6 @@ var ( DefaultProvider = NewProvider("") ) -var ( - // provider holds a global reference to the default provider. - provider *Provider -) - // Holds the default no proxy values. var DefaultNoProxy = []string{"localhost", "127.0.0.1", ".cluster.local", ".svc"} @@ -23,6 +18,9 @@ const SeaweedFSNamespace = "seaweedfs" const RegistryNamespace = "registry" const VeleroNamespace = "velero" +const AdminConsolePort = 30000 +const LocalArtifactMirrorPort = 50000 + // BinaryName calls BinaryName on the default provider. func BinaryName() string { return DefaultProvider.BinaryName() diff --git a/pkg/helm/values.go b/pkg/helm/values.go new file mode 100644 index 000000000..4ca5f6bc9 --- /dev/null +++ b/pkg/helm/values.go @@ -0,0 +1,41 @@ +package helm + +import ( + "fmt" + + "github.com/k0sproject/dig" + "github.com/ohler55/ojg/jp" + "gopkg.in/yaml.v2" +) + +func UnmarshalValues(valuesYaml string) (map[string]interface{}, error) { + newValuesMap := map[string]interface{}{} + if err := yaml.Unmarshal([]byte(valuesYaml), &newValuesMap); err != nil { + return nil, fmt.Errorf("yaml unmarshal: %w", err) + } + return newValuesMap, nil +} + +func MarshalValues(values map[string]interface{}) (string, error) { + newValuesYaml, err := yaml.Marshal(values) + if err != nil { + return "", fmt.Errorf("yaml marshal: %w", err) + } + return string(newValuesYaml), nil +} + +func SetValue(values map[string]interface{}, path string, newValue interface{}) (map[string]interface{}, error) { + newValuesMap := dig.Mapping(values) + + x, err := jp.ParseString(path) + if err != nil { + return nil, fmt.Errorf("parse json path %q: %w", path, err) + } + + err = x.Set(newValuesMap, newValue) + if err != nil { + return nil, fmt.Errorf("set json path %q to %q: %w", path, newValue, err) + } + + return newValuesMap, nil +}