Skip to content

Commit 7f1ff44

Browse files
committed
feat(workspace): migrate controller from owner-based auth to workspace model
1 parent abe3fa3 commit 7f1ff44

64 files changed

Lines changed: 2407 additions & 1148 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

rootfs/api/admin.py

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,21 @@
66

77

88
from django.contrib import admin
9-
from guardian.admin import GuardedModelAdmin
109

11-
from .models import App
1210
from .models import Build
1311
from .models import Config
1412
from .models import Domain
1513
from .models import Key
1614
from .models import Release
1715

1816

19-
class AppAdmin(GuardedModelAdmin):
20-
"""Set presentation options for :class:`~api.models.App` models
21-
in the Django admin.
22-
"""
23-
date_hierarchy = 'created'
24-
list_display = ('id', 'owner')
25-
list_filter = ('owner',)
26-
27-
28-
admin.site.register(App, AppAdmin)
29-
30-
3117
class BuildAdmin(admin.ModelAdmin):
3218
"""Set presentation options for :class:`~api.models.Build` models
3319
in the Django admin.
3420
"""
3521
date_hierarchy = 'created'
36-
list_display = ('created', 'owner', 'app')
37-
list_filter = ('owner', 'app')
22+
list_display = ('created', 'app',)
23+
list_filter = ('app',)
3824

3925

4026
admin.site.register(Build, BuildAdmin)
@@ -45,8 +31,8 @@ class ConfigAdmin(admin.ModelAdmin):
4531
in the Django admin.
4632
"""
4733
date_hierarchy = 'created'
48-
list_display = ('created', 'owner', 'app')
49-
list_filter = ('owner', 'app')
34+
list_display = ('created', 'app',)
35+
list_filter = ('app',)
5036

5137

5238
admin.site.register(Config, ConfigAdmin)
@@ -57,8 +43,8 @@ class DomainAdmin(admin.ModelAdmin):
5743
in the Django admin.
5844
"""
5945
date_hierarchy = 'created'
60-
list_display = ('owner', 'app', 'domain')
61-
list_filter = ('owner', 'app')
46+
list_display = ('app', 'domain')
47+
list_filter = ('app',)
6248

6349

