Initial Pulumi Typescript implementation
Some checks failed
Pulumi / pulumi (push) Failing after 1m17s
Some checks failed
Pulumi / pulumi (push) Failing after 1m17s
This commit is contained in:
59
.gitea/workflows/pulumi.yml
Normal file
59
.gitea/workflows/pulumi.yml
Normal 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
12
.gitignore
vendored
Normal 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
3
Pulumi.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
name: netbird-gitops-poc-ts
|
||||||
|
runtime: nodejs
|
||||||
|
description: NetBird GitOps PoC - TypeScript implementation
|
||||||
164
index.ts
Normal file
164
index.ts
Normal 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
244
netbird.ts
Normal 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
2970
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
package.json
Normal file
20
package.json
Normal 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
13
tsconfig.json
Normal 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"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user