Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ packages:
- "with-web-terminal"
- "with-opencode"
- "with-sandbox-agent"
- "with-k3s"
- "types/*"
160 changes: 160 additions & 0 deletions with-k3s/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# @freestyle-sh/with-k3s

A Freestyle VM extension that adds [k3s](https://k3s.io/) (lightweight Kubernetes) support to your VMs.

## Installation

```bash
npm install @freestyle-sh/with-k3s freestyle-sandboxes
# or
pnpm add @freestyle-sh/with-k3s freestyle-sandboxes
```

## Usage

### Basic Example

```typescript
import { freestyle, VmSpec } from "freestyle-sandboxes";
import { VmK3s } from "@freestyle-sh/with-k3s";

const spec = new VmSpec({
with: {
k3s: new VmK3s(),
},
});

const { vm, vmId } = await freestyle.vms.create({ spec });

// Get cluster info
const clusterInfo = await vm.k3s.getClusterInfo();
console.log(clusterInfo.stdout);

// Apply a manifest
const manifest = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
`;

await vm.k3s.applyManifest(manifest);

// Get resources
const pods = await vm.k3s.getResources("pods", "default");
console.log(pods.data);

await freestyle.vms.delete({ vmId });
```

### Configuration Options

```typescript
new VmK3s({
version: "v1.28.5+k3s1", // Optional: specific k3s version
serverArgs: ["--disable=traefik"], // Optional: additional k3s server arguments
});
```

## API

### `VmK3sInstance`

The instance provides methods for interacting with the k3s cluster:

#### `kubectl(args: string[]): Promise<KubectlResult>`

Execute a kubectl command.

```typescript
const result = await vm.k3s.kubectl(["get", "pods", "-A"]);
console.log(result.stdout);
```

#### `applyManifest(manifest: string): Promise<ApplyManifestResult>`

Apply a Kubernetes manifest.

```typescript
const result = await vm.k3s.applyManifest(yamlManifest);
```

#### `deleteResource(resourceType: string, name: string, namespace?: string): Promise<KubectlResult>`

Delete a Kubernetes resource.

```typescript
await vm.k3s.deleteResource("deployment", "nginx", "default");
```

#### `getResources<T>(resourceType: string, namespace?: string): Promise<{success: boolean; data?: T; error?: string}>`

Get resources as JSON.

```typescript
const deployments = await vm.k3s.getResources("deployments", "default");
if (deployments.success) {
console.log(deployments.data);
}
```

#### `getKubeconfig(): Promise<{success: boolean; content?: string; error?: string}>`

Get the kubeconfig file content.

```typescript
const config = await vm.k3s.getKubeconfig();
if (config.success) {
console.log(config.content);
}
```

#### `getClusterInfo(): Promise<KubectlResult>`

Get cluster information.

```typescript
const info = await vm.k3s.getClusterInfo();
```

#### `getNodes(): Promise<KubectlResult>`

Get all nodes in the cluster.

```typescript
const nodes = await vm.k3s.getNodes();
```

## Examples

Check out the [examples](./examples) directory for more detailed usage:

- [basic.ts](./examples/basic.ts) - Basic usage with nginx deployment
- [advanced.ts](./examples/advanced.ts) - Advanced usage with namespace, service, and deployment

## Features

- ✅ Automatic k3s installation via systemd service
- ✅ Support for specific k3s versions
- ✅ Full kubectl command execution
- ✅ Manifest application and deletion
- ✅ JSON resource queries
- ✅ Kubeconfig access
- ✅ Cluster info and node inspection

## License

MIT
106 changes: 106 additions & 0 deletions with-k3s/examples/advanced.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import "dotenv/config";
import { freestyle, VmSpec } from "freestyle-sandboxes";
import { VmK3s } from "../src/index.js";

// Create a VM with k3s installed
const spec = new VmSpec({
with: {
k3s: new VmK3s(),
},
});

const { vm, vmId } = await freestyle.vms.create({ spec });

console.log("K3s VM created successfully!");

// Create a complete application with deployment and service
const manifest = `
apiVersion: v1
kind: Namespace
metadata:
name: demo-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world
namespace: demo-app
spec:
replicas: 2
selector:
matchLabels:
app: hello-world
template:
metadata:
labels:
app: hello-world
spec:
containers:
- name: hello-world
image: gcr.io/google-samples/hello-app:1.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: hello-world-service
namespace: demo-app
spec:
selector:
app: hello-world
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
`;

console.log("\nApplying complete application manifest...");
const result = await vm.k3s.applyManifest(manifest);
console.log("Apply result:", result.success ? "Success" : "Failed");

// Wait for resources to be created
await new Promise((resolve) => setTimeout(resolve, 3000));

// Check namespace
console.log("\nNamespaces:");
const nsResult = await vm.k3s.kubectl(["get", "namespaces"]);
console.log(nsResult.stdout);

// Check deployments in demo-app namespace
console.log("\nDeployments in demo-app namespace:");
const deployments = await vm.k3s.getResources("deployments", "demo-app");
if (deployments.success && deployments.data) {
const items = (deployments.data as any).items || [];
items.forEach((item: any) => {
console.log(`- ${item.metadata.name}: ${item.status.replicas || 0} replicas`);
});
}

// Check services
console.log("\nServices in demo-app namespace:");
const services = await vm.k3s.getResources("services", "demo-app");
if (services.success && services.data) {
const items = (services.data as any).items || [];
items.forEach((item: any) => {
console.log(`- ${item.metadata.name}: ${item.spec.clusterIP}`);
});
}

// Execute a custom kubectl command
console.log("\nGetting all resources:");
const allResources = await vm.k3s.kubectl([
"get",
"all",
"-n",
"demo-app",
]);
console.log(allResources.stdout);

// Clean up
console.log("\nCleaning up...");
await vm.k3s.kubectl(["delete", "namespace", "demo-app"]);

await freestyle.vms.delete({ vmId });
console.log("VM deleted successfully!");
86 changes: 86 additions & 0 deletions with-k3s/examples/basic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import "dotenv/config";
import { freestyle, VmSpec } from "freestyle-sandboxes";
import { VmK3s } from "../src/index.js";

const spec = new VmSpec({
with: {
k3s: new VmK3s(),
},
});

const { vm, vmId } = await freestyle.vms.create({ spec });

console.log("K3s VM created successfully!");
console.log("VM ID:", vmId);

// Get cluster info
const clusterInfo = await vm.k3s.getClusterInfo();
console.log("\nCluster Info:");
console.log(clusterInfo.stdout);

// Get nodes
const nodes = await vm.k3s.getNodes();
console.log("\nNodes:");
console.log(nodes.stdout);

// Apply a simple nginx deployment
const nginxManifest = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
`;

console.log("\nApplying nginx deployment...");
const applyResult = await vm.k3s.applyManifest(nginxManifest);
console.log("Apply result:", applyResult.success ? "Success" : "Failed");
if (applyResult.stdout) {
console.log(applyResult.stdout);
}
if (applyResult.stderr) {
console.error(applyResult.stderr);
}

// Wait a bit for the deployment to be processed
await new Promise((resolve) => setTimeout(resolve, 2000));

// Get deployments
const deployments = await vm.k3s.getResources("deployments", "default");
console.log("\nDeployments:");
if (deployments.success && deployments.data) {
console.log(JSON.stringify(deployments.data, null, 2));
}

// Get pods
const pods = await vm.k3s.kubectl(["get", "pods", "-o", "wide"]);
console.log("\nPods:");
console.log(pods.stdout);

// Get kubeconfig
const kubeconfigResult = await vm.k3s.getKubeconfig();
console.log("\nKubeconfig available:", kubeconfigResult.success);

// Cleanup
console.log("\nCleaning up...");
const deleteResult = await vm.k3s.deleteResource("deployment", "nginx-deployment", "default");
console.log("Delete result:", deleteResult.success ? "Success" : "Failed");

await freestyle.vms.delete({ vmId });
console.log("VM deleted successfully!");
Loading