Skip to content
This repository has been archived by the owner on Mar 16, 2024. It is now read-only.

Add acorn copy command (#1809) #1883

Merged
merged 5 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docs/100-reference/01-command-line/acorn.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ acorn [flags]
* [acorn build](acorn_build.md) - Build an app from a Acornfile file
* [acorn check](acorn_check.md) - Check if the cluster is ready for Acorn
* [acorn container](acorn_container.md) - Manage containers
* [acorn copy](acorn_copy.md) - Copy Acorn images between registries
* [acorn credential](acorn_credential.md) - Manage registry credentials
* [acorn dev](acorn_dev.md) - Run an app from an image or Acornfile in dev mode or attach a dev session to a currently running app
* [acorn events](acorn_events.md) - List events about Acorn resources
Expand Down
51 changes: 51 additions & 0 deletions docs/docs/100-reference/01-command-line/acorn_copy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
title: "acorn copy"
---
## acorn copy

Copy Acorn images between registries

```
acorn copy [flags] SOURCE DESTINATION

This command can copy local images to remote registries, and can copy images between remote registries.
It cannot copy images from remote registries to the local registry (use acorn pull instead).

The --all-tags option only works with remote registries.
```

### Examples

```
# Copy the local image tagged "myimage:v1" to Docker Hub:
acorn copy myimage:v1 docker.io/<username>/myimage:v1

# Copy an image from Docker Hub to GHCR:
acorn copy docker.io/<username>/myimage:v1 ghcr.io/<username>/myimage:v1

# Copy all tags on a particular image repo in Docker Hub to GHCR:
acorn copy --all-tags docker.io/<username>/myimage ghcr.io/<username>/myimage
```

### Options

```
-a, --all-tags Copy all tags of the image
-f, --force Overwrite the destination image if it already exists
-h, --help help for copy
```

### Options inherited from parent commands

```
-A, --all-projects Use all known projects
--debug Enable debug logging
--debug-level int Debug log level (valid 0-9) (default 7)
--kubeconfig string Explicitly use kubeconfig file, overriding current project
-j, --project string Project to work in
```

### SEE ALSO

* [acorn](acorn.md) -

1 change: 1 addition & 0 deletions docs/docs/100-reference/01-command-line/acorn_image.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ acorn images
### SEE ALSO

* [acorn](acorn.md) -
* [acorn image copy](acorn_image_copy.md) - Copy Acorn images between registries
* [acorn image details](acorn_image_details.md) - Show details of an Image
* [acorn image rm](acorn_image_rm.md) - Delete an Image

51 changes: 51 additions & 0 deletions docs/docs/100-reference/01-command-line/acorn_image_copy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
title: "acorn image copy"
---
## acorn image copy

Copy Acorn images between registries

```
acorn image copy [flags] SOURCE DESTINATION

This command can copy local images to remote registries, and can copy images between remote registries.
It cannot copy images from remote registries to the local registry (use acorn pull instead).

The --all-tags option only works with remote registries.
```

### Examples

```
# Copy the local image tagged "myimage:v1" to Docker Hub:
acorn copy myimage:v1 docker.io/<username>/myimage:v1

# Copy an image from Docker Hub to GHCR:
acorn copy docker.io/<username>/myimage:v1 ghcr.io/<username>/myimage:v1

# Copy all tags on a particular image repo in Docker Hub to GHCR:
acorn copy --all-tags docker.io/<username>/myimage ghcr.io/<username>/myimage
```

### Options

```
-a, --all-tags Copy all tags of the image
-f, --force Overwrite the destination image if it already exists
-h, --help help for copy
```

### Options inherited from parent commands

```
-A, --all-projects Use all known projects
--debug Enable debug logging
--debug-level int Debug log level (valid 0-9) (default 7)
--kubeconfig string Explicitly use kubeconfig file, overriding current project
-j, --project string Project to work in
```

### SEE ALSO

* [acorn image](acorn_image.md) - Manage images

109 changes: 109 additions & 0 deletions integration/client/images/images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/acorn-io/runtime/integration/helper"
"github.com/acorn-io/runtime/pkg/client"
kclient "github.com/acorn-io/runtime/pkg/k8sclient"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/stretchr/testify/assert"
apierrors "k8s.io/apimachinery/pkg/api/errors"
)
Expand Down Expand Up @@ -416,3 +418,110 @@ func TestImageBadTag(t *testing.T) {
err = c.ImageTag(ctx, image.Name, "foo@@:badtag")
assert.Equal(t, "could not parse reference: foo@@:badtag", err.Error())
}

