Repository: kubernetes-sigs/kwok
Pull request #796 (2023-09-01 07:20:42)

feat: compares files with make output
<!--  Thanks for sending a pull request!  Here are some tips for you:

1. If this is your first time, please read our contributor guidelines: https://git.k8s.io/community/contributors/guide/first-contribution.md#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request. For reference on required PR/issue labels, read here:
https://git.k8s.io/community/contributors/devel/sig-release/release.md#issuepr-kind-label
3. Ensure you have added or ran the appropriate tests for your PR: https://git.k8s.io/community/contributors/devel/sig-testing/testing.md
4. If you want *faster* PR reviews, read how: https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
5. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
-->

#### What type of PR is this?

<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->

/kind cleanup

#### What this PR does / why we need it:

Uses files instead of bash functions to explictly indicate output of `make` commands.

#### Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #

#### Special notes for your reviewer:

#### Does this PR introduce a user-facing change?
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://git.k8s.io/community/contributors/guide/release-notes.md
-->
```release-note
NONE
```

#### Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:

<!--
This section can be blank if this pull request does not require a release note.

When adding links which point to resources within git repositories, like
KEPs or supporting documentation, please reference a specific commit and avoid
linking directly to the master branch. This ensures that links reference a
specific point in time, rather than a document that may change over time.

See here for guidance on getting permanent links to files: https://help.github.com/en/articles/getting-permanent-links-to-files

Please use the following format for linking documentation:
- [KEP]: <link>
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs

```


k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/kwok/pull/796#" title="Author self-approved">muma378</a>*
**Once this PR has been reviewed and has the lgtm label**, please assign [wzshiming](https://github.com/wzshiming) for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fkwok).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/kwok/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["wzshiming"]} -->
netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *k8s-kwok* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 2edab8daa99d1f90ca6d5e26ab7be6774a584bcc |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/k8s-kwok/deploys/64f190ccfc2b300008d05fad |
k8s-ci-robot: Hi @muma378. Thanks for your PR.

I'm waiting for a [kubernetes-sigs](https://github.com/orgs/kubernetes-sigs/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fkwok).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

wzshiming: /ok-to-test
k8s-ci-robot: @muma378: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-kwok-verify-main | 2edab8daa99d1f90ca6d5e26ab7be6774a584bcc | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-verify-main/1697511435933847552) | true | `/test pull-kwok-verify-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=kwok&pr=796). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amuma378). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/kwok/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @muma378: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-kwok-verify-main | 2edab8daa99d1f90ca6d5e26ab7be6774a584bcc | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-verify-main/1697511435933847552) | true | `/test pull-kwok-verify-main`
pull-kwok-e2e-test-main | 2edab8daa99d1f90ca6d5e26ab7be6774a584bcc | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-e2e-test-main/1697511436093231104) | true | `/test pull-kwok-e2e-test-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=kwok&pr=796). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amuma378). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/kwok/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @muma378: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-kwok-e2e-test-main | 2edab8daa99d1f90ca6d5e26ab7be6774a584bcc | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-e2e-test-main/1697511436093231104) | true | `/test pull-kwok-e2e-test-main`
pull-kwok-verify-main | f9808217b9fd2c6e43bf519fed4a09ea0f899240 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-verify-main/1697519362686062592) | true | `/test pull-kwok-verify-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=kwok&pr=796). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amuma378). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/kwok/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @muma378: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-kwok-verify-main | f9808217b9fd2c6e43bf519fed4a09ea0f899240 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-verify-main/1697519362686062592) | true | `/test pull-kwok-verify-main`
pull-kwok-e2e-test-main | f9808217b9fd2c6e43bf519fed4a09ea0f899240 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-e2e-test-main/1697519362832863232) | true | `/test pull-kwok-e2e-test-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=kwok&pr=796). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amuma378). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/kwok/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @muma378: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-kwok-verify-main | f9808217b9fd2c6e43bf519fed4a09ea0f899240 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-verify-main/1697519362686062592) | true | `/test pull-kwok-verify-main`
pull-kwok-e2e-test-main | f9808217b9fd2c6e43bf519fed4a09ea0f899240 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-e2e-test-main/1697519362832863232) | true | `/test pull-kwok-e2e-test-main`
pull-kwok-unit-test-main | 46b9031255b3072a9ab6f5f90226e3fdeeaa7b79 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-unit-test-main/1697535691631628288) | true | `/test pull-kwok-unit-test-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=kwok&pr=796). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amuma378). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/kwok/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @muma378: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-kwok-e2e-test-main | f9808217b9fd2c6e43bf519fed4a09ea0f899240 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-e2e-test-main/1697519362832863232) | true | `/test pull-kwok-e2e-test-main`
pull-kwok-verify-main | 381dbc0e1dbac8df9765c8b7b01048c0fd75bb3d | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-verify-main/1697536710541316096) | true | `/test pull-kwok-verify-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=kwok&pr=796). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amuma378). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/kwok/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @muma378: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-kwok-verify-main | 96d2cb3be5c444a466b52da6c2c4165818f936db | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-verify-main/1700052620665688064) | true | `/test pull-kwok-verify-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=kwok&pr=796). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amuma378). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/kwok/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @muma378: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-kwok-verify-main | 96d2cb3be5c444a466b52da6c2c4165818f936db | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-verify-main/1700052620665688064) | true | `/test pull-kwok-verify-main`
pull-kwok-e2e-test-main | 96d2cb3be5c444a466b52da6c2c4165818f936db | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-e2e-test-main/1700052620816683008) | true | `/test pull-kwok-e2e-test-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=kwok&pr=796). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amuma378). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/kwok/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @muma378: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-kwok-e2e-test-main | 96d2cb3be5c444a466b52da6c2c4165818f936db | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-e2e-test-main/1700052620816683008) | true | `/test pull-kwok-e2e-test-main`
pull-kwok-verify-main | feda2c02fdc1a0c2985bf47eb5c4f508c291896a | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_kwok/796/pull-kwok-verify-main/1700063738129813504) | true | `/test pull-kwok-verify-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=kwok&pr=796). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amuma378). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/kwok/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->

Patch:
diff --git a/go.mod b/go.mod
index d72a92ba..9381c863 100644
--- a/go.mod
+++ b/go.mod
@@ -36,10 +36,7 @@ require (
 	k8s.io/utils v0.0.0-20230726121419-3b25d923346b
 	sigs.k8s.io/controller-runtime v0.16.0
 	sigs.k8s.io/controller-tools v0.13.0
-	sigs.k8s.io/e2e-framework v0.3.0
-	sigs.k8s.io/kustomize/api v0.14.0
 	sigs.k8s.io/kustomize/kustomize/v5 v5.1.1
-	sigs.k8s.io/kustomize/kyaml v0.14.3
 	sigs.k8s.io/yaml v1.3.0
 )
 
