feat: pulumi + pipeline
This commit is contained in:
parent
2df5f3d7f9
commit
ac9b6a25bf
11 changed files with 4795 additions and 0 deletions
2
infrastructure/.gitignore
vendored
Normal file
2
infrastructure/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/bin/
|
||||||
|
/node_modules/
|
||||||
3
infrastructure/Pulumi.dev.yaml
Normal file
3
infrastructure/Pulumi.dev.yaml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
encryptionsalt: v1:Jf+BVg+eXws=:v1:MScHsvDc/TE20MSb:p+6j/0hGULp3a4VXaAzcd+vti9Mn3g==
|
||||||
|
config:
|
||||||
|
gcp:project: digital-equator-311106
|
||||||
10
infrastructure/Pulumi.yaml
Normal file
10
infrastructure/Pulumi.yaml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
name: picoclaw-cloud-run
|
||||||
|
description: A minimal Google Cloud TypeScript Pulumi program
|
||||||
|
runtime:
|
||||||
|
name: nodejs
|
||||||
|
options:
|
||||||
|
packagemanager: npm
|
||||||
|
config:
|
||||||
|
pulumi:tags:
|
||||||
|
value:
|
||||||
|
pulumi:template: gcp-typescript
|
||||||
81
infrastructure/README.md
Normal file
81
infrastructure/README.md
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
# Pulumi GCP TypeScript Template
|
||||||
|
|
||||||
|
A minimal Google Cloud Storage bucket example using Pulumi and TypeScript. This template helps you get started quickly with a basic Pulumi program on GCP.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This template provisions a Google Cloud Storage bucket in the `US` region and exports its URL. It demonstrates how to use the Pulumi GCP provider with TypeScript.
|
||||||
|
|
||||||
|
## Providers
|
||||||
|
|
||||||
|
- `@pulumi/pulumi`
|
||||||
|
- `@pulumi/gcp`
|
||||||
|
|
||||||
|
## Resources Created
|
||||||
|
|
||||||
|
- **Storage Bucket** (`gcp.storage.Bucket`)
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
- `bucketName` – The URL of the created Storage Bucket.
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
Use this template when you:
|
||||||
|
- Want a quick, minimal example of provisioning GCP resources with Pulumi.
|
||||||
|
- Are exploring Pulumi and TypeScript on Google Cloud.
|
||||||
|
- Need a starting point for building more complex GCP infrastructure in TypeScript.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Node.js installed on your machine.
|
||||||
|
- Pulumi CLI installed.
|
||||||
|
- A Google Cloud project.
|
||||||
|
- GCP credentials configured (for example, via `gcloud auth login` or the `GOOGLE_APPLICATION_CREDENTIALS` environment variable).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Create a new Pulumi project from this template:
|
||||||
|
```bash
|
||||||
|
pulumi new gcp-typescript
|
||||||
|
```
|
||||||
|
Follow the interactive prompts to set:
|
||||||
|
- Project name and description.
|
||||||
|
- `gcp:project` (the target Google Cloud project ID).
|
||||||
|
|
||||||
|
## Project Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── Pulumi.yaml # Pulumi project definition and template metadata
|
||||||
|
├── index.ts # Entry point for the Pulumi program
|
||||||
|
├── package.json # Node.js dependencies and metadata
|
||||||
|
└── tsconfig.json # TypeScript compiler configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
This template recognizes the following configuration values:
|
||||||
|
|
||||||
|
- `gcp:project` – The Google Cloud project where resources will be deployed.
|
||||||
|
|
||||||
|
Set this value in your stack with:
|
||||||
|
```bash
|
||||||
|
pulumi config set gcp:project YOUR_PROJECT_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- Customize the storage bucket (e.g., change location, storage class, access policies).
|
||||||
|
- Add more GCP resources such as Compute Engine instances, Pub/Sub topics, or Firestore databases.
|
||||||
|
- Explore the full Pulumi GCP provider documentation:
|
||||||
|
https://www.pulumi.com/docs/reference/pkg/gcp/
|
||||||
|
- Learn more about Pulumi with TypeScript:
|
||||||
|
https://www.pulumi.com/docs/get-started/typescript/
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
If you run into issues or have questions, check out:
|
||||||
|
- Pulumi Documentation: https://www.pulumi.com/docs/
|
||||||
|
- Community Slack: https://slack.pulumi.com/
|
||||||
|
- GitHub Issues: https://github.com/pulumi/pulumi/issues
|
||||||
168
infrastructure/index.ts
Normal file
168
infrastructure/index.ts
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
import * as pulumi from "@pulumi/pulumi";
|
||||||
|
import * as gcp from "@pulumi/gcp";
|
||||||
|
|
||||||
|
const config = new pulumi.Config();
|
||||||
|
const gcpConfig = new pulumi.Config("gcp");
|
||||||
|
|
||||||
|
const project = gcpConfig.require("project");
|
||||||
|
const region = config.get("region") ?? "asia-southeast1";
|
||||||
|
const imageTag = config.get("imageTag") ?? "latest";
|
||||||
|
const imageName = config.get("imageName") ?? "picoclaw";
|
||||||
|
|
||||||
|
const PICOCLAW_IMAGE = pulumi.interpolate`${region}-docker.pkg.dev/enterprise-automation-352103/container-repo/${imageName}:${imageTag}`;
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// Look up pre-existing secrets in Secret Manager
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
const awsAccessKeySecret = gcp.secretmanager.Secret.get(
|
||||||
|
"picoclaw-aws-access-key-id",
|
||||||
|
`projects/${project}/secrets/PICOCLAW_AWS_ACCESS_KEY_ID`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const awsSecretKeySecret = gcp.secretmanager.Secret.get(
|
||||||
|
"picoclaw-aws-secret-access-key",
|
||||||
|
`projects/${project}/secrets/PICOCLAW_AWS_SECRET_ACCESS_KEY`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const awsRegionNameSecret = gcp.secretmanager.Secret.get(
|
||||||
|
"picoclaw-aws-region-name",
|
||||||
|
`projects/${project}/secrets/PICOCLAW_AWS_REGION_NAME`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const launcherTokenSecret = gcp.secretmanager.Secret.get(
|
||||||
|
"picoclaw-launcher-token",
|
||||||
|
`projects/${project}/secrets/PICOCLAW_LAUNCHER_TOKEN`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// Dedicated service account for the Cloud Run service
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
const gatewayServiceAccount = new gcp.serviceaccount.Account("picoclaw-gateway-sa", {
|
||||||
|
project,
|
||||||
|
accountId: "picoclaw-gateway",
|
||||||
|
displayName: "PicoClaw Gateway Service Account",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Grant the service account secretAccessor at the project level so it can
|
||||||
|
// read all pre-existing secrets without needing setIamPolicy on each one.
|
||||||
|
const iamSecretAccessor = new gcp.projects.IAMMember("picoclaw-sa-secret-accessor", {
|
||||||
|
project,
|
||||||
|
role: "roles/secretmanager.secretAccessor",
|
||||||
|
member: pulumi.interpolate`serviceAccount:${gatewayServiceAccount.email}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
// Cloud Run v2 service — picoclaw gateway
|
||||||
|
// ─────────────────────────────────────────────
|
||||||
|
const gatewayService = new gcp.cloudrunv2.Service("picoclaw-gateway", {
|
||||||
|
name: "picoclaw-gateway",
|
||||||
|
location: region,
|
||||||
|
project,
|
||||||
|
ingress: "INGRESS_TRAFFIC_ALL",
|
||||||
|
template: {
|
||||||
|
serviceAccount: gatewayServiceAccount.email,
|
||||||
|
scaling: {
|
||||||
|
minInstanceCount: 1,
|
||||||
|
maxInstanceCount: 3,
|
||||||
|
},
|
||||||
|
containers: [
|
||||||
|
{
|
||||||
|
image: PICOCLAW_IMAGE,
|
||||||
|
ports: {
|
||||||
|
containerPort: 18790,
|
||||||
|
},
|
||||||
|
envs: [
|
||||||
|
{ name: "PICOCLAW_GATEWAY_HOST", value: "0.0.0.0" },
|
||||||
|
{
|
||||||
|
name: "AWS_ACCESS_KEY_ID",
|
||||||
|
valueSource: {
|
||||||
|
secretKeyRef: {
|
||||||
|
secret: awsAccessKeySecret.secretId,
|
||||||
|
version: "latest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AWS_SECRET_ACCESS_KEY",
|
||||||
|
valueSource: {
|
||||||
|
secretKeyRef: {
|
||||||
|
secret: awsSecretKeySecret.secretId,
|
||||||
|
version: "latest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AWS_REGION",
|
||||||
|
valueSource: {
|
||||||
|
secretKeyRef: {
|
||||||
|
secret: awsRegionNameSecret.secretId,
|
||||||
|
version: "latest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AWS_DEFAULT_REGION",
|
||||||
|
valueSource: {
|
||||||
|
secretKeyRef: {
|
||||||
|
secret: awsRegionNameSecret.secretId,
|
||||||
|
version: "latest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "PICOCLAW_LAUNCHER_TOKEN",
|
||||||
|
valueSource: {
|
||||||
|
secretKeyRef: {
|
||||||
|
secret: launcherTokenSecret.secretId,
|
||||||
|
version: "latest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
resources: {
|
||||||
|
limits: {
|
||||||
|
cpu: "1",
|
||||||
|
memory: "512Mi",
|
||||||
|
},
|
||||||
|
cpuIdle: false,
|
||||||
|
},
|
||||||
|
startupProbe: {
|
||||||
|
httpGet: {
|
||||||
|
path: "/health",
|
||||||
|
port: 18790,
|
||||||
|
},
|
||||||
|
initialDelaySeconds: 5,
|
||||||
|
periodSeconds: 10,
|
||||||
|
failureThreshold: 6,
|
||||||
|
},
|
||||||
|
livenessProbe: {
|
||||||
|
httpGet: {
|
||||||
|
path: "/health",
|
||||||
|
port: 18790,
|
||||||
|
},
|
||||||
|
periodSeconds: 30,
|
||||||
|
failureThreshold: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
dependsOn: [iamSecretAccessor],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Grant invoker access only to authenticated members of the current project
|
||||||
|
new gcp.cloudrunv2.ServiceIamBinding("picoclaw-gateway-invoker", {
|
||||||
|
project,
|
||||||
|
location: region,
|
||||||
|
name: gatewayService.name,
|
||||||
|
role: "roles/run.invoker",
|
||||||
|
members: [
|
||||||
|
`projectOwner:${project}`,
|
||||||
|
`projectEditor:${project}`,
|
||||||
|
`projectViewer:${project}`,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const serviceUrl = gatewayService.uri;
|
||||||
|
export const serviceName = gatewayService.name;
|
||||||
|
export const serviceAccountEmail = gatewayServiceAccount.email;
|
||||||
4415
infrastructure/package-lock.json
generated
Normal file
4415
infrastructure/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
12
infrastructure/package.json
Normal file
12
infrastructure/package.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"name": "picoclaw-cloud-run",
|
||||||
|
"main": "index.ts",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^18",
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@pulumi/gcp": "^9.0.0",
|
||||||
|
"@pulumi/pulumi": "^3.113.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
infrastructure/tsconfig.json
Normal file
18
infrastructure/tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"outDir": "bin",
|
||||||
|
"target": "es2020",
|
||||||
|
"module": "nodenext",
|
||||||
|
"moduleResolution": "nodenext",
|
||||||
|
"sourceMap": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"pretty": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"index.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
36
pipeline/pipeline.yaml
Normal file
36
pipeline/pipeline.yaml
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
pr:
|
||||||
|
branches:
|
||||||
|
include:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
branches:
|
||||||
|
include:
|
||||||
|
- main
|
||||||
|
|
||||||
|
variables:
|
||||||
|
- name: DockerImageName
|
||||||
|
value: 'enterprise-automation-352103/container-repo/picoclaw'
|
||||||
|
- name: Registry
|
||||||
|
value: 'europe-west4-docker.pkg.dev'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- stage: build
|
||||||
|
jobs:
|
||||||
|
- job: buildAndPush
|
||||||
|
steps:
|
||||||
|
- template: pipeline_templates/gcp_auth.yaml
|
||||||
|
parameters:
|
||||||
|
environment: 'automation'
|
||||||
|
- script: |
|
||||||
|
gcloud auth configure-docker $(Registry) --quiet
|
||||||
|
displayName: Configure Docker for GCR
|
||||||
|
- script: |
|
||||||
|
docker build -t $(Registry)/$(DockerImageName):latest -f docker/Dockerfile.launcher --build-arg GO_BUILD_TAGS=goolm,stdjson,bedrock .
|
||||||
|
displayName: Build docker image
|
||||||
|
- script: |
|
||||||
|
docker push $(Registry)/$(DockerImageName):latest
|
||||||
|
displayName: Push docker image
|
||||||
18
pipeline/pipeline_templates/account_mappings.json
Normal file
18
pipeline/pipeline_templates/account_mappings.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"dev": {
|
||||||
|
"project": "digital-equator-311106",
|
||||||
|
"account": "digital-equator-311106@digital-equator-311106.iam.gserviceaccount.com"
|
||||||
|
},
|
||||||
|
"uat": {
|
||||||
|
"project": "intnt-in-house-uat",
|
||||||
|
"account": "intnt-in-house-uat@intnt-in-house-uat.iam.gserviceaccount.com"
|
||||||
|
},
|
||||||
|
"prod": {
|
||||||
|
"project": "intnt-in-house-prod",
|
||||||
|
"account": "intnt-in-house-prod@intnt-in-house-prod.iam.gserviceaccount.com"
|
||||||
|
},
|
||||||
|
"automation": {
|
||||||
|
"project": "enterprise-automation-352103",
|
||||||
|
"account": "application-automation-account@enterprise-automation-352103.iam.gserviceaccount.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
32
pipeline/pipeline_templates/gcp_auth.yaml
Normal file
32
pipeline/pipeline_templates/gcp_auth.yaml
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
parameters:
|
||||||
|
- name: 'environment' # defaults for any parameters that aren't specified
|
||||||
|
type: string
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- script: |
|
||||||
|
ls
|
||||||
|
pwd
|
||||||
|
PROJECT=$(jq -r '.${{parameters.environment}}.project' account_mappings.json)
|
||||||
|
ACCOUNT=$(jq -r '.${{parameters.environment}}.account' account_mappings.json)
|
||||||
|
echo "##vso[task.setvariable variable=gcpProject]$PROJECT"
|
||||||
|
echo "##vso[task.setvariable variable=gcpAccount]$ACCOUNT"
|
||||||
|
displayName: 'Set Environment Variables from JSON'
|
||||||
|
workingDirectory: scripts/pipeline_templates
|
||||||
|
env:
|
||||||
|
ENVIRONMENT: ${{ parameters.environment }}
|
||||||
|
- task: DownloadSecureFile@1
|
||||||
|
name: gcpServiceAccountKey_${{ parameters.environment }}
|
||||||
|
inputs:
|
||||||
|
secureFile: 'gcp-services-account-key-${{parameters.environment}}.json'
|
||||||
|
- script: |
|
||||||
|
echo "Authenticating with Google Cloud..."
|
||||||
|
echo $(gcpServiceAccountKey_${{ parameters.environment }}.secureFilePath)
|
||||||
|
echo $(gcpProject)
|
||||||
|
echo $(gcpAccount)
|
||||||
|
gcloud auth login --cred-file=$(gcpServiceAccountKey_${{ parameters.environment }}.secureFilePath)
|
||||||
|
gcloud config set project $(gcpProject)
|
||||||
|
gcloud config set account $(gcpAccount)
|
||||||
|
echo "##vso[task.setvariable variable=GOOGLE_APPLICATION_CREDENTIALS]$(gcpServiceAccountKey_${{ parameters.environment }}.secureFilePath)"
|
||||||
|
displayName: 'authenticate gcp'
|
||||||
|
env:
|
||||||
|
GOOGLE_APPLICATION_CREDENTIALS: $(gcpServiceAccountKey_${{ parameters.environment }}.secureFilePath)
|
||||||
Loading…
Add table
Reference in a new issue