diff --git a/irictl-volume/cmd/irictl-volume/irictlvolume/common/common.go b/irictl-volume/cmd/irictl-volume/irictlvolume/common/common.go index ed2fe63a0..77e2477bb 100644 --- a/irictl-volume/cmd/irictl-volume/irictlvolume/common/common.go +++ b/irictl-volume/cmd/irictl-volume/irictlvolume/common/common.go @@ -58,5 +58,6 @@ func NewOutputOptions() *clicommon.OutputOptions { } var ( - VolumeAliases = []string{"volumes", "vol", "vols"} + VolumeAliases = []string{"volumes", "vol", "vols"} + VolumeSnapshotAliases = []string{"volumesnapshots", "volsnap", "volsnaps"} ) diff --git a/irictl-volume/cmd/irictl-volume/irictlvolume/create/create.go b/irictl-volume/cmd/irictl-volume/irictlvolume/create/create.go index 91bf45d9d..b4ddeaf0c 100644 --- a/irictl-volume/cmd/irictl-volume/irictlvolume/create/create.go +++ b/irictl-volume/cmd/irictl-volume/irictlvolume/create/create.go @@ -6,6 +6,7 @@ package create import ( "github.com/ironcore-dev/ironcore/irictl-volume/cmd/irictl-volume/irictlvolume/common" "github.com/ironcore-dev/ironcore/irictl-volume/cmd/irictl-volume/irictlvolume/create/volume" + "github.com/ironcore-dev/ironcore/irictl-volume/cmd/irictl-volume/irictlvolume/create/volumesnapshot" clicommon "github.com/ironcore-dev/ironcore/irictl/cmd" "github.com/spf13/cobra" ) @@ -17,6 +18,7 @@ func Command(streams clicommon.Streams, clientFactory common.ClientFactory) *cob cmd.AddCommand( volume.Command(streams, clientFactory), + volumesnapshot.Command(streams, clientFactory), ) return cmd diff --git a/irictl-volume/cmd/irictl-volume/irictlvolume/create/volumesnapshot/volumesnapshot.go b/irictl-volume/cmd/irictl-volume/irictlvolume/create/volumesnapshot/volumesnapshot.go new file mode 100644 index 000000000..6a3954aa8 --- /dev/null +++ b/irictl-volume/cmd/irictl-volume/irictlvolume/create/volumesnapshot/volumesnapshot.go @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package volumesnapshot + +import ( + "context" + "fmt" + "os" + + iri "github.com/ironcore-dev/ironcore/iri/apis/volume/v1alpha1" + "github.com/ironcore-dev/ironcore/irictl-volume/cmd/irictl-volume/irictlvolume/common" + clicommon "github.com/ironcore-dev/ironcore/irictl/cmd" + "github.com/ironcore-dev/ironcore/irictl/decoder" + "github.com/ironcore-dev/ironcore/irictl/renderer" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + ctrl "sigs.k8s.io/controller-runtime" +) + +type Options struct { + Filename string +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + fs.StringVarP(&o.Filename, "filename", "f", o.Filename, "Path to a file to read.") +} + +func Command(streams clicommon.Streams, clientFactory common.ClientFactory) *cobra.Command { + var ( + outputOpts = common.NewOutputOptions() + opts Options + ) + + cmd := &cobra.Command{ + Use: "volumesnapshot", + Aliases: common.VolumeSnapshotAliases, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + log := ctrl.LoggerFrom(ctx) + + client, cleanup, err := clientFactory.New() + if err != nil { + return err + } + defer func() { + if err := cleanup(); err != nil { + log.Error(err, "Error cleaning up") + } + }() + + r, err := outputOpts.RendererOrNil() + if err != nil { + return err + } + + return Run(ctx, streams, client, r, opts) + }, + } + + outputOpts.AddFlags(cmd.Flags()) + opts.AddFlags(cmd.Flags()) + + return cmd +} + +func Run(ctx context.Context, streams clicommon.Streams, client iri.VolumeRuntimeClient, r renderer.Renderer, opts Options) error { + data, err := clicommon.ReadFileOrReader(opts.Filename, os.Stdin) + if err != nil { + return err + } + + volumeSnapshot := &iri.VolumeSnapshot{} + if err := decoder.Decode(data, volumeSnapshot); err != nil { + return err + } + + res, err := client.CreateVolumeSnapshot(ctx, &iri.CreateVolumeSnapshotRequest{VolumeSnapshot: volumeSnapshot}) + if err != nil { + return err + } + + if r != nil { + return r.Render(res.VolumeSnapshot, streams.Out) + } + + _, _ = fmt.Fprintf(streams.Out, "Created volume snapshot %s\n", res.VolumeSnapshot.Metadata.Id) + return nil +} diff --git a/irictl-volume/cmd/irictl-volume/irictlvolume/delete/delete.go b/irictl-volume/cmd/irictl-volume/irictlvolume/delete/delete.go index 59ac2462c..e4741a24c 100644 --- a/irictl-volume/cmd/irictl-volume/irictlvolume/delete/delete.go +++ b/irictl-volume/cmd/irictl-volume/irictlvolume/delete/delete.go @@ -6,6 +6,7 @@ package delete import ( "github.com/ironcore-dev/ironcore/irictl-volume/cmd/irictl-volume/irictlvolume/common" "github.com/ironcore-dev/ironcore/irictl-volume/cmd/irictl-volume/irictlvolume/delete/volume" + "github.com/ironcore-dev/ironcore/irictl-volume/cmd/irictl-volume/irictlvolume/delete/volumesnapshot" clicommon "github.com/ironcore-dev/ironcore/irictl/cmd" "github.com/spf13/cobra" ) @@ -17,6 +18,7 @@ func Command(streams clicommon.Streams, clientFactory common.ClientFactory) *cob cmd.AddCommand( volume.Command(streams, clientFactory), + volumesnapshot.Command(streams, clientFactory), ) return cmd diff --git a/irictl-volume/cmd/irictl-volume/irictlvolume/delete/volumesnapshot/volumesnapshot.go b/irictl-volume/cmd/irictl-volume/irictlvolume/delete/volumesnapshot/volumesnapshot.go new file mode 100644 index 000000000..626ad6a69 --- /dev/null +++ b/irictl-volume/cmd/irictl-volume/irictlvolume/delete/volumesnapshot/volumesnapshot.go @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package volumesnapshot + +import ( + "context" + "fmt" + + iri "github.com/ironcore-dev/ironcore/iri/apis/volume/v1alpha1" + "github.com/ironcore-dev/ironcore/irictl-volume/cmd/irictl-volume/irictlvolume/common" + clicommon "github.com/ironcore-dev/ironcore/irictl/cmd" + "github.com/spf13/cobra" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + ctrl "sigs.k8s.io/controller-runtime" +) + +func Command(streams clicommon.Streams, clientFactory common.ClientFactory) *cobra.Command { + cmd := &cobra.Command{ + Use: "volumesnapshot id [ids...]", + Aliases: common.VolumeSnapshotAliases, + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + log := ctrl.LoggerFrom(ctx) + + client, cleanup, err := clientFactory.New() + if err != nil { + return err + } + defer func() { + if err := cleanup(); err != nil { + log.Error(err, "Error cleaning up") + } + }() + + ids := args + + return Run(cmd.Context(), streams, client, ids) + }, + } + + return cmd +} + +func Run(ctx context.Context, streams clicommon.Streams, client iri.VolumeRuntimeClient, ids []string) error { + for _, id := range ids { + if _, err := client.DeleteVolumeSnapshot(ctx, &iri.DeleteVolumeSnapshotRequest{ + VolumeSnapshotId: id, + }); err != nil { + if status.Code(err) != codes.NotFound { + return fmt.Errorf("error deleting volume snapshot %s: %w", id, err) + } + + _, _ = fmt.Fprintf(streams.Out, "VolumeSnapshot %s not found\n", id) + } else { + _, _ = fmt.Fprintf(streams.Out, "VolumeSnapshot %s deleted\n", id) + } + } + return nil +} diff --git a/irictl-volume/cmd/irictl-volume/irictlvolume/get/get.go b/irictl-volume/cmd/irictl-volume/irictlvolume/get/get.go index ca8b01938..5b22ed508 100644 --- a/irictl-volume/cmd/irictl-volume/irictlvolume/get/get.go +++ b/irictl-volume/cmd/irictl-volume/irictlvolume/get/get.go @@ -8,6 +8,7 @@ import ( "github.com/ironcore-dev/ironcore/irictl-volume/cmd/irictl-volume/irictlvolume/get/event" "github.com/ironcore-dev/ironcore/irictl-volume/cmd/irictl-volume/irictlvolume/get/status" "github.com/ironcore-dev/ironcore/irictl-volume/cmd/irictl-volume/irictlvolume/get/volume" + "github.com/ironcore-dev/ironcore/irictl-volume/cmd/irictl-volume/irictlvolume/get/volumesnapshot" clicommon "github.com/ironcore-dev/ironcore/irictl/cmd" "github.com/spf13/cobra" ) @@ -21,6 +22,7 @@ func Command(streams clicommon.Streams, clientFactory common.ClientFactory) *cob volume.Command(streams, clientFactory), status.Command(streams, clientFactory), event.Command(streams, clientFactory), + volumesnapshot.Command(streams, clientFactory), ) return cmd diff --git a/irictl-volume/cmd/irictl-volume/irictlvolume/get/volumesnapshot/volumesnapshot.go b/irictl-volume/cmd/irictl-volume/irictlvolume/get/volumesnapshot/volumesnapshot.go new file mode 100644 index 000000000..82b4fe66a --- /dev/null +++ b/irictl-volume/cmd/irictl-volume/irictlvolume/get/volumesnapshot/volumesnapshot.go @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package volumesnapshot + +import ( + "context" + "fmt" + + iri "github.com/ironcore-dev/ironcore/iri/apis/volume/v1alpha1" + "github.com/ironcore-dev/ironcore/irictl-volume/cmd/irictl-volume/irictlvolume/common" + clicommon "github.com/ironcore-dev/ironcore/irictl/cmd" + "github.com/ironcore-dev/ironcore/irictl/renderer" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + ctrl "sigs.k8s.io/controller-runtime" +) + +type Options struct { +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { +} + +func Command(streams clicommon.Streams, clientFactory common.ClientFactory) *cobra.Command { + var ( + opts Options + outputOpts = common.NewOutputOptions() + ) + + cmd := &cobra.Command{ + Use: "volumesnapshot", + Aliases: common.VolumeSnapshotAliases, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + log := ctrl.LoggerFrom(ctx) + + client, cleanup, err := clientFactory.New() + if err != nil { + return err + } + defer func() { + if err := cleanup(); err != nil { + log.Error(err, "Error cleaning up") + } + }() + + render, err := outputOpts.Renderer("table") + if err != nil { + return err + } + + return Run(cmd.Context(), streams, client, render, opts) + }, + } + + outputOpts.AddFlags(cmd.Flags()) + opts.AddFlags(cmd.Flags()) + + return cmd +} + +func Run(ctx context.Context, streams clicommon.Streams, client iri.VolumeRuntimeClient, render renderer.Renderer, opts Options) error { + res, err := client.ListVolumeSnapshots(ctx, &iri.ListVolumeSnapshotsRequest{}) + if err != nil { + return fmt.Errorf("error listing volume snapshots: %w", err) + } + + return render.Render(res.VolumeSnapshots, streams.Out) +} diff --git a/irictl-volume/config/samples/volume.yaml b/irictl-volume/config/samples/volume.yaml index 15dd0e07a..c4cb4ea27 100644 --- a/irictl-volume/config/samples/volume.yaml +++ b/irictl-volume/config/samples/volume.yaml @@ -7,4 +7,4 @@ spec: class: volumeclass-sample image: ghcr.io/ironcore-dev/ironcore-image/gardenlinux:latest resources: - storage_bytes: 1073741824 + storage_bytes: 1073741824 \ No newline at end of file diff --git a/irictl-volume/config/samples/volumesnapshot.yaml b/irictl-volume/config/samples/volumesnapshot.yaml new file mode 100644 index 000000000..a6135309a --- /dev/null +++ b/irictl-volume/config/samples/volumesnapshot.yaml @@ -0,0 +1,7 @@ +metadata: + annotations: + foo: bar + labels: + bar: baz +spec: + volume_id: 2992076108895c9fb7a3f46ccac649763ed3370922ed5dd33c1695cb5f770cd diff --git a/irictl-volume/tableconverters/volumesnapshot.go b/irictl-volume/tableconverters/volumesnapshot.go new file mode 100644 index 000000000..32fa756f9 --- /dev/null +++ b/irictl-volume/tableconverters/volumesnapshot.go @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package tableconverters + +import ( + "time" + + iri "github.com/ironcore-dev/ironcore/iri/apis/volume/v1alpha1" + "github.com/ironcore-dev/ironcore/irictl/api" + "github.com/ironcore-dev/ironcore/irictl/tableconverter" + "k8s.io/apimachinery/pkg/util/duration" +) + +var ( + volumeSnapshotHeaders = []api.Header{ + {Name: "ID"}, + {Name: "VolumeID"}, + {Name: "State"}, + {Name: "Age"}, + } +) + +var ( + VolumeSnapshot = tableconverter.Funcs[*iri.VolumeSnapshot]{ + Headers: tableconverter.Headers(volumeSnapshotHeaders), + Rows: tableconverter.SingleRowFrom(func(volumeSnapshot *iri.VolumeSnapshot) (api.Row, error) { + return api.Row{ + volumeSnapshot.Metadata.Id, + volumeSnapshot.Spec.VolumeId, + volumeSnapshot.Status.State.String(), + duration.HumanDuration(time.Since(time.Unix(0, volumeSnapshot.Metadata.CreatedAt))), + }, nil + }), + } + VolumeSnapshotSlice = tableconverter.SliceFuncs[*iri.VolumeSnapshot](VolumeSnapshot) +) + +func init() { + RegistryBuilder.Register( + tableconverter.ToTagAndTypedAny[*iri.VolumeSnapshot](VolumeSnapshot), + tableconverter.ToTagAndTypedAny[[]*iri.VolumeSnapshot](VolumeSnapshotSlice), + ) +}