@@ -53,7 +50,6 @@ require (
 	github.com/distribution/distribution/v3 v3.0.0-20230214150026-36d8c594d7aa // indirect
 	github.com/docker/go-connections v0.4.0 // indirect
 	github.com/evanphx/json-patch v5.6.0+incompatible // indirect
-	github.com/evanphx/json-patch/v5 v5.6.0 // indirect
 	github.com/fatih/color v1.15.0 // indirect
 	github.com/go-errors/errors v1.4.2 // indirect
 	github.com/go-logr/logr v1.2.4 // indirect
@@ -90,7 +86,6 @@ require (
 	github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/stoewer/go-strcase v1.3.0 // indirect
-	github.com/vladimirvivien/gexe v0.2.0 // indirect
 	github.com/wzshiming/trie v0.1.1 // indirect
 	github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae // indirect
 	github.com/xlab/treeprint v1.2.0 // indirect
@@ -115,6 +110,8 @@ require (
 	k8s.io/klog/v2 v2.100.1 // indirect
 	k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
 	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
+	sigs.k8s.io/kustomize/api v0.14.0 // indirect
 	sigs.k8s.io/kustomize/cmd/config v0.11.3 // indirect
+	sigs.k8s.io/kustomize/kyaml v0.14.3 // indirect
 	sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
 )
diff --git a/go.sum b/go.sum
index 54dae345..df956d57 100644
--- a/go.sum
+++ b/go.sum
@@ -34,8 +34,6 @@ github.com/emicklei/go-restful/v3 v3.10.2 h1:hIovbnmBTLjHXkqEBUz3HGpXZdM7ZrE9fJI
 github.com/emicklei/go-restful/v3 v3.10.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
 github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
 github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
-github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
-github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
 github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
 github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -48,7 +46,6 @@ github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg
 github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
 github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
 github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
 github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
 github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
@@ -106,7 +103,6 @@ github.com/itchyny/gojq v0.12.13 h1:IxyYlHYIlspQHHTE0f3cJF0NKDMfajxViuhBLnHd/QU=
 github.com/itchyny/gojq v0.12.13/go.mod h1:JzwzAqenfhrPUuwbmEz3nu3JQmFLlQTQMUcOdnu/Sf4=
 github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
 github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
-github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -157,7 +153,6 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl
 github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -192,8 +187,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
-github.com/vladimirvivien/gexe v0.2.0 h1:nbdAQ6vbZ+ZNsolCgSVb9Fno60kzSuvtzVh6Ytqi/xY=
-github.com/vladimirvivien/gexe v0.2.0/go.mod h1:LHQL00w/7gDUKIak24n801ABp8C+ni6eBht9vGVst8w=
 github.com/wzshiming/cmux v0.3.2 h1:lBEWbfbRqUDdXB6Mro/g35kvCuUEmAgIdpGEuER3bis=
 github.com/wzshiming/cmux v0.3.2/go.mod h1:lPhqJN2E3frzkxrPdjesxL09z7nTcuZ6i8Is+2G/Xw4=
 github.com/wzshiming/ctc v1.2.3 h1:q+hW3IQNsjIlOFBTGZZZeIXTElFM4grF4spW/errh/c=
@@ -212,8 +205,6 @@ go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee33
 go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
 go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
-go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
-go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -284,7 +275,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ=
@@ -351,8 +341,6 @@ sigs.k8s.io/controller-runtime v0.16.0 h1:5koYaaRVBHDr0LZAJjO5dWzUjMsh6cwa7q1Mmu
 sigs.k8s.io/controller-runtime v0.16.0/go.mod h1:77DnuwA8+J7AO0njzv3wbNlMOnGuLrwFr8JPNwx3J7g=
 sigs.k8s.io/controller-tools v0.13.0 h1:NfrvuZ4bxyolhDBt/rCZhDnx3M2hzlhgo5n3Iv2RykI=
 sigs.k8s.io/controller-tools v0.13.0/go.mod h1:5vw3En2NazbejQGCeWKRrE7q4P+CW8/klfVqP8QZkgA=
-sigs.k8s.io/e2e-framework v0.3.0 h1:eqQALBtPCth8+ulTs6lcPK7ytV5rZSSHJzQHZph4O7U=
-sigs.k8s.io/e2e-framework v0.3.0/go.mod h1:C+ef37/D90Dc7Xq1jQnNbJYscrUGpxrWog9bx2KIa+c=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
 sigs.k8s.io/kustomize/api v0.14.0 h1:6+QLmXXA8X4eDM7ejeaNUyruA1DDB3PVIjbpVhDOJRA=


Test patch:
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 613dd5ed..d8f45603 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -68,25 +68,6 @@ jobs:
         run: |
           BUILDER=${{ matrix.builder }} make build-cluster-image
 
-  test-kwok-e2e:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v3
-    - name: Set up Go
-      uses: actions/setup-go@v4
-      with:
-        go-version: "1.20"
-
-    - name: Test e2e default
-      shell: bash
-      run: |
-        ./hack/e2e-test.sh e2e/kwok/default
-
-    - name: Test e2e single
-      shell: bash
-      run: |
-        ./hack/e2e-test.sh e2e/kwok/single
-
   test-kwok:
     strategy:
       fail-fast: false
@@ -94,6 +75,7 @@ jobs:
         case:
           - kwok
           - kwok-with-cni
+          - kwok-single-node
     continue-on-error: false
     runs-on: ubuntu-latest
     steps:
@@ -281,11 +263,6 @@ jobs:
         run: |
           ./hack/e2e-test.sh kwokctl/kwokctl_with_dryrun
 
-      - name: Test e2e
-        shell: bash
-        run: |
-          ./hack/e2e-test.sh e2e/kwokctl/${{ matrix.kwokctl-runtime }}
-
       - name: Test Export Logs
         shell: bash
         run: |
diff --git a/hack/e2e-test.sh b/hack/e2e-test.sh
index a941c84d..a7bfcf0f 100755
--- a/hack/e2e-test.sh
+++ b/hack/e2e-test.sh
@@ -28,9 +28,6 @@ function all_cases() {
     cases="$(echo "${cases}" | grep -v "${skip}" || :)"
   done
   echo "${cases}"
-  go test -json -v sigs.k8s.io/kwok/test/e2e/... -args --dry-run |
-    jq -r '. | select( .Action == "pass" ) | select( .Test == null ) | .Package' |
-    sed 's|^sigs.k8s.io/kwok/test/||g'
 }
 
 function usage() {
@@ -76,19 +73,6 @@ function main() {
   local failed=()
   for target in "${TARGETS[@]}"; do
     echo "================================================================================"
-    if [[ "${target}" == "e2e/"* ]]; then
-      echo "Testing ${target}..."
-      if ! go test -v "sigs.k8s.io/kwok/test/${target}" -args --v=6; then
-        failed+=("${target}")
-        echo "------------------------------"
-        echo "Test ${target} failed."
-      else
-        echo "------------------------------"
-        echo "Test ${target} passed."
-      fi
-      continue
-    fi
-
     target="${target%.test.sh}"
     test="${test_dir}/${target}.test.sh"
     if [[ ! -x "${test}" ]]; then
diff --git a/test/e2e/cases.go b/test/e2e/cases.go
deleted file mode 100644
index 0fb5348d..00000000
--- a/test/e2e/cases.go
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package e2e
-
-import (
-	"sigs.k8s.io/e2e-framework/pkg/features"
-
-	"sigs.k8s.io/kwok/test/e2e/helper"
-)
-
-// CaseNode creates a feature that tests node creation and deletion
-func CaseNode(nodeName string) *features.FeatureBuilder {
-	node := helper.NewNodeBuilder(nodeName).
-		Build()
-	return features.New("Node can be created and deleted").
-		Assess("create node", helper.CreateNode(node)).
-		Assess("delete node", helper.DeleteNode(node))
-}
-
-// CasePod creates a feature that tests pod creation and deletion
-func CasePod(nodeName string, namespace string) *features.FeatureBuilder {
-	node := helper.NewNodeBuilder(nodeName).
-		Build()
-	pod0 := helper.NewPodBuilder("pod0").
-		WithNamespace(namespace).
-		WithNodeName(nodeName).
-		Build()
-	pod1 := helper.NewPodBuilder("pod1").
-		WithNamespace(namespace).
-		WithNodeName(nodeName).
-		WithHostNetwork(true).
-		Build()
-	return features.New("Pod can be created and deleted").
-		Setup(helper.CreateNode(node)).
-		Teardown(helper.DeleteNode(node)).
-		Assess("create pod", helper.CreatePod(pod0)).
-		Assess("create pod for host network", helper.CreatePod(pod1)).
-		Assess("delete pod", helper.DeletePod(pod0)).
-		Assess("delete pod for host network", helper.DeletePod(pod1))
-}
diff --git a/test/e2e/doc.go b/test/e2e/doc.go
deleted file mode 100644
index 818710d1..00000000
--- a/test/e2e/doc.go
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-// Package e2e is a test package for e2e test.
-package e2e
diff --git a/test/e2e/helper/build.go b/test/e2e/helper/build.go
deleted file mode 100644
index 6b3986b3..00000000
--- a/test/e2e/helper/build.go
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package helper
-
-import (
-	"context"
-	"fmt"
-	"runtime"
-	"strings"
-
-	"sigs.k8s.io/e2e-framework/pkg/env"
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-
-	"sigs.k8s.io/kwok/pkg/utils/exec"
-)
-
-// BuildKwokImage builds the kwok image and returns a function that can be used
-func BuildKwokImage(rootDir string, image string, builder string) env.Func {
-	return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
-		ref := strings.SplitN(image, ":", 2)
-		if len(ref) != 2 {
-			return nil, fmt.Errorf("invalid image reference %q", image)
-		}
-		ctx = exec.WithStdIO(ctx)
-		ctx = exec.WithDir(ctx, rootDir)
-
-		err := exec.Exec(ctx, "bash", "./hack/releases.sh", "--bin", "kwok", "--platform", "linux/"+runtime.GOARCH)
-		if err != nil {
-			return nil, err
-		}
-
-		err = exec.Exec(ctx, "bash", "./images/kwok/build.sh", "--image", ref[0], "--builder", builder, "--version", ref[1], "--platform", "linux/"+runtime.GOARCH)
-		if err != nil {
-			return nil, err
-		}
-		return ctx, nil
-	}
-}
-
-// BuildKwokBinary builds the kwok binary and returns a function that can be used
-func BuildKwokBinary(rootDir string) env.Func {
-	return buildBinary(rootDir, "kwok")
-}
-
-// BuildKwokctlBinary builds the kwokctl binary and returns a function that can be used
-func BuildKwokctlBinary(rootDir string) env.Func {
-	return buildBinary(rootDir, "kwokctl")
-}
-
-// buildBinary builds the kwok binary and returns a function that can be used
-func buildBinary(rootDir string, binary string) env.Func {
-	return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
-		ctx = exec.WithStdIO(ctx)
-		ctx = exec.WithDir(ctx, rootDir)
-
-		err := exec.Exec(ctx, "bash", "./hack/releases.sh", "--bin", binary, "--platform", runtime.GOOS+"/"+runtime.GOARCH)
-		if err != nil {
-			return nil, err
-		}
-		return ctx, nil
-	}
-}
-
-// BinSuffix is the suffix for binaries on Windows
-var BinSuffix string
-
-func init() {
-	if runtime.GOOS == "windows" {
-		BinSuffix = ".exe"
-	}
-}
diff --git a/test/e2e/helper/doc.go b/test/e2e/helper/doc.go
deleted file mode 100644
index 94a9aeec..00000000
--- a/test/e2e/helper/doc.go
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-// Package helper provides tools for e2e test.
-package helper
diff --git a/test/e2e/helper/kustomize.go b/test/e2e/helper/kustomize.go
deleted file mode 100644
index 7882f6b3..00000000
--- a/test/e2e/helper/kustomize.go
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package helper
-
-import (
-	"bytes"
-	"context"
-
-	"sigs.k8s.io/e2e-framework/klient/decoder"
-	"sigs.k8s.io/e2e-framework/klient/k8s/resources"
-	"sigs.k8s.io/e2e-framework/pkg/env"
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-	"sigs.k8s.io/kustomize/api/krusty"
-	"sigs.k8s.io/kustomize/kyaml/filesys"
-)
-
-// CreateByKustomize applies kustomize to the cluster
-func CreateByKustomize(kustomizeDir string) env.Func {
-	return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
-		r, err := resources.New(cfg.Client().RESTConfig())
-		if err != nil {
-			return ctx, err
-		}
-		initYAML, err := buildKustomizeToYaml(kustomizeDir)
-		if err != nil {
-			return nil, err
-		}
-
-		err = decoder.DecodeEach(ctx, bytes.NewReader(initYAML), decoder.CreateHandler(r))
-		if err != nil {
-			return ctx, err
-		}
-		return ctx, nil
-	}
-}
-
-// DeleteByKustomize deletes kustomize from the cluster
-func DeleteByKustomize(kustomizeDir string) env.Func {
-	return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
-		r, err := resources.New(cfg.Client().RESTConfig())
-		if err != nil {
-			return ctx, err
-		}
-		initYAML, err := buildKustomizeToYaml(kustomizeDir)
-		if err != nil {
-			return nil, err
-		}
-
-		err = decoder.DecodeEach(ctx, bytes.NewReader(initYAML), decoder.DeleteHandler(r))
-		if err != nil {
-			return ctx, err
-		}
-		return ctx, nil
-	}
-}
-
-// buildKustomizeToYaml builds kustomize to yaml
-func buildKustomizeToYaml(dir string) ([]byte, error) {
-	k := krusty.MakeKustomizer(krusty.MakeDefaultOptions())
-	fs := filesys.MakeFsOnDisk()
-
-	objs, err := k.Run(fs, dir)
-	if err != nil {
-		return nil, err
-	}
-
-	yaml, err := objs.AsYaml()
-	if err != nil {
-		return nil, err
-	}
-	return yaml, err
-}
diff --git a/test/e2e/helper/node.go b/test/e2e/helper/node.go
deleted file mode 100644
index 47dde300..00000000
--- a/test/e2e/helper/node.go
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package helper
-
-import (
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
-// NodeBuilder is a builder to build a node.
-type NodeBuilder struct {
-	node *corev1.Node
-}
-
-// NewNodeBuilder will create a node builder.
-func NewNodeBuilder(name string) *NodeBuilder {
-	return &NodeBuilder{
-		node: &corev1.Node{
-			ObjectMeta: metav1.ObjectMeta{
-				Name: name,
-				Annotations: map[string]string{
-					"kwok.x-k8s.io/node": "fake",
-				},
-				Labels: map[string]string{
-					"type": "kwok",
-				},
-			},
-		},
-	}
-}
-
-// WithPodCIDR will set podCIDR for node.
-func (b NodeBuilder) WithPodCIDR(podCIDR string) *NodeBuilder {
-	b.node.Spec.PodCIDR = podCIDR
-	return &b
-}
-
-// Build will build a node.
-func (b NodeBuilder) Build() *corev1.Node {
-	return b.node.DeepCopy()
-}
diff --git a/test/e2e/helper/pod.go b/test/e2e/helper/pod.go
deleted file mode 100644
index 42efaaa5..00000000
--- a/test/e2e/helper/pod.go
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package helper
-
-import (
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
-// PodBuilder is a builder for pod.
-type PodBuilder struct {
-	pod *corev1.Pod
-}
-
-// NewPodBuilder will create a pod builder.
-func NewPodBuilder(name string) *PodBuilder {
-	return &PodBuilder{
-		pod: &corev1.Pod{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      name,
-				Namespace: "default",
-			},
-			Spec: corev1.PodSpec{
-				Containers: []corev1.Container{{
-					Name:  "container",
-					Image: "image",
-				}},
-			},
-		},
-	}
-}
-
-// WithHostNetwork will set host network for pod.
-func (b PodBuilder) WithHostNetwork(hostNetwork bool) *PodBuilder {
-	b.pod.Spec.HostNetwork = hostNetwork
-	return &b
-}
-
-// WithNamespace will set namespace for pod.
-func (b PodBuilder) WithNamespace(namespace string) *PodBuilder {
-	b.pod.ObjectMeta.Namespace = namespace
-	return &b
-}
-
-// WithNodeName will set node name for pod.
-func (b PodBuilder) WithNodeName(nodeName string) *PodBuilder {
-	b.pod.Spec.NodeName = nodeName
-	return &b
-}
-
-// Build will build a pod.
-func (b PodBuilder) Build() *corev1.Pod {
-	return b.pod.DeepCopy()
-}
diff --git a/test/e2e/helper/utils.go b/test/e2e/helper/utils.go
deleted file mode 100644
index 9d07d836..00000000
--- a/test/e2e/helper/utils.go
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package helper
-
-import (
-	"context"
-	"fmt"
-	"net"
-	"testing"
-	"time"
-
-	corev1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/api/meta"
-	"sigs.k8s.io/e2e-framework/klient/k8s"
-	"sigs.k8s.io/e2e-framework/klient/k8s/resources"
-	"sigs.k8s.io/e2e-framework/klient/wait"
-	"sigs.k8s.io/e2e-framework/klient/wait/conditions"
-	"sigs.k8s.io/e2e-framework/pkg/env"
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-	"sigs.k8s.io/e2e-framework/pkg/features"
-
-	"sigs.k8s.io/kwok/pkg/log"
-	"sigs.k8s.io/kwok/pkg/utils/slices"
-)
-
-// nodeIsReady returns a function that checks if a node is ready
-func nodeIsReady(name string) func(obj k8s.Object) bool {
-	return func(obj k8s.Object) bool {
-		node := obj.(*corev1.Node)
-		if node.Name != name {
-			return false
-		}
-		cond, ok := slices.Find(node.Status.Conditions, func(cond corev1.NodeCondition) bool {
-			return cond.Type == corev1.NodeReady
-		})
-		if ok && cond.Status == corev1.ConditionTrue {
-			return true
-		}
-		return true
-	}
-}
-
-// CreateNode creates a node and waits for it to be ready
-func CreateNode(node *corev1.Node) features.Func {
-	return func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
-		client, err := resources.New(c.Client().RESTConfig())
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		t.Log("creating node", node.Name)
-		err = client.Create(ctx, node)
-		if err != nil {
-			t.Fatal(err)
-		}
-		t.Log("waiting for node to be ready", node.Name)
-		err = wait.For(
-			conditions.New(client).ResourceMatch(node, nodeIsReady(node.Name)),
-			wait.WithContext(ctx),
-			wait.WithTimeout(600*time.Second),
-		)
-		if err != nil {
-			t.Fatal(err)
-		}
-		t.Log("node is ready", node.Name)
-		return ctx
-	}
-}
-
-// DeleteNode deletes a node
-func DeleteNode(node *corev1.Node) features.Func {
-	return func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
-		client, err := resources.New(c.Client().RESTConfig())
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		t.Log("deleting node", node.Name)
-		err = client.Delete(ctx, node)
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		err = wait.For(
-			conditions.New(client).ResourceDeleted(node),
-			wait.WithContext(ctx),
-			wait.WithTimeout(600*time.Second),
-		)
-		if err != nil {
-			t.Fatal(err)
-		}
-		return ctx
-	}
-}
-
-// CreatePod creates a pod and waits for it to be ready
-func CreatePod(pod *corev1.Pod) features.Func {
-	return func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
-		client, err := resources.New(c.Client().RESTConfig())
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		t.Log("creating pod", log.KObj(pod))
-		err = client.Create(ctx, pod)
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		t.Log("waiting for pod to be ready", log.KObj(pod))
-		err = wait.For(
-			conditions.New(client).PodConditionMatch(pod, corev1.PodReady, corev1.ConditionTrue),
-			wait.WithContext(ctx),
-			wait.WithTimeout(600*time.Second),
-		)
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		err = client.Get(ctx, pod.GetName(), pod.GetNamespace(), pod)
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		if pod.Spec.NodeName == "" {
-			t.Fatal("pod node name is empty", log.KObj(pod))
-		}
-
-		if pod.Status.PodIP != "" {
-			if pod.Spec.HostNetwork {
-				if pod.Status.PodIP != pod.Status.HostIP {
-					t.Fatal("pod ip is not equal to host ip", log.KObj(pod))
-				}
-			} else {
-				if pod.Status.PodIP == pod.Status.HostIP {
-					t.Fatal("pod ip is equal to host ip", log.KObj(pod))
-				}
-
-				var node corev1.Node
-				err = client.Get(ctx, pod.Spec.NodeName, "", &node)
-				if err != nil {
-					t.Fatal(err)
-				}
-
-				if node.Spec.PodCIDR != "" {
-					_, ipnet, err := net.ParseCIDR(node.Spec.PodCIDR)
-					if err != nil {
-						t.Fatal(err)
-					}
-
-					ip := net.ParseIP(pod.Status.PodIP)
-					if !ipnet.Contains(ip) {
-						t.Fatal("pod ip is not in pod cidr", log.KObj(pod))
-					}
-				}
-			}
-		}
-
-		t.Log("pod is ready", log.KObj(pod))
-		return ctx
-	}
-}
-
-// DeletePod deletes a pod
-func DeletePod(pod *corev1.Pod) features.Func {
-	return func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context {
-		client, err := resources.New(c.Client().RESTConfig())
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		t.Log("deleting pod", log.KObj(pod))
-		err = client.Delete(ctx, pod)
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		err = wait.For(
-			conditions.New(client).ResourceDeleted(pod),
-			wait.WithContext(ctx),
-			wait.WithTimeout(600*time.Second),
-		)
-		if err != nil {
-			t.Fatal(err)
-		}
-		return ctx
-	}
-}
-
-// WaitForAllNodesReady waits for all nodes to be ready
-func WaitForAllNodesReady() env.Func {
-	return func(ctx context.Context, c *envconf.Config) (context.Context, error) {
-		client, err := resources.New(c.Client().RESTConfig())
-		if err != nil {
-			return nil, err
-		}
-
-		var list corev1.NodeList
-		err = wait.For(
-			func(ctx context.Context) (done bool, err error) {
-				if err = client.List(ctx, &list); err != nil {
-					return false, err
-				}
-				var found int
-				metaList, err := meta.ExtractList(&list)
-				if err != nil {
-					return false, err
-				}
-				if len(metaList) == 0 {
-					return false, fmt.Errorf("no node found")
-				}
-				for _, obj := range metaList {
-					node := obj.(*corev1.Node)
-					cond, ok := slices.Find(node.Status.Conditions, func(cond corev1.NodeCondition) bool {
-						return cond.Type == corev1.NodeReady
-					})
-					if ok && cond.Status == corev1.ConditionTrue {
-						found++
-					}
-				}
-				return found == len(metaList), nil
-			},
-			wait.WithContext(ctx),
-			wait.WithTimeout(600*time.Second),
-		)
-		if err != nil {
-			return nil, err
-		}
-
-		return ctx, nil
-	}
-}
-
-// WaitForAllPodsReady waits for all pods to be ready
-func WaitForAllPodsReady() env.Func {
-	return func(ctx context.Context, c *envconf.Config) (context.Context, error) {
-		client, err := resources.New(c.Client().RESTConfig())
-		if err != nil {
-			return nil, err
-		}
-
-		var list corev1.PodList
-		err = wait.For(
-			func(ctx context.Context) (done bool, err error) {
-				if err = client.List(ctx, &list); err != nil {
-					return false, err
-				}
-				var found int
-				metaList, err := meta.ExtractList(&list)
-				if err != nil {
-					return false, err
-				}
-				if len(metaList) == 0 {
-					return false, fmt.Errorf("no pod found")
-				}
-				for _, obj := range metaList {
-					pod := obj.(*corev1.Pod)
-					if pod.Status.Phase == corev1.PodRunning || pod.Status.Phase == corev1.PodSucceeded {
-						found++
-					}
-				}
-				return found == len(metaList), nil
-			},
-			wait.WithContext(ctx),
-			wait.WithTimeout(600*time.Second),
-		)
-		if err != nil {
-			return nil, err
-		}
-
-		return ctx, nil
-	}
-}
diff --git a/test/e2e/kwok/default/kustomization.yaml b/test/e2e/kwok/default/kustomization.yaml
deleted file mode 100644
index 0096f808..00000000
--- a/test/e2e/kwok/default/kustomization.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-
-images:
-- name: registry.k8s.io/kwok/kwok
-  newName: localhost/kwok
-  newTag: test
-
-resources:
-- ../../../../kustomize/kwok
diff --git a/test/e2e/kwok/default/kwok_test.go b/test/e2e/kwok/default/kwok_test.go
deleted file mode 100644
index 9616eceb..00000000
--- a/test/e2e/kwok/default/kwok_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package default_test
-
-import (
-	"testing"
-
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-
-	"sigs.k8s.io/kwok/test/e2e"
-)
-
-func TestNode(t *testing.T) {
-	f0 := e2e.CaseNode(envconf.RandomName("node", 16)).
-		Feature()
-	testEnv.Test(t, f0)
-}
-
-func TestPod(t *testing.T) {
-	f0 := e2e.CasePod(envconf.RandomName("node", 16), namespace).
-		Feature()
-	testEnv.Test(t, f0)
-}
diff --git a/test/e2e/kwok/default/main_test.go b/test/e2e/kwok/default/main_test.go
deleted file mode 100644
index e9f6335e..00000000
--- a/test/e2e/kwok/default/main_test.go
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-// Package default_test is a test environment for kwok.
-package default_test
-
-import (
-	"context"
-	"os"
-	"testing"
-
-	"sigs.k8s.io/e2e-framework/pkg/env"
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-	"sigs.k8s.io/e2e-framework/pkg/envfuncs"
-	"sigs.k8s.io/e2e-framework/support/kind"
-
-	"sigs.k8s.io/kwok/pkg/consts"
-	"sigs.k8s.io/kwok/pkg/utils/path"
-	"sigs.k8s.io/kwok/test/e2e/helper"
-)
-
-var (
-	testEnv     env.Environment
-	pwd         = os.Getenv("PWD")
-	rootDir     = path.Join(pwd, "../../../..")
-	clusterName = envconf.RandomName("kwok-e2e", 16)
-	namespace   = envconf.RandomName("ns", 16)
-	testImage   = "localhost/kwok:test"
-)
-
-func TestMain(m *testing.M) {
-	ctx := context.Background()
-	cfg, _ := envconf.NewFromFlags()
-
-	testEnv, _ = env.NewWithContext(ctx, cfg)
-	deploy := pwd
-	crs := path.Join(rootDir, "kustomize/stage/fast")
-	testEnv.Setup(
-		helper.BuildKwokImage(rootDir, testImage, consts.RuntimeTypeDocker),
-		envfuncs.CreateCluster(kind.NewProvider(), clusterName),
-		helper.WaitForAllNodesReady(),
-		envfuncs.LoadImageToCluster(clusterName, testImage),
-		helper.CreateByKustomize(deploy),
-		helper.WaitForAllPodsReady(),
-		helper.CreateByKustomize(crs),
-		envfuncs.CreateNamespace(namespace),
-	)
-	testEnv.Finish(
-		envfuncs.DestroyCluster(clusterName),
-	)
-	os.Exit(testEnv.Run(m))
-}
diff --git a/test/e2e/kwok/single/kwok_test.go b/test/e2e/kwok/single/kwok_test.go
deleted file mode 100644
index 4a4f41c3..00000000
--- a/test/e2e/kwok/single/kwok_test.go
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package single_test
-
-import (
-	"testing"
-
-	"sigs.k8s.io/kwok/test/e2e"
-)
-
-func TestNode(t *testing.T) {
-	f0 := e2e.CaseNode("node").
-		Feature()
-	testEnv.Test(t, f0)
-}
-
-func TestPod(t *testing.T) {
-	f0 := e2e.CasePod("node", namespace).
-		Feature()
-	testEnv.Test(t, f0)
-}
diff --git a/test/e2e/kwok/single/main_test.go b/test/e2e/kwok/single/main_test.go
deleted file mode 100644
index a12213be..00000000
--- a/test/e2e/kwok/single/main_test.go
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-// Package single_test is a test environment for kwok.
-package single_test
-
-import (
-	"context"
-	"os"
-	"testing"
-
-	"sigs.k8s.io/e2e-framework/pkg/env"
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-	"sigs.k8s.io/e2e-framework/pkg/envfuncs"
-	"sigs.k8s.io/e2e-framework/support/kind"
-
-	"sigs.k8s.io/kwok/pkg/consts"
-	"sigs.k8s.io/kwok/pkg/utils/path"
-	"sigs.k8s.io/kwok/test/e2e/helper"
-)
-
-var (
-	testEnv     env.Environment
-	pwd         = os.Getenv("PWD")
-	rootDir     = path.Join(pwd, "../../../..")
-	clusterName = envconf.RandomName("kwok-e2e", 16)
-	namespace   = envconf.RandomName("ns", 16)
-	testImage   = "localhost/kwok:test"
-)
-
-func TestMain(m *testing.M) {
-	ctx := context.Background()
-	cfg, _ := envconf.NewFromFlags()
-
-	testEnv, _ = env.NewWithContext(ctx, cfg)
-	deploy := pwd
-	crs := path.Join(rootDir, "kustomize/stage/fast")
-	testEnv.Setup(
-		helper.BuildKwokImage(rootDir, testImage, consts.RuntimeTypeDocker),
-		envfuncs.CreateCluster(kind.NewProvider(), clusterName),
-		helper.WaitForAllNodesReady(),
-		envfuncs.LoadImageToCluster(clusterName, testImage),
-		helper.CreateByKustomize(deploy),
-		helper.WaitForAllPodsReady(),
-		helper.CreateByKustomize(crs),
-		envfuncs.CreateNamespace(namespace),
-	)
-	testEnv.Finish(
-		envfuncs.DestroyCluster(clusterName),
-	)
-	os.Exit(testEnv.Run(m))
-}
diff --git a/test/e2e/kwokctl/binary/kwok_test.go b/test/e2e/kwokctl/binary/kwok_test.go
deleted file mode 100644
index 8eef7e39..00000000
--- a/test/e2e/kwokctl/binary/kwok_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package binary_test
-
-import (
-	"testing"
-
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-
-	"sigs.k8s.io/kwok/test/e2e"
-)
-
-func TestNode(t *testing.T) {
-	f0 := e2e.CaseNode(envconf.RandomName("node", 16)).
-		Feature()
-	testEnv.Test(t, f0)
-}
-
-func TestPod(t *testing.T) {
-	f0 := e2e.CasePod(envconf.RandomName("node", 16), namespace).
-		Feature()
-	testEnv.Test(t, f0)
-}
diff --git a/test/e2e/kwokctl/binary/main_test.go b/test/e2e/kwokctl/binary/main_test.go
deleted file mode 100644
index 79277a61..00000000
--- a/test/e2e/kwokctl/binary/main_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-// Package binary_test is a test environment for kwok.
-package binary_test
-
-import (
-	"context"
-	"os"
-	"runtime"
-	"testing"
-
-	"sigs.k8s.io/e2e-framework/pkg/env"
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-	"sigs.k8s.io/e2e-framework/pkg/envfuncs"
-	"sigs.k8s.io/e2e-framework/support/kwok"
-
-	"sigs.k8s.io/kwok/pkg/consts"
-	"sigs.k8s.io/kwok/pkg/utils/path"
-	"sigs.k8s.io/kwok/test/e2e/helper"
-)
-
-var (
-	testEnv     env.Environment
-	pwd         = os.Getenv("PWD")
-	rootDir     = path.Join(pwd, "../../../..")
-	clusterName = envconf.RandomName("kwok-e2e", 16)
-	namespace   = envconf.RandomName("ns", 16)
-)
-
-func init() {
-	_ = os.Setenv("KWOK_WORKDIR", path.Join(rootDir, "workdir"))
-}
-
-func TestMain(m *testing.M) {
-	ctx := context.Background()
-	cfg, _ := envconf.NewFromFlags()
-
-	testEnv, _ = env.NewWithContext(ctx, cfg)
-
-	kwokPath := path.Join(rootDir, "bin", runtime.GOOS, runtime.GOARCH, "kwok"+helper.BinSuffix)
-	kwokctlPath := path.Join(rootDir, "bin", runtime.GOOS, runtime.GOARCH, "kwokctl"+helper.BinSuffix)
-
-	k := kwok.NewProvider().
-		WithName(clusterName).
-		WithPath(kwokctlPath)
-	testEnv.Setup(
-		helper.BuildKwokBinary(rootDir),
-		helper.BuildKwokctlBinary(rootDir),
-		func(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
-			kubecfg, err := k.Create(ctx,
-				"--kwok-controller-binary", kwokPath,
-				"--runtime", consts.RuntimeTypeBinary,
-			)
-			if err != nil {
-				return ctx, err
-			}
-
-			cfg.WithKubeconfigFile(kubecfg)
-
-			if err := k.WaitForControlPlane(ctx, cfg.Client()); err != nil {
-				return ctx, err
-			}
-
-			return ctx, nil
-		},
-		envfuncs.CreateNamespace(namespace),
-	)
-	testEnv.Finish(
-		func(ctx context.Context, config *envconf.Config) (context.Context, error) {
-			err := k.Destroy(ctx)
-			if err != nil {
-				return ctx, err
-			}
-
-			return ctx, nil
-		},
-	)
-	os.Exit(testEnv.Run(m))
-}
diff --git a/test/e2e/kwokctl/docker/kwok_test.go b/test/e2e/kwokctl/docker/kwok_test.go
deleted file mode 100644
index 958b5f04..00000000
--- a/test/e2e/kwokctl/docker/kwok_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package docker_test
-
-import (
-	"testing"
-
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-
-	"sigs.k8s.io/kwok/test/e2e"
-)
-
-func TestNode(t *testing.T) {
-	f0 := e2e.CaseNode(envconf.RandomName("node", 16)).
-		Feature()
-	testEnv.Test(t, f0)
-}
-
-func TestPod(t *testing.T) {
-	f0 := e2e.CasePod(envconf.RandomName("node", 16), namespace).
-		Feature()
-	testEnv.Test(t, f0)
-}
diff --git a/test/e2e/kwokctl/docker/main_test.go b/test/e2e/kwokctl/docker/main_test.go
deleted file mode 100644
index 19ba52ba..00000000
--- a/test/e2e/kwokctl/docker/main_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-// Package docker_test is a test environment for kwok.
-package docker_test
-
-import (
-	"context"
-	"os"
-	"runtime"
-	"testing"
-
-	"sigs.k8s.io/e2e-framework/pkg/env"
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-	"sigs.k8s.io/e2e-framework/pkg/envfuncs"
-	"sigs.k8s.io/e2e-framework/support/kwok"
-
-	"sigs.k8s.io/kwok/pkg/consts"
-	"sigs.k8s.io/kwok/pkg/utils/path"
-	"sigs.k8s.io/kwok/test/e2e/helper"
-)
-
-var (
-	testEnv     env.Environment
-	pwd         = os.Getenv("PWD")
-	rootDir     = path.Join(pwd, "../../../..")
-	clusterName = envconf.RandomName("kwok-e2e", 16)
-	namespace   = envconf.RandomName("ns", 16)
-	testImage   = "localhost/kwok:test"
-)
-
-func init() {
-	_ = os.Setenv("KWOK_WORKDIR", path.Join(rootDir, "workdir"))
-}
-
-func TestMain(m *testing.M) {
-	ctx := context.Background()
-	cfg, _ := envconf.NewFromFlags()
-
-	testEnv, _ = env.NewWithContext(ctx, cfg)
-
-	kwokctlPath := path.Join(rootDir, "bin", runtime.GOOS, runtime.GOARCH, "kwokctl"+helper.BinSuffix)
-
-	k := kwok.NewProvider().
-		WithName(clusterName).
-		WithPath(kwokctlPath)
-	testEnv.Setup(
-		helper.BuildKwokImage(rootDir, testImage, consts.RuntimeTypeDocker),
-		helper.BuildKwokctlBinary(rootDir),
-		func(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
-			kubecfg, err := k.Create(ctx,
-				"--kwok-controller-image", testImage,
-				"--runtime", consts.RuntimeTypeDocker,
-			)
-			if err != nil {
-				return ctx, err
-			}
-
-			cfg.WithKubeconfigFile(kubecfg)
-
-			if err := k.WaitForControlPlane(ctx, cfg.Client()); err != nil {
-				return ctx, err
-			}
-
-			return ctx, nil
-		},
-		envfuncs.CreateNamespace(namespace),
-	)
-	testEnv.Finish(
-		func(ctx context.Context, config *envconf.Config) (context.Context, error) {
-			err := k.Destroy(ctx)
-			if err != nil {
-				return ctx, err
-			}
-
-			return ctx, nil
-		},
-	)
-	os.Exit(testEnv.Run(m))
-}
diff --git a/test/e2e/kwokctl/kind-podman/kwok_test.go b/test/e2e/kwokctl/kind-podman/kwok_test.go
deleted file mode 100644
index 95dc8489..00000000
--- a/test/e2e/kwokctl/kind-podman/kwok_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package kind_podman_test
-
-import (
-	"testing"
-
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-
-	"sigs.k8s.io/kwok/test/e2e"
-)
-
-func TestNode(t *testing.T) {
-	f0 := e2e.CaseNode(envconf.RandomName("node", 16)).
-		Feature()
-	testEnv.Test(t, f0)
-}
-
-func TestPod(t *testing.T) {
-	f0 := e2e.CasePod(envconf.RandomName("node", 16), namespace).
-		Feature()
-	testEnv.Test(t, f0)
-}
diff --git a/test/e2e/kwokctl/kind-podman/main_test.go b/test/e2e/kwokctl/kind-podman/main_test.go
deleted file mode 100644
index 3119f1d3..00000000
--- a/test/e2e/kwokctl/kind-podman/main_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-// Package kind_podman_test is a test environment for kwok.
-package kind_podman_test
-
-import (
-	"context"
-	"os"
-	"runtime"
-	"testing"
-
-	"sigs.k8s.io/e2e-framework/pkg/env"
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-	"sigs.k8s.io/e2e-framework/pkg/envfuncs"
-	"sigs.k8s.io/e2e-framework/support/kwok"
-
-	"sigs.k8s.io/kwok/pkg/consts"
-	"sigs.k8s.io/kwok/pkg/utils/path"
-	"sigs.k8s.io/kwok/test/e2e/helper"
-)
-
-var (
-	testEnv     env.Environment
-	pwd         = os.Getenv("PWD")
-	rootDir     = path.Join(pwd, "../../../..")
-	clusterName = envconf.RandomName("kwok-e2e", 16)
-	namespace   = envconf.RandomName("ns", 16)
-	testImage   = "localhost/kwok:test"
-)
-
-func init() {
-	_ = os.Setenv("KWOK_WORKDIR", path.Join(rootDir, "workdir"))
-}
-
-func TestMain(m *testing.M) {
-	ctx := context.Background()
-	cfg, _ := envconf.NewFromFlags()
-
-	testEnv, _ = env.NewWithContext(ctx, cfg)
-
-	kwokctlPath := path.Join(rootDir, "bin", runtime.GOOS, runtime.GOARCH, "kwokctl"+helper.BinSuffix)
-
-	k := kwok.NewProvider().
-		WithName(clusterName).
-		WithPath(kwokctlPath)
-	testEnv.Setup(
-		helper.BuildKwokImage(rootDir, testImage, consts.RuntimeTypePodman),
-		helper.BuildKwokctlBinary(rootDir),
-		func(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
-			kubecfg, err := k.Create(ctx,
-				"--kwok-controller-image", testImage,
-				"--runtime", consts.RuntimeTypeKindPodman,
-			)
-			if err != nil {
-				return ctx, err
-			}
-
-			cfg.WithKubeconfigFile(kubecfg)
-
-			if err := k.WaitForControlPlane(ctx, cfg.Client()); err != nil {
-				return ctx, err
-			}
-
-			return ctx, nil
-		},
-		envfuncs.CreateNamespace(namespace),
-	)
-	testEnv.Finish(
-		func(ctx context.Context, config *envconf.Config) (context.Context, error) {
-			err := k.Destroy(ctx)
-			if err != nil {
-				return ctx, err
-			}
-
-			return ctx, nil
-		},
-	)
-	os.Exit(testEnv.Run(m))
-}
diff --git a/test/e2e/kwokctl/kind/kwok_test.go b/test/e2e/kwokctl/kind/kwok_test.go
deleted file mode 100644
index 10b3a33f..00000000
--- a/test/e2e/kwokctl/kind/kwok_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package kind_test
-
-import (
-	"testing"
-
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-
-	"sigs.k8s.io/kwok/test/e2e"
-)
-
-func TestNode(t *testing.T) {
-	f0 := e2e.CaseNode(envconf.RandomName("node", 16)).
-		Feature()
-	testEnv.Test(t, f0)
-}
-
-func TestPod(t *testing.T) {
-	f0 := e2e.CasePod(envconf.RandomName("node", 16), namespace).
-		Feature()
-	testEnv.Test(t, f0)
-}
diff --git a/test/e2e/kwokctl/kind/main_test.go b/test/e2e/kwokctl/kind/main_test.go
deleted file mode 100644
index 255d7200..00000000
--- a/test/e2e/kwokctl/kind/main_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-// Package kind_test is a test environment for kwok.
-package kind_test
-
-import (
-	"context"
-	"os"
-	"runtime"
-	"testing"
-
-	"sigs.k8s.io/e2e-framework/pkg/env"
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-	"sigs.k8s.io/e2e-framework/pkg/envfuncs"
-	"sigs.k8s.io/e2e-framework/support/kwok"
-
-	"sigs.k8s.io/kwok/pkg/consts"
-	"sigs.k8s.io/kwok/pkg/utils/path"
-	"sigs.k8s.io/kwok/test/e2e/helper"
-)
-
-var (
-	testEnv     env.Environment
-	pwd         = os.Getenv("PWD")
-	rootDir     = path.Join(pwd, "../../../..")
-	clusterName = envconf.RandomName("kwok-e2e", 16)
-	namespace   = envconf.RandomName("ns", 16)
-	testImage   = "localhost/kwok:test"
-)
-
-func init() {
-	_ = os.Setenv("KWOK_WORKDIR", path.Join(rootDir, "workdir"))
-}
-
-func TestMain(m *testing.M) {
-	ctx := context.Background()
-	cfg, _ := envconf.NewFromFlags()
-
-	testEnv, _ = env.NewWithContext(ctx, cfg)
-
-	kwokctlPath := path.Join(rootDir, "bin", runtime.GOOS, runtime.GOARCH, "kwokctl"+helper.BinSuffix)
-
-	k := kwok.NewProvider().
-		WithName(clusterName).
-		WithPath(kwokctlPath)
-	testEnv.Setup(
-		helper.BuildKwokImage(rootDir, testImage, consts.RuntimeTypeDocker),
-		helper.BuildKwokctlBinary(rootDir),
-		func(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
-			kubecfg, err := k.Create(ctx,
-				"--kwok-controller-image", testImage,
-				"--runtime", consts.RuntimeTypeKind,
-			)
-			if err != nil {
-				return ctx, err
-			}
-
-			cfg.WithKubeconfigFile(kubecfg)
-
-			if err := k.WaitForControlPlane(ctx, cfg.Client()); err != nil {
-				return ctx, err
-			}
-
-			return ctx, nil
-		},
-		envfuncs.CreateNamespace(namespace),
-	)
-	testEnv.Finish(
-		func(ctx context.Context, config *envconf.Config) (context.Context, error) {
-			err := k.Destroy(ctx)
-			if err != nil {
-				return ctx, err
-			}
-
-			return ctx, nil
-		},
-	)
-	os.Exit(testEnv.Run(m))
-}
diff --git a/test/e2e/kwokctl/nerdctl/kwok_test.go b/test/e2e/kwokctl/nerdctl/kwok_test.go
deleted file mode 100644
index 52199e15..00000000
--- a/test/e2e/kwokctl/nerdctl/kwok_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package nerdctl_test
-
-import (
-	"testing"
-
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-
-	"sigs.k8s.io/kwok/test/e2e"
-)
-
-func TestNode(t *testing.T) {
-	f0 := e2e.CaseNode(envconf.RandomName("node", 16)).
-		Feature()
-	testEnv.Test(t, f0)
-}
-
-func TestPod(t *testing.T) {
-	f0 := e2e.CasePod(envconf.RandomName("node", 16), namespace).
-		Feature()
-	testEnv.Test(t, f0)
-}
diff --git a/test/e2e/kwokctl/nerdctl/main_test.go b/test/e2e/kwokctl/nerdctl/main_test.go
deleted file mode 100644
index 5f31e8f6..00000000
--- a/test/e2e/kwokctl/nerdctl/main_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-// Package nerdctl_test is a test environment for kwok.
-package nerdctl_test
-
-import (
-	"context"
-	"os"
-	"runtime"
-	"testing"
-
-	"sigs.k8s.io/e2e-framework/pkg/env"
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-	"sigs.k8s.io/e2e-framework/pkg/envfuncs"
-	"sigs.k8s.io/e2e-framework/support/kwok"
-
-	"sigs.k8s.io/kwok/pkg/consts"
-	"sigs.k8s.io/kwok/pkg/utils/path"
-	"sigs.k8s.io/kwok/test/e2e/helper"
-)
-
-var (
-	testEnv     env.Environment
-	pwd         = os.Getenv("PWD")
-	rootDir     = path.Join(pwd, "../../../..")
-	clusterName = envconf.RandomName("kwok-e2e", 16)
-	namespace   = envconf.RandomName("ns", 16)
-	testImage   = "localhost/kwok:test"
-)
-
-func init() {
-	_ = os.Setenv("KWOK_WORKDIR", path.Join(rootDir, "workdir"))
-}
-
-func TestMain(m *testing.M) {
-	ctx := context.Background()
-	cfg, _ := envconf.NewFromFlags()
-
-	testEnv, _ = env.NewWithContext(ctx, cfg)
-
-	kwokctlPath := path.Join(rootDir, "bin", runtime.GOOS, runtime.GOARCH, "kwokctl"+helper.BinSuffix)
-
-	k := kwok.NewProvider().
-		WithName(clusterName).
-		WithPath(kwokctlPath)
-	testEnv.Setup(
-		helper.BuildKwokImage(rootDir, testImage, consts.RuntimeTypeNerdctl),
-		helper.BuildKwokctlBinary(rootDir),
-		func(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
-			kubecfg, err := k.Create(ctx,
-				"--kwok-controller-image", testImage,
-				"--runtime", consts.RuntimeTypeNerdctl,
-			)
-			if err != nil {
-				return ctx, err
-			}
-
-			cfg.WithKubeconfigFile(kubecfg)
-
-			if err := k.WaitForControlPlane(ctx, cfg.Client()); err != nil {
-				return ctx, err
-			}
-
-			return ctx, nil
-		},
-		envfuncs.CreateNamespace(namespace),
-	)
-	testEnv.Finish(
-		func(ctx context.Context, config *envconf.Config) (context.Context, error) {
-			err := k.Destroy(ctx)
-			if err != nil {
-				return ctx, err
-			}
-
-			return ctx, nil
-		},
-	)
-	os.Exit(testEnv.Run(m))
-}
diff --git a/test/e2e/kwokctl/podman/kwok_test.go b/test/e2e/kwokctl/podman/kwok_test.go
deleted file mode 100644
index 00123096..00000000
--- a/test/e2e/kwokctl/podman/kwok_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package podman_test
-
-import (
-	"testing"
-
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-
-	"sigs.k8s.io/kwok/test/e2e"
-)
-
-func TestNode(t *testing.T) {
-	f0 := e2e.CaseNode(envconf.RandomName("node", 16)).
-		Feature()
-	testEnv.Test(t, f0)
-}
-
-func TestPod(t *testing.T) {
-	f0 := e2e.CasePod(envconf.RandomName("node", 16), namespace).
-		Feature()
-	testEnv.Test(t, f0)
-}
diff --git a/test/e2e/kwokctl/podman/main_test.go b/test/e2e/kwokctl/podman/main_test.go
deleted file mode 100644
index 75c98b9e..00000000
--- a/test/e2e/kwokctl/podman/main_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-// Package podman_test is a test environment for kwok.
-package podman_test
-
-import (
-	"context"
-	"os"
-	"runtime"
-	"testing"
-
-	"sigs.k8s.io/e2e-framework/pkg/env"
-	"sigs.k8s.io/e2e-framework/pkg/envconf"
-	"sigs.k8s.io/e2e-framework/pkg/envfuncs"
-	"sigs.k8s.io/e2e-framework/support/kwok"
-
-	"sigs.k8s.io/kwok/pkg/consts"
-	"sigs.k8s.io/kwok/pkg/utils/path"
-	"sigs.k8s.io/kwok/test/e2e/helper"
-)
-
-var (
-	testEnv     env.Environment
-	pwd         = os.Getenv("PWD")
-	rootDir     = path.Join(pwd, "../../../..")
-	clusterName = envconf.RandomName("kwok-e2e", 16)
-	namespace   = envconf.RandomName("ns", 16)
-	testImage   = "localhost/kwok:test"
-)
-
-func init() {
-	_ = os.Setenv("KWOK_WORKDIR", path.Join(rootDir, "workdir"))
-}
-
-func TestMain(m *testing.M) {
-	ctx := context.Background()
-	cfg, _ := envconf.NewFromFlags()
-
-	testEnv, _ = env.NewWithContext(ctx, cfg)
-
-	kwokctlPath := path.Join(rootDir, "bin", runtime.GOOS, runtime.GOARCH, "kwokctl"+helper.BinSuffix)
-
-	k := kwok.NewProvider().
-		WithName(clusterName).
-		WithPath(kwokctlPath)
-	testEnv.Setup(
-		helper.BuildKwokImage(rootDir, testImage, consts.RuntimeTypePodman),
-		helper.BuildKwokctlBinary(rootDir),
-		func(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
-			kubecfg, err := k.Create(ctx,
-				"--kwok-controller-image", testImage,
-				"--runtime", consts.RuntimeTypePodman,
-			)
-			if err != nil {
-				return ctx, err
-			}
-
-			cfg.WithKubeconfigFile(kubecfg)
-
-			if err := k.WaitForControlPlane(ctx, cfg.Client()); err != nil {
-				return ctx, err
-			}
-
-			return ctx, nil
-		},
-		envfuncs.CreateNamespace(namespace),
-	)
-	testEnv.Finish(
-		func(ctx context.Context, config *envconf.Config) (context.Context, error) {
-			err := k.Destroy(ctx)
-			if err != nil {
-				return ctx, err
-			}
-
-			return ctx, nil
-		},
-	)
-	os.Exit(testEnv.Run(m))
-}
diff --git a/test/kwok-single-node/fake-deployment.yaml b/test/kwok-single-node/fake-deployment.yaml
new file mode 100644
index 00000000..bc63bf96
--- /dev/null
+++ b/test/kwok-single-node/fake-deployment.yaml
@@ -0,0 +1,32 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: fake-pod
+  namespace: default
+spec:
+  replicas: 5
+  selector:
+    matchLabels:
+      app: fake-pod
+  template:
+    metadata:
+      labels:
+        app: fake-pod
+    spec:
+      affinity:
+        nodeAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+            nodeSelectorTerms:
+              - matchExpressions:
+                  - key: type
+                    operator: In
+                    values:
+                      - kwok-controller
+      tolerations:
+        - key: "kwok-controller/provider"
+          operator: "Exists"
+          effect: "NoSchedule"
+      containers:
+        - name: fake-container
+          image: fake
+      nodeName: fake-node
diff --git a/test/kwok-single-node/fake-node.yaml b/test/kwok-single-node/fake-node.yaml
new file mode 100644
index 00000000..08a654f9
--- /dev/null
+++ b/test/kwok-single-node/fake-node.yaml
@@ -0,0 +1,21 @@
+apiVersion: v1
+kind: Node
+metadata:
+  annotations:
+    kwok.x-k8s.io/node: fake
+    node.alpha.kubernetes.io/ttl: "0"
+  labels:
+    beta.kubernetes.io/arch: amd64
+    beta.kubernetes.io/os: linux
+    kubernetes.io/arch: amd64
+    kubernetes.io/hostname: fake-node
+    kubernetes.io/os: linux
+    kubernetes.io/role: agent
+    node-role.kubernetes.io/agent: ""
+    type: kwok-controller
+  name: fake-node
+spec:
+  taints:
+    - effect: NoSchedule
+      key: kwok-controller/provider
+      value: fake
diff --git a/test/e2e/kwok/single/kustomization.yaml b/test/kwok-single-node/kustomization.yaml
similarity index 83%
rename from test/e2e/kwok/single/kustomization.yaml
rename to test/kwok-single-node/kustomization.yaml
index c08b9868..061e96a8 100644
--- a/test/e2e/kwok/single/kustomization.yaml
+++ b/test/kwok-single-node/kustomization.yaml
@@ -3,12 +3,9 @@ kind: Kustomization
 
 images:
 - name: registry.k8s.io/kwok/kwok
