Skip to content

Commit f53f9c1

Browse files
authored
Get charts and k0s images from KOTS and deprecate airgap bundle flag for node joins (#2131)
* Get charts and k0s images from KOTS and deprecate airgap bundle flag for node joins
1 parent d82d025 commit f53f9c1

File tree

13 files changed

+289
-56
lines changed

13 files changed

+289
-56
lines changed

cmd/installer/cli/join.go

+34-28
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/AlecAivazis/survey/v2/terminal"
1212
k0sconfig "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1"
13+
"github.com/replicatedhq/embedded-cluster/cmd/installer/goods"
1314
ecv1beta1 "github.com/replicatedhq/embedded-cluster/kinds/apis/v1beta1"
1415
"github.com/replicatedhq/embedded-cluster/kinds/types/join"
1516
"github.com/replicatedhq/embedded-cluster/pkg/addons"
@@ -27,6 +28,7 @@ import (
2728
"github.com/replicatedhq/embedded-cluster/pkg/release"
2829
"github.com/replicatedhq/embedded-cluster/pkg/runtimeconfig"
2930
"github.com/replicatedhq/embedded-cluster/pkg/spinner"
31+
"github.com/replicatedhq/embedded-cluster/pkg/support"
3032
"github.com/replicatedhq/embedded-cluster/pkg/versions"
3133
"github.com/sirupsen/logrus"
3234
"github.com/spf13/cobra"
@@ -36,8 +38,6 @@ import (
3638
)
3739

3840
type JoinCmdFlags struct {
39-
airgapBundle string
40-
isAirgap bool
4141
noHA bool
4242
networkInterface string
4343
assumeYes bool
@@ -60,8 +60,6 @@ func JoinCmd(ctx context.Context, name string) *cobra.Command {
6060
return err
6161
}
6262

63-
flags.isAirgap = flags.airgapBundle != ""
64-
6563
return nil
6664
},
6765
PostRun: func(cmd *cobra.Command, args []string) {
@@ -84,7 +82,7 @@ func JoinCmd(ctx context.Context, name string) *cobra.Command {
8482
metricsReporter.ReportSignalAborted(ctx, sig)
8583
})
8684

87-
if err := runJoin(cmd.Context(), name, flags, jcmd, metricsReporter); err != nil {
85+
if err := runJoin(cmd.Context(), name, flags, jcmd, args[0], metricsReporter); err != nil {
8886
// Check if this is an interrupt error from the terminal
8987
if errors.Is(err, terminal.InterruptErr) {
9088
metricsReporter.ReportSignalAborted(ctx, syscall.SIGINT)
@@ -114,8 +112,6 @@ func preRunJoin(flags *JoinCmdFlags) error {
114112
return fmt.Errorf("join command must be run as root")
115113
}
116114

117-
flags.isAirgap = flags.airgapBundle != ""
118-
119115
// if a network interface flag was not provided, attempt to discover it
120116
if flags.networkInterface == "" {
121117
autoInterface, err := determineBestNetworkInterface()
@@ -128,7 +124,11 @@ func preRunJoin(flags *JoinCmdFlags) error {
128124
}
129125

130126
func addJoinFlags(cmd *cobra.Command, flags *JoinCmdFlags) error {
131-
cmd.Flags().StringVar(&flags.airgapBundle, "airgap-bundle", "", "Path to the air gap bundle. If set, the installation will complete without internet access.")
127+
cmd.Flags().String("airgap-bundle", "", "Path to the air gap bundle. If set, the installation will complete without internet access.")
128+
if err := cmd.Flags().MarkDeprecated("airgap-bundle", "This flag is deprecated (ignored) and will be removed in a future version. The cluster will automatically determine if it's in airgap mode and fetch the necessary artifacts from other nodes."); err != nil {
129+
return err
130+
}
131+
132132
cmd.Flags().StringVar(&flags.networkInterface, "network-interface", "", "The network interface to use for the cluster")
133133
cmd.Flags().BoolVar(&flags.ignoreHostPreflights, "ignore-host-preflights", false, "Run host preflight checks, but prompt the user to continue if they fail instead of exiting.")
134134
cmd.Flags().BoolVar(&flags.noHA, "no-ha", false, "Do not prompt for or enable high availability.")
@@ -147,7 +147,7 @@ func addJoinFlags(cmd *cobra.Command, flags *JoinCmdFlags) error {
147147
return nil
148148
}
149149

150-
func runJoin(ctx context.Context, name string, flags JoinCmdFlags, jcmd *join.JoinCommandResponse, metricsReporter preflights.MetricsReporter) error {
150+
func runJoin(ctx context.Context, name string, flags JoinCmdFlags, jcmd *join.JoinCommandResponse, kotsAPIAddress string, metricsReporter preflights.MetricsReporter) error {
151151
// both controller and worker nodes will have 'worker' in the join command
152152
isWorker := !strings.Contains(jcmd.K0sJoinCommand, "controller")
153153
if !isWorker {
@@ -158,7 +158,7 @@ func runJoin(ctx context.Context, name string, flags JoinCmdFlags, jcmd *join.Jo
158158
return err
159159
}
160160

161-
cidrCfg, err := initializeJoin(ctx, name, flags, jcmd)
161+
cidrCfg, err := initializeJoin(ctx, name, jcmd, kotsAPIAddress)
162162
if err != nil {
163163
return fmt.Errorf("unable to initialize join: %w", err)
164164
}
@@ -221,18 +221,6 @@ func runJoinVerifyAndPrompt(name string, flags JoinCmdFlags, jcmd *join.JoinComm
221221
return err
222222
}
223223

224-
err = verifyChannelRelease("join", flags.isAirgap, flags.assumeYes)
225-
if err != nil {
226-
return err
227-
}
228-
229-
if flags.isAirgap {
230-
logrus.Debugf("checking airgap bundle matches binary")
231-
if err := checkAirgapMatches(flags.airgapBundle); err != nil {
232-
return err // we want the user to see the error message without a prefix
233-
}
234-
}
235-
236224
runtimeconfig.Set(jcmd.InstallationSpec.RuntimeConfig)
237225
isWorker := !strings.Contains(jcmd.K0sJoinCommand, "controller")
238226
if isWorker {
@@ -282,7 +270,7 @@ func runJoinVerifyAndPrompt(name string, flags JoinCmdFlags, jcmd *join.JoinComm
282270
return nil
283271
}
284272

285-
func initializeJoin(ctx context.Context, name string, flags JoinCmdFlags, jcmd *join.JoinCommandResponse) (cidrCfg *CIDRConfig, err error) {
273+
func initializeJoin(ctx context.Context, name string, jcmd *join.JoinCommandResponse, kotsAPIAddress string) (cidrCfg *CIDRConfig, err error) {
286274
logrus.Info("")
287275
spinner := spinner.Start()
288276
spinner.Infof("Initializing")
@@ -305,8 +293,8 @@ func initializeJoin(ctx context.Context, name string, flags JoinCmdFlags, jcmd *
305293
}
306294

307295
logrus.Debugf("materializing %s binaries", name)
308-
if err := materializeFiles(flags.airgapBundle); err != nil {
309-
return nil, err
296+
if err := materializeFilesForJoin(ctx, jcmd, kotsAPIAddress); err != nil {
297+
return nil, fmt.Errorf("failed to materialize files: %w", err)
310298
}
311299

312300
logrus.Debugf("configuring sysctl")
@@ -337,6 +325,24 @@ func initializeJoin(ctx context.Context, name string, flags JoinCmdFlags, jcmd *
337325
return cidrCfg, nil
338326
}
339327

328+
func materializeFilesForJoin(ctx context.Context, jcmd *join.JoinCommandResponse, kotsAPIAddress string) error {
329+
materializer := goods.NewMaterializer()
330+
if err := materializer.Materialize(); err != nil {
331+
return fmt.Errorf("materialize binaries: %w", err)
332+
}
333+
if err := support.MaterializeSupportBundleSpec(); err != nil {
334+
return fmt.Errorf("materialize support bundle spec: %w", err)
335+
}
336+
337+
if jcmd.InstallationSpec.AirGap {
338+
if err := airgap.FetchAndWriteArtifacts(ctx, kotsAPIAddress); err != nil {
339+
return fmt.Errorf("failed to fetch artifacts: %w", err)
340+
}
341+
}
342+
343+
return nil
344+
}
345+
340346
func getJoinCIDRConfig(jcmd *join.JoinCommandResponse) (*CIDRConfig, error) {
341347
podCIDR, serviceCIDR, err := netutils.SplitNetworkCIDR(ecv1beta1.DefaultNetworkCIDR)
342348
if err != nil {
@@ -400,7 +406,7 @@ func installAndJoinCluster(ctx context.Context, jcmd *join.JoinCommandResponse,
400406
return fmt.Errorf("unable to join node to cluster: %w", err)
401407
}
402408

403-
if err := startAndWaitForK0s(ctx, name, jcmd); err != nil {
409+
if err := startAndWaitForK0s(name); err != nil {
404410
return err
405411
}
406412

@@ -464,7 +470,7 @@ func applyNetworkConfiguration(networkInterface string, jcmd *join.JoinCommandRe
464470
}
465471

466472
// startAndWaitForK0s starts the k0s service and waits for the node to be ready.
467-
func startAndWaitForK0s(ctx context.Context, name string, jcmd *join.JoinCommandResponse) error {
473+
func startAndWaitForK0s(name string) error {
468474
logrus.Debugf("starting %s service", name)
469475
if _, err := helpers.RunCommand(runtimeconfig.K0sBinaryPath(), "start"); err != nil {
470476
return fmt.Errorf("unable to start service: %w", err)
@@ -600,7 +606,7 @@ func maybeEnableHA(ctx context.Context, kcli client.Client, flags JoinCmdFlags,
600606
}
601607

602608
airgapChartsPath := ""
603-
if flags.isAirgap {
609+
if jcmd.InstallationSpec.AirGap {
604610
airgapChartsPath = runtimeconfig.EmbeddedClusterChartsSubDir()
605611
}
606612
hcli, err := helm.NewClient(helm.HelmOptions{

cmd/installer/cli/join_runpreflights.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func JoinRunPreflightsCmd(ctx context.Context, name string) *cobra.Command {
3939
if err != nil {
4040
return fmt.Errorf("unable to get join token: %w", err)
4141
}
42-
if err := runJoinRunPreflights(cmd.Context(), name, flags, jcmd); err != nil {
42+
if err := runJoinRunPreflights(cmd.Context(), name, flags, jcmd, args[0]); err != nil {
4343
return err
4444
}
4545

@@ -54,14 +54,14 @@ func JoinRunPreflightsCmd(ctx context.Context, name string) *cobra.Command {
5454
return cmd
5555
}
5656

57-
func runJoinRunPreflights(ctx context.Context, name string, flags JoinCmdFlags, jcmd *join.JoinCommandResponse) error {
57+
func runJoinRunPreflights(ctx context.Context, name string, flags JoinCmdFlags, jcmd *join.JoinCommandResponse, kotsAPIAddress string) error {
5858
if err := runJoinVerifyAndPrompt(name, flags, jcmd); err != nil {
5959
return err
6060
}
6161

6262
logrus.Debugf("materializing %s binaries", name)
63-
if err := materializeFiles(flags.airgapBundle); err != nil {
64-
return err
63+
if err := materializeFilesForJoin(ctx, jcmd, kotsAPIAddress); err != nil {
64+
return fmt.Errorf("failed to materialize files: %w", err)
6565
}
6666

6767
logrus.Debugf("configuring sysctl")
@@ -107,7 +107,7 @@ func runJoinPreflights(ctx context.Context, jcmd *join.JoinCommandResponse, flag
107107
PodCIDR: cidrCfg.PodCIDR,
108108
ServiceCIDR: cidrCfg.ServiceCIDR,
109109
NodeIP: nodeIP,
110-
IsAirgap: flags.isAirgap,
110+
IsAirgap: jcmd.InstallationSpec.AirGap,
111111
SkipHostPreflights: flags.skipHostPreflights,
112112
IgnoreHostPreflights: flags.ignoreHostPreflights,
113113
AssumeYes: flags.assumeYes,

dev/dockerfiles/local-artifact-mirror/Dockerfile.ttlsh

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.24 AS build
1+
FROM golang:1.24.2 AS build
22

33
WORKDIR /app
44

dev/dockerfiles/operator/Dockerfile.local

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.24-alpine AS build
1+
FROM golang:1.24.2-alpine AS build
22

33
RUN apk add --no-cache ca-certificates curl git make bash
44

dev/dockerfiles/operator/Dockerfile.ttlsh

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.24 AS build
1+
FROM golang:1.24.2 AS build
22

33
WORKDIR /app
44

pkg/addons/adminconsole/static/metadata.yaml

+7-7
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,24 @@
55
# $ make buildtools
66
# $ output/bin/buildtools update addon <addon name>
77
#
8-
version: 1.124.15-ec.2
8+
version: 1.124.16-ec.0
99
location: oci://proxy.replicated.com/anonymous/registry.replicated.com/library/admin-console
1010
images:
1111
kotsadm:
1212
repo: proxy.replicated.com/anonymous/kotsadm/kotsadm
1313
tag:
14-
amd64: v1.124.15-ec.2-amd64@sha256:55647c859506b1462cac40078d2279bb5ea284a39d7b8ab5c8caceea8cf57382
15-
arm64: v1.124.15-ec.2-arm64@sha256:bc4608169f30250d2b997a28fbd59711cdf975766332110776c52b4d87563db5
14+
amd64: v1.124.16-ec.0-amd64@sha256:30bf71416ac4e28f343fc31b72e19fead80e3c833135b6175f6bf85f8a72916a
15+
arm64: v1.124.16-ec.0-arm64@sha256:8b99803ed1ae94889f93646b61e991988293e5dbc253f76d7adf3185403c51af
1616
kotsadm-migrations:
1717
repo: proxy.replicated.com/anonymous/kotsadm/kotsadm-migrations
1818
tag:
19-
amd64: v1.124.15-ec.2-amd64@sha256:8563ebe149f917e6030737e5e8b58b8a7796f47638ff87546d1d33c26bb27c80
20-
arm64: v1.124.15-ec.2-arm64@sha256:4d72708542edfc5956aa815fe106e7172aee98550cc51e90dad8eb34300b55f5
19+
amd64: v1.124.16-ec.0-amd64@sha256:a9f0a7313e43579f33a2cf776d4a6096e4f942545cda4b0fe2a4fd48de81ffcc
20+
arm64: v1.124.16-ec.0-arm64@sha256:0cdab3221eeb9aedf7a70e915b2adfad72d484b4dd147401ee563fc8959cd530
2121
kurl-proxy:
2222
repo: proxy.replicated.com/anonymous/kotsadm/kurl-proxy
2323
tag:
24-
amd64: v1.124.15-ec.2-amd64@sha256:0dc979da23442b7e4ae53fa46fbbbb1835c4f186b2e9d4d1f11000431a6dd6ad
25-
arm64: v1.124.15-ec.2-arm64@sha256:4d369bee17c270991f5daee2b01762be7caee1dd2882b8a320902e910cbd05cc
24+
amd64: v1.124.16-ec.0-amd64@sha256:67555f18b467d96c886aee8b868acb3ba719ad0ff9562d3fb87aaa093338adcd
25+
arm64: v1.124.16-ec.0-arm64@sha256:677d752ce90e1831f58868c618322720affe21443c5c638d2d4b416860828402
2626
rqlite:
2727
repo: proxy.replicated.com/anonymous/kotsadm/rqlite
2828
tag:

pkg/airgap/materialize.go

+30-1
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ package airgap
33
import (
44
"archive/tar"
55
"compress/gzip"
6+
"context"
67
"fmt"
78
"io"
89
"os"
910
"path/filepath"
1011

12+
"github.com/replicatedhq/embedded-cluster/pkg/kotsadm"
1113
"github.com/replicatedhq/embedded-cluster/pkg/runtimeconfig"
1214
)
1315

14-
const K0sImagePath = "images/images-amd64.tar"
16+
const K0sImagePath = "images/ec-images-amd64.tar"
1517

1618
// MaterializeAirgap places the airgap image bundle for k0s and the embedded cluster charts on disk.
1719
// - image bundle should be located at 'images-amd64.tar' within the embedded-cluster directory within the airgap bundle.
@@ -58,6 +60,33 @@ func MaterializeAirgap(airgapReader io.Reader) error {
5860
}
5961
}
6062

63+
// FetchAndWriteArtifacts fetches the k0s images and Helm charts from the KOTS API
64+
// and writes them to the appropriate directories
65+
func FetchAndWriteArtifacts(ctx context.Context, kotsAPIAddress string) error {
66+
// Fetch and write k0s images
67+
imagesFile, err := kotsadm.GetK0sImagesFile(ctx, kotsAPIAddress)
68+
if err != nil {
69+
return fmt.Errorf("failed to get k0s images file: %w", err)
70+
}
71+
defer imagesFile.Close()
72+
73+
if err := writeOneFile(imagesFile, filepath.Join(runtimeconfig.EmbeddedClusterK0sSubDir(), K0sImagePath), 0644); err != nil {
74+
return fmt.Errorf("failed to write k0s images file: %w", err)
75+
}
76+
77+
// Fetch and write Helm charts
78+
chartsTGZ, err := kotsadm.GetECCharts(ctx, kotsAPIAddress)
79+
if err != nil {
80+
return fmt.Errorf("failed to get ec charts: %w", err)
81+
}
82+
defer chartsTGZ.Close()
83+
84+
if err := writeChartFiles(chartsTGZ); err != nil {
85+
return fmt.Errorf("failed to write chart files: %w", err)
86+
}
87+
return nil
88+
}
89+
6190
func writeOneFile(reader io.Reader, path string, mode int64) error {
6291
// setup destination
6392
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {

pkg/dryrun/kotsadm.go

+43-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dryrun
33
import (
44
"context"
55
"fmt"
6+
"io"
67
"reflect"
78
"strings"
89

@@ -45,21 +46,57 @@ func (c *Kotsadm) setResponse(resp interface{}, err error, methodName string, ar
4546
return nil
4647
}
4748

48-
// SetGetJoinTokenResponse sets the response for the GetJoinToken method, based on the provided address and shortToken.
49-
func (c *Kotsadm) SetGetJoinTokenResponse(address, shortToken string, resp *join.JoinCommandResponse, err error) {
50-
mockErr := c.setResponse(resp, err, "GetJoinToken", address, shortToken)
49+
// SetGetJoinTokenResponse sets the response for the GetJoinToken method, based on the provided kotsAPIAddress and shortToken.
50+
func (c *Kotsadm) SetGetJoinTokenResponse(kotsAPIAddress, shortToken string, resp *join.JoinCommandResponse, err error) {
51+
mockErr := c.setResponse(resp, err, "GetJoinToken", kotsAPIAddress, shortToken)
5152
if mockErr != nil {
5253
panic(mockErr)
5354
}
5455
}
5556

5657
// GetJoinToken issues a request to the kots api to get the actual join command
5758
// based on the short token provided by the user.
58-
func (c *Kotsadm) GetJoinToken(ctx context.Context, address, shortToken string) (*join.JoinCommandResponse, error) {
59-
key := strings.Join([]string{"GetJoinToken", address, shortToken}, ":")
59+
func (c *Kotsadm) GetJoinToken(ctx context.Context, kotsAPIAddress, shortToken string) (*join.JoinCommandResponse, error) {
60+
key := strings.Join([]string{"GetJoinToken", kotsAPIAddress, shortToken}, ":")
6061
if handler, ok := c.mockHandlers[key]; ok {
6162
return handler.resp.(*join.JoinCommandResponse), handler.err
6263
} else {
63-
return nil, fmt.Errorf("no response set for GetJoinToken, address: %s, shortToken: %s", address, shortToken)
64+
return nil, fmt.Errorf("no response set for GetJoinToken, kotsAPIAddress: %s, shortToken: %s", kotsAPIAddress, shortToken)
65+
}
66+
}
67+
68+
func (c *Kotsadm) SetGetK0sImagesFileResponse(kotsAPIAddress string, resp io.ReadCloser, err error) {
69+
mockErr := c.setResponse(resp, err, "GetK0sImagesFile", kotsAPIAddress)
70+
if mockErr != nil {
71+
panic(mockErr)
72+
}
73+
}
74+
75+
// GetK0sImagesFile fetches the k0s images file from the KOTS API.
76+
// caller is responsible for closing the response body.
77+
func (c *Kotsadm) GetK0sImagesFile(ctx context.Context, kotsAPIAddress string) (io.ReadCloser, error) {
78+
key := strings.Join([]string{"GetK0sImagesFile", kotsAPIAddress}, ":")
79+
if handler, ok := c.mockHandlers[key]; ok {
80+
return handler.resp.(io.ReadCloser), handler.err
81+
} else {
82+
return nil, fmt.Errorf("no response set for GetK0sImagesFile, kotsAPIAddress: %s", kotsAPIAddress)
83+
}
84+
}
85+
86+
func (c *Kotsadm) SetGetECChartsResponse(kotsAPIAddress string, resp io.ReadCloser, err error) {
87+
mockErr := c.setResponse(resp, err, "GetECCharts", kotsAPIAddress)
88+
if mockErr != nil {
89+
panic(mockErr)
90+
}
91+
}
92+
93+
// GetECCharts fetches the Helm charts file from the KOTS API.
94+
// caller is responsible for closing the response body.
95+
func (c *Kotsadm) GetECCharts(ctx context.Context, kotsAPIAddress string) (io.ReadCloser, error) {
96+
key := strings.Join([]string{"GetECCharts", kotsAPIAddress}, ":")
97+
if handler, ok := c.mockHandlers[key]; ok {
98+
return handler.resp.(io.ReadCloser), handler.err
99+
} else {
100+
return nil, fmt.Errorf("no response set for GetECCharts, kotsAPIAddress: %s", kotsAPIAddress)
64101
}
65102
}

0 commit comments

Comments
 (0)