-
Notifications
You must be signed in to change notification settings - Fork 7
Feat: object storage bucket, policy, object, acl, version crud #659
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
Merged
Merged
Changes from all commits
Commits
Show all changes
51 commits
Select commit
Hold shift + click to select a range
78f87ff
test: dump output when testing
avirtopeanu-ionos 6168c69
test: assert stderr
avirtopeanu-ionos 763f722
redact stderr too
avirtopeanu-ionos 2ea8313
fix: redact full flag values including PEM, silence setup_file noise
avirtopeanu-ionos 6de3e6f
feat: add object-storage bucket get command
cristiGuranIonos a6ac60a
bats: add e2e test
cristiGuranIonos b3c42b0
go: remove replace
cristiGuranIonos cfc21f2
go mod vendor
cristiGuranIonos c46f235
license
cristiGuranIonos e8dd8f2
fmt: changes
cristiGuranIonos b2eb13d
fmt: make docs
cristiGuranIonos a6e4396
feat: add object-storage bucket create and delete commands
cristiGuranIonos 606da36
fmt: fail if not exist bats
cristiGuranIonos 238d7b6
fix: get and tests
cristiGuranIonos 859400f
fix: docs
cristiGuranIonos a293ef8
fix: docs and remove region
cristiGuranIonos 60aa642
fix: claude
cristiGuranIonos 69051bf
feat: add list
cristiGuranIonos e2238e9
feat: head bucket
cristiGuranIonos 1d54f71
feat: get versioning
cristiGuranIonos e86b6a7
feat: add list objects command
cristiGuranIonos c174df1
feat: add recursive to delete bucket with objects
cristiGuranIonos cac0292
feat: code review, remove unused
cristiGuranIonos 08bed97
feat: fix bats, move versioning to subcommand
cristiGuranIonos 1d63ed4
feat: align with repo
cristiGuranIonos 86c73bc
feat: simplify, add tests
cristiGuranIonos f8ffcf5
feat: set bucket versioning
cristiGuranIonos 4f3f75e
add bucket policy command tree (#660)
glimberea 0fec8f4
implement object subcommands (#661)
glimberea 607a7ce
add cors subcommands (#662)
glimberea 62f46bb
feat: use GetRegionalObjectStorageClient
cristiGuranIonos 4665304
feat: add tagging
cristiGuranIonos 8f5c84d
feat: fix ci
cristiGuranIonos 2de2511
add encryption subcommands
b5bb7c0
autocomplete on flags and object list
00ea23b
fix: transport rewrite xml, use context
cristiGuranIonos d0d737f
use client Get/Must model, add location flag with fileconfig support …
glimberea 182ad5c
add --all flags to bucket and object delection, add commands for obje…
glimberea 48f1af3
fix tests cleanup
glimberea 42a8ba8
fix cols flags
glimberea 01464ba
remove single-purpose variable declarations
glimberea feaa4ef
refactor and add s3 credentials provenance
glimberea 333150a
add object tagging, lifecycle and public access block commands. move …
glimberea 0f103a9
still show provenance sources even when creds are not set
glimberea bde2545
remove mentions of s3
glimberea b51742e
make docs
glimberea 7903e85
comments
glimberea 9d99213
test: move object-storage tests to suites, use setup.bats conventions
avirtopeanu-ionos 8b0d7c9
doc: changelog
avirtopeanu-ionos 0d7c8b3
test: add object storage unit tests
avirtopeanu-ionos a19b617
fix: copilot review
avirtopeanu-ionos File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| # CLAUDE.md | ||
|
|
||
| ## Git Workflow (MANDATORY) | ||
|
|
||
| **Never work directly on `master` or `<base-branch>`.** Always create a feature branch and open a PR. | ||
|
|
||
| ```bash | ||
| # Before starting any work: | ||
| git checkout master && git pull # git checkout <base-branch> && git pull | ||
| git checkout -b feat/<short-description> # or fix:/, doc:/, test:/, refactor:/ | ||
|
|
||
| # When done: | ||
| git push -u origin feat/<short-description> | ||
| gh pr create --title "feat: <description>" --body "..." | ||
| ``` | ||
|
|
||
| - **Never commit directly to `master` or `<base-branch>`** — all changes go through a PR reviewed and merged by a human. | ||
| - Branch naming: `feat/<name>`, `fix/<name>`, `doc/<name>`, `test/<name>`, `refactor/<name>`. | ||
| - One logical change per branch. Keep PRs focused. | ||
|
|
||
| This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
|
|
||
| ## Overview | ||
|
|
||
| `ionosctl` is a Go CLI tool for managing IONOS Cloud resources. It uses Cobra for the CLI framework, Viper for configuration, and multiple IONOS SDK packages for different services (Cloud API v6, DBaaS, DNS, CDN, VPN, Kafka, etc.). | ||
|
|
||
| ## Common Commands | ||
|
|
||
| ```bash | ||
| make build # Build the binary (runs tools/build.sh build) | ||
| make install # Install locally | ||
| make utest # Run unit tests with coverage | ||
| make itest # Run integration + unit tests (requires build tag: integration) | ||
| make test # Full test suite: bats-core shell tests + go tests | ||
| make lint # Run golangci-lint | ||
| make gofmt_check # Check code formatting | ||
| make gofmt # Format code | ||
| make mocks # Regenerate mocks (uses golang/mock) | ||
| make vendor # Update vendor dependencies | ||
| ``` | ||
| ## Testing (MANDATORY) | ||
|
|
||
| **YOU ARE NOT ALLOWED TO RUN TESTS WITHOUT MY EXPLICIT APPROVAL.** | ||
|
|
||
| Run a single Go test: | ||
| ```bash | ||
| go test ./commands/cloudapi-v6/... -run TestFunctionName -v | ||
| go test ./internal/printer/... -run TestFunctionName -v | ||
| ``` | ||
|
|
||
| Run tests with integration tag: | ||
| ```bash | ||
| go test -tags integration ./... | ||
| ``` | ||
|
|
||
| ## Architecture | ||
|
|
||
| ### Layer Structure | ||
|
|
||
| ``` | ||
| main.go → commands/ → internal/core/ → services/ → IONOS SDKs | ||
| ↘ internal/printer/ | ||
| ↘ internal/client/ | ||
| ``` | ||
|
|
||
| 1. **`commands/`** — Command definitions organized by service (`cloudapi-v6/`, `dns/`, `cdn/`, `dbaas/`, etc.) | ||
| 2. **`internal/core/`** — Command framework wrapping Cobra (`Command`, `CommandBuilder`, `CommandConfig`) | ||
| 3. **`services/cloudapi-v6/resources/`** — Service layer wrapping Cloud API v6 SDK calls | ||
| 4. **`internal/printer/jsontabwriter/`** — Output formatting (text tables, JSON, api-json) | ||
| 5. **`internal/client/`** — API client initialization, credentials, multi-SDK support | ||
| 6. **`internal/constants/`** — Flag name constants and shared strings | ||
|
|
||
| ### Command Structure Pattern | ||
|
|
||
| Every command follows a **Namespace.Resource.Verb** hierarchy and is built with `core.CommandBuilder`: | ||
|
|
||
| ```go | ||
| core.NewCommand(ctx, parentCmd, core.CommandBuilder{ | ||
| Namespace: "datacenter", | ||
| Resource: "datacenter", | ||
| Verb: "list", | ||
| Aliases: []string{"l", "ls"}, | ||
| ShortDesc: "List Data Centers", | ||
| PreCmdRun: core.NoPreRun, // validation (returns error to abort) | ||
| CmdRun: RunDataCenterList, // main logic | ||
| InitClient: true, // creates API client before CmdRun | ||
| }) | ||
| ``` | ||
|
|
||
| - **`PreCmdRun`** validates flags and preconditions; return an error to abort execution | ||
| - **`CmdRun`** receives `*core.CommandConfig` which provides access to services, viper config, and the cobra command | ||
|
|
||
| ### Adding a New Command | ||
|
|
||
| 1. Create `commands/{service}/{resource}.go` | ||
| 2. Define a root function returning `*core.Command` (see `commands/cloudapi-v6/location.go` as a reference) | ||
| 3. Add subcommands via `core.NewCommand` with a `CommandBuilder` | ||
| 4. Implement `PreCmdRun` (validation) and `CmdRun` (logic) functions | ||
| 5. Use `jsontabwriter.GenerateOutput()` for output; define column headers with `tabheaders` | ||
| 6. Register in `commands/root.go` → `addCommands()` function | ||
|
|
||
| ### Flag Naming Convention | ||
|
|
||
| Flag names are constants in `internal/constants/`. Access flag values in `CmdRun` via: | ||
| ```go | ||
| viper.GetString(core.GetFlagName(c.NS, constants.FlagDatacenterId)) | ||
| ``` | ||
|
|
||
| ### Authentication & Configuration | ||
|
|
||
| Credentials are loaded in priority order: | ||
| 1. Environment variables: `IONOS_USERNAME`, `IONOS_PASSWORD`, `IONOS_TOKEN`, `IONOS_API_URL` | ||
| 2. Config file: `~/.config/ionosctl/config.json` (Linux) | ||
|
|
||
| ### Output Formatting | ||
|
|
||
| The `--output` global flag controls format: `text` (default table), `json`, or `api-json` (raw API response). | ||
| The `--cols` flag controls which columns to display. | ||
| The `--filters` flag supports case-insensitive key filtering. | ||
|
|
||
| ### Services (Cloud API v6) | ||
|
|
||
| Services are lazy-loaded in `services/cloudapi-v6/services.go`: | ||
| ```go | ||
| type Services struct { | ||
| Locations func() resources.LocationsService | ||
| DataCenters func() resources.DatacentersService | ||
| // ... | ||
| } | ||
| ``` | ||
|
|
||
| Each resource service implements CRUD operations and is accessible via `c.CloudApiV6Services` in `CmdRun`. | ||
|
|
||
| ### Mocks | ||
|
|
||
| Mocks for service interfaces are generated using `golang/mock`. Regenerate with `make mocks`. Mock files live alongside their interfaces, typically in `services/cloudapi-v6/resources/mocks/`. | ||
|
|
||
| ### Prerequisites | ||
| - Remember to run make gofmt after implementing changes | ||
| - Remember to run make docs after implementing changes | ||
| - ionosctl uses bats for e2e tests, implement e2e tests for new features | ||
| - Remember to run gofmt on the files to correctly import tests | ||
| - Cyclomatic complexity for new functions should not exceed 15; refactor into smaller functions if necessary. Only for new code. | ||
| - Add bats test if adding a new feature |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package bucket | ||
|
|
||
| import ( | ||
| "github.com/spf13/cobra" | ||
|
|
||
| "github.com/ionos-cloud/ionosctl/v6/commands/object-storage/bucket/cors" | ||
| "github.com/ionos-cloud/ionosctl/v6/commands/object-storage/bucket/encryption" | ||
| "github.com/ionos-cloud/ionosctl/v6/commands/object-storage/bucket/lifecycle" | ||
| objectlock "github.com/ionos-cloud/ionosctl/v6/commands/object-storage/bucket/object-lock" | ||
| "github.com/ionos-cloud/ionosctl/v6/commands/object-storage/bucket/policy" | ||
| "github.com/ionos-cloud/ionosctl/v6/commands/object-storage/bucket/publicaccessblock" | ||
| "github.com/ionos-cloud/ionosctl/v6/commands/object-storage/bucket/tagging" | ||
| "github.com/ionos-cloud/ionosctl/v6/commands/object-storage/bucket/versioning" | ||
| "github.com/ionos-cloud/ionosctl/v6/internal/constants" | ||
| "github.com/ionos-cloud/ionosctl/v6/internal/core" | ||
| "github.com/ionos-cloud/ionosctl/v6/internal/printer/table" | ||
| ) | ||
|
|
||
| var allCols = []table.Column{ | ||
| {Name: "Name", JSONPath: "Name", Default: true}, | ||
| {Name: "CreationDate", JSONPath: "CreationDate", Default: true}, | ||
| {Name: "Region", JSONPath: "Region", Default: true}, | ||
|
avirtopeanu-ionos marked this conversation as resolved.
|
||
| } | ||
|
|
||
| func BucketCommand() *core.Command { | ||
| cmd := &core.Command{ | ||
| Command: &cobra.Command{ | ||
| Use: "bucket", | ||
| Aliases: []string{"b"}, | ||
| Short: "Bucket operations for contract-owned object storage", | ||
| TraverseChildren: true, | ||
| }, | ||
| } | ||
|
|
||
| cmd.Command.PersistentFlags().StringSlice(constants.ArgCols, nil, table.ColsMessage(allCols)) | ||
| _ = cmd.Command.RegisterFlagCompletionFunc( | ||
| constants.ArgCols, | ||
| func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||
| return table.AllCols(allCols), cobra.ShellCompDirectiveNoFileComp | ||
| }, | ||
| ) | ||
|
glimberea marked this conversation as resolved.
|
||
|
|
||
| cmd.AddCommand(ListBucketsCmd()) | ||
| cmd.AddCommand(CreateBucketCmd()) | ||
| cmd.AddCommand(GetBucketCmd()) | ||
| cmd.AddCommand(HeadBucketCmd()) | ||
| cmd.AddCommand(DeleteBucketCmd()) | ||
| cmd.AddCommand(versioning.Root()) | ||
| cmd.AddCommand(objectlock.Root()) | ||
| cmd.AddCommand(cors.CorsCmd()) | ||
| cmd.AddCommand(encryption.EncryptionCmd()) | ||
| cmd.AddCommand(tagging.TaggingCmd()) | ||
| cmd.AddCommand(policy.PolicyCmd()) | ||
| cmd.AddCommand(lifecycle.LifecycleCmd()) | ||
| cmd.AddCommand(publicaccessblock.PublicAccessBlockCmd()) | ||
| return cmd | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| package cors | ||
|
|
||
| import ( | ||
| "github.com/spf13/cobra" | ||
|
|
||
| "github.com/ionos-cloud/ionosctl/v6/internal/constants" | ||
| "github.com/ionos-cloud/ionosctl/v6/internal/core" | ||
| "github.com/ionos-cloud/ionosctl/v6/internal/printer/table" | ||
| ) | ||
|
|
||
| var allCols = []table.Column{ | ||
| {Name: "AllowedOrigins", JSONPath: "AllowedOrigins", Default: true}, | ||
| {Name: "AllowedMethods", JSONPath: "AllowedMethods", Default: true}, | ||
| {Name: "AllowedHeaders", JSONPath: "AllowedHeaders", Default: true}, | ||
| {Name: "ExposeHeaders", JSONPath: "ExposeHeaders"}, | ||
| {Name: "MaxAgeSeconds", JSONPath: "MaxAgeSeconds"}, | ||
| {Name: "ID", JSONPath: "ID"}, | ||
| } | ||
|
|
||
| type corsRuleInfo struct { | ||
| AllowedOrigins string `json:"AllowedOrigins"` | ||
| AllowedMethods string `json:"AllowedMethods"` | ||
| AllowedHeaders string `json:"AllowedHeaders"` | ||
| ExposeHeaders string `json:"ExposeHeaders"` | ||
| MaxAgeSeconds string `json:"MaxAgeSeconds"` | ||
| ID string `json:"ID"` | ||
| } | ||
|
|
||
| func CorsCmd() *core.Command { | ||
| cmd := &core.Command{ | ||
| Command: &cobra.Command{ | ||
| Use: "cors", | ||
| Short: "Bucket CORS operations for contract-owned object storage", | ||
| TraverseChildren: true, | ||
| }, | ||
| } | ||
|
|
||
| cmd.Command.PersistentFlags().StringSlice(constants.ArgCols, nil, table.ColsMessage(allCols)) | ||
| _ = cmd.Command.RegisterFlagCompletionFunc( | ||
| constants.ArgCols, | ||
| func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||
| return table.AllCols(allCols), cobra.ShellCompDirectiveNoFileComp | ||
| }, | ||
| ) | ||
|
|
||
| cmd.AddCommand(GetCmd()) | ||
| cmd.AddCommand(PutCmd()) | ||
| cmd.AddCommand(DeleteCmd()) | ||
|
|
||
| return cmd | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| package cors | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
|
|
||
| "github.com/spf13/viper" | ||
|
|
||
| "github.com/ionos-cloud/ionosctl/v6/commands/object-storage/completer" | ||
| "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/pkg/confirm" | ||
| ) | ||
|
|
||
| func DeleteCmd() *core.Command { | ||
| cmd := core.NewCommand(context.Background(), nil, core.CommandBuilder{ | ||
| Namespace: "object-storage", | ||
| Resource: "cors", | ||
| Verb: "delete", | ||
| Aliases: []string{"d"}, | ||
| ShortDesc: "Delete the CORS configuration for a bucket", | ||
| Example: "ionosctl object-storage bucket cors delete --name my-bucket\nionosctl object-storage bucket cors delete --name my-bucket -f", | ||
| PreCmdRun: func(c *core.PreCommandConfig) error { | ||
| return core.CheckRequiredFlags(c.Command, c.NS, constants.FlagName) | ||
| }, | ||
| CmdRun: func(c *core.CommandConfig) error { | ||
| name := viper.GetString(core.GetFlagName(c.NS, constants.FlagName)) | ||
|
|
||
| if !confirm.FAsk(c.Command.Command.InOrStdin(), fmt.Sprintf("delete CORS configuration for bucket %q", name), viper.GetBool(constants.ArgForce)) { | ||
| return fmt.Errorf(confirm.UserDenied) | ||
| } | ||
|
|
||
| _, err := client.MustObjectStorage().ObjectStorageClient.CORSApi.DeleteBucketCors(c.Context, name).Execute() | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| fmt.Fprintf(c.Command.Command.OutOrStdout(), "CORS configuration for %q deleted successfully\n", name) | ||
| return nil | ||
| }, | ||
| InitClient: false, | ||
| }) | ||
|
|
||
| cmd.AddStringFlag(constants.FlagName, constants.FlagNameShort, "", "Name of the bucket", core.RequiredFlagOption(), | ||
| core.WithCompletion(completer.BucketNames, constants.ObjectStorageApiRegionalURL, constants.ObjectStorageLocations)) | ||
|
|
||
| cmd.Command.SilenceUsage = true | ||
| cmd.Command.Flags().SortFlags = false | ||
| return cmd | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.