-  newName: localhost/kwok
+  newName: kwok
   newTag: test
 
-resources:
-- ../../../../kustomize/kwok
-
 patches:
 - target:
     group: apps
@@ -27,4 +24,9 @@ patches:
       value: --manage-nodes-with-annotation-selector=
     - op: replace
       path: /spec/template/spec/containers/0/args/3
-      value: --manage-single-node=node
+      value: --manage-single-node=fake-node
+
+resources:
+- ../../kustomize/kwok
+- fake-deployment.yaml
+- fake-node.yaml
diff --git a/test/kwok-single-node/kwok-single-node.test.sh b/test/kwok-single-node/kwok-single-node.test.sh
new file mode 100755
index 00000000..ba24bf4b
--- /dev/null
+++ b/test/kwok-single-node/kwok-single-node.test.sh
@@ -0,0 +1,197 @@
+#!/usr/bin/env bash
+# Copyright 2023 The Kubernetes Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+DIR="$(dirname "${BASH_SOURCE[0]}")"
+
+DIR="$(realpath "${DIR}")"
+
+ROOT_DIR="$(realpath "${DIR}/../..")"
+
+CLUSTER_NAME=kwok-test
+KWOK_IMAGE="kwok"
+KWOK_VERSION="test"
+
+export PATH="${ROOT_DIR}/bin:${PATH}"
+
+function start_cluster() {
+  local linux_platform
+  linux_platform="linux/$(go env GOARCH)"
+  "${ROOT_DIR}"/hack/releases.sh --bin kwok --platform "${linux_platform}"
+  "${ROOT_DIR}"/images/kwok/build.sh --image "${KWOK_IMAGE}" --version="${KWOK_VERSION}" --platform "${linux_platform}"
+
+  kind create cluster --name="${CLUSTER_NAME}"
+
+  kind load docker-image --name="${CLUSTER_NAME}" "${KWOK_IMAGE}:${KWOK_VERSION}"
+
+  kubectl kustomize "${DIR}" | kubectl apply -f -
+  kubectl kustomize "${ROOT_DIR}/stages" | kubectl apply -f -
+}
+
+# Check for normal heartbeat
+function test_node_ready() {
+  for ((i = 0; i < 30; i++)); do
+    if [[ ! "$(kubectl get node fake-node)" =~ "Ready" ]]; then
+      echo "Waiting for fake-node to be ready..."
+      sleep 1
+    else
+      break
+    fi
+  done
+
+  if [[ ! "$(kubectl get node fake-node)" =~ "Ready" ]]; then
+    echo "Error: fake-node is not ready"
+    kubectl get node fake-node
+    return 1
+  fi
+}
+
+# Check pod status
+function test_check_pod_status() {
+  _jq() {
+    echo "${row}" | base64 --decode | jq -r "${1}"
+  }
+  for row in $(kubectl get po -ojson | jq -r '.items[] | @base64'); do
+    pod_name=$(_jq '.metadata.name')
+    node_name=$(_jq '.spec.nodeName')
+    host_network=$(_jq '.spec.hostNetwork')
+    host_ip=$(_jq '.status.hostIP')
+    pod_ip=$(_jq '.status.podIP')
+    node_ip=$(kubectl get nodes "${node_name}" -ojson | jq -r '.status.addresses[] | select( .type == "InternalIP" ) | .address')
+    if [[ "${host_ip}" != "${node_ip}" ]]; then
+      echo "Error: ${pod_name} Pod: HostIP (${host_ip}) is not equal to the IP on its Node (${node_ip})"
+      return 1
+    fi
+    if [[ $host_network == "true" ]]; then
+      if [[ "${pod_ip}" != "${node_ip}" ]]; then
+        echo "Error: ${pod_name} Pod with hostNetwork=${host_network}: PodIP (${pod_ip}) and HostIP (${host_ip}) are not equal"
+        return 1
+      fi
+    fi
+  done
+  echo "Pod's status checked"
+}
+
+# Check for the Pod is running
+function test_pod_running() {
+  for ((i = 0; i < 30; i++)); do
+    if [[ ! "$(kubectl get pod | grep -c Running)" -eq 5 ]]; then
+      echo "Waiting all pods to be running..."
+      sleep 1
+    else
+      break
+    fi
+  done
+
+  if [[ ! "$(kubectl get pod | grep -c Running)" -eq 5 ]]; then
+    echo "Error: Not all pods are running"
+    kubectl get pod -o wide
+    return 1
+  fi
+}
+
+# Check for the status of the Node is modified by Kubectl
+function test_modify_node_status() {
+  kubectl annotate node fake-node --overwrite kwok.x-k8s.io/status=custom
+  kubectl patch node fake-node --subresource=status -p '{"status":{"nodeInfo":{"kubeletVersion":"fake-new"}}}'
+
+  sleep 2
+
+  if [[ ! "$(kubectl get node fake-node)" =~ "fake-new" ]]; then
+    echo "Error: fake-node is not updated"
+    kubectl get node fake-node
+    return 1
+  fi
+  kubectl annotate node fake-node --overwrite kwok.x-k8s.io/status-
+}
+
+# Check for the status of the Pod is modified by Kubectl
+function test_modify_pod_status() {
+  local first_pod
+  first_pod="$(kubectl get pod | grep Running | head -n 1 | awk '{print $1}')"
+
+  kubectl annotate pod "${first_pod}" --overwrite kwok.x-k8s.io/status=custom
+  kubectl patch pod "${first_pod}" --subresource=status -p '{"status":{"podIP":"192.168.0.1"}}'
+
+  sleep 2
+
+  if [[ ! "$(kubectl get pod "${first_pod}" -o wide)" == *"192.168.0.1"* ]]; then
+    echo "Error: fake-pod is not updated"
+    kubectl get pod "${first_pod}" -o wide
+    return 1
+  fi
+
+  kubectl annotate pod "${first_pod}" --overwrite kwok.x-k8s.io/status-
+}
+
+function test_check_node_lease_transitions() {
+  local want="${1}"
+  local node_leases_transitions
+  node_leases_transitions="$(kubectl get leases fake-node -n kube-node-lease -ojson | jq -r '.spec.leaseTransitions // 0')"
+  if [[ "${node_leases_transitions}" != "${want}" ]]; then
+    echo "Error: fake-node lease transitions is not ${want}, got ${node_leases_transitions}"
+    return 1
+  fi
+}
+
+function recreate_kwok() {
+  kubectl scale deployment/kwok-controller -n kube-system --replicas=0
+  kubectl wait --for=delete pod -l app=kwok-controller -n kube-system --timeout=60s
+
+  kubectl scale deployment/kwok-controller -n kube-system --replicas=2
+}
+
+function recreate_pods() {
+  kubectl delete pod --all -n default
+}
+
+# Cleanup
+function cleanup() {
+  kind delete cluster --name="${CLUSTER_NAME}"
+}
+
+function main() {
+  failed=()
+  start_cluster || {
+    echo "Error: Failed to start cluster"
+    exit 1
+  }
+  trap cleanup EXIT
+
+  test_node_ready || failed+=("node_ready")
+  test_pod_running || failed+=("pod_running")
+  test_check_pod_status || failed+=("check_pod_status")
+
+  test_modify_node_status || failed+=("modify_node_status")
+  test_modify_pod_status || failed+=("modify_pod_status")
+  test_check_node_lease_transitions 0 || failed+=("check_node_lease_transitions")
+
+  recreate_kwok || failed+=("recreate_kwok")
+  recreate_pods || failed+=("recreate_pods")
+  test_node_ready || failed+=("node_ready_again")
+  test_pod_running || failed+=("pod_running_again")
+  test_check_pod_status || failed+=("check_pod_status_again")
+
+  test_check_node_lease_transitions 1 || failed+=("check_node_lease_transitions_again")
+
+  if [[ "${#failed[@]}" -ne 0 ]]; then
+    echo "Error: Some tests failed"
+    for test in "${failed[@]}"; do
+      echo " - ${test}"
+    done
+    exit 1
+  fi
+}
+
+main
diff --git a/test/release/build.test.sh b/test/release/build.test.sh
index 314bd4dd..8d710610 100755
--- a/test/release/build.test.sh
+++ b/test/release/build.test.sh
@@ -24,498 +24,71 @@ VERSION="$("${ROOT_DIR}/hack/get-version.sh")"
 GOOS="$(go env GOOS)"
 GOARCH="$(go env GOARCH)"
 
