Skip to content

Commit c87976d

Browse files
committed
Add initial cron task to check VCS providers for new commits on branch.
Add git commit to configuration version DB table. Issue #80
1 parent 473d8b6 commit c87976d

10 files changed

+207
-21
lines changed

cron_tasks.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
from terrarun.cron_tasks import CronTasks
3+
4+
5+
cron_tasks = CronTasks()
6+
cron_tasks.start()

docker-compose.yml

+18-4
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,14 @@ services:
8585
volumes:
8686
- ./:/app
8787

88-
agent:
88+
cron-tasks:
8989
restart: unless-stopped
9090
build: .
9191
networks:
9292
- web
93-
depends_on:
94-
- traefik
9593
env_file:
9694
- ./.env
97-
entrypoint: ["python", "-u", "./agent.py", "--address", "$BASE_URL", "--token", "$AGENT_TOKEN"]
95+
entrypoint: ["python", "-u", "./cron_tasks.py"]
9896

9997
# Only for local development
10098
volumes:
@@ -142,6 +140,22 @@ services:
142140
exit 0;
143141
"
144142
143+
# Example custom agent
144+
agent:
145+
restart: unless-stopped
146+
build: .
147+
networks:
148+
- web
149+
depends_on:
150+
- traefik
151+
env_file:
152+
- ./.env
153+
entrypoint: ["python", "-u", "./agent.py", "--address", "$BASE_URL", "--token", "$AGENT_TOKEN"]
154+
155+
# Only for local development
156+
volumes:
157+
- ./:/app
158+
145159
# Example run task application
146160
tfsec-task:
147161
build: https://gitlab.dockstudios.co.uk/pub/terra/tfcloud-tfsec.git

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ mysql-connector-python==8.0.29
1010
boto3==1.26.70
1111
botocore==1.29.70
1212
cryptography==40.0.2
13+
schedule==1.2.0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Add git commit sha field to configuration version
2+
3+
Revision ID: 90149a6955df
4+
Revises: 9afeb4e61715
5+
Create Date: 2023-05-08 15:31:37.174335
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '90149a6955df'
14+
down_revision = '9afeb4e61715'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade() -> None:
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.add_column('configuration_version', sa.Column('git_commit_sha', sa.String(length=128), nullable=True))
22+
# ### end Alembic commands ###
23+
24+
25+
def downgrade() -> None:
26+
# ### commands auto generated by Alembic - please adjust! ###
27+
op.drop_column('configuration_version', 'git_commit_sha')
28+
# ### end Alembic commands ###

