From 239b0e57cc772d76a2e293d171bd5b63b1402421 Mon Sep 17 00:00:00 2001 From: Salah Al Saleh Date: Wed, 6 Nov 2024 11:12:41 -0800 Subject: [PATCH] Consolidate the logic for determining downstream version status (#4987) * Consolidate the logic for determining downstream version status --- .../tests/@change-license/test.spec.ts | 2 +- .../test.spec.ts | 4 +- .../tests/@multi-app-install/test.spec.ts | 4 +- .../tests/@no-required-config/test.spec.ts | 2 +- pkg/airgap/airgap.go | 69 +-- pkg/airgap/update.go | 31 +- pkg/handlers/config.go | 26 +- pkg/handlers/identity.go | 2 +- pkg/handlers/registry.go | 2 +- pkg/handlers/upload.go | 28 +- pkg/kotsadmupstream/upstream.go | 14 +- pkg/kotsutil/kots.go | 23 +- pkg/kotsutil/kots_test.go | 77 ++- pkg/online/online.go | 66 +-- pkg/operator/operator.go | 2 +- pkg/preflight/preflight.go | 233 +++----- pkg/pull/pull.go | 2 - pkg/rewrite/rewrite.go | 2 - pkg/store/kotsstore/license_store.go | 2 +- pkg/store/kotsstore/version_store.go | 146 ++--- pkg/store/kotsstore/version_store_test.go | 515 ++++++++++++++++++ pkg/store/mock/mock.go | 16 +- pkg/store/store_interface.go | 2 +- pkg/upgradeservice/preflight/preflight.go | 66 +-- 24 files changed, 772 insertions(+), 564 deletions(-) create mode 100644 pkg/store/kotsstore/version_store_test.go diff --git a/e2e/playwright/tests/@change-license/test.spec.ts b/e2e/playwright/tests/@change-license/test.spec.ts index 7fdd4e72eb..58255d6ecb 100644 --- a/e2e/playwright/tests/@change-license/test.spec.ts +++ b/e2e/playwright/tests/@change-license/test.spec.ts @@ -6,8 +6,8 @@ test('change license', async ({ page }) => { await login(page); await uploadLicense(page, expect, "community-license.yaml"); await expect(page.locator('#app')).toContainText('Change License', { timeout: 10000 }); + await expect(page.locator('#app')).toContainText('Currently deployed version', { timeout: 15000 }); await expect(page.locator('#app')).toContainText('Ready', { timeout: 30000 }); - await expect(page.locator('#app')).toContainText('Currently deployed version'); await page.getByRole('link', { name: 'License', exact: true }).click(); await expect(page.locator('#app')).toContainText('change-license-community', { timeout: 10000 }); await expect(page.locator('#app')).toContainText('Community license'); diff --git a/e2e/playwright/tests/@multi-app-backup-and-restore/test.spec.ts b/e2e/playwright/tests/@multi-app-backup-and-restore/test.spec.ts index 2a3ec5805c..5526a71899 100644 --- a/e2e/playwright/tests/@multi-app-backup-and-restore/test.spec.ts +++ b/e2e/playwright/tests/@multi-app-backup-and-restore/test.spec.ts @@ -6,14 +6,14 @@ test('multi app backup and restore', async ({ page }) => { await login(page); await uploadLicense(page, expect, "app1-license.yaml"); await expect(page.locator('#app')).toContainText('Multi App Backup and Restore 1', { timeout: 10000 }); + await expect(page.locator('#app')).toContainText('Currently deployed version', { timeout: 15000 }); await expect(page.locator('#app')).toContainText('Ready', { timeout: 30000 }); - await expect(page.locator('#app')).toContainText('Currently deployed version'); await page.locator('div').filter({ hasText: /^Change passwordAdd new applicationLog out$/ }).getByRole('img').click(); await page.getByText('Add new application').click(); await uploadLicense(page, expect, "app2-license.yaml"); await expect(page.locator('#app')).toContainText('Multi App Backup and Restore 2', { timeout: 10000 }); + await expect(page.locator('#app')).toContainText('Currently deployed version', { timeout: 15000 }); await expect(page.locator('#app')).toContainText('Ready', { timeout: 30000 }); - await expect(page.locator('#app')).toContainText('Currently deployed version'); await page.locator('.NavItem').getByText('Snapshots', { exact: true }).click(); await page.getByRole('link', { name: 'Partial Snapshots' }).click({ timeout: 10000 }); await page.getByRole('button', { name: 'Start a snapshot' }).click({ timeout: 15000 }); diff --git a/e2e/playwright/tests/@multi-app-install/test.spec.ts b/e2e/playwright/tests/@multi-app-install/test.spec.ts index 5a83b2c2b4..82f628db7d 100644 --- a/e2e/playwright/tests/@multi-app-install/test.spec.ts +++ b/e2e/playwright/tests/@multi-app-install/test.spec.ts @@ -13,8 +13,8 @@ test('multi app install', async ({ page }) => { await expect(page.locator('#app')).toContainText('Your cluster meets the recommended and required versions of Kubernetes'); await page.getByRole('button', { name: 'Deploy' }).click(); await expect(page.locator('#app')).toContainText('Multi App Install 1'); + await expect(page.locator('#app')).toContainText('Currently deployed version', { timeout: 15000 }); await expect(page.locator('#app')).toContainText('Ready', { timeout: 30000 }); - await expect(page.locator('#app')).toContainText('Currently deployed version'); await expect(page.locator('#app')).toContainText('0.1.3'); const app1Status = execSync(`kubectl kots get apps -n ${process.env.NAMESPACE} | grep mutli-app-install | awk '{print $2}'`).toString().trim(); expect(app1Status).toBe('ready'); @@ -27,8 +27,8 @@ test('multi app install', async ({ page }) => { await expect(page.locator('#app')).toContainText('Your cluster meets the recommended and required versions of Kubernetes'); await page.getByRole('button', { name: 'Deploy' }).click(); await expect(page.locator('#app')).toContainText('Multi App Install 2'); + await expect(page.locator('#app')).toContainText('Currently deployed version', { timeout: 15000 }); await expect(page.locator('#app')).toContainText('Ready', { timeout: 30000 }); - await expect(page.locator('#app')).toContainText('Currently deployed version'); await expect(page.locator('#app')).toContainText('2.1.2'); const app2Status = execSync(`kubectl kots get apps -n ${process.env.NAMESPACE} | grep multi-app-install-2 | awk '{print $2}'`).toString().trim(); expect(app2Status).toBe('ready'); diff --git a/e2e/playwright/tests/@no-required-config/test.spec.ts b/e2e/playwright/tests/@no-required-config/test.spec.ts index 0d72e076b7..bf1abcca44 100644 --- a/e2e/playwright/tests/@no-required-config/test.spec.ts +++ b/e2e/playwright/tests/@no-required-config/test.spec.ts @@ -12,5 +12,5 @@ test('no required config', async ({ page }) => { await page.getByRole('button', { name: 'Continue' }).click(); await expect(page.locator('#app')).toContainText('Ready', { timeout: 30000 }); await page.getByRole('link', { name: 'Version history' }).click(); - await expect(page.locator('#app')).toContainText('Currently deployed version'); + await expect(page.locator('#app')).toContainText('Currently deployed version', { timeout: 15000 }); }); diff --git a/pkg/airgap/airgap.go b/pkg/airgap/airgap.go index 7de2044ba4..bda7ae902e 100644 --- a/pkg/airgap/airgap.go +++ b/pkg/airgap/airgap.go @@ -257,7 +257,7 @@ func CreateAppFromAirgap(opts CreateAirgapAppOpts) (finalError error) { return errors.Wrap(err, "failed to set app is airgap the second time") } - newSequence, err := store.GetStore().CreateAppVersion(a.ID, nil, tmpRoot, "Airgap Install", opts.SkipPreflights, render.Renderer{}) + newSequence, err := store.GetStore().CreateAppVersion(a.ID, nil, tmpRoot, "Airgap Install", true, opts.IsAutomated, configFile, opts.SkipPreflights, render.Renderer{}) if err != nil { return errors.Wrap(err, "failed to create new version") } @@ -270,77 +270,16 @@ func CreateAppFromAirgap(opts CreateAirgapAppOpts) (finalError error) { return errors.Wrap(err, "failed to create support bundle dependencies") } - kotsKinds, err := kotsutil.LoadKotsKinds(tmpRoot) - if err != nil { - return errors.Wrap(err, "failed to load kotskinds from path") - } - status, err := store.GetStore().GetDownstreamVersionStatus(opts.PendingApp.ID, newSequence) if err != nil { return errors.Wrap(err, "failed to get downstream version status") } - - if status == storetypes.VersionPendingClusterManagement { - if configFile != "" { - // if there is a config file, then we should proceed with the installation normally, not wait for the user - // to click through the UI to add nodes and configure the app - status = storetypes.VersionPendingConfig - } else { - // if pending cluster management, we don't want to deploy the app - return nil - } - } - - hasStrictPreflights, err := store.GetStore().HasStrictPreflights(a.ID, newSequence) - if err != nil { - return errors.Wrap(err, "failed to check if app preflight has strict analyzers") - } - - if hasStrictPreflights && opts.SkipPreflights { - logger.Warnf("preflights will not be skipped, strict preflights are set to %t", hasStrictPreflights) - } - - if opts.IsAutomated && kotsKinds.IsConfigurable() { - // bypass the config screen if no configuration is required - registrySettings := registrytypes.RegistrySettings{ - Hostname: opts.RegistryHost, - Namespace: opts.RegistryNamespace, - Username: opts.RegistryUsername, - Password: opts.RegistryPassword, - IsReadOnly: opts.RegistryIsReadOnly, - } - needsConfig, err := kotsadmconfig.NeedsConfiguration(a.Slug, newSequence, a.IsAirgap, kotsKinds, registrySettings) - if err != nil { - return errors.Wrap(err, "failed to check if app needs configuration") - } - if !needsConfig { - if err := store.GetStore().SetDownstreamVersionStatus(opts.PendingApp.ID, newSequence, storetypes.VersionPending, ""); err != nil { - return errors.Wrap(err, "failed to set downstream version status to pending") - } - if opts.SkipPreflights && !hasStrictPreflights { - if err := version.DeployVersion(opts.PendingApp.ID, newSequence); err != nil { - return errors.Wrap(err, "failed to deploy version") - } - } else { - err := store.GetStore().SetDownstreamVersionStatus(opts.PendingApp.ID, newSequence, storetypes.VersionPendingPreflight, "") - if err != nil { - return errors.Wrap(err, "failed to set downstream version status to 'pending preflight'") - } - } - } - } - - if !opts.SkipPreflights || hasStrictPreflights { + switch status { + case storetypes.VersionPendingPreflight: if err := preflight.Run(opts.PendingApp.ID, opts.PendingApp.Slug, newSequence, true, opts.SkipPreflights, tmpRoot); err != nil { return errors.Wrap(err, "failed to start preflights") } - } - - if !kotsKinds.IsConfigurable() && opts.SkipPreflights && !hasStrictPreflights { - // app is not configurable and preflights are skipped, so just deploy the app - if err := store.GetStore().SetDownstreamVersionStatus(opts.PendingApp.ID, newSequence, storetypes.VersionPending, ""); err != nil { - return errors.Wrap(err, "failed to set downstream version status to pending") - } + case storetypes.VersionPending: if err := version.DeployVersion(opts.PendingApp.ID, newSequence); err != nil { return errors.Wrap(err, "failed to deploy version") } diff --git a/pkg/airgap/update.go b/pkg/airgap/update.go index 4f4d208dc6..998fb93a22 100644 --- a/pkg/airgap/update.go +++ b/pkg/airgap/update.go @@ -229,7 +229,7 @@ func UpdateAppFromPath(a *apptypes.App, airgapRoot string, airgapBundlePath stri } // Create the app in the db - newSequence, err := store.GetStore().CreateAppVersion(a.ID, &baseSequence, archiveDir, "Airgap Update", skipPreflights, render.Renderer{}) + newSequence, err := store.GetStore().CreateAppVersion(a.ID, &baseSequence, archiveDir, "Airgap Update", false, false, "", skipPreflights, render.Renderer{}) if err != nil { return errors.Wrap(err, "failed to create new version") } @@ -239,40 +239,17 @@ func UpdateAppFromPath(a *apptypes.App, airgapRoot string, airgapBundlePath stri logger.Error(errors.Wrapf(err, "failed to reset channel changed flag")) } - hasStrictPreflights, err := store.GetStore().HasStrictPreflights(a.ID, newSequence) + status, err := store.GetStore().GetDownstreamVersionStatus(a.ID, newSequence) if err != nil { - return errors.Wrap(err, "failed to check if app preflight has strict analyzers") + return errors.Wrap(err, "failed to get downstream version status") } - - if hasStrictPreflights && skipPreflights { - logger.Warnf("preflights will not be skipped, strict preflights are set to %t", hasStrictPreflights) - } - - if !skipPreflights || hasStrictPreflights { + if status == storetypes.VersionPendingPreflight { if err := preflight.Run(a.ID, a.Slug, newSequence, true, skipPreflights, archiveDir); err != nil { return errors.Wrap(err, "failed to start preflights") } } if deploy { - downstreams, err := store.GetStore().ListDownstreamsForApp(a.ID) - if err != nil { - return errors.Wrap(err, "failed to fetch downstreams") - } - if len(downstreams) == 0 { - return errors.Errorf("no downstreams found for app %q", a.Slug) - } - downstream := downstreams[0] - - status, err := store.GetStore().GetStatusForVersion(a.ID, downstream.ClusterID, newSequence) - if err != nil { - return errors.Wrapf(err, "failed to get status for version %d", newSequence) - } - - if status == storetypes.VersionPendingConfig { - return errors.Errorf("not deploying version %d because it's %s", newSequence, status) - } - if err := version.DeployVersion(a.ID, newSequence); err != nil { return errors.Wrap(err, "failed to deploy app version") } diff --git a/pkg/handlers/config.go b/pkg/handlers/config.go index f33fda2ceb..ef7e507b42 100644 --- a/pkg/handlers/config.go +++ b/pkg/handlers/config.go @@ -749,7 +749,7 @@ func updateAppConfig(updateApp *apptypes.App, sequence int64, configGroups []kot } if createNewVersion { - newSequence, err := store.GetStore().CreateAppVersion(updateApp.ID, &sequence, archiveDir, "Config Change", skipPreflights, render.Renderer{}) + newSequence, err := store.GetStore().CreateAppVersion(updateApp.ID, &sequence, archiveDir, "Config Change", false, false, "", skipPreflights, render.Renderer{}) if err != nil { updateAppConfigResponse.Error = "failed to create an app version" return updateAppConfigResponse, err @@ -767,22 +767,23 @@ func updateAppConfig(updateApp *apptypes.App, sequence int64, configGroups []kot } } - if err := store.GetStore().SetDownstreamVersionStatus(updateApp.ID, int64(sequence), storetypes.VersionPendingPreflight, ""); err != nil { - updateAppConfigResponse.Error = "failed to set downstream status to 'pending preflight'" - return updateAppConfigResponse, err - } - - hasStrictPreflights, err := store.GetStore().HasStrictPreflights(updateApp.ID, sequence) + status, err := store.GetStore().GetDownstreamVersionStatus(updateApp.ID, sequence) if err != nil { - updateAppConfigResponse.Error = "failed to check if version has strict preflights" + updateAppConfigResponse.Error = "failed to get downstream version status" return updateAppConfigResponse, err } - if hasStrictPreflights && skipPreflights { - logger.Warnf("preflights will not be skipped, strict preflights are set to %t", hasStrictPreflights) + if sequence == 0 && status == storetypes.VersionPending { + // we're in the initial config page and the app is now ready to be deployed + if err := version.DeployVersion(updateApp.ID, sequence); err != nil { + updateAppConfigResponse.Error = "failed to deploy" + return updateAppConfigResponse, err + } + updateAppConfigResponse.Success = true + return updateAppConfigResponse, nil } - if !skipPreflights || hasStrictPreflights { + if status == storetypes.VersionPendingPreflight { if err := preflight.Run(updateApp.ID, updateApp.Slug, int64(sequence), updateApp.IsAirgap, skipPreflights, archiveDir); err != nil { updateAppConfigResponse.Error = errors.Cause(err).Error() return updateAppConfigResponse, err @@ -790,8 +791,7 @@ func updateAppConfig(updateApp *apptypes.App, sequence int64, configGroups []kot } if deploy { - err := version.DeployVersion(updateApp.ID, sequence) - if err != nil { + if err := version.DeployVersion(updateApp.ID, sequence); err != nil { updateAppConfigResponse.Error = "failed to deploy" return updateAppConfigResponse, err } diff --git a/pkg/handlers/identity.go b/pkg/handlers/identity.go index 2c6bc45532..e58e035b2f 100644 --- a/pkg/handlers/identity.go +++ b/pkg/handlers/identity.go @@ -470,7 +470,7 @@ func (h *Handler) ConfigureAppIdentityService(w http.ResponseWriter, r *http.Req return } - newSequence, err := store.GetStore().CreateAppVersion(a.ID, &latestSequence, archiveDir, "Identity Service", false, render.Renderer{}) + newSequence, err := store.GetStore().CreateAppVersion(a.ID, &latestSequence, archiveDir, "Identity Service", false, false, "", false, render.Renderer{}) if err != nil { err = errors.Wrap(err, "failed to create an app version") logger.Error(err) diff --git a/pkg/handlers/registry.go b/pkg/handlers/registry.go index 5890e7b4ea..cc4e5da5c2 100644 --- a/pkg/handlers/registry.go +++ b/pkg/handlers/registry.go @@ -257,7 +257,7 @@ func (h *Handler) UpdateAppRegistry(w http.ResponseWriter, r *http.Request) { } defer os.RemoveAll(appDir) - newSequence, err := store.GetStore().CreateAppVersion(foundApp.ID, &latestSequence, appDir, "Registry Change", false, render.Renderer{}) + newSequence, err := store.GetStore().CreateAppVersion(foundApp.ID, &latestSequence, appDir, "Registry Change", false, false, "", false, render.Renderer{}) if err != nil { logger.Error(errors.Wrap(err, "failed to create app version")) return diff --git a/pkg/handlers/upload.go b/pkg/handlers/upload.go index b40583681f..d62e1daca1 100644 --- a/pkg/handlers/upload.go +++ b/pkg/handlers/upload.go @@ -190,7 +190,7 @@ func (h *Handler) UploadExistingApp(w http.ResponseWriter, r *http.Request) { return } - newSequence, err := store.GetStore().CreateAppVersion(a.ID, &baseSequence, archiveDir, "KOTS Upload", uploadExistingAppRequest.SkipPreflights, render.Renderer{}) + newSequence, err := store.GetStore().CreateAppVersion(a.ID, &baseSequence, archiveDir, "KOTS Upload", false, false, "", uploadExistingAppRequest.SkipPreflights, render.Renderer{}) if err != nil { uploadResponse.Error = util.StrPointer("failed to create app version") logger.Error(errors.Wrap(err, *uploadResponse.Error)) @@ -198,19 +198,14 @@ func (h *Handler) UploadExistingApp(w http.ResponseWriter, r *http.Request) { return } - hasStrictPreflights, err := store.GetStore().HasStrictPreflights(a.ID, newSequence) + status, err := store.GetStore().GetDownstreamVersionStatus(a.ID, newSequence) if err != nil { - uploadResponse.Error = util.StrPointer("failed to check if app preflight has strict analyzers") + uploadResponse.Error = util.StrPointer("failed to get downstream version status") logger.Error(errors.Wrap(err, *uploadResponse.Error)) JSON(w, http.StatusInternalServerError, uploadResponse) return } - - if hasStrictPreflights && uploadExistingAppRequest.SkipPreflights { - logger.Warnf("preflights will not be skipped, strict preflights are set to %t", hasStrictPreflights) - } - - if !uploadExistingAppRequest.SkipPreflights || hasStrictPreflights { + if status == storetypes.VersionPendingPreflight { if err := preflight.Run(a.ID, a.Slug, newSequence, a.IsAirgap, uploadExistingAppRequest.SkipPreflights, archiveDir); err != nil { uploadResponse.Error = util.StrPointer("failed to get run preflights") logger.Error(errors.Wrap(err, *uploadResponse.Error)) @@ -220,21 +215,6 @@ func (h *Handler) UploadExistingApp(w http.ResponseWriter, r *http.Request) { } if uploadExistingAppRequest.Deploy { - status, err := store.GetStore().GetStatusForVersion(a.ID, downstreams[0].ClusterID, newSequence) - if err != nil { - uploadResponse.Error = util.StrPointer("failed to get update downstream status") - logger.Error(errors.Wrap(err, *uploadResponse.Error)) - JSON(w, http.StatusInternalServerError, uploadResponse) - return - } - - if status == storetypes.VersionPendingConfig { - uploadResponse.Error = util.StrPointer(fmt.Sprintf("not deploying version %d because it's %s", newSequence, status)) - logger.Error(errors.Wrap(err, *uploadResponse.Error)) - JSON(w, http.StatusInternalServerError, uploadResponse) - return - } - if err := version.DeployVersion(a.ID, newSequence); err != nil { cause := errors.Cause(err) if _, ok := cause.(util.ActionableError); ok { diff --git a/pkg/kotsadmupstream/upstream.go b/pkg/kotsadmupstream/upstream.go index 1338f6d4c3..5bc6fa1656 100644 --- a/pkg/kotsadmupstream/upstream.go +++ b/pkg/kotsadmupstream/upstream.go @@ -17,6 +17,7 @@ import ( "github.com/replicatedhq/kots/pkg/render" "github.com/replicatedhq/kots/pkg/reporting" "github.com/replicatedhq/kots/pkg/store" + storetypes "github.com/replicatedhq/kots/pkg/store/types" "github.com/replicatedhq/kots/pkg/tasks" "github.com/replicatedhq/kots/pkg/upstream" "github.com/replicatedhq/kots/pkg/upstream/types" @@ -265,7 +266,7 @@ func DownloadUpdate(appID string, update types.Update, skipPreflights bool, skip if afterKotsKinds.Installation.Spec.UpdateCursor == beforeInstallation.UpdateCursor && afterKotsKinds.Installation.Spec.ChannelID == beforeInstallation.ChannelID { return } - newSequence, err := store.GetStore().CreateAppVersion(a.ID, &baseSequence, archiveDir, "Upstream Update", skipPreflights, render.Renderer{}) + newSequence, err := store.GetStore().CreateAppVersion(a.ID, &baseSequence, archiveDir, "Upstream Update", false, false, "", skipPreflights, render.Renderer{}) if err != nil { finalError = errors.Wrap(err, "failed to create version") return @@ -280,17 +281,12 @@ func DownloadUpdate(appID string, update types.Update, skipPreflights bool, skip finalSequence = update.AppSequence } - hasStrictPreflights, err := store.GetStore().HasStrictPreflights(a.ID, *finalSequence) + status, err := store.GetStore().GetDownstreamVersionStatus(a.ID, *finalSequence) if err != nil { - finalError = errors.Wrap(err, "failed to check if app preflight has strict analyzers") + finalError = errors.Wrap(err, "failed to get downstream version status") return } - - if hasStrictPreflights && skipPreflights { - logger.Warnf("preflights will not be skipped, strict preflights are set to %t", hasStrictPreflights) - } - - if !skipPreflights || hasStrictPreflights { + if status == storetypes.VersionPendingPreflight { if err := preflight.Run(appID, a.Slug, *finalSequence, a.IsAirgap, skipPreflights, archiveDir); err != nil { finalError = errors.Wrap(err, "failed to run preflights") return diff --git a/pkg/kotsutil/kots.go b/pkg/kotsutil/kots.go index 9e6d5e010a..67b26214a1 100644 --- a/pkg/kotsutil/kots.go +++ b/pkg/kotsutil/kots.go @@ -32,10 +32,12 @@ import ( kotsscheme "github.com/replicatedhq/kotskinds/client/kotsclientset/scheme" kurlscheme "github.com/replicatedhq/kurlkinds/client/kurlclientset/scheme" kurlv1beta1 "github.com/replicatedhq/kurlkinds/pkg/apis/cluster/v1beta1" + troubleshootanalyze "github.com/replicatedhq/troubleshoot/pkg/analyze" troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" troubleshootscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme" "github.com/replicatedhq/troubleshoot/pkg/collect" "github.com/replicatedhq/troubleshoot/pkg/docrewrite" + troubleshootpreflight "github.com/replicatedhq/troubleshoot/pkg/preflight" velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "gopkg.in/yaml.v2" kuberneteserrors "k8s.io/apimachinery/pkg/api/errors" @@ -238,7 +240,24 @@ func (k *KotsKinds) HasPreflights() bool { if k == nil || k.Preflight == nil { return false } - return len(k.Preflight.Spec.Analyzers) > 0 + for _, a := range k.Preflight.Spec.Analyzers { + exclude := troubleshootanalyze.GetExcludeFlag(a).BoolOrDefaultFalse() + if !exclude { + return true + } + } + return false +} + +func (k *KotsKinds) HasStrictPreflights() (bool, error) { + if !k.HasPreflights() { + return false, nil + } + strict, err := troubleshootpreflight.HasStrictAnalyzers(k.Preflight) + if err != nil { + return false, errors.Wrap(err, "failed to check strict preflights from spec") + } + return strict, nil } func (o KotsKinds) Marshal(g string, v string, k string) (string, error) { @@ -543,7 +562,7 @@ func (k *KotsKinds) addKotsKinds(content []byte) error { case "troubleshoot.sh/v1beta2, Kind=Redactor": k.Redactor = decoded.(*troubleshootv1beta2.Redactor) case "troubleshoot.sh/v1beta2, Kind=Preflight": - k.Preflight = decoded.(*troubleshootv1beta2.Preflight) + k.Preflight = troubleshootpreflight.ConcatPreflightSpec(k.Preflight, decoded.(*troubleshootv1beta2.Preflight)) case "troubleshoot.sh/v1beta2, Kind=HostPreflight": k.HostPreflight = decoded.(*troubleshootv1beta2.HostPreflight) case "velero.io/v1, Kind=Backup": diff --git a/pkg/kotsutil/kots_test.go b/pkg/kotsutil/kots_test.go index d6b200fbd0..db580d103f 100644 --- a/pkg/kotsutil/kots_test.go +++ b/pkg/kotsutil/kots_test.go @@ -19,6 +19,7 @@ import ( kotsv1beta2 "github.com/replicatedhq/kotskinds/apis/kots/v1beta2" kurlv1beta1 "github.com/replicatedhq/kurlkinds/pkg/apis/cluster/v1beta1" troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" + "github.com/replicatedhq/troubleshoot/pkg/multitype" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" @@ -567,7 +568,20 @@ var _ = Describe("Kots", func() { Expect(preflightResult).To(BeFalse()) }) - It("returns true when there are more than one analyzers defined in the preflight spec", func() { + It("returns false when the client-side object does not have analyzers", func() { + kotsKind := &kotsutil.KotsKinds{ + Preflight: &troubleshootv1beta2.Preflight{ + Spec: troubleshootv1beta2.PreflightSpec{ + Analyzers: []*troubleshootv1beta2.Analyze{}, + }, + }, + } + preflightResult := kotsKind.HasPreflights() + Expect(preflightResult).To(BeFalse()) + }) + + It("returns true when there are analyzers defined in the preflight spec", func() { + // single spec kotsKind := &kotsutil.KotsKinds{ Preflight: &troubleshootv1beta2.Preflight{ Spec: troubleshootv1beta2.PreflightSpec{ @@ -580,6 +594,67 @@ var _ = Describe("Kots", func() { preflightResult := kotsKind.HasPreflights() Expect(preflightResult).To(BeTrue()) }) + + It("returns false when all analyzers are excluded", func() { + kotsKind := &kotsutil.KotsKinds{ + Preflight: &troubleshootv1beta2.Preflight{ + Spec: troubleshootv1beta2.PreflightSpec{ + Analyzers: []*troubleshootv1beta2.Analyze{ + { + ClusterVersion: &troubleshootv1beta2.ClusterVersion{ + AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{ + Exclude: multitype.FromBool(true), + }, + }, + }, + { + ClusterVersion: &troubleshootv1beta2.ClusterVersion{ + AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{ + Exclude: multitype.FromBool(true), + }, + }, + }, + { + ClusterVersion: &troubleshootv1beta2.ClusterVersion{ + AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{ + Exclude: multitype.FromBool(true), + }, + }, + }, + }, + }, + }, + } + preflightResult := kotsKind.HasPreflights() + Expect(preflightResult).To(BeFalse()) + }) + + It("returns true when a single analyzer is not excluded", func() { + kotsKind := &kotsutil.KotsKinds{ + Preflight: &troubleshootv1beta2.Preflight{ + Spec: troubleshootv1beta2.PreflightSpec{ + Analyzers: []*troubleshootv1beta2.Analyze{ + { + ClusterVersion: &troubleshootv1beta2.ClusterVersion{ + AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{ + Exclude: multitype.FromBool(true), + }, + }, + }, + { + ClusterVersion: &troubleshootv1beta2.ClusterVersion{ + AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{ + Exclude: multitype.FromBool(false), + }, + }, + }, + }, + }, + }, + } + preflightResult := kotsKind.HasPreflights() + Expect(preflightResult).To(BeTrue()) + }) }) Describe("LoadBrandingArchiveFromPath()", func() { diff --git a/pkg/online/online.go b/pkg/online/online.go index 7767874e45..f469e625a2 100644 --- a/pkg/online/online.go +++ b/pkg/online/online.go @@ -191,7 +191,7 @@ func CreateAppFromOnline(opts CreateOnlineAppOpts) (_ *kotsutil.KotsKinds, final return nil, errors.Wrap(err, "failed to set app is not airgap") } - newSequence, err := store.GetStore().CreateAppVersion(opts.PendingApp.ID, nil, tmpRoot, "Online Install", opts.SkipPreflights, render.Renderer{}) + newSequence, err := store.GetStore().CreateAppVersion(opts.PendingApp.ID, nil, tmpRoot, "Online Install", true, opts.IsAutomated, configFile, opts.SkipPreflights, render.Renderer{}) if err != nil { return nil, errors.Wrap(err, "failed to create new version") } @@ -218,70 +218,12 @@ func CreateAppFromOnline(opts CreateOnlineAppOpts) (_ *kotsutil.KotsKinds, final if err != nil { return nil, errors.Wrap(err, "failed to get downstream version status") } - - if status == storetypes.VersionPendingClusterManagement { - if configFile != "" { - // if there is a config file, then we should proceed with the installation normally, not wait for the user - // to click through the UI to add nodes and configure the app - status = storetypes.VersionPendingConfig - } else { - // if pending cluster management, we don't want to deploy the app - return kotsKinds, nil - } - } - - hasStrictPreflights, err := store.GetStore().HasStrictPreflights(opts.PendingApp.ID, newSequence) - if err != nil { - return nil, errors.Wrap(err, "failed to check if app preflight has strict analyzers") - } - - if hasStrictPreflights && opts.SkipPreflights { - logger.Warnf("preflights will not be skipped, strict preflights are set to %t", hasStrictPreflights) - } - - if opts.IsAutomated && kotsKinds.IsConfigurable() { - // bypass the config screen if no configuration is required and it's an automated install - registrySettings, err := store.GetStore().GetRegistryDetailsForApp(opts.PendingApp.ID) - if err != nil { - return nil, errors.Wrap(err, "failed to get registry settings for app") - } - needsConfig, err := kotsadmconfig.NeedsConfiguration(opts.PendingApp.Slug, newSequence, false, kotsKinds, registrySettings) - if err != nil { - return nil, errors.Wrap(err, "failed to check if app needs configuration") - } - if !needsConfig { - if opts.SkipPreflights && !hasStrictPreflights { - if err := store.GetStore().SetDownstreamVersionStatus(opts.PendingApp.ID, newSequence, storetypes.VersionPending, ""); err != nil { - return nil, errors.Wrap(err, "failed to set downstream version status to pending") - } - if err := version.DeployVersion(opts.PendingApp.ID, newSequence); err != nil { - return nil, errors.Wrap(err, "failed to deploy version") - } - go func() { - if err := reporting.WaitAndReportPreflightChecks(opts.PendingApp.ID, newSequence, opts.SkipPreflights, opts.IsAutomated); err != nil { - logger.Debugf("failed to send preflights data to replicated app: %v", err) - } - }() - } else { - err := store.GetStore().SetDownstreamVersionStatus(opts.PendingApp.ID, newSequence, storetypes.VersionPendingPreflight, "") - if err != nil { - return nil, errors.Wrap(err, "failed to set downstream version status to 'pending preflight'") - } - } - } - } - - if !opts.SkipPreflights || hasStrictPreflights { + switch status { + case storetypes.VersionPendingPreflight: if err := preflight.Run(opts.PendingApp.ID, opts.PendingApp.Slug, newSequence, false, opts.SkipPreflights, tmpRoot); err != nil { return nil, errors.Wrap(err, "failed to start preflights") } - } - - if !kotsKinds.IsConfigurable() && opts.SkipPreflights && !hasStrictPreflights { - if err := store.GetStore().SetDownstreamVersionStatus(opts.PendingApp.ID, newSequence, storetypes.VersionPending, ""); err != nil { - return nil, errors.Wrap(err, "failed to set downstream version status to pending") - } - // app is not configurable and preflights are skipped, so just deploy the app + case storetypes.VersionPending: if err := version.DeployVersion(opts.PendingApp.ID, newSequence); err != nil { return nil, errors.Wrap(err, "failed to deploy version") } diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index de011fdaa2..a4348efe17 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -1001,7 +1001,7 @@ func (o *Operator) reconcileDeployment(cm *corev1.ConfigMap) (finalError error) return errors.Wrap(err, "failed to extract app archive") } - sequence, err := o.store.CreateAppVersion(appID, &baseSequence, archiveDir, source, skipPreflights, render.Renderer{}) + sequence, err := o.store.CreateAppVersion(appID, &baseSequence, archiveDir, source, false, false, "", skipPreflights, render.Renderer{}) if err != nil { return errors.Wrap(err, "failed to create app version") } diff --git a/pkg/preflight/preflight.go b/pkg/preflight/preflight.go index 8ec1b21b1e..c95aebeefd 100644 --- a/pkg/preflight/preflight.go +++ b/pkg/preflight/preflight.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "os" - "path/filepath" "time" "github.com/pkg/errors" @@ -19,9 +18,7 @@ import ( "github.com/replicatedhq/kots/pkg/preflight/types" "github.com/replicatedhq/kots/pkg/registry" registrytypes "github.com/replicatedhq/kots/pkg/registry/types" - "github.com/replicatedhq/kots/pkg/render" "github.com/replicatedhq/kots/pkg/render/helper" - rendertypes "github.com/replicatedhq/kots/pkg/render/types" "github.com/replicatedhq/kots/pkg/reporting" "github.com/replicatedhq/kots/pkg/store" storetypes "github.com/replicatedhq/kots/pkg/store/types" @@ -47,11 +44,6 @@ const ( ) func Run(appID string, appSlug string, sequence int64, isAirgap bool, ignoreNonStrict bool, archiveDir string) error { - kotsKinds, err := kotsutil.LoadKotsKinds(archiveDir) - if err != nil { - return errors.Wrap(err, "failed to load rendered kots kinds") - } - status, err := store.GetStore().GetDownstreamVersionStatus(appID, sequence) if err != nil { return errors.Wrapf(err, "failed to check downstream version %d status", sequence) @@ -65,180 +57,107 @@ func Run(appID string, appSlug string, sequence int64, isAirgap bool, ignoreNonS return nil } - var ignoreRBAC bool - var registrySettings registrytypes.RegistrySettings - var preflight *troubleshootv1beta2.Preflight + kotsKinds, err := kotsutil.LoadKotsKinds(archiveDir) + if err != nil { + return errors.Wrap(err, "failed to load rendered kots kinds") + } + if !kotsKinds.HasPreflights() { + return nil + } - ignoreRBAC, err = store.GetStore().GetIgnoreRBACErrors(appID, sequence) + ignoreRBAC, err := store.GetStore().GetIgnoreRBACErrors(appID, sequence) if err != nil { return errors.Wrap(err, "failed to get ignore rbac flag") } - registrySettings, err = store.GetStore().GetRegistryDetailsForApp(appID) + registrySettings, err := store.GetStore().GetRegistryDetailsForApp(appID) if err != nil { return errors.Wrap(err, "failed to get registry settings for app") } - tsKinds, err := kotsutil.LoadTSKindsFromPath(filepath.Join(archiveDir, "rendered")) - if err != nil { - return errors.Wrap(err, fmt.Sprintf("failed to load troubleshoot kinds from path: %s", filepath.Join(archiveDir, "rendered"))) - } + InjectDefaultPreflights(kotsKinds.Preflight, kotsKinds, registrySettings) - runPreflights := false - if tsKinds.PreflightsV1Beta2 != nil { + var preflightErr error + defer func() { + if preflightErr != nil { + preflightResults := &types.PreflightResults{ + Errors: []*types.PreflightError{ + { + Error: preflightErr.Error(), + IsRBAC: false, + }, + }, + } + if err := setPreflightResults(appID, sequence, preflightResults); err != nil { + logger.Error(errors.Wrap(err, "failed to set preflight results")) + return + } + } + }() - for _, v := range tsKinds.PreflightsV1Beta2 { - preflight = troubleshootpreflight.ConcatPreflightSpec(preflight, &v) + if status != storetypes.VersionDeployed && status != storetypes.VersionDeploying { + if err := store.GetStore().SetDownstreamVersionStatus(appID, sequence, storetypes.VersionPendingPreflight, ""); err != nil { + preflightErr = errors.Wrapf(err, "failed to set downstream version %d pending preflight", sequence) + return preflightErr } + } - InjectDefaultPreflights(preflight, kotsKinds, registrySettings) + collectors, err := registry.UpdateCollectorSpecsWithRegistryData(kotsKinds.Preflight.Spec.Collectors, registrySettings, kotsKinds.Installation, kotsKinds.License, &kotsKinds.KotsApplication) + if err != nil { + preflightErr = errors.Wrap(err, "failed to rewrite images in preflight") + return preflightErr + } + kotsKinds.Preflight.Spec.Collectors = collectors - numAnalyzers := 0 - for _, analyzer := range preflight.Spec.Analyzers { - exclude := troubleshootanalyze.GetExcludeFlag(analyzer).BoolOrDefaultFalse() - if !exclude { - numAnalyzers += 1 - } - } - runPreflights = numAnalyzers > 0 - } else if kotsKinds.Preflight != nil { - // render the preflight file - // we need to convert to bytes first, so that we can reuse the renderfile function - renderedMarshalledPreflights, err := kotsKinds.Marshal("troubleshoot.replicated.com", "v1beta1", "Preflight") - if err != nil { - return errors.Wrap(err, "failed to marshal rendered preflight") - } + go func() { + logger.Info("preflight checks beginning", + zap.String("appID", appID), + zap.Int64("sequence", sequence)) - renderedPreflight, err := render.RenderFile(rendertypes.RenderFileOptions{ - KotsKinds: kotsKinds, - RegistrySettings: registrySettings, - AppSlug: appSlug, - Sequence: sequence, - IsAirgap: isAirgap, - Namespace: util.PodNamespace, - InputContent: []byte(renderedMarshalledPreflights), - }) - if err != nil { - return errors.Wrap(err, "failed to render preflights") + setProgress := func(progress map[string]interface{}) error { + return setPreflightProgress(appID, sequence, progress) } - preflight, err = kotsutil.LoadPreflightFromContents(renderedPreflight) + setResults := func(results *types.PreflightResults) error { + return setPreflightResults(appID, sequence, results) + } + uploadPreflightResults, err := Execute(kotsKinds.Preflight, ignoreRBAC, setProgress, setResults) if err != nil { - return errors.Wrap(err, "failed to load rendered preflight") + logger.Error(errors.Wrap(err, "failed to run preflight checks")) + return } + logger.Info("preflight checks completed") - InjectDefaultPreflights(preflight, kotsKinds, registrySettings) - - numAnalyzers := 0 - for _, analyzer := range preflight.Spec.Analyzers { - exclude := troubleshootanalyze.GetExcludeFlag(analyzer).BoolOrDefaultFalse() - if !exclude { - numAnalyzers += 1 - } - } - runPreflights = numAnalyzers > 0 - } - - if runPreflights { - var preflightErr error - defer func() { - if preflightErr != nil { - preflightResults := &types.PreflightResults{ - Errors: []*types.PreflightError{ - { - Error: preflightErr.Error(), - IsRBAC: false, - }, - }, - } - if err := setPreflightResults(appID, sequence, preflightResults); err != nil { - logger.Error(errors.Wrap(err, "failed to set preflight results")) - return - } + go func() { + err := reporting.GetReporter().SubmitAppInfo(appID) // send app and preflight info when preflights finish + if err != nil { + logger.Debugf("failed to submit app info: %v", err) } }() + // status could've changed while preflights were running status, err := store.GetStore().GetDownstreamVersionStatus(appID, sequence) if err != nil { - preflightErr = errors.Wrap(err, "failed to get version status") - return preflightErr + logger.Error(errors.Wrapf(err, "failed to check downstream version %d status", sequence)) + return } - - if status != storetypes.VersionDeployed && status != storetypes.VersionDeploying { - if err := store.GetStore().SetDownstreamVersionStatus(appID, sequence, storetypes.VersionPendingPreflight, ""); err != nil { - preflightErr = errors.Wrapf(err, "failed to set downstream version %d pending preflight", sequence) - return preflightErr - } + if status == storetypes.VersionDeployed || status == storetypes.VersionDeploying || status == storetypes.VersionFailed { + return } - collectors, err := registry.UpdateCollectorSpecsWithRegistryData(preflight.Spec.Collectors, registrySettings, kotsKinds.Installation, kotsKinds.License, &kotsKinds.KotsApplication) + isDeployed, err := maybeDeployFirstVersion(appID, sequence, uploadPreflightResults, ignoreNonStrict) if err != nil { - preflightErr = errors.Wrap(err, "failed to rewrite images in preflight") - return preflightErr + logger.Error(errors.Wrap(err, "failed to deploy first version")) + return } - preflight.Spec.Collectors = collectors - - go func() { - logger.Info("preflight checks beginning", - zap.String("appID", appID), - zap.Int64("sequence", sequence)) - - setProgress := func(progress map[string]interface{}) error { - return setPreflightProgress(appID, sequence, progress) - } - setResults := func(results *types.PreflightResults) error { - return setPreflightResults(appID, sequence, results) - } - uploadPreflightResults, err := Execute(preflight, ignoreRBAC, setProgress, setResults) - if err != nil { - logger.Error(errors.Wrap(err, "failed to run preflight checks")) - return - } - logger.Info("preflight checks completed") - - go func() { - err := reporting.GetReporter().SubmitAppInfo(appID) // send app and preflight info when preflights finish - if err != nil { - logger.Debugf("failed to submit app info: %v", err) - } - }() - - // status could've changed while preflights were running - status, err := store.GetStore().GetDownstreamVersionStatus(appID, sequence) - if err != nil { - logger.Error(errors.Wrapf(err, "failed to check downstream version %d status", sequence)) - return - } - if status == storetypes.VersionDeployed || status == storetypes.VersionDeploying || status == storetypes.VersionFailed { - return - } - isDeployed, err := maybeDeployFirstVersion(appID, sequence, uploadPreflightResults, ignoreNonStrict) - if err != nil { - logger.Error(errors.Wrap(err, "failed to deploy first version")) + // preflight reporting + if isDeployed { + if err := reporting.WaitAndReportPreflightChecks(appID, sequence, false, false); err != nil { + logger.Debugf("failed to send preflights data to replicated app: %v", err) return } - - // preflight reporting - if isDeployed { - if err := reporting.WaitAndReportPreflightChecks(appID, sequence, false, false); err != nil { - logger.Debugf("failed to send preflights data to replicated app: %v", err) - return - } - } - }() - } else if status != storetypes.VersionDeployed && status != storetypes.VersionFailed { - if sequence == 0 { - _, err := maybeDeployFirstVersion(appID, sequence, &types.PreflightResults{}, ignoreNonStrict) - if err != nil { - return errors.Wrap(err, "failed to deploy first version") - } - } else { - err := store.GetStore().SetDownstreamVersionStatus(appID, sequence, storetypes.VersionPending, "") - if err != nil { - return errors.Wrap(err, "failed to set downstream version status to pending") - } } - } + }() return nil } @@ -373,21 +292,7 @@ func GetPreflightCommand(appSlug string) []string { } func CreateRenderedSpec(app *apptypes.App, sequence int64, origin string, inCluster bool, kotsKinds *kotsutil.KotsKinds, archiveDir string) error { - var builtPreflight *troubleshootv1beta2.Preflight - - tsKinds, err := kotsutil.LoadTSKindsFromPath(filepath.Join(archiveDir, "rendered")) - if err != nil { - return errors.Wrap(err, fmt.Sprintf("failed to load troubleshoot kinds from path: %s", filepath.Join(archiveDir, "rendered"))) - } - - if tsKinds.PreflightsV1Beta2 != nil { - for _, v := range tsKinds.PreflightsV1Beta2 { - builtPreflight = troubleshootpreflight.ConcatPreflightSpec(builtPreflight, &v) - } - } else { - builtPreflight = kotsKinds.Preflight.DeepCopy() - } - + builtPreflight := kotsKinds.Preflight.DeepCopy() if builtPreflight == nil { builtPreflight = &troubleshootv1beta2.Preflight{ TypeMeta: v1.TypeMeta{ diff --git a/pkg/pull/pull.go b/pkg/pull/pull.go index b632cf76cd..0962bee32b 100644 --- a/pkg/pull/pull.go +++ b/pkg/pull/pull.go @@ -607,13 +607,11 @@ func Pull(upstreamURI string, pullOptions PullOptions) (string, error) { if err != nil { return "", errors.Wrap(err, fmt.Sprintf("failed to load troubleshoot kinds from path: %s", u.GetRenderedDir(writeUpstreamOptions))) } - if tsKinds.PreflightsV1Beta2 != nil { var renderedPreflight *troubleshootv1beta2.Preflight for _, v := range tsKinds.PreflightsV1Beta2 { renderedPreflight = troubleshootpreflight.ConcatPreflightSpec(renderedPreflight, &v) } - if renderedPreflight != nil { renderedPreflightBytes, err := kotsutil.MarshalRuntimeObject(renderedPreflight) if err != nil { diff --git a/pkg/rewrite/rewrite.go b/pkg/rewrite/rewrite.go index b8fdab2b77..095f750b5d 100644 --- a/pkg/rewrite/rewrite.go +++ b/pkg/rewrite/rewrite.go @@ -337,13 +337,11 @@ func Rewrite(rewriteOptions RewriteOptions) error { if err != nil { return errors.Wrap(err, fmt.Sprintf("failed to load troubleshoot kinds from path: %s", u.GetRenderedDir(writeUpstreamOptions))) } - if tsKinds.PreflightsV1Beta2 != nil { var renderedPreflight *troubleshootv1beta2.Preflight for _, v := range tsKinds.PreflightsV1Beta2 { renderedPreflight = troubleshootpreflight.ConcatPreflightSpec(renderedPreflight, &v) } - if renderedPreflight != nil { renderedPreflightBytes, err := kotsutil.MarshalRuntimeObject(renderedPreflight) if err != nil { diff --git a/pkg/store/kotsstore/license_store.go b/pkg/store/kotsstore/license_store.go index 4cc4da6e0e..c4516261bf 100644 --- a/pkg/store/kotsstore/license_store.go +++ b/pkg/store/kotsstore/license_store.go @@ -216,7 +216,7 @@ func (s *KOTSStore) createNewVersionForLicenseChangeStatements(appID string, bas return nil, int64(0), errors.Wrap(err, "failed to render new version") } - appVersionStatements, newSequence, err := s.createAppVersionStatements(appID, &baseSequence, archiveDir, "License Change", false, renderer) + appVersionStatements, newSequence, err := s.createAppVersionStatements(appID, &baseSequence, archiveDir, "License Change", false, false, "", false, renderer) if err != nil { return nil, int64(0), errors.Wrap(err, "failed to construct app version statements") } diff --git a/pkg/store/kotsstore/version_store.go b/pkg/store/kotsstore/version_store.go index 4f1dbffc44..cc6f41ab02 100644 --- a/pkg/store/kotsstore/version_store.go +++ b/pkg/store/kotsstore/version_store.go @@ -25,20 +25,19 @@ import ( "github.com/replicatedhq/kots/pkg/k8sutil" kotsadmconfig "github.com/replicatedhq/kots/pkg/kotsadmconfig" "github.com/replicatedhq/kots/pkg/kotsutil" + "github.com/replicatedhq/kots/pkg/logger" "github.com/replicatedhq/kots/pkg/persistence" rendertypes "github.com/replicatedhq/kots/pkg/render/types" "github.com/replicatedhq/kots/pkg/secrets" + "github.com/replicatedhq/kots/pkg/store" "github.com/replicatedhq/kots/pkg/store/types" upstreamtypes "github.com/replicatedhq/kots/pkg/upstream/types" "github.com/replicatedhq/kots/pkg/util" kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1" - troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" - troubleshootpreflight "github.com/replicatedhq/troubleshoot/pkg/preflight" "github.com/rqlite/gorqlite" velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/application/api/v1beta1" ) func (s *KOTSStore) IsRollbackSupportedForVersion(appID string, sequence int64) (bool, error) { @@ -240,11 +239,6 @@ func (s *KOTSStore) GetEmbeddedClusterConfigForVersion(appID string, sequence in // GetAppVersionArchive will fetch the archive and extract it into the given dstPath directory name func (s *KOTSStore) GetAppVersionArchive(appID string, sequence int64, dstPath string) error { - // too noisy - // logger.Debug("getting app version archive", - // zap.String("appID", appID), - // zap.Int64("sequence", sequence)) - path := fmt.Sprintf("%s/%d.tar.gz", appID, sequence) bundlePath, err := filestore.GetStore().ReadArchive(path) if err != nil { @@ -417,7 +411,7 @@ func (s *KOTSStore) UpdateAppVersion(appID string, sequence int64, baseSequence db := persistence.MustGetDBSession() - appVersionStatements, err := s.upsertAppVersionStatements(appID, sequence, baseSequence, filesInDir, source, skipPreflights, renderer) + appVersionStatements, err := s.upsertAppVersionStatements(appID, sequence, baseSequence, filesInDir, source, false, false, "", skipPreflights, renderer) if err != nil { return errors.Wrap(err, "failed to construct app version statements") } @@ -433,9 +427,9 @@ func (s *KOTSStore) UpdateAppVersion(appID string, sequence int64, baseSequence return nil } -func (s *KOTSStore) CreateAppVersion(appID string, baseSequence *int64, filesInDir string, source string, skipPreflights bool, renderer rendertypes.Renderer) (int64, error) { +func (s *KOTSStore) CreateAppVersion(appID string, baseSequence *int64, filesInDir string, source string, isInstall bool, isAutomated bool, configFile string, skipPreflights bool, renderer rendertypes.Renderer) (int64, error) { db := persistence.MustGetDBSession() - appVersionStatements, newSequence, err := s.createAppVersionStatements(appID, baseSequence, filesInDir, source, skipPreflights, renderer) + appVersionStatements, newSequence, err := s.createAppVersionStatements(appID, baseSequence, filesInDir, source, isInstall, isAutomated, configFile, skipPreflights, renderer) if err != nil { return 0, errors.Wrap(err, "failed to construct app version statements") } @@ -451,13 +445,13 @@ func (s *KOTSStore) CreateAppVersion(appID string, baseSequence *int64, filesInD return newSequence, nil } -func (s *KOTSStore) createAppVersionStatements(appID string, baseSequence *int64, filesInDir string, source string, skipPreflights bool, renderer rendertypes.Renderer) ([]gorqlite.ParameterizedStatement, int64, error) { +func (s *KOTSStore) createAppVersionStatements(appID string, baseSequence *int64, filesInDir string, source string, isInstall bool, isAutomated bool, configFile string, skipPreflights bool, renderer rendertypes.Renderer) ([]gorqlite.ParameterizedStatement, int64, error) { newSequence, err := s.GetNextAppSequence(appID) if err != nil { return nil, 0, errors.Wrap(err, "failed to get next sequence number") } - appVersionStatements, err := s.upsertAppVersionStatements(appID, newSequence, baseSequence, filesInDir, source, skipPreflights, renderer) + appVersionStatements, err := s.upsertAppVersionStatements(appID, newSequence, baseSequence, filesInDir, source, isInstall, isAutomated, configFile, skipPreflights, renderer) if err != nil { return nil, 0, errors.Wrap(err, "failed to construct app version statements") } @@ -465,7 +459,7 @@ func (s *KOTSStore) createAppVersionStatements(appID string, baseSequence *int64 return appVersionStatements, newSequence, nil } -func (s *KOTSStore) upsertAppVersionStatements(appID string, sequence int64, baseSequence *int64, filesInDir string, source string, skipPreflights bool, renderer rendertypes.Renderer) ([]gorqlite.ParameterizedStatement, error) { +func (s *KOTSStore) upsertAppVersionStatements(appID string, sequence int64, baseSequence *int64, filesInDir string, source string, isInstall bool, isAutomated bool, configFile string, skipPreflights bool, renderer rendertypes.Renderer) ([]gorqlite.ParameterizedStatement, error) { statements := []gorqlite.ParameterizedStatement{} kotsKinds, err := kotsutil.LoadKotsKinds(filesInDir) @@ -484,18 +478,6 @@ func (s *KOTSStore) upsertAppVersionStatements(appID string, sequence int64, bas appIcon := kotsKinds.KotsApplication.Spec.Icon - renderedPreflight, err := s.renderPreflightSpec(appID, a.Slug, sequence, a.IsAirgap, kotsKinds, renderer) - if err != nil { - return nil, errors.Wrap(err, "failed to render app preflight spec") - } - kotsKinds.Preflight = renderedPreflight - - renderedApplication, err := s.renderApplicationSpec(appID, a.Slug, sequence, a.IsAirgap, kotsKinds, renderer) - if err != nil { - return nil, errors.Wrap(err, "failed to render app application spec") - } - kotsKinds.Application = renderedApplication - brandingArchive, err := kotsutil.LoadBrandingArchiveFromPath(filepath.Join(filesInDir, "upstream")) if err != nil { return nil, errors.Wrap(err, "failed to load branding archive") @@ -546,7 +528,7 @@ func (s *KOTSStore) upsertAppVersionStatements(appID string, sequence int64, bas for _, d := range downstreams { // there's a small chance this is not optimal, but no current code path // will support multiple downstreams, so this is cleaner here for now - downstreamStatus, err := s.determineDownstreamVersionStatus(a, sequence, baseSequence, kotsKinds, skipPreflights) + downstreamStatus, err := determineDownstreamVersionStatus(s, a, sequence, kotsKinds, isInstall, isAutomated, configFile, skipPreflights) if err != nil { return nil, errors.Wrap(err, "failed to determine downstream version status") } @@ -727,19 +709,14 @@ func (s *KOTSStore) upsertAppVersionRecordStatements(appID string, sequence int6 return statements, nil } -func (s *KOTSStore) determineDownstreamVersionStatus(a *apptypes.App, sequence int64, baseSequence *int64, kotsKinds *kotsutil.KotsKinds, skipPreflights bool) (types.DownstreamVersionStatus, error) { - if util.IsEmbeddedCluster() && baseSequence == nil { - // embedded clusters always require cluster management on initial install +func determineDownstreamVersionStatus(s store.Store, a *apptypes.App, sequence int64, kotsKinds *kotsutil.KotsKinds, isInstall bool, isAutomated bool, configFile string, skipPreflights bool) (types.DownstreamVersionStatus, error) { + // Check if we need to show cluster management + if util.IsEmbeddedCluster() && isInstall && configFile == "" { return types.VersionPendingClusterManagement, nil } - if kotsKinds.IsConfigurable() && baseSequence == nil { - // initial version should always require configuration (if exists) even if all required items are already set and have values (except for automated installs, which can override this later) - return types.VersionPendingConfig, nil - } - - // only check if the version needs configuration for later versions (not the initial one) since the config is always required for the initial version (except for automated installs, which can override that later) - if baseSequence != nil { + // Check if we need to configure the app + if kotsKinds.IsConfigurable() { registrySettings, err := s.GetRegistryDetailsForApp(a.ID) if err != nil { return types.VersionUnknown, errors.Wrap(err, "failed to get app registry info") @@ -751,14 +728,25 @@ func (s *KOTSStore) determineDownstreamVersionStatus(a *apptypes.App, sequence i if needsConfig { return types.VersionPendingConfig, nil } + if isInstall && !isAutomated { + // initial installs from the UI should show the config page even if all required items are already set and have values + return types.VersionPendingConfig, nil + } } - hasStrictPreflights, err := troubleshootpreflight.HasStrictAnalyzers(kotsKinds.Preflight) - if err != nil { - return types.VersionUnknown, errors.Wrap(err, "failed to check strict preflights from spec") - } - if kotsKinds.HasPreflights() && (!skipPreflights || hasStrictPreflights) { - return types.VersionPendingPreflight, nil + // Check if we need to run preflights + if kotsKinds.HasPreflights() { + if !skipPreflights { + return types.VersionPendingPreflight, nil + } + strict, err := kotsKinds.HasStrictPreflights() + if err != nil { + return types.VersionUnknown, errors.Wrap(err, "failed to check strict preflights from spec") + } + if strict { + logger.Warnf("preflights will not be skipped, strict preflights are set to true") + return types.VersionPendingPreflight, nil + } } return types.VersionPending, nil @@ -988,78 +976,6 @@ func (s *KOTSStore) HasStrictPreflights(appID string, sequence int64) (bool, err return s.hasStrictPreflights(preflightSpecStr) } -func (s *KOTSStore) renderPreflightSpec(appID string, appSlug string, sequence int64, isAirgap bool, kotsKinds *kotsutil.KotsKinds, renderer rendertypes.Renderer) (*troubleshootv1beta2.Preflight, error) { - if kotsKinds.HasPreflights() { - // render the preflight file - // we need to convert to bytes first, so that we can reuse the renderfile function - renderedMarshalledPreflights, err := kotsKinds.Marshal("troubleshoot.replicated.com", "v1beta1", "Preflight") - if err != nil { - return nil, errors.Wrap(err, "failed to marshal preflight") - } - - registrySettings, err := s.GetRegistryDetailsForApp(appID) - if err != nil { - return nil, errors.Wrap(err, "failed to get registry settings for app") - } - - renderedPreflight, err := renderer.RenderFile(rendertypes.RenderFileOptions{ - KotsKinds: kotsKinds, - RegistrySettings: registrySettings, - AppSlug: appSlug, - Sequence: sequence, - IsAirgap: isAirgap, - Namespace: util.PodNamespace, - InputContent: []byte(renderedMarshalledPreflights), - }) - if err != nil { - return nil, errors.Wrap(err, "failed to render preflights") - } - preflight, err := kotsutil.LoadPreflightFromContents(renderedPreflight) - if err != nil { - return nil, errors.Wrap(err, "failed to load rendered preflight") - } - return preflight, nil - } - - return nil, nil -} - -func (s *KOTSStore) renderApplicationSpec(appID string, appSlug string, sequence int64, isAirgap bool, kotsKinds *kotsutil.KotsKinds, renderer rendertypes.Renderer) (*v1beta1.Application, error) { - if kotsKinds.Application != nil { - // render the application file - // we need to convert to bytes first, so that we can reuse the renderfile function - renderedMarshalledPreflights, err := kotsKinds.Marshal("app.k8s.io", "v1beta1", "Application") - if err != nil { - return nil, errors.Wrap(err, "failed to marshal application") - } - - registrySettings, err := s.GetRegistryDetailsForApp(appID) - if err != nil { - return nil, errors.Wrap(err, "failed to get registry settings for app") - } - - renderedApplication, err := renderer.RenderFile(rendertypes.RenderFileOptions{ - KotsKinds: kotsKinds, - RegistrySettings: registrySettings, - AppSlug: appSlug, - Sequence: sequence, - IsAirgap: isAirgap, - Namespace: util.PodNamespace, - InputContent: []byte(renderedMarshalledPreflights), - }) - if err != nil { - return nil, errors.Wrap(err, "failed to render application") - } - application, err := kotsutil.LoadApplicationFromContents(renderedApplication) - if err != nil { - return nil, errors.Wrap(err, "failed to load rendered application") - } - return application, nil - } - - return nil, nil -} - func (s *KOTSStore) appVersionFromRow(row gorqlite.QueryResult) (*versiontypes.AppVersion, error) { v := &versiontypes.AppVersion{} diff --git a/pkg/store/kotsstore/version_store_test.go b/pkg/store/kotsstore/version_store_test.go new file mode 100644 index 0000000000..ea94297d0b --- /dev/null +++ b/pkg/store/kotsstore/version_store_test.go @@ -0,0 +1,515 @@ +package kotsstore + +import ( + "testing" + + "github.com/golang/mock/gomock" + apptypes "github.com/replicatedhq/kots/pkg/app/types" + "github.com/replicatedhq/kots/pkg/kotsutil" + registrytypes "github.com/replicatedhq/kots/pkg/registry/types" + mock_store "github.com/replicatedhq/kots/pkg/store/mock" + "github.com/replicatedhq/kots/pkg/store/types" + kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1" + troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" + "github.com/replicatedhq/troubleshoot/pkg/multitype" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Test_determineDownstreamVersionStatus(t *testing.T) { + tests := []struct { + name string + app *apptypes.App + sequence int64 + kotsKinds *kotsutil.KotsKinds + isInstall bool + isAutomated bool + configFile string + skipPreflights bool + setup func(t *testing.T, mockStore *mock_store.MockStore) + expected types.DownstreamVersionStatus + }{ + { + name: "embedded cluster installation without config file", + app: &apptypes.App{ + ID: "test-app", + }, + isInstall: true, + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + t.Setenv("EMBEDDED_CLUSTER_ID", "1234") + }, + expected: types.VersionPendingClusterManagement, + }, + { + name: "embedded cluster installation with config file", + app: &apptypes.App{ + ID: "test-app", + }, + isInstall: true, + configFile: "config.yaml", + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + t.Setenv("EMBEDDED_CLUSTER_ID", "1234") + }, + expected: types.VersionPending, + }, + { + name: "embedded cluster update without config file", + app: &apptypes.App{ + ID: "test-app", + }, + isInstall: false, + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + t.Setenv("EMBEDDED_CLUSTER_ID", "1234") + }, + expected: types.VersionPending, + }, + { + name: "embedded cluster update with config file", + app: &apptypes.App{ + ID: "test-app", + }, + isInstall: false, + configFile: "config.yaml", + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + t.Setenv("EMBEDDED_CLUSTER_ID", "1234") + }, + expected: types.VersionPending, + }, + { + name: "app needs configuration in automated installs", + app: &apptypes.App{ + ID: "test-app", + Slug: "test-app", + }, + sequence: 1, + isInstall: true, + isAutomated: true, + kotsKinds: &kotsutil.KotsKinds{ + Config: &kotsv1beta1.Config{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "kots.io/v1beta1", + Kind: "Config", + }, + Spec: kotsv1beta1.ConfigSpec{ + Groups: []kotsv1beta1.ConfigGroup{ + { + Items: []kotsv1beta1.ConfigItem{ + { + Name: "required_item", + Required: true, + }, + }, + }, + }, + }, + }, + }, + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + mockStore.EXPECT().GetRegistryDetailsForApp("test-app").Return(registrytypes.RegistrySettings{}, nil) + }, + expected: types.VersionPendingConfig, + }, + { + name: "app needs configuration in non-automated installs", + app: &apptypes.App{ + ID: "test-app", + Slug: "test-app", + }, + sequence: 1, + isInstall: true, + isAutomated: false, + kotsKinds: &kotsutil.KotsKinds{ + Config: &kotsv1beta1.Config{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "kots.io/v1beta1", + Kind: "Config", + }, + Spec: kotsv1beta1.ConfigSpec{ + Groups: []kotsv1beta1.ConfigGroup{ + { + Items: []kotsv1beta1.ConfigItem{ + { + Name: "required_item", + Required: true, + }, + }, + }, + }, + }, + }, + }, + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + mockStore.EXPECT().GetRegistryDetailsForApp("test-app").Return(registrytypes.RegistrySettings{}, nil) + }, + expected: types.VersionPendingConfig, + }, + { + name: "app needs configuration in non-automated installs even if all items are optional", + app: &apptypes.App{ + ID: "test-app", + Slug: "test-app", + }, + sequence: 1, + isInstall: true, + isAutomated: false, + kotsKinds: &kotsutil.KotsKinds{ + Config: &kotsv1beta1.Config{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "kots.io/v1beta1", + Kind: "Config", + }, + Spec: kotsv1beta1.ConfigSpec{ + Groups: []kotsv1beta1.ConfigGroup{ + { + Items: []kotsv1beta1.ConfigItem{ + { + Name: "optional_item_1", + Required: false, + }, + { + Name: "optional_item_2", + Required: false, + }, + }, + }, + }, + }, + }, + }, + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + mockStore.EXPECT().GetRegistryDetailsForApp("test-app").Return(registrytypes.RegistrySettings{}, nil) + }, + expected: types.VersionPendingConfig, + }, + { + name: "app needs configuration in app updates", + app: &apptypes.App{ + ID: "test-app", + Slug: "test-app", + }, + sequence: 1, + isInstall: false, + isAutomated: false, + kotsKinds: &kotsutil.KotsKinds{ + Config: &kotsv1beta1.Config{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "kots.io/v1beta1", + Kind: "Config", + }, + Spec: kotsv1beta1.ConfigSpec{ + Groups: []kotsv1beta1.ConfigGroup{ + { + Items: []kotsv1beta1.ConfigItem{ + { + Name: "required_item", + Required: true, + }, + }, + }, + }, + }, + }, + }, + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + mockStore.EXPECT().GetRegistryDetailsForApp("test-app").Return(registrytypes.RegistrySettings{}, nil) + }, + expected: types.VersionPendingConfig, + }, + { + name: "optional configurations in automated installs should skip config page", + app: &apptypes.App{ + ID: "test-app", + Slug: "test-app", + }, + sequence: 1, + isInstall: true, + isAutomated: true, + kotsKinds: &kotsutil.KotsKinds{ + Config: &kotsv1beta1.Config{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "kots.io/v1beta1", + Kind: "Config", + }, + Spec: kotsv1beta1.ConfigSpec{ + Groups: []kotsv1beta1.ConfigGroup{ + { + Items: []kotsv1beta1.ConfigItem{ + { + Name: "optional_item_1", + Required: false, + }, + { + Name: "optional_item_2", + Required: false, + }, + }, + }, + }, + }, + }, + }, + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + mockStore.EXPECT().GetRegistryDetailsForApp("test-app").Return(registrytypes.RegistrySettings{}, nil) + }, + expected: types.VersionPending, + }, + { + name: "optional configurations in app update should not require config", + app: &apptypes.App{ + ID: "test-app", + Slug: "test-app", + }, + sequence: 1, + isInstall: false, + isAutomated: false, + kotsKinds: &kotsutil.KotsKinds{ + Config: &kotsv1beta1.Config{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "kots.io/v1beta1", + Kind: "Config", + }, + Spec: kotsv1beta1.ConfigSpec{ + Groups: []kotsv1beta1.ConfigGroup{ + { + Items: []kotsv1beta1.ConfigItem{ + { + Name: "optional_item_1", + Required: false, + }, + { + Name: "optional_item_2", + Required: false, + }, + }, + }, + }, + }, + }, + }, + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + mockStore.EXPECT().GetRegistryDetailsForApp("test-app").Return(registrytypes.RegistrySettings{}, nil) + }, + expected: types.VersionPending, + }, + { + name: "has optional preflights and not skipped", + app: &apptypes.App{ + ID: "test-app", + Slug: "test-app", + }, + kotsKinds: &kotsutil.KotsKinds{ + Preflight: &troubleshootv1beta2.Preflight{ + Spec: troubleshootv1beta2.PreflightSpec{ + Analyzers: []*troubleshootv1beta2.Analyze{ + {}, + }, + }, + }, + }, + skipPreflights: false, + expected: types.VersionPendingPreflight, + }, + { + name: "has optional preflights and is skipped", + app: &apptypes.App{ + ID: "test-app", + Slug: "test-app", + }, + kotsKinds: &kotsutil.KotsKinds{ + Preflight: &troubleshootv1beta2.Preflight{ + Spec: troubleshootv1beta2.PreflightSpec{ + Analyzers: []*troubleshootv1beta2.Analyze{ + {}, + }, + }, + }, + }, + skipPreflights: true, + expected: types.VersionPending, + }, + { + name: "cannot skip strict preflights", + app: &apptypes.App{ + ID: "test-app", + Slug: "test-app", + }, + kotsKinds: &kotsutil.KotsKinds{ + Preflight: &troubleshootv1beta2.Preflight{ + Spec: troubleshootv1beta2.PreflightSpec{ + Analyzers: []*troubleshootv1beta2.Analyze{ + { + ClusterVersion: &troubleshootv1beta2.ClusterVersion{ + AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{ + Strict: multitype.FromBool(true), + }, + }, + }, + }, + }, + }, + }, + skipPreflights: true, + expected: types.VersionPendingPreflight, + }, + { + name: "can skip excluded strict preflights", + app: &apptypes.App{ + ID: "test-app", + Slug: "test-app", + }, + kotsKinds: &kotsutil.KotsKinds{ + Preflight: &troubleshootv1beta2.Preflight{ + Spec: troubleshootv1beta2.PreflightSpec{ + Analyzers: []*troubleshootv1beta2.Analyze{ + { + ClusterVersion: &troubleshootv1beta2.ClusterVersion{ + AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{ + Strict: multitype.FromBool(true), + Exclude: multitype.FromBool(true), + }, + }, + }, + }, + }, + }, + }, + skipPreflights: true, + expected: types.VersionPending, + }, + { + name: "should not run preflights if all preflights are excluded", + app: &apptypes.App{ + ID: "test-app", + Slug: "test-app", + }, + kotsKinds: &kotsutil.KotsKinds{ + Preflight: &troubleshootv1beta2.Preflight{ + Spec: troubleshootv1beta2.PreflightSpec{ + Analyzers: []*troubleshootv1beta2.Analyze{ + { + ClusterVersion: &troubleshootv1beta2.ClusterVersion{ + AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{ + Exclude: multitype.FromBool(true), + }, + }, + }, + { + ClusterVersion: &troubleshootv1beta2.ClusterVersion{ + AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{ + Strict: multitype.FromBool(true), + Exclude: multitype.FromBool(true), + }, + }, + }, + }, + }, + }, + }, + skipPreflights: false, + expected: types.VersionPending, + }, + { + name: "embedded cluster shows cluster management first", + app: &apptypes.App{ + ID: "test-app", + Slug: "test-app", + }, + isInstall: true, + kotsKinds: &kotsutil.KotsKinds{ + Config: &kotsv1beta1.Config{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "kots.io/v1beta1", + Kind: "Config", + }, + Spec: kotsv1beta1.ConfigSpec{ + Groups: []kotsv1beta1.ConfigGroup{ + { + Items: []kotsv1beta1.ConfigItem{ + { + Name: "item_1", + }, + }, + }, + }, + }, + }, + Preflight: &troubleshootv1beta2.Preflight{ + Spec: troubleshootv1beta2.PreflightSpec{ + Analyzers: []*troubleshootv1beta2.Analyze{ + {}, + }, + }, + }, + }, + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + t.Setenv("EMBEDDED_CLUSTER_ID", "1234") + }, + expected: types.VersionPendingClusterManagement, + }, + { + name: "config comes before preflights", + app: &apptypes.App{ + ID: "test-app", + Slug: "test-app", + }, + isInstall: true, + kotsKinds: &kotsutil.KotsKinds{ + Config: &kotsv1beta1.Config{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "kots.io/v1beta1", + Kind: "Config", + }, + Spec: kotsv1beta1.ConfigSpec{ + Groups: []kotsv1beta1.ConfigGroup{ + { + Items: []kotsv1beta1.ConfigItem{ + { + Name: "item_1", + }, + }, + }, + }, + }, + }, + Preflight: &troubleshootv1beta2.Preflight{ + Spec: troubleshootv1beta2.PreflightSpec{ + Analyzers: []*troubleshootv1beta2.Analyze{ + {}, + }, + }, + }, + }, + setup: func(t *testing.T, mockStore *mock_store.MockStore) { + mockStore.EXPECT().GetRegistryDetailsForApp("test-app").Return(registrytypes.RegistrySettings{}, nil) + }, + expected: types.VersionPendingConfig, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req := require.New(t) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockStore := mock_store.NewMockStore(ctrl) + if test.setup != nil { + test.setup(t, mockStore) + } + + status, err := determineDownstreamVersionStatus( + mockStore, + test.app, + test.sequence, + test.kotsKinds, + test.isInstall, + test.isAutomated, + test.configFile, + test.skipPreflights, + ) + req.NoError(err) + req.Equal(test.expected, status) + }) + } +} diff --git a/pkg/store/mock/mock.go b/pkg/store/mock/mock.go index d1a9887e46..4a69bffc71 100644 --- a/pkg/store/mock/mock.go +++ b/pkg/store/mock/mock.go @@ -112,18 +112,18 @@ func (mr *MockStoreMockRecorder) CreateApp(name, channelID, upstreamURI, license } // CreateAppVersion mocks base method. -func (m *MockStore) CreateAppVersion(appID string, baseSequence *int64, filesInDir, source string, skipPreflights bool, renderer types9.Renderer) (int64, error) { +func (m *MockStore) CreateAppVersion(appID string, baseSequence *int64, filesInDir, source string, isInstall, isAutomated bool, configFile string, skipPreflights bool, renderer types9.Renderer) (int64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateAppVersion", appID, baseSequence, filesInDir, source, skipPreflights, renderer) + ret := m.ctrl.Call(m, "CreateAppVersion", appID, baseSequence, filesInDir, source, isInstall, isAutomated, configFile, skipPreflights, renderer) ret0, _ := ret[0].(int64) ret1, _ := ret[1].(error) return ret0, ret1 } // CreateAppVersion indicates an expected call of CreateAppVersion. -func (mr *MockStoreMockRecorder) CreateAppVersion(appID, baseSequence, filesInDir, source, skipPreflights, renderer interface{}) *gomock.Call { +func (mr *MockStoreMockRecorder) CreateAppVersion(appID, baseSequence, filesInDir, source, isInstall, isAutomated, configFile, skipPreflights, renderer interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAppVersion", reflect.TypeOf((*MockStore)(nil).CreateAppVersion), appID, baseSequence, filesInDir, source, skipPreflights, renderer) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAppVersion", reflect.TypeOf((*MockStore)(nil).CreateAppVersion), appID, baseSequence, filesInDir, source, isInstall, isAutomated, configFile, skipPreflights, renderer) } // CreateInProgressSupportBundle mocks base method. @@ -3481,18 +3481,18 @@ func (m *MockVersionStore) EXPECT() *MockVersionStoreMockRecorder { } // CreateAppVersion mocks base method. -func (m *MockVersionStore) CreateAppVersion(appID string, baseSequence *int64, filesInDir, source string, skipPreflights bool, renderer types9.Renderer) (int64, error) { +func (m *MockVersionStore) CreateAppVersion(appID string, baseSequence *int64, filesInDir, source string, isInstall, isAutomated bool, configFile string, skipPreflights bool, renderer types9.Renderer) (int64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateAppVersion", appID, baseSequence, filesInDir, source, skipPreflights, renderer) + ret := m.ctrl.Call(m, "CreateAppVersion", appID, baseSequence, filesInDir, source, isInstall, isAutomated, configFile, skipPreflights, renderer) ret0, _ := ret[0].(int64) ret1, _ := ret[1].(error) return ret0, ret1 } // CreateAppVersion indicates an expected call of CreateAppVersion. -func (mr *MockVersionStoreMockRecorder) CreateAppVersion(appID, baseSequence, filesInDir, source, skipPreflights, renderer interface{}) *gomock.Call { +func (mr *MockVersionStoreMockRecorder) CreateAppVersion(appID, baseSequence, filesInDir, source, isInstall, isAutomated, configFile, skipPreflights, renderer interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAppVersion", reflect.TypeOf((*MockVersionStore)(nil).CreateAppVersion), appID, baseSequence, filesInDir, source, skipPreflights, renderer) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAppVersion", reflect.TypeOf((*MockVersionStore)(nil).CreateAppVersion), appID, baseSequence, filesInDir, source, isInstall, isAutomated, configFile, skipPreflights, renderer) } // CreatePendingDownloadAppVersion mocks base method. diff --git a/pkg/store/store_interface.go b/pkg/store/store_interface.go index 4b6d12aa18..8320a13f66 100644 --- a/pkg/store/store_interface.go +++ b/pkg/store/store_interface.go @@ -184,7 +184,7 @@ type VersionStore interface { GetAppVersionBaseArchive(appID string, versionLabel string) (string, int64, error) CreatePendingDownloadAppVersion(appID string, update upstreamtypes.Update, kotsApplication *kotsv1beta1.Application, license *kotsv1beta1.License) (int64, error) UpdateAppVersion(appID string, sequence int64, baseSequence *int64, filesInDir string, source string, skipPreflights bool, renderer rendertypes.Renderer) error - CreateAppVersion(appID string, baseSequence *int64, filesInDir string, source string, skipPreflights bool, renderer rendertypes.Renderer) (int64, error) + CreateAppVersion(appID string, baseSequence *int64, filesInDir string, source string, isInstall bool, isAutomated bool, configFile string, skipPreflights bool, renderer rendertypes.Renderer) (int64, error) GetAppVersion(appID string, sequence int64) (*versiontypes.AppVersion, error) GetLatestAppSequence(appID string, downloadedOnly bool) (int64, error) UpdateNextAppVersionDiffSummary(appID string, baseSequence int64) error diff --git a/pkg/upgradeservice/preflight/preflight.go b/pkg/upgradeservice/preflight/preflight.go index cc73cb75c7..2a0894ab33 100644 --- a/pkg/upgradeservice/preflight/preflight.go +++ b/pkg/upgradeservice/preflight/preflight.go @@ -2,7 +2,6 @@ package preflight import ( "encoding/json" - "fmt" "os" "path/filepath" "time" @@ -14,12 +13,7 @@ import ( "github.com/replicatedhq/kots/pkg/preflight/types" "github.com/replicatedhq/kots/pkg/registry" registrytypes "github.com/replicatedhq/kots/pkg/registry/types" - "github.com/replicatedhq/kots/pkg/render" - rendertypes "github.com/replicatedhq/kots/pkg/render/types" upgradeservicetypes "github.com/replicatedhq/kots/pkg/upgradeservice/types" - "github.com/replicatedhq/kots/pkg/util" - troubleshootanalyze "github.com/replicatedhq/troubleshoot/pkg/analyze" - troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" troubleshootpreflight "github.com/replicatedhq/troubleshoot/pkg/preflight" "go.uber.org/zap" ) @@ -45,10 +39,9 @@ func Run(params upgradeservicetypes.UpgradeServiceParams) error { if err != nil { return errors.Wrap(err, "failed to load rendered kots kinds") } - - tsKinds, err := kotsutil.LoadTSKindsFromPath(filepath.Join(params.AppArchive, "rendered")) - if err != nil { - return errors.Wrap(err, fmt.Sprintf("failed to load troubleshoot kinds from path: %s", filepath.Join(params.AppArchive, "rendered"))) + if !kotsKinds.HasPreflights() { + logger.Info("no preflights found, not running preflights") + return nil } registrySettings := registrytypes.RegistrySettings{ @@ -59,52 +52,7 @@ func Run(params upgradeservicetypes.UpgradeServiceParams) error { IsReadOnly: params.RegistryIsReadOnly, } - var preflight *troubleshootv1beta2.Preflight - if tsKinds.PreflightsV1Beta2 != nil { - for _, v := range tsKinds.PreflightsV1Beta2 { - preflight = troubleshootpreflight.ConcatPreflightSpec(preflight, &v) - } - } else if kotsKinds.Preflight != nil { - renderedMarshalledPreflights, err := kotsKinds.Marshal("troubleshoot.replicated.com", "v1beta1", "Preflight") - if err != nil { - return errors.Wrap(err, "failed to marshal rendered preflight") - } - renderedPreflight, err := render.RenderFile(rendertypes.RenderFileOptions{ - KotsKinds: kotsKinds, - RegistrySettings: registrySettings, - AppSlug: params.AppSlug, - Sequence: params.NextSequence, - IsAirgap: params.AppIsAirgap, - Namespace: util.PodNamespace, - InputContent: []byte(renderedMarshalledPreflights), - }) - if err != nil { - return errors.Wrap(err, "failed to render preflights") - } - preflight, err = kotsutil.LoadPreflightFromContents(renderedPreflight) - if err != nil { - return errors.Wrap(err, "failed to load rendered preflight") - } - } - - if preflight == nil { - logger.Info("no preflight spec found, not running preflights") - return nil - } - - preflightpkg.InjectDefaultPreflights(preflight, kotsKinds, registrySettings) - - numAnalyzers := 0 - for _, analyzer := range preflight.Spec.Analyzers { - exclude := troubleshootanalyze.GetExcludeFlag(analyzer).BoolOrDefaultFalse() - if !exclude { - numAnalyzers += 1 - } - } - if numAnalyzers == 0 { - logger.Info("no analyzers found, not running preflights") - return nil - } + preflightpkg.InjectDefaultPreflights(kotsKinds.Preflight, kotsKinds, registrySettings) var preflightErr error defer func() { @@ -124,12 +72,12 @@ func Run(params upgradeservicetypes.UpgradeServiceParams) error { } }() - collectors, err := registry.UpdateCollectorSpecsWithRegistryData(preflight.Spec.Collectors, registrySettings, kotsKinds.Installation, kotsKinds.License, &kotsKinds.KotsApplication) + collectors, err := registry.UpdateCollectorSpecsWithRegistryData(kotsKinds.Preflight.Spec.Collectors, registrySettings, kotsKinds.Installation, kotsKinds.License, &kotsKinds.KotsApplication) if err != nil { preflightErr = errors.Wrap(err, "failed to rewrite images in preflight") return preflightErr } - preflight.Spec.Collectors = collectors + kotsKinds.Preflight.Spec.Collectors = collectors go func() { logger.Info("preflight checks beginning", @@ -140,7 +88,7 @@ func Run(params upgradeservicetypes.UpgradeServiceParams) error { return setPreflightResults(params.AppSlug, results) } - _, err := preflightpkg.Execute(preflight, false, setPreflightProgress, setResults) + _, err := preflightpkg.Execute(kotsKinds.Preflight, false, setPreflightProgress, setResults) if err != nil { logger.Error(errors.Wrap(err, "failed to run preflight checks")) return