func TestImageCopy(t *testing.T) {
helper.StartController(t)
helper.StartAPI(t)
registry, closeRegistry := helper.StartRegistry(t)
t.Cleanup(closeRegistry)

ctx := helper.GetCTX(t)
c, _ := helper.ClientAndProject(t)

// Step 1: build an image and copy it to the registry
image, err := c.AcornImageBuild(ctx, "../testdata/nginx/Acornfile", &client.AcornImageBuildOptions{
Cwd: "../testdata/nginx",
})
if err != nil {
t.Fatal(err)
}

remoteTagName := registry + "/test:ci"

progress, err := c.ImageCopy(ctx, image.ID, remoteTagName, nil)
if err != nil {
t.Fatal(err)
}

for update := range progress {
if update.Error != "" {
t.Fatal(update.Error)
}
}

// Step 2: build a second image and attempt to copy it to the registry - should fail because "force" is not set
image2, err := c.AcornImageBuild(ctx, "../testdata/sidecar/Acornfile", &client.AcornImageBuildOptions{
Cwd: "../testdata/sidecar",
})
if err != nil {
t.Fatal(err)
}

progress, err = c.ImageCopy(ctx, image2.ID, remoteTagName, nil)
if err != nil {
t.Fatal(err)
}

var errorFound bool
for update := range progress {
if update.Error != "" {
errorFound = true
assert.Contains(t, update.Error, "not copying image")
assert.Contains(t, update.Error, "since it already exists")
}
}
assert.True(t, errorFound)

// Now that it failed, force copy it to make sure that works
progress, err = c.ImageCopy(ctx, image2.ID, remoteTagName, &client.ImageCopyOptions{Force: true})
if err != nil {
t.Fatal(err)
}

for update := range progress {
if update.Error != "" {
t.Fatal(update.Error)
}
}

// Step 4: copy the first image again, this time under a different tag
progress, err = c.ImageCopy(ctx, image.ID, remoteTagName+"-2", nil)
if err != nil {
t.Fatal(err)
}

for update := range progress {
if update.Error != "" {
t.Fatal(update.Error)
}
}

// Step 5: copy all tags from the first image in the registry to a new image in the registry
oldRemoteRepo := registry + "/test"
newRemoteRepo := registry + "/test2"
progress, err = c.ImageCopy(ctx, oldRemoteRepo, newRemoteRepo, &client.ImageCopyOptions{AllTags: true})
if err != nil {
t.Fatal(err)
}

for update := range progress {
if update.Error != "" {
t.Fatal(update.Error)
}
}

// Step 6: verify that both tags exist in the registry
repo, err := name.NewRepository(newRemoteRepo)
if err != nil {
t.Fatal(err)
}

tags, err := remote.List(repo, remote.WithContext(ctx))
if err != nil {
t.Fatal(err)
}

assert.Len(t, tags, 2)
assert.Contains(t, tags, "ci")
assert.Contains(t, tags, "ci-2")
}
1 change: 1 addition & 0 deletions pkg/apis/api.acorn.io/v1/scheme.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func AddToSchemeWithGV(scheme *runtime.Scheme, schemeGroupVersion schema.GroupVe
&ImageTag{},
&ImagePush{},
&ImagePull{},
&ImageCopy{},
&Info{},
&InfoList{},
&LogOptions{},
Expand Down
12 changes: 12 additions & 0 deletions pkg/apis/api.acorn.io/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,18 @@ type ImagePull struct {
Auth *RegistryAuth `json:"auth,omitempty"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type ImageCopy struct {
metav1.TypeMeta `json:",inline"`
Source string `json:"source,omitempty"`
Dest string `json:"dest,omitempty"`
SourceAuth *RegistryAuth `json:"sourceAuth,omitempty"`
DestAuth *RegistryAuth `json:"destAuth,omitempty"`
AllTags bool `json:"allTags,omitempty"`
Force bool `json:"force,omitempty"`
}

type LogMessage struct {
Line string `json:"line,omitempty"`
AppName string `json:"appName,omitempty"`
Expand Down
34 changes: 34 additions & 0 deletions pkg/apis/api.acorn.io/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/cli/acorn.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func New() *cobra.Command {
NewEvent(cmdContext),
NewFmt(cmdContext),
NewImage(cmdContext),
NewImageCopy(cmdContext),
NewInstall(cmdContext),
NewOfferings(cmdContext),
NewUninstall(cmdContext),
Expand Down
Loading