Initial Pulumi Typescript implementation
Some checks failed
Pulumi / pulumi (push) Failing after 1m17s

This commit is contained in:
Prox
2026-02-15 17:16:47 +02:00
commit feb959703f
8 changed files with 3485 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
name: Pulumi
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
AWS_ACCESS_KEY_ID: ${{ secrets.MINIO_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.MINIO_SECRET_KEY }}
AWS_REGION: us-east-1
MINIO_ENDPOINT: "127.0.0.1:9000"
jobs:
pulumi:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Pulumi CLI
run: |
curl -fsSL https://get.pulumi.com | sh
echo "$HOME/.pulumi/bin" >> $GITHUB_PATH
- name: Install dependencies
run: npm ci
- name: Install Pulumi plugins
run: |
pulumi plugin install resource command
- name: Login to MinIO backend
run: |
pulumi login "s3://pulumi-state?endpoint=$MINIO_ENDPOINT&disableSSL=true&s3ForcePathStyle=true"
- name: Select stack
run: |
pulumi stack select poc --create
- name: Set Pulumi config
run: |
pulumi config set netbird:url https://netbird-poc.networkmonitor.cc
pulumi config set --secret netbird:token "${{ secrets.NETBIRD_TOKEN }}"
- name: Pulumi Preview
if: github.event_name == 'pull_request'
run: pulumi preview
- name: Pulumi Up
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: pulumi up --yes

12
.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
# Pulumi
Pulumi.*.yaml
!Pulumi.yaml
# Node
node_modules/
bin/
# Build
*.js
*.js.map
*.d.ts

3
Pulumi.yaml Normal file
View File

@@ -0,0 +1,3 @@
name: netbird-gitops-poc-ts
runtime: nodejs
description: NetBird GitOps PoC - TypeScript implementation

164
index.ts Normal file
View File

@@ -0,0 +1,164 @@
import * as pulumi from "@pulumi/pulumi";
import { Group, Policy, SetupKey, NetBirdConfig } from "./netbird";
// =============================================================================
// Configuration
// =============================================================================
const config = new pulumi.Config("netbird");
const netbirdConfig: NetBirdConfig = {
url: config.require("url"),
token: config.requireSecret("token"),
};
// =============================================================================
// Groups - Achilles Network Structure
// =============================================================================
const groups = {
groundStations: new Group(
"ground-stations",
{ name: "ground-stations", peers: [] },
netbirdConfig
),
pilots: new Group("pilots", { name: "pilots", peers: [] }, netbirdConfig),
operators: new Group(
"operators",
{ name: "operators", peers: [] },
netbirdConfig
),
fusionServers: new Group(
"fusion-servers",
{ name: "fusion-servers", peers: [] },
netbirdConfig
),
};
// =============================================================================
// Policies - Access Control
// =============================================================================
const policies = {
pilotToGs: new Policy(
"pilot-to-ground-station",
{
name: "pilot-to-ground-station",
description: "Allow pilots to connect to ground stations",
enabled: true,
rules: [
{
name: "pilot-gs-access",
description: "Pilots can access ground stations",
enabled: true,
sources: [groups.pilots.id],
destinations: [groups.groundStations.id],
bidirectional: true,
protocol: "all",
action: "accept",
},
],
},
netbirdConfig,
{ dependsOn: [groups.pilots, groups.groundStations] }
),
operatorFullAccess: new Policy(
"operator-full-access",
{
name: "operator-full-access",
description: "Operators can access all network resources",
enabled: true,
rules: [
{
name: "operator-all",
description: "Full operator access",
enabled: true,
sources: [groups.operators.id],
destinations: [
groups.groundStations.id,
groups.pilots.id,
groups.fusionServers.id,
],
bidirectional: true,
protocol: "all",
action: "accept",
},
],
},
netbirdConfig,
{
dependsOn: [
groups.operators,
groups.groundStations,
groups.pilots,
groups.fusionServers,
],
}
),
fusionToGs: new Policy(
"fusion-to-ground-station",
{
name: "fusion-to-ground-station",
description: "Fusion servers coordinate with ground stations",
enabled: true,
rules: [
{
name: "fusion-gs",
description: "Fusion to GS access",
enabled: true,
sources: [groups.fusionServers.id],
destinations: [groups.groundStations.id],
bidirectional: true,
protocol: "all",
action: "accept",
},
],
},
netbirdConfig,
{ dependsOn: [groups.fusionServers, groups.groundStations] }
),
};
// =============================================================================
// Setup Keys - Peer Onboarding
// =============================================================================
const setupKeys = {
gsOnboarding: new SetupKey(
"ground-station-onboarding",
{
name: "ground-station-onboarding",
type: "reusable",
autoGroups: [groups.groundStations.id],
usageLimit: 0,
expiresIn: 0,
ephemeral: false,
},
netbirdConfig,
{ dependsOn: [groups.groundStations] }
),
pilotOnboarding: new SetupKey(
"pilot-onboarding",
{
name: "pilot-onboarding",
type: "reusable",
autoGroups: [groups.pilots.id],
usageLimit: 0,
expiresIn: 2592000, // 30 days
ephemeral: false,
},
netbirdConfig,
{ dependsOn: [groups.pilots] }
),
};
// =============================================================================
// Outputs
// =============================================================================
export const groupIds = {
groundStations: groups.groundStations.id,
pilots: groups.pilots.id,
operators: groups.operators.id,
fusionServers: groups.fusionServers.id,
};
export const gsSetupKey = pulumi.secret(setupKeys.gsOnboarding.key);
export const pilotSetupKey = pulumi.secret(setupKeys.pilotOnboarding.key);

244
netbird.ts Normal file
View File

@@ -0,0 +1,244 @@
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.
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;
}
// =============================================================================
// 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: pulumi.interpolate`GROUP_ID=$(curl -s -H "Authorization: Token ${config.token}" \
${config.url}/api/groups | jq -r '.[] | select(.name=="${args.name}") | .id') && \
curl -s -X DELETE -H "Authorization: Token ${config.token}" \
${config.url}/api/groups/$GROUP_ID`,
},
{ 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: pulumi.interpolate`POLICY_ID=$(curl -s -H "Authorization: Token ${config.token}" \
${config.url}/api/policies | jq -r '.[] | select(.name=="${args.name}") | .id') && \
curl -s -X DELETE -H "Authorization: Token ${config.token}" \
${config.url}/api/policies/$POLICY_ID`,
},
{ 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: pulumi.interpolate`KEY_ID=$(curl -s -H "Authorization: Token ${config.token}" \
${config.url}/api/setup-keys | jq -r '.[] | select(.name=="${args.name}") | .id') && \
curl -s -X DELETE -H "Authorization: Token ${config.token}" \
${config.url}/api/setup-keys/$KEY_ID`,
},
{ 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,
});
}
}

2970
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "netbird-gitops-poc-ts",
"version": "1.0.0",
"description": "NetBird GitOps PoC - TypeScript implementation",
"main": "index.ts",
"scripts": {
"build": "tsc",
"preview": "pulumi preview",
"up": "pulumi up",
"destroy": "pulumi destroy"
},
"dependencies": {
"@pulumi/pulumi": "^3.0.0",
"@pulumi/command": "^1.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
}
}

13
tsconfig.json Normal file
View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./bin"
},
"include": ["*.ts"],
"exclude": ["node_modules"]
}