Repository: kubevirt/containerdisks
Pull request #336: docs: Add virtctl create examples 
Author: login: 0xFelix
Labels: name: dco-signoff: yes
description: Indicates the PR's author has DCO signed all their commits.
name: size/M
name: release-note-none
description: Denotes a PR that doesn't merit a release note.
name: lgtm
description: Indicates that a PR is ready to be merged.
name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
Created: 2025-05-13T14:54:13Z | Merged: 2025-05-14T07:01:59Z

<!--  Thanks for sending a pull request!  Here are some tips for you:
1. Follow the instructions for writing a release note from k8s: https://git.k8s.io/community/contributors/guide/release-notes.md
-->

**What this PR does / why we need it**:

This extends description.tpl with examples, that show how to use or
import containerdisks with virtctl create.

It also introduces the getPreferredArtifact helper, which will try to find
the artifact with the amd64 to allow using it for documentation purposes.

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes #

**Special notes for your reviewer**:

**Release note**:
<!--  Write your release note:
1. Enter your extended release note in the below block. If the PR requires additional action from users switching to the new release, include the string "action required".
2. If no release note is required, just write "NONE".
-->
```release-note
None
```



Diff:
diff --git a/cmd/medius/docs/docs.go b/cmd/medius/docs/docs.go
index 62b6e3e33..f8a7822e4 100644
--- a/cmd/medius/docs/docs.go
+++ b/cmd/medius/docs/docs.go
@@ -14,6 +14,7 @@ import (
 
 	"kubevirt.io/containerdisks/cmd/medius/common"
 	"kubevirt.io/containerdisks/pkg/api"
+	pkgcommon "kubevirt.io/containerdisks/pkg/common"
 	"kubevirt.io/containerdisks/pkg/docs"
 	"kubevirt.io/containerdisks/pkg/quay"
 )
