Files
netbird-iac/netbird.ts
Prox 4245adc670
Some checks failed
Pulumi / pulumi (push) Failing after 22s
Test deleting group
2026-02-15 18:18:36 +02:00

268 lines
7.9 KiB
TypeScript

import * as pulumi from "@pulumi/pulumi";
import * as command from "@pulumi/command";
// =============================================================================
// NetBird API Client using Pulumi Command Provider
// =============================================================================
// Since there's no TypeScript SDK for NetBird, we use the command provider
// to make API calls. This demonstrates the pattern while being practical.
//
// Note: Delete commands use grep/sed instead of jq for CI compatibility.
// The pattern extracts the ID by finding the object with matching name.
export interface NetBirdConfig {
url: string;
token: pulumi.Output<string>;
}
export interface GroupArgs {
name: string;
peers?: string[];
}
export interface PolicyRuleArgs {
name: string;
description?: string;
enabled: boolean;
sources: pulumi.Input<string>[];
destinations: pulumi.Input<string>[];
bidirectional: boolean;
protocol: string;
action: "accept" | "drop";
}
export interface PolicyArgs {
name: string;
description?: string;
enabled: boolean;
rules: PolicyRuleArgs[];
}
export interface SetupKeyArgs {
name: string;
type: "one-off" | "reusable";
autoGroups: pulumi.Input<string>[];
usageLimit: number;
expiresIn: number;
ephemeral: boolean;
}
// =============================================================================
// Helper: Extract ID from JSON array by name (no jq dependency)
// =============================================================================
// Uses grep/sed to find an object by name and extract its ID.
// This is fragile but works for simple JSON structures.
// Pattern: finds "name":"<value>" then extracts preceding "id":"<value>"
function makeDeleteScript(
endpoint: string,
resourceName: string,
token: pulumi.Output<string>,
url: string
): pulumi.Output<string> {
// Use Python for reliable JSON parsing (available in most CI environments)
return pulumi.interpolate`python3 -c "
import json, urllib.request, sys
req = urllib.request.Request('${url}/api/${endpoint}', headers={'Authorization': 'Token ${token}'})
data = json.loads(urllib.request.urlopen(req).read())
matches = [x['id'] for x in data if x.get('name') == '${resourceName}']
if matches:
print(matches[0])
else:
sys.exit(0) # Not found, nothing to delete
" | while read ID; do
if [ -n "$ID" ]; then
curl -s -X DELETE -H "Authorization: Token ${token}" "${url}/api/${endpoint}/$ID"
fi
done`;
}
// =============================================================================
// NetBird Group Resource
// =============================================================================
export class Group extends pulumi.ComponentResource {
public readonly id: pulumi.Output<string>;
public readonly name: string;
constructor(
name: string,
args: GroupArgs,
config: NetBirdConfig,
opts?: pulumi.ComponentResourceOptions
) {
super("netbird:custom:Group", name, {}, opts);
const createCmd = new command.local.Command(
`${name}-create`,
{
create: pulumi.interpolate`curl -s -X POST \
-H "Authorization: Token ${config.token}" \
-H "Content-Type: application/json" \
-d '{"name": "${args.name}", "peers": ${JSON.stringify(args.peers || [])}}' \
${config.url}/api/groups`,
delete: makeDeleteScript("groups", args.name, config.token, config.url),
},
{ parent: this }
);
this.id = createCmd.stdout.apply((stdout): string => {
try {
const result = JSON.parse(stdout);
return result.id || "";
} catch {
return "";
}
});
this.name = args.name;
this.registerOutputs({
id: this.id,
name: this.name,
});
}
}
// =============================================================================
// NetBird Policy Resource
// =============================================================================
export class Policy extends pulumi.ComponentResource {
public readonly id: pulumi.Output<string>;
public readonly name: string;
constructor(
name: string,
args: PolicyArgs,
config: NetBirdConfig,
opts?: pulumi.ComponentResourceOptions
) {
super("netbird:custom:Policy", name, {}, opts);
// Build the policy payload
const rules = args.rules.map((rule) => ({
name: rule.name,
description: rule.description || "",
enabled: rule.enabled,
sources: rule.sources,
destinations: rule.destinations,
bidirectional: rule.bidirectional,
protocol: rule.protocol,
action: rule.action,
}));
const createCmd = new command.local.Command(
`${name}-create`,
{
create: pulumi.all([config.token, ...args.rules.flatMap(r => [...r.sources, ...r.destinations])]).apply(
([token, ...groupIds]) => {
const payload = {
name: args.name,
description: args.description || "",
enabled: args.enabled,
rules: args.rules.map((rule, i) => ({
name: rule.name,
description: rule.description || "",
enabled: rule.enabled,
sources: rule.sources,
destinations: rule.destinations,
bidirectional: rule.bidirectional,
protocol: rule.protocol,
action: rule.action,
})),
};
return `curl -s -X POST \
-H "Authorization: Token ${token}" \
-H "Content-Type: application/json" \
-d '${JSON.stringify(payload)}' \
${config.url}/api/policies`;
}
),
delete: makeDeleteScript("policies", args.name, config.token, config.url),
},
{ parent: this }
);
this.id = createCmd.stdout.apply((stdout): string => {
try {
const result = JSON.parse(stdout);
return result.id || "";
} catch {
return "";
}
});
this.name = args.name;
this.registerOutputs({
id: this.id,
name: this.name,
});
}
}
// =============================================================================
// NetBird Setup Key Resource
// =============================================================================
export class SetupKey extends pulumi.ComponentResource {
public readonly id: pulumi.Output<string>;
public readonly key: pulumi.Output<string>;
public readonly name: string;
constructor(
name: string,
args: SetupKeyArgs,
config: NetBirdConfig,
opts?: pulumi.ComponentResourceOptions
) {
super("netbird:custom:SetupKey", name, {}, opts);
const createCmd = new command.local.Command(
`${name}-create`,
{
create: pulumi.all([config.token, ...args.autoGroups]).apply(
([token, ...groupIds]) => {
const payload = {
name: args.name,
type: args.type,
auto_groups: groupIds,
usage_limit: args.usageLimit,
expires_in: args.expiresIn,
ephemeral: args.ephemeral,
};
return `curl -s -X POST \
-H "Authorization: Token ${token}" \
-H "Content-Type: application/json" \
-d '${JSON.stringify(payload)}' \
${config.url}/api/setup-keys`;
}
),
delete: makeDeleteScript("setup-keys", args.name, config.token, config.url),
},
{ parent: this }
);
this.id = createCmd.stdout.apply((stdout): string => {
try {
const result = JSON.parse(stdout);
return result.id || "";
} catch {
return "";
}
});
this.key = createCmd.stdout.apply((stdout): string => {
try {
const result = JSON.parse(stdout);
return result.key || "";
} catch {
return "";
}
});
this.name = args.name;
this.registerOutputs({
id: this.id,
key: this.key,
name: this.name,
});
}
}