Skip to content

Instantly share code, notes, and snippets.

@sebmellen
Last active January 3, 2024 20:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sebmellen/ee0396feec7ba73d734f927331cef49d to your computer and use it in GitHub Desktop.
Save sebmellen/ee0396feec7ba73d734f927331cef49d to your computer and use it in GitHub Desktop.
Push SOPS secrets to a multi-environment windmill.dev workspace
name: Push SOPS secrets to Windmill
on:
workflow_dispatch:
jobs:
deploy:
permissions:
id-token: write
contents: write
runs-on: ubuntu-latest
env:
sopsFileName: ${{ (github.ref_name == 'master' && 'development') || github.ref_name }}
steps:
# SETUP
- name: SETUP - Checkout
uses: actions/checkout@v3
- uses: denoland/setup-deno@v1
with:
deno-version: "1.38.5"
- name: SETUP - Tailscale
uses: tailscale/github-action@v1
with:
version: 1.54.1
authkey: ${{ secrets.TAILSCALE_AUTHKEY }}
- name: SETUP - Download and setup SOPS
run: |
curl -LO https://github.com/mozilla/sops/releases/download/v3.8.1/sops_3.8.1_amd64.deb
sudo dpkg -i sops_3.8.1_amd64.deb
- name: SETUP - Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::<IAM_ID_HERE>:role/github-actions
aws-region: us-east-1
role-session-name: windmill
- name: SETUP - Determine correct Windmill token based on environment
id: token
run: |
if [ "${{ github.ref_name }}" = "demo" ]; then
ACTION_ENVIRONMENT="DEMO"
elif [ "${{ github.ref_name }}" = "staging" ]; then
ACTION_ENVIRONMENT="STAGING"
elif [ "${{ github.ref_name }}" = "production" ]; then
ACTION_ENVIRONMENT="PRODUCTION"
fi
echo "ACTION_ENVIRONMENT=${ACTION_ENVIRONMENT}" >> "$GITHUB_OUTPUT"
- name: SETUP - Download Windmill CLI and set it up
run: |
deno install --unstable -A https://deno.land/x/wmill/main.ts
sed -i 's/deno run/deno run --unsafely-ignore-certificate-errors/' ~/.deno/bin/wmill
if [ "${{ github.ref_name }}" = "master" ]; then
wmill workspace add integrations integrations https://windmill.services.cerebrum.internal/ --token ${{ secrets.WINDMILL_DEV_INTEGRATIONS_TOKEN }}
else
wmill workspace add integrations-${{ github.ref_name }} integrations-${{ github.ref_name }} https://windmill.services.cerebrum.internal/ --token ${{ secrets[format('WINDMILL_{0}_INTEGRATIONS_TOKEN', steps.token.outputs.ACTION_ENVIRONMENT)] }}
fi
# ACTION
- name: ACTION - Sync with Windmill
run: |
wmill sync pull --yes --skip-variables --skip-secrets --skip-resources
- name: ACTION - Decrypt SOPS variables into the decrypted folder
run: |
sops -d sops/sops.${{ env.sopsFileName }}.yml > sops/${{ env.sopsFileName }}.yml
cd scripts && npm i && cd ..
# SETUP
- name: SETUP - Set dynamic envName
id: set_env_name
run: echo "envName=$(if [ ${{ github.ref_name }} = 'master' ]; then echo ''; else echo '-${{ github.ref_name }}'; fi)" >> $GITHUB_ENV
# ACTION
- name: ACTION - Push secrets to Windmill and delete the decrypted folder
run: |
node scripts/windmill.js
rm -rf decrypted
env:
WORKSPACE: integrations${{ env.envName }}
INPUT_FILE_PATH: sops/${{ env.sopsFileName }}.yml
WMILL_TOKEN: ${{ (github.ref_name == 'master' && secrets.WINDMILL_DEV_INTEGRATIONS_TOKEN) || secrets[format('WINDMILL_{0}_INTEGRATIONS_TOKEN', steps.token.outputs.ACTION_ENVIRONMENT)] }}
NODE_TLS_REJECT_UNAUTHORIZED: 0
const fs = require("fs").promises;
const yaml = require("js-yaml");
const inputFilePath = process.env.INPUT_FILE_PATH;
const workspace = process.env.WORKSPACE;
const API_URL = `https://windmill.services.cerebrum.internal/api/w/${workspace}`;
const wMilltoken = process.env.WMILL_TOKEN;
console.log(`api url: ${API_URL}`);
async function sopsSecretRemap(inputFilePath) {
const fileContents = await fs.readFile(inputFilePath, "utf8");
const data = yaml.load(fileContents);
const secrets = data.secrets;
return Object.keys(secrets).map((key) => {
return {
name: key,
value: secrets[key],
is_secret: true,
description: "SECRET VALUE IMPORTED FROM SOPS",
path: `f/sops/${key}`,
};
});
}
async function push(secrets) {
const variableList = await getVariableList();
for (const secret of secrets) {
const secretPath = secret.path;
const requestBody = JSON.stringify(secret);
if (variableList.includes(secretPath)) {
await updateSecret(secretPath, requestBody);
} else {
await createSecret(secretPath, requestBody);
}
}
}
async function getVariableList() {
console.log("fetching secret variable list");
const response = await fetch(`${API_URL}/variables/list`, {
headers: {
Authorization: `Bearer ${wMilltoken}`,
"Accept-Encoding": "application/json",
},
});
const variables = await response.json();
return variables.map((v) => v.path);
}
async function updateSecret(secretPath, requestBody) {
await fetch(`${API_URL}/variables/update/${secretPath}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${wMilltoken}`,
},
body: requestBody,
});
console.log(`secret ${secretPath} updated`);
}
async function createSecret(secretPath, requestBody) {
await fetch(`${API_URL}/variables/create`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${wMilltoken}`,
},
body: requestBody,
});
console.log(`secret ${secretPath} created`);
}
(async () => {
const secrets = await sopsSecretRemap(inputFilePath);
console.log("sops converted to JSON");
await push(secrets);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment