Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pulumi #101

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
08a07a6
chore: Moved from ansible to pulumi
Fgruntjes Mar 16, 2023
f1d4105
chore: Removed ansible
Fgruntjes Mar 16, 2023
497d72c
chore: Fixed flipping backend settings provider name for mongodb
Fgruntjes Mar 16, 2023
90ecf67
chore: Removed ansible code quality test
Fgruntjes Mar 17, 2023
4f68e88
chore: Dotnet format
Fgruntjes Mar 17, 2023
6add631
chore: Made Security:ProtectionCertificate configuration optional
Fgruntjes Mar 17, 2023
4f2c6d6
chore: Fixed deploy and rm github actions calls
Fgruntjes Mar 17, 2023
22ae358
chore: removed old deploy folder
Fgruntjes Mar 17, 2023
533c504
chore: Merged frontend url config
Fgruntjes Mar 22, 2023
6828f5f
chore: Added gcloud `roles/storage.objectCreator` role
Fgruntjes Mar 22, 2023
f353d9a
chore: Added pulumi install in deploy
Fgruntjes Mar 22, 2023
0194713
chore: Added pulumi setup to ci
Fgruntjes Mar 22, 2023
038d588
chore: Canceled old pulumi deploys. Concurrency for deployment is han…
Fgruntjes Mar 23, 2023
a800753
chore: Dotnet format
Fgruntjes Mar 23, 2023
201ff3e
chore: Fixed test_quality_success check
Fgruntjes Mar 23, 2023
9f83335
chore: Fixed google artifect role
Fgruntjes Mar 23, 2023
177218f
chore: Added test for local config file or file not exists
Fgruntjes Mar 23, 2023
70cdd7f
chore: Fixed ci warnings
Fgruntjes Mar 23, 2023
1baa66f
chore: Updated pulumi version
Fgruntjes Mar 23, 2023
91f57f0
chore: Fixed secret volume mount
Fgruntjes Mar 23, 2023
f5c514a
chore: Updated certificate path
Fgruntjes Mar 23, 2023
1149e9e
chore: Added secrets permissions for gcloud
Fgruntjes Mar 23, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/actions/config_cli_tools/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,11 @@ runs:
smoke_test: "${binary} --version"
tarball_binary_path: "${binary}"
download_url: 'https://github.com/auth0/auth0-cli/releases/download/v${version}/auth0-cli_${version}_Linux_x86_64.tar.gz'

- uses: giantswarm/install-binary-action@v1
with:
binary: 'pulumi'
version: '3.59.0'
smoke_test: "${binary} version"
tarball_binary_path: "${binary}"
download_url: 'https://get.pulumi.com/releases/sdk/pulumi-v${version}-linux-x64.tar.gz'
3 changes: 2 additions & 1 deletion .github/workflows/delete_resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ jobs:
with:
google_workload_identity_provider: ${{ secrets.GOOGLE_WORKLOAD_IDENTITY_PROVIDER }}
google_service_account: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_EMAIL }}
- run: ./deploy/run-ansible.sh src/delete.yml
- run: ./App.Deploy/deploy.sh rm
env:
APP_ENVIRONMENT: ${{ inputs.environment }}
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
GOOGLE_REGION: ${{ secrets.GOOGLE_REGION }}
GOOGLE_PROJECT_ID: ${{ secrets.GOOGLE_PROJECT_ID }}
MONGODB_ATLAS_PUBLIC_KEY: ${{ secrets.MONGODB_ATLAS_PUBLIC_KEY }}
Expand Down
11 changes: 2 additions & 9 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,6 @@ jobs:
deployments: write
steps:
- uses: actions/checkout@v3
- uses: haya14busa/action-cond@v1
id: environment_url
with:
cond: ${{ inputs.environment == 'main' }}
# When urls change also change App.Lib.Configuration.DevEnvConfigurationProvider.GetFrontendUrl
if_true: "https://${{ secrets.GOOGLE_PROJECT_ID }}.pages.dev"
if_false: "https://${{ inputs.environment }}.${{ secrets.GOOGLE_PROJECT_ID }}.pages.dev"
- uses: bobheadxi/deployments@v1
if: ${{ inputs.register_deployment }}
id: deployment
Expand All @@ -72,11 +65,11 @@ jobs:
with:
google_workload_identity_provider: ${{ secrets.GOOGLE_WORKLOAD_IDENTITY_PROVIDER }}
google_service_account: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_EMAIL }}
- run: ./deploy/run-ansible.sh src/deploy.yml
- run: ./App.Deploy/deploy.sh up
env:
APP_TAG: ${{ inputs.tag }}
APP_ENVIRONMENT: ${{ inputs.environment }}
APP_FRONTEND: ${{ steps.environment_url.outputs.value }}
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
GOOGLE_REGION: ${{ secrets.GOOGLE_REGION }}
GOOGLE_PROJECT_ID: ${{ secrets.GOOGLE_PROJECT_ID }}
Expand Down
9 changes: 1 addition & 8 deletions .github/workflows/test_quality.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@ jobs:
dotnet-version: 7.0
- run: dotnet format --verify-no-changes

