-
Notifications
You must be signed in to change notification settings - Fork 5
feat: preflight storage needed to perform airgap install #2336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 7 commits
0b06490
e270d49
d54331b
2143149
668b930
473f5e1
46ca2d3
b88f435
5e53eca
9911048
3fff57f
a1e8816
a3d3847
367c093
69038ae
a1e0ee4
ee7964f
d93f5cf
8691b79
fb4f1b5
05f7910
fc96cf4
30e78de
f72e05c
5206aab
5235538
00f00dc
9f3562b
a8f86ba
b263856
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -100,6 +100,16 @@ | |
} | ||
|
||
func (m *infraManager) install(ctx context.Context, license *kotsv1beta1.License, rc runtimeconfig.RuntimeConfig) (finalErr error) { | ||
// extract airgap info if airgap bundle is provided | ||
var airgapInfo *kotsv1beta1.Airgap | ||
Check failure on line 104 in api/internal/managers/infra/install.go
|
||
if m.airgapBundle != "" { | ||
var err error | ||
airgapInfo, err = airgap.AirgapInfoFromPath(m.airgapBundle) | ||
if err != nil { | ||
return fmt.Errorf("failed to get airgap info: %w", err) | ||
} | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
defer func() { | ||
if r := recover(); r != nil { | ||
finalErr = fmt.Errorf("panic: %v: %s", r, string(debug.Stack())) | ||
|
@@ -136,7 +146,7 @@ | |
} | ||
defer hcli.Close() | ||
|
||
in, err := m.recordInstallation(ctx, kcli, license, rc) | ||
in, err := m.recordInstallation(ctx, kcli, license, rc, airgapInfo) | ||
if err != nil { | ||
return fmt.Errorf("record installation: %w", err) | ||
} | ||
|
@@ -231,21 +241,28 @@ | |
return k0sCfg, nil | ||
} | ||
|
||
func (m *infraManager) recordInstallation(ctx context.Context, kcli client.Client, license *kotsv1beta1.License, rc runtimeconfig.RuntimeConfig) (*ecv1beta1.Installation, error) { | ||
func (m *infraManager) recordInstallation(ctx context.Context, kcli client.Client, license *kotsv1beta1.License, rc runtimeconfig.RuntimeConfig, airgapInfo *kotsv1beta1.Airgap) (*ecv1beta1.Installation, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this will no longer need to be an argument as |
||
logFn := m.logFn("metadata") | ||
|
||
// get the configured custom domains | ||
ecDomains := utils.GetDomains(m.releaseData) | ||
|
||
// extract airgap uncompressed size if airgap info is provided | ||
var airgapUncompressedSize int64 | ||
if airgapInfo != nil { | ||
airgapUncompressedSize = airgapInfo.Spec.UncompressedSize | ||
} | ||
|
||
// record the installation | ||
logFn("recording installation") | ||
in, err := kubeutils.RecordInstallation(ctx, kcli, kubeutils.RecordInstallationOptions{ | ||
IsAirgap: m.airgapBundle != "", | ||
License: license, | ||
ConfigSpec: m.getECConfigSpec(), | ||
MetricsBaseURL: netutils.MaybeAddHTTPS(ecDomains.ReplicatedAppDomain), | ||
RuntimeConfig: rc.Get(), | ||
EndUserConfig: m.endUserConfig, | ||
IsAirgap: m.airgapBundle != "", | ||
License: license, | ||
ConfigSpec: m.getECConfigSpec(), | ||
MetricsBaseURL: netutils.MaybeAddHTTPS(ecDomains.ReplicatedAppDomain), | ||
RuntimeConfig: rc.Get(), | ||
EndUserConfig: m.endUserConfig, | ||
AirgapUncompressedSize: airgapUncompressedSize, | ||
}) | ||
if err != nil { | ||
return nil, fmt.Errorf("record installation: %w", err) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -95,7 +95,16 @@ func InstallCmd(ctx context.Context, name string) *cobra.Command { | |
cancel() // Cancel context when command completes | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
if err := verifyAndPrompt(ctx, name, flags, prompts.New()); err != nil { | ||
var airgapInfo *kotsv1beta1.Airgap | ||
if flags.airgapBundle != "" { | ||
var err error | ||
airgapInfo, err = airgap.AirgapInfoFromPath(flags.airgapBundle) | ||
if err != nil { | ||
return fmt.Errorf("failed to get airgap info: %w", err) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any reason this is not in the preRunInstall function as suggested? That way it exists with the rest of the flag parsing code and will work for both install and install run-preflights command. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll create a new flag like the rest |
||
|
||
if err := verifyAndPrompt(ctx, name, flags, prompts.New(), airgapInfo); err != nil { | ||
return err | ||
} | ||
if err := preRunInstall(cmd, &flags, rc); err != nil { | ||
|
@@ -120,7 +129,7 @@ func InstallCmd(ctx context.Context, name string) *cobra.Command { | |
installReporter.ReportSignalAborted(ctx, sig) | ||
}) | ||
|
||
if err := runInstall(cmd.Context(), flags, rc, installReporter); err != nil { | ||
if err := runInstall(cmd.Context(), flags, rc, installReporter, airgapInfo); err != nil { | ||
// Check if this is an interrupt error from the terminal | ||
if errors.Is(err, terminal.InterruptErr) { | ||
installReporter.ReportSignalAborted(ctx, syscall.SIGINT) | ||
|
@@ -435,7 +444,7 @@ func runManagerExperienceInstall(ctx context.Context, flags InstallCmdFlags, rc | |
return nil | ||
} | ||
|
||
func runInstall(ctx context.Context, flags InstallCmdFlags, rc runtimeconfig.RuntimeConfig, installReporter *InstallReporter) (finalErr error) { | ||
func runInstall(ctx context.Context, flags InstallCmdFlags, rc runtimeconfig.RuntimeConfig, installReporter *InstallReporter, airgapInfo *kotsv1beta1.Airgap) (finalErr error) { | ||
if flags.enableManagerExperience { | ||
return nil | ||
} | ||
|
@@ -470,7 +479,7 @@ func runInstall(ctx context.Context, flags InstallCmdFlags, rc runtimeconfig.Run | |
errCh := kubeutils.WaitForKubernetes(ctx, kcli) | ||
defer logKubernetesErrors(errCh) | ||
|
||
in, err := recordInstallation(ctx, kcli, flags, rc, flags.license) | ||
in, err := recordInstallation(ctx, kcli, flags, rc, flags.license, airgapInfo) | ||
if err != nil { | ||
return fmt.Errorf("unable to record installation: %w", err) | ||
} | ||
|
@@ -571,7 +580,7 @@ func getAddonInstallOpts(flags InstallCmdFlags, rc runtimeconfig.RuntimeConfig, | |
return opts, nil | ||
} | ||
|
||
func verifyAndPrompt(ctx context.Context, name string, flags InstallCmdFlags, prompt prompts.Prompt) error { | ||
func verifyAndPrompt(ctx context.Context, name string, flags InstallCmdFlags, prompt prompts.Prompt, airgapInfo *kotsv1beta1.Airgap) error { | ||
logrus.Debugf("checking if k0s is already installed") | ||
err := verifyNoInstallation(name, "reinstall") | ||
if err != nil { | ||
|
@@ -588,9 +597,9 @@ func verifyAndPrompt(ctx context.Context, name string, flags InstallCmdFlags, pr | |
if err != nil { | ||
return err | ||
} | ||
if flags.isAirgap { | ||
if airgapInfo != nil { | ||
logrus.Debugf("checking airgap bundle matches binary") | ||
if err := checkAirgapMatches(flags.airgapBundle); err != nil { | ||
if err := checkAirgapMatches(airgapInfo); err != nil { | ||
return err // we want the user to see the error message without a prefix | ||
} | ||
} | ||
|
@@ -885,23 +894,15 @@ func installExtensions(ctx context.Context, hcli helm.Client) error { | |
return nil | ||
} | ||
|
||
func checkAirgapMatches(airgapBundle string) error { | ||
func checkAirgapMatches(airgapInfo *kotsv1beta1.Airgap) error { | ||
rel := release.GetChannelRelease() | ||
if rel == nil { | ||
return fmt.Errorf("airgap bundle provided but no release was found in binary, please rerun without the airgap-bundle flag") | ||
} | ||
|
||
// read file from path | ||
rawfile, err := os.Open(airgapBundle) | ||
if err != nil { | ||
return fmt.Errorf("failed to open airgap file: %w", err) | ||
} | ||
defer rawfile.Close() | ||
|
||
appSlug, channelID, airgapVersion, err := airgap.ChannelReleaseMetadata(rawfile) | ||
if err != nil { | ||
return fmt.Errorf("failed to get airgap bundle versions: %w", err) | ||
} | ||
appSlug := airgapInfo.Spec.AppSlug | ||
channelID := airgapInfo.Spec.ChannelID | ||
airgapVersion := airgapInfo.Spec.VersionLabel | ||
|
||
// Check if the airgap bundle matches the application version data | ||
if rel.AppSlug != appSlug { | ||
|
@@ -1047,7 +1048,7 @@ func waitForNode(ctx context.Context) error { | |
} | ||
|
||
func recordInstallation( | ||
ctx context.Context, kcli client.Client, flags InstallCmdFlags, rc runtimeconfig.RuntimeConfig, license *kotsv1beta1.License, | ||
ctx context.Context, kcli client.Client, flags InstallCmdFlags, rc runtimeconfig.RuntimeConfig, license *kotsv1beta1.License, airgapInfo *kotsv1beta1.Airgap, | ||
) (*ecv1beta1.Installation, error) { | ||
// get the embedded cluster config | ||
cfg := release.GetEmbeddedClusterConfig() | ||
|
@@ -1062,14 +1063,21 @@ func recordInstallation( | |
return nil, fmt.Errorf("process overrides file: %w", err) | ||
} | ||
|
||
// extract airgap uncompressed size if airgap info is provided | ||
var airgapUncompressedSize int64 | ||
if airgapInfo != nil { | ||
airgapUncompressedSize = airgapInfo.Spec.UncompressedSize | ||
} | ||
|
||
// record the installation | ||
installation, err := kubeutils.RecordInstallation(ctx, kcli, kubeutils.RecordInstallationOptions{ | ||
IsAirgap: flags.isAirgap, | ||
License: license, | ||
ConfigSpec: cfgspec, | ||
MetricsBaseURL: replicatedAppURL(), | ||
RuntimeConfig: rc.Get(), | ||
EndUserConfig: eucfg, | ||
IsAirgap: flags.isAirgap, | ||
License: license, | ||
ConfigSpec: cfgspec, | ||
MetricsBaseURL: replicatedAppURL(), | ||
RuntimeConfig: rc.Get(), | ||
EndUserConfig: eucfg, | ||
AirgapUncompressedSize: airgapUncompressedSize, | ||
}) | ||
if err != nil { | ||
return nil, fmt.Errorf("record installation: %w", err) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,11 +7,13 @@ import ( | |
|
||
"github.com/replicatedhq/embedded-cluster/pkg-new/hostutils" | ||
"github.com/replicatedhq/embedded-cluster/pkg-new/preflights" | ||
"github.com/replicatedhq/embedded-cluster/pkg/airgap" | ||
"github.com/replicatedhq/embedded-cluster/pkg/metrics" | ||
"github.com/replicatedhq/embedded-cluster/pkg/netutils" | ||
"github.com/replicatedhq/embedded-cluster/pkg/prompts" | ||
"github.com/replicatedhq/embedded-cluster/pkg/release" | ||
"github.com/replicatedhq/embedded-cluster/pkg/runtimeconfig" | ||
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1" | ||
"github.com/sirupsen/logrus" | ||
"github.com/spf13/cobra" | ||
) | ||
|
@@ -41,7 +43,16 @@ func InstallRunPreflightsCmd(ctx context.Context, name string) *cobra.Command { | |
rc.Cleanup() | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
if err := runInstallRunPreflights(cmd.Context(), name, flags, rc); err != nil { | ||
var airgapInfo *kotsv1beta1.Airgap | ||
if flags.airgapBundle != "" { | ||
var err error | ||
airgapInfo, err = airgap.AirgapInfoFromPath(flags.airgapBundle) | ||
if err != nil { | ||
return fmt.Errorf("failed to get airgap info: %w", err) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move this to preRunInstall |
||
|
||
if err := runInstallRunPreflights(cmd.Context(), name, flags, rc, airgapInfo); err != nil { | ||
return err | ||
} | ||
|
||
|
@@ -59,8 +70,8 @@ func InstallRunPreflightsCmd(ctx context.Context, name string) *cobra.Command { | |
return cmd | ||
} | ||
|
||
func runInstallRunPreflights(ctx context.Context, name string, flags InstallCmdFlags, rc runtimeconfig.RuntimeConfig) error { | ||
if err := verifyAndPrompt(ctx, name, flags, prompts.New()); err != nil { | ||
func runInstallRunPreflights(ctx context.Context, name string, flags InstallCmdFlags, rc runtimeconfig.RuntimeConfig, airgapInfo *kotsv1beta1.Airgap) error { | ||
if err := verifyAndPrompt(ctx, name, flags, prompts.New(), airgapInfo); err != nil { | ||
return err | ||
} | ||
|
||
|
@@ -94,20 +105,32 @@ func runInstallPreflights(ctx context.Context, flags InstallCmdFlags, rc runtime | |
return fmt.Errorf("unable to find first valid address: %w", err) | ||
} | ||
|
||
// Calculate airgap storage space requirement (2x uncompressed size for controller nodes) | ||
var controllerAirgapStorageSpace string | ||
if flags.airgapBundle != "" { | ||
airgapInfo, err := airgap.AirgapInfoFromPath(flags.airgapBundle) | ||
if err != nil { | ||
return fmt.Errorf("failed to get airgap info: %w", err) | ||
} | ||
// The first installed node is always a controller | ||
controllerAirgapStorageSpace = preflights.CalculateAirgapStorageSpace(airgapInfo.Spec.UncompressedSize, true) | ||
} | ||
|
||
opts := preflights.PrepareOptions{ | ||
HostPreflightSpec: release.GetHostPreflights(), | ||
ReplicatedAppURL: replicatedAppURL, | ||
ProxyRegistryURL: proxyRegistryURL, | ||
AdminConsolePort: rc.AdminConsolePort(), | ||
LocalArtifactMirrorPort: rc.LocalArtifactMirrorPort(), | ||
DataDir: rc.EmbeddedClusterHomeDirectory(), | ||
K0sDataDir: rc.EmbeddedClusterK0sSubDir(), | ||
OpenEBSDataDir: rc.EmbeddedClusterOpenEBSLocalSubDir(), | ||
Proxy: rc.ProxySpec(), | ||
PodCIDR: rc.PodCIDR(), | ||
ServiceCIDR: rc.ServiceCIDR(), | ||
NodeIP: nodeIP, | ||
IsAirgap: flags.isAirgap, | ||
HostPreflightSpec: release.GetHostPreflights(), | ||
ReplicatedAppURL: replicatedAppURL, | ||
ProxyRegistryURL: proxyRegistryURL, | ||
AdminConsolePort: rc.AdminConsolePort(), | ||
LocalArtifactMirrorPort: rc.LocalArtifactMirrorPort(), | ||
DataDir: rc.EmbeddedClusterHomeDirectory(), | ||
K0sDataDir: rc.EmbeddedClusterK0sSubDir(), | ||
OpenEBSDataDir: rc.EmbeddedClusterOpenEBSLocalSubDir(), | ||
Proxy: rc.ProxySpec(), | ||
PodCIDR: rc.PodCIDR(), | ||
ServiceCIDR: rc.ServiceCIDR(), | ||
NodeIP: nodeIP, | ||
IsAirgap: flags.isAirgap, | ||
ControllerAirgapStorageSpace: controllerAirgapStorageSpace, | ||
} | ||
if globalCIDR := rc.GlobalCIDR(); globalCIDR != "" { | ||
opts.GlobalCIDR = &globalCIDR | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we move this to the flag parsing code in preRunInstall so it is only run once and fails much earlier? Then make airgapInfo a property of InstallController like airgapBundle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method gets called by postInstallRunHostPreflights which is a http request served by the controller. At which point does
preRunInstall
come in?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
preRunInstall is called in the cli and stores properties for later use in the InstallCmdFlags struct. You can add an
airgapInfo
property and pass this to the api server and store it as a property similar to airgapBundle on the API and InstallController structs.