Skip to content

Commit e4e1f47

Browse files
committed
feat: demonstrate how to integrate with milo
1 parent 46d404b commit e4e1f47

File tree

9 files changed

+1905
-15
lines changed

9 files changed

+1905
-15
lines changed

datumcfg

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
apiVersion: v1
2+
clusters:
3+
- cluster:
4+
server: https://api.staging.env.datum.net
5+
name: datum-cloud
6+
contexts:
7+
- context:
8+
cluster: datum-cloud
9+
user: datum-user
10+
name: datum-cloud
11+
current-context: datum-cloud
12+
kind: Config
13+
preferences: {}
14+
users:
15+
- name: datum-user
16+
user:
17+
exec:
18+
apiVersion: client.authentication.k8s.io/v1
19+
args:
20+
- auth
21+
- get-token
22+
- --output=client.authentication.k8s.io/v1
23+
command: /Users/scotwells/go/bin/datumctl
24+
env: null
25+
installHint: |2
26+
27+
The datumctl command is required to authenticate to the current cluster. It can
28+
be installed by running the following command.
29+
30+
go install go.datum.net/datumctl@latest
31+
interactiveMode: IfAvailable
32+
provideClusterInfo: false

docs/integration-with-milo.md

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
# Integrate with Milo API for Local Development
2+
3+
This guide shows you how to connect to the Milo API server from your local development environment. You'll authenticate using `datumctl` with your Datum Cloud staff account.
4+
5+
## Prerequisites
6+
7+
Before you begin, verify that you have:
8+
9+
- Node.js 18.20.8 or later installed
10+
- Go installed (to install `datumctl`)
11+
- A Datum Cloud staff account
12+
- Access to the Milo API server endpoint (`https://api.staging.env.datum.net`)
13+
14+
## Install datumctl
15+
16+
Install the `datumctl` command-line tool:
17+
18+
```bash
19+
go install go.datum.net/datumctl@latest
20+
```
21+
22+
Verify the installation:
23+
24+
```bash
25+
datumctl version
26+
```
27+
28+
## Authenticate with Datum Cloud
29+
30+
Log in to your Datum Cloud staff account:
31+
32+
```bash
33+
datumctl auth login --hostname auth.staging.env.datum.net
34+
```
35+
36+
This command opens your browser and prompts you to authenticate. After successful authentication, `datumctl` stores your credentials locally.
37+
38+
## Set up your project
39+
40+
This project includes a Kubernetes client wrapper that handles authentication with the Milo API server.
41+
42+
### Install dependencies
43+
44+
If you haven't already, install the project dependencies:
45+
46+
```bash
47+
npm install
48+
```
49+
50+
The `@kubernetes/client-node` package is already included in the dependencies.
51+
52+
## Configure authentication
53+
54+
The Kubernetes client uses a kubeconfig file for authentication. The kubeconfig uses an **exec plugin** that calls `datumctl` to retrieve authentication tokens dynamically.
55+
56+
### Create your kubeconfig file
57+
58+
Create a kubeconfig file with the following structure:
59+
60+
```yaml
61+
apiVersion: v1
62+
clusters:
63+
- cluster:
64+
server: https://api.staging.env.datum.net
65+
name: datum-organization-scots-real-org
66+
contexts:
67+
- context:
68+
cluster: datum-organization-scots-real-org
69+
user: datum-user
70+
name: datum-organization-scots-real-org
71+
current-context: datum-organization-scots-real-org
72+
kind: Config
73+
preferences: {}
74+
users:
75+
- name: datum-user
76+
user:
77+
exec:
78+
apiVersion: client.authentication.k8s.io/v1
79+
args:
80+
- auth
81+
- get-token
82+
- --output=client.authentication.k8s.io/v1
83+
command: datumctl
84+
env: null
85+
interactiveMode: IfAvailable
86+
provideClusterInfo: false
87+
```
88+
89+
### Store your kubeconfig file
90+
91+
Save your kubeconfig file to one of these locations:
92+
93+
1. **Project directory**: `./datumcfg` (recommended for local development)
94+
3. **Custom location**: Any path you specify
95+
96+
### How authentication works
97+
98+
When you use the Kubernetes client:
99+
100+
1. The client reads your kubeconfig file
101+
2. The kubeconfig specifies an **exec plugin** that runs `datumctl auth get-token`
102+
3. `datumctl` retrieves a valid authentication token from your logged-in session
103+
4. The token is used to authenticate API requests to the Milo API server
104+
105+
This approach provides:
106+
- **Dynamic tokens**: Tokens are generated on-demand and automatically refreshed
107+
- **No stored credentials**: No client certificates or keys to manage
108+
- **SSO integration**: Uses your Datum Cloud staff account
109+
110+
## Use the Kubernetes client
111+
112+
The project includes a pre-built client wrapper at `src/libs/k8s-client.ts`. This section shows you how to use it.
113+
114+
### Create a client instance
115+
116+
Import and initialize the client:
117+
118+
```typescript
119+
import { K8sClient } from '@libs/k8s-client';
120+
121+
// Use the datumcfg file from your project directory
122+
const client = new K8sClient({
123+
kubeconfigPath: process.env.KUBECONFIG || './datumcfg',
124+
namespace: 'milo-system'
125+
});
126+
```
127+
128+
The client automatically uses the exec plugin to call `datumctl` for authentication.
129+
130+
### List resources
131+
132+
List all Contact resources in the `milo-system` namespace:
133+
134+
```typescript
135+
import { K8sClient } from '@libs/k8s-client';
136+
import type { ContactSpec } from '@/src/types/k8s-resources';
137+
138+
const client = new K8sClient({
139+
kubeconfigPath: './config/kubeconfig.yaml',
140+
});
141+
142+
async function listContacts() {
143+
try {
144+
const contacts = await client.listCustomResources<ContactSpec>(
145+
'notification.miloapis.com',
146+
'v1alpha1',
147+
'contacts',
148+
'milo-system'
149+
);
150+
151+
console.log(`Found ${contacts.items.length} contacts`);
152+
contacts.items.forEach((contact) => {
153+
console.log(`- ${contact.metadata.name}: ${contact.spec.email}`);
154+
});
155+
} catch (error) {
156+
console.error('Failed to list contacts:', error);
157+
}
158+
}
159+
```
160+
161+
### Create a resource
162+
163+
Create a new Contact resource:
164+
165+
```typescript
166+
import { K8sClient } from '@libs/k8s-client';
167+
import { createContact, type ContactSpec } from '@/src/types/k8s-resources';
168+
169+
const client = new K8sClient({
170+
kubeconfigPath: './config/kubeconfig.yaml',
171+
namespace: 'milo-system',
172+
});
173+
174+
async function createNewContact() {
175+
const contact = createContact('john-doe', {
176+
email: 'john.doe@example.com',
177+
givenName: 'John',
178+
familyName: 'Doe',
179+
company: 'Example Corp',
180+
});
181+
182+
try {
183+
const created = await client.createCustomResource<ContactSpec>(
184+
'notification.miloapis.com',
185+
'v1alpha1',
186+
'contacts',
187+
contact
188+
);
189+
190+
console.log('Contact created:', created.metadata.name);
191+
} catch (error) {
192+
console.error('Failed to create contact:', error);
193+
}
194+
}
195+
```
196+
197+
### Get a specific resource
198+
199+
Retrieve a single Contact by name:
200+
201+
```typescript
202+
async function getContact(name: string) {
203+
try {
204+
const contact = await client.getCustomResource<ContactSpec>(
205+
'notification.miloapis.com',
206+
'v1alpha1',
207+
'contacts',
208+
name,
209+
'milo-system'
210+
);
211+
212+
console.log('Contact details:');
213+
console.log('Email:', contact.spec.email);
214+
console.log('Name:', `${contact.spec.givenName} ${contact.spec.familyName}`);
215+
} catch (error) {
216+
console.error('Failed to get contact:', error);
217+
}
218+
}
219+
```
220+
221+
### Update a resource
222+
223+
Modify an existing Contact resource:
224+
225+
```typescript
226+
async function updateContact(name: string) {
227+
try {
228+
// Get the current resource
229+
const contact = await client.getCustomResource<ContactSpec>(
230+
'notification.miloapis.com',
231+
'v1alpha1',
232+
'contacts',
233+
name,
234+
'milo-system'
235+
);
236+
237+
// Modify the spec
238+
contact.spec.company = 'Updated Company Name';
239+
240+
// Save the changes
241+
const updated = await client.updateCustomResource<ContactSpec>(
242+
'notification.miloapis.com',
243+
'v1alpha1',
244+
'contacts',
245+
name,
246+
contact,
247+
'milo-system'
248+
);
249+
250+
console.log('Contact updated:', updated.metadata.name);
251+
} catch (error) {
252+
console.error('Failed to update contact:', error);
253+
}
254+
}
255+
```
256+
257+
### Delete a resource
258+
259+
Remove a Contact resource:
260+
261+
```typescript
262+
async function deleteContact(name: string) {
263+
try {
264+
await client.deleteCustomResource(
265+
'notification.miloapis.com',
266+
'v1alpha1',
267+
'contacts',
268+
name,
269+
'milo-system'
270+
);
271+
272+
console.log('Contact deleted:', name);
273+
} catch (error) {
274+
console.error('Failed to delete contact:', error);
275+
}
276+
}
277+
```
278+
279+
## Use environment variables
280+
281+
Configure the client using environment variables for flexibility across environments:
282+
283+
```typescript
284+
const client = new K8sClient({
285+
kubeconfigPath: process.env.KUBECONFIG,
286+
context: process.env.K8S_CONTEXT,
287+
namespace: process.env.K8S_NAMESPACE || 'milo-system',
288+
});
289+
```
290+
291+
Create a `.env` file in your project root:
292+
293+
```bash
294+
KUBECONFIG=./config/kubeconfig.yaml
295+
K8S_CONTEXT=milo-context
296+
K8S_NAMESPACE=milo-system
297+
```
298+
299+
## Troubleshoot common issues
300+
301+
### Authentication fails
302+
303+
If you see authentication errors:
304+
305+
1. Verify that your kubeconfig file contains valid certificates
306+
2. Check that the certificates haven't expired
307+
3. Confirm that your client certificate is trusted by the API server's CA
308+
309+
To check certificate expiration:
310+
311+
```bash
312+
# Extract and check client certificate
313+
echo "<base64-cert-data>" | base64 -d | openssl x509 -noout -dates
314+
```
315+
316+
### Connection refused
317+
318+
If the client cannot connect to the API server:
319+
320+
1. Verify the server URL in your kubeconfig matches the API server endpoint
321+
2. Check that your network can reach the API server
322+
3. Confirm that the API server is running
323+
324+
Test connectivity:
325+
326+
```bash
327+
curl -k https://api.staging.env.datum.net/healthz
328+
```
329+
330+
### Resource not found
331+
332+
If you receive "resource not found" errors:
333+
334+
1. Verify the resource exists in the specified namespace
335+
2. Check that you're using the correct API group, version, and plural name
336+
3. Confirm that your user has permission to access the resource
337+
338+
## Additional resources
339+
340+
For more examples, see:
341+
342+
- [src/examples/k8s-client-example.ts](../../examples/k8s-client-example.ts) - Comprehensive usage examples
343+
- [src/libs/k8s-client.ts](../../libs/k8s-client.ts) - Full API documentation
344+
- [src/types/k8s-resources.ts](../../types/k8s-resources.ts) - Type definitions
345+
346+
## Next steps
347+
348+
Now that you can authenticate with the Milo API server:
349+
350+
1. Explore the available custom resource types in your cluster
351+
2. Create integration tests for your application
352+
3. Build features that interact with Milo resources

0 commit comments

Comments
 (0)