6450
admin.site.register(Domain, DomainAdmin)
@@ -81,9 +67,9 @@ class ReleaseAdmin(admin.ModelAdmin):
8167
in the Django admin.
8268
"""
8369
date_hierarchy = 'created'
84-
list_display = ('created', 'version', 'owner', 'app')
70+
list_display = ('created', 'version', 'app')
8571
list_display_links = ('created', 'version')
86-
list_filter = ('owner', 'app')
72+
list_filter = ('app',)
8773

8874

8975
admin.site.register(Release, ReleaseAdmin)

rootfs/api/consumers.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import ssl
55
import aiohttp
66
import asyncio
7+
from collections import namedtuple
78
from urllib.parse import urljoin
89
from django.conf import settings
910
from django.core.cache import cache
@@ -22,11 +23,12 @@
2223

2324
from .models.app import App
2425
from .models.volume import Volume
25-
from .permissions import has_app_permission
26+
from .permissions import IsAppUser, get_app_status
2627

2728

2829
class AppPermChecker(object):
2930
timeout = 60 * 60
31+
check_permission = IsAppUser().has_object_permission
3032

3133
def __init__(self, scope):
3234
self.scope = scope
@@ -40,8 +42,11 @@ async def has_perm(self):
4042
if permission is None:
4143
try:
4244
app = await App.objects.aget(id=app_id)
43-
permission = await sync_to_async(has_app_permission)(
44-
self.scope["user"], app, "GET")
45+
request = namedtuple("Request", ["user", "method"])(self.scope["user"], "GET")
46+
if await sync_to_async(self.check_permission)(request, None, app):
47+
permission = await sync_to_async(get_app_status)(app)
48+
else:
49+
permission = (False, "permission denied")
4550
if permission[0]:
4651
await cache.aset(key, permission, timeout=self.timeout)
4752
except App.DoesNotExist:

rootfs/api/management/commands/upload_network_usage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def _upload_network_usage(self, start_time, app_map, timestamp):
2727
pod_name = metric['pod']
2828
networks.append({
2929
"app_id": str(app_map[metric['namespace']].uuid),
30-
"owner": app_map[metric['namespace']].owner_id,
30+
"workspace": app_map[metric['namespace']].workspace_id,
3131
"type": "network",
3232
"unit": "bytes",
3333
"name": metric['direction'],

rootfs/api/management/commands/upload_volume_usage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def _upload_volume_usage(self, start_time, app_map, timestamp):
2727
pvc_name = metric['persistentvolumeclaim']
2828
volumes.append({
2929
"app_id": str(app_map[metric['namespace']].uuid),
30-
"owner": app_map[metric['namespace']].owner_id,
30+
"workspace": app_map[metric['namespace']].workspace_id,
3131
"type": "volume",
3232
"unit": "bytes",
3333
"name": metric["storageclass"],

rootfs/api/manager.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,38 +54,42 @@ def delete(self, url, **kwargs):
5454
return self.request('delete', url, **kwargs)
5555

5656

57-
class UserAPI(ManagerAPI):
57+
class WorkspaceAPI(ManagerAPI):
5858

59-
def get_status(self, id):
59+
def get_status(self, workspace_id):
6060
"""
6161
{
6262
"is_active": False,
6363
"message": "The user is in arrears"
6464
}
6565
"""
66-
key = f"user:status:{id}"
66+
key = f"workspace:status:{workspace_id}"
6767
status = cache.get(key)
6868
if not status:
69-
url = f"{settings.WORKFLOW_MANAGER_URL}/users/{id}/status/"
69+
url = f"{settings.WORKFLOW_MANAGER_URL}/workspaces/{workspace_id}/status/"
7070
try:
7171
status = self.get(url=url, timeout=self.timeout).json()
7272
except requests.exceptions.Timeout as ex:
73-
msg = f"request user {id} timeout, skipping verification."
73+
msg = f"request workspace {workspace_id} timeout, skipping verification."
7474
status = {"is_active": True, "message": msg}
7575
logger.error(msg)
7676
logger.exception(ex)
7777
cache.set(key, status, timeout=settings.DRYCC_CACHE_USER_TIME)
7878
return status
7979

8080

81+
class UserAPI(WorkspaceAPI):
82+
"""Backward-compatible alias for legacy call sites."""
83+
84+
8185
class UsageAPI(ManagerAPI):
8286

8387
def post(self, usages: List[Dict[str, str]]):
8488
"""
8589
[
8690
{
8791
"app_id": "test",
88-
"owner": "test",
92+
"workspace": "test",
8993
"name": "web",
9094
"type": "limits",
9195
"unit": "std1.large.c1m1",

rootfs/api/migrations/0013_migration_permissions_and_certificates.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
11
# Generated by Django 4.2.15 on 2024-09-03 03:48
22

33
from django.db import migrations
4-
from guardian.shortcuts import assign_perm, get_users_with_perms, remove_perm
5-
from api.models.app import VIEW_APP_PERMISSION, CHANGE_APP_PERMISSION
64
from api.models.domain import Domain
75
from api.models.certificate import Certificate
86

97

10-
def migration_permission(apps, schema_editor):
11-
App = apps.get_model('api', 'App')
12-
for app in App.objects.all():
13-
for user in get_users_with_perms(app):
14-
remove_perm('use_app', user, app)
15-
assign_perm(VIEW_APP_PERMISSION.codename, user, app)
16-
assign_perm(CHANGE_APP_PERMISSION.codename, user, app)
17-
18-
198
def migration_certificate(apps, schema_editor):
209
for domain in Domain.objects.all():
2110
if domain.certificate:
@@ -38,6 +27,5 @@ class Migration(migrations.Migration):
3827
]
3928

4029
operations = [
41-
migrations.RunPython(migration_permission),
4230
migrations.RunPython(migration_certificate),
4331
]
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Generated by Django 5.2.9 on 2026-03-23 05:42
2+
3+
import api.models.workspace
4+
import django.db.models.deletion
5+
from django.conf import settings
6+
from django.db import migrations, models
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
('api', '0027_rename_lifecycle_post_start_config_lifecycle_and_more'),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name='Workspace',
18+
fields=[
19+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20+
('name', models.SlugField(max_length=150, unique=True, validators=[api.models.workspace.validate_workspace_name], verbose_name='workspace name')),
21+
('email', models.EmailField(max_length=254, verbose_name='email address')),
22+
('created', models.DateTimeField(auto_now_add=True)),
23+
('updated', models.DateTimeField(auto_now=True)),
24+
],
25+
),
26+
migrations.RemoveField(
27+
model_name='app',
28+
name='owner',
29+
),
30+
migrations.RemoveField(
31+
model_name='appsettings',
32+
name='owner',
33+
),
34+
migrations.RemoveField(
35+
model_name='build',
36+
name='owner',
37+
),
38+
migrations.RemoveField(
39+
model_name='certificate',
40+
name='owner',
41+
),
42+
migrations.RemoveField(
43+
model_name='config',
44+
name='owner',
45+
),
46+
migrations.RemoveField(
47+
model_name='domain',
48+
name='owner',
49+
),
50+
migrations.RemoveField(
51+
model_name='gateway',
52+
name='owner',
53+
),
54+
migrations.RemoveField(
55+
model_name='release',
56+
name='owner',
57+
),
58+
migrations.RemoveField(
59+
model_name='route',
60+
name='owner',
61+
),
62+
migrations.RemoveField(
63+
model_name='resource',
64+
name='owner',
65+
),
66+
migrations.RemoveField(
67+
model_name='service',
68+
name='owner',
69+
),
70+
migrations.RemoveField(
71+
model_name='tls',
72+
name='owner',
73+
),
74+
migrations.RemoveField(
75+
model_name='volume',
76+
name='owner',
77+
),
78+
migrations.AlterField(
79+
model_name='blocklist',
80+
name='type',
81+
field=models.PositiveIntegerField(choices=[(1, 'app'), (2, 'workspace')]),
82+
),
83+
migrations.AddField(
84+
model_name='app',
85+
name='workspace',
86+
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='api.workspace'),
87+
preserve_default=False,
88+
),
89+
migrations.CreateModel(
90+
name='WorkspaceInvitation',
91+
fields=[
92+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
93+
('email', models.EmailField(max_length=254, verbose_name='email address')),
94+
('token', models.CharField(max_length=128, unique=True)),
95+
('created', models.DateTimeField(auto_now_add=True)),
96+
('accepted', models.BooleanField(default=False, verbose_name='accepted')),
97+
('inviter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
98+
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.workspace')),
99+
],
100+
options={
101+
'unique_together': {('email', 'workspace')},
102+
},
103+
),
104+
migrations.CreateModel(
105+
name='WorkspaceMember',
106+
fields=[
107+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
108+
('role', models.CharField(choices=[('admin', 'Admin'), ('member', 'Member'), ('viewer', 'Viewer')], max_length=50)),
109+
('alerts', models.BooleanField(default=True)),
110+
('created', models.DateTimeField(auto_now_add=True)),
111+
('updated', models.DateTimeField(auto_now=True)),
112+
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
113+
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.workspace')),
114+
],
115+
options={
116+
'unique_together': {('user', 'workspace')},
117+
},
118+
),
119+
]
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Generated by Django 5.2.9 on 2026-03-26 08:26
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api', '0028_workspace_remove_app_owner_remove_appsettings_owner_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.AddIndex(
14+
model_name='key',
15+
index=models.Index(fields=['fingerprint'], name='api_key_fingerp_4e77c5_idx'),
16+
),
17+
migrations.AddIndex(
18+
model_name='limitplan',
19+
index=models.Index(fields=['spec', 'cpu', 'memory'], name='api_limitpl_spec_id_f95ef2_idx'),
20+
),
21+
migrations.AddIndex(
22+
model_name='release',
23+
index=models.Index(fields=['app', 'failed', 'state'], name='api_release_app_id_9849f2_idx'),
24+
),
25+
]

0 commit comments

Comments
 (0)