Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --reuse to incus image import #1428

Merged
merged 4 commits into from
Nov 28, 2024
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
23 changes: 18 additions & 5 deletions cmd/incus/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@ type cmdImageImport struct {
image *cmdImage

flagPublic bool
flagReuse bool
flagAliases []string
}

Expand All @@ -669,6 +670,7 @@ func (c *cmdImageImport) Command() *cobra.Command {
Directory import is only available on Linux and must be performed as root.`))

cmd.Flags().BoolVar(&c.flagPublic, "public", false, i18n.G("Make image public"))
cmd.Flags().BoolVar(&c.flagReuse, "reuse", false, i18n.G("If the image alias already exists, delete and create a new one"))
cmd.Flags().StringArrayVar(&c.flagAliases, "alias", nil, i18n.G("New aliases to add to the image")+"``")
cmd.RunE = c.Run

Expand Down Expand Up @@ -880,13 +882,24 @@ func (c *cmdImageImport) Run(cmd *cobra.Command, args []string) error {
fingerprint := opAPI.Metadata["fingerprint"].(string)
progress.Done(fmt.Sprintf(i18n.G("Image imported with fingerprint: %s"), fingerprint))

// Add the aliases
if len(c.flagAliases) > 0 {
aliases := make([]api.ImageAlias, len(c.flagAliases))
for i, entry := range c.flagAliases {
aliases[i].Name = entry
// Reformat aliases
aliases := []api.ImageAlias{}
for _, entry := range c.flagAliases {
alias := api.ImageAlias{}
alias.Name = entry
aliases = append(aliases, alias)
}

// Delete images if necessary
if c.flagReuse {
err = deleteImagesByAliases(d, aliases)
if err != nil {
return err
}
}

// Add the aliases
if len(c.flagAliases) > 0 {
err = ensureImageAliases(d, aliases, fingerprint)
if err != nil {
return err
Expand Down
38 changes: 4 additions & 34 deletions cmd/incus/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,40 +313,10 @@ func (c *cmdPublish) Run(cmd *cobra.Command, args []string) error {
}

// Delete images if necessary
if c.flagReuse && len(existingAliases) > 0 {
visitedImages := make(map[string]interface{})
for _, alias := range existingAliases {
image, _, _ := d.GetImage(alias.Target)

// If the image has already been visited then continue
if image != nil {
_, found := visitedImages[image.Fingerprint]
if found {
continue
}

visitedImages[image.Fingerprint] = nil
}

// An image can have multiple aliases. If an image being published
// reuses all the aliases from an existing image then that existing image is removed.
// In other case only specific aliases should be removed. E.g.
// 1. If image with 'foo' and 'bar' aliases already exists and new image is published
// with aliases 'foo' and 'bar' (and flag '--reuse'). Old image should be removed.
// 2. If image with 'foo' and 'bar' aliases already exists and new image is published
// with alias 'foo' (and flag '--reuse'). Old image should be kept with alias 'bar'
// and new image will have 'foo' alias.
if image != nil && IsAliasesSubset(image.Aliases, aliases) {
op, err := d.DeleteImage(alias.Target)
if err != nil {
return err
}

err = op.Wait()
if err != nil {
return err
}
}
if c.flagReuse {
err = deleteImagesByAliases(d, aliases)
if err != nil {
return err
}
}

Expand Down
54 changes: 54 additions & 0 deletions cmd/incus/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,60 @@ func GetExistingAliases(aliases []string, allAliases []api.ImageAliasesEntry) []
return existing
}

// deleteImagesByAliases deletes images based on provided aliases. E.g.
// aliases=[a1], image aliases=[a1] - image will be deleted
// aliases=[a1, a2], image aliases=[a1] - image will be deleted
// aliases=[a1], image aliases=[a1, a2] - image will be preserved.
func deleteImagesByAliases(client incus.InstanceServer, aliases []api.ImageAlias) error {
existingAliases, err := GetCommonAliases(client, aliases...)
if err != nil {
return fmt.Errorf(i18n.G("Error retrieving aliases: %w"), err)
}

// Nothing to do. Just return.
if len(existingAliases) == 0 {
return nil
}

// Delete images if necessary
visitedImages := make(map[string]any)
for _, alias := range existingAliases {
image, _, _ := client.GetImage(alias.Target)

// If the image has already been visited then continue
if image != nil {
_, found := visitedImages[image.Fingerprint]
if found {
continue
}

visitedImages[image.Fingerprint] = nil
}

// An image can have multiple aliases. If an image being published
// reuses all the aliases from an existing image then that existing image is removed.
// In other case only specific aliases should be removed. E.g.
// 1. If image with 'foo' and 'bar' aliases already exists and new image is published
// with aliases 'foo' and 'bar'. Old image should be removed.
// 2. If image with 'foo' and 'bar' aliases already exists and new image is published
// with alias 'foo'. Old image should be kept with alias 'bar'
// and new image will have 'foo' alias.
if image != nil && IsAliasesSubset(image.Aliases, aliases) {
op, err := client.DeleteImage(alias.Target)
if err != nil {
return err
}

err = op.Wait()
if err != nil {
return err
}
}
}

return nil
}

func getConfig(args ...string) (map[string]string, error) {
if len(args) == 2 && !strings.Contains(args[0], "=") {
if args[1] == "-" && !termios.IsTerminal(getStdinFd()) {
Expand Down
Loading