Skip to content

Commit

Permalink
Fix zarf init from UI (#1213)
Browse files Browse the repository at this point in the history
## Description

Due to the changes to `ZarfInitOptions`, `ZarfDeployOptions` and some
overarching codebase changes, the UI's backend init process is broken.
This PR fixes that flow.

- Fix init via UI
- Add docs for UI's E2E tests
- Add an E2E test to actually run init

## Related Issue

Fixes #1212 

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)

## Checklist before merging

- [x] Test, docs, adr added or updated as needed
- [x] [Contributor Guide
Steps](https://github.com/defenseunicorns/zarf/blob/main/CONTRIBUTING.md#developer-workflow)
followed
  • Loading branch information
Noxsios authored Jan 19, 2023
1 parent 0d29920 commit a36ae72
Show file tree
Hide file tree
Showing 11 changed files with 311 additions and 259 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/test-ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ jobs:
key: ${{ runner.os }}-browsers

- name: Run UI tests
run: npm test
run: >
npm run test:pre-init &&
npm run test:init &&
npm run test:post-init
- name: Save logs
if: always()
Expand Down
59 changes: 36 additions & 23 deletions docs/6-developer-guide/2-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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.

Expand All @@ -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:

Expand All @@ -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:

Expand All @@ -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.

Expand All @@ -112,3 +100,28 @@ 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
# dont forget to install dependencies
npm ci

# 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
```
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
63 changes: 13 additions & 50 deletions src/internal/api/packages/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,78 +7,41 @@ 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{}
config.IsInteractive = false

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

// 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 types.APIZarfDeployPayload

// 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
}
}
}
} 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
if body.InitOpts != nil {
config.InitOpts = *body.InitOpts
}
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)
}
17 changes: 15 additions & 2 deletions src/test/ui/01_start_page.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ test.beforeEach(async ({ page }) => {
page.on('pageerror', (err) => console.log(err.message));
});

test.describe('start page without an initialized cluster', () => {
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');
Expand All @@ -30,4 +30,17 @@ test.describe('start page without an initialized cluster', () => {

await page.waitForURL('**/initialize/configure');
});
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 });
});
});
40 changes: 38 additions & 2 deletions src/test/ui/02_initialize_cluster.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ test.beforeEach(async ({ page }) => {
});

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
Expand Down Expand Up @@ -55,11 +55,47 @@ 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 });
});
});

async function validateRequiredCheckboxes(page) {
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/03_view_packages.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', '/');
Expand Down
7 changes: 7 additions & 0 deletions src/types/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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"`
}
Loading

0 comments on commit a36ae72

Please sign in to comment.