Skip to content

Commit 5cbca73

Browse files
committed
updates
1 parent 0c3e667 commit 5cbca73

File tree

9 files changed

+622
-68
lines changed

9 files changed

+622
-68
lines changed

.gcloudignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# ignore everything
2+
*
3+
4+
# except the following
5+
!.gcloudignore
6+
!.main.py
7+
!.requirements.txt
8+
!telegram_bot/bot.py
9+
!telegram_bot/menu.py
10+
!telegram_bot/init.py

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ python-dotenv = "*"
1414
python-telegram-bot = "*"
1515
pytz = "*"
1616
functions-framework = "*"
17+
pulumi-random = "*"
1718

1819
[requires]
1920
python_version = "3.13"

Pipfile.lock

Lines changed: 233 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

infra/Pulumi.prod.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
encryptionsalt: v1:MpvbSqwJ0R0=:v1:Gb5Aayq2PEDs+22I:lFM2LBSteNXhvO4PjNRCgZ5h783eDQ==
1+
encryptionsalt: v1:dPeFWTLi9AI=:v1:oVtFIvCQGewIBERG:46y/iE1CtLrjeTkc2SilpwEjfJg0iw==
22
config:
3-
gcp:project: osakunta-telegram-bot
3+
gcp:region: europe-north1
4+
gcp:disableGlobalProjectWarning: "true"

infra/__main__.py

Lines changed: 163 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,188 @@
11
import pulumi
2-
import pulumi_gcp as gcp
2+
import pulumi_gcp as gcp
3+
import pulumi_random as random
4+
import utils
35

4-
# Be careful editing this file, if you are unfamiliar with Pulumi or the Google Cloud Platform.
5-
# Make sure you read the README.md in the root of this repository first.
6+
gcp_config = pulumi.Config("gcp")
7+
LOCATION = gcp_config.require("region")
8+
STACK_NAME = pulumi.get_stack()
69

7-
# setup infrastructure
10+
project_id = random.RandomPet("project-id",
11+
length=2
12+
)
813

9-
PROJECT_ID = "osakunta-telegram-bot"
10-
LOCATION = "europe-north1"
14+
project = gcp.organizations.Project("project",
15+
name=f"Telegram Bot {STACK_NAME}",
16+
project_id=project_id.id,
17+
folder_id="452932952214"
18+
)
1119

12-
# Set up secret to hold the Telegram API token
13-
telegram_bot_token = gcp.secretmanager.Secret("telegram-bot-token",
14-
secret_id="telegram-bot-token",
20+
# Enable required services / APIs
21+
secretmanager_service = gcp.projects.Service("secretmanager-service",
22+
project=project_id.id,
23+
service="secretmanager.googleapis.com",
24+
disable_on_destroy=True
25+
)
26+
27+
cloudbuild_service = gcp.projects.Service("cloudbuild-service",
28+
project=project_id.id,
29+
service="cloudbuild.googleapis.com",
30+
disable_on_destroy=True
31+
)
32+
33+
cloudrun_service = gcp.projects.Service("cloudrun-service",
34+
project=project_id.id,
35+
service="run.googleapis.com",
36+
disable_on_destroy=True
37+
)
38+
39+
cloudfunctions_service = gcp.projects.Service("cloudfunctions-service",
40+
project=project_id.id,
41+
service="cloudfunctions.googleapis.com",
42+
disable_on_destroy=True
43+
)
44+
45+
cloudresourcemanager_service = gcp.projects.Service("cloudresourcemanager-service",
46+
project=project_id.id,
47+
service="cloudresourcemanager.googleapis.com",
48+
disable_on_destroy=True
49+
)
50+
51+
52+
# --- Set up github repo connection ---
53+
github_token_secret = gcp.secretmanager.Secret("github-token-secret",
54+
project=project_id.id,
55+
secret_id="github-token",
1556
replication={
1657
"user_managed": {
1758
"replicas": [{ "location": LOCATION }]
1859
}
19-
}
60+
},
61+
opts=pulumi.ResourceOptions(
62+
depends_on=[secretmanager_service]
63+
)
2064
)
2165

