Repository: ionos-cloud/ionosctl
Pull request #512: feat: add label support for images
Author: login: avirtopeanu-ionos
Created: 2025-04-04T13:53:49Z | Merged: 2025-04-07T14:27:24Z

Adds support for images label operations


Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2badddaed..02aaac9a8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 ### Added
 - Added --server-type for managed kubernetes nodepools
+- Added support for labeling images
 
 ### Changed
 - Changed login message to include the username you have logged in as, if possible.
diff --git a/commands/cloudapi-v6/label.go b/commands/cloudapi-v6/label.go
index aff424632..31a6e1e90 100644
--- a/commands/cloudapi-v6/label.go
+++ b/commands/cloudapi-v6/label.go
@@ -12,6 +12,7 @@ import (
 	"github.com/ionos-cloud/ionosctl/v6/internal/printer/jsontabwriter"
 	"github.com/ionos-cloud/ionosctl/v6/internal/printer/tabheaders"
 	cloudapiv6 "github.com/ionos-cloud/ionosctl/v6/services/cloudapi-v6"
+	ionoscloud "github.com/ionos-cloud/sdk-go/v6"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
 )
@@ -38,7 +39,8 @@ func LabelCmd() *core.Command {
 	})
 
 	var (
-		allowedValues = []string{cloudapiv6.DatacenterResource, cloudapiv6.VolumeResource, cloudapiv6.ServerResource, cloudapiv6.SnapshotResource, cloudapiv6.IpBlockResource}
+		allowedValues = []string{cloudapiv6.DatacenterResource, cloudapiv6.VolumeResource, cloudapiv6.ServerResource,
+			cloudapiv6.SnapshotResource, cloudapiv6.IpBlockResource, cloudapiv6.ImageResource}
 	)
 
 	/*
@@ -76,6 +78,14 @@ func LabelCmd() *core.Command {
 	_ = list.Command.RegisterFlagCompletionFunc(cloudapiv6.ArgSnapshotId, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 		return completer.SnapshotIds(), cobra.ShellCompDirectiveNoFileComp
 	})
+	list.AddUUIDFlag(cloudapiv6.ArgImageId, "", "", cloudapiv6.ImageId+"(note: only private images supported)")
+	_ = list.Command.RegisterFlagCompletionFunc(cloudapiv6.ArgImageId, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		// complete with private images only (cannot add labels to public images)
+		return completer.ImageIds(
+			func(request ionoscloud.ApiImagesGetRequest) ionoscloud.ApiImagesGetRequest {
+				return request.Filter("public", "false")
+			}), cobra.ShellCompDirectiveNoFileComp
+	})
 	list.AddSetFlag(cloudapiv6.ArgResourceType, "", "", allowedValues, "Type of resource to list labels from", core.RequiredFlagOption())
 	list.AddInt32Flag(constants.FlagMaxResults, constants.FlagMaxResultsShort, cloudapiv6.DefaultMaxResults, constants.DescMaxResults)
 	list.AddInt32Flag(cloudapiv6.ArgDepth, cloudapiv6.ArgDepthShort, cloudapiv6.DefaultListDepth, cloudapiv6.ArgDepthDescription)
@@ -96,7 +106,7 @@ func LabelCmd() *core.Command {
 		Verb:       "get",
 		Aliases:    []string{"g"},
 		ShortDesc:  "Get a Label",
-		LongDesc:   "Use this command to get information about a specified Label from a specified Resource.\n\nRequired values to run command:\n\n* Resource Type\n* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id or Snapshot Id\n* Label Key",
+		LongDesc:   "Use this command to get information about a specified Label from a specified Resource.\n\nRequired values to run command:\n\n* Resource Type\n* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id, Image ID, or Snapshot Id\n* Label Key",
 		Example:    getLabelExample,
 		PreCmdRun:  PreRunResourceTypeLabelKey,
 		CmdRun:     RunLabelGet,
@@ -123,6 +133,14 @@ func LabelCmd() *core.Command {
 	_ = get.Command.RegisterFlagCompletionFunc(cloudapiv6.ArgSnapshotId, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 		return completer.SnapshotIds(), cobra.ShellCompDirectiveNoFileComp
 	})
+	get.AddUUIDFlag(cloudapiv6.ArgImageId, "", "", cloudapiv6.ImageId+"(note: only private images supported)")
+	_ = get.Command.RegisterFlagCompletionFunc(cloudapiv6.ArgImageId, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		// complete with private images only (cannot add labels to public images)
+		return completer.ImageIds(
+			func(request ionoscloud.ApiImagesGetRequest) ionoscloud.ApiImagesGetRequest {
+				return request.Filter("public", "false")
+			}), cobra.ShellCompDirectiveNoFileComp
+	})
 	get.AddSetFlag(cloudapiv6.ArgResourceType, "", "", allowedValues, "Type of resource to get labels from", core.RequiredFlagOption())
 	get.AddInt32Flag(cloudapiv6.ArgDepth, cloudapiv6.ArgDepthShort, cloudapiv6.DefaultGetDepth, cloudapiv6.ArgDepthDescription)
 
@@ -152,7 +170,7 @@ func LabelCmd() *core.Command {
 		Verb:       "add",
 		Aliases:    []string{"a"},
 		ShortDesc:  "Add a Label to a Resource",
-		LongDesc:   "Use this command to add a Label to a specific Resource.\n\nRequired values to run command:\n\n* Resource Type\n* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id or Snapshot Id\n* Label Key\n* Label Value",
+		LongDesc:   "Use this command to add a Label to a specific Resource.\n\nRequired values to run command:\n\n* Resource Type\n* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id, Image ID, or Snapshot Id\n* Label Key\n* Label Value",
 		Example:    addLabelExample,
 		PreCmdRun:  PreRunResourceTypeLabelKeyValue,
 		CmdRun:     RunLabelAdd,
@@ -180,6 +198,14 @@ func LabelCmd() *core.Command {
 	_ = addLabel.Command.RegisterFlagCompletionFunc(cloudapiv6.ArgSnapshotId, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 		return completer.SnapshotIds(), cobra.ShellCompDirectiveNoFileComp
 	})
+	addLabel.AddUUIDFlag(cloudapiv6.ArgImageId, "", "", cloudapiv6.ImageId+"(note: only private images supported)")
+	_ = addLabel.Command.RegisterFlagCompletionFunc(cloudapiv6.ArgImageId, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		// complete with private images only (cannot add labels to public images)
+		return completer.ImageIds(
+			func(request ionoscloud.ApiImagesGetRequest) ionoscloud.ApiImagesGetRequest {
+				return request.Filter("public", "false")
+			}), cobra.ShellCompDirectiveNoFileComp
+	})
 	addLabel.AddSetFlag(cloudapiv6.ArgResourceType, "", "", allowedValues, "Type of resource to add labels to", core.RequiredFlagOption())
 	addLabel.AddInt32Flag(cloudapiv6.ArgDepth, cloudapiv6.ArgDepthShort, cloudapiv6.DefaultMiscDepth, cloudapiv6.ArgDepthDescription)
 
@@ -190,9 +216,9 @@ func LabelCmd() *core.Command {
 		Namespace:  "label",
 		Resource:   "label",
 		Verb:       "remove",
-		Aliases:    []string{"r"},
+		Aliases:    []string{"delete", "del", "r", "rm"},
 		ShortDesc:  "Remove a Label from a Resource",
-		LongDesc:   "Use this command to remove a Label from a Resource.\n\nRequired values to run command:\n\n* Resource Type\n* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id or Snapshot Id\n* Label Key",
+		LongDesc:   "Use this command to remove a Label from a Resource.\n\nRequired values to run command:\n\n* Resource Type\n* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id, Image ID, or Snapshot Id\n* Label Key",
 		Example:    removeLabelExample,
 		PreCmdRun:  PreRunResourceTypeLabelKeyRemove,
 		CmdRun:     RunLabelRemove,
@@ -219,6 +245,14 @@ func LabelCmd() *core.Command {
 	_ = removeLabel.Command.RegisterFlagCompletionFunc(cloudapiv6.ArgSnapshotId, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 		return completer.SnapshotIds(), cobra.ShellCompDirectiveNoFileComp
 	})
+	removeLabel.AddUUIDFlag(cloudapiv6.ArgImageId, "", "", cloudapiv6.ImageId+"(note: only private images supported)")
+	_ = removeLabel.Command.RegisterFlagCompletionFunc(cloudapiv6.ArgImageId, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		// complete with private images only (cannot add labels to public images)
+		return completer.ImageIds(
+			func(request ionoscloud.ApiImagesGetRequest) ionoscloud.ApiImagesGetRequest {
+				return request.Filter("public", "false")
+			}), cobra.ShellCompDirectiveNoFileComp
+	})
 	removeLabel.AddSetFlag(cloudapiv6.ArgResourceType, "", "", allowedValues, "Type of resource to remove labels from", core.RequiredFlagOption())
 	removeLabel.AddBoolFlag(cloudapiv6.ArgAll, cloudapiv6.ArgAllShort, false, "Remove all Labels")
 	removeLabel.AddInt32Flag(cloudapiv6.ArgDepth, cloudapiv6.ArgDepthShort, cloudapiv6.DefaultMiscDepth, cloudapiv6.ArgDepthDescription)
@@ -256,6 +290,10 @@ func generateFlagSets(c *core.PreCommandConfig, extraFlags ...string) []core.Fla
 			FlagNameSet:    append([]string{cloudapiv6.ArgResourceType, cloudapiv6.ArgIpBlockId}, extraFlags...),
 			Predicate:      funcResourceTypeSetAndMatches,
 			PredicateParam: cloudapiv6.IpBlockResource,
+		}, {
+			FlagNameSet:    append([]string{cloudapiv6.ArgResourceType, cloudapiv6.ArgImageId}, extraFlags...),
+			Predicate:      funcResourceTypeSetAndMatches,
+			PredicateParam: cloudapiv6.ImageResource,
 		},
 	}
 }
@@ -312,6 +350,8 @@ func RunLabelList(c *core.CommandConfig) error {
 		return RunIpBlockLabelsList(c)
 	case cloudapiv6.SnapshotResource:
 		return RunSnapshotLabelsList(c)
+	case cloudapiv6.ImageResource:
+		return RunImageLabelsList(c)
 	default:
 		labelDcs, _, err := c.CloudApiV6Services.Labels().List(listQueryParams)
 		if err != nil {
@@ -353,6 +393,8 @@ func RunLabelGet(c *core.CommandConfig) error {
 		return RunIpBlockLabelGet(c)
 	case cloudapiv6.SnapshotResource:
 		return RunSnapshotLabelGet(c)
+	case cloudapiv6.ImageResource:
+		return RunImageLabelGet(c)
 	default:
 		fmt.Fprintf(c.Command.Command.OutOrStdout(), jsontabwriter.GenerateLogOutput(labelResourceWarning))
 
@@ -395,6 +437,8 @@ func RunLabelAdd(c *core.CommandConfig) error {
 		return RunIpBlockLabelAdd(c)
 	case cloudapiv6.SnapshotResource:
 		return RunSnapshotLabelAdd(c)
+	case cloudapiv6.ImageResource:
+		return RunImageLabelAdd(c)
 	default:
 		fmt.Fprintf(c.Command.Command.OutOrStdout(), jsontabwriter.GenerateLogOutput(labelResourceWarning))
 
@@ -405,7 +449,7 @@ func RunLabelAdd(c *core.CommandConfig) error {
 func RunLabelRemove(c *core.CommandConfig) error {
 	resourceType := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgResourceType))
 
-	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput("Label is removing from %v...", resourceType))
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput("removing label from %v...", resourceType))
 
 	switch resourceType {
 	case cloudapiv6.DatacenterResource:
@@ -418,6 +462,8 @@ func RunLabelRemove(c *core.CommandConfig) error {
 		return RunIpBlockLabelRemove(c)
 	case cloudapiv6.SnapshotResource:
 		return RunSnapshotLabelRemove(c)
+	case cloudapiv6.ImageResource:
+		return RunImageLabelRemove(c)
 	default:
 		fmt.Fprintf(c.Command.Command.OutOrStdout(), jsontabwriter.GenerateLogOutput(labelResourceWarning))
 
diff --git a/commands/cloudapi-v6/labelresource.go b/commands/cloudapi-v6/labelresource.go
index a71eac626..06651e161 100644
--- a/commands/cloudapi-v6/labelresource.go
+++ b/commands/cloudapi-v6/labelresource.go
@@ -1,11 +1,13 @@
 package commands
 
 import (
+	"context"
 	"errors"
 	"fmt"
 
 	"github.com/ionos-cloud/ionosctl/v6/commands/cloudapi-v6/query"
 	"github.com/ionos-cloud/ionosctl/v6/commands/cloudapi-v6/waiter"
+	"github.com/ionos-cloud/ionosctl/v6/internal/client"
 	"github.com/ionos-cloud/ionosctl/v6/internal/constants"
 	"github.com/ionos-cloud/ionosctl/v6/internal/core"
 	"github.com/ionos-cloud/ionosctl/v6/internal/printer/json2table/jsonpaths"
@@ -16,6 +18,7 @@ import (
 	"github.com/ionos-cloud/ionosctl/v6/pkg/confirm"
 	cloudapiv6 "github.com/ionos-cloud/ionosctl/v6/services/cloudapi-v6"
 	"github.com/ionos-cloud/ionosctl/v6/services/cloudapi-v6/resources"
+	ionoscloud "github.com/ionos-cloud/sdk-go/v6"
 	"github.com/spf13/viper"
 )
 
@@ -210,6 +213,186 @@ func RemoveAllDatacenterLabels(c *core.CommandConfig) error {
 	return nil
 }
 
+func listImageLabels(c *core.CommandConfig) (ionoscloud.LabelResources, error) {
+	listQueryParams, err := query.GetListQueryParams(c)
+	if err != nil {
+		return ionoscloud.LabelResources{}, err
+	}
+
+	req := client.Must().CloudClient.LabelsApi.ImagesLabelsGet(
+		context.Background(),
+		viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId)))
+
+	if listQueryParams.QueryParams.Depth != nil {
+		req = req.Depth(*listQueryParams.QueryParams.Depth)
+	}
+
+	if listQueryParams.MaxResults != nil {
+		req = req.MaxResults(*listQueryParams.MaxResults)
+	}
+
+	if listQueryParams.OrderBy != nil {
+		req = req.OrderBy(*listQueryParams.OrderBy)
+	}
+
+	if listQueryParams.Filters != nil {
+		for key, values := range *listQueryParams.Filters {
+			for _, value := range values {
+				req = req.Filter(key, value)
+			}
+		}
+	}
+
+	labels, _, err := req.Execute()
+	if err != nil {
+		return ionoscloud.LabelResources{}, err
+	}
+	return labels, nil
+}
+
+func RunImageLabelsList(c *core.CommandConfig) error {
+	labels, err := listImageLabels(c)
+	if err != nil {
+		return fmt.Errorf("could not get items of Image Labels: %w", err)
+	}
+
+	cols := viper.GetStringSlice(core.GetFlagName(c.Resource, constants.ArgCols))
+
+	out, err := jsontabwriter.GenerateOutput("items", jsonpaths.Label, labels,
+		tabheaders.GetHeadersAllDefault(defaultLabelCols, cols))
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
+
+	return nil
+}
+
+func RunImageLabelGet(c *core.CommandConfig) error {
+	imageId := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))
+	labelKey := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelKey))
+
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput(
+		"Getting label with key: %v of Image with id: %v...", labelKey, imageId))
+
+	labelDc, _, err := client.Must().CloudClient.LabelsApi.ImagesLabelsFindByKey(context.Background(), imageId, labelKey).Execute()
+	if err != nil {
+		return err
+	}
+
+	cols := viper.GetStringSlice(core.GetFlagName(c.Resource, constants.ArgCols))
+
+	out, err := jsontabwriter.GenerateOutput("", jsonpaths.Label, labelDc,
+		tabheaders.GetHeadersAllDefault(defaultLabelCols, cols))
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
+
+	return nil
+}
+
+func RunImageLabelAdd(c *core.CommandConfig) error {
+	imageId := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))
+	labelKey := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelKey))
+	labelValue := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelValue))
+
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput(
+		"Adding label with key: %v and value: %v to Image with id: %v...", labelKey, labelValue, imageId))
+
+	labelDc, _, err := client.Must().CloudClient.LabelsApi.ImagesLabelsPost(context.Background(), imageId).Label(
+		ionoscloud.LabelResource{
+			Properties: &ionoscloud.LabelResourceProperties{
+				Key:   &labelKey,
+				Value: &labelValue,
+			},
+		}).Execute()
+	if err != nil {
+		return err
+	}
+
+	cols := viper.GetStringSlice(core.GetFlagName(c.Resource, constants.ArgCols))
+
+	out, err := jsontabwriter.GenerateOutput("", jsonpaths.Label, labelDc,
+		tabheaders.GetHeadersAllDefault(defaultLabelCols, cols))
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
+
+	return nil
+}
+
+func RunImageLabelRemove(c *core.CommandConfig) error {
+	if viper.GetBool(core.GetFlagName(c.NS, cloudapiv6.ArgAll)) {
+		if err := RemoveAllImageLabels(c); err != nil {
+			return err
+		}
+
+		return nil
+	}
+
+	imageId := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))
+	labelKey := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelKey))
+
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput(
+		"Removing label with key: %v for Image with id: %v...", labelKey, imageId))
+
+	_, err := client.Must().CloudClient.LabelsApi.ImagesLabelsDelete(context.Background(), imageId, labelKey).Execute()
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), jsontabwriter.GenerateLogOutput("Image Label successfully deleted"))
+	return nil
+}
+
+func RemoveAllImageLabels(c *core.CommandConfig) error {
+	labels, err := listImageLabels(c)
+	if err != nil {
+		return fmt.Errorf("failed getting labels: %w", err)
+	}
+	labelsItems, ok := labels.GetItemsOk()
+
+	if !ok || labelsItems == nil {
+		return fmt.Errorf("could not get items of Image Labels")
+	}
+
+	if len(*labelsItems) <= 0 {
+		return fmt.Errorf("no Image Labels found")
+	}
+
+	if !confirm.FAsk(c.Command.Command.InOrStdin(),
+		fmt.Sprintf("delete all the image labels on image %s? ", viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))),
+		viper.GetBool(constants.ArgForce)) {
+		return fmt.Errorf(confirm.UserDenied)
+	}
+
+	var multiErr error
+	for _, label := range *labelsItems {
+		id, ok := label.GetIdOk()
+		if !ok || id == nil {
+			continue
+		}
+
+		fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput("Starting deleting Label with id: %v...", *id))
+
+		_, err := client.Must().CloudClient.LabelsApi.ImagesLabelsDelete(c.Context, viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId)), *id).Execute()
+		if err != nil {
+			multiErr = errors.Join(multiErr, fmt.Errorf(constants.ErrDeleteAll, c.Resource, *id, err))
+			continue
+		}
+	}
+
+	if multiErr != nil {
+		return multiErr
+	}
+	return nil
+}
+
 func RunServerLabelsList(c *core.CommandConfig) error {
 	listQueryParams, err := query.GetListQueryParams(c)
 	if err != nil {
diff --git a/docs/subcommands/Compute Engine/label/add.md b/docs/subcommands/Compute Engine/label/add.md
index c726518c3..8a9099de6 100644
--- a/docs/subcommands/Compute Engine/label/add.md	
+++ b/docs/subcommands/Compute Engine/label/add.md	
@@ -25,7 +25,7 @@ Use this command to add a Label to a specific Resource.
 Required values to run command:
 
 * Resource Type
-* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id or Snapshot Id
+* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id, Image ID, or Snapshot Id
 * Label Key
 * Label Value
 
@@ -40,13 +40,14 @@ Required values to run command:
   -D, --depth int32            Controls the detail depth of the response objects. Max depth is 10.
   -f, --force                  Force command to execute without user input
   -h, --help                   Print usage
+      --image-id string        The unique Image Id(note: only private images supported)
       --ipblock-id string      The unique IpBlock Id
       --label-key string       The unique Label Key (required)
       --label-value string     The unique Label Value (required)
       --no-headers             Don't print table headers when table output is used
   -o, --output string          Desired output format [text|json|api-json] (default "text")
   -q, --quiet                  Quiet output
-      --resource-type string   Type of resource to add labels to. Can be one of: datacenter, volume, server, snapshot, ipblock (required)
+      --resource-type string   Type of resource to add labels to. Can be one of: datacenter, volume, server, snapshot, ipblock, image (required)
       --server-id string       The unique Server Id
       --snapshot-id string     The unique Snapshot Id
   -v, --verbose                Print step-by-step process when running command
diff --git a/docs/subcommands/Compute Engine/label/get.md b/docs/subcommands/Compute Engine/label/get.md
index 6d4526a36..1a9f9bba0 100644
--- a/docs/subcommands/Compute Engine/label/get.md	
+++ b/docs/subcommands/Compute Engine/label/get.md	
@@ -25,7 +25,7 @@ Use this command to get information about a specified Label from a specified Res
 Required values to run command:
 
 * Resource Type
-* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id or Snapshot Id
+* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id, Image ID, or Snapshot Id
 * Label Key
 
 ## Options
@@ -39,12 +39,13 @@ Required values to run command:
   -D, --depth int32            Controls the detail depth of the response objects. Max depth is 10.
   -f, --force                  Force command to execute without user input
   -h, --help                   Print usage
+      --image-id string        The unique Image Id(note: only private images supported)
       --ipblock-id string      The unique IpBlock Id
       --label-key string       The unique Label Key (required)
       --no-headers             Don't print table headers when table output is used
   -o, --output string          Desired output format [text|json|api-json] (default "text")
   -q, --quiet                  Quiet output
-      --resource-type string   Type of resource to get labels from. Can be one of: datacenter, volume, server, snapshot, ipblock (required)
+      --resource-type string   Type of resource to get labels from. Can be one of: datacenter, volume, server, snapshot, ipblock, image (required)
       --server-id string       The unique Server Id
       --snapshot-id string     The unique Snapshot Id
   -v, --verbose                Print step-by-step process when running command
diff --git a/docs/subcommands/Compute Engine/label/list.md b/docs/subcommands/Compute Engine/label/list.md
index fc717cd43..7f2f657db 100644
--- a/docs/subcommands/Compute Engine/label/list.md	
+++ b/docs/subcommands/Compute Engine/label/list.md	
@@ -39,13 +39,14 @@ Available Filters:
   -F, --filters strings        Limits results to those containing a matching value for a specific property. Use the following format to set filters: --filters KEY1=VALUE1,KEY2=VALUE2
   -f, --force                  Force command to execute without user input
   -h, --help                   Print usage
+      --image-id string        The unique Image Id(note: only private images supported)
       --ipblock-id string      The unique IpBlock Id
   -M, --max-results int32      The maximum number of elements to return
       --no-headers             Don't print table headers when table output is used
       --order-by string        Limits results to those containing a matching value for a specific property
   -o, --output string          Desired output format [text|json|api-json] (default "text")
   -q, --quiet                  Quiet output
-      --resource-type string   Type of resource to list labels from. Can be one of: datacenter, volume, server, snapshot, ipblock (required)
+      --resource-type string   Type of resource to list labels from. Can be one of: datacenter, volume, server, snapshot, ipblock, image (required)
       --server-id string       The unique Server Id
       --snapshot-id string     The unique Snapshot Id
   -v, --verbose                Print step-by-step process when running command
diff --git a/docs/subcommands/Compute Engine/label/remove.md b/docs/subcommands/Compute Engine/label/remove.md
index b59e03d0c..11a0cb096 100644
--- a/docs/subcommands/Compute Engine/label/remove.md	
+++ b/docs/subcommands/Compute Engine/label/remove.md	
@@ -15,7 +15,7 @@ ionosctl label remove [flags]
 For `remove` command:
 
 ```text
-[r]
+[delete del r rm]
 ```
 
 ## Description
@@ -25,7 +25,7 @@ Use this command to remove a Label from a Resource.
 Required values to run command:
 
 * Resource Type
-* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id or Snapshot Id
+* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id, Image ID, or Snapshot Id
 * Label Key
 
 ## Options
@@ -40,12 +40,13 @@ Required values to run command:
   -D, --depth int32            Controls the detail depth of the response objects. Max depth is 10.
   -f, --force                  Force command to execute without user input
   -h, --help                   Print usage
+      --image-id string        The unique Image Id(note: only private images supported)
       --ipblock-id string      The unique IpBlock Id
       --label-key string       The unique Label Key (required)
       --no-headers             Don't print table headers when table output is used
   -o, --output string          Desired output format [text|json|api-json] (default "text")
   -q, --quiet                  Quiet output
-      --resource-type string   Type of resource to remove labels from. Can be one of: datacenter, volume, server, snapshot, ipblock (required)
+      --resource-type string   Type of resource to remove labels from. Can be one of: datacenter, volume, server, snapshot, ipblock, image (required)
       --server-id string       The unique Server Id
       --snapshot-id string     The unique Snapshot Id
   -v, --verbose                Print step-by-step process when running command
diff --git a/services/cloudapi-v6/constants.go b/services/cloudapi-v6/constants.go
index a939195a7..ce413c530 100644
--- a/services/cloudapi-v6/constants.go
+++ b/services/cloudapi-v6/constants.go
@@ -13,6 +13,7 @@ const (
 	VolumeResource     = "volume"
 	IpBlockResource    = "ipblock"
 	SnapshotResource   = "snapshot"
+	ImageResource      = "image"
 )
 
 // Flags


Reviews:
user: login: avirtopeanu-ionos
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T09:59:27Z
user: login: glimberea
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T13:27:42Z
user: login: glimberea
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T13:40:49Z
user: login: avirtopeanu-ionos
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T13:58:07Z
user: login: avirtopeanu-ionos
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T13:58:19Z
user: login: avirtopeanu-ionos
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T13:58:50Z

Review comments:
diff_hunk: @@ -210,6 +213,139 @@ func RemoveAllDatacenterLabels(c *core.CommandConfig) error {
 	return nil
 }
 
+func RunImageLabelsList(c *core.CommandConfig) error {
+	listQueryParams, err := query.GetListQueryParams(c)
+	if err != nil {
+		return err
+	}
+
+	req := client.Must().CloudClient.LabelsApi.ImagesLabelsGet(
+		context.Background(),
+		viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId)))
+
+	if listQueryParams.QueryParams.Depth != nil {
+		req = req.Depth(*listQueryParams.QueryParams.Depth)
+	}
+
+	if listQueryParams.MaxResults != nil {
+		req = req.MaxResults(*listQueryParams.MaxResults)
+	}
+
+	if listQueryParams.OrderBy != nil {
+		req = req.OrderBy(*listQueryParams.OrderBy)
+	}
+
+	if listQueryParams.Filters != nil {
+		for key, values := range *listQueryParams.Filters {
+			for _, value := range values {
+				req = req.Filter(key, value)
+			}
+		}
+	}
+
+	labels, _, err := req.Execute()
+	if err != nil {
+		return err
+	}
+
+	cols := viper.GetStringSlice(core.GetFlagName(c.Resource, constants.ArgCols))
+
+	out, err := jsontabwriter.GenerateOutput("items", jsonpaths.Label, labels,
+		tabheaders.GetHeadersAllDefault(defaultLabelCols, cols))
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
+
+	return nil
+}
+
+func RunImageLabelGet(c *core.CommandConfig) error {
+	imageId := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))
+	labelKey := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelKey))
+
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput(
+		"Getting label with key: %v of Image with id: %v...", labelKey, imageId))
+
+	labelDc, _, err := client.Must().CloudClient.LabelsApi.ImagesLabelsFindByKey(context.Background(), imageId, labelKey).Execute()
+	if err != nil {
+		return err
+	}
+
+	cols := viper.GetStringSlice(core.GetFlagName(c.Resource, constants.ArgCols))
+
+	out, err := jsontabwriter.GenerateOutput("", jsonpaths.Label, labelDc,
+		tabheaders.GetHeadersAllDefault(defaultLabelCols, cols))
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
+
+	return nil
+}
+
+func RunImageLabelAdd(c *core.CommandConfig) error {
+	imageId := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))
+	labelKey := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelKey))
+	labelValue := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelValue))
+
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput(
+		"Adding label with key: %v and value: %v to Image with id: %v...", labelKey, labelValue, imageId))
+
+	labelDc, _, err := client.Must().CloudClient.LabelsApi.ImagesLabelsPost(context.Background(), imageId).Label(
+		ionoscloud.LabelResource{
+			Properties: &ionoscloud.LabelResourceProperties{
+				Key:   &labelKey,
+				Value: &labelValue,
+			},
+		}).Execute()
+	if err != nil {
+		return err
+	}
+
+	cols := viper.GetStringSlice(core.GetFlagName(c.Resource, constants.ArgCols))
+
+	out, err := jsontabwriter.GenerateOutput("", jsonpaths.Label, labelDc,
+		tabheaders.GetHeadersAllDefault(defaultLabelCols, cols))
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
+
+	return nil
+}
+
+func RunImageLabelRemove(c *core.CommandConfig) error {
+	if viper.GetBool(core.GetFlagName(c.NS, cloudapiv6.ArgAll)) {
+		if err := RemoveAllImageLabels(c); err != nil {
+			return err
+		}
+
+		return nil
+	}
+
+	imageId := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))
+	labelKey := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelKey))
+
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput(
+		"Removing label with key: %v for Image with id: %v...", labelKey, imageId))
+
+	_, err := client.Must().CloudClient.LabelsApi.ImagesLabelsDelete(context.Background(), imageId, labelKey).Execute()
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), jsontabwriter.GenerateLogOutput("Image Label successfully deleted"))
+	return nil
+}
+
+func RemoveAllImageLabels(c *core.CommandConfig) error {
+	return nil
path: commands/cloudapi-v6/labelresource.go
user: login: avirtopeanu-ionos
body: forgot to add `delete --all` support...
created_at: 2025-04-07T09:59:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,7 +106,7 @@ func LabelCmd() *core.Command {
 		Verb:       "get",
 		Aliases:    []string{"g"},
 		ShortDesc:  "Get a Label",
-		LongDesc:   "Use this command to get information about a specified Label from a specified Resource.\n\nRequired values to run command:\n\n* Resource Type\n* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id or Snapshot Id\n* Label Key",
+		LongDesc:   "Use this command to get information about a specified Label from a specified Resource.\n\nRequired values to run command:\n\n* Resource Type\n* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id, Image ID, or Snapshot Id\n* Label Key",
path: commands/cloudapi-v6/label.go
user: login: glimberea
body: just a nitpick, but maybe we should have a uniform format for writing ID, at least in the same sentence
created_at: 2025-04-07T12:56:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -180,6 +198,14 @@ func LabelCmd() *core.Command {
 	_ = addLabel.Command.RegisterFlagCompletionFunc(cloudapiv6.ArgSnapshotId, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 		return completer.SnapshotIds(), cobra.ShellCompDirectiveNoFileComp
 	})
+	addLabel.AddUUIDFlag(cloudapiv6.ArgImageId, "", "", cloudapiv6.ImageId+"(note: only private images supported)")
+	_ = addLabel.Command.RegisterFlagCompletionFunc(cloudapiv6.ArgImageId, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		// complete with private images only (cnanot add labels to public images)
path: commands/cloudapi-v6/label.go
user: login: glimberea
body: `cnanot` little typo here
created_at: 2025-04-07T12:58:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -152,7 +170,7 @@ func LabelCmd() *core.Command {
 		Verb:       "add",
 		Aliases:    []string{"a"},
 		ShortDesc:  "Add a Label to a Resource",
-		LongDesc:   "Use this command to add a Label to a specific Resource.\n\nRequired values to run command:\n\n* Resource Type\n* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id or Snapshot Id\n* Label Key\n* Label Value",
+		LongDesc:   "Use this command to add a Label to a specific Resource.\n\nRequired values to run command:\n\n* Resource Type\n* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id, Image ID, or Snapshot Id\n* Label Key\n* Label Value",
path: commands/cloudapi-v6/label.go
user: login: glimberea
body: same nitpick
created_at: 2025-04-07T12:59:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -190,9 +216,9 @@ func LabelCmd() *core.Command {
 		Namespace:  "label",
 		Resource:   "label",
 		Verb:       "remove",
-		Aliases:    []string{"r"},
+		Aliases:    []string{"delete", "del", "r", "rm"},
 		ShortDesc:  "Remove a Label from a Resource",
-		LongDesc:   "Use this command to remove a Label from a Resource.\n\nRequired values to run command:\n\n* Resource Type\n* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id or Snapshot Id\n* Label Key",
+		LongDesc:   "Use this command to remove a Label from a Resource.\n\nRequired values to run command:\n\n* Resource Type\n* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id, Image ID, or Snapshot Id\n* Label Key",
path: commands/cloudapi-v6/label.go
user: login: glimberea
body: same nitpick
created_at: 2025-04-07T12:59:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -210,6 +213,205 @@ func RemoveAllDatacenterLabels(c *core.CommandConfig) error {
 	return nil
 }
 
+func listImageLabels(c *core.CommandConfig) (ionoscloud.LabelResources, error) {
+	listQueryParams, err := query.GetListQueryParams(c)
+	if err != nil {
+		return ionoscloud.LabelResources{}, err
+	}
+
+	req := client.Must().CloudClient.LabelsApi.ImagesLabelsGet(
+		context.Background(),
+		viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId)))
+
+	if listQueryParams.QueryParams.Depth != nil {
+		req = req.Depth(*listQueryParams.QueryParams.Depth)
+	}
+
+	if listQueryParams.MaxResults != nil {
+		req = req.MaxResults(*listQueryParams.MaxResults)
+	}
+
+	if listQueryParams.OrderBy != nil {
+		req = req.OrderBy(*listQueryParams.OrderBy)
+	}
+
+	if listQueryParams.Filters != nil {
+		for key, values := range *listQueryParams.Filters {
+			for _, value := range values {
+				req = req.Filter(key, value)
+			}
+		}
+	}
+
+	labels, _, err := req.Execute()
+	if err != nil {
+		return ionoscloud.LabelResources{}, err
+	}
+	return labels, nil
+}
+
+func RunImageLabelsList(c *core.CommandConfig) error {
+	labels, err := listImageLabels(c)
+	if err != nil {
+		return fmt.Errorf("could not get items of Image Labels: %w", err)
+	}
+
+	cols := viper.GetStringSlice(core.GetFlagName(c.Resource, constants.ArgCols))
+
+	out, err := jsontabwriter.GenerateOutput("items", jsonpaths.Label, labels,
+		tabheaders.GetHeadersAllDefault(defaultLabelCols, cols))
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
+
+	return nil
+}
+
+func RunImageLabelGet(c *core.CommandConfig) error {
+	imageId := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))
+	labelKey := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelKey))
+
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput(
+		"Getting label with key: %v of Image with id: %v...", labelKey, imageId))
+
+	labelDc, _, err := client.Must().CloudClient.LabelsApi.ImagesLabelsFindByKey(context.Background(), imageId, labelKey).Execute()
+	if err != nil {
+		return err
+	}
+
+	cols := viper.GetStringSlice(core.GetFlagName(c.Resource, constants.ArgCols))
+
+	out, err := jsontabwriter.GenerateOutput("", jsonpaths.Label, labelDc,
+		tabheaders.GetHeadersAllDefault(defaultLabelCols, cols))
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
+
+	return nil
+}
+
+func RunImageLabelAdd(c *core.CommandConfig) error {
+	imageId := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))
+	labelKey := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelKey))
+	labelValue := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelValue))
+
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput(
+		"Adding label with key: %v and value: %v to Image with id: %v...", labelKey, labelValue, imageId))
+
+	labelDc, _, err := client.Must().CloudClient.LabelsApi.ImagesLabelsPost(context.Background(), imageId).Label(
+		ionoscloud.LabelResource{
+			Properties: &ionoscloud.LabelResourceProperties{
+				Key:   &labelKey,
+				Value: &labelValue,
+			},
+		}).Execute()
+	if err != nil {
+		return err
+	}
+
+	cols := viper.GetStringSlice(core.GetFlagName(c.Resource, constants.ArgCols))
+
+	out, err := jsontabwriter.GenerateOutput("", jsonpaths.Label, labelDc,
+		tabheaders.GetHeadersAllDefault(defaultLabelCols, cols))
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
+
+	return nil
+}
+
+func RunImageLabelRemove(c *core.CommandConfig) error {
+	if viper.GetBool(core.GetFlagName(c.NS, cloudapiv6.ArgAll)) {
+		if err := RemoveAllImageLabels(c); err != nil {
+			return err
+		}
+
+		return nil
+	}
+
+	imageId := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))
+	labelKey := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelKey))
+
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput(
+		"Removing label with key: %v for Image with id: %v...", labelKey, imageId))
+
+	_, err := client.Must().CloudClient.LabelsApi.ImagesLabelsDelete(context.Background(), imageId, labelKey).Execute()
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), jsontabwriter.GenerateLogOutput("Image Label successfully deleted"))
+	return nil
+}
+
+func RemoveAllImageLabels(c *core.CommandConfig) error {
+	labels, err := listImageLabels(c)
+	if err != nil {
+		return fmt.Errorf("failed getting labels: %w", err)
+	}
+	labelsItems, ok := labels.GetItemsOk()
+
+	if !ok || labelsItems == nil {
+		return fmt.Errorf("could not get items of Image Labels")
+	}
+
+	if len(*labelsItems) <= 0 {
+		return fmt.Errorf("no Image Labels found")
+	}
+
+	for _, label := range *labelsItems {
+		delIdAndName := ""
+
+		if properties, ok := label.GetPropertiesOk(); ok && properties != nil {
+			if key, ok := properties.GetKeyOk(); ok && key != nil {
+				delIdAndName += "Label Key: " + *key
+			}
+
+			if value, ok := properties.GetValueOk(); ok && value != nil {
+				delIdAndName += " Label Value: " + *value
+			}
+		}
+	}
+
+	if !confirm.FAsk(c.Command.Command.InOrStdin(),
path: commands/cloudapi-v6/labelresource.go
user: login: glimberea
body: wouldn't it be better to ask for permission per label instead of in bulk? if we're doing this for the sake of uniformity with other types of labels, I get it, just wanted to ask
created_at: 2025-04-07T13:24:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -210,6 +213,205 @@ func RemoveAllDatacenterLabels(c *core.CommandConfig) error {
 	return nil
 }
 
+func listImageLabels(c *core.CommandConfig) (ionoscloud.LabelResources, error) {
+	listQueryParams, err := query.GetListQueryParams(c)
+	if err != nil {
+		return ionoscloud.LabelResources{}, err
+	}
+
+	req := client.Must().CloudClient.LabelsApi.ImagesLabelsGet(
+		context.Background(),
+		viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId)))
+
+	if listQueryParams.QueryParams.Depth != nil {
+		req = req.Depth(*listQueryParams.QueryParams.Depth)
+	}
+
+	if listQueryParams.MaxResults != nil {
+		req = req.MaxResults(*listQueryParams.MaxResults)
+	}
+
+	if listQueryParams.OrderBy != nil {
+		req = req.OrderBy(*listQueryParams.OrderBy)
+	}
+
+	if listQueryParams.Filters != nil {
+		for key, values := range *listQueryParams.Filters {
+			for _, value := range values {
+				req = req.Filter(key, value)
+			}
+		}
+	}
+
+	labels, _, err := req.Execute()
+	if err != nil {
+		return ionoscloud.LabelResources{}, err
+	}
+	return labels, nil
+}
+
+func RunImageLabelsList(c *core.CommandConfig) error {
+	labels, err := listImageLabels(c)
+	if err != nil {
+		return fmt.Errorf("could not get items of Image Labels: %w", err)
+	}
+
+	cols := viper.GetStringSlice(core.GetFlagName(c.Resource, constants.ArgCols))
+
+	out, err := jsontabwriter.GenerateOutput("items", jsonpaths.Label, labels,
+		tabheaders.GetHeadersAllDefault(defaultLabelCols, cols))
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
+
+	return nil
+}
+
+func RunImageLabelGet(c *core.CommandConfig) error {
+	imageId := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))
+	labelKey := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelKey))
+
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput(
+		"Getting label with key: %v of Image with id: %v...", labelKey, imageId))
+
+	labelDc, _, err := client.Must().CloudClient.LabelsApi.ImagesLabelsFindByKey(context.Background(), imageId, labelKey).Execute()
+	if err != nil {
+		return err
+	}
+
+	cols := viper.GetStringSlice(core.GetFlagName(c.Resource, constants.ArgCols))
+
+	out, err := jsontabwriter.GenerateOutput("", jsonpaths.Label, labelDc,
+		tabheaders.GetHeadersAllDefault(defaultLabelCols, cols))
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
+
+	return nil
+}
+
+func RunImageLabelAdd(c *core.CommandConfig) error {
+	imageId := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))
+	labelKey := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelKey))
+	labelValue := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelValue))
+
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput(
+		"Adding label with key: %v and value: %v to Image with id: %v...", labelKey, labelValue, imageId))
+
+	labelDc, _, err := client.Must().CloudClient.LabelsApi.ImagesLabelsPost(context.Background(), imageId).Label(
+		ionoscloud.LabelResource{
+			Properties: &ionoscloud.LabelResourceProperties{
+				Key:   &labelKey,
+				Value: &labelValue,
+			},
+		}).Execute()
+	if err != nil {
+		return err
+	}
+
+	cols := viper.GetStringSlice(core.GetFlagName(c.Resource, constants.ArgCols))
+
+	out, err := jsontabwriter.GenerateOutput("", jsonpaths.Label, labelDc,
+		tabheaders.GetHeadersAllDefault(defaultLabelCols, cols))
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
+
+	return nil
+}
+
+func RunImageLabelRemove(c *core.CommandConfig) error {
+	if viper.GetBool(core.GetFlagName(c.NS, cloudapiv6.ArgAll)) {
+		if err := RemoveAllImageLabels(c); err != nil {
+			return err
+		}
+
+		return nil
+	}
+
+	imageId := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))
+	labelKey := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelKey))
+
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput(
+		"Removing label with key: %v for Image with id: %v...", labelKey, imageId))
+
+	_, err := client.Must().CloudClient.LabelsApi.ImagesLabelsDelete(context.Background(), imageId, labelKey).Execute()
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), jsontabwriter.GenerateLogOutput("Image Label successfully deleted"))
+	return nil
+}
+
+func RemoveAllImageLabels(c *core.CommandConfig) error {
+	labels, err := listImageLabels(c)
+	if err != nil {
+		return fmt.Errorf("failed getting labels: %w", err)
+	}
+	labelsItems, ok := labels.GetItemsOk()
+
+	if !ok || labelsItems == nil {
+		return fmt.Errorf("could not get items of Image Labels")
+	}
+
+	if len(*labelsItems) <= 0 {
+		return fmt.Errorf("no Image Labels found")
+	}
+
+	for _, label := range *labelsItems {
+		delIdAndName := ""
path: commands/cloudapi-v6/labelresource.go
user: login: glimberea
body: I don't see this used anywhereother than in this for
created_at: 2025-04-07T13:40:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,7 +106,7 @@ func LabelCmd() *core.Command {
 		Verb:       "get",
 		Aliases:    []string{"g"},
 		ShortDesc:  "Get a Label",
-		LongDesc:   "Use this command to get information about a specified Label from a specified Resource.\n\nRequired values to run command:\n\n* Resource Type\n* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id or Snapshot Id\n* Label Key",
+		LongDesc:   "Use this command to get information about a specified Label from a specified Resource.\n\nRequired values to run command:\n\n* Resource Type\n* Resource Id: Datacenter Id, Server Id, Volume Id, IpBlock Id, Image ID, or Snapshot Id\n* Label Key",
path: commands/cloudapi-v6/label.go
user: login: avirtopeanu-ionos
body: imo it should be `ID` in all customer facing docs. But let's do this in another PR.
created_at: 2025-04-07T13:58:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -180,6 +198,14 @@ func LabelCmd() *core.Command {
 	_ = addLabel.Command.RegisterFlagCompletionFunc(cloudapiv6.ArgSnapshotId, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 		return completer.SnapshotIds(), cobra.ShellCompDirectiveNoFileComp
 	})
+	addLabel.AddUUIDFlag(cloudapiv6.ArgImageId, "", "", cloudapiv6.ImageId+"(note: only private images supported)")
+	_ = addLabel.Command.RegisterFlagCompletionFunc(cloudapiv6.ArgImageId, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		// complete with private images only (cnanot add labels to public images)
path: commands/cloudapi-v6/label.go
user: login: avirtopeanu-ionos
body: thanks
created_at: 2025-04-07T13:58:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -210,6 +213,205 @@ func RemoveAllDatacenterLabels(c *core.CommandConfig) error {
 	return nil
 }
 
+func listImageLabels(c *core.CommandConfig) (ionoscloud.LabelResources, error) {
+	listQueryParams, err := query.GetListQueryParams(c)
+	if err != nil {
+		return ionoscloud.LabelResources{}, err
+	}
+
+	req := client.Must().CloudClient.LabelsApi.ImagesLabelsGet(
+		context.Background(),
+		viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId)))
+
+	if listQueryParams.QueryParams.Depth != nil {
+		req = req.Depth(*listQueryParams.QueryParams.Depth)
+	}
+
+	if listQueryParams.MaxResults != nil {
+		req = req.MaxResults(*listQueryParams.MaxResults)
+	}
+
+	if listQueryParams.OrderBy != nil {
+		req = req.OrderBy(*listQueryParams.OrderBy)
+	}
+
+	if listQueryParams.Filters != nil {
+		for key, values := range *listQueryParams.Filters {
+			for _, value := range values {
+				req = req.Filter(key, value)
+			}
+		}
+	}
+
+	labels, _, err := req.Execute()
+	if err != nil {
+		return ionoscloud.LabelResources{}, err
+	}
+	return labels, nil
+}
+
+func RunImageLabelsList(c *core.CommandConfig) error {
+	labels, err := listImageLabels(c)
+	if err != nil {
+		return fmt.Errorf("could not get items of Image Labels: %w", err)
+	}
+
+	cols := viper.GetStringSlice(core.GetFlagName(c.Resource, constants.ArgCols))
+
+	out, err := jsontabwriter.GenerateOutput("items", jsonpaths.Label, labels,
+		tabheaders.GetHeadersAllDefault(defaultLabelCols, cols))
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
+
+	return nil
+}
+
+func RunImageLabelGet(c *core.CommandConfig) error {
+	imageId := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))
+	labelKey := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelKey))
+
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput(
+		"Getting label with key: %v of Image with id: %v...", labelKey, imageId))
+
+	labelDc, _, err := client.Must().CloudClient.LabelsApi.ImagesLabelsFindByKey(context.Background(), imageId, labelKey).Execute()
+	if err != nil {
+		return err
+	}
+
+	cols := viper.GetStringSlice(core.GetFlagName(c.Resource, constants.ArgCols))
+
+	out, err := jsontabwriter.GenerateOutput("", jsonpaths.Label, labelDc,
+		tabheaders.GetHeadersAllDefault(defaultLabelCols, cols))
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
+
+	return nil
+}
+
+func RunImageLabelAdd(c *core.CommandConfig) error {
+	imageId := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))
+	labelKey := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelKey))
+	labelValue := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelValue))
+
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput(
+		"Adding label with key: %v and value: %v to Image with id: %v...", labelKey, labelValue, imageId))
+
+	labelDc, _, err := client.Must().CloudClient.LabelsApi.ImagesLabelsPost(context.Background(), imageId).Label(
+		ionoscloud.LabelResource{
+			Properties: &ionoscloud.LabelResourceProperties{
+				Key:   &labelKey,
+				Value: &labelValue,
+			},
+		}).Execute()
+	if err != nil {
+		return err
+	}
+
+	cols := viper.GetStringSlice(core.GetFlagName(c.Resource, constants.ArgCols))
+
+	out, err := jsontabwriter.GenerateOutput("", jsonpaths.Label, labelDc,
+		tabheaders.GetHeadersAllDefault(defaultLabelCols, cols))
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), out)
+
+	return nil
+}
+
+func RunImageLabelRemove(c *core.CommandConfig) error {
+	if viper.GetBool(core.GetFlagName(c.NS, cloudapiv6.ArgAll)) {
+		if err := RemoveAllImageLabels(c); err != nil {
+			return err
+		}
+
+		return nil
+	}
+
+	imageId := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgImageId))
+	labelKey := viper.GetString(core.GetFlagName(c.NS, cloudapiv6.ArgLabelKey))
+
+	fmt.Fprintf(c.Command.Command.ErrOrStderr(), jsontabwriter.GenerateVerboseOutput(
+		"Removing label with key: %v for Image with id: %v...", labelKey, imageId))
+
+	_, err := client.Must().CloudClient.LabelsApi.ImagesLabelsDelete(context.Background(), imageId, labelKey).Execute()
+	if err != nil {
+		return err
+	}
+
+	fmt.Fprintf(c.Command.Command.OutOrStdout(), jsontabwriter.GenerateLogOutput("Image Label successfully deleted"))
+	return nil
+}
+
+func RemoveAllImageLabels(c *core.CommandConfig) error {
+	labels, err := listImageLabels(c)
+	if err != nil {
+		return fmt.Errorf("failed getting labels: %w", err)
+	}
+	labelsItems, ok := labels.GetItemsOk()
+
+	if !ok || labelsItems == nil {
+		return fmt.Errorf("could not get items of Image Labels")
+	}
+
+	if len(*labelsItems) <= 0 {
+		return fmt.Errorf("no Image Labels found")
+	}
+
+	for _, label := range *labelsItems {
+		delIdAndName := ""
+
+		if properties, ok := label.GetPropertiesOk(); ok && properties != nil {
+			if key, ok := properties.GetKeyOk(); ok && key != nil {
+				delIdAndName += "Label Key: " + *key
+			}
+
+			if value, ok := properties.GetValueOk(); ok && value != nil {
+				delIdAndName += " Label Value: " + *value
+			}
+		}
+	}
+
+	if !confirm.FAsk(c.Command.Command.InOrStdin(),
path: commands/cloudapi-v6/labelresource.go
user: login: avirtopeanu-ionos
body: yes it would! but indeed we're doing it for the sake of uniformity.
created_at: 2025-04-07T13:58:49Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-04-07T14:06:18Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=cli-ionosctl&pullRequest=512) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1 New issue](https://sonarcloud.io/project/issues?id=cli-ionosctl&pullRequest=512&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=cli-ionosctl&pullRequest=512&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=cli-ionosctl&pullRequest=512&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=cli-ionosctl&pullRequest=512&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=cli-ionosctl&pullRequest=512&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=cli-ionosctl&pullRequest=512)


author_association: NONE