test_quality_ansible:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ansible/ansible-lint-action@v6

test_quality_frontend_web_typescript:
runs-on: ubuntu-latest
steps:
Expand All @@ -37,8 +31,7 @@ jobs:
runs-on: ubuntu-latest
needs:
- test_quality_dotnet
- test_quality_ansible
- test_quality_frontend_web_typescript
if: ${{ always() }}
steps:
- run: "[[ '${{ needs.test_quality_dotnet.result }}' == 'success' && '${{ needs.test_quality_ansible.result }}' == 'success' && '${{ needs.test_quality_frontend_web_typescript.result }}' == 'success' ]]"
- run: "[[ '${{ needs.test_quality_dotnet.result }}' == 'success' && '${{ needs.test_quality_frontend_web_typescript.result }}' == 'success' ]]"
File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions App.Deploy/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/node_modules
Pulumi.*.yaml
24 changes: 24 additions & 0 deletions App.Deploy/App.Deploy.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Pulumi" Version="3.*" />
<PackageReference Include="Pulumi.Auth0" Version="2.17.1" />
<PackageReference Include="Pulumi.Cloudflare" Version="4.16.0" />
<PackageReference Include="Pulumi.Command" Version="0.5.2" />
<PackageReference Include="Pulumi.Gcp" Version="6.*" />
<PackageReference Include="Pulumi.Mongodbatlas" Version="3.7.1" />
<PackageReference Include="Pulumi.Random" Version="4.12.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\App.Lib\App.Lib.csproj" />
</ItemGroup>

</Project>
2 changes: 2 additions & 0 deletions App.Deploy/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name: App.Deploy
runtime: dotnet
78 changes: 78 additions & 0 deletions App.Deploy/deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env bash

set -e

cd "$(dirname "$(realpath "$0")")";

set -a
test -f .env.deploy.local && source .env.deploy.local
test -f .env.local && source .env.local
set +a

# Login to cloud
pulumi login "gs://${GOOGLE_PROJECT_ID}-pulumi"

# Set configuration only if modified
if { [ ".env.deploy.local" -nt "Pulumi.${APP_ENVIRONMENT}.yaml" ] || [ ".env.local" -nt "Pulumi.${APP_ENVIRONMENT}.yaml" ]; } || [ ! -f "Pulumi.${APP_ENVIRONMENT}.yaml" ]; then
# So old configs are cleared
rm -f "Pulumi.${APP_ENVIRONMENT}.yaml"

pulumi stack select \
--non-interactive \
--create "${APP_ENVIRONMENT}"

# Set configurations
function pulumi_config_set {
echo "Setting config ${1}"
pulumi config set --non-interactive "${@}"
}

pulumi_config_set gcp:project "${GOOGLE_PROJECT_ID}"
pulumi_config_set gcp:region "${GOOGLE_REGION}"

pulumi_config_set app:tag "${APP_TAG}"
pulumi_config_set app:environment "${APP_ENVIRONMENT}"
pulumi_config_set app:projectDir "$(dirname $(pwd))"
pulumi_config_set app:dataProtectionCert "${DATA_PROTECTION_CERTIFICATE}"

pulumi_config_set sentry:dsn "${SENTRY_DSN}" --secret

pulumi_config_set mongodbatlas:publicKey "${MONGODB_ATLAS_PUBLIC_KEY}" --secret
pulumi_config_set mongodbatlas:privateKey "${MONGODB_ATLAS_PRIVATE_KEY}" --secret
pulumi_config_set mongodbatlasCustom:projectId "${MONGODB_ATLAS_PROJECT_ID}"

