Skip to content

Commit b7a94b1

Browse files
committed
feat: add datumctl config, clusters and context
This commit contains the foundation of being able to connect to multiple Datum clusters and to switch in between projects and orgs via contexts.
1 parent 48bc99f commit b7a94b1

29 files changed

+1425
-356
lines changed

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,26 @@ See the [Installation Guide](https://www.datum.net/docs/quickstart/datumctl/) fo
4646
```
4747
Now you can use `kubectl` to interact with your Datum Cloud control plane.
4848

49+
4. **Manage contexts and clusters (optional):**
50+
```bash
51+
# Define a cluster (base API server URL)
52+
datumctl config set-cluster prod --server https://api.datum.net
53+
54+
# Create a context for a project and namespace
55+
datumctl config set-context prod-project \
56+
--cluster prod \
57+
--project <project-id> \
58+
--namespace default
59+
60+
# Switch the current context
61+
datumctl config use-context prod-project
62+
63+
# View or list contexts
64+
datumctl config view
65+
datumctl config get-contexts
66+
```
67+
Contexts are stored in `~/.datumctl/config`. Credentials remain in the system keychain.
68+
4969
### MCP Setup
5070

5171
MCP can target either an **organization** or **project** control plane. For maximum flexibility, we recommend starting with an organization context.
@@ -86,7 +106,7 @@ echo "Project ready: $PRJ_ID"
86106

87107
Start the Model Context Protocol (MCP) server targeting a specific Datum Cloud context:
88108
```bash
89-
# Exactly one of --organization or --project is required.
109+
# Exactly one of --organization or --project is required (unless a current context provides one).
90110
datumctl mcp --organization <org-id> --namespace <ns> [--port 8080]
91111
# or
92112
datumctl mcp --project <project-id> --namespace <ns> [--port 8080]

docs/authentication.md

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@ API keys directly.
1111
Authentication involves the following commands:
1212

1313
* `datumctl auth login`
14-
* `datumctl auth list`
1514
* `datumctl auth logout`
1615
* `datumctl auth get-token`
1716
* `datumctl auth update-kubeconfig`
18-
* `datumctl auth switch`
1917

2018
Credentials and tokens are stored securely in your operating system's default
2119
keyring.
@@ -47,6 +45,14 @@ Running this command will:
4745
Your credentials (including refresh tokens) are stored securely in the system
4846
keyring, associated with your user identifier (typically your email address).
4947

48+
On every successful login, `datumctl` also ensures a matching cluster/context
49+
entry exists in `~/.datumctl/config` for the API host you authenticated against.
50+
If a current context already exists, it remains unchanged.
51+
52+
`datumctl` stores a list of users in `~/.datumctl/config` and links each context
53+
to a user key (in the `subject@auth-hostname` format). The actual tokens are
54+
stored in your OS keyring under the `datumctl-auth` service.
55+
5056
## Updating kubeconfig
5157

5258
Once logged in, you typically need to configure `kubectl` to authenticate to
@@ -73,38 +79,8 @@ This command adds or updates the necessary cluster, user, and context entries
7379
in your kubeconfig file. The user entry will be configured to use
7480
`datumctl auth get-token --output=client.authentication.k8s.io/v1` as an `exec`
7581
credential plugin. This means `kubectl` commands targeting this cluster will
76-
automatically use your active `datumctl` login session for authentication.
77-
78-
## Listing logged-in users
79-
80-
To see which users you have authenticated locally, use the `list` command:
81-
82-
```
83-
datumctl auth list
84-
# Alias: datumctl auth ls
85-
```
86-
87-
This will output a table showing the Name, Email, and Status (Active or blank)
88-
for each set of stored credentials. The user marked `Active` is the one whose
89-
credentials will be used by default for other `datumctl` commands and
90-
`kubectl` (if configured via `update-kubeconfig`).
91-
92-
## Switching active user
93-
94-
If you have logged in with multiple user accounts (visible via
95-
`datumctl auth list`), you can switch which account is active using the
96-
`switch` command:
97-
98-
```
99-
datumctl auth switch <user-email>
100-
```
101-
102-
Replace `<user-email>` with the email address of the user you want to make
103-
active. This user must already be logged in.
104-
105-
After switching, subsequent commands that require authentication (like
106-
`datumctl organizations list` or `kubectl` operations configured via
107-
`update-kubeconfig`) will use the credentials of the newly activated user.
82+
automatically use the credentials associated with your current `datumctl`
83+
context for authentication.
10884

10985
## Logging out
11086

@@ -113,11 +89,11 @@ To remove stored credentials, use the `logout` command.
11389
**Log out a specific user:**
11490

11591
```
116-
datumctl auth logout <user-email>
92+
datumctl auth logout <user-key>
11793
```
11894

119-
Replace `<user-email>` with the email address shown in the
120-
`datumctl auth list` command.
95+
Replace `<user-key>` with the key shown in the `users` list in
96+
`~/.datumctl/config`. Use `--all` to remove all credentials.
12197

12298
**Log out all users:**
12399

@@ -129,12 +105,12 @@ This removes all Datum Cloud credentials stored by `datumctl` in your keyring.
129105

130106
## Getting tokens (advanced)
131107

132-
The `get-token` command retrieves the current access token for the *active*
133-
authenticated user. This is primarily used internally by other tools (like
108+
The `get-token` command retrieves the current access token for the credentials
109+
associated with the current context. This is primarily used internally by other tools (like
134110
`kubectl`) but can be used directly if needed.
135111

136112
```
137-
datumctl auth get-token [-o <format>]
113+
datumctl auth get-token [-o <format>] [--cluster <datumctl-cluster>]
138114
```
139115

140116
* `-o, --output <format>`: (Optional) Specify the output format. Defaults to
@@ -143,6 +119,8 @@ datumctl auth get-token [-o <format>]
143119
* `client.authentication.k8s.io/v1`: Prints a Kubernetes `ExecCredential`
144120
JSON object containing the ID token, suitable for `kubectl`
145121
authentication.
122+
* `--cluster <datumctl-cluster>`: (Optional) Use credentials bound to the
123+
specified datumctl cluster instead of the current context.
146124

147125
If the stored access token is expired, `get-token` will attempt to use the
148126
refresh token to obtain a new one automatically.

docs/developer/authentication_flow.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ sequenceDiagram
3636
Datumctl->>DatumAuth: Verify ID Token signature & claims
3737
Datumctl->>Datumctl: Extract user info (email, name) from ID Token
3838
Datumctl->>OSKeyring: Store Credentials (Hostname, Tokens, User Info) keyed by user email
39-
Datumctl->>OSKeyring: Update Active User pointer
4039
Datumctl->>OSKeyring: Update Known Users list
40+
Datumctl->>OSKeyring: Bind credentials to active_user.cluster.<cluster>
41+
Datumctl->>Datumctl: Upsert cluster/context/user in ~/.datumctl/config (current-context unchanged if set)
4142
Datumctl-->>-User: Login Successful
4243
```
4344

@@ -81,10 +82,13 @@ sequenceDiagram
8182
endpoints, scopes, and user info, is marshalled to JSON and stored
8283
securely in the OS keyring. The key for this entry is the user's email
8384
address.
84-
11. **Active User:** A pointer (`active_user`) is also stored in the keyring,
85-
indicating which user's credentials should be used by default.
86-
12. **Known Users:** The user's key (email) is added to a list (`known_users`)
87-
in the keyring to facilitate listing and multi-user management.
85+
11. **Cluster Mapping:** A cluster-specific pointer
86+
(`active_user.cluster.<cluster>`) is stored in the keyring, indicating which
87+
user's credentials should be used for that cluster.
88+
12. **Config Users:** The config file stores a `users` list keyed by
89+
`subject@hostname` and each context references a user.
90+
13. **Known Users:** The user's key (subject@hostname) is added to a list (`known_users`)
91+
in the keyring for compatibility and multi-user management.
8892

8993
## Token refresh & usage
9094

@@ -112,7 +116,7 @@ When commands like `datumctl organizations list` or
112116
* The `internal/keyring` package provides a wrapper around
113117
`github.com/zalando/go-keyring`.
114118
* The `internal/authutil` package defines constants for the service name
115-
(`datumctl-auth`) and keys (`active_user`, `known_users`, `<user-email>`)
119+
(`datumctl-auth`) and keys (`active_user.cluster.<cluster>`, `known_users`, `<subject@hostname>`)
116120
used within the keyring.
117121
* `authutil.StoredCredentials` is the structure marshalled into JSON for
118122
storage.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package authutil
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"go.datum.net/datumctl/internal/keyring"
8+
)
9+
10+
const clusterActiveUserKeyPrefix = "active_user.cluster."
11+
12+
func clusterActiveUserKey(clusterName string) string {
13+
return clusterActiveUserKeyPrefix + clusterName
14+
}
15+
16+
// GetActiveUserKeyForCluster returns the user key mapped to the cluster.
17+
func GetActiveUserKeyForCluster(clusterName string) (string, error) {
18+
if clusterName == "" {
19+
return "", ErrNoCurrentContext
20+
}
21+
22+
userKey, err := keyring.Get(ServiceName, clusterActiveUserKey(clusterName))
23+
if err == nil && userKey != "" {
24+
return userKey, nil
25+
}
26+
if err != nil && !errors.Is(err, keyring.ErrNotFound) {
27+
return "", fmt.Errorf("failed to get active user for cluster %q: %w", clusterName, err)
28+
}
29+
30+
return "", ErrNoActiveUserForCluster
31+
}
32+
33+
// SetActiveUserKeyForCluster stores a cluster-specific mapping to a user key.
34+
func SetActiveUserKeyForCluster(clusterName, userKey string) error {
35+
if clusterName == "" || userKey == "" {
36+
return fmt.Errorf("cluster name and user key are required")
37+
}
38+
return keyring.Set(ServiceName, clusterActiveUserKey(clusterName), userKey)
39+
}

0 commit comments

Comments
 (0)