@@ -14,10 +14,12 @@ import (
1414 k8syaml "sigs.k8s.io/yaml"
1515
1616 "github.com/replicatedhq/embedded-cluster/pkg/addons"
17+ "github.com/replicatedhq/embedded-cluster/pkg/airgap"
1718 "github.com/replicatedhq/embedded-cluster/pkg/config"
1819 "github.com/replicatedhq/embedded-cluster/pkg/defaults"
1920 "github.com/replicatedhq/embedded-cluster/pkg/goods"
2021 "github.com/replicatedhq/embedded-cluster/pkg/helpers"
22+ "github.com/replicatedhq/embedded-cluster/pkg/kubeutils"
2123 "github.com/replicatedhq/embedded-cluster/pkg/metrics"
2224 "github.com/replicatedhq/embedded-cluster/pkg/preflights"
2325 "github.com/replicatedhq/embedded-cluster/pkg/prompts"
@@ -52,7 +54,7 @@ func runCommand(bin string, args ...string) (string, error) {
5254}
5355
5456// installAndEnableLocalArtifactMirror installs and enables the local artifact mirror. This
55- // service is reponsible for serving on localhost, through http, all files that are used
57+ // service is responsible for serving on localhost, through http, all files that are used
5658// during a cluster upgrade.
5759func installAndEnableLocalArtifactMirror () error {
5860 ourbin := defaults .PathToEmbeddedClusterBinary ("local-artifact-mirror" )
@@ -179,7 +181,73 @@ func checkLicenseMatches(c *cli.Context) error {
179181 }
180182
181183 return nil
184+ }
185+
186+ func checkAirgapMatches (c * cli.Context ) error {
187+ rel , err := release .GetChannelRelease ()
188+ if err != nil {
189+ return fmt .Errorf ("failed to get release from binary: %w" , err ) // this should only be if the release is malformed
190+ }
191+ if rel == nil {
192+ return fmt .Errorf ("airgap bundle provided but no release was found in binary, please rerun without the airgap-bundle flag" )
193+ }
194+
195+ // read file from path
196+ rawfile , err := os .Open (c .String ("airgap-bundle" ))
197+ if err != nil {
198+ return fmt .Errorf ("failed to open airgap file: %w" , err )
199+ }
200+ defer rawfile .Close ()
201+
202+ appSlug , channelID , airgapVersion , err := airgap .ChannelReleaseMetadata (rawfile )
203+ if err != nil {
204+ return fmt .Errorf ("failed to get airgap bundle versions: %w" , err )
205+ }
206+
207+ // Check if the airgap bundle matches the application version data
208+ if rel .AppSlug != appSlug {
209+ // if the app is different, we will not be able to provide the correct vendor supplied charts and k0s overrides
210+ return fmt .Errorf ("airgap bundle app %s does not match binary app %s, please provide the correct bundle" , appSlug , rel .AppSlug )
211+ }
212+ if rel .ChannelID != channelID {
213+ // if the channel is different, we will not be able to install the pinned vendor application version within kots
214+ return fmt .Errorf ("airgap bundle channel %s does not match binary channel %s, please provide the correct bundle" , channelID , rel .ChannelID )
215+ }
216+ if rel .VersionLabel != airgapVersion {
217+ // if the version is different, who knows what might be different
218+ return fmt .Errorf ("airgap bundle version %s does not match binary version %s, please provide the correct bundle" , airgapVersion , rel .VersionLabel )
219+ }
220+
221+ return nil
222+ }
223+
224+ func materializeFiles (c * cli.Context ) error {
225+ mat := spinner .Start ()
226+ defer mat .Close ()
227+ mat .Infof ("Materializing files" )
228+
229+ if err := goods .Materialize (); err != nil {
230+ return fmt .Errorf ("unable to materialize binaries: %w" , err )
231+ }
232+ if c .String ("airgap-bundle" ) != "" {
233+ mat .Infof ("Materializing airgap installation files" )
234+
235+ // read file from path
236+ rawfile , err := os .Open (c .String ("airgap-bundle" ))
237+ if err != nil {
238+ return fmt .Errorf ("failed to open airgap file: %w" , err )
239+ }
240+ defer rawfile .Close ()
182241
242+ if err := airgap .MaterializeAirgap (rawfile ); err != nil {
243+ err = fmt .Errorf ("unable to materialize airgap files: %w" , err )
244+ return err
245+ }
246+ }
247+
248+ mat .Infof ("Host files materialized" )
249+
250+ return nil
183251}
184252
185253// createK0sConfig creates a new k0s.yaml configuration file. The file is saved in the
@@ -205,13 +273,21 @@ func ensureK0sConfig(c *cli.Context) error {
205273 }
206274 opts = append (opts , addons .WithLicense (license ))
207275 }
276+ if c .String ("airgap-bundle" ) != "" {
277+ opts = append (opts , addons .Airgap ())
278+ }
208279 if err := config .UpdateHelmConfigs (cfg , opts ... ); err != nil {
209280 return fmt .Errorf ("unable to update helm configs: %w" , err )
210281 }
211282 var err error
212283 if cfg , err = applyUnsupportedOverrides (c , cfg ); err != nil {
213284 return fmt .Errorf ("unable to apply unsupported overrides: %w" , err )
214285 }
286+ if c .String ("airgap-bundle" ) != "" {
287+ // update the k0s config to install with airgap
288+ airgap .RemapHelm (cfg )
289+ airgap .SetAirgapConfig (cfg )
290+ }
215291 data , err := k8syaml .Marshal (cfg )
216292 if err != nil {
217293 return fmt .Errorf ("unable to marshal config: %w" , err )
@@ -296,6 +372,32 @@ func waitForK0s() error {
296372 return nil
297373}
298374
375+ // createAirgapConfigMaps creates the airgap configmaps in the k8s cluster from the airgap file.
376+ func createAirgapConfigMaps (c * cli.Context ) error {
377+ loading := spinner .Start ()
378+ defer loading .Close ()
379+ loading .Infof ("Creating airgap configmaps" )
380+ // create k8s client
381+ os .Setenv ("KUBECONFIG" , defaults .PathToKubeConfig ())
382+ cli , err := kubeutils .KubeClient ()
383+ if err != nil {
384+ return fmt .Errorf ("failed to create k8s client: %w" , err )
385+ }
386+
387+ // read file from path
388+ rawfile , err := os .Open (c .String ("airgap-bundle" ))
389+ if err != nil {
390+ return fmt .Errorf ("failed to open airgap file: %w" , err )
391+ }
392+ defer rawfile .Close ()
393+
394+ if err = airgap .CreateAppConfigMaps (c .Context , cli , rawfile ); err != nil {
395+ return fmt .Errorf ("unable to create airgap configmaps: %w" , err )
396+ }
397+ loading .Infof ("Airgap configmaps created" )
398+ return nil
399+ }
400+
299401// runOutro calls Outro() in all enabled addons by means of Applier.
300402func runOutro (c * cli.Context ) error {
301403 os .Setenv ("KUBECONFIG" , defaults .PathToKubeConfig ())
@@ -314,6 +416,9 @@ func runOutro(c *cli.Context) error {
314416 }
315417 opts = append (opts , addons .WithEndUserConfig (eucfg ))
316418 }
419+ if c .String ("airgap-bundle" ) != "" {
420+ opts = append (opts , addons .Airgap ())
421+ }
317422 return addons .NewApplier (opts ... ).Outro (c .Context )
318423}
319424
@@ -327,6 +432,10 @@ var installCommand = &cli.Command{
327432 if os .Getuid () != 0 {
328433 return fmt .Errorf ("install command must be run as root" )
329434 }
435+
436+ if c .String ("airgap-bundle" ) != "" {
437+ metrics .DisableMetrics ()
438+ }
330439 return nil
331440 },
332441 Flags : []cli.Flag {
@@ -346,6 +455,11 @@ var installCommand = &cli.Command{
346455 Usage : "Path to the application license file" ,
347456 Hidden : false ,
348457 },
458+ & cli.StringFlag {
459+ Name : "airgap-bundle" ,
460+ Usage : "Path to the airgap bundle. If set, the installation will be completed without internet access." ,
461+ Hidden : false ,
462+ },
349463 },
350464 Action : func (c * cli.Context ) error {
351465 logrus .Debugf ("checking if %s is already installed" , binName )
@@ -365,9 +479,14 @@ var installCommand = &cli.Command{
365479 metrics .ReportApplyFinished (c , metricErr )
366480 return err // do not return the metricErr, as we want the user to see the error message without a prefix
367481 }
482+ if c .String ("airgap-bundle" ) != "" {
483+ logrus .Debugf ("checking airgap bundle matches binary" )
484+ if err := checkAirgapMatches (c ); err != nil {
485+ return err // we want the user to see the error message without a prefix
486+ }
487+ }
368488 logrus .Debugf ("materializing binaries" )
369- if err := goods .Materialize (); err != nil {
370- err := fmt .Errorf ("unable to materialize binaries: %w" , err )
489+ if err := materializeFiles (c ); err != nil {
371490 metrics .ReportApplyFinished (c , err )
372491 return err
373492 }
@@ -401,6 +520,13 @@ var installCommand = &cli.Command{
401520 metrics .ReportApplyFinished (c , err )
402521 return err
403522 }
523+ if c .String ("airgap-bundle" ) != "" {
524+ err := createAirgapConfigMaps (c )
525+ if err != nil {
526+ err = fmt .Errorf ("unable to create airgap configmaps: %w" , err )
527+ return err
528+ }
529+ }
404530 logrus .Debugf ("running outro" )
405531 if err := runOutro (c ); err != nil {
406532 metrics .ReportApplyFinished (c , err )
0 commit comments