-SUPPORTED_KUBE_RELEASES=$(cat "${ROOT_DIR}/supported_releases.txt")
+CASES_PATH="${DIR}/cases.txt"
 
-NUMBER_SUPPORTED_KUBE_RELEASES=3
-
-KUBE_RELEASES="$(echo "${SUPPORTED_KUBE_RELEASES}" | head -n "${NUMBER_SUPPORTED_KUBE_RELEASES}")"
-
-LAST_KUBE_RELEASE="$(echo "${SUPPORTED_KUBE_RELEASES}" | head -n 1)"
-
-IMAGE_PREFIX=image-prefix
-PREFIX=prefix
-
-function want_build() {
-  cat <<EOF
-GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/${GOOS}/${GOARCH}/kwok ./cmd/kwok
-GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/${GOOS}/${GOARCH}/kwokctl ./cmd/kwokctl
-EOF
-}
-
-function want_build_with_push_bucket() {
-  cat <<EOF
-GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/${GOOS}/${GOARCH}/kwok ./cmd/kwok
-gsutil cp -P ./bin/${GOOS}/${GOARCH}/kwok bucket/releases/${VERSION}/bin/${GOOS}/${GOARCH}/kwok
-GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/${GOOS}/${GOARCH}/kwokctl ./cmd/kwokctl
-gsutil cp -P ./bin/${GOOS}/${GOARCH}/kwokctl bucket/releases/${VERSION}/bin/${GOOS}/${GOARCH}/kwokctl
-EOF
-}
-
-function want_build_with_push_bucket_staging() {
-  cat <<EOF
-GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/${GOOS}/${GOARCH}/kwok ./cmd/kwok
-gsutil cp -P ./bin/${GOOS}/${GOARCH}/kwok bucket/releases/${PREFIX}-${VERSION}/bin/${GOOS}/${GOARCH}/kwok
-GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/${GOOS}/${GOARCH}/kwokctl ./cmd/kwokctl
-gsutil cp -P ./bin/${GOOS}/${GOARCH}/kwokctl bucket/releases/${PREFIX}-${VERSION}/bin/${GOOS}/${GOARCH}/kwokctl
-EOF
-}
-
-function want_build_with_push_ghrelease() {
-  cat <<EOF
-GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/${GOOS}/${GOARCH}/kwok ./cmd/kwok
-cp ./bin/${GOOS}/${GOARCH}/kwok kwok-${GOOS}-${GOARCH}
-gh -R ghrelease release upload ${VERSION} kwok-${GOOS}-${GOARCH}
-GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/${GOOS}/${GOARCH}/kwokctl ./cmd/kwokctl
-cp ./bin/${GOOS}/${GOARCH}/kwokctl kwokctl-${GOOS}-${GOARCH}
-gh -R ghrelease release upload ${VERSION} kwokctl-${GOOS}-${GOARCH}
-EOF
-}
-
-function want_image() {
-  cat <<EOF
-docker buildx build --tag=kwok:${VERSION} --platform=linux/amd64 --load -f ./images/kwok/Dockerfile .
-EOF
-}
-
-function want_image_nerdctl() {
-  cat <<EOF
-nerdctl build --tag=kwok:${VERSION} --platform=linux/amd64 -f ./images/kwok/Dockerfile .
-EOF
-}
-
-function want_image_podman() {
-  cat <<EOF
-podman build --tag=kwok:${VERSION} --platform=linux/amd64 -f ./images/kwok/Dockerfile .
-EOF
-}
-
-function want_image_with_push() {
-  cat <<EOF
-docker buildx build --tag=kwok:${VERSION} --platform=linux/amd64 --push -f ./images/kwok/Dockerfile .
-EOF
-}
-
-function want_image_nerdctl_with_push() {
-  cat <<EOF
-nerdctl build --tag=kwok:${VERSION} --platform=linux/amd64 -f ./images/kwok/Dockerfile .
-nerdctl push --platform=linux/amd64 kwok:${VERSION}
-EOF
-}
-
-function want_image_podman_with_push() {
-  cat <<EOF
-podman build --tag=kwok:${VERSION} --platform=linux/amd64 -f ./images/kwok/Dockerfile .
-podman push --platform=linux/amd64 kwok:${VERSION}
-EOF
-}
-
-function want_image_with_push_staging() {
-  cat <<EOF
-docker buildx build --tag=${IMAGE_PREFIX}/kwok:${PREFIX}-${VERSION} --platform=linux/amd64 --push -f ./images/kwok/Dockerfile .
-EOF
-}
-
-function want_image_nerdctl_with_push_staging() {
-  cat <<EOF
-nerdctl build --tag=${IMAGE_PREFIX}/kwok:${PREFIX}-${VERSION} --platform=linux/amd64 -f ./images/kwok/Dockerfile .
-nerdctl push --platform=linux/amd64 ${IMAGE_PREFIX}/kwok:${PREFIX}-${VERSION}
-EOF
-}
-
-function want_image_podman_with_push_staging() {
-  cat <<EOF
-podman build --tag=${IMAGE_PREFIX}/kwok:${PREFIX}-${VERSION} --platform=linux/amd64 -f ./images/kwok/Dockerfile .
-podman push --platform=linux/amd64 ${IMAGE_PREFIX}/kwok:${PREFIX}-${VERSION}
-EOF
-}
-
-function want_cluster_image() {
-  for v in ${KUBE_RELEASES}; do
-    echo "docker buildx build --build-arg=kube_version=v${v} --tag=cluster:${VERSION}-k8s.v${v} --platform=linux/amd64 --load -f ./images/cluster/Dockerfile ."
-  done
-}
-
-function want_cluster_image_nerdctl() {
-  for v in ${KUBE_RELEASES}; do
-    echo "nerdctl build --build-arg=kube_version=v${v} --tag=cluster:${VERSION}-k8s.v${v} --platform=linux/amd64 -f ./images/cluster/Dockerfile ."
-  done
-}
-
-function want_cluster_image_podman() {
-  for v in ${KUBE_RELEASES}; do
-    echo "podman build --build-arg=kube_version=v${v} --tag=cluster:${VERSION}-k8s.v${v} --platform=linux/amd64 -f ./images/cluster/Dockerfile ."
-  done
-}
-
-function want_cluster_image_with_push() {
-  for v in ${KUBE_RELEASES}; do
-    echo "docker buildx build --build-arg=kube_version=v${v} --tag=cluster:${VERSION}-k8s.v${v} --platform=linux/amd64 --push -f ./images/cluster/Dockerfile ."
-  done
-}
-
-function want_cluster_image_nerdctl_with_push() {
-  for v in ${KUBE_RELEASES}; do
-    echo "nerdctl build --build-arg=kube_version=v${v} --tag=cluster:${VERSION}-k8s.v${v} --platform=linux/amd64 -f ./images/cluster/Dockerfile ."
-    echo "nerdctl push --platform=linux/amd64 cluster:${VERSION}-k8s.v${v}"
-  done
-}
-
-function want_cluster_image_podman_with_push() {
-  for v in ${KUBE_RELEASES}; do
-    echo "podman build --build-arg=kube_version=v${v} --tag=cluster:${VERSION}-k8s.v${v} --platform=linux/amd64 -f ./images/cluster/Dockerfile ."
-    echo "podman push --platform=linux/amd64 cluster:${VERSION}-k8s.v${v}"
-  done
-}
-
-function want_cluster_image_with_push_staging() {
-  for v in ${KUBE_RELEASES}; do
-    echo "docker buildx build --build-arg=kube_version=v${v} --tag=${IMAGE_PREFIX}/cluster:${PREFIX}-${VERSION}-k8s.v${v} --platform=linux/amd64 --push -f ./images/cluster/Dockerfile ."
-  done
-}
-
-function want_cluster_image_nerdctl_with_push_staging() {
-  for v in ${KUBE_RELEASES}; do
-    echo "nerdctl build --build-arg=kube_version=v${v} --tag=${IMAGE_PREFIX}/cluster:${PREFIX}-${VERSION}-k8s.v${v} --platform=linux/amd64 -f ./images/cluster/Dockerfile ."
-    echo "nerdctl push --platform=linux/amd64 ${IMAGE_PREFIX}/cluster:${PREFIX}-${VERSION}-k8s.v${v}"
-  done
-}
-
-function want_cluster_image_podman_with_push_staging() {
-  for v in ${KUBE_RELEASES}; do
-    echo "podman build --build-arg=kube_version=v${v} --tag=${IMAGE_PREFIX}/cluster:${PREFIX}-${VERSION}-k8s.v${v} --platform=linux/amd64 -f ./images/cluster/Dockerfile ."
-    echo "podman push --platform=linux/amd64 ${IMAGE_PREFIX}/cluster:${PREFIX}-${VERSION}-k8s.v${v}"
-  done
-}
-
-function want_cross_build() {
-  cat <<EOF
-GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/amd64/kwok ./cmd/kwok
-GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/amd64/kwokctl ./cmd/kwokctl
-GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/arm64/kwok ./cmd/kwok
-GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/arm64/kwokctl ./cmd/kwokctl
-GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/amd64/kwok ./cmd/kwok
-GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/amd64/kwokctl ./cmd/kwokctl
-GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/arm64/kwok ./cmd/kwok
-GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/arm64/kwokctl ./cmd/kwokctl
-GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/amd64/kwok.exe ./cmd/kwok
-GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/amd64/kwokctl.exe ./cmd/kwokctl
-GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/arm64/kwok.exe ./cmd/kwok
-GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/arm64/kwokctl.exe ./cmd/kwokctl
-EOF
-}
-
-function want_cross_build_with_push_bucket() {
-  cat <<EOF
-GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/amd64/kwok ./cmd/kwok
-gsutil cp -P ./bin/linux/amd64/kwok bucket/releases/${VERSION}/bin/linux/amd64/kwok
-GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/amd64/kwokctl ./cmd/kwokctl
-gsutil cp -P ./bin/linux/amd64/kwokctl bucket/releases/${VERSION}/bin/linux/amd64/kwokctl
-GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/arm64/kwok ./cmd/kwok
-gsutil cp -P ./bin/linux/arm64/kwok bucket/releases/${VERSION}/bin/linux/arm64/kwok
-GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/arm64/kwokctl ./cmd/kwokctl
-gsutil cp -P ./bin/linux/arm64/kwokctl bucket/releases/${VERSION}/bin/linux/arm64/kwokctl
-GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/amd64/kwok ./cmd/kwok
-gsutil cp -P ./bin/darwin/amd64/kwok bucket/releases/${VERSION}/bin/darwin/amd64/kwok
-GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/amd64/kwokctl ./cmd/kwokctl
-gsutil cp -P ./bin/darwin/amd64/kwokctl bucket/releases/${VERSION}/bin/darwin/amd64/kwokctl
-GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/arm64/kwok ./cmd/kwok
-gsutil cp -P ./bin/darwin/arm64/kwok bucket/releases/${VERSION}/bin/darwin/arm64/kwok
-GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/arm64/kwokctl ./cmd/kwokctl
-gsutil cp -P ./bin/darwin/arm64/kwokctl bucket/releases/${VERSION}/bin/darwin/arm64/kwokctl
-GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/amd64/kwok.exe ./cmd/kwok
-gsutil cp -P ./bin/windows/amd64/kwok.exe bucket/releases/${VERSION}/bin/windows/amd64/kwok.exe
-GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/amd64/kwokctl.exe ./cmd/kwokctl
-gsutil cp -P ./bin/windows/amd64/kwokctl.exe bucket/releases/${VERSION}/bin/windows/amd64/kwokctl.exe
-GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/arm64/kwok.exe ./cmd/kwok
-gsutil cp -P ./bin/windows/arm64/kwok.exe bucket/releases/${VERSION}/bin/windows/arm64/kwok.exe
-GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/arm64/kwokctl.exe ./cmd/kwokctl
-gsutil cp -P ./bin/windows/arm64/kwokctl.exe bucket/releases/${VERSION}/bin/windows/arm64/kwokctl.exe
-EOF
-}
-
-function want_cross_build_with_push_bucket_staging() {
-  cat <<EOF
-GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/amd64/kwok ./cmd/kwok
-gsutil cp -P ./bin/linux/amd64/kwok bucket/releases/${PREFIX}-${VERSION}/bin/linux/amd64/kwok
-GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/amd64/kwokctl ./cmd/kwokctl
-gsutil cp -P ./bin/linux/amd64/kwokctl bucket/releases/${PREFIX}-${VERSION}/bin/linux/amd64/kwokctl
-GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/arm64/kwok ./cmd/kwok
-gsutil cp -P ./bin/linux/arm64/kwok bucket/releases/${PREFIX}-${VERSION}/bin/linux/arm64/kwok
-GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/arm64/kwokctl ./cmd/kwokctl
-gsutil cp -P ./bin/linux/arm64/kwokctl bucket/releases/${PREFIX}-${VERSION}/bin/linux/arm64/kwokctl
-GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/amd64/kwok ./cmd/kwok
-gsutil cp -P ./bin/darwin/amd64/kwok bucket/releases/${PREFIX}-${VERSION}/bin/darwin/amd64/kwok
-GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/amd64/kwokctl ./cmd/kwokctl
-gsutil cp -P ./bin/darwin/amd64/kwokctl bucket/releases/${PREFIX}-${VERSION}/bin/darwin/amd64/kwokctl
-GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/arm64/kwok ./cmd/kwok
-gsutil cp -P ./bin/darwin/arm64/kwok bucket/releases/${PREFIX}-${VERSION}/bin/darwin/arm64/kwok
-GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/arm64/kwokctl ./cmd/kwokctl
-gsutil cp -P ./bin/darwin/arm64/kwokctl bucket/releases/${PREFIX}-${VERSION}/bin/darwin/arm64/kwokctl
-GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/amd64/kwok.exe ./cmd/kwok
-gsutil cp -P ./bin/windows/amd64/kwok.exe bucket/releases/${PREFIX}-${VERSION}/bin/windows/amd64/kwok.exe
-GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/amd64/kwokctl.exe ./cmd/kwokctl
-gsutil cp -P ./bin/windows/amd64/kwokctl.exe bucket/releases/${PREFIX}-${VERSION}/bin/windows/amd64/kwokctl.exe
-GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/arm64/kwok.exe ./cmd/kwok
-gsutil cp -P ./bin/windows/arm64/kwok.exe bucket/releases/${PREFIX}-${VERSION}/bin/windows/arm64/kwok.exe
-GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/arm64/kwokctl.exe ./cmd/kwokctl
-gsutil cp -P ./bin/windows/arm64/kwokctl.exe bucket/releases/${PREFIX}-${VERSION}/bin/windows/arm64/kwokctl.exe
-EOF
-}
-
-function want_cross_build_with_push_ghrelease() {
-  cat <<EOF
-GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/amd64/kwok ./cmd/kwok
-cp ./bin/linux/amd64/kwok kwok-linux-amd64
-gh -R ghrelease release upload ${VERSION} kwok-linux-amd64
-GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/amd64/kwokctl ./cmd/kwokctl
-cp ./bin/linux/amd64/kwokctl kwokctl-linux-amd64
-gh -R ghrelease release upload ${VERSION} kwokctl-linux-amd64
-GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/arm64/kwok ./cmd/kwok
-cp ./bin/linux/arm64/kwok kwok-linux-arm64
-gh -R ghrelease release upload ${VERSION} kwok-linux-arm64
-GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/linux/arm64/kwokctl ./cmd/kwokctl
-cp ./bin/linux/arm64/kwokctl kwokctl-linux-arm64
-gh -R ghrelease release upload ${VERSION} kwokctl-linux-arm64
-GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/amd64/kwok ./cmd/kwok
-cp ./bin/darwin/amd64/kwok kwok-darwin-amd64
-gh -R ghrelease release upload ${VERSION} kwok-darwin-amd64
-GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/amd64/kwokctl ./cmd/kwokctl
-cp ./bin/darwin/amd64/kwokctl kwokctl-darwin-amd64
-gh -R ghrelease release upload ${VERSION} kwokctl-darwin-amd64
-GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/arm64/kwok ./cmd/kwok
-cp ./bin/darwin/arm64/kwok kwok-darwin-arm64
-gh -R ghrelease release upload ${VERSION} kwok-darwin-arm64
-GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/darwin/arm64/kwokctl ./cmd/kwokctl
-cp ./bin/darwin/arm64/kwokctl kwokctl-darwin-arm64
-gh -R ghrelease release upload ${VERSION} kwokctl-darwin-arm64
-GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/amd64/kwok.exe ./cmd/kwok
-cp ./bin/windows/amd64/kwok.exe kwok-windows-amd64.exe
-gh -R ghrelease release upload ${VERSION} kwok-windows-amd64.exe
-GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/amd64/kwokctl.exe ./cmd/kwokctl
-cp ./bin/windows/amd64/kwokctl.exe kwokctl-windows-amd64.exe
-gh -R ghrelease release upload ${VERSION} kwokctl-windows-amd64.exe
-GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/arm64/kwok.exe ./cmd/kwok
-cp ./bin/windows/arm64/kwok.exe kwok-windows-arm64.exe
-gh -R ghrelease release upload ${VERSION} kwok-windows-arm64.exe
-GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=${VERSION} -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v${LAST_KUBE_RELEASE}' -o ./bin/windows/arm64/kwokctl.exe ./cmd/kwokctl
-cp ./bin/windows/arm64/kwokctl.exe kwokctl-windows-arm64.exe
-gh -R ghrelease release upload ${VERSION} kwokctl-windows-arm64.exe
-EOF
+function clear_testdata() {
+  local kind="${1}"
+  sed "s|${VERSION}|<VERSION>|g" |
+    clear_testdata_goos "${kind}" |
+    clear_testdata_goarch "${kind}"
 }
 
-function want_cross_image() {
-  cat <<EOF
-docker buildx build --tag=kwok:${VERSION} --platform=linux/amd64 --platform=linux/arm64 --load -f ./images/kwok/Dockerfile .
-EOF
-}
-
-function want_cross_image_nerdctl() {
-  cat <<EOF
-nerdctl build --tag=kwok:${VERSION} --platform=linux/amd64 --platform=linux/arm64 -f ./images/kwok/Dockerfile .
-EOF
-}
-
-function want_cross_image_podman() {
-  cat <<EOF
-podman build --tag=kwok:${VERSION} --platform=linux/amd64 --platform=linux/arm64 -f ./images/kwok/Dockerfile .
-EOF
-}
-
-function want_cross_image_with_push() {
-  cat <<EOF
-docker buildx build --tag=kwok:${VERSION} --platform=linux/amd64 --platform=linux/arm64 --push -f ./images/kwok/Dockerfile .
-EOF
-}
-
-function want_cross_image_nerdctl_with_push() {
-  cat <<EOF
-nerdctl build --tag=kwok:${VERSION} --platform=linux/amd64 --platform=linux/arm64 -f ./images/kwok/Dockerfile .
-nerdctl push --platform=linux/amd64 --platform=linux/arm64 kwok:${VERSION}
-EOF
-}
-
-function want_cross_image_podman_with_push() {
-  cat <<EOF
-podman build --tag=kwok:${VERSION} --platform=linux/amd64 --platform=linux/arm64 -f ./images/kwok/Dockerfile .
-podman push --platform=linux/amd64 --platform=linux/arm64 kwok:${VERSION}
-EOF
-}
-
-function want_cross_image_with_push_staging() {
-  cat <<EOF
-docker buildx build --tag=${IMAGE_PREFIX}/kwok:${PREFIX}-${VERSION} --platform=linux/amd64 --platform=linux/arm64 --push -f ./images/kwok/Dockerfile .
-EOF
-}
-
-function want_cross_image_nerdctl_with_push_staging() {
-  cat <<EOF
-nerdctl build --tag=${IMAGE_PREFIX}/kwok:${PREFIX}-${VERSION} --platform=linux/amd64 --platform=linux/arm64 -f ./images/kwok/Dockerfile .
-nerdctl push --platform=linux/amd64 --platform=linux/arm64 ${IMAGE_PREFIX}/kwok:${PREFIX}-${VERSION}
-EOF
-}
-
-function want_cross_image_podman_with_push_staging() {
-  cat <<EOF
-podman build --tag=${IMAGE_PREFIX}/kwok:${PREFIX}-${VERSION} --platform=linux/amd64 --platform=linux/arm64 -f ./images/kwok/Dockerfile .
-podman push --platform=linux/amd64 --platform=linux/arm64 ${IMAGE_PREFIX}/kwok:${PREFIX}-${VERSION}
-EOF
-}
-
-function want_cross_cluster_image() {
-  for v in ${KUBE_RELEASES}; do
-    echo "docker buildx build --build-arg=kube_version=v${v} --tag=cluster:${VERSION}-k8s.v${v} --platform=linux/amd64 --platform=linux/arm64 --load -f ./images/cluster/Dockerfile ."
-  done
-}
-
-function want_cross_cluster_image_nerdctl() {
-  for v in ${KUBE_RELEASES}; do
-    echo "nerdctl build --build-arg=kube_version=v${v} --tag=cluster:${VERSION}-k8s.v${v} --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile ."
-  done
-}
-
-function want_cross_cluster_image_podman() {
-  for v in ${KUBE_RELEASES}; do
-    echo "podman build --build-arg=kube_version=v${v} --tag=cluster:${VERSION}-k8s.v${v} --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile ."
-  done
-}
-
-function want_cross_cluster_image_with_push() {
-  for v in ${KUBE_RELEASES}; do
-    echo "docker buildx build --build-arg=kube_version=v${v} --tag=cluster:${VERSION}-k8s.v${v} --platform=linux/amd64 --platform=linux/arm64 --push -f ./images/cluster/Dockerfile ."
-  done
-}
-
-function want_cross_cluster_image_nerdctl_with_push() {
-  for v in ${KUBE_RELEASES}; do
-    echo "nerdctl build --build-arg=kube_version=v${v} --tag=cluster:${VERSION}-k8s.v${v} --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile ."
-    echo "nerdctl push --platform=linux/amd64 --platform=linux/arm64 cluster:${VERSION}-k8s.v${v}"
-  done
-}
-
-function want_cross_cluster_image_podman_with_push() {
-  for v in ${KUBE_RELEASES}; do
-    echo "podman build --build-arg=kube_version=v${v} --tag=cluster:${VERSION}-k8s.v${v} --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile ."
-    echo "podman push --platform=linux/amd64 --platform=linux/arm64 cluster:${VERSION}-k8s.v${v}"
-  done
-}
-
-function want_cross_cluster_image_with_push_staging() {
-  for v in ${KUBE_RELEASES}; do
-    echo "docker buildx build --build-arg=kube_version=v${v} --tag=${IMAGE_PREFIX}/cluster:${PREFIX}-${VERSION}-k8s.v${v} --platform=linux/amd64 --platform=linux/arm64 --push -f ./images/cluster/Dockerfile ."
-  done
-}
-
-function want_cross_cluster_image_nerdctl_with_push_staging() {
-  for v in ${KUBE_RELEASES}; do
-    echo "nerdctl build --build-arg=kube_version=v${v} --tag=${IMAGE_PREFIX}/cluster:${PREFIX}-${VERSION}-k8s.v${v} --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile ."
-    echo "nerdctl push --platform=linux/amd64 --platform=linux/arm64 ${IMAGE_PREFIX}/cluster:${PREFIX}-${VERSION}-k8s.v${v}"
-  done
-}
-
-function want_cross_cluster_image_podman_with_push_staging() {
-  for v in ${KUBE_RELEASES}; do
-    echo "podman build --build-arg=kube_version=v${v} --tag=${IMAGE_PREFIX}/cluster:${PREFIX}-${VERSION}-k8s.v${v} --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile ."
-    echo "podman push --platform=linux/amd64 --platform=linux/arm64 ${IMAGE_PREFIX}/cluster:${PREFIX}-${VERSION}-k8s.v${v}"
-  done
-}
-
-function want_manifests() {
-  echo "mkdir -p ./artifacts"
-  echo "cp -r ./kustomize ./artifacts/"
-  echo "cd ./artifacts/kustomize/kwok"
-  echo "kustomize edit set image registry.k8s.io/kwok/kwok=${IMAGE_PREFIX}/kwok:${VERSION}"
-  echo "cd -"
-  echo "kustomize build ./artifacts/kustomize/kwok -o ./artifacts/kwok.yaml"
-  echo "rm -r ./artifacts/kustomize"
-}
-
-function manifests_with_push_ghrelease() {
-  echo "mkdir -p ./artifacts"
-  echo "cp -r ./kustomize ./artifacts/"
-  echo "cd ./artifacts/kustomize/kwok"
-  echo "kustomize edit set image registry.k8s.io/kwok/kwok=${IMAGE_PREFIX}/kwok:${VERSION}"
-  echo "cd -"
-  echo "kustomize build ./artifacts/kustomize/kwok -o ./artifacts/kwok.yaml"
-  echo "gh -R ghrelease release upload ${VERSION} ./artifacts/kwok.yaml"
-  echo "rm -r ./artifacts/kustomize"
+function clear_testdata_goos() {
+  local kind="${1}"
+  if [[ "${kind}" == *"GOOS"* ]]; then
+    sed "s|${GOOS}|<OS>|g"
+  else
+    cat
+  fi
 }
 
-function manifests_with_push_bucket() {
-  echo "mkdir -p ./artifacts"
-  echo "cp -r ./kustomize ./artifacts/"
-  echo "cd ./artifacts/kustomize/kwok"
-  echo "kustomize edit set image registry.k8s.io/kwok/kwok=${IMAGE_PREFIX}/kwok:${VERSION}"
-  echo "cd -"
-  echo "kustomize build ./artifacts/kustomize/kwok -o ./artifacts/kwok.yaml"
-  echo "gsutil cp -P ./artifacts/kwok.yaml bucket/releases/${PREFIX}-${VERSION}/manifests/kwok.yaml"
-  echo "rm -r ./artifacts/kustomize"
+function clear_testdata_goarch() {
+  local kind="${1}"
+  if [[ "${kind}" == *"GOARCH"* ]]; then
+    sed "s|${GOARCH}|<ARCH>|g"
+  else
+    cat
+  fi
 }
 
 function main() {
   failed=()
   export DRY_RUN=true
-  export NUMBER_SUPPORTED_KUBE_RELEASES="${NUMBER_SUPPORTED_KUBE_RELEASES}"
-  make --no-print-directory -C "${ROOT_DIR}" build | diff -u <(want_build) - || failed+=("build")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true BUCKET=bucket build | diff -u <(want_build_with_push_bucket) - || failed+=("build-with-push-bucket")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true BUCKET=bucket STAGING=true STAGING_PREFIX=${PREFIX} build | diff -u <(want_build_with_push_bucket_staging) - || failed+=("build-with-push-bucket-staging")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true GH_RELEASE=ghrelease build | diff -u <(want_build_with_push_ghrelease) - || failed+=("build-with-push-ghrelease")
-
-  make --no-print-directory -C "${ROOT_DIR}" image | diff -u <(want_image) - || failed+=("image")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true image | diff -u <(want_image_with_push) - || failed+=("image-with-push")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true BUCKET=bucket STAGING=true STAGING_PREFIX=${PREFIX} STAGING_IMAGE_PREFIX=${IMAGE_PREFIX} image | diff -u <(want_image_with_push_staging) - || failed+=("image-with-push-staging")
-
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=nerdctl image | diff -u <(want_image_nerdctl) - || failed+=("image-nerdctl")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=nerdctl PUSH=true image | diff -u <(want_image_nerdctl_with_push) - || failed+=("image-nerdctl-with-push")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=nerdctl PUSH=true BUCKET=bucket STAGING=true STAGING_PREFIX=${PREFIX} STAGING_IMAGE_PREFIX=${IMAGE_PREFIX} image | diff -u <(want_image_nerdctl_with_push_staging) - || failed+=("image--nerdctl-with-push-staging")
-
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=podman image | diff -u <(want_image_podman) - || failed+=("image-podman")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=podman PUSH=true image | diff -u <(want_image_podman_with_push) - || failed+=("image-podman-with-push")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=podman PUSH=true BUCKET=bucket STAGING=true STAGING_PREFIX=${PREFIX} STAGING_IMAGE_PREFIX=${IMAGE_PREFIX} image | diff -u <(want_image_podman_with_push_staging) - || failed+=("image--podman-with-push-staging")
-
-  make --no-print-directory -C "${ROOT_DIR}" cluster-image | diff -u <(want_cluster_image) - || failed+=("cluster-image")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true cluster-image | diff -u <(want_cluster_image_with_push) - || failed+=("cluster-image-with-push")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true BUCKET=bucket STAGING=true STAGING_PREFIX=${PREFIX} STAGING_IMAGE_PREFIX=${IMAGE_PREFIX} cluster-image | diff -u <(want_cluster_image_with_push_staging) - || failed+=("cluster-image-with-push-staging")
-
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=nerdctl cluster-image | diff -u <(want_cluster_image_nerdctl) - || failed+=("cluster-image-nerdctl")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=nerdctl PUSH=true cluster-image | diff -u <(want_cluster_image_nerdctl_with_push) - || failed+=("cluster-image-nerdctl-with-push")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=nerdctl PUSH=true BUCKET=bucket STAGING=true STAGING_PREFIX=${PREFIX} STAGING_IMAGE_PREFIX=${IMAGE_PREFIX} cluster-image | diff -u <(want_cluster_image_nerdctl_with_push_staging) - || failed+=("cluster-image-nerdctl-with-push-staging")
-
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=podman cluster-image | diff -u <(want_cluster_image_podman) - || failed+=("cluster-image-podman")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=podman PUSH=true cluster-image | diff -u <(want_cluster_image_podman_with_push) - || failed+=("cluster-image-podman-with-push")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=podman PUSH=true BUCKET=bucket STAGING=true STAGING_PREFIX=${PREFIX} STAGING_IMAGE_PREFIX=${IMAGE_PREFIX} cluster-image | diff -u <(want_cluster_image_podman_with_push_staging) - || failed+=("cluster-image-podman-with-push-staging")
-
-  make --no-print-directory -C "${ROOT_DIR}" cross-build | diff -u <(want_cross_build) - || failed+=("cross-build")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true BUCKET=bucket cross-build | diff -u <(want_cross_build_with_push_bucket) - || failed+=("cross-build-with-push-bucket")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true BUCKET=bucket STAGING=true STAGING_PREFIX=${PREFIX} cross-build | diff -u <(want_cross_build_with_push_bucket_staging) - || failed+=("cross-build-with-push-bucket-staging")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true GH_RELEASE=ghrelease cross-build | diff -u <(want_cross_build_with_push_ghrelease) - || failed+=("cross-build-with-push-ghrelease")
-
-  make --no-print-directory -C "${ROOT_DIR}" cross-image | diff -u <(want_cross_image) - || failed+=("cross-image")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true cross-image | diff -u <(want_cross_image_with_push) - || failed+=("cross-image-with-push")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true BUCKET=bucket STAGING=true STAGING_PREFIX=${PREFIX} STAGING_IMAGE_PREFIX=${IMAGE_PREFIX} cross-image | diff -u <(want_cross_image_with_push_staging) - || failed+=("cross-image-with-push-staging")
-
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=nerdctl cross-image | diff -u <(want_cross_image_nerdctl) - || failed+=("cross-image-nerdctl")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=nerdctl PUSH=true cross-image | diff -u <(want_cross_image_nerdctl_with_push) - || failed+=("cross-image-nerdctl-with-push")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=nerdctl PUSH=true BUCKET=bucket STAGING=true STAGING_PREFIX=${PREFIX} STAGING_IMAGE_PREFIX=${IMAGE_PREFIX} cross-image | diff -u <(want_cross_image_nerdctl_with_push_staging) - || failed+=("cross-image-nerdctl-with-push-staging")
-
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=podman cross-image | diff -u <(want_cross_image_podman) - || failed+=("cross-image-podman")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=podman PUSH=true cross-image | diff -u <(want_cross_image_podman_with_push) - || failed+=("cross-image-podman-with-push")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=podman PUSH=true BUCKET=bucket STAGING=true STAGING_PREFIX=${PREFIX} STAGING_IMAGE_PREFIX=${IMAGE_PREFIX} cross-image | diff -u <(want_cross_image_podman_with_push_staging) - || failed+=("cross-image-podman-with-push-staging")
-
-  make --no-print-directory -C "${ROOT_DIR}" cross-cluster-image | diff -u <(want_cross_cluster_image) - || failed+=("cross-cluster-image")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true cross-cluster-image | diff -u <(want_cross_cluster_image_with_push) - || failed+=("cross-cluster-image-with-push")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true BUCKET=bucket STAGING=true STAGING_PREFIX=${PREFIX} STAGING_IMAGE_PREFIX=${IMAGE_PREFIX} cross-cluster-image | diff -u <(want_cross_cluster_image_with_push_staging) - || failed+=("cross-cluster-image-with-push-staging")
-
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=nerdctl cross-cluster-image | diff -u <(want_cross_cluster_image_nerdctl) - || failed+=("cross-cluster-image-nerdctl")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=nerdctl PUSH=true cross-cluster-image | diff -u <(want_cross_cluster_image_nerdctl_with_push) - || failed+=("cross-cluster-image-nerdctl-with-push")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=nerdctl PUSH=true BUCKET=bucket STAGING=true STAGING_PREFIX=${PREFIX} STAGING_IMAGE_PREFIX=${IMAGE_PREFIX} cross-cluster-image | diff -u <(want_cross_cluster_image_nerdctl_with_push_staging) - || failed+=("cross-cluster-image-nerdctl-with-push-staging")
-
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=podman cross-cluster-image | diff -u <(want_cross_cluster_image_podman) - || failed+=("cross-cluster-image-podman")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=podman PUSH=true cross-cluster-image | diff -u <(want_cross_cluster_image_podman_with_push) - || failed+=("cross-cluster-image-podman-with-push")
-  make --no-print-directory -C "${ROOT_DIR}" BUILDER=podman PUSH=true BUCKET=bucket STAGING=true STAGING_PREFIX=${PREFIX} STAGING_IMAGE_PREFIX=${IMAGE_PREFIX} cross-cluster-image | diff -u <(want_cross_cluster_image_podman_with_push_staging) - || failed+=("cross-cluster-image-podman-with-push-staging")
-
-  make --no-print-directory -C "${ROOT_DIR}" MANIFESTS=kwok IMAGE_PREFIX=${IMAGE_PREFIX} manifests | diff -u <(want_manifests) - || failed+=("manifests")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true MANIFESTS=kwok GH_RELEASE=ghrelease IMAGE_PREFIX=${IMAGE_PREFIX} manifests | diff -u <(manifests_with_push_ghrelease) - || failed+=("manifests-with-push-ghrelease")
-  make --no-print-directory -C "${ROOT_DIR}" PUSH=true MANIFESTS=kwok BUCKET=bucket STAGING=true STAGING_PREFIX=${PREFIX} IMAGE_PREFIX=${IMAGE_PREFIX} manifests | diff -u <(manifests_with_push_bucket) - || failed+=("manifests-with-push-bucket")
+  export IMAGE_PREFIX=image-prefix
+  export PREFIX=prefix
+  export STAGING_PREFIX=staging-prefix
+  export NUMBER_SUPPORTED_KUBE_RELEASES="${NUMBER_SUPPORTED_KUBE_RELEASES:-3}"
+
+  while read -r line; do
+    local args="${line}"
+    local name="${args%% *}"
+    args="${args#* }"
+    local kind="${args%% *}"
+    args="${args#* }"
+
+    echo "------------------------------"
+    echo "Testing ${name}, ${args}"
+    want_file="${DIR}/testdata/${name}.txt"
+    want="$(<"${want_file}")"
+
+    IFS=' ' read -r -a argsArray <<<"${args}"
+
+    got="$(make --no-print-directory -C "${ROOT_DIR}" "${argsArray[@]}" | clear_testdata "${kind}")"
+    if [[ "${got}" != "${want}" ]]; then
+      diff -u <(echo "${want}") <(echo "${got}")
+      failed+=("${name}")
+      if [[ "${UPDATE_DRY_RUN_TESTDATA}" == "true" ]]; then
+        echo "${got}" >"${want_file}"
+      else
+        # prints the command to update the testdata
+        echo "------------------------------"
+        echo "cat <<ALL >${want_file}"
+        echo "${got}"
+        echo "ALL"
+      fi
+    fi
+  done <"${CASES_PATH}"
+  echo "------------------------------"
 
   if [[ "${#failed[@]}" -ne 0 ]]; then
     echo "Error: Some tests failed"
diff --git a/test/release/cases.txt b/test/release/cases.txt
new file mode 100644
index 00000000..9a8b8c4b
--- /dev/null
+++ b/test/release/cases.txt
@@ -0,0 +1,47 @@
+build GOOS,GOARCH build
+build-with-push-bucket GOOS,GOARCH PUSH=true BUCKET=bucket build
+build-with-push-bucket-staging GOOS,GOARCH PUSH=true BUCKET=bucket STAGING=true build
+build-with-push-ghrelease GOOS,GOARCH PUSH=true GH_RELEASE=ghrelease build
+image GOARCH image
+image-with-push GOARCH PUSH=true image
+image-with-push-staging GOARCH PUSH=true BUCKET=bucket STAGING=true image
+image-nerdctl GOARCH BUILDER=nerdctl image
+image-nerdctl-with-push GOARCH BUILDER=nerdctl PUSH=true image
+image-nerdctl-with-push-staging GOARCH BUILDER=nerdctl PUSH=true BUCKET=bucket STAGING=true image
+image-podman GOARCH BUILDER=podman image
+image-podman-with-push GOARCH BUILDER=podman PUSH=true image
+image-podman-with-push-staging GOARCH BUILDER=podman PUSH=true BUCKET=bucket STAGING=true image
+cluster-image GOARCH cluster-image
+cluster-image-with-push GOARCH PUSH=true cluster-image
+cluster-image-with-push-staging GOARCH PUSH=true BUCKET=bucket STAGING=true cluster-image
+cluster-image-nerdctl GOARCH BUILDER=nerdctl cluster-image
+cluster-image-nerdctl-with-push GOARCH BUILDER=nerdctl PUSH=true cluster-image
+cluster-image-nerdctl-with-push-staging GOARCH BUILDER=nerdctl PUSH=true BUCKET=bucket STAGING=true cluster-image
+cluster-image-podman GOARCH BUILDER=podman cluster-image
+cluster-image-podman-with-push GOARCH BUILDER=podman PUSH=true cluster-image
+cluster-image-podman-with-push-staging GOARCH BUILDER=podman PUSH=true BUCKET=bucket STAGING=true cluster-image
+cross-build NONE cross-build
+cross-build-with-push-bucket NONE PUSH=true BUCKET=bucket cross-build
+cross-build-with-push-bucket-staging NONE PUSH=true BUCKET=bucket STAGING=true cross-build
+cross-build-with-push-ghrelease NONE PUSH=true GH_RELEASE=ghrelease cross-build
+cross-image NONE cross-image
+cross-image-with-push NONE PUSH=true cross-image
+cross-image-with-push-staging NONE PUSH=true BUCKET=bucket STAGING=true cross-image
+cross-image-nerdctl NONE BUILDER=nerdctl cross-image
+cross-image-nerdctl-with-push NONE BUILDER=nerdctl PUSH=true cross-image
+cross-image-nerdctl-with-push-staging NONE BUILDER=nerdctl PUSH=true BUCKET=bucket STAGING=true cross-image
+cross-image-podman NONE BUILDER=podman cross-image
+cross-image-podman-with-push NONE BUILDER=podman PUSH=true cross-image
+cross-image-podman-with-push-staging NONE BUILDER=podman PUSH=true BUCKET=bucket STAGING=true cross-image
+cross-cluster-image NONE cross-cluster-image
+cross-cluster-image-with-push NONE PUSH=true cross-cluster-image
+cross-cluster-image-with-push-staging NONE PUSH=true BUCKET=bucket STAGING=true cross-cluster-image
+cross-cluster-image-nerdctl NONE BUILDER=nerdctl cross-cluster-image
+cross-cluster-image-nerdctl-with-push NONE BUILDER=nerdctl PUSH=true cross-cluster-image
+cross-cluster-image-nerdctl-with-push-staging NONE BUILDER=nerdctl PUSH=true BUCKET=bucket STAGING=true cross-cluster-image
+cross-cluster-image-podman NONE BUILDER=podman cross-cluster-image
+cross-cluster-image-podman-with-push NONE BUILDER=podman PUSH=true cross-cluster-image
+cross-cluster-image-podman-with-push-staging NONE BUILDER=podman PUSH=true BUCKET=bucket STAGING=true cross-cluster-image
+manifests GOOS,GOARCH MANIFESTS=kwok manifests
+manifests-with-push-ghrelease GOOS,GOARCH PUSH=true MANIFESTS=kwok GH_RELEASE=ghrelease manifests
+manifests-with-push-bucket GOOS,GOARCH PUSH=true MANIFESTS=kwok BUCKET=bucket STAGING=true manifests
diff --git a/test/release/testdata/build-with-push-bucket-staging.txt b/test/release/testdata/build-with-push-bucket-staging.txt
new file mode 100644
index 00000000..82cda67c
--- /dev/null
+++ b/test/release/testdata/build-with-push-bucket-staging.txt
@@ -0,0 +1,4 @@
+GOOS=<OS> GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/<OS>/<ARCH>/kwok ./cmd/kwok
+gsutil cp -P ./bin/<OS>/<ARCH>/kwok bucket/releases/staging-prefix-<VERSION>/bin/<OS>/<ARCH>/kwok
+GOOS=<OS> GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/<OS>/<ARCH>/kwokctl ./cmd/kwokctl
+gsutil cp -P ./bin/<OS>/<ARCH>/kwokctl bucket/releases/staging-prefix-<VERSION>/bin/<OS>/<ARCH>/kwokctl
diff --git a/test/release/testdata/build-with-push-bucket.txt b/test/release/testdata/build-with-push-bucket.txt
new file mode 100644
index 00000000..8a4ed1a2
--- /dev/null
+++ b/test/release/testdata/build-with-push-bucket.txt
@@ -0,0 +1,4 @@
+GOOS=<OS> GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/<OS>/<ARCH>/kwok ./cmd/kwok
+gsutil cp -P ./bin/<OS>/<ARCH>/kwok bucket/releases/<VERSION>/bin/<OS>/<ARCH>/kwok
+GOOS=<OS> GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/<OS>/<ARCH>/kwokctl ./cmd/kwokctl
+gsutil cp -P ./bin/<OS>/<ARCH>/kwokctl bucket/releases/<VERSION>/bin/<OS>/<ARCH>/kwokctl
diff --git a/test/release/testdata/build-with-push-ghrelease.txt b/test/release/testdata/build-with-push-ghrelease.txt
new file mode 100644
index 00000000..1930c544
--- /dev/null
+++ b/test/release/testdata/build-with-push-ghrelease.txt
@@ -0,0 +1,6 @@
+GOOS=<OS> GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/<OS>/<ARCH>/kwok ./cmd/kwok
+cp ./bin/<OS>/<ARCH>/kwok kwok-<OS>-<ARCH>
+gh -R ghrelease release upload <VERSION> kwok-<OS>-<ARCH>
+GOOS=<OS> GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/<OS>/<ARCH>/kwokctl ./cmd/kwokctl
+cp ./bin/<OS>/<ARCH>/kwokctl kwokctl-<OS>-<ARCH>
+gh -R ghrelease release upload <VERSION> kwokctl-<OS>-<ARCH>
diff --git a/test/release/testdata/build.txt b/test/release/testdata/build.txt
new file mode 100644
index 00000000..23a1edc1
--- /dev/null
+++ b/test/release/testdata/build.txt
@@ -0,0 +1,2 @@
+GOOS=<OS> GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/<OS>/<ARCH>/kwok ./cmd/kwok
+GOOS=<OS> GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/<OS>/<ARCH>/kwokctl ./cmd/kwokctl
diff --git a/test/release/testdata/cluster-image-nerdctl-with-push-staging.txt b/test/release/testdata/cluster-image-nerdctl-with-push-staging.txt
new file mode 100644
index 00000000..fdfd4f0b
--- /dev/null
+++ b/test/release/testdata/cluster-image-nerdctl-with-push-staging.txt
@@ -0,0 +1,6 @@
+nerdctl build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.28.0 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+nerdctl push --platform=linux/<ARCH> image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.28.0
+nerdctl build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.27.3 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+nerdctl push --platform=linux/<ARCH> image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.27.3
+nerdctl build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.26.6 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+nerdctl push --platform=linux/<ARCH> image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.26.6
diff --git a/test/release/testdata/cluster-image-nerdctl-with-push.txt b/test/release/testdata/cluster-image-nerdctl-with-push.txt
new file mode 100644
index 00000000..fb20585a
--- /dev/null
+++ b/test/release/testdata/cluster-image-nerdctl-with-push.txt
@@ -0,0 +1,6 @@
+nerdctl build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:<VERSION>-k8s.v1.28.0 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+nerdctl push --platform=linux/<ARCH> image-prefix/cluster:<VERSION>-k8s.v1.28.0
+nerdctl build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:<VERSION>-k8s.v1.27.3 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+nerdctl push --platform=linux/<ARCH> image-prefix/cluster:<VERSION>-k8s.v1.27.3
+nerdctl build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:<VERSION>-k8s.v1.26.6 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+nerdctl push --platform=linux/<ARCH> image-prefix/cluster:<VERSION>-k8s.v1.26.6
diff --git a/test/release/testdata/cluster-image-nerdctl.txt b/test/release/testdata/cluster-image-nerdctl.txt
new file mode 100644
index 00000000..458f7245
--- /dev/null
+++ b/test/release/testdata/cluster-image-nerdctl.txt
@@ -0,0 +1,3 @@
+nerdctl build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:<VERSION>-k8s.v1.28.0 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+nerdctl build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:<VERSION>-k8s.v1.27.3 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+nerdctl build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:<VERSION>-k8s.v1.26.6 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
diff --git a/test/release/testdata/cluster-image-podman-with-push-staging.txt b/test/release/testdata/cluster-image-podman-with-push-staging.txt
new file mode 100644
index 00000000..ecde267d
--- /dev/null
+++ b/test/release/testdata/cluster-image-podman-with-push-staging.txt
@@ -0,0 +1,6 @@
+podman build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.28.0 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+podman push --platform=linux/<ARCH> image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.28.0
+podman build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.27.3 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+podman push --platform=linux/<ARCH> image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.27.3
+podman build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.26.6 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+podman push --platform=linux/<ARCH> image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.26.6
diff --git a/test/release/testdata/cluster-image-podman-with-push.txt b/test/release/testdata/cluster-image-podman-with-push.txt
new file mode 100644
index 00000000..5a8797be
--- /dev/null
+++ b/test/release/testdata/cluster-image-podman-with-push.txt
@@ -0,0 +1,6 @@
+podman build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:<VERSION>-k8s.v1.28.0 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+podman push --platform=linux/<ARCH> image-prefix/cluster:<VERSION>-k8s.v1.28.0
+podman build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:<VERSION>-k8s.v1.27.3 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+podman push --platform=linux/<ARCH> image-prefix/cluster:<VERSION>-k8s.v1.27.3
+podman build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:<VERSION>-k8s.v1.26.6 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+podman push --platform=linux/<ARCH> image-prefix/cluster:<VERSION>-k8s.v1.26.6
diff --git a/test/release/testdata/cluster-image-podman.txt b/test/release/testdata/cluster-image-podman.txt
new file mode 100644
index 00000000..a208b78d
--- /dev/null
+++ b/test/release/testdata/cluster-image-podman.txt
@@ -0,0 +1,3 @@
+podman build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:<VERSION>-k8s.v1.28.0 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+podman build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:<VERSION>-k8s.v1.27.3 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
+podman build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:<VERSION>-k8s.v1.26.6 --platform=linux/<ARCH> -f ./images/cluster/Dockerfile .
diff --git a/test/release/testdata/cluster-image-with-push-staging.txt b/test/release/testdata/cluster-image-with-push-staging.txt
new file mode 100644
index 00000000..9ce7c9ee
--- /dev/null
+++ b/test/release/testdata/cluster-image-with-push-staging.txt
@@ -0,0 +1,3 @@
+docker buildx build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.28.0 --platform=linux/<ARCH> --push -f ./images/cluster/Dockerfile .
+docker buildx build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.27.3 --platform=linux/<ARCH> --push -f ./images/cluster/Dockerfile .
+docker buildx build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.26.6 --platform=linux/<ARCH> --push -f ./images/cluster/Dockerfile .
diff --git a/test/release/testdata/cluster-image-with-push.txt b/test/release/testdata/cluster-image-with-push.txt
new file mode 100644
index 00000000..f620bd42
--- /dev/null
+++ b/test/release/testdata/cluster-image-with-push.txt
@@ -0,0 +1,3 @@
+docker buildx build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:<VERSION>-k8s.v1.28.0 --platform=linux/<ARCH> --push -f ./images/cluster/Dockerfile .
+docker buildx build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:<VERSION>-k8s.v1.27.3 --platform=linux/<ARCH> --push -f ./images/cluster/Dockerfile .
+docker buildx build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:<VERSION>-k8s.v1.26.6 --platform=linux/<ARCH> --push -f ./images/cluster/Dockerfile .
diff --git a/test/release/testdata/cluster-image.txt b/test/release/testdata/cluster-image.txt
new file mode 100644
index 00000000..a7fae646
--- /dev/null
+++ b/test/release/testdata/cluster-image.txt
@@ -0,0 +1,3 @@
+docker buildx build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:<VERSION>-k8s.v1.28.0 --platform=linux/<ARCH> --load -f ./images/cluster/Dockerfile .
+docker buildx build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:<VERSION>-k8s.v1.27.3 --platform=linux/<ARCH> --load -f ./images/cluster/Dockerfile .
+docker buildx build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:<VERSION>-k8s.v1.26.6 --platform=linux/<ARCH> --load -f ./images/cluster/Dockerfile .
diff --git a/test/release/testdata/cross-build-with-push-bucket-staging.txt b/test/release/testdata/cross-build-with-push-bucket-staging.txt
new file mode 100644
index 00000000..58d8340e
--- /dev/null
+++ b/test/release/testdata/cross-build-with-push-bucket-staging.txt
@@ -0,0 +1,24 @@
+GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwok ./cmd/kwok
+gsutil cp -P ./bin/linux/amd64/kwok bucket/releases/staging-prefix-<VERSION>/bin/linux/amd64/kwok
+GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwokctl ./cmd/kwokctl
+gsutil cp -P ./bin/linux/amd64/kwokctl bucket/releases/staging-prefix-<VERSION>/bin/linux/amd64/kwokctl
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwok ./cmd/kwok
+gsutil cp -P ./bin/linux/arm64/kwok bucket/releases/staging-prefix-<VERSION>/bin/linux/arm64/kwok
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwokctl ./cmd/kwokctl
+gsutil cp -P ./bin/linux/arm64/kwokctl bucket/releases/staging-prefix-<VERSION>/bin/linux/arm64/kwokctl
+GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwok ./cmd/kwok
+gsutil cp -P ./bin/darwin/amd64/kwok bucket/releases/staging-prefix-<VERSION>/bin/darwin/amd64/kwok
+GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwokctl ./cmd/kwokctl
+gsutil cp -P ./bin/darwin/amd64/kwokctl bucket/releases/staging-prefix-<VERSION>/bin/darwin/amd64/kwokctl
+GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwok ./cmd/kwok
+gsutil cp -P ./bin/darwin/arm64/kwok bucket/releases/staging-prefix-<VERSION>/bin/darwin/arm64/kwok
+GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwokctl ./cmd/kwokctl
+gsutil cp -P ./bin/darwin/arm64/kwokctl bucket/releases/staging-prefix-<VERSION>/bin/darwin/arm64/kwokctl
+GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwok.exe ./cmd/kwok
+gsutil cp -P ./bin/windows/amd64/kwok.exe bucket/releases/staging-prefix-<VERSION>/bin/windows/amd64/kwok.exe
+GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwokctl.exe ./cmd/kwokctl
+gsutil cp -P ./bin/windows/amd64/kwokctl.exe bucket/releases/staging-prefix-<VERSION>/bin/windows/amd64/kwokctl.exe
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwok.exe ./cmd/kwok
+gsutil cp -P ./bin/windows/arm64/kwok.exe bucket/releases/staging-prefix-<VERSION>/bin/windows/arm64/kwok.exe
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwokctl.exe ./cmd/kwokctl
+gsutil cp -P ./bin/windows/arm64/kwokctl.exe bucket/releases/staging-prefix-<VERSION>/bin/windows/arm64/kwokctl.exe
diff --git a/test/release/testdata/cross-build-with-push-bucket.txt b/test/release/testdata/cross-build-with-push-bucket.txt
new file mode 100644
index 00000000..4254c497
--- /dev/null
+++ b/test/release/testdata/cross-build-with-push-bucket.txt
@@ -0,0 +1,24 @@
+GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwok ./cmd/kwok
+gsutil cp -P ./bin/linux/amd64/kwok bucket/releases/<VERSION>/bin/linux/amd64/kwok
+GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwokctl ./cmd/kwokctl
+gsutil cp -P ./bin/linux/amd64/kwokctl bucket/releases/<VERSION>/bin/linux/amd64/kwokctl
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwok ./cmd/kwok
+gsutil cp -P ./bin/linux/arm64/kwok bucket/releases/<VERSION>/bin/linux/arm64/kwok
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwokctl ./cmd/kwokctl
+gsutil cp -P ./bin/linux/arm64/kwokctl bucket/releases/<VERSION>/bin/linux/arm64/kwokctl
+GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwok ./cmd/kwok
+gsutil cp -P ./bin/darwin/amd64/kwok bucket/releases/<VERSION>/bin/darwin/amd64/kwok
+GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwokctl ./cmd/kwokctl
+gsutil cp -P ./bin/darwin/amd64/kwokctl bucket/releases/<VERSION>/bin/darwin/amd64/kwokctl
+GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwok ./cmd/kwok
+gsutil cp -P ./bin/darwin/arm64/kwok bucket/releases/<VERSION>/bin/darwin/arm64/kwok
+GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwokctl ./cmd/kwokctl
+gsutil cp -P ./bin/darwin/arm64/kwokctl bucket/releases/<VERSION>/bin/darwin/arm64/kwokctl
+GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwok.exe ./cmd/kwok
+gsutil cp -P ./bin/windows/amd64/kwok.exe bucket/releases/<VERSION>/bin/windows/amd64/kwok.exe
+GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwokctl.exe ./cmd/kwokctl
+gsutil cp -P ./bin/windows/amd64/kwokctl.exe bucket/releases/<VERSION>/bin/windows/amd64/kwokctl.exe
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwok.exe ./cmd/kwok
+gsutil cp -P ./bin/windows/arm64/kwok.exe bucket/releases/<VERSION>/bin/windows/arm64/kwok.exe
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwokctl.exe ./cmd/kwokctl
+gsutil cp -P ./bin/windows/arm64/kwokctl.exe bucket/releases/<VERSION>/bin/windows/arm64/kwokctl.exe
diff --git a/test/release/testdata/cross-build-with-push-ghrelease.txt b/test/release/testdata/cross-build-with-push-ghrelease.txt
new file mode 100644
index 00000000..95e45aaf
--- /dev/null
+++ b/test/release/testdata/cross-build-with-push-ghrelease.txt
@@ -0,0 +1,36 @@
+GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwok ./cmd/kwok
+cp ./bin/linux/amd64/kwok kwok-linux-amd64
+gh -R ghrelease release upload <VERSION> kwok-linux-amd64
+GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwokctl ./cmd/kwokctl
+cp ./bin/linux/amd64/kwokctl kwokctl-linux-amd64
+gh -R ghrelease release upload <VERSION> kwokctl-linux-amd64
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwok ./cmd/kwok
+cp ./bin/linux/arm64/kwok kwok-linux-arm64
+gh -R ghrelease release upload <VERSION> kwok-linux-arm64
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwokctl ./cmd/kwokctl
+cp ./bin/linux/arm64/kwokctl kwokctl-linux-arm64
+gh -R ghrelease release upload <VERSION> kwokctl-linux-arm64
+GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwok ./cmd/kwok
+cp ./bin/darwin/amd64/kwok kwok-darwin-amd64
+gh -R ghrelease release upload <VERSION> kwok-darwin-amd64
+GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwokctl ./cmd/kwokctl
+cp ./bin/darwin/amd64/kwokctl kwokctl-darwin-amd64
+gh -R ghrelease release upload <VERSION> kwokctl-darwin-amd64
+GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwok ./cmd/kwok
+cp ./bin/darwin/arm64/kwok kwok-darwin-arm64
+gh -R ghrelease release upload <VERSION> kwok-darwin-arm64
+GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwokctl ./cmd/kwokctl
+cp ./bin/darwin/arm64/kwokctl kwokctl-darwin-arm64
+gh -R ghrelease release upload <VERSION> kwokctl-darwin-arm64
+GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwok.exe ./cmd/kwok
+cp ./bin/windows/amd64/kwok.exe kwok-windows-amd64.exe
+gh -R ghrelease release upload <VERSION> kwok-windows-amd64.exe
+GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwokctl.exe ./cmd/kwokctl
+cp ./bin/windows/amd64/kwokctl.exe kwokctl-windows-amd64.exe
+gh -R ghrelease release upload <VERSION> kwokctl-windows-amd64.exe
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwok.exe ./cmd/kwok
+cp ./bin/windows/arm64/kwok.exe kwok-windows-arm64.exe
+gh -R ghrelease release upload <VERSION> kwok-windows-arm64.exe
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwokctl.exe ./cmd/kwokctl
+cp ./bin/windows/arm64/kwokctl.exe kwokctl-windows-arm64.exe
+gh -R ghrelease release upload <VERSION> kwokctl-windows-arm64.exe
diff --git a/test/release/testdata/cross-build-with-push-staging.txt b/test/release/testdata/cross-build-with-push-staging.txt
new file mode 100644
index 00000000..49fc376c
--- /dev/null
+++ b/test/release/testdata/cross-build-with-push-staging.txt
@@ -0,0 +1,24 @@
+GOOS=linux GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/linux/<ARCH>/kwok ./cmd/kwok
+gsutil cp -P ./bin/linux/<ARCH>/kwok bucket/releases/prefix-<VERSION>/bin/linux/<ARCH>/kwok
+GOOS=linux GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/linux/<ARCH>/kwokctl ./cmd/kwokctl
+gsutil cp -P ./bin/linux/<ARCH>/kwokctl bucket/releases/prefix-<VERSION>/bin/linux/<ARCH>/kwokctl
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/linux/arm64/kwok ./cmd/kwok
+gsutil cp -P ./bin/linux/arm64/kwok bucket/releases/prefix-<VERSION>/bin/linux/arm64/kwok
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/linux/arm64/kwokctl ./cmd/kwokctl
+gsutil cp -P ./bin/linux/arm64/kwokctl bucket/releases/prefix-<VERSION>/bin/linux/arm64/kwokctl
+GOOS=<OS> GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/<OS>/<ARCH>/kwok ./cmd/kwok
+gsutil cp -P ./bin/<OS>/<ARCH>/kwok bucket/releases/prefix-<VERSION>/bin/<OS>/<ARCH>/kwok
+GOOS=<OS> GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/<OS>/<ARCH>/kwokctl ./cmd/kwokctl
+gsutil cp -P ./bin/<OS>/<ARCH>/kwokctl bucket/releases/prefix-<VERSION>/bin/<OS>/<ARCH>/kwokctl
+GOOS=<OS> GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/<OS>/arm64/kwok ./cmd/kwok
+gsutil cp -P ./bin/<OS>/arm64/kwok bucket/releases/prefix-<VERSION>/bin/<OS>/arm64/kwok
+GOOS=<OS> GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/<OS>/arm64/kwokctl ./cmd/kwokctl
+gsutil cp -P ./bin/<OS>/arm64/kwokctl bucket/releases/prefix-<VERSION>/bin/<OS>/arm64/kwokctl
+GOOS=windows GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/windows/<ARCH>/kwok.exe ./cmd/kwok
+gsutil cp -P ./bin/windows/<ARCH>/kwok.exe bucket/releases/prefix-<VERSION>/bin/windows/<ARCH>/kwok.exe
+GOOS=windows GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/windows/<ARCH>/kwokctl.exe ./cmd/kwokctl
+gsutil cp -P ./bin/windows/<ARCH>/kwokctl.exe bucket/releases/prefix-<VERSION>/bin/windows/<ARCH>/kwokctl.exe
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/windows/arm64/kwok.exe ./cmd/kwok
+gsutil cp -P ./bin/windows/arm64/kwok.exe bucket/releases/prefix-<VERSION>/bin/windows/arm64/kwok.exe
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/windows/arm64/kwokctl.exe ./cmd/kwokctl
+gsutil cp -P ./bin/windows/arm64/kwokctl.exe bucket/releases/prefix-<VERSION>/bin/windows/arm64/kwokctl.exe
diff --git a/test/release/testdata/cross-build-with-push.txt b/test/release/testdata/cross-build-with-push.txt
new file mode 100644
index 00000000..4706bea3
--- /dev/null
+++ b/test/release/testdata/cross-build-with-push.txt
@@ -0,0 +1,12 @@
+GOOS=linux GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/linux/<ARCH>/kwok ./cmd/kwok
+GOOS=linux GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/linux/<ARCH>/kwokctl ./cmd/kwokctl
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/linux/arm64/kwok ./cmd/kwok
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/linux/arm64/kwokctl ./cmd/kwokctl
+GOOS=<OS> GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/<OS>/<ARCH>/kwok ./cmd/kwok
+GOOS=<OS> GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/<OS>/<ARCH>/kwokctl ./cmd/kwokctl
+GOOS=<OS> GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/<OS>/arm64/kwok ./cmd/kwok
+GOOS=<OS> GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/<OS>/arm64/kwokctl ./cmd/kwokctl
+GOOS=windows GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/windows/<ARCH>/kwok.exe ./cmd/kwok
+GOOS=windows GOARCH=<ARCH> go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/windows/<ARCH>/kwokctl.exe ./cmd/kwokctl
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/windows/arm64/kwok.exe ./cmd/kwok
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0' -o ./bin/windows/arm64/kwokctl.exe ./cmd/kwokctl
diff --git a/test/release/testdata/cross-build.txt b/test/release/testdata/cross-build.txt
new file mode 100644
index 00000000..af5d179c
--- /dev/null
+++ b/test/release/testdata/cross-build.txt
@@ -0,0 +1,12 @@
+GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwok ./cmd/kwok
+GOOS=linux GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/amd64/kwokctl ./cmd/kwokctl
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwok ./cmd/kwok
+GOOS=linux GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/linux/arm64/kwokctl ./cmd/kwokctl
+GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwok ./cmd/kwok
+GOOS=darwin GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/amd64/kwokctl ./cmd/kwokctl
+GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwok ./cmd/kwok
+GOOS=darwin GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/darwin/arm64/kwokctl ./cmd/kwokctl
+GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwok.exe ./cmd/kwok
+GOOS=windows GOARCH=amd64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/amd64/kwokctl.exe ./cmd/kwokctl
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwok.exe ./cmd/kwok
+GOOS=windows GOARCH=arm64 go build -ldflags '-X sigs.k8s.io/kwok/pkg/consts.Version=<VERSION> -X sigs.k8s.io/kwok/pkg/consts.KubeVersion=v1.28.0 -X sigs.k8s.io/kwok/pkg/consts.ImagePrefix=image-prefix' -o ./bin/windows/arm64/kwokctl.exe ./cmd/kwokctl
diff --git a/test/release/testdata/cross-cluster-image-nerdctl-with-push-staging.txt b/test/release/testdata/cross-cluster-image-nerdctl-with-push-staging.txt
new file mode 100644
index 00000000..ddebf2c3
--- /dev/null
+++ b/test/release/testdata/cross-cluster-image-nerdctl-with-push-staging.txt
@@ -0,0 +1,6 @@
+nerdctl build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.28.0 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+nerdctl push --platform=linux/amd64 --platform=linux/arm64 image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.28.0
+nerdctl build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.27.3 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+nerdctl push --platform=linux/amd64 --platform=linux/arm64 image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.27.3
+nerdctl build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.26.6 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+nerdctl push --platform=linux/amd64 --platform=linux/arm64 image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.26.6
diff --git a/test/release/testdata/cross-cluster-image-nerdctl-with-push.txt b/test/release/testdata/cross-cluster-image-nerdctl-with-push.txt
new file mode 100644
index 00000000..deab3c14
--- /dev/null
+++ b/test/release/testdata/cross-cluster-image-nerdctl-with-push.txt
@@ -0,0 +1,6 @@
+nerdctl build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:<VERSION>-k8s.v1.28.0 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+nerdctl push --platform=linux/amd64 --platform=linux/arm64 image-prefix/cluster:<VERSION>-k8s.v1.28.0
+nerdctl build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:<VERSION>-k8s.v1.27.3 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+nerdctl push --platform=linux/amd64 --platform=linux/arm64 image-prefix/cluster:<VERSION>-k8s.v1.27.3
+nerdctl build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:<VERSION>-k8s.v1.26.6 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+nerdctl push --platform=linux/amd64 --platform=linux/arm64 image-prefix/cluster:<VERSION>-k8s.v1.26.6
diff --git a/test/release/testdata/cross-cluster-image-nerdctl.txt b/test/release/testdata/cross-cluster-image-nerdctl.txt
new file mode 100644
index 00000000..d39bbbc9
--- /dev/null
+++ b/test/release/testdata/cross-cluster-image-nerdctl.txt
@@ -0,0 +1,3 @@
+nerdctl build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:<VERSION>-k8s.v1.28.0 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+nerdctl build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:<VERSION>-k8s.v1.27.3 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+nerdctl build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:<VERSION>-k8s.v1.26.6 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
diff --git a/test/release/testdata/cross-cluster-image-podman-with-push-staging.txt b/test/release/testdata/cross-cluster-image-podman-with-push-staging.txt
new file mode 100644
index 00000000..02feb194
--- /dev/null
+++ b/test/release/testdata/cross-cluster-image-podman-with-push-staging.txt
@@ -0,0 +1,6 @@
+podman build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.28.0 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+podman push --platform=linux/amd64 --platform=linux/arm64 image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.28.0
+podman build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.27.3 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+podman push --platform=linux/amd64 --platform=linux/arm64 image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.27.3
+podman build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.26.6 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+podman push --platform=linux/amd64 --platform=linux/arm64 image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.26.6
diff --git a/test/release/testdata/cross-cluster-image-podman-with-push.txt b/test/release/testdata/cross-cluster-image-podman-with-push.txt
new file mode 100644
index 00000000..bd099328
--- /dev/null
+++ b/test/release/testdata/cross-cluster-image-podman-with-push.txt
@@ -0,0 +1,6 @@
+podman build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:<VERSION>-k8s.v1.28.0 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+podman push --platform=linux/amd64 --platform=linux/arm64 image-prefix/cluster:<VERSION>-k8s.v1.28.0
+podman build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:<VERSION>-k8s.v1.27.3 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+podman push --platform=linux/amd64 --platform=linux/arm64 image-prefix/cluster:<VERSION>-k8s.v1.27.3
+podman build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:<VERSION>-k8s.v1.26.6 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+podman push --platform=linux/amd64 --platform=linux/arm64 image-prefix/cluster:<VERSION>-k8s.v1.26.6
diff --git a/test/release/testdata/cross-cluster-image-podman.txt b/test/release/testdata/cross-cluster-image-podman.txt
new file mode 100644
index 00000000..df9fb0d5
--- /dev/null
+++ b/test/release/testdata/cross-cluster-image-podman.txt
@@ -0,0 +1,3 @@
+podman build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:<VERSION>-k8s.v1.28.0 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+podman build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:<VERSION>-k8s.v1.27.3 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
+podman build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:<VERSION>-k8s.v1.26.6 --platform=linux/amd64 --platform=linux/arm64 -f ./images/cluster/Dockerfile .
diff --git a/test/release/testdata/cross-cluster-image-with-push-staging.txt b/test/release/testdata/cross-cluster-image-with-push-staging.txt
new file mode 100644
index 00000000..df4bf001
--- /dev/null
+++ b/test/release/testdata/cross-cluster-image-with-push-staging.txt
@@ -0,0 +1,3 @@
+docker buildx build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.28.0 --platform=linux/amd64 --platform=linux/arm64 --push -f ./images/cluster/Dockerfile .
+docker buildx build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.27.3 --platform=linux/amd64 --platform=linux/arm64 --push -f ./images/cluster/Dockerfile .
+docker buildx build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:staging-prefix-<VERSION>-k8s.v1.26.6 --platform=linux/amd64 --platform=linux/arm64 --push -f ./images/cluster/Dockerfile .
diff --git a/test/release/testdata/cross-cluster-image-with-push.txt b/test/release/testdata/cross-cluster-image-with-push.txt
new file mode 100644
index 00000000..e7f7fcad
--- /dev/null
+++ b/test/release/testdata/cross-cluster-image-with-push.txt
@@ -0,0 +1,3 @@
+docker buildx build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:<VERSION>-k8s.v1.28.0 --platform=linux/amd64 --platform=linux/arm64 --push -f ./images/cluster/Dockerfile .
+docker buildx build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:<VERSION>-k8s.v1.27.3 --platform=linux/amd64 --platform=linux/arm64 --push -f ./images/cluster/Dockerfile .
+docker buildx build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:<VERSION>-k8s.v1.26.6 --platform=linux/amd64 --platform=linux/arm64 --push -f ./images/cluster/Dockerfile .
diff --git a/test/release/testdata/cross-cluster-image.txt b/test/release/testdata/cross-cluster-image.txt
new file mode 100644
index 00000000..57a130aa
--- /dev/null
+++ b/test/release/testdata/cross-cluster-image.txt
@@ -0,0 +1,3 @@
+docker buildx build --build-arg=kube_version=v1.28.0 --tag=image-prefix/cluster:<VERSION>-k8s.v1.28.0 --platform=linux/amd64 --platform=linux/arm64 --load -f ./images/cluster/Dockerfile .
+docker buildx build --build-arg=kube_version=v1.27.3 --tag=image-prefix/cluster:<VERSION>-k8s.v1.27.3 --platform=linux/amd64 --platform=linux/arm64 --load -f ./images/cluster/Dockerfile .
+docker buildx build --build-arg=kube_version=v1.26.6 --tag=image-prefix/cluster:<VERSION>-k8s.v1.26.6 --platform=linux/amd64 --platform=linux/arm64 --load -f ./images/cluster/Dockerfile .
diff --git a/test/release/testdata/cross-image-nerdctl-with-push-staging.txt b/test/release/testdata/cross-image-nerdctl-with-push-staging.txt
new file mode 100644
index 00000000..7e4bb0fe
--- /dev/null
+++ b/test/release/testdata/cross-image-nerdctl-with-push-staging.txt
@@ -0,0 +1,2 @@
+nerdctl build --tag=image-prefix/kwok:staging-prefix-<VERSION> --platform=linux/amd64 --platform=linux/arm64 -f ./images/kwok/Dockerfile .
+nerdctl push --platform=linux/amd64 --platform=linux/arm64 image-prefix/kwok:staging-prefix-<VERSION>
diff --git a/test/release/testdata/cross-image-nerdctl-with-push.txt b/test/release/testdata/cross-image-nerdctl-with-push.txt
new file mode 100644
index 00000000..5fd9c4b8
--- /dev/null
+++ b/test/release/testdata/cross-image-nerdctl-with-push.txt
@@ -0,0 +1,2 @@
+nerdctl build --tag=image-prefix/kwok:<VERSION> --platform=linux/amd64 --platform=linux/arm64 -f ./images/kwok/Dockerfile .
+nerdctl push --platform=linux/amd64 --platform=linux/arm64 image-prefix/kwok:<VERSION>
diff --git a/test/release/testdata/cross-image-nerdctl.txt b/test/release/testdata/cross-image-nerdctl.txt
new file mode 100644
index 00000000..394ef580
--- /dev/null
+++ b/test/release/testdata/cross-image-nerdctl.txt
@@ -0,0 +1,1 @@
+nerdctl build --tag=image-prefix/kwok:<VERSION> --platform=linux/amd64 --platform=linux/arm64 -f ./images/kwok/Dockerfile .
diff --git a/test/release/testdata/cross-image-podman-with-push-staging.txt b/test/release/testdata/cross-image-podman-with-push-staging.txt
new file mode 100644
index 00000000..77092d3e
--- /dev/null
+++ b/test/release/testdata/cross-image-podman-with-push-staging.txt
@@ -0,0 +1,2 @@
+podman build --tag=image-prefix/kwok:staging-prefix-<VERSION> --platform=linux/amd64 --platform=linux/arm64 -f ./images/kwok/Dockerfile .
+podman push --platform=linux/amd64 --platform=linux/arm64 image-prefix/kwok:staging-prefix-<VERSION>
diff --git a/test/release/testdata/cross-image-podman-with-push.txt b/test/release/testdata/cross-image-podman-with-push.txt
new file mode 100644
index 00000000..b33e1ba7
--- /dev/null
+++ b/test/release/testdata/cross-image-podman-with-push.txt
@@ -0,0 +1,2 @@
+podman build --tag=image-prefix/kwok:<VERSION> --platform=linux/amd64 --platform=linux/arm64 -f ./images/kwok/Dockerfile .
+podman push --platform=linux/amd64 --platform=linux/arm64 image-prefix/kwok:<VERSION>
diff --git a/test/release/testdata/cross-image-podman.txt b/test/release/testdata/cross-image-podman.txt
new file mode 100644
index 00000000..3140a124
--- /dev/null
+++ b/test/release/testdata/cross-image-podman.txt
@@ -0,0 +1,1 @@
+podman build --tag=image-prefix/kwok:<VERSION> --platform=linux/amd64 --platform=linux/arm64 -f ./images/kwok/Dockerfile .
diff --git a/test/release/testdata/cross-image-with-push-staging.txt b/test/release/testdata/cross-image-with-push-staging.txt
new file mode 100644
index 00000000..d8fece03
--- /dev/null
+++ b/test/release/testdata/cross-image-with-push-staging.txt
@@ -0,0 +1,1 @@
+docker buildx build --tag=image-prefix/kwok:staging-prefix-<VERSION> --platform=linux/amd64 --platform=linux/arm64 --push -f ./images/kwok/Dockerfile .
diff --git a/test/release/testdata/cross-image-with-push.txt b/test/release/testdata/cross-image-with-push.txt
new file mode 100644
index 00000000..8ecc0eaa
--- /dev/null
+++ b/test/release/testdata/cross-image-with-push.txt
@@ -0,0 +1,1 @@
+docker buildx build --tag=image-prefix/kwok:<VERSION> --platform=linux/amd64 --platform=linux/arm64 --push -f ./images/kwok/Dockerfile .
diff --git a/test/release/testdata/cross-image.txt b/test/release/testdata/cross-image.txt
new file mode 100644
index 00000000..b350192e
--- /dev/null
+++ b/test/release/testdata/cross-image.txt
@@ -0,0 +1,1 @@
+docker buildx build --tag=image-prefix/kwok:<VERSION> --platform=linux/amd64 --platform=linux/arm64 --load -f ./images/kwok/Dockerfile .
diff --git a/test/release/testdata/image-nerdctl-with-push-staging.txt b/test/release/testdata/image-nerdctl-with-push-staging.txt
new file mode 100644
index 00000000..ec04163a
--- /dev/null
+++ b/test/release/testdata/image-nerdctl-with-push-staging.txt
@@ -0,0 +1,2 @@
+nerdctl build --tag=image-prefix/kwok:staging-prefix-<VERSION> --platform=linux/<ARCH> -f ./images/kwok/Dockerfile .
+nerdctl push --platform=linux/<ARCH> image-prefix/kwok:staging-prefix-<VERSION>
diff --git a/test/release/testdata/image-nerdctl-with-push.txt b/test/release/testdata/image-nerdctl-with-push.txt
new file mode 100644
index 00000000..9f8f2fb6
--- /dev/null
+++ b/test/release/testdata/image-nerdctl-with-push.txt
@@ -0,0 +1,2 @@
+nerdctl build --tag=image-prefix/kwok:<VERSION> --platform=linux/<ARCH> -f ./images/kwok/Dockerfile .
+nerdctl push --platform=linux/<ARCH> image-prefix/kwok:<VERSION>
diff --git a/test/release/testdata/image-nerdctl.txt b/test/release/testdata/image-nerdctl.txt
new file mode 100644
index 00000000..8777e342
--- /dev/null
+++ b/test/release/testdata/image-nerdctl.txt
@@ -0,0 +1,1 @@
+nerdctl build --tag=image-prefix/kwok:<VERSION> --platform=linux/<ARCH> -f ./images/kwok/Dockerfile .
diff --git a/test/release/testdata/image-podman-with-push-staging.txt b/test/release/testdata/image-podman-with-push-staging.txt
new file mode 100644
index 00000000..649161df
--- /dev/null
+++ b/test/release/testdata/image-podman-with-push-staging.txt
@@ -0,0 +1,2 @@
+podman build --tag=image-prefix/kwok:staging-prefix-<VERSION> --platform=linux/<ARCH> -f ./images/kwok/Dockerfile .
+podman push --platform=linux/<ARCH> image-prefix/kwok:staging-prefix-<VERSION>
diff --git a/test/release/testdata/image-podman-with-push.txt b/test/release/testdata/image-podman-with-push.txt
new file mode 100644
index 00000000..7ab4a9a6
--- /dev/null
+++ b/test/release/testdata/image-podman-with-push.txt
@@ -0,0 +1,2 @@
+podman build --tag=image-prefix/kwok:<VERSION> --platform=linux/<ARCH> -f ./images/kwok/Dockerfile .
+podman push --platform=linux/<ARCH> image-prefix/kwok:<VERSION>
diff --git a/test/release/testdata/image-podman.txt b/test/release/testdata/image-podman.txt
new file mode 100644
index 00000000..84924864
--- /dev/null
+++ b/test/release/testdata/image-podman.txt
@@ -0,0 +1,1 @@
+podman build --tag=image-prefix/kwok:<VERSION> --platform=linux/<ARCH> -f ./images/kwok/Dockerfile .
diff --git a/test/release/testdata/image-with-push-staging.txt b/test/release/testdata/image-with-push-staging.txt
new file mode 100644
index 00000000..14b78578
--- /dev/null
+++ b/test/release/testdata/image-with-push-staging.txt
@@ -0,0 +1,1 @@
+docker buildx build --tag=image-prefix/kwok:staging-prefix-<VERSION> --platform=linux/<ARCH> --push -f ./images/kwok/Dockerfile .
diff --git a/test/release/testdata/image-with-push.txt b/test/release/testdata/image-with-push.txt
new file mode 100644
index 00000000..5f836f62
--- /dev/null
+++ b/test/release/testdata/image-with-push.txt
@@ -0,0 +1,1 @@
+docker buildx build --tag=image-prefix/kwok:<VERSION> --platform=linux/<ARCH> --push -f ./images/kwok/Dockerfile .
diff --git a/test/release/testdata/image.txt b/test/release/testdata/image.txt
new file mode 100644
index 00000000..7734784d
--- /dev/null
+++ b/test/release/testdata/image.txt
@@ -0,0 +1,1 @@
+docker buildx build --tag=image-prefix/kwok:<VERSION> --platform=linux/<ARCH> --load -f ./images/kwok/Dockerfile .
diff --git a/test/release/testdata/manifests-with-push-bucket.txt b/test/release/testdata/manifests-with-push-bucket.txt
new file mode 100644
index 00000000..f4943fd6
--- /dev/null
+++ b/test/release/testdata/manifests-with-push-bucket.txt
@@ -0,0 +1,8 @@
+mkdir -p ./artifacts
+cp -r ./kustomize ./artifacts/
+cd ./artifacts/kustomize/kwok
+kustomize edit set image registry.k8s.io/kwok/kwok=image-prefix/kwok:<VERSION>
+cd -
+kustomize build ./artifacts/kustomize/kwok -o ./artifacts/kwok.yaml
+gsutil cp -P ./artifacts/kwok.yaml bucket/releases/staging-prefix-<VERSION>/manifests/kwok.yaml
+rm -r ./artifacts/kustomize
diff --git a/test/release/testdata/manifests-with-push-ghrelease.txt b/test/release/testdata/manifests-with-push-ghrelease.txt
new file mode 100644
index 00000000..74ded83e
--- /dev/null
+++ b/test/release/testdata/manifests-with-push-ghrelease.txt
@@ -0,0 +1,8 @@
+mkdir -p ./artifacts
+cp -r ./kustomize ./artifacts/
+cd ./artifacts/kustomize/kwok
+kustomize edit set image registry.k8s.io/kwok/kwok=image-prefix/kwok:<VERSION>
+cd -
+kustomize build ./artifacts/kustomize/kwok -o ./artifacts/kwok.yaml
+gh -R ghrelease release upload <VERSION> ./artifacts/kwok.yaml
+rm -r ./artifacts/kustomize
diff --git a/test/release/testdata/manifests.txt b/test/release/testdata/manifests.txt
new file mode 100644
index 00000000..9965fffc
--- /dev/null
+++ b/test/release/testdata/manifests.txt
@@ -0,0 +1,7 @@
+mkdir -p ./artifacts
+cp -r ./kustomize ./artifacts/
+cd ./artifacts/kustomize/kwok
+kustomize edit set image registry.k8s.io/kwok/kwok=image-prefix/kwok:<VERSION>
+cd -
+kustomize build ./artifacts/kustomize/kwok -o ./artifacts/kwok.yaml
+rm -r ./artifacts/kustomize