268 lines
7.9 KiB
TypeScript
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,
|
|
});
|
|
}
|
|
}
|