|
| 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