pulumi_config_set cloudflare:apiToken "${CLOUDFLARE_API_TOKEN}" --secret
pulumi_config_set cloudflareCustom:accountId "${CLOUDFLARE_ACCOUNT_ID}"

pulumi_config_set auth0:domain "${AUTH0_DOMAIN}"
pulumi_config_set auth0:client_id "${AUTH0_CLIENT_ID}" --secret
pulumi_config_set auth0:client_secret "${AUTH0_CLIENT_SECRET}" --secret

pulumi_config_set ynab:clientId "${YNAB_CLIENT_ID}"
pulumi_config_set ynab:clientSecret "${YNAB_CLIENT_SECRET}" --secret
else
pulumi stack select \
--non-interactive \
--create "${APP_ENVIRONMENT}"
fi

# Concurrency is handled by github actions
pulumi cancel --yes

if [ "$1" == "up" ]; then
pulumi up \
--non-interactive \
--yes \
--show-full-output \
--show-config \
--diff \
"${@:2}"
elif [ "$1" == "rm" ]; then
pulumi destroy --yes
pulumi stack rm --yes
elif [ "$1" == "refresh" ]; then
pulumi refresh --yes
else
echo "deploy.sh [up|rm|refresh]"
exit 1
fi
13 changes: 13 additions & 0 deletions App.Deploy/pulumi-state-bucket-lifecycle.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"lifecycle": {
"rule": [
{
"action": {"type": "Delete"},
"condition": {
"numNewerVersions": 7,
"isLive": false
}
}
]
}
}
32 changes: 30 additions & 2 deletions deploy/setup.sh → App.Deploy/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ PROJECT_SLUG=$(echo "${PROJECT_NAME}" | slugify )
SERVICE_ACCOUNT_NAME="github-cicd"
IDENTITY_POOL_NAME="github-pool"
IDENTITY_PROVIDER_NAME="github-provider"
PULUMI_CONFIG_PASSPHRASE=$(echo $RANDOM | md5sum | head -c 20; echo;)

echo "Generating data protection cert"
DATA_PROTECTION_CERTIFICATE=$(openssl req -x509 -newkey rsa:2048 -keyout /dev/stdout -out /dev/stdout -nodes -days 365 --subj '/')

# Create project
if ! gcloud projects describe "${PROJECT_SLUG}" > /dev/null; then
Expand All @@ -37,6 +41,7 @@ echo "Enabling required services"
gcloud services enable iamcredentials.googleapis.com --project "${PROJECT_SLUG}"
gcloud services enable artifactregistry.googleapis.com --project "${PROJECT_SLUG}"
gcloud services enable run.googleapis.com --project "${PROJECT_SLUG}"
gcloud services enable secretmanager.googleapis.com --project "${PROJECT_SLUG}"
echo ""

# Link billing account
Expand Down Expand Up @@ -100,8 +105,10 @@ gcloud iam service-accounts add-iam-policy-binding "${GOOGLE_SERVICE_ACCOUNT_EMA
--member="principalSet://iam.googleapis.com/${IDENTITY_POOL_ID}/attribute.repository/${GITHUB_REPOSITORY}"
GCLOUD_ROLES=(
"roles/artifactregistry.repoAdmin"
"roles/storage.objectViewer"
"roles/storage.objectAdmin"
"roles/iam.serviceAccountUser"
"roles/artifactregistry.repoAdmin"
"roles/secretmanager.admin"
)
for GCLOUD_ROLE in "${GCLOUD_ROLES[@]}"; do
gcloud projects add-iam-policy-binding "${PROJECT_SLUG}" \
Expand Down Expand Up @@ -132,6 +139,23 @@ fi
echo ""


# Create pulumi state bucket
if ! gcloud storage buckets describe --project="${PROJECT_SLUG}" "gs://${PROJECT_SLUG}-pulumi" > /dev/null; then
echo "Creating pulumi state bucket"
gcloud storage buckets create "gs://${PROJECT_SLUG}-pulumi" \
--no-public-access-prevention \
--location="${GOOGLE_REGION}" \
--project="${PROJECT_SLUG}"