22-
# Set up a service account that has access to the secret, for the Function to use
23-
service_account = gcp.serviceaccount.Account("service-account",
24-
account_id="telegram-bot-service-account",
25-
display_name="Telegram Bot Service Account")
26-
27-
secret_access = gcp.secretmanager.SecretIamMember("secret-access",
28-
secret_id=telegram_bot_token.id,
66+
github_connection_service_account_secret_access = gcp.secretmanager.SecretIamMember("github-connection-service-account-secret-access",
67+
project=project_id.id,
68+
secret_id=github_token_secret.id,
2969
role="roles/secretmanager.secretAccessor",
30-
member=service_account.email.apply(lambda email: f"serviceAccount:{email}")
70+
member=pulumi.Output.concat(
71+
"serviceAccount:service-",
72+
project.number,
73+
"@gcp-sa-cloudbuild.iam.gserviceaccount.com"
74+
),
75+
opts=pulumi.ResourceOptions(
76+
depends_on=[cloudbuild_service]
77+
)
3178
)
3279

80+
github_connection = gcp.cloudbuildv2.Connection("github-connection",
81+
project=project_id.id,
82+
name="github-connection",
83+
location=LOCATION,
84+
github_config={
85+
"app_installation_id": 30357801,
86+
"authorizer_credential": {
87+
"oauth_token_secret_version": github_token_secret.name.apply(lambda name: f"{name}/versions/latest")
88+
}
89+
},
90+
opts=pulumi.ResourceOptions(
91+
depends_on=[github_connection_service_account_secret_access]
92+
)
93+
)
3394

34-
# Set up the source code
35-
source_bucket = gcp.storage.Bucket("source-bucket",
95+
github_repository = gcp.cloudbuildv2.Repository("github-repository",
96+
project=project_id.id,
97+
name="telegram-bot",
3698
location=LOCATION,
37-
name=f"{PROJECT_ID}-source-bucket",
99+
parent_connection=github_connection.name,
100+
remote_uri="https://github.com/osakunta/telegram-bot.git",
38101
)
39102

40-
source_asset = pulumi.AssetArchive({
41-
"telegram_bot": pulumi.FileArchive("../telegram_bot"),
42-
"main.py": pulumi.FileAsset("../main.py"),
43-
"requirements.txt": pulumi.FileAsset("../requirements.txt")
44-
})
45-
source_object = gcp.storage.BucketObject("source-object",
46-
bucket=source_bucket.name,
47-
name="telegram-bot-source",
48-
source=source_asset
103+
# --- Set up CI/CD ---
104+
105+
cicd_service_account = utils.service_account_with_roles(
106+
"cicd-service-account",
107+
[
108+
"roles/logging.logWriter",
109+
"roles/cloudfunctions.developer",
110+
"roles/iam.serviceAccountUser",
111+
"roles/storage.objectViewer",
112+
"roles/artifactregistry.writer"
113+
],
114+
project=project_id.id,
115+
account_id="cicd-service-account",
116+
display_name="CICD Service Account"
49117
)
50118

51-
# Set up the Function, which handles the requests
52-
function = gcp.cloudfunctionsv2.Function("function",
53-
location=LOCATION,
54-
name="telegram-bot-function",
55-
description="Cloud Run Function for handling telegram bot requests",
56-
build_config={
57-
"runtime": "python313",
58-
"entryPoint": "telegram_bot",
59-
"source": {
60-
"storage_source": {
61-
"bucket": source_bucket.name,
62-
"object": source_object.name,
63-
"generation": source_object.generation
64-
}
119+
runtime_service_account = utils.service_account_with_roles(
120+
"runtime-service-account",
121+
[ "roles/iam.serviceAccountUser" ],
122+
project=project_id.id,
123+
account_id="runtime-service-account",
124+
display_name="Function Runtime Service Account"
125+
)
126+
127+
telegram_bot_token = gcp.secretmanager.Secret("telegram-bot-token",
128+
project=project_id.id,
129+
secret_id="telegram-bot-token",
130+
replication={
131+
"user_managed": {
132+
"replicas": [{ "location": LOCATION }]
65133
}
66-
},
67-
service_config={
68-
"availableMemory": "128Mi",
69-
"maxInstanceCount": 1, # No need for more than one instance
70-
"minInstanceCount": 0, # Important to allow scale-to-zero, to save costs
71-
"service_account_email": service_account.email,
72-
"ingressSettings": "ALLOW_ALL",
73-
"secret_environment_variables": [{
74-
"key": "TOKEN",
75-
"project_id": PROJECT_ID,
76-
"secret": telegram_bot_token.secret_id,
77-
"version": "latest"
78-
}],
79-
}
134+
},
135+
opts=pulumi.ResourceOptions(
136+
depends_on=[secretmanager_service]
137+
)
80138
)
81139

82-
# Finally, set an IAM policy to allow unauthenticated people (anyone) to invoke the function
83-
# this has to be cloudrun.ServiceIamMember instead of cloudfunctions.FunctionIamMember
84-
# because the function is v2
85-
function_public_iam = gcp.cloudrunv2.ServiceIamMember("function-public-iam",
140+
telegram_bot_token_secret_access = gcp.secretmanager.SecretIamMember("telegram-bot-token-secret-access",
141+
project=project_id.id,
142+
secret_id=telegram_bot_token.id,
143+
role="roles/secretmanager.secretAccessor",
144+
member=runtime_service_account.member,
145+
)
146+
147+
deploy_trigger = gcp.cloudbuild.Trigger("deploy-trigger",
148+
project=project_id.id,
149+
name="deploy",
86150
location=LOCATION,
87-
name=function.name,
88-
role="roles/run.invoker",
89-
member="allUsers"
151+
service_account=cicd_service_account.id,
152+
repository_event_config={
153+
"repository": github_repository.id,
154+
"push": {
155+
"branch": f"^{STACK_NAME}$",
156+
}
157+
},
158+
build={
159+
"steps": [
160+
{
161+
"name": "gcr.io/cloud-builders/gcloud",
162+
"args": [
163+
"functions", "deploy", "telegram-bot",
164+
"--region", LOCATION,
165+
"--runtime", "python313",
166+
"--entry-point", "telegram_bot",
167+
"--trigger-http",
168+
"--allow-unauthenticated",
169+
"--timeout", "5s",
170+
"--gen2",
171+
"--max-instances", "1",
172+
"--min-instances", "0",
173+
"--memory", "128Mi",
174+
"--set-secrets", telegram_bot_token.name.apply(lambda name: f"TOKEN={name}/versions/latest"),
175+
"--source", ".",
176+
"--run-service-account", runtime_service_account.email,
177+
"--build-service-account", cicd_service_account.id,
178+
],
179+
}
180+
],
181+
"options": {
182+
"logging": "CLOUD_LOGGING_ONLY"
183+
}
184+
},
185+
opts=pulumi.ResourceOptions(
186+
depends_on=[ cloudrun_service, cloudfunctions_service, cloudresourcemanager_service ]
187+
)
90188
)

infra/cicd.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import pulumi
2+
import pulumi_gcp as gcp
3+
4+
def setup_cicd():
5+
gcp_config = pulumi.Config("gcp")
6+
LOCATION = gcp_config.require("region")
7+
PROJECT_ID = gcp_config.require("project")
8+
9+
config = pulumi.Config()
10+
STACK_NAME = config.require("stack_name")
11+
GITHUB_BRANCH = config.require("github_branch")
12+
13+
14+
# Set up secret to hold the GitHub token
15+
github_token_secret = gcp.secretmanager.Secret("github-token-secret",
16+
secret_id="github-token",
17+
replication={
18+
"user_managed": {
19+
"replicas": [{ "location": LOCATION }]
20+
}
21+
}
22+
)
23+
# Populate this manually
24+
github_token_secret_version = gcp.secretmanager.get_secret_version(secret=github_token_secret.id)
25+
26+
PROJECT_NUMBER = gcp.projects.get_project(filter=f"id:{PROJECT_ID}").projects[0].number
27+
service_account_secret_access = gcp.secretmanager.SecretIamMember("service-account-secret-access",
28+
secret_id=github_token_secret.id,
29+
role="roles/secretmanager.secretAccessor",
30+
member=f"serviceAccount:service-{PROJECT_NUMBER}@gcp-sa-cloudbuild.iam.gserviceaccount.com"
31+
)
32+
33+
github_connection = gcp.cloudbuildv2.Connection("github-connection",
34+
name="github-connection",
35+
location=LOCATION,
36+
github_config={
37+
"app_installation_id": 30357801,
38+
"authorizer_credential": {
39+
"oauth_token_secret_version": github_token_secret_version.id,
40+
}
41+
},
42+
opts=pulumi.ResourceOptions(
43+
depends_on=[service_account_secret_access]
44+
)
45+
)
46+
47+
github_repository = gcp.cloudbuildv2.Repository("github-repository",
48+
name="telegram-bot",
49+
location=LOCATION,
50+
parent_connection=github_connection.name,
51+
remote_uri="https://github.com/osakunta/telegram-bot.git",
52+
)
53+
54+
cicd_service_account = gcp.serviceaccount.Account("cicd-service-account",
55+
account_id="cicd-service-account",
56+
display_name="CICD Service Account"
57+
)
58+
59+
# Artefact Registry stores the Docker images built by Cloud Build
60+
artefact_repository = gcp.artifactregistry.Repository("artefact-repository",
61+
repository_id="telegram-bot",
62+
location=LOCATION,
63+
format="DOCKER",
64+
description="Docker repository for the Telegram Bot",
65+
labels={
66+
"osakunta": "telegram-bot"
67+
}
68+
)
69+
70+
# staging_build_trigger = gcp.cloudbuild.Trigger("staging-build-trigger",
71+
# name="staging-build",
72+
# location=LOCATION,
73+
# repository_event_config={
74+
# "repository": github_repository.id,
75+
# "push": {
76+
# "branch": "^staging$",
77+
# }
78+
# }
79+
# )
80+
81+
IMAGE_URL = artefact_repository.name.apply(lambda name: f"{LOCATION}-docker.pkg.dev/{PROJECT_ID}/{name}/telegram-bot:$COMMIT_SHA")
82+
build_trigger = gcp.cloudbuild.Trigger("build-trigger",
83+
name="prod-build",
84+
location=LOCATION,
85+
service_account=cicd_service_account.id,
86+
repository_event_config={
87+
"repository": github_repository.id,
88+
"push": {
89+
"branch": "^main$",
90+
}
91+
},
92+
build={
93+
"images": [IMAGE_URL],
94+
"steps": [{
95+
"name": "gcr.io/k8s-skaffold/pack",
96+
"entrypoint": "pack",
97+
"args": [
98+
"build",
99+
IMAGE_URL,
100+
"--builder", "gcr.io/buildpacks/builder:latest",
101+
"--network",
102+
"cloudbuild"
103+
],
104+
}],
105+
}
106+
)

infra/github.py

Whitespace-only changes.

0 commit comments

Comments
 (0)