terrarun/cron_tasks.py

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
2+
import signal
3+
import time
4+
5+
import schedule
6+
from terrarun.database import Database
7+
8+
from terrarun.models.authorised_repo import AuthorisedRepo
9+
from terrarun.models.configuration import ConfigurationVersion
10+
from terrarun.models.run import Run
11+
12+
13+
class CronTasks:
14+
"""Interface to start cron tasks."""
15+
16+
def __init__(self):
17+
"""Store member variables"""
18+
self._running = True
19+
schedule.every(60).seconds.do(self.check_for_vcs_commits)
20+
21+
def stop(self):
22+
"""Mark as stopped, stopping any further jobs from executing"""
23+
self._running = False
24+
25+
def start(self):
26+
"""Start scheduler"""
27+
signal.signal(signal.SIGINT, self.stop)
28+
#signal.pause()
29+
while self._running:
30+
schedule.run_pending()
31+
time.sleep(1)
32+
33+
def _process_authorised_repo_workspace(self, authorised_repo, workspace, branch_shas):
34+
"""Handle checking workspace for new commits to create run for"""
35+
print(f'Handling workspace: {workspace.name}')
36+
service_provider = authorised_repo.oauth_token.oauth_client.service_provider_instance
37+
workspace_branch = workspace.get_branch()
38+
39+
# Obtain latest sha for branch, if not already cached
40+
if workspace_branch not in branch_shas:
41+
branch_shas[workspace_branch] = service_provider.get_latest_commit_ref(
42+
authorised_repo=workspace.authorised_repo, branch=workspace_branch
43+
)
44+
if not branch_shas[workspace_branch]:
45+
print(f'Could not find latest commit for branch: {workspace_branch}')
46+
return branch_shas
47+
48+
if not ConfigurationVersion.get_configuration_version_by_git_commit_sha(
49+
workspace=workspace,
50+
git_commit_sha=branch_shas[workspace_branch]):
51+
# If there is not a configuration version for the git commit,
52+
# create one
53+
cv = ConfigurationVersion.generate_from_vcs(
54+
workspace=workspace,
55+
commit_ref=branch_shas[workspace_branch],
56+
# Allow all runs to be queued to be applied
57+
speculative=False
58+
)
59+
if not cv:
60+
print('Unable to create configuration version')
61+
return branch_shas
62+
63+
# Create run
64+
Run.create(
65+
configuration_version=cv,
66+
created_by=None,
67+
is_destroy=False,
68+
refresh=True,
69+
refresh_only=False,
70+
auto_apply=workspace.auto_apply,
71+
plan_only=False
72+
)
73+
return branch_shas
74+
75+
def check_for_vcs_commits(self):
76+
"""Check for new commits on VCS repositories"""
77+
# Iterate over all authorised repos that have one workspace or project defined
78+
for authorised_repo in AuthorisedRepo.get_all_utilised_repos():
79+
print(f'Handling repo: {authorised_repo.name}')
80+
branch_shas = {}
81+
82+
for project in authorised_repo.projects:
83+
print(f'Handling project: {project.name}')
84+
85+
for workspace in project.workspaces:
86+
branch_shas = self._process_authorised_repo_workspace(
87+
authorised_repo=authorised_repo,
88+
workspace=workspace,
89+
branch_shas=branch_shas
90+
)
91+
92+
# Handle direct member workspaces
93+
for workspace in authorised_repo.workspaces:
94+
branch_shas = self._process_authorised_repo_workspace(
95+
authorised_repo=authorised_repo,
96+
workspace=workspace,
97+
branch_shas=branch_shas
98+
)
99+
100+
# Clear database session to avoid cached queries
101+
Database.get_session().remove()
102+
103+
print("Checking for VCS commits")

terrarun/models/authorised_repo.py

+8
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ def get_by_external_id(cls, oauth_token, external_id):
8383
session = Database.get_session()
8484
return session.query(cls).filter(cls.oauth_token==oauth_token, cls.external_id==external_id).first()
8585

86+
@classmethod
87+
def get_all_utilised_repos(cls):
88+
"""Get all repos that are used by a workspace or project"""
89+
session = Database.get_session()
90+
return session.query(cls).filter(
91+
sqlalchemy.or_(cls.projects.any(), cls.workspaces.any())
92+
).all()
93+
8694
@property
8795
def vendor_configuration(self):
8896
"""Return vendor configuration"""

terrarun/models/configuration.py

+26-16
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,16 @@ class ConfigurationVersion(Base, BaseObject):
4949
auto_queue_runs = sqlalchemy.Column(sqlalchemy.Boolean)
5050
status = sqlalchemy.Column(sqlalchemy.Enum(ConfigurationVersionStatus))
5151

52+
git_commit_sha = sqlalchemy.Column(Database.GeneralString, nullable=True)
53+
5254
@classmethod
53-
def create(cls, workspace, auto_queue_runs=True, speculative=False):
55+
def create(cls, workspace, auto_queue_runs=True, speculative=False, git_commit_sha=None):
5456
"""Create configuration and return instance."""
5557
cv = ConfigurationVersion(
5658
workspace=workspace,
5759
speculative=speculative,
5860
auto_queue_runs=auto_queue_runs,
61+
git_commit_sha=git_commit_sha,
5962
status=ConfigurationVersionStatus.PENDING
6063
)
6164
session = Database.get_session()
@@ -67,22 +70,19 @@ def create(cls, workspace, auto_queue_runs=True, speculative=False):
6770
return cv
6871

6972
@classmethod
70-
def generate_from_vcs(cls, workspace, speculative):
73+
def generate_from_vcs(cls, workspace, speculative, commit_ref=None):
7174
"""Create configuration version from VCS"""
7275
service_provider = workspace.authorised_repo.oauth_token.oauth_client.service_provider_instance
7376

74-
# Obtain branch from workspace
75-
branch = workspace.vcs_repo_branch
76-
# If it doesn't exist, obtain default branch from repository
77-
if not branch:
78-
branch = service_provider.get_default_branch(authorised_repo=workspace.authorised_repo)
79-
80-
if not branch:
81-
return None
82-
83-
commit_ref = service_provider.get_latest_commit_ref(
84-
authorised_repo=workspace.authorised_repo, branch=branch
85-
)
77+
if commit_ref is None:
78+
# Obtain branch from workspace
79+
branch = workspace.get_branch()
80+
if not branch:
81+
return None
82+
83+
commit_ref = service_provider.get_latest_commit_ref(
84+
authorised_repo=workspace.authorised_repo, branch=branch
85+
)
8686
if commit_ref is None:
8787
return None
8888

@@ -94,12 +94,22 @@ def generate_from_vcs(cls, workspace, speculative):
9494

9595
configuration_version = cls.create(
9696
workspace=workspace,
97-
auto_queue_runs=False,
98-
speculative=speculative
97+
auto_queue_runs=True,
98+
speculative=speculative,
99+
git_commit_sha=commit_ref
99100
)
100101
configuration_version.process_upload(archive_data)
101102
return configuration_version
102103

104+
@classmethod
105+
def get_configuration_version_by_git_commit_sha(cls, workspace, git_commit_sha):
106+
"""Return configuration versions by workspace and git commit sha"""
107+
session = Database.get_session()
108+
return session.query(cls).filter(
109+
cls.workspace==workspace,
110+
cls.git_commit_sha==git_commit_sha
111+
).all()
112+
103113
@property
104114
def plan_only(self):
105115
"""Return whether only a plan."""

terrarun/models/run.py

+4
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ class Run(Base, BaseObject):
107107

108108
@property
109109
def replace_addrs(self):
110+
if not self._replace_addrs:
111+
return []
110112
return json.loads(self._replace_addrs)
111113

112114
@replace_addrs.setter
@@ -115,6 +117,8 @@ def replace_addrs(self, value):
115117

116118
@property
117119
def target_addrs(self):
120+
if not self._target_addrs:
121+
return []
118122
return json.loads(self._target_addrs)
119123

120124
@target_addrs.setter

terrarun/models/task_result.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def generate_payload(self):
184184
"run_id": self.task_stage.run.api_id,
185185
"run_message": self.task_stage.run.message,
186186
"run_created_at": datetime_to_json(self.task_stage.run.created_at),
187-
"run_created_by": self.task_stage.run.created_by.username,
187+
"run_created_by": self.task_stage.run.created_by.username if self.task_stage.run.created_by else None,
188188
"workspace_id": self.task_stage.run.configuration_version.workspace.api_id,
189189
"workspace_name": self.task_stage.run.configuration_version.workspace.name,
190190
"workspace_app_url": f"{config.BASE_URL}/app/{self.task_stage.run.configuration_version.workspace.organisation.name}/{self.task_stage.run.configuration_version.workspace.name}",

terrarun/models/workspace.py

+12
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,18 @@ def assessments_enabled(self, value):
394394
"""Set assessments_enabled"""
395395
self._assessments_enabled = value
396396

397+
def get_branch(self):
398+
"""Get branch, either defined in workspace, project or default VCS branch"""
399+
# Obtain branch from workspace
400+
branch = self.vcs_repo_branch
401+
402+
# If it doesn't exist, obtain default branch from repository
403+
if not branch and self.authorised_repo:
404+
branch = self.authorised_repo.oauth_token.oauth_client.service_provider_instance.get_default_branch(
405+
authorised_repo=self.authorised_repo
406+
)
407+
return branch
408+
397409
def check_vcs_repo_update_from_request(self, vcs_repo_attributes):
398410
"""Update VCS repo from request"""
399411
# Check if VCS is defined in project

0 commit comments

Comments
 (0)