gcloud storage buckets update "gs://${PROJECT_SLUG}-pulumi" \
--versioning \
--lifecycle-file=pulumi-state-bucket-lifecycle.json
else
echo "Pulumi state bucket already created"
fi
echo ""


# Create mongodb atlas project
if ! atlas project list | grep "${PROJECT_SLUG}" > /dev/null; then
echo "Creating MongoDB Atlas project"
Expand Down Expand Up @@ -179,7 +203,8 @@ function storeSecret {

echo "${SECRET_VALUE}" | gh secret set "${SECRET_NAME}" --app actions
echo "${SECRET_VALUE}" | gh secret set "${SECRET_NAME}" --app dependabot
echo "${SECRET_NAME}=\"${SECRET_VALUE}\"" >> .env.deploy.local

echo "${SECRET_NAME}='${SECRET_VALUE}'" >> .env.deploy.local
}
cat /dev/null > .env.deploy.local
storeSecret GOOGLE_WORKLOAD_IDENTITY_PROVIDER
Expand All @@ -191,6 +216,9 @@ storeSecret MONGODB_ATLAS_PRIVATE_KEY
storeSecret MONGODB_ATLAS_PUBLIC_KEY
storeSecret MONGODB_ATLAS_PROJECT_ID

# Secret protection keys
storeSecret PULUMI_CONFIG_PASSPHRASE
storeSecret DATA_PROTECTION_CERTIFICATE

# Variables that are required before setup. Preferably these are created with cli tools.
storeSecret AUTH0_DOMAIN
Expand Down
27 changes: 27 additions & 0 deletions App.Deploy/src/AppStack.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using App.Deploy.Component;
using App.Deploy.Component.Function.Integration;
using App.Deploy.Config;
using Pulumi;

namespace App.Deploy;

internal class AppStack : Stack
{
public AppStack()
{
var config = new AppConfig();

// Infra
new Auth0Component($"{config.Environment}-auth", config);
var database = new DatabaseComponent($"{config.Environment}-database", config);

// Cloud functions
var ynabComponent = new YnabComponent($"{config.Environment}-fn-integration-ynab", config, database);

// Frontend
new FrontendComponent($"{config.Environment}-frontend", config, new Dictionary<string, ICloudFunctionComponent>
{
{"FUNCTION_INTEGRATION_YNAB", ynabComponent}
});
}
}
87 changes: 87 additions & 0 deletions App.Deploy/src/Component/Auth0Component.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using App.Deploy.Config;
using Pulumi;
using Pulumi.Auth0;
using Pulumi.Auth0.Inputs;

namespace App.Deploy.Component;

internal class Auth0Component : ComponentResource
{
private readonly Dictionary<string, string> _scopes = new()
{
{ "function.integration.ynab", "Configure YNAB integration" }
};

private readonly ResourceServer _publicApi;

public Auth0Component(
string name,
AppConfig config
) : this(name, config, new ComponentResourceOptions())
{
}

public Auth0Component(
string name,
AppConfig config,
ComponentResourceOptions opts
) : base("app:auth0", name, opts)
{
_publicApi = new ResourceServer(
$"{name}-public",
new ResourceServerArgs
{
Identifier = $"{name}-public",
Scopes = GetApiScopes()
},
new CustomResourceOptions { Parent = this });

new Role(
$"{name}-user",
new()
{
Description = "App user",
Permissions = GetRolePermissions()
},
new CustomResourceOptions { Parent = this });

new Rule(
$"{name}-tenant-claim",
new()
{
Script = $@"
function (user, context, callback) {{
if (context.clientName.startsWith('{config.Environment}:')) {{
context.accessToken['app/tenants'] = user?.app_metadata?.tenants || [];
}}

return callback(null, user, context);
}}"
},
new CustomResourceOptions { Parent = this });

RegisterOutputs();
}

private InputList<ResourceServerScopeArgs> GetApiScopes()
{
return _scopes
.Select(kvp => new ResourceServerScopeArgs
{
Value = kvp.Key,
Description = kvp.Value
}
).ToList();
}

private InputList<RolePermissionArgs> GetRolePermissions()
{
return _scopes
.Select(kvp => new RolePermissionArgs
{
ResourceServerIdentifier = _publicApi.Identifier,
Name = kvp.Key
}
).ToList();
}
}
Loading