Skip to content

Conversation

@gianarb
Copy link

@gianarb gianarb commented Dec 22, 2025

What

This commit replaces the datumctl get command that currently supports
organizations only with the kubectl get command.

DATUMCTL_EXPERIMENTAL=true go run main.go get projects --organization-id personal-org-f9a87347
NAME                   READY   AGE
shippingbytes-jh601e   True    87d
DATUMCTL_EXPERIMENTAL=true go run main.go get domains --project-id shippingbytes-jh601e
NAME       DOMAIN NAME   AGE   VERIFIED
hello-it   hello.it      46h   False
DATUMCTL_EXPERIMENTAL=true go run main.go get organizationmemberships --all-namespaces
NAME                   READY   AGE
shippingbytes-jh601e   True    87d

It also adds support for -v the same used by the kubectl and for two
new commands: api-resources and api-versions

Why

We want to offer a fimilar environment to interact with for users coming from
Kubernetes. No better way to achive that than using the kubectl code itself.

For Reviewer

I decied to open this PR now becuase I am working at it from a couple of days
and the features are there, I want other people to look at it.

The implementation is pretty rusty I am still not sure about how to properly
combine clientcmd.ConfigFlags an the Factory. I wrote Datum implementation to
see if I was able to get somewhere but I feel we can do better. Primarly
because I had to modify the clientcmd to support --all-namespaces and it
sounds pretty wrong!

I am open for suggesitions

@scotwells
Copy link
Contributor

scotwells commented Dec 22, 2025

@gianarb FYI I had to run go mod tidy to get this to build on a fresh checkout.

$ go build -o datumctl .
go: updates to go.mod needed; to update it:
        go mod tidy
$ go mod tidy
$ go build -o datumctl .
$ ./datumctl --help
A CLI for interacting with the Datum platform
...

This commit replaces the datumctl `get` command that currently supports
`organizations` only with the kubectl get command.

```
DATUMCTL_EXPERIMENTAL=true go run main.go get projects --organization-id personal-org-f9a87347
NAME                   READY   AGE
shippingbytes-jh601e   True    87d
```

```
DATUMCTL_EXPERIMENTAL=true go run main.go get domains --project-id shippingbytes-jh601e
NAME       DOMAIN NAME   AGE   VERIFIED
hello-it   hello.it      46h   False
```

```
DATUMCTL_EXPERIMENTAL=true go run main.go get organizationmemberships --all-namespaces
NAME                   READY   AGE
shippingbytes-jh601e   True    87d
```

It also adds support for `-v` the same used by the kubectl and for two
new commands: `api-resources` and `api-versions`

Signed-off-by: Gianluca Arbezzano <ciao@gianarb.it>
@gianarb gianarb force-pushed the feat/experimental-kubectl-get branch from 292e9f3 to 2dfabc0 Compare December 22, 2025 16:00
@scotwells
Copy link
Contributor

Hey @gianarb, I was finally got around to playing around with this locally. Overall it seems to work great!

I looked into the issue you were mentioning on Slack where it would only load resources from the default namespace if the user provided the --all-namespaces option.

I found I was able to remove the custom fork of client-go that resolved that issue if I apply the diff below to datumctl.

diff --git a/internal/client/factory.go b/internal/client/factory.go
index 97e0904..4a56f69 100644
--- a/internal/client/factory.go
+++ b/internal/client/factory.go
@@ -59,7 +59,7 @@ func (c *customConfigFlags) ToRawKubeConfigLoader() clientcmd.ClientConfig {
 			"inmemory": {
 				Cluster:   "inmemory",
 				AuthInfo:  "inmemory",
-				Namespace: *c.Namespace,
+				Namespace: "",
 			},
 		},
 		CurrentContext: "inmemory",
@@ -73,8 +73,8 @@ func (c *customConfigFlags) ToRawKubeConfigLoader() clientcmd.ClientConfig {
 		overrides.CurrentContext = *c.Context
 	}
 