@@ -58,10 +59,15 @@ func run(options *common.Options) error {
 		if common.ShouldSkip(options.Focus, &registry[i]) || !p.UseForDocs {
 			continue
 		}
-
 		focusMatched = true
 
-		artifact := p.Artifacts[0]
+		artifact, err := getPreferredArtifact(p.Artifacts)
+		if err != nil {
+			success = false
+			logrus.Errorf("error getting artifact: %v", err)
+			continue
+		}
+
 		log := common.Logger(artifact)
 		name := artifact.Metadata().Name
 
@@ -104,11 +110,32 @@ func getQuayOrg(registry string) (string, error) {
 	return elements[1], nil
 }
 
+// getPreferredArtifact returns the preferred artifact which has the amd64 architecture.
+// If no artifact with the amd64 architecture can be found, it will try to return the first artifact.
+func getPreferredArtifact(artifacts []api.Artifact) (api.Artifact, error) {
+	if len(artifacts) == 0 {
+		return nil, errors.New("no artifacts provided")
+	}
+
+	for _, artifact := range artifacts {
+		details, err := artifact.Inspect()
+		if err != nil {
+			return nil, err
+		}
+		if details.ImageArchitecture == "amd64" {
+			return artifact, nil
+		}
+	}
+
+	return artifacts[0], nil
+}
+
 func createDescription(artifact api.Artifact, registry string) (string, error) {
 	metadata := artifact.Metadata()
+	image := path.Join(registry, metadata.Describe())
 	vm := artifact.VM(
 		metadata.Name,
-		path.Join(registry, metadata.Describe()),
+		image,
 		artifact.UserData(&metadata.ExampleUserData),
 	)
 
@@ -118,9 +145,12 @@ func createDescription(artifact api.Artifact, registry string) (string, error) {
 	}
 
 	data := &docs.TemplateData{
-		Name:        metadata.Name,
-		Description: metadata.Description,
-		Example:     string(example),
+		Name:         metadata.Name,
+		Description:  metadata.Description,
+		Example:      string(example),
+		Image:        image,
+		Instancetype: metadata.EnvVariables[pkgcommon.DefaultInstancetypeEnv],
+		Preference:   metadata.EnvVariables[pkgcommon.DefaultPreferenceEnv],
 	}
 
 	var result bytes.Buffer
diff --git a/pkg/docs/data/description.tpl b/pkg/docs/data/description.tpl
index 18cd344e7..d3f75ed9d 100644
--- a/pkg/docs/data/description.tpl
+++ b/pkg/docs/data/description.tpl
@@ -5,13 +5,34 @@
 ## Documentation
 
 This image is maintained by [KubeVirt](https://kubevirt.io/) and automatically created from https://github.com/kubevirt/containerdisks.
-
-For how to get started with `KubeVirt` visit the [UserGuide](https://kubevirt.io/user-guide/)
+<br />
+<br />
+For how to get started with `KubeVirt` visit the [user guide](https://kubevirt.io/user-guide/):
   * [Installation](https://kubevirt.io/user-guide/operations/installation/#installing-kubevirt-on-kubernetes)
-  * [ContainerDisks](https://kubevirt.io/user-guide/virtual_machines/disks_and_volumes/#containerdisk)
+  * [Containerdisks](https://kubevirt.io/user-guide/virtual_machines/disks_and_volumes/#containerdisk)
+  * [virtctl](https://kubevirt.io/user-guide/user_workloads/virtctl_client_tool)
+  * [Creating VirtualMachines by using virtctl](https://kubevirt.io/user-guide/user_workloads/creating_vms/#creating-virtualmachines-by-using-virtctl)
+
+## Examples
+
+### Creating a VirtualMachine and importing this containerdisk with virtctl
+
+You can create a VirtualMachine that will import this containerdisk by running the following command:
+
+```shell
+virtctl create vm {{ if .Instancetype }}--instancetype={{ .Instancetype }} {{ end }}{{ if .Preference }}--preference={{ .Preference }} {{ end }}--volume-import=type:registry,url:docker://{{ .Image }},size:10Gi | kubectl create -f -
+```
+
+### Creating a VirtualMachine without persistence with virtctl
+
+You can create a VirtualMachine that will use this containerdisk as an ephemeral volume by running the following command:
+
+```shell
+virtctl create vm {{ if .Instancetype }}--instancetype={{ .Instancetype }} {{ end }}{{ if .Preference }}--preference={{ .Preference }} {{ end }}--volume-containerdisk=src:{{ .Image }} | kubectl create -f -
+```
 
-## Example
+### Using this containerdisk in a VirtualMachine definition
 
 ```yaml
-{{ .Example }}
+{{ .Example -}}
 ```
\ No newline at end of file
diff --git a/pkg/docs/docs.go b/pkg/docs/docs.go
index 21a84da1f..e939e7757 100644
--- a/pkg/docs/docs.go
+++ b/pkg/docs/docs.go
@@ -16,9 +16,12 @@ import (
 )
 
 type TemplateData struct {
-	Name        string
-	Description string
-	Example     string
+	Name         string
+	Description  string
+	Example      string
+	Image        string
+	Instancetype string
+	Preference   string
 }
 
 type UserData struct {


Reviews:
user: login: sourcery-ai[bot]
body: Hey @0xFelix - I've reviewed your changes and found some issues that need to be addressed.

**Blocking issues**:
- Undeclared variable ‘success’ causes compilation error ([link](https://github.com/kubevirt/containerdisks/pull/336/files#diff-36387ee9dcfcaac6e8079a9d9bce27e47aa60e66ea574b0dced0477710fce694R66))

<details>
<summary>Here's what I looked at during the review</summary>

- 🔴 **General issues**: 1 blocking issue, 2 other issues
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟢 **Complexity**: all looks good
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-13T15:01:32Z
user: login: jcanocan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T15:14:01Z
user: login: sourcery-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-13T15:14:38Z
user: login: 0xFelix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T15:39:04Z
user: login: sourcery-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-13T15:39:17Z
user: login: 0xFelix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T15:40:06Z
user: login: jcanocan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T15:42:26Z
user: login: 0xFelix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T16:08:21Z
user: login: 0xFelix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T16:09:15Z
user: login: lyarwood
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T16:11:12Z
user: login: 0xFelix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T16:15:02Z
user: login: 0xFelix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T16:19:11Z
user: login: lyarwood
body: /approve
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T16:23:29Z

Review comments:
diff_hunk: @@ -61,7 +62,8 @@ func run(options *common.Options) error {
 
 		focusMatched = true
 
-		artifact := p.Artifacts[0]
+		var artifact api.Artifact
+		artifact, success = getPreferredArtifact(p.Artifacts)
path: cmd/medius/docs/docs.go
user: login: sourcery-ai[bot]
body: **issue (bug_risk):** Undeclared variable ‘success’ causes compilation error

Either declare ‘success’ before this line or replace ‘=’ with ‘:=’ to declare it here.
created_at: 2025-05-13T15:01:32Z
author_association: NONE
diff_hunk: @@ -104,11 +106,33 @@ func getQuayOrg(registry string) (string, error) {
 	return elements[1], nil
 }
 
+// getPreferredArtifact returns the preferred artifact which has the amd64 architecture.
+// If no artifact with the amd64 architecture can be found, it will return the first artifact.
+func getPreferredArtifact(artifacts []api.Artifact) (api.Artifact, bool) {
+	artifact := artifacts[0]
path: cmd/medius/docs/docs.go
user: login: sourcery-ai[bot]
body: **issue (bug_risk):** Panics on empty artifacts slice

Check whether `artifacts` is empty before indexing and return an error if no artifacts are provided.
created_at: 2025-05-13T15:01:32Z
author_association: NONE
diff_hunk: @@ -104,11 +106,33 @@ func getQuayOrg(registry string) (string, error) {
 	return elements[1], nil
 }
 
+// getPreferredArtifact returns the preferred artifact which has the amd64 architecture.
+// If no artifact with the amd64 architecture can be found, it will return the first artifact.
+func getPreferredArtifact(artifacts []api.Artifact) (api.Artifact, bool) {
path: cmd/medius/docs/docs.go
user: login: sourcery-ai[bot]
body: **suggestion:** Boolean return value is ambiguous

Currently the bool signals inspect failures, not AMD64 presence. Return an error or separate flags to distinguish failure from finding the preferred artifact.
created_at: 2025-05-13T15:01:32Z
author_association: NONE
diff_hunk: @@ -118,9 +142,12 @@ func createDescription(artifact api.Artifact, registry string) (string, error) {
 	}
 
 	data := &docs.TemplateData{
-		Name:        metadata.Name,
-		Description: metadata.Description,
-		Example:     string(example),
+		Name:         metadata.Name,
+		Description:  metadata.Description,
+		Example:      string(example),
+		Image:        image,
+		Instancetype: metadata.EnvVariables[pkgcommon.DefaultInstancetypeEnv],
+		Preference:   metadata.EnvVariables[pkgcommon.DefaultPreferenceEnv],
path: cmd/medius/docs/docs.go
user: login: jcanocan
body: This may be empty, what would happen in such case?
created_at: 2025-05-13T15:09:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -104,11 +106,33 @@ func getQuayOrg(registry string) (string, error) {
 	return elements[1], nil
 }
 
+// getPreferredArtifact returns the preferred artifact which has the amd64 architecture.
+// If no artifact with the amd64 architecture can be found, it will return the first artifact.
+func getPreferredArtifact(artifacts []api.Artifact) (api.Artifact, bool) {
+	artifact := artifacts[0]
path: cmd/medius/docs/docs.go
user: login: jcanocan
body: Could this happen?
created_at: 2025-05-13T15:10:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -104,11 +106,33 @@ func getQuayOrg(registry string) (string, error) {
 	return elements[1], nil
 }
 
+// getPreferredArtifact returns the preferred artifact which has the amd64 architecture.
+// If no artifact with the amd64 architecture can be found, it will return the first artifact.
+func getPreferredArtifact(artifacts []api.Artifact) (api.Artifact, bool) {
+	artifact := artifacts[0]
path: cmd/medius/docs/docs.go
user: login: sourcery-ai[bot]
body: It’s true that in our current call‐sites we only ever pass in a non‐empty slice (we construct it from the list of containerdisk artifacts fetched from Quay), so you shouldn’t hit an empty slice today—but that guarantee is implicit and fragile. If, for whatever reason, Quay ever returned zero artifacts (or we add a new importer down the line that can return an empty list), today’s code would panic at runtime.

I’d still recommend adding an explicit check and turning this into an error (or at least returning a “not found” bool) rather than indexing blindly. For example:

```go
// getPreferredArtifact returns the artifact with amd64 if present.
// Returns an error if the slice is empty.
func getPreferredArtifact(artifacts []api.Artifact) (api.Artifact, error) {
    if len(artifacts) == 0 {
        return api.Artifact{}, fmt.Errorf("no artifacts provided")
    }
    // fall back to first
    best := artifacts[0]
    for _, a := range artifacts {
        if strings.Contains(a.Architecture, "amd64") {
            return a, nil
        }
    }
    return best, nil
}
```

Then callers can handle the error explicitly. This makes the code more robust against future changes or unexpected API responses.
created_at: 2025-05-13T15:14:38Z
author_association: NONE
diff_hunk: @@ -61,7 +62,8 @@ func run(options *common.Options) error {
 
 		focusMatched = true
 
-		artifact := p.Artifacts[0]
+		var artifact api.Artifact
+		artifact, success = getPreferredArtifact(p.Artifacts)
path: cmd/medius/docs/docs.go
user: login: 0xFelix
body: It is declared on L48
created_at: 2025-05-13T15:39:03Z
author_association: MEMBER
diff_hunk: @@ -61,7 +62,8 @@ func run(options *common.Options) error {
 
 		focusMatched = true
 
-		artifact := p.Artifacts[0]
+		var artifact api.Artifact
+		artifact, success = getPreferredArtifact(p.Artifacts)
path: cmd/medius/docs/docs.go
user: login: sourcery-ai[bot]
body: You’re right—my mistake, I missed the declaration on line 48. Sorry for the noise.

One thing I did notice though: after you call  
```
artifact, success = getPreferredArtifact(p.Artifacts)
```  
you don’t actually handle the case where `success` is false. I’d suggest adding something along the lines of:  
```go
if !success {
    return fmt.Errorf("no supported artifact found for %s", p.Name)
}
```  
(or however you’d like to surface that error) to avoid a nil/zero‐value artifact downstream.
created_at: 2025-05-13T15:39:17Z
author_association: NONE
diff_hunk: @@ -118,9 +142,12 @@ func createDescription(artifact api.Artifact, registry string) (string, error) {
 	}
 
 	data := &docs.TemplateData{
-		Name:        metadata.Name,
-		Description: metadata.Description,
-		Example:     string(example),
+		Name:         metadata.Name,
+		Description:  metadata.Description,
+		Example:      string(example),
+		Image:        image,
+		Instancetype: metadata.EnvVariables[pkgcommon.DefaultInstancetypeEnv],
+		Preference:   metadata.EnvVariables[pkgcommon.DefaultPreferenceEnv],
path: cmd/medius/docs/docs.go
user: login: 0xFelix
body: The template will omit empty variables, see `description.tpl`.
created_at: 2025-05-13T15:40:06Z
author_association: MEMBER
diff_hunk: @@ -118,9 +142,12 @@ func createDescription(artifact api.Artifact, registry string) (string, error) {
 	}
 
 	data := &docs.TemplateData{
-		Name:        metadata.Name,
-		Description: metadata.Description,
-		Example:     string(example),
+		Name:         metadata.Name,
+		Description:  metadata.Description,
+		Example:      string(example),
+		Image:        image,
+		Instancetype: metadata.EnvVariables[pkgcommon.DefaultInstancetypeEnv],
+		Preference:   metadata.EnvVariables[pkgcommon.DefaultPreferenceEnv],
path: cmd/medius/docs/docs.go
user: login: jcanocan
body: All right! Thanks for the clarification.
created_at: 2025-05-13T15:42:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -104,11 +106,33 @@ func getQuayOrg(registry string) (string, error) {
 	return elements[1], nil
 }
 
+// getPreferredArtifact returns the preferred artifact which has the amd64 architecture.
+// If no artifact with the amd64 architecture can be found, it will return the first artifact.
+func getPreferredArtifact(artifacts []api.Artifact) (api.Artifact, bool) {
+	artifact := artifacts[0]
path: cmd/medius/docs/docs.go
user: login: 0xFelix
body: Thank you, updated
created_at: 2025-05-13T16:08:18Z
author_association: MEMBER
diff_hunk: @@ -61,7 +62,8 @@ func run(options *common.Options) error {
 
 		focusMatched = true
 
-		artifact := p.Artifacts[0]
+		var artifact api.Artifact
+		artifact, success = getPreferredArtifact(p.Artifacts)
path: cmd/medius/docs/docs.go
user: login: 0xFelix
body: It is handled on L94
created_at: 2025-05-13T16:09:15Z
author_association: MEMBER
diff_hunk: @@ -5,13 +5,34 @@
 ## Documentation
 
 This image is maintained by [KubeVirt](https://kubevirt.io/) and automatically created from https://github.com/kubevirt/containerdisks.
-
-For how to get started with `KubeVirt` visit the [UserGuide](https://kubevirt.io/user-guide/)
+<br />
+<br />
+For how to get started with `KubeVirt` visit the [user guide](https://kubevirt.io/user-guide/):
   * [Installation](https://kubevirt.io/user-guide/operations/installation/#installing-kubevirt-on-kubernetes)
-  * [ContainerDisks](https://kubevirt.io/user-guide/virtual_machines/disks_and_volumes/#containerdisk)
+  * [Containerdisks](https://kubevirt.io/user-guide/virtual_machines/disks_and_volumes/#containerdisk)
+  * [virtctl](https://kubevirt.io/user-guide/user_workloads/virtctl_client_tool)
+  * [Creating VirtualMachines by using virtctl](https://kubevirt.io/user-guide/user_workloads/creating_vms/#creating-virtualmachines-by-using-virtctl)
+
+## Examples
+
+### Creating a VirtualMachine and importing this containerdisk with virtctl
+
+You can create a VirtualMachine that will import this containerdisk by running the following command:
+
+```shell
+virtctl create vm --volume-import=type:registry,url:docker://{{ .Image }},size:10Gi | kubectl create -f -
path: pkg/docs/data/description.tpl
user: login: lyarwood
body: This doesn't infer the defaults from the containerdisk or datavolume FWIW so we should still include the instance type and preference here for now.

@0xFelix Did we ever track this as a bug anywhere? Would be good justification to move infer into the controller sync loop this cycle as I think that might be the only way we can fix this post DV creation.
created_at: 2025-05-13T16:10:11Z
author_association: MEMBER
diff_hunk: @@ -5,13 +5,34 @@
 ## Documentation
 
 This image is maintained by [KubeVirt](https://kubevirt.io/) and automatically created from https://github.com/kubevirt/containerdisks.
-
-For how to get started with `KubeVirt` visit the [UserGuide](https://kubevirt.io/user-guide/)
+<br />
+<br />
+For how to get started with `KubeVirt` visit the [user guide](https://kubevirt.io/user-guide/):
   * [Installation](https://kubevirt.io/user-guide/operations/installation/#installing-kubevirt-on-kubernetes)
-  * [ContainerDisks](https://kubevirt.io/user-guide/virtual_machines/disks_and_volumes/#containerdisk)
+  * [Containerdisks](https://kubevirt.io/user-guide/virtual_machines/disks_and_volumes/#containerdisk)
+  * [virtctl](https://kubevirt.io/user-guide/user_workloads/virtctl_client_tool)
+  * [Creating VirtualMachines by using virtctl](https://kubevirt.io/user-guide/user_workloads/creating_vms/#creating-virtualmachines-by-using-virtctl)
+
+## Examples
+
+### Creating a VirtualMachine and importing this containerdisk with virtctl
+
+You can create a VirtualMachine that will import this containerdisk by running the following command:
+
+```shell
+virtctl create vm --volume-import=type:registry,url:docker://{{ .Image }},size:10Gi | kubectl create -f -
path: pkg/docs/data/description.tpl
user: login: 0xFelix
body: It does infer the defaults from the containerdisk because `virtctl` tells the API to do so at the moment.
created_at: 2025-05-13T16:15:02Z
author_association: MEMBER
diff_hunk: @@ -5,13 +5,34 @@
 ## Documentation
 
 This image is maintained by [KubeVirt](https://kubevirt.io/) and automatically created from https://github.com/kubevirt/containerdisks.
-
-For how to get started with `KubeVirt` visit the [UserGuide](https://kubevirt.io/user-guide/)
+<br />
+<br />
+For how to get started with `KubeVirt` visit the [user guide](https://kubevirt.io/user-guide/):
   * [Installation](https://kubevirt.io/user-guide/operations/installation/#installing-kubevirt-on-kubernetes)
-  * [ContainerDisks](https://kubevirt.io/user-guide/virtual_machines/disks_and_volumes/#containerdisk)
+  * [Containerdisks](https://kubevirt.io/user-guide/virtual_machines/disks_and_volumes/#containerdisk)
+  * [virtctl](https://kubevirt.io/user-guide/user_workloads/virtctl_client_tool)
+  * [Creating VirtualMachines by using virtctl](https://kubevirt.io/user-guide/user_workloads/creating_vms/#creating-virtualmachines-by-using-virtctl)
+
+## Examples
+
+### Creating a VirtualMachine and importing this containerdisk with virtctl
+
+You can create a VirtualMachine that will import this containerdisk by running the following command:
+
+```shell
+virtctl create vm --volume-import=type:registry,url:docker://{{ .Image }},size:10Gi | kubectl create -f -
path: pkg/docs/data/description.tpl
user: login: 0xFelix
body: Okay, now that I'm thinking of it again. No it can't be inferred because the DV is not imported yet on creation of the VM. You are right. Let me fix it.
created_at: 2025-05-13T16:19:11Z
author_association: MEMBER

PR comments:
user: login: jcanocan
created_at: 2025-05-13T16:11:08Z
body: Thanks @0xFelix! Nice job!
/lgtm
author_association: CONTRIBUTOR
user: login: kubevirt-bot
created_at: 2025-05-13T16:23:51Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubevirt/containerdisks/pull/336#pullrequestreview-2837415555" title="Approved">lyarwood</a>*

The full list of commands accepted by this bot can be found [here](https://prow.ci.kubevirt.io/command-help?repo=kubevirt%2Fcontainerdisks).

The pull request process is described [here](https://github.com/kubevirt/community/blob/main/docs/add-merge-automation-to-your-repository.md#example-workflow)

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

- ~~[OWNERS](https://github.com/kubevirt/containerdisks/blob/main/OWNERS)~~ [lyarwood]

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":[]} -->
author_association: CONTRIBUTOR
user: login: jcanocan
created_at: 2025-05-13T16:28:47Z
body: /lgtm
author_association: CONTRIBUTOR
user: login: 0xFelix
created_at: 2025-05-14T06:58:09Z
body: /test pull-containerdisks-test
author_association: MEMBER