From 8d623ffee2ca29b00627e4fffa04b338bacd51b1 Mon Sep 17 00:00:00 2001 From: razzle Date: Tue, 17 Jan 2023 20:40:53 -0500 Subject: [PATCH 01/15] begin transition to new init style --- src/internal/api/packages/deploy.go | 69 +++++++++-------------------- 1 file changed, 21 insertions(+), 48 deletions(-) diff --git a/src/internal/api/packages/deploy.go b/src/internal/api/packages/deploy.go index 1770cf6233..f0b0df1743 100644 --- a/src/internal/api/packages/deploy.go +++ b/src/internal/api/packages/deploy.go @@ -7,78 +7,51 @@ package packages import ( "encoding/json" "net/http" - "path" - "path/filepath" globalConfig "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/internal/api/common" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager" - "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/types" ) // DeployPackage deploys a package to the Zarf cluster. func DeployPackage(w http.ResponseWriter, r *http.Request) { - isInitPkg := r.URL.Query().Get("isInitPkg") == "true" - config := types.PackagerConfig{} - if isInitPkg { - var body = types.ZarfInitOptions{} - err := json.NewDecoder(r.Body).Decode(&body) - if err != nil { - message.ErrorWebf(err, w, "Unable to decode the request to deploy the cluster") - return - } - config.IsInitConfig = true - config.InitOpts = body - initPackageName := packager.GetInitPackageName("") - config.DeployOpts.PackagePath = initPackageName + type DeployPayload struct { + DeployOpts types.ZarfDeployOptions `json:"deployOpts"` + InitOpts *types.ZarfInitOptions `json:"initOpts,omitempty"` + } - // Try to use an init-package in the executable directory if none exist in current working directory - if utils.InvalidPath(config.DeployOpts.PackagePath) { - // Get the path to the executable - if executablePath, err := utils.GetFinalExecutablePath(); err != nil { - message.Errorf(err, "Unable to get the path to the executable") - } else { - executableDir := path.Dir(executablePath) - config.DeployOpts.PackagePath = filepath.Join(executableDir, initPackageName) - } + var body DeployPayload - // If the init-package doesn't exist in the executable directory, try the cache directory - if err != nil || utils.InvalidPath(config.DeployOpts.PackagePath) { - config.DeployOpts.PackagePath = filepath.Join(globalConfig.GetAbsCachePath(), initPackageName) + err := json.NewDecoder(r.Body).Decode(&body) + if err != nil { + message.ErrorWebf(err, w, "Unable to decode the request to deploy the cluster") + return + } - // If the init-package doesn't exist in the cache directory, return an error - if utils.InvalidPath(config.DeployOpts.PackagePath) { - common.WriteJSONResponse(w, false, http.StatusBadRequest) - return - } - } - } + // check if init options is empty + if body.InitOpts != nil { + config.InitOpts = *body.InitOpts + config.DeployOpts = body.DeployOpts + initPackageName := packager.GetInitPackageName("") + config.DeployOpts.PackagePath = initPackageName + // now find the init package like in src/cmd/initialize.go } else { - var body = types.ZarfDeployOptions{} - err := json.NewDecoder(r.Body).Decode(&body) - if err != nil { - message.ErrorWebf(err, w, "Unable to decode the request to deploy the cluster") - return - } - config.DeployOpts = body + config.DeployOpts = body.DeployOpts } globalConfig.CommonOptions.Confirm = true - pkg, err := packager.New(&config) - if err != nil { - message.ErrorWebf(err, w, "Unable to deploy the zarf package to the cluster") - } + pkgClient := packager.NewOrDie(&config) + defer pkgClient.ClearTempPaths() - if err := pkg.Deploy(); err != nil { + if err := pkgClient.Deploy(); err != nil { message.ErrorWebf(err, w, "Unable to deploy the zarf package to the cluster") return } - defer pkg.ClearTempPaths() common.WriteJSONResponse(w, true, http.StatusCreated) } From 941ab807f4e7ce8ae597e3e1ce47590827c52d58 Mon Sep 17 00:00:00 2001 From: razzle Date: Tue, 17 Jan 2023 20:51:44 -0500 Subject: [PATCH 02/15] reflect changes to UI --- src/ui/lib/api.ts | 3 +-- src/ui/routes/initialize/deploy/+page.svelte | 28 ++++++++++++-------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/ui/lib/api.ts b/src/ui/lib/api.ts index 8f7f442f33..2fee860475 100644 --- a/src/ui/lib/api.ts +++ b/src/ui/lib/api.ts @@ -39,8 +39,7 @@ const Packages = { findInit: () => http.get('/packages/find-init'), read: (name: string) => http.get(`/packages/read/${encodeURIComponent(name)}`), getDeployedPackages: () => http.get('/packages/list'), - deploy: (body: ZarfDeployOptions | ZarfInitOptions, isInitPkg: boolean) => - http.put(isInitPkg ? `/packages/deploy?isInitPkg=true` : `/packages/deploy`, body), + deploy: (body: any) => http.put(`/packages/deploy`, body), remove: (name: string) => http.del(`/packages/remove/${encodeURIComponent(name)}`) }; diff --git a/src/ui/routes/initialize/deploy/+page.svelte b/src/ui/routes/initialize/deploy/+page.svelte index 57bdb66e7b..3f26a87633 100644 --- a/src/ui/routes/initialize/deploy/+page.svelte +++ b/src/ui/routes/initialize/deploy/+page.svelte @@ -32,10 +32,23 @@ .join(','); const isInitPkg = $pkgStore.zarfPackage.kind === 'ZarfInitConfig'; - let options: ZarfDeployOptions | ZarfInitOptions; + + type DeployPayloadBody = { + initOptions?: ZarfInitOptions; + deployOptions: ZarfDeployOptions; + } + + let options: DeployPayloadBody = { + deployOptions: { + components: requestedComponents, + sGetKeyPath: '', + packagePath: $pkgStore.path, + setVariables: {} + } as ZarfDeployOptions + }; if (isInitPkg) { - options = { + options.initOptions = { applianceMode: false, components: requestedComponents, gitServer: { @@ -57,14 +70,7 @@ pushUsername: 'zarf-push', secret: '' } - }; - } else { - options = { - components: requestedComponents, - sGetKeyPath: '', - packagePath: $pkgStore.path, - setVariables: {} - }; + } as ZarfInitOptions; } let successful = false; @@ -81,7 +87,7 @@ } onMount(() => { - Packages.deploy(options, isInitPkg).then( + Packages.deploy(options).then( (value: boolean) => { finishedDeploying = true; successful = value; From 20df63e1bb411d3d6b738321332b1c6590a82b0d Mon Sep 17 00:00:00 2001 From: razzle Date: Wed, 18 Jan 2023 11:18:11 -0500 Subject: [PATCH 03/15] adapt findInitPackage fn from cmd/initialize.go to work in API flow --- src/internal/api/packages/deploy.go | 52 +++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/src/internal/api/packages/deploy.go b/src/internal/api/packages/deploy.go index f0b0df1743..273a9a9cb3 100644 --- a/src/internal/api/packages/deploy.go +++ b/src/internal/api/packages/deploy.go @@ -6,12 +6,20 @@ package packages import ( "encoding/json" + "errors" + "fmt" "net/http" + "os" + "path" + "path/filepath" + "strings" globalConfig "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/api/common" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager" + "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/types" ) @@ -21,7 +29,7 @@ func DeployPackage(w http.ResponseWriter, r *http.Request) { type DeployPayload struct { DeployOpts types.ZarfDeployOptions `json:"deployOpts"` - InitOpts *types.ZarfInitOptions `json:"initOpts,omitempty"` + InitOpts *types.ZarfInitOptions `json:"initOpts,omitempty"` } var body DeployPayload @@ -32,13 +40,18 @@ func DeployPackage(w http.ResponseWriter, r *http.Request) { return } - // check if init options is empty + // Check if init options is empty if body.InitOpts != nil { config.InitOpts = *body.InitOpts config.DeployOpts = body.DeployOpts initPackageName := packager.GetInitPackageName("") config.DeployOpts.PackagePath = initPackageName - // now find the init package like in src/cmd/initialize.go + // Now find the init package like in src/cmd/initialize.go + var err error + if config.DeployOpts.PackagePath, err = findInitPackage(initPackageName); err != nil { + message.ErrorWebf(err, w, fmt.Sprintf("Unable to find the %s to deploy the cluster", initPackageName)) + return + } } else { config.DeployOpts = body.DeployOpts } @@ -55,3 +68,36 @@ func DeployPackage(w http.ResponseWriter, r *http.Request) { common.WriteJSONResponse(w, true, http.StatusCreated) } + +// Taken from src/cmd/initialize.go +func findInitPackage(initPackageName string) (string, error) { + // First, look for the init package in the current working directory + if !utils.InvalidPath(initPackageName) { + return initPackageName, nil + } + + // Next, look for the init package in the executable directory + executablePath, err := utils.GetFinalExecutablePath() + if err != nil { + return "", err + } + executableDir := path.Dir(executablePath) + if !utils.InvalidPath(filepath.Join(executableDir, initPackageName)) { + return filepath.Join(executableDir, initPackageName), nil + } + + // Create the cache directory if it doesn't exist + if utils.InvalidPath(globalConfig.GetAbsCachePath()) { + if err := os.MkdirAll(globalConfig.GetAbsCachePath(), 0755); err != nil { + return "", fmt.Errorf(strings.ToLower(lang.CmdInitErrUnableCreateCache), globalConfig.GetAbsCachePath()) + } + } + + // Next, look in the cache directory + if !utils.InvalidPath(filepath.Join(globalConfig.GetAbsCachePath(), initPackageName)) { + return filepath.Join(globalConfig.GetAbsCachePath(), initPackageName), nil + } + + // Otherwise return an error + return "", errors.New("unable to find the init package") +} From 9ae2c39cb9a1f7d9615cd648567e7a26115039b2 Mon Sep 17 00:00:00 2001 From: razzle Date: Wed, 18 Jan 2023 15:19:26 -0500 Subject: [PATCH 04/15] reflect new type to ui --- src/ui/routes/initialize/deploy/+page.svelte | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ui/routes/initialize/deploy/+page.svelte b/src/ui/routes/initialize/deploy/+page.svelte index 3f26a87633..9d43723992 100644 --- a/src/ui/routes/initialize/deploy/+page.svelte +++ b/src/ui/routes/initialize/deploy/+page.svelte @@ -43,14 +43,16 @@ components: requestedComponents, sGetKeyPath: '', packagePath: $pkgStore.path, - setVariables: {} + setVariables: {}, + insecure: false, + // "as" will cause the obj to satisfy the type + // it is missing "shasum" } as ZarfDeployOptions }; if (isInitPkg) { options.initOptions = { applianceMode: false, - components: requestedComponents, gitServer: { address: '', pushUsername: 'zarf-git-user', @@ -70,7 +72,7 @@ pushUsername: 'zarf-push', secret: '' } - } as ZarfInitOptions; + }; } let successful = false; From 0106c30daca91ac2642259865a0238e1225a5eab Mon Sep 17 00:00:00 2001 From: razzle Date: Wed, 18 Jan 2023 20:25:02 -0500 Subject: [PATCH 05/15] remove unneeded code --- src/internal/api/packages/deploy.go | 50 +------------------- src/ui/routes/initialize/deploy/+page.svelte | 9 ++-- 2 files changed, 6 insertions(+), 53 deletions(-) diff --git a/src/internal/api/packages/deploy.go b/src/internal/api/packages/deploy.go index 273a9a9cb3..013dbbffd5 100644 --- a/src/internal/api/packages/deploy.go +++ b/src/internal/api/packages/deploy.go @@ -6,26 +6,19 @@ package packages import ( "encoding/json" - "errors" - "fmt" "net/http" - "os" - "path" - "path/filepath" - "strings" globalConfig "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/api/common" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager" - "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/types" ) // DeployPackage deploys a package to the Zarf cluster. func DeployPackage(w http.ResponseWriter, r *http.Request) { config := types.PackagerConfig{} + config.IsInteractive = false type DeployPayload struct { DeployOpts types.ZarfDeployOptions `json:"deployOpts"` @@ -44,14 +37,6 @@ func DeployPackage(w http.ResponseWriter, r *http.Request) { if body.InitOpts != nil { config.InitOpts = *body.InitOpts config.DeployOpts = body.DeployOpts - initPackageName := packager.GetInitPackageName("") - config.DeployOpts.PackagePath = initPackageName - // Now find the init package like in src/cmd/initialize.go - var err error - if config.DeployOpts.PackagePath, err = findInitPackage(initPackageName); err != nil { - message.ErrorWebf(err, w, fmt.Sprintf("Unable to find the %s to deploy the cluster", initPackageName)) - return - } } else { config.DeployOpts = body.DeployOpts } @@ -68,36 +53,3 @@ func DeployPackage(w http.ResponseWriter, r *http.Request) { common.WriteJSONResponse(w, true, http.StatusCreated) } - -// Taken from src/cmd/initialize.go -func findInitPackage(initPackageName string) (string, error) { - // First, look for the init package in the current working directory - if !utils.InvalidPath(initPackageName) { - return initPackageName, nil - } - - // Next, look for the init package in the executable directory - executablePath, err := utils.GetFinalExecutablePath() - if err != nil { - return "", err - } - executableDir := path.Dir(executablePath) - if !utils.InvalidPath(filepath.Join(executableDir, initPackageName)) { - return filepath.Join(executableDir, initPackageName), nil - } - - // Create the cache directory if it doesn't exist - if utils.InvalidPath(globalConfig.GetAbsCachePath()) { - if err := os.MkdirAll(globalConfig.GetAbsCachePath(), 0755); err != nil { - return "", fmt.Errorf(strings.ToLower(lang.CmdInitErrUnableCreateCache), globalConfig.GetAbsCachePath()) - } - } - - // Next, look in the cache directory - if !utils.InvalidPath(filepath.Join(globalConfig.GetAbsCachePath(), initPackageName)) { - return filepath.Join(globalConfig.GetAbsCachePath(), initPackageName), nil - } - - // Otherwise return an error - return "", errors.New("unable to find the init package") -} diff --git a/src/ui/routes/initialize/deploy/+page.svelte b/src/ui/routes/initialize/deploy/+page.svelte index 9d43723992..cee0ac88d4 100644 --- a/src/ui/routes/initialize/deploy/+page.svelte +++ b/src/ui/routes/initialize/deploy/+page.svelte @@ -34,12 +34,12 @@ const isInitPkg = $pkgStore.zarfPackage.kind === 'ZarfInitConfig'; type DeployPayloadBody = { - initOptions?: ZarfInitOptions; - deployOptions: ZarfDeployOptions; + initOpts?: ZarfInitOptions; + deployOpts: ZarfDeployOptions; } let options: DeployPayloadBody = { - deployOptions: { + deployOpts: { components: requestedComponents, sGetKeyPath: '', packagePath: $pkgStore.path, @@ -51,7 +51,7 @@ }; if (isInitPkg) { - options.initOptions = { + options.initOpts = { applianceMode: false, gitServer: { address: '', @@ -89,6 +89,7 @@ } onMount(() => { + console.log('deploying', options) Packages.deploy(options).then( (value: boolean) => { finishedDeploying = true; From 7b653271a5b6309b3a8acb9c3e1c9377773bc00e Mon Sep 17 00:00:00 2001 From: razzle Date: Wed, 18 Jan 2023 20:50:46 -0500 Subject: [PATCH 06/15] update ui tests to differentiate between tests pre/post init --- .github/workflows/test-ui.yml | 5 ++- package.json | 3 ++ src/test/ui/01_start_page.spec.ts | 2 +- src/test/ui/02_initialize_cluster.spec.ts | 47 ++++++++++++++++++++++- src/test/ui/03_view_packages.spec.ts | 2 +- 5 files changed, 54 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-ui.yml b/.github/workflows/test-ui.yml index 1322b84d8e..22471ddd4b 100644 --- a/.github/workflows/test-ui.yml +++ b/.github/workflows/test-ui.yml @@ -43,7 +43,10 @@ jobs: key: ${{ runner.os }}-browsers - name: Run UI tests - run: npm test + run: | + npm test:pre-init && + npm test:init && + npm test:post-init - name: Save logs if: always() diff --git a/package.json b/package.json index f583ebe165..b7ffb24c8d 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,9 @@ "dev": "hack/ui-dev.sh", "build": "vite build", "test": "playwright test -x --reporter github,html", + "test:pre-init": "playwright test -x --reporter github,html --grep @pre-init", + "test:init": "playwright test -x --reporter github,html --grep @init", + "test:post-init": "playwright test -x --reporter github,html --grep @post-init", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" }, diff --git a/src/test/ui/01_start_page.spec.ts b/src/test/ui/01_start_page.spec.ts index eee0cf0910..d38a800f67 100644 --- a/src/test/ui/01_start_page.spec.ts +++ b/src/test/ui/01_start_page.spec.ts @@ -4,7 +4,7 @@ test.beforeEach(async ({ page }) => { page.on('pageerror', (err) => console.log(err.message)); }); -test.describe('start page without an initialized cluster', () => { +test.describe('start page without an initialized cluster @pre-init', () => { test('spinner loads properly, then displays init btn', async ({ page }) => { await page.goto('/auth?token=insecure'); diff --git a/src/test/ui/02_initialize_cluster.spec.ts b/src/test/ui/02_initialize_cluster.spec.ts index e19ab01485..a3217971ac 100644 --- a/src/test/ui/02_initialize_cluster.spec.ts +++ b/src/test/ui/02_initialize_cluster.spec.ts @@ -1,11 +1,16 @@ import { expect, test } from '@playwright/test'; +import { execSync } from 'child_process'; + +function resetK3D() { + execSync('k3d cluster delete && k3d cluster create'); +} test.beforeEach(async ({ page }) => { page.on('pageerror', (err) => console.log(err.message)); }); test.describe('initialize a zarf cluster', () => { - test('configure the init package', async ({ page }) => { + test('configure the init package @pre-init', async ({ page }) => { await page.goto('/auth?token=insecure&next=/initialize/configure'); // Stepper @@ -55,11 +60,49 @@ test.describe('initialize a zarf cluster', () => { await expect(page).toHaveURL('/initialize/review'); }); - test('review the init package', async ({ page }) => { + test('review the init package @pre-init', async ({ page }) => { await page.goto('/auth?token=insecure&next=/initialize/review'); await validateRequiredCheckboxes(page); }); + + test('deploy the init package @init', async ({ page }) => { + await page.goto('/auth?token=insecure&next=/'); + await page.getByRole('link', { name: 'Initialize Cluster' }).click(); + await page.waitForURL('/initialize/configure'); + await page.getByRole('link', { name: 'review deployment' }).click(); + await page.waitForURL('/initialize/review'); + await page.getByRole('link', { name: 'deploy' }).click(); + await page.waitForURL('/initialize/deploy'); + + // expect all steps to have success class + const stepperItems = page.locator('.stepper-vertical .step-icon'); + + // deploy zarf-injector + await expect(stepperItems.nth(0)).toHaveClass(/success/, { + timeout: 45000 + }); + // deploy zarf-seed-registry + await expect(stepperItems.nth(1)).toHaveClass(/success/, { + timeout: 45000 + }); + // deploy zarf-registry + await expect(stepperItems.nth(2)).toHaveClass(/success/, { + timeout: 45000 + }); + // deploy zarf-agent + await expect(stepperItems.nth(3)).toHaveClass(/success/, { + timeout: 45000 + }); + + // verify the final step succeeded + await expect(page.locator('text=Deployment Succeeded')).toBeVisible(); + + // then verify the page redirects to the packages dashboard + await page.waitForURL('/packages', { timeout: 10000 }); + // finally reset the cluster + resetK3D(); + }); }); async function validateRequiredCheckboxes(page) { diff --git a/src/test/ui/03_view_packages.spec.ts b/src/test/ui/03_view_packages.spec.ts index f0cf1151b5..361ec9f397 100644 --- a/src/test/ui/03_view_packages.spec.ts +++ b/src/test/ui/03_view_packages.spec.ts @@ -5,7 +5,7 @@ test.beforeEach(async ({ page }) => { }); test.describe('view packages', () => { - test('is initially blank', async ({ page }) => { + test('is initially blank @pre-init', async ({ page }) => { await page.goto('/auth?token=insecure&next=/packages'); await expect(page.locator('text=No deployed packages found 🙁')).toBeVisible(); await expect(page.locator("a:has-text('Go Home')")).toHaveAttribute('href', '/'); From 72ba9018b61edd28c6d289883392dc5660e45994 Mon Sep 17 00:00:00 2001 From: razzle Date: Wed, 18 Jan 2023 20:59:16 -0500 Subject: [PATCH 07/15] use correct verbiage in ci --- .github/workflows/test-ui.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-ui.yml b/.github/workflows/test-ui.yml index 22471ddd4b..0b7da5fff8 100644 --- a/.github/workflows/test-ui.yml +++ b/.github/workflows/test-ui.yml @@ -43,10 +43,10 @@ jobs: key: ${{ runner.os }}-browsers - name: Run UI tests - run: | - npm test:pre-init && - npm test:init && - npm test:post-init + run: > + npm run test:pre-init && + npm run test:init && + npm run test:post-init - name: Save logs if: always() From fcca4893d88a11c2dde37235268cbda0f7db1c2e Mon Sep 17 00:00:00 2001 From: razzle Date: Wed, 18 Jan 2023 21:39:44 -0500 Subject: [PATCH 08/15] add initial docs for ui e2e tests --- docs/6-developer-guide/2-testing.md | 56 +++++++++++++++++------------ 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/docs/6-developer-guide/2-testing.md b/docs/6-developer-guide/2-testing.md index bf4eb1140f..5ccb16a5f2 100644 --- a/docs/6-developer-guide/2-testing.md +++ b/docs/6-developer-guide/2-testing.md @@ -4,19 +4,16 @@ Currently, we primarily test Zarf through a series of end-to-end tests which can For certain functions, we also test Zarf with a set of unit tests where there are edge cases that are difficult to fully flesh out with an end-to-end test alone. These tests are located as `*_test.go` files within the [src/pkg directory](https://github.com/defenseunicorns/zarf/tree/main/src/pkg). -## Running E2E Tests Locally - -Below are instructions on how you can run our end-to-end tests locally - -### Dependencies +## Dependencies Running the end-to-end tests locally have the same prerequisites as running and building Zarf: -1. GoLang >= `1.19.x` -2. Make -3. Any clean K8s cluster (local or remote) or Linux with sudo if you want to do the Zarf-installed K3s cluster +1. GoLang >= `1.19.x` +2. Make +3. Any clean K8s cluster (local or remote) or Linux with sudo if you want to do the Zarf-installed K3s cluster +4. NodeJS >= `18.x.x` -### Actually Running The Tests +### CLI End-To-End Tests Here are a few different ways to run the tests, based on your specific situation: @@ -40,7 +37,7 @@ go test ./src/test/... -v -run TestFooBarBaz The zarf binary and built packages need to live in the ./build directory but if you're trying to run the tests locally with 'go test ./...' then the zarf-init package will need to be in this directory. ::: -## Adding More End-to-End Tests +### Adding More CLI End-to-End Tests There are a few requirements for all of our tests, that will need to be followed when new tests are added. @@ -57,7 +54,7 @@ func TestFooBarBaz(t *testing.T) { } ``` -## End-to-End Test Naming Conventions +### CLI End-to-End Test Naming Conventions The end-to-end tests are run sequentially and the naming convention is set intentionally: @@ -75,18 +72,9 @@ Due to resource constraints in public github runners, K8s tests are only perform - 23-98 are for the remaining tests that only require a basic zarf cluster without logging for the git-server - 99 is reserved for the `zarf destroy` and [YOLO Mode](../../examples/yolo/README.md) test -## Running Unit Tests Locally - -Below are instructions on how you can run our unit tests locally - -### Dependencies - -Running the unit tests locally have the same prerequisites as building Zarf: +## CLI Unit Tests -1. GoLang >= `1.19.x` -2. Make - -### Actually Running The Tests +### Running CLI Unit Tests Here are a few different ways to run the tests, based on your specific situation: @@ -101,7 +89,7 @@ go test ./src/pkg/... -v go test ./src/pkg/... -v -run TestFooBarBaz ``` -## Adding More Unit Tests +### Adding More CLI Unit Tests There are a few requirements to be considered when thinking about adding new unit tests. @@ -112,3 +100,25 @@ There are a few requirements to be considered when thinking about adding new uni If the answer to these is yes, then this would be a great place for a unit test, if not, you should likely consider writing an end-to-end test instead, or modifying your approach so that you can answer yes. To create a unit test, look for or add a file ending in `_test.go` to the package for the file you are looking to test (e.g. `auth.go` -> `auth_test.go`). Import the testing library and then create your test functions as needed. If you need to mock something out consider the best way to do this, and if it is something that can be used in many tests, consider placing the mock in `./src/test/mocks/`. + +## UI End-To-End Tests + +The UI end-to-end tests are run using [Playwright](https://playwright.dev/). Playwright is a NodeJS library that allows you to run end-to-end tests against a browser. The tests are run against the Zarf UI and are located in the `./src/test/ui` directory. + +### Running UI End-To-End Tests + +Here are a few different ways to run the tests, based on your specific situation: + +```shell +# get help with playwright +npx playwright --help + +# run tests with @pre-init tag +npm run test:pre-init + +# run tests with @init tag +npm run test:init + +# run tests with @post-init tag +npm run test:post-init +``` From b738d89e2c7e73bdcbb7cc59b1955ccc5c2d6574 Mon Sep 17 00:00:00 2001 From: razzle Date: Wed, 18 Jan 2023 21:44:51 -0500 Subject: [PATCH 09/15] update docs --- docs/6-developer-guide/2-testing.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/6-developer-guide/2-testing.md b/docs/6-developer-guide/2-testing.md index 5ccb16a5f2..b229d4adcf 100644 --- a/docs/6-developer-guide/2-testing.md +++ b/docs/6-developer-guide/2-testing.md @@ -110,6 +110,9 @@ The UI end-to-end tests are run using [Playwright](https://playwright.dev/). Pl Here are a few different ways to run the tests, based on your specific situation: ```shell +# dont forget to install dependencies +npm ci + # get help with playwright npx playwright --help From ca82ea8c5960951d9dcefe9b7aacedf8cb7e879e Mon Sep 17 00:00:00 2001 From: razzle Date: Wed, 18 Jan 2023 22:01:25 -0500 Subject: [PATCH 10/15] move type to types/api.go --- src/internal/api/packages/deploy.go | 7 +- src/types/api.go | 7 + src/ui/lib/api-types.ts | 343 ++++++++++--------- src/ui/routes/initialize/deploy/+page.svelte | 9 +- 4 files changed, 187 insertions(+), 179 deletions(-) diff --git a/src/internal/api/packages/deploy.go b/src/internal/api/packages/deploy.go index 013dbbffd5..05b94d469b 100644 --- a/src/internal/api/packages/deploy.go +++ b/src/internal/api/packages/deploy.go @@ -20,12 +20,7 @@ func DeployPackage(w http.ResponseWriter, r *http.Request) { config := types.PackagerConfig{} config.IsInteractive = false - type DeployPayload struct { - DeployOpts types.ZarfDeployOptions `json:"deployOpts"` - InitOpts *types.ZarfInitOptions `json:"initOpts,omitempty"` - } - - var body DeployPayload + var body types.APIZarfDeployPayload err := json.NewDecoder(r.Body).Decode(&body) if err != nil { diff --git a/src/types/api.go b/src/types/api.go index 208fd0ae89..80be071dcb 100644 --- a/src/types/api.go +++ b/src/types/api.go @@ -16,6 +16,7 @@ type RestAPI struct { ClusterSummary ClusterSummary `json:"clusterSummary"` DeployedPackage DeployedPackage `json:"deployedPackage"` APIZarfPackage APIZarfPackage `json:"apiZarfPackage"` + APIZarfDeployPayload APIZarfDeployPayload `json:"apiZarfDeployPayload"` } // ClusterSummary contains the summary of a cluster for the API. @@ -31,3 +32,9 @@ type APIZarfPackage struct { Path string `json:"path"` ZarfPackage ZarfPackage `json:"zarfPackage"` } + +// APIZarfDeployPayload represents the needed data to deploy a ZarfPackage/ZarfInit +type APIZarfDeployPayload struct { + DeployOpts ZarfDeployOptions `json:"deployOpts"` + InitOpts *ZarfInitOptions `json:"initOpts,omitempty"` +} \ No newline at end of file diff --git a/src/ui/lib/api-types.ts b/src/ui/lib/api-types.ts index fd215a51cf..56b63544c1 100644 --- a/src/ui/lib/api-types.ts +++ b/src/ui/lib/api-types.ts @@ -8,16 +8,146 @@ // match the expected interface, even if the JSON is valid. export interface APITypes { - apiZarfPackage: APIZarfPackage; - clusterSummary: ClusterSummary; - connectStrings: { [key: string]: ConnectString }; - deployedPackage: DeployedPackage; - zarfCommonOptions: ZarfCommonOptions; - zarfCreateOptions: ZarfCreateOptions; - zarfDeployOptions: ZarfDeployOptions; - zarfInitOptions: ZarfInitOptions; - zarfPackage: ZarfPackage; - zarfState: ZarfState; + apiZarfDeployPayload: APIZarfDeployPayload; + apiZarfPackage: APIZarfPackage; + clusterSummary: ClusterSummary; + connectStrings: { [key: string]: ConnectString }; + deployedPackage: DeployedPackage; + zarfCommonOptions: ZarfCommonOptions; + zarfCreateOptions: ZarfCreateOptions; + zarfDeployOptions: ZarfDeployOptions; + zarfInitOptions: ZarfInitOptions; + zarfPackage: ZarfPackage; + zarfState: ZarfState; +} + +export interface APIZarfDeployPayload { + deployOpts: ZarfDeployOptions; + initOpts?: ZarfInitOptions; +} + +export interface ZarfDeployOptions { + /** + * Comma separated list of optional components to deploy + */ + components: string; + /** + * Allow insecure connections for remote packages + */ + insecure: boolean; + /** + * Location where a Zarf package to deploy can be found + */ + packagePath: string; + /** + * Key-Value map of variable names and their corresponding values that will be used to + * template against the Zarf package being used + */ + setVariables: { [key: string]: string }; + /** + * Location where the public key component of a cosign key-pair can be found + */ + sGetKeyPath: string; + /** + * The SHA256 checksum of the package to deploy + */ + shasum: string; +} + +export interface ZarfInitOptions { + /** + * Indicates if Zarf was initialized while deploying its own k8s cluster + */ + applianceMode: boolean; + /** + * Information about the repository Zarf is going to be using + */ + gitServer: GitServerInfo; + /** + * Information about the registry Zarf is going to be using + */ + registryInfo: RegistryInfo; + /** + * StorageClass of the k8s cluster Zarf is initializing + */ + storageClass: string; +} + +/** + * Information about the repository Zarf is going to be using + * + * Information about the repository Zarf is configured to use + */ +export interface GitServerInfo { + /** + * URL address of the git server + */ + address: string; + /** + * Indicates if we are using a git server that Zarf is directly managing + */ + internalServer: boolean; + /** + * Password of a user with pull-only access to the git repository. If not provided for an + * external repository than the push-user is used + */ + pullPassword: string; + /** + * Username of a user with pull-only access to the git repository. If not provided for an + * external repository than the push-user is used + */ + pullUsername: string; + /** + * Password of a user with push access to the git repository + */ + pushPassword: string; + /** + * Username of a user with push access to the git repository + */ + pushUsername: string; +} + +/** + * Information about the registry Zarf is going to be using + * + * Information about the registry Zarf is configured to use + */ +export interface RegistryInfo { + /** + * URL address of the registry + */ + address: string; + /** + * Indicates if we are using a registry that Zarf is directly managing + */ + internalRegistry: boolean; + /** + * Nodeport of the registry. Only needed if the registry is running inside the kubernetes + * cluster + */ + nodePort: number; + /** + * Password of a user with pull-only access to the registry. If not provided for an external + * registry than the push-user is used + */ + pullPassword: string; + /** + * Username of a user with pull-only access to the registry. If not provided for an external + * registry than the push-user is used + */ + pullUsername: string; + /** + * Password of a user with push access to the registry + */ + pushPassword: string; + /** + * Username of a user with push access to the registry + */ + pushUsername: string; + /** + * Secret value that the registry was seeded with + */ + secret: string; } export interface APIZarfPackage { @@ -462,83 +592,6 @@ export interface GeneratedPKI { key: string; } -/** - * Information about the repository Zarf is configured to use - * - * Information about the repository Zarf is going to be using - */ -export interface GitServerInfo { - /** - * URL address of the git server - */ - address: string; - /** - * Indicates if we are using a git server that Zarf is directly managing - */ - internalServer: boolean; - /** - * Password of a user with pull-only access to the git repository. If not provided for an - * external repository than the push-user is used - */ - pullPassword: string; - /** - * Username of a user with pull-only access to the git repository. If not provided for an - * external repository than the push-user is used - */ - pullUsername: string; - /** - * Password of a user with push access to the git repository - */ - pushPassword: string; - /** - * Username of a user with push access to the git repository - */ - pushUsername: string; -} - -/** - * Information about the registry Zarf is configured to use - * - * Information about the registry Zarf is going to be using - */ -export interface RegistryInfo { - /** - * URL address of the registry - */ - address: string; - /** - * Indicates if we are using a registry that Zarf is directly managing - */ - internalRegistry: boolean; - /** - * Nodeport of the registry. Only needed if the registry is running inside the kubernetes - * cluster - */ - nodePort: number; - /** - * Password of a user with pull-only access to the registry. If not provided for an external - * registry than the push-user is used - */ - pullPassword: string; - /** - * Username of a user with pull-only access to the registry. If not provided for an external - * registry than the push-user is used - */ - pullUsername: string; - /** - * Password of a user with push access to the registry - */ - pushPassword: string; - /** - * Username of a user with push access to the registry - */ - pushUsername: string; - /** - * Secret value that the registry was seeded with - */ - secret: string; -} - export interface ConnectString { /** * Descriptive text that explains what the resource you would be connecting to is used for @@ -619,53 +672,6 @@ export interface ZarfCreateOptions { skipSBOM: boolean; } -export interface ZarfDeployOptions { - /** - * Comma separated list of optional components to deploy - */ - components: string; - /** - * Allow insecure connections for remote packages - */ - insecure: boolean; - /** - * Location where a Zarf package to deploy can be found - */ - packagePath: string; - /** - * Key-Value map of variable names and their corresponding values that will be used to - * template against the Zarf package being used - */ - setVariables: { [key: string]: string }; - /** - * Location where the public key component of a cosign key-pair can be found - */ - sGetKeyPath: string; - /** - * The SHA256 checksum of the package to deploy - */ - shasum: string; -} - -export interface ZarfInitOptions { - /** - * Indicates if Zarf was initialized while deploying its own k8s cluster - */ - applianceMode: boolean; - /** - * Information about the repository Zarf is going to be using - */ - gitServer: GitServerInfo; - /** - * Information about the registry Zarf is going to be using - */ - registryInfo: RegistryInfo; - /** - * StorageClass of the k8s cluster Zarf is initializing - */ - storageClass: string; -} - // Converts JSON strings to/from your types // and asserts the results of JSON.parse at runtime export class Convert { @@ -832,6 +838,7 @@ function r(name: string) { const typeMap: any = { "APITypes": o([ + { json: "apiZarfDeployPayload", js: "apiZarfDeployPayload", typ: r("APIZarfDeployPayload") }, { json: "apiZarfPackage", js: "apiZarfPackage", typ: r("APIZarfPackage") }, { json: "clusterSummary", js: "clusterSummary", typ: r("ClusterSummary") }, { json: "connectStrings", js: "connectStrings", typ: m(r("ConnectString")) }, @@ -843,6 +850,42 @@ const typeMap: any = { { json: "zarfPackage", js: "zarfPackage", typ: r("ZarfPackage") }, { json: "zarfState", js: "zarfState", typ: r("ZarfState") }, ], false), + "APIZarfDeployPayload": o([ + { json: "deployOpts", js: "deployOpts", typ: r("ZarfDeployOptions") }, + { json: "initOpts", js: "initOpts", typ: u(undefined, r("ZarfInitOptions")) }, + ], false), + "ZarfDeployOptions": o([ + { json: "components", js: "components", typ: "" }, + { json: "insecure", js: "insecure", typ: true }, + { json: "packagePath", js: "packagePath", typ: "" }, + { json: "setVariables", js: "setVariables", typ: m("") }, + { json: "sGetKeyPath", js: "sGetKeyPath", typ: "" }, + { json: "shasum", js: "shasum", typ: "" }, + ], false), + "ZarfInitOptions": o([ + { json: "applianceMode", js: "applianceMode", typ: true }, + { json: "gitServer", js: "gitServer", typ: r("GitServerInfo") }, + { json: "registryInfo", js: "registryInfo", typ: r("RegistryInfo") }, + { json: "storageClass", js: "storageClass", typ: "" }, + ], false), + "GitServerInfo": o([ + { json: "address", js: "address", typ: "" }, + { json: "internalServer", js: "internalServer", typ: true }, + { json: "pullPassword", js: "pullPassword", typ: "" }, + { json: "pullUsername", js: "pullUsername", typ: "" }, + { json: "pushPassword", js: "pushPassword", typ: "" }, + { json: "pushUsername", js: "pushUsername", typ: "" }, + ], false), + "RegistryInfo": o([ + { json: "address", js: "address", typ: "" }, + { json: "internalRegistry", js: "internalRegistry", typ: true }, + { json: "nodePort", js: "nodePort", typ: 0 }, + { json: "pullPassword", js: "pullPassword", typ: "" }, + { json: "pullUsername", js: "pullUsername", typ: "" }, + { json: "pushPassword", js: "pushPassword", typ: "" }, + { json: "pushUsername", js: "pushUsername", typ: "" }, + { json: "secret", js: "secret", typ: "" }, + ], false), "APIZarfPackage": o([ { json: "path", js: "path", typ: "" }, { json: "zarfPackage", js: "zarfPackage", typ: r("ZarfPackage") }, @@ -978,24 +1021,6 @@ const typeMap: any = { { json: "cert", js: "cert", typ: "" }, { json: "key", js: "key", typ: "" }, ], false), - "GitServerInfo": o([ - { json: "address", js: "address", typ: "" }, - { json: "internalServer", js: "internalServer", typ: true }, - { json: "pullPassword", js: "pullPassword", typ: "" }, - { json: "pullUsername", js: "pullUsername", typ: "" }, - { json: "pushPassword", js: "pushPassword", typ: "" }, - { json: "pushUsername", js: "pushUsername", typ: "" }, - ], false), - "RegistryInfo": o([ - { json: "address", js: "address", typ: "" }, - { json: "internalRegistry", js: "internalRegistry", typ: true }, - { json: "nodePort", js: "nodePort", typ: 0 }, - { json: "pullPassword", js: "pullPassword", typ: "" }, - { json: "pullUsername", js: "pullUsername", typ: "" }, - { json: "pushPassword", js: "pushPassword", typ: "" }, - { json: "pushUsername", js: "pushUsername", typ: "" }, - { json: "secret", js: "secret", typ: "" }, - ], false), "ConnectString": o([ { json: "description", js: "description", typ: "" }, { json: "url", js: "url", typ: "" }, @@ -1029,20 +1054,6 @@ const typeMap: any = { { json: "setVariables", js: "setVariables", typ: m("") }, { json: "skipSBOM", js: "skipSBOM", typ: true }, ], false), - "ZarfDeployOptions": o([ - { json: "components", js: "components", typ: "" }, - { json: "insecure", js: "insecure", typ: true }, - { json: "packagePath", js: "packagePath", typ: "" }, - { json: "setVariables", js: "setVariables", typ: m("") }, - { json: "sGetKeyPath", js: "sGetKeyPath", typ: "" }, - { json: "shasum", js: "shasum", typ: "" }, - ], false), - "ZarfInitOptions": o([ - { json: "applianceMode", js: "applianceMode", typ: true }, - { json: "gitServer", js: "gitServer", typ: r("GitServerInfo") }, - { json: "registryInfo", js: "registryInfo", typ: r("RegistryInfo") }, - { json: "storageClass", js: "storageClass", typ: "" }, - ], false), "Architecture": [ "amd64", "arm64", diff --git a/src/ui/routes/initialize/deploy/+page.svelte b/src/ui/routes/initialize/deploy/+page.svelte index cee0ac88d4..bc478b3d4f 100644 --- a/src/ui/routes/initialize/deploy/+page.svelte +++ b/src/ui/routes/initialize/deploy/+page.svelte @@ -15,7 +15,7 @@ import { Packages } from '$lib/api'; import { Dialog, Stepper, Typography } from '@ui'; import bigZarf from '@images/zarf-bubbles-right.png'; - import type { ZarfDeployOptions, ZarfInitOptions } from '$lib/api-types'; + import type { APIZarfDeployPayload, ZarfDeployOptions } from '$lib/api-types'; import { pkgComponentDeployStore, pkgStore } from '$lib/store'; import type { StepProps } from '@defense-unicorns/unicorn-ui/Stepper/Step.svelte'; @@ -32,13 +32,8 @@ .join(','); const isInitPkg = $pkgStore.zarfPackage.kind === 'ZarfInitConfig'; - - type DeployPayloadBody = { - initOpts?: ZarfInitOptions; - deployOpts: ZarfDeployOptions; - } - let options: DeployPayloadBody = { + let options: APIZarfDeployPayload = { deployOpts: { components: requestedComponents, sGetKeyPath: '', From 16c5c636171bf7697827c1ab72cae90d815696ae Mon Sep 17 00:00:00 2001 From: razzle Date: Wed, 18 Jan 2023 22:13:59 -0500 Subject: [PATCH 11/15] add a @post-init test --- src/test/ui/01_start_page.spec.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/test/ui/01_start_page.spec.ts b/src/test/ui/01_start_page.spec.ts index d38a800f67..272c12280f 100644 --- a/src/test/ui/01_start_page.spec.ts +++ b/src/test/ui/01_start_page.spec.ts @@ -4,8 +4,8 @@ test.beforeEach(async ({ page }) => { page.on('pageerror', (err) => console.log(err.message)); }); -test.describe('start page without an initialized cluster @pre-init', () => { - test('spinner loads properly, then displays init btn', async ({ page }) => { +test.describe('start page', () => { + test('spinner loads properly, then displays init btn @pre-init', async ({ page }) => { await page.goto('/auth?token=insecure'); const clusterSelector = page.locator('#cluster-selector'); @@ -30,4 +30,10 @@ test.describe('start page without an initialized cluster @pre-init', () => { await page.waitForURL('**/initialize/configure'); }); + test('page redirects to /packages @post-init', async ({ page }) => { + await page.goto('/auth?token=insecure'); + + // expect to be redirected to /packages + await page.waitForURL('/packages', { timeout: 10000 }); + }); }); From 0ba7058c72f50b3138a38602522e07e24e033099 Mon Sep 17 00:00:00 2001 From: razzle Date: Wed, 18 Jan 2023 22:14:31 -0500 Subject: [PATCH 12/15] cleanup --- src/ui/routes/initialize/deploy/+page.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ui/routes/initialize/deploy/+page.svelte b/src/ui/routes/initialize/deploy/+page.svelte index bc478b3d4f..05592ca21b 100644 --- a/src/ui/routes/initialize/deploy/+page.svelte +++ b/src/ui/routes/initialize/deploy/+page.svelte @@ -84,7 +84,6 @@ } onMount(() => { - console.log('deploying', options) Packages.deploy(options).then( (value: boolean) => { finishedDeploying = true; From 61dbb55c36e0bfd8082999a013717bd5a8be2363 Mon Sep 17 00:00:00 2001 From: razzle Date: Wed, 18 Jan 2023 22:26:15 -0500 Subject: [PATCH 13/15] dont reset the cluster after init --- src/test/ui/01_start_page.spec.ts | 7 +++++++ src/test/ui/02_initialize_cluster.spec.ts | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test/ui/01_start_page.spec.ts b/src/test/ui/01_start_page.spec.ts index 272c12280f..42ef4ff811 100644 --- a/src/test/ui/01_start_page.spec.ts +++ b/src/test/ui/01_start_page.spec.ts @@ -33,6 +33,13 @@ test.describe('start page', () => { test('page redirects to /packages @post-init', async ({ page }) => { await page.goto('/auth?token=insecure'); + // display loading spinner + const spinner = page.locator('.spinner'); + await expect(spinner).toBeVisible(); + + // spinner disappears + await expect(spinner).not.toBeVisible(); + // expect to be redirected to /packages await page.waitForURL('/packages', { timeout: 10000 }); }); diff --git a/src/test/ui/02_initialize_cluster.spec.ts b/src/test/ui/02_initialize_cluster.spec.ts index a3217971ac..a9a3e95246 100644 --- a/src/test/ui/02_initialize_cluster.spec.ts +++ b/src/test/ui/02_initialize_cluster.spec.ts @@ -1,9 +1,4 @@ import { expect, test } from '@playwright/test'; -import { execSync } from 'child_process'; - -function resetK3D() { - execSync('k3d cluster delete && k3d cluster create'); -} test.beforeEach(async ({ page }) => { page.on('pageerror', (err) => console.log(err.message)); @@ -100,8 +95,6 @@ test.describe('initialize a zarf cluster', () => { // then verify the page redirects to the packages dashboard await page.waitForURL('/packages', { timeout: 10000 }); - // finally reset the cluster - resetK3D(); }); }); From a80dffe8c56303045b4186102ebe126b7c83dc7c Mon Sep 17 00:00:00 2001 From: razzle Date: Wed, 18 Jan 2023 22:46:49 -0500 Subject: [PATCH 14/15] update type on api.ts --- src/ui/lib/api.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/lib/api.ts b/src/ui/lib/api.ts index 2fee860475..01c1214300 100644 --- a/src/ui/lib/api.ts +++ b/src/ui/lib/api.ts @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2021-Present The Zarf Authors import type { + APIZarfDeployPayload, APIZarfPackage, ClusterSummary, DeployedComponent, @@ -39,7 +40,7 @@ const Packages = { findInit: () => http.get('/packages/find-init'), read: (name: string) => http.get(`/packages/read/${encodeURIComponent(name)}`), getDeployedPackages: () => http.get('/packages/list'), - deploy: (body: any) => http.put(`/packages/deploy`, body), + deploy: (options: APIZarfDeployPayload) => http.put(`/packages/deploy`, options), remove: (name: string) => http.del(`/packages/remove/${encodeURIComponent(name)}`) }; From 3b3934721f1bd08b9bfaea1af85ebc0514a780bc Mon Sep 17 00:00:00 2001 From: razzle Date: Wed, 18 Jan 2023 23:09:12 -0500 Subject: [PATCH 15/15] remove unneeded code --- src/internal/api/packages/deploy.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/internal/api/packages/deploy.go b/src/internal/api/packages/deploy.go index 05b94d469b..6ca26dc384 100644 --- a/src/internal/api/packages/deploy.go +++ b/src/internal/api/packages/deploy.go @@ -28,13 +28,10 @@ func DeployPackage(w http.ResponseWriter, r *http.Request) { return } - // Check if init options is empty if body.InitOpts != nil { config.InitOpts = *body.InitOpts - config.DeployOpts = body.DeployOpts - } else { - config.DeployOpts = body.DeployOpts } + config.DeployOpts = body.DeployOpts globalConfig.CommonOptions.Confirm = true