-	if c.Namespace != nil {
-		overrides.Context.Namespace = *c.Namespace
+	if c.ConfigFlags.Namespace != nil && *c.ConfigFlags.Namespace != "" {
+		overrides.Context.Namespace = *c.ConfigFlags.Namespace
 	}
 
 	// Apply cluster overrides if set
diff --git a/internal/cmd/get/v2/get.go b/internal/cmd/get/v2/get.go
index 388d0b6..0a4bf0d 100644
--- a/internal/cmd/get/v2/get.go
+++ b/internal/cmd/get/v2/get.go
@@ -13,15 +13,15 @@ import (
 )
 
 func Command(factory *client.MyFactory, ioStreams genericclioptions.IOStreams, projectID *string, organizationID *string) *cobra.Command {
-	getCmd := get.NewCmdGet("datumctl", factory, ioStreams)
-	getCmd.Run = func(cmd *cobra.Command, args []string) {
+	// Create a PreRun hook to configure the factory before kubectl's command runs
+	preRunFunc := func(cmd *cobra.Command, args []string) {
 		apiHostname, err := authutil.GetAPIHostname()
 		if err != nil {
 			panic(fmt.Errorf("get API hostname: %w", err))
 		}
 		restConfig, err := factory.ToRESTConfig()
 		if err != nil {
-			panic(fmt.Errorf("get API hostname: %w", err))
+			panic(fmt.Errorf("get REST config: %w", err))
 		}
 		tknSrc, err := authutil.GetTokenSource(cmd.Context())
 		if err != nil {
@@ -41,8 +41,9 @@ func Command(factory *client.MyFactory, ioStreams genericclioptions.IOStreams, p
 		default:
 			panic(fmt.Errorf("exactly one of organizationID or projectID must be provided"))
 		}
-		cmdInt := get.NewCmdGet("datumctl", factory, ioStreams)
-		cmdInt.Run(cmd, args)
 	}
+
+	getCmd := get.NewCmdGet("datumctl", factory, ioStreams)
+	getCmd.PreRun = preRunFunc
 	return getCmd
 }

Here's the results of what I get when I try to get namespaced / all-namespace resources from the updated version of datumctl.

$ ./datumctl get domains --project-id=datum-cloud -n default     
NAME                      DOMAIN NAME         AGE   VERIFIED
datum-staging-net-gvn6p   datum-staging.net   48d   True

$ ./datumctl get domains --project-id=datum-cloud -n default --all-namespaces
NAMESPACE   NAME                      DOMAIN NAME         AGE   VERIFIED
default     datum-staging-net-gvn6p   datum-staging.net   48d   True

I'd also like to get your thoughts around some of the CLI flags that are exposed through the standard kubectl get command. I see several flags that are for adjusting the configuration of a kubeconfig file.

      --client-certificate string      Path to a client certificate file for TLS
      --client-key string              Path to a client key file for TLS
      --cluster string                 The name of the kubeconfig cluster to use
      --context string                 The name of the kubeconfig context to use

Datum Cloud users shouldn't have to deal with kubeconfig files since they're integrating with Datum Cloud's APIs and not individual kubernetes clusters. Ideally a user would be able to switch between projects / organizations easily without having to setup new contexts in a config file. Additionally users can't use client certificates to authenticate with Datum Cloud so the option being available could be misleading. Is there a way to hide or remove these flags?

@gianarb
Copy link
Author

gianarb commented Dec 29, 2025

Great! Thanks I was feeling stuck and I am glad you figured out the right direction! Do you want to push such changes to my branch or send me a patch with your contacts that I can apply so it goes to you?

I am disabling the flags you shared since datum won't make use of those. I have two questions my side that are left in order to mark this as done IMHO but feel free to leave your todo list for me if you have any items for this specific PR (I am trying to keep since under control in size)

  1. Do you want to keep the old organization command with the envvar or should we wipe all of that in favor of this get?
  2. What should we do with organization specifically since the current datumctl has a bit of a hack since organization are kind of a map in there? Should I update doc to reflect org membership? I don't know what to do with this because technically internal datum people should be able to get organizations if they want because I suppose they have permissions to do that. I suppose I can do some sort of mapping like this by inspecting the command arg and I can add a specific flag to the get command that can be used to disable the map and actually get organizations so internal datum people can do that, but not sure if you like it

Quote from @scotwells

Datum Cloud users shouldn't have to deal with kubeconfig files since
they're integrating with Datum Cloud's APIs and not individual
kubernetes clusters. Ideally a user would be able to switch between
projects / organizations easily without having to setup new contexts in
a config file. Additionally users can't use client certificates to
authenticate with Datum Cloud so the option being available could be
misleading.
@scotwells
Copy link
Contributor

I don't mind if you push those changes directly. They don't need to be in my name.

  1. Let's make the breaking change and replace the existing command.
  2. I like the idea of being able to map types to more ergonomic types to improve UX. The primary audience for this tool is external consumers so we should prioritize the UX for them. Eventually we'll have an API available that supports listing resources just the resources a user has access to so users can query for organizations and just get a filtered list.

@gianarb
Copy link
Author

gianarb commented Dec 29, 2025 via email

@scotwells
Copy link
Contributor

@gianarb yes, let's have "get organizations" map to "get organizationmemberships"

@gianarb
Copy link
Author

gianarb commented Dec 29, 2025 via email

This commit contains a few things. Scot's suggestion to rollback to
client-go.

It implements a remap for organization to organization membership to
keep compatibility with the current version of the CLI.
Scot suggested a breaking change is a good at this stage. This commit
removes the need for `DATUMCTL_EXPERIMENTAL` and it replace the old
`get` command with the new one
@gianarb
Copy link
Author

gianarb commented Jan 2, 2026

At the end I didn't manage to get this done before new year, but I feel like now it is ready for a review and merge eventually.

My plan next is to the other commands useful for a crud operation like: delete, update, create, apply but I would like to do it in their own PR.

Copy link
Contributor

@scotwells scotwells left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great! Just some minor feedback so far.

Comment on lines +13 to +17
type MyFactory struct {
util.Factory
ConfigFlags *genericclioptions.ConfigFlags
RestConfig *rest.Config
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on naming this something like DatumCloudFactory since it's specific to building configs for interacting with Datum Cloud's APIs?

Are you thinking we'd use this factory to build the necessary configs in other locations we're building a kubeconfig? Example:

// Create Kubernetes client configuration with scheme
config := &rest.Config{
Host: userContextAPI,
WrapTransport: func(rt http.RoundTripper) http.RoundTripper {
return &oauth2.Transport{
Source: tknSrc,
Base: rt,
}
},
}

Comment on lines +29 to +31
if c.BearerToken != nil && *c.BearerToken != "" {
config.BearerToken = *c.BearerToken
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to worry about passing this through since we will always need to override the bearer token with the access token stored in the user's keychain?

Comment on lines +25 to +42
tknSrc, err := authutil.GetTokenSource(cmd.Context())
if err != nil {
return err
}
restConfig.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
return &oauth2.Transport{Source: tknSrc, Base: rt}
}
switch {
case (projectID == nil || *projectID == "") && (organizationID == nil || *organizationID == ""):
case (projectID == nil || *projectID == "") && (organizationID != nil || *organizationID != ""):
factory.RestConfig.Host = fmt.Sprintf("https://%s/apis/resourcemanager.miloapis.com/v1alpha1/organizations/%s/control-plane",
apiHostname, *organizationID)
case (projectID != nil || *projectID != "") && (organizationID == nil || *organizationID == ""):
factory.RestConfig.Host = fmt.Sprintf("https://%s/apis/resourcemanager.miloapis.com/v1alpha1/projects/%s/control-plane",
apiHostname, *projectID)
default:
return fmt.Errorf("exactly one of organizationID or projectID must be provided")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be happening within the client configuration factory? This code would be used across any command that interacts with the API to make sure the correct control plane is used.

Comment on lines -65 to -66
// Add the verbose flag
LoginCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Print the full ID token claims after successful login")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this CLI flag removed because we support the generic -v short-hand used by kubectl? If so, I think we need to update the login flow to support using -v because now this change disables verbose output entirely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants