Skip to content

Commit 473d8b6

Browse files
committed
Merge branch '75-generate-random-ids-for-each-entity' into 'main'
Resolve "Generate random Ids for each entity" Closes #75 See merge request pub/terra/terrarun!42
2 parents 00df757 + 860f42f commit 473d8b6

35 files changed

+365
-34
lines changed

terrarun/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@
3838
from terrarun.models.oauth_client import OauthClient
3939
from terrarun.models.oauth_token import OauthToken
4040
from terrarun.models.github_app_oauth_token import GithubAppOauthToken
41-
from terrarun.models.authorised_repo import AuthorisedRepo
41+
from terrarun.models.authorised_repo import AuthorisedRepo
42+
from terrarun.models.api_id import ApiId
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Add indexes to API ID table
2+
3+
Revision ID: 39e8a680df30
4+
Revises: 614792b159a0
5+
Create Date: 2023-05-08 08:05:56.092727
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '39e8a680df30'
14+
down_revision = '614792b159a0'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade() -> None:
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.create_index('_api_id_suffix_index', 'api_id', ['api_id_suffix'], unique=False)
22+
op.create_index('_object_class_object_id_in', 'api_id', ['object_class', 'object_id'], unique=False)
23+
# ### end Alembic commands ###
24+
25+
26+
def downgrade() -> None:
27+
# ### commands auto generated by Alembic - please adjust! ###
28+
op.drop_index('_object_class_object_id_in', table_name='api_id')
29+
op.drop_index('_api_id_suffix_index', table_name='api_id')
30+
# ### end Alembic commands ###
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Add table for assignment of API IDs
2+
3+
Revision ID: 614792b159a0
4+
Revises: 46fb6be21022
5+
Create Date: 2023-05-08 07:32:15.266859
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '614792b159a0'
14+
down_revision = '46fb6be21022'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade() -> None:
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.create_table('api_id',
22+
sa.Column('id', sa.Integer(), nullable=False),
23+
sa.Column('api_id_suffix', sa.String(length=128), nullable=True),
24+
sa.Column('object_class', sa.String(length=128), nullable=True),
25+
sa.Column('object_id', sa.Integer(), nullable=True),
26+
sa.PrimaryKeyConstraint('id'),
27+
sa.UniqueConstraint('api_id_suffix'),
28+
sa.UniqueConstraint('object_class', 'object_id', name='_object_class_object_id_uc')
29+
)
30+
# ### end Alembic commands ###
31+
32+
33+
def downgrade() -> None:
34+
# ### commands auto generated by Alembic - please adjust! ###
35+
op.drop_table('api_id')
36+
# ### end Alembic commands ###
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"""Add foreign key to each object table to link to api id row
2+
3+
Revision ID: 9afeb4e61715
4+
Revises: 39e8a680df30
5+
Create Date: 2023-05-08 12:24:21.494334
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
from sqlalchemy.dialects import mysql
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '9afeb4e61715'
14+
down_revision = '39e8a680df30'
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('agent', sa.Column('api_id_fk', sa.Integer(), nullable=True))
22+
op.create_foreign_key(None, 'agent', 'api_id', ['api_id_fk'], ['id'])
23+
op.add_column('agent_pool', sa.Column('api_id_fk', sa.Integer(), nullable=True))
24+
op.create_foreign_key(None, 'agent_pool', 'api_id', ['api_id_fk'], ['id'])
25+
op.add_column('agent_token', sa.Column('api_id_fk', sa.Integer(), nullable=True))
26+
op.create_foreign_key(None, 'agent_token', 'api_id', ['api_id_fk'], ['id'])
27+
op.drop_index('_api_id_suffix_index', table_name='api_id')
28+
op.drop_index('_object_class_object_id_in', table_name='api_id')
29+
op.drop_index('_object_class_object_id_uc', table_name='api_id')
30+
op.drop_column('api_id', 'object_class')
31+
op.drop_column('api_id', 'object_id')
32+
op.add_column('apply', sa.Column('api_id_fk', sa.Integer(), nullable=True))
33+
op.create_foreign_key(None, 'apply', 'api_id', ['api_id_fk'], ['id'])
34+
op.add_column('audit_event', sa.Column('api_id_fk', sa.Integer(), nullable=True))
35+
op.create_foreign_key(None, 'audit_event', 'api_id', ['api_id_fk'], ['id'])
36+
op.add_column('authorised_repo', sa.Column('api_id_fk', sa.Integer(), nullable=True))
37+
op.create_foreign_key(None, 'authorised_repo', 'api_id', ['api_id_fk'], ['id'])
38+
op.add_column('blob', sa.Column('api_id_fk', sa.Integer(), nullable=True))
39+
op.create_foreign_key(None, 'blob', 'api_id', ['api_id_fk'], ['id'])
40+
op.add_column('configuration_version', sa.Column('api_id_fk', sa.Integer(), nullable=True))
41+
op.create_foreign_key(None, 'configuration_version', 'api_id', ['api_id_fk'], ['id'])
42+
op.add_column('environment', sa.Column('api_id_fk', sa.Integer(), nullable=True))
43+
op.create_foreign_key(None, 'environment', 'api_id', ['api_id_fk'], ['id'])
44+
op.add_column('github_app_oauth_token', sa.Column('api_id_fk', sa.Integer(), nullable=True))
45+
op.create_foreign_key(None, 'github_app_oauth_token', 'api_id', ['api_id_fk'], ['id'])
46+
op.add_column('lifecycle', sa.Column('api_id_fk', sa.Integer(), nullable=True))
47+
op.create_foreign_key(None, 'lifecycle', 'api_id', ['api_id_fk'], ['id'])
48+
op.add_column('oauth_client', sa.Column('api_id_fk', sa.Integer(), nullable=True))
49+
op.create_foreign_key(None, 'oauth_client', 'api_id', ['api_id_fk'], ['id'])
50+
op.add_column('oauth_token', sa.Column('api_id_fk', sa.Integer(), nullable=True))
51+
op.create_foreign_key(None, 'oauth_token', 'api_id', ['api_id_fk'], ['id'])
52+
op.add_column('organisation', sa.Column('api_id_fk', sa.Integer(), nullable=True))
53+
op.create_foreign_key(None, 'organisation', 'api_id', ['api_id_fk'], ['id'])
54+
op.add_column('plan', sa.Column('api_id_fk', sa.Integer(), nullable=True))
55+
op.create_foreign_key(None, 'plan', 'api_id', ['api_id_fk'], ['id'])
56+
op.add_column('project', sa.Column('api_id_fk', sa.Integer(), nullable=True))
57+
op.create_foreign_key(None, 'project', 'api_id', ['api_id_fk'], ['id'])
58+
op.add_column('run', sa.Column('api_id_fk', sa.Integer(), nullable=True))
59+
op.create_foreign_key(None, 'run', 'api_id', ['api_id_fk'], ['id'])
60+
op.add_column('run_queue', sa.Column('api_id_fk', sa.Integer(), nullable=True))
61+
op.create_foreign_key(None, 'run_queue', 'api_id', ['api_id_fk'], ['id'])
62+
op.add_column('state_version', sa.Column('api_id_fk', sa.Integer(), nullable=True))
63+
op.create_foreign_key(None, 'state_version', 'api_id', ['api_id_fk'], ['id'])
64+
op.add_column('tag', sa.Column('api_id_fk', sa.Integer(), nullable=True))
65+
op.create_foreign_key(None, 'tag', 'api_id', ['api_id_fk'], ['id'])
66+
op.add_column('task', sa.Column('api_id_fk', sa.Integer(), nullable=True))
67+
op.create_foreign_key(None, 'task', 'api_id', ['api_id_fk'], ['id'])
68+
op.add_column('task_result', sa.Column('api_id_fk', sa.Integer(), nullable=True))
69+
op.create_foreign_key(None, 'task_result', 'api_id', ['api_id_fk'], ['id'])
70+
op.add_column('task_stage', sa.Column('api_id_fk', sa.Integer(), nullable=True))
71+
op.create_foreign_key(None, 'task_stage', 'api_id', ['api_id_fk'], ['id'])
72+
op.add_column('team', sa.Column('api_id_fk', sa.Integer(), nullable=True))
73+
op.create_foreign_key(None, 'team', 'api_id', ['api_id_fk'], ['id'])
74+
op.add_column('team_workspace_access', sa.Column('api_id_fk', sa.Integer(), nullable=True))
75+
op.create_foreign_key(None, 'team_workspace_access', 'api_id', ['api_id_fk'], ['id'])
76+
op.add_column('user', sa.Column('api_id_fk', sa.Integer(), nullable=True))
77+
op.create_foreign_key(None, 'user', 'api_id', ['api_id_fk'], ['id'])
78+
op.add_column('user_token', sa.Column('api_id_fk', sa.Integer(), nullable=True))
79+
op.create_foreign_key(None, 'user_token', 'api_id', ['api_id_fk'], ['id'])
80+
op.add_column('workspace', sa.Column('api_id_fk', sa.Integer(), nullable=True))
81+
op.create_foreign_key(None, 'workspace', 'api_id', ['api_id_fk'], ['id'])
82+
op.add_column('workspace_task', sa.Column('api_id_fk', sa.Integer(), nullable=True))
83+
op.create_foreign_key(None, 'workspace_task', 'api_id', ['api_id_fk'], ['id'])
84+
# ### end Alembic commands ###
85+
86+
87+
def downgrade() -> None:
88+
# ### commands auto generated by Alembic - please adjust! ###
89+
op.drop_constraint(None, 'workspace_task', type_='foreignkey')
90+
op.drop_column('workspace_task', 'api_id_fk')
91+
op.drop_constraint(None, 'workspace', type_='foreignkey')
92+
op.drop_column('workspace', 'api_id_fk')
93+
op.drop_constraint(None, 'user_token', type_='foreignkey')
94+
op.drop_column('user_token', 'api_id_fk')
95+
op.drop_constraint(None, 'user', type_='foreignkey')
96+
op.drop_column('user', 'api_id_fk')
97+
op.drop_constraint(None, 'team_workspace_access', type_='foreignkey')
98+
op.drop_column('team_workspace_access', 'api_id_fk')
99+
op.drop_constraint(None, 'team', type_='foreignkey')
100+
op.drop_column('team', 'api_id_fk')
101+
op.drop_constraint(None, 'task_stage', type_='foreignkey')
102+
op.drop_column('task_stage', 'api_id_fk')
103+
op.drop_constraint(None, 'task_result', type_='foreignkey')
104+
op.drop_column('task_result', 'api_id_fk')
105+
op.drop_constraint(None, 'task', type_='foreignkey')
106+
op.drop_column('task', 'api_id_fk')
107+
op.drop_constraint(None, 'tag', type_='foreignkey')
108+
op.drop_column('tag', 'api_id_fk')
109+
op.drop_constraint(None, 'state_version', type_='foreignkey')
110+
op.drop_column('state_version', 'api_id_fk')
111+
op.drop_constraint(None, 'run_queue', type_='foreignkey')
112+
op.drop_column('run_queue', 'api_id_fk')
113+
op.drop_constraint(None, 'run', type_='foreignkey')
114+
op.drop_column('run', 'api_id_fk')
115+
op.drop_constraint(None, 'project', type_='foreignkey')
116+
op.drop_column('project', 'api_id_fk')
117+
op.drop_constraint(None, 'plan', type_='foreignkey')
118+
op.drop_column('plan', 'api_id_fk')
119+
op.drop_constraint(None, 'organisation', type_='foreignkey')
120+
op.drop_column('organisation', 'api_id_fk')
121+
op.drop_constraint(None, 'oauth_token', type_='foreignkey')
122+
op.drop_column('oauth_token', 'api_id_fk')
123+
op.drop_constraint(None, 'oauth_client', type_='foreignkey')
124+
op.drop_column('oauth_client', 'api_id_fk')
125+
op.drop_constraint(None, 'lifecycle', type_='foreignkey')
126+
op.drop_column('lifecycle', 'api_id_fk')
127+
op.drop_constraint(None, 'github_app_oauth_token', type_='foreignkey')
128+
op.drop_column('github_app_oauth_token', 'api_id_fk')
129+
op.drop_constraint(None, 'environment', type_='foreignkey')
130+
op.drop_column('environment', 'api_id_fk')
131+
op.drop_constraint(None, 'configuration_version', type_='foreignkey')
132+
op.drop_column('configuration_version', 'api_id_fk')
133+
op.drop_constraint(None, 'blob', type_='foreignkey')
134+
op.drop_column('blob', 'api_id_fk')
135+
op.drop_constraint(None, 'authorised_repo', type_='foreignkey')
136+
op.drop_column('authorised_repo', 'api_id_fk')
137+
op.drop_constraint(None, 'audit_event', type_='foreignkey')
138+
op.drop_column('audit_event', 'api_id_fk')
139+
op.drop_constraint(None, 'apply', type_='foreignkey')
140+
op.drop_column('apply', 'api_id_fk')
141+
op.add_column('api_id', sa.Column('object_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True))
142+
op.add_column('api_id', sa.Column('object_class', mysql.VARCHAR(length=128), nullable=True))
143+
op.create_index('_object_class_object_id_uc', 'api_id', ['object_class', 'object_id'], unique=False)
144+
op.create_index('_object_class_object_id_in', 'api_id', ['object_class', 'object_id'], unique=False)
145+
op.create_index('_api_id_suffix_index', 'api_id', ['api_id_suffix'], unique=False)
146+
op.drop_constraint(None, 'agent_token', type_='foreignkey')
147+
op.drop_column('agent_token', 'api_id_fk')
148+
op.drop_constraint(None, 'agent_pool', type_='foreignkey')
149+
op.drop_column('agent_pool', 'api_id_fk')
150+
op.drop_constraint(None, 'agent', type_='foreignkey')
151+
op.drop_column('agent', 'api_id_fk')
152+
# ### end Alembic commands ###

terrarun/models/agent.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ class Agent(Base, BaseObject):
3333

3434
__tablename__ = "agent"
3535
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
36+
api_id_fk = sqlalchemy.Column(sqlalchemy.ForeignKey("api_id.id"), nullable=True)
37+
api_id_obj = sqlalchemy.orm.relation("ApiId", foreign_keys=[api_id_fk])
38+
3639
name = sqlalchemy.Column(terrarun.database.Database.GeneralString, default=None)
3740
created_at = sqlalchemy.Column(sqlalchemy.DateTime, default=sqlalchemy.sql.func.now())
3841

terrarun/models/agent_pool.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ class AgentPool(Base, BaseObject):
2424
name = sqlalchemy.Column(terrarun.database.Database.GeneralString, default=None)
2525
created_at = sqlalchemy.Column(sqlalchemy.DateTime, default=sqlalchemy.sql.func.now())
2626

27+
api_id_fk = sqlalchemy.Column(sqlalchemy.ForeignKey("api_id.id"), nullable=True)
28+
api_id_obj = sqlalchemy.orm.relation("ApiId", foreign_keys=[api_id_fk])
29+
2730
organisation_id = sqlalchemy.Column(sqlalchemy.ForeignKey("organisation.id"), nullable=True)
2831
organisation = sqlalchemy.orm.relationship("Organisation")
2932

terrarun/models/agent_token.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ class AgentToken(Base, BaseObject):
2020

2121
__tablename__ = "agent_token"
2222
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
23+
api_id_fk = sqlalchemy.Column(sqlalchemy.ForeignKey("api_id.id"), nullable=True)
24+
api_id_obj = sqlalchemy.orm.relation("ApiId", foreign_keys=[api_id_fk])
25+
2326
description = sqlalchemy.Column(terrarun.database.Database.GeneralString, default=None)
2427
created_at = sqlalchemy.Column(sqlalchemy.DateTime, default=sqlalchemy.sql.func.now())
2528
last_used_at = sqlalchemy.Column(sqlalchemy.DateTime, default=None)

terrarun/models/api_id.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
2+
import secrets
3+
import string
4+
import sqlalchemy
5+
6+
from terrarun.database import Base, Database
7+
import terrarun.database
8+
9+
10+
class ApiId(Base):
11+
"""DB model for associating a random API ID to an object"""
12+
13+
__tablename__ = "api_id"
14+
15+
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
16+
api_id_suffix = sqlalchemy.Column(terrarun.database.Database.GeneralString, unique=True)
17+
18+
@classmethod
19+
def _generate_api_id(cls):
20+
"""Generate random ID for object"""
21+
alphabet = string.ascii_letters + string.digits
22+
return ''.join(secrets.choice(alphabet) for i in range(16))
23+
24+
@classmethod
25+
def get_db_id_from_api_id(cls, target_class, api_id):
26+
"""Get DB ID from api id"""
27+
if len(api_id.split('-')) != 2:
28+
return None
29+
30+
stripped_id = api_id.split('-')[1]
31+
if len(stripped_id) != 16:
32+
return None
33+
34+
session = Database.get_session()
35+
res = session.query(target_class).join(cls).filter(
36+
cls.api_id_suffix==stripped_id
37+
).first()
38+
if not res:
39+
return None
40+
41+
return res.id
42+
43+
@classmethod
44+
def get_api_id(cls, obj):
45+
"""Return api ID for given object"""
46+
if not 'ID_PREFIX' in dir(obj) or not obj.ID_PREFIX:
47+
raise Exception("Object does not have an ID prefix")
48+
49+
session = Database.get_session()
50+
51+
if obj.api_id_obj is None:
52+
api_id_object = cls(
53+
api_id_suffix=cls._generate_api_id()
54+
)
55+
session.add(api_id_object)
56+
obj.api_id_obj = api_id_object
57+
session.add(api_id_object)
58+
session.commit()
59+
60+
return f"{obj.ID_PREFIX}-{obj.api_id_obj.api_id_suffix}"
61+
62+

terrarun/models/apply.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ class Apply(TerraformCommand, Base):
2020
__tablename__ = 'apply'
2121
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
2222

23+
api_id_fk = sqlalchemy.Column(sqlalchemy.ForeignKey("api_id.id"), nullable=True)
24+
api_id_obj = sqlalchemy.orm.relation("ApiId", foreign_keys=[api_id_fk])
25+
2326
plan_id = sqlalchemy.Column(sqlalchemy.ForeignKey("plan.id"), nullable=False)
2427
plan = sqlalchemy.orm.relationship("Plan", back_populates="applies")
2528

terrarun/models/audit_event.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ class AuditEvent(Base, BaseObject):
3030
__tablename__ = 'audit_event'
3131

3232
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
33+
api_id_fk = sqlalchemy.Column(sqlalchemy.ForeignKey("api_id.id"), nullable=True)
34+
api_id_obj = sqlalchemy.orm.relation("ApiId", foreign_keys=[api_id_fk])
35+
3336
timestamp = sqlalchemy.Column(sqlalchemy.DateTime, default=sqlalchemy.sql.func.now())
3437

3538
organisation_id = sqlalchemy.Column(sqlalchemy.ForeignKey("organisation.id"), nullable=False)
@@ -71,7 +74,7 @@ def get_api_details(self):
7174
},
7275
"relationships": {
7376
"user": {
74-
"data": { "id": terrarun.models.user.User.api_id_from_db_id(self.user_id), "type": "users" } if self.user_id else {}
77+
"data": { "id": self.api_id, "type": "users" } if self.user_id else {}
7578
}
7679
},
7780
"type": "audit-events"

0 commit comments

Comments
 (0)