dataflow-core 2.1.14rc1__py3-none-any.whl → 2.1.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. authenticator/dataflowairflowauthenticator.py +3 -9
  2. authenticator/dataflowhubauthenticator.py +29 -44
  3. dataflow/dataflow.py +45 -69
  4. dataflow/environment.py +20 -11
  5. dataflow/models/__init__.py +5 -3
  6. dataflow/models/app_types.py +8 -3
  7. dataflow/models/connection.py +5 -4
  8. dataflow/models/dataflow_zone.py +2 -3
  9. dataflow/models/environment.py +64 -21
  10. dataflow/models/git_ssh.py +2 -1
  11. dataflow/models/org_associations.py +38 -0
  12. dataflow/models/organization.py +78 -0
  13. dataflow/models/pinned_projects.py +2 -2
  14. dataflow/models/project_details.py +9 -6
  15. dataflow/models/recent_project_studio.py +1 -1
  16. dataflow/models/role.py +12 -6
  17. dataflow/models/role_server.py +2 -5
  18. dataflow/models/role_zone.py +8 -3
  19. dataflow/models/server_config.py +9 -5
  20. dataflow/models/team.py +10 -4
  21. dataflow/models/user.py +49 -12
  22. dataflow/models/user_team.py +1 -4
  23. dataflow/models/variables.py +6 -4
  24. dataflow/secrets_manager/factory.py +14 -8
  25. dataflow/secrets_manager/providers/gcp_manager.py +332 -0
  26. dataflow/secrets_manager/service.py +11 -9
  27. dataflow/utils/get_current_user.py +51 -28
  28. {dataflow_core-2.1.14rc1.dist-info → dataflow_core-2.1.15.dist-info}/METADATA +4 -1
  29. dataflow_core-2.1.15.dist-info/RECORD +63 -0
  30. {dataflow_core-2.1.14rc1.dist-info → dataflow_core-2.1.15.dist-info}/top_level.txt +1 -0
  31. dfmigration/__init__.py +0 -0
  32. dfmigration/env.py +45 -0
  33. dfmigration/versions/001_initial_baseline_migration.py +20 -0
  34. dfmigration/versions/__init__.py +0 -0
  35. dataflow/models/user_environment.py +0 -16
  36. dataflow_core-2.1.14rc1.dist-info/RECORD +0 -57
  37. {dataflow_core-2.1.14rc1.dist-info → dataflow_core-2.1.15.dist-info}/WHEEL +0 -0
  38. {dataflow_core-2.1.14rc1.dist-info → dataflow_core-2.1.15.dist-info}/entry_points.txt +0 -0
@@ -1,5 +1,8 @@
1
- from sqlalchemy import Column, Integer, String, Boolean, Text, ForeignKey, DateTime
2
- from sqlalchemy.orm import relationship
1
+ from sqlalchemy import (
2
+ Column, Integer, String, Boolean, Text,
3
+ ForeignKey, DateTime, UniqueConstraint, CheckConstraint
4
+ )
5
+ from sqlalchemy.orm import relationship, Session
3
6
  from sqlalchemy.sql import func
4
7
  from datetime import datetime, timezone
5
8
  from dataflow.db import Base
@@ -11,14 +14,14 @@ class EnvironmentAttributes(Base):
11
14
  """
12
15
  __abstract__ = True
13
16
 
14
- name = Column(String)
17
+ name = Column(String, nullable=False)
15
18
  url = Column(String)
16
- enabled = Column(Boolean, default=True)
17
- version = Column(String, default=0)
18
- is_latest = Column(Boolean, default=True)
19
+ enabled = Column(Boolean, default=True, server_default='true')
20
+ version = Column(String, default=0, server_default='0')
21
+ is_latest = Column(Boolean, default=True, server_default='true')
19
22
  base_env_id = Column(Integer, default=None)
20
23
  short_name = Column(String(5))
21
- status = Column(String, default="Saved")
24
+ status = Column(String, default="Saved", server_default="Saved")
22
25
  icon = Column(String)
23
26
  py_version = Column(String)
24
27
  r_version = Column(String)
@@ -27,16 +30,15 @@ class EnvironmentAttributes(Base):
27
30
  r_requirements = Column(Text)
28
31
  created_date = Column(DateTime, server_default=func.now())
29
32
  created_by = Column(String)
30
-
31
-
33
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id'))
32
34
 
33
35
  class Environment(EnvironmentAttributes):
34
36
  __tablename__ = 'ENVIRONMENT'
35
-
37
+ __table_args__ = (UniqueConstraint('short_name', 'org_id', name='_env_short_name_org_uc'),)
36
38
  id = Column(Integer, primary_key=True, autoincrement=True)
37
- short_name = Column(String(5), unique=True)
38
39
 
39
- # Relationship with ArchivedEnvironment
40
+ # Relationships
41
+ organization = relationship("Organization", back_populates="environments")
40
42
  archived_versions = relationship("ArchivedEnvironment", back_populates="original_environment")
41
43
 
42
44
  class ArchivedEnvironment(EnvironmentAttributes):
@@ -44,23 +46,22 @@ class ArchivedEnvironment(EnvironmentAttributes):
44
46
 
45
47
  id = Column(Integer, primary_key=True, autoincrement=True)
46
48
  original_env_id = Column(Integer, ForeignKey('ENVIRONMENT.id', ondelete='CASCADE'))
47
- is_latest = Column(Boolean, default=False)
48
49
 
49
50
  # Relationship with Environment
50
51
  original_environment = relationship("Environment", back_populates="archived_versions")
51
52
 
52
-
53
-
54
53
  class JobLogs(Base):
55
54
  __tablename__ = "JOB_LOG"
55
+ __table_args__ = (UniqueConstraint('log_file_name', 'org_id', name='_job_log_file_org_uc'),)
56
56
 
57
57
  id = Column(Integer, primary_key=True, index=True)
58
- created_at = Column(DateTime, default=datetime.now)
58
+ created_at = Column(DateTime, default=datetime.now, server_default=func.now())
59
59
  completed_at = Column(DateTime, nullable=True)
60
- log_file_name = Column(String, unique=True, nullable=False)
60
+ log_file_name = Column(String, nullable=False)
61
61
  log_file_location = Column(String, nullable=False)
62
62
  status = Column(String)
63
63
  created_by = Column(String)
64
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete='CASCADE'))
64
65
 
65
66
 
66
67
  class LocalEnvironment(Base):
@@ -69,14 +70,56 @@ class LocalEnvironment(Base):
69
70
  id = Column(Integer, primary_key=True, autoincrement=True)
70
71
  name = Column(String, nullable=False, index=True)
71
72
  user_name = Column(String, ForeignKey('USER.user_name', ondelete='CASCADE'), nullable=False, index=True)
73
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete='CASCADE'), nullable=False, index=True)
72
74
  py_version = Column(String)
73
75
  pip_libraries = Column(Text)
74
76
  conda_libraries = Column(Text)
75
- status = Column(String, default="Created")
76
- cloned_from = Column(String, ForeignKey('ENVIRONMENT.short_name', ondelete='SET NULL'), nullable=True)
77
+ status = Column(String, default="Created", server_default="Created")
78
+ cloned_from = Column(String, nullable=True)
77
79
  updated_at = Column(DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
78
- need_refresh = Column(Boolean, default=False)
80
+ need_refresh = Column(Boolean, default=False, server_default='false')
79
81
 
80
82
  class EnvType(str, Enum):
81
83
  dataflow = "dataflow"
82
- local = "local"
84
+ local = "local"
85
+
86
+ class PipSource(Base):
87
+ __tablename__ = "PIP_SOURCE"
88
+
89
+ id = Column(Integer, primary_key=True, autoincrement=True)
90
+
91
+ org_id = Column(Integer, ForeignKey("ORGANIZATION.id", ondelete="CASCADE"), nullable=False, index=True)
92
+ user_name = Column(String, ForeignKey("USER.user_name", ondelete="CASCADE"), nullable=True, index=True)
93
+
94
+ name = Column(String, nullable=False)
95
+ url = Column(String, nullable=False)
96
+ is_index = Column(Boolean, default=False, nullable=False, server_default='false')
97
+
98
+ created_at = Column(DateTime, default=datetime.now(timezone.utc), nullable=False)
99
+ updated_at = Column(DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc), nullable=False)
100
+
101
+ __table_args__ = (
102
+ UniqueConstraint("org_id", "name", "user_name", name="uq_pip_source_per_user_org"),
103
+ CheckConstraint("NOT (is_index = TRUE AND user_name IS NOT NULL)", name="check_no_user_index_url"),
104
+ )
105
+
106
+ @classmethod
107
+ def get_org_sources(cls, session: Session, org_id: int):
108
+ """
109
+ Returns all sources for the given org (org-level).
110
+ """
111
+ return session.query(cls).filter(
112
+ cls.org_id == org_id,
113
+ cls.user_name == None
114
+ ).all()
115
+
116
+ @classmethod
117
+ def get_user_sources(cls, session: Session, org_id: int, user_name: str):
118
+ """
119
+ Returns merged sources for a user in an org (org-level + user-level personal sources).
120
+ """
121
+ return session.query(cls).filter(
122
+ cls.org_id == org_id,
123
+ ((cls.user_name == None) | (cls.user_name == user_name))
124
+ ).all()
125
+
@@ -8,11 +8,12 @@ class GitSSH(Base):
8
8
 
9
9
  id = Column(Integer, primary_key=True, index=True, autoincrement=True)
10
10
  user_name = Column(String, ForeignKey('USER.user_name', ondelete="CASCADE"), nullable=False)
11
+ org_id = Column(Integer, ForeignKey("ORGANIZATION.id"), index=True, nullable=False)
11
12
  description = Column(String)
12
13
  key_name = Column(String, nullable=False)
13
14
  created_date = Column(DateTime, server_default=func.now(), nullable=False)
14
15
  last_used_date = Column(DateTime)
15
16
 
16
17
  __table_args__ = (
17
- UniqueConstraint(user_name, key_name, name='user_name_key_name_unique'),
18
+ UniqueConstraint(user_name, key_name, org_id, name='user_name_key_name_org_id_unique'),
18
19
  )
@@ -0,0 +1,38 @@
1
+ from sqlalchemy import Column , Integer, String, Boolean, ForeignKey, UniqueConstraint, Enum
2
+ from sqlalchemy.orm import relationship
3
+ from dataflow.db import Base
4
+ import enum
5
+ from dataflow.models.environment import EnvType
6
+
7
+ class OrganizationUser(Base):
8
+ """
9
+ Association Table between USER, ROLE, and ORGANIZATION
10
+ """
11
+ __tablename__ = "ORGANIZATION_USER"
12
+ __table_args__ = (UniqueConstraint('org_id', 'user_id', name='uq_org_user'),)
13
+
14
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete="CASCADE"), primary_key=True, nullable=False)
15
+ user_id = Column(Integer, ForeignKey('USER.user_id', ondelete="CASCADE"), primary_key=True, nullable=False)
16
+ role_id = Column(Integer, ForeignKey('ROLE.id', ondelete="SET NULL"), nullable=False)
17
+ active_env_short_name = Column(String, nullable=True)
18
+ active_env_type = Column(Enum(EnvType), nullable=True)
19
+ active_server_id = Column(Integer, ForeignKey('CUSTOM_SERVER.id', ondelete="SET NULL"))
20
+ show_server_page = Column(Boolean, default = True, server_default='true')
21
+ monthly_allocation = Column(Integer, nullable=True, default=0, server_default='0')
22
+
23
+ # Relationships
24
+ user = relationship("User", back_populates="org_user_assocs")
25
+ role = relationship("Role", back_populates="org_user_assocs")
26
+ organization = relationship("Organization", back_populates="org_user_assocs")
27
+
28
+ class OrganizationServer(Base):
29
+ __tablename__ = "ORGANIZATION_SERVER"
30
+
31
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete="CASCADE"), primary_key=True, nullable=False)
32
+ server_id = Column(Integer, ForeignKey('SERVER_CONFIG.id', ondelete="CASCADE"), primary_key=True, nullable=False)
33
+
34
+ class OrganizationAppType(Base):
35
+ __tablename__ = "ORGANIZATION_APP_TYPE"
36
+
37
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete="CASCADE"), primary_key=True, nullable=False)
38
+ app_type_id = Column(Integer, ForeignKey('APP_TYPE.id', ondelete="CASCADE"), primary_key=True, nullable=False)
@@ -0,0 +1,78 @@
1
+ from sqlalchemy import (
2
+ Column, Integer, String, Enum, DateTime, ForeignKey, Index, text
3
+ )
4
+ import uuid, enum
5
+ from sqlalchemy.dialects.postgresql import JSONB, UUID
6
+ from sqlalchemy.sql import func
7
+ from sqlalchemy.orm import relationship
8
+ from datetime import datetime
9
+ from dataflow.db import Base
10
+
11
+
12
+ class Organization(Base):
13
+ """
14
+ Organization model for the database.
15
+ """
16
+ __tablename__ = "ORGANIZATION"
17
+
18
+ id = Column(Integer, primary_key=True, autoincrement=True)
19
+ uid = Column(UUID(as_uuid=True), default=uuid.uuid4, nullable=False, unique=True, server_default=text("gen_random_uuid()"))
20
+ name = Column(String(255), nullable=False, unique=True)
21
+ invite_code = Column(String(64), nullable=False, unique=True)
22
+ email_domain = Column(String(255), nullable=False, unique=True)
23
+ spark_enabled_zones = Column(JSONB, default=func.json([]), server_default=text("'[]'::jsonb")) # List of zone IDs where Spark is enabled
24
+ created_at = Column(DateTime, default=datetime.utcnow, server_default=func.now())
25
+
26
+ # Association object link
27
+ org_user_assocs = relationship("OrganizationUser", back_populates="organization", cascade="all, delete-orphan")
28
+ custom_servers = relationship("CustomServerConfig")
29
+ onboarding_requests = relationship("UserOnboarding", back_populates="organization", cascade="all, delete-orphan")
30
+ servers = relationship("ServerConfig", secondary="ORGANIZATION_SERVER", back_populates="organizations")
31
+ apps = relationship("AppType", secondary="ORGANIZATION_APP_TYPE", back_populates="organizations")
32
+ roles = relationship("Role", cascade="all, delete-orphan")
33
+ environments = relationship("Environment", back_populates="organization")
34
+
35
+ class OnboardingStatus(enum.Enum):
36
+ pending = 'pending'
37
+ rejected = 'rejected'
38
+ accepted = 'accepted'
39
+
40
+ class OrganizationOnboarding(Base):
41
+ __tablename__ = 'ORGANIZATION_ONBOARDING'
42
+ # This prevents an org from having more than one active ('pending' or 'accepted') application
43
+ # while allowing multiple 'rejected' entries.
44
+ __table_args__ = (
45
+ Index(
46
+ 'idx_pending_org_application',
47
+ 'name',
48
+ unique=True,
49
+ postgresql_where=Column('status').in_([
50
+ OnboardingStatus.pending.value,
51
+ OnboardingStatus.accepted.value
52
+ ])
53
+ ),
54
+ )
55
+
56
+ id = Column(Integer, primary_key=True, autoincrement=True)
57
+
58
+ name = Column(String(255), nullable=False)
59
+ age = Column(Integer, nullable=True)
60
+ domain = Column(String(255), nullable=True)
61
+ no_of_employees = Column(String(50), nullable=True)
62
+ address = Column(String(500), nullable=True)
63
+
64
+ admin_first_name = Column(String(100), nullable=False)
65
+ admin_last_name = Column(String(100), nullable=True)
66
+ admin_designation = Column(String(100), nullable=False)
67
+ admin_email = Column(String(255), nullable=False, unique=True)
68
+ admin_username = Column(String(100), nullable=False, unique=True)
69
+ admin_password = Column(String(255), nullable=False)
70
+
71
+ discovery_source = Column(String(255), nullable=True)
72
+ additional_info = Column(String(1000), nullable=True)
73
+ size_of_data = Column(String(100), nullable=True)
74
+
75
+ user_id = Column(Integer, ForeignKey('USER.user_id'), nullable=False)
76
+ status = Column(Enum(OnboardingStatus), default=OnboardingStatus.pending, nullable=False)
77
+
78
+ user = relationship("User", back_populates="organization_onboarding")
@@ -1,4 +1,4 @@
1
- from sqlalchemy import Column, Integer, ForeignKey, DateTime, UniqueConstraint
1
+ from sqlalchemy import Column, Integer, ForeignKey, DateTime, UniqueConstraint, func
2
2
  from dataflow.db import Base
3
3
  from datetime import datetime
4
4
 
@@ -8,7 +8,7 @@ class PinnedProject(Base):
8
8
  id = Column(Integer, primary_key=True)
9
9
  user_id = Column(Integer, ForeignKey('USER.user_id', ondelete="CASCADE"), index=True)
10
10
  project_id = Column(Integer, ForeignKey('PROJECT_DETAIL.project_id', ondelete="CASCADE"), index=True)
11
- pinned_at = Column(DateTime, default=datetime.utcnow)
11
+ pinned_at = Column(DateTime, default=datetime.utcnow, server_default=func.now())
12
12
 
13
13
  __table_args__ = (
14
14
  UniqueConstraint("user_id", "project_id", name="uix_user_project"),
@@ -1,23 +1,26 @@
1
- from sqlalchemy import Column, String, Enum, DateTime, Integer, func, ForeignKey
1
+ from sqlalchemy import Column, String, Enum, DateTime, Integer, func, ForeignKey, UniqueConstraint
2
2
  from sqlalchemy.orm import relationship
3
3
  from dataflow.db import Base
4
4
 
5
5
  class ProjectDetails(Base):
6
6
  __tablename__ = "PROJECT_DETAIL"
7
+ __table_args__ = (UniqueConstraint('org_id', 'slug', name='uq_project_org_slug'),)
7
8
 
8
9
  project_id = Column(Integer, primary_key=True, autoincrement=True)
9
10
  project_name = Column(String, nullable=False)
10
11
  git_url = Column(String)
11
12
  git_branch = Column(String, nullable=True)
12
13
  git_folder = Column(String, nullable=True)
13
- type = Column(String, ForeignKey('RUNTIME_APP_TYPE.name', ondelete="CASCADE"), nullable=False)
14
- slug = Column(String, nullable=False, unique=True)
14
+ type = Column(String, ForeignKey('APP_TYPE.name'), nullable=False)
15
+ slug = Column(String, nullable=False)
15
16
  runtime = Column(String, nullable=False)
16
- py_env = Column(String, nullable=True)
17
+ py_env = Column(Integer, nullable=True)
17
18
  launch_url = Column(String, nullable=True)
18
- status = Column(Enum("pending", "created" ,"deployed", "stopped", "failed", name="deployment_status"), default="created")
19
+ status = Column(Enum("pending", "created" ,"deployed", "stopped", "failed", name="deployment_status"), default="created", server_default="created")
19
20
  last_deployed = Column(DateTime, nullable=True)
20
21
  created_at = Column(DateTime, nullable=False, server_default=func.now())
21
22
  created_by = Column(String, nullable=False)
23
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete='CASCADE'), nullable=False)
24
+ airflow_config_file = Column(String, nullable=True)
22
25
 
23
- app_type = relationship("AppType")
26
+ app_type = relationship("AppType")
@@ -12,7 +12,7 @@ class RecentProjectStudio(Base):
12
12
  project_name = Column(String, nullable=False)
13
13
  project_path = Column(String, nullable=False)
14
14
  last_opened_date = Column(DateTime, server_default=func.now(), nullable=False)
15
- remember = Column(Boolean, default=False)
15
+ remember = Column(Boolean, default=False, server_default='false')
16
16
 
17
17
  __table_args__ = (
18
18
  UniqueConstraint(user_name, project_path, app_name, name='user_name_project_path_app_name_unique'),
dataflow/models/role.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """models.py"""
2
- from sqlalchemy import Column, Integer, String, Enum
2
+ from sqlalchemy import Column, Integer, String, Enum, ForeignKey, UniqueConstraint
3
3
  from sqlalchemy.orm import relationship
4
4
  from dataflow.db import Base
5
5
  import enum
@@ -11,19 +11,25 @@ class BaseRoleField(enum.Enum):
11
11
 
12
12
  class Role(Base):
13
13
  """
14
- Table Role
14
+ Table ROLE
15
15
  """
16
16
 
17
17
  __tablename__='ROLE'
18
+ __table_args__ = (
19
+ UniqueConstraint('name', 'org_id', name='uq_role_name_org'),
20
+ )
18
21
 
19
22
  id = Column(Integer, primary_key=True, index=True, autoincrement=True, nullable=False)
20
- name = Column(String, unique=True, nullable=False)
23
+ name = Column(String, nullable=False)
24
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id'))
21
25
  description = Column(String, nullable=True)
22
- base_role = Column(Enum(BaseRoleField), nullable=False, default=BaseRoleField.user)
26
+ base_role = Column(Enum(BaseRoleField), nullable=False, default=BaseRoleField.user, server_default=BaseRoleField.user.value)
23
27
 
24
- users = relationship("User", back_populates="role_details", cascade="all, delete-orphan")
25
- role_server_assocs = relationship("RoleServer", back_populates="role")
28
+ # Relationships
26
29
  role_zone_assocs = relationship("RoleZone", back_populates="role")
30
+ org_user_assocs = relationship("OrganizationUser", back_populates="role", cascade="all, delete-orphan")
31
+ organization = relationship("Organization", back_populates="roles")
32
+ servers = relationship("CustomServerConfig", secondary="ROLE_SERVER", back_populates="roles")
27
33
 
28
34
  def __repr__(self):
29
35
  return f"<Role(id={self.id}, name='{self.name}', base_role='{self.base_role}')>"
@@ -5,10 +5,7 @@ from dataflow.db import Base
5
5
 
6
6
  class RoleServer(Base):
7
7
  __tablename__ = 'ROLE_SERVER'
8
- __table_args__ = (UniqueConstraint('role_id', 'server_id', name='_role_server_uc'),)
8
+ __table_args__ = (UniqueConstraint('role_id', 'custom_server_id', name='_role_server_uc'),)
9
9
 
10
10
  role_id = Column(Integer, ForeignKey('ROLE.id', ondelete="CASCADE"), nullable=False, primary_key=True)
11
- server_id = Column(Integer, ForeignKey('CUSTOM_SERVER.id', ondelete="CASCADE"), nullable=False, primary_key=True)
12
-
13
- role = relationship("Role", back_populates="role_server_assocs")
14
- server = relationship("CustomServerConfig", back_populates="role_server_assocs")
11
+ custom_server_id = Column(Integer, ForeignKey('CUSTOM_SERVER.id', ondelete="CASCADE"), nullable=False, primary_key=True)
@@ -1,14 +1,19 @@
1
1
  from typing import Dict, List, Optional
2
- from sqlalchemy import Column, Integer, ForeignKey, UniqueConstraint, Boolean
2
+ from sqlalchemy import Column, Integer, ForeignKey, UniqueConstraint, Boolean, Index
3
3
  from sqlalchemy.orm import relationship
4
4
  from dataflow.db import Base
5
5
 
6
6
  class RoleZone(Base):
7
7
  __tablename__ = 'ROLE_ZONE'
8
-
8
+
9
9
  role_id = Column(Integer, ForeignKey('ROLE.id', ondelete="CASCADE"), primary_key=True)
10
10
  zone_id = Column(Integer, ForeignKey('DATAFLOW_ZONE.id', ondelete="CASCADE"), primary_key=True)
11
- is_default = Column(Boolean, default=False, nullable=False)
11
+ is_default = Column(Boolean, default=False, nullable=False, server_default='false')
12
+
13
+ __table_args__ = (
14
+ Index('idx_role_runtime_default', 'role_id', unique=True,
15
+ postgresql_where=is_default.is_(True)),
16
+ )
12
17
 
13
18
  role = relationship("Role", back_populates="role_zone_assocs")
14
19
  zone = relationship("DataflowZone", back_populates="role_zone_assocs")
@@ -1,4 +1,4 @@
1
- from sqlalchemy import Column, Integer, String, Boolean, Text, ForeignKey
1
+ from sqlalchemy import Column, Integer, String, Boolean, Text, ForeignKey, text
2
2
  from sqlalchemy.dialects.postgresql import JSONB
3
3
  from sqlalchemy.sql import func
4
4
  from sqlalchemy.orm import relationship
@@ -14,20 +14,24 @@ class ServerConfig(Base):
14
14
  ram = Column(String, nullable=False)
15
15
  cpu = Column(String, nullable=False)
16
16
  gpu = Column(String)
17
- default = Column(Boolean, default=False)
18
- tags = Column(JSONB, default=func.json([]))
17
+ default = Column(Boolean, default=False, server_default='false')
18
+ tags = Column(JSONB, default=func.json([]), server_default=text("'[]'::jsonb"))
19
19
  description = Column(Text, nullable=True)
20
- kubespawner_override = Column(JSONB, default=func.json({}))
20
+ kubespawner_override = Column(JSONB, default=func.json({}), server_default=text("'{}'::jsonb"))
21
21
 
22
+ # Relationships
23
+ organizations = relationship("Organization", secondary="ORGANIZATION_SERVER", back_populates="servers")
22
24
 
23
25
  class CustomServerConfig(Base):
24
26
  __tablename__ = "CUSTOM_SERVER"
25
27
 
26
28
  id = Column(Integer, primary_key=True, autoincrement=True)
27
29
  base_server_id = Column(Integer, ForeignKey(ServerConfig.id, ondelete="CASCADE"), nullable=False)
30
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete="CASCADE"), nullable=False)
28
31
  display_name = Column(String, nullable=False, unique=True, index=True)
29
32
  description = Column(Text, nullable=True)
30
33
 
31
34
  # Relationship to the server_config table
32
35
  server_config = relationship(ServerConfig)
33
- role_server_assocs = relationship("RoleServer", back_populates="server", cascade="all, delete-orphan")
36
+ organization = relationship("Organization", back_populates="custom_servers")
37
+ roles = relationship("Role", secondary="ROLE_SERVER", back_populates="servers")
dataflow/models/team.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """models.py"""
2
- from sqlalchemy import Column, Integer, String
2
+ from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint
3
3
  from sqlalchemy.orm import relationship
4
4
  from dataflow.db import Base
5
5
 
@@ -9,9 +9,15 @@ class Team(Base):
9
9
  """
10
10
 
11
11
  __tablename__='TEAM'
12
+ __table_args__ = (
13
+ UniqueConstraint('team_name', 'org_id', name='uc_team_name_org_id'),
14
+ )
12
15
 
13
16
  team_id = Column(Integer, primary_key=True, index=True, autoincrement=True, nullable=False)
14
- team_name = Column(String, unique=True, nullable=False)
17
+ team_name = Column(String, nullable=False)
18
+ org_id = Column(Integer, ForeignKey('ORGANIZATION.id', ondelete="CASCADE"), nullable=False)
15
19
  description = Column(String, nullable=True)
16
-
17
- user_team_assocs = relationship("UserTeam", back_populates="team")
20
+
21
+ # relationships
22
+ users = relationship("User", secondary="USER_TEAM", back_populates="teams")
23
+ organization = relationship("Organization")
dataflow/models/user.py CHANGED
@@ -1,31 +1,68 @@
1
1
  """models.py"""
2
- from sqlalchemy import Column, Integer, String, Boolean, LargeBinary, ForeignKey
2
+ from sqlalchemy import Column, Integer, String, Boolean, LargeBinary, Enum, ForeignKey, DateTime, func
3
+ from sqlalchemy.dialects.postgresql import ENUM
4
+ from sqlalchemy import Index
3
5
  from sqlalchemy.orm import relationship
4
6
  from dataflow.db import Base
7
+ import enum
5
8
 
6
9
  class User(Base):
7
10
  """
8
11
  Table USER
9
12
  """
10
-
11
- __tablename__='USER'
13
+ __tablename__ = 'USER'
12
14
 
13
15
  user_id = Column(Integer, primary_key=True, index=True, autoincrement=True, nullable=False)
14
16
  user_name = Column(String, unique=True, nullable=False)
15
17
  first_name = Column(String)
16
18
  last_name = Column(String)
17
19
  email = Column(String, unique=True)
18
- role_id = Column(Integer, ForeignKey('ROLE.id'), nullable=False)
19
20
  image = Column(LargeBinary)
20
21
  image_url = Column(String, nullable=True)
21
- active = Column(Boolean, nullable=False, default=True)
22
+ active = Column(Boolean, nullable=False, default=True, server_default='true')
22
23
  password = Column(String, nullable=False)
23
- active_env = Column(String)
24
- active_env_type = Column(String, nullable=True)
25
- current_server = Column(String)
26
- show_server_page = Column(Boolean, default = True)
27
- monthly_allocation = Column(Integer, nullable=True, default=0)
24
+ active_org_id = Column(Integer, ForeignKey('ORGANIZATION.id'))
25
+
26
+ # Relationships
27
+ org_user_assocs = relationship("OrganizationUser", back_populates="user", cascade="all, delete-orphan")
28
+ teams = relationship("Team", secondary="USER_TEAM", back_populates="users")
29
+ onboarding_requests = relationship("UserOnboarding", back_populates="user", cascade="all, delete-orphan")
30
+ organization_onboarding = relationship("OrganizationOnboarding", back_populates="user", cascade="all, delete-orphan")
31
+
32
+
33
+ class OnboardingStatus(enum.Enum):
34
+ pending = 'pending'
35
+ rejected = 'rejected'
36
+ accepted = 'accepted'
37
+
38
+ class UserOnboarding(Base):
39
+ """
40
+ SQLAlchemy model for the "USER_ONBOARDING" table.
41
+ This table stores user applications to organizations.
42
+ """
43
+ __tablename__ = "USER_ONBOARDING"
44
+ # This prevents a user from having more than one active ('pending' or 'accepted') application
45
+ # for a given organization, while allowing multiple 'rejected' entries.
46
+ __table_args__ = (
47
+ Index(
48
+ 'idx_pending_user_org_application',
49
+ 'user_id',
50
+ 'org_id',
51
+ unique=True,
52
+ postgresql_where=Column('status').in_([
53
+ OnboardingStatus.pending.value,
54
+ OnboardingStatus.accepted.value
55
+ ])
56
+ ),
57
+ )
28
58
 
29
- role_details = relationship("Role")
59
+ id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
60
+ user_id = Column(Integer, ForeignKey("USER.user_id", ondelete="CASCADE"), nullable=False)
61
+ org_id = Column(Integer, ForeignKey("ORGANIZATION.id", ondelete="CASCADE"), nullable=False)
62
+ status = Column(Enum(OnboardingStatus, name='onboarding_status'), nullable=False, default=OnboardingStatus.pending.value, server_default='pending')
63
+ created_at = Column(DateTime, default=func.now(), nullable=False, server_default=func.now())
64
+ updated_at = Column(DateTime, default=func.now(), onupdate=func.now(), nullable=False, server_default=func.now())
30
65
 
31
- user_team_assocs = relationship("UserTeam", back_populates="user")
66
+ # Relationships
67
+ user = relationship("User", back_populates="onboarding_requests")
68
+ organization = relationship("Organization", back_populates="onboarding_requests")
@@ -8,7 +8,4 @@ class UserTeam(Base):
8
8
  __table_args__ = (UniqueConstraint('user_id', 'team_id', name='_user_team_uc'),)
9
9
 
10
10
  user_id = Column(Integer, ForeignKey('USER.user_id', ondelete="CASCADE"), nullable=False, primary_key=True)
11
- team_id = Column(Integer, ForeignKey('TEAM.team_id', ondelete="CASCADE"), nullable=False, primary_key=True)
12
-
13
- user = relationship("User", back_populates="user_team_assocs")
14
- team = relationship("Team", back_populates="user_team_assocs")
11
+ team_id = Column(Integer, ForeignKey('TEAM.team_id', ondelete="CASCADE"), nullable=False, primary_key=True)
@@ -14,7 +14,8 @@ class Variable(Base):
14
14
  __tablename__ = 'VARIABLE'
15
15
 
16
16
  id = Column(Integer, primary_key=True, index=True, autoincrement=True, nullable=False)
17
- key = Column(String, nullable=False)
17
+ key = Column(String, index=True, nullable=False)
18
+ org_id = Column(Integer, ForeignKey("ORGANIZATION.id"), index=True, nullable=False)
18
19
  value = Column(Text, nullable=False)
19
20
  type = Column(String, nullable=False)
20
21
  description = Column(Text, nullable=True)
@@ -24,11 +25,12 @@ class Variable(Base):
24
25
  created_at = Column(DateTime, server_default=func.now())
25
26
  updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
26
27
  created_by = Column(String, ForeignKey('USER.user_name'), nullable=True)
27
- is_active = Column(Boolean, default=True, nullable=False)
28
+ is_active = Column(Boolean, default=True, nullable=False, server_default='true')
28
29
  datatype = Column(Enum(DataType, name="data_type"), nullable=False)
29
- set_as_env = Column(Boolean, default=False, nullable=False)
30
+ set_as_env = Column(Boolean, default=False, nullable=False, server_default='false')
31
+
30
32
 
31
33
  __table_args__ = (
32
34
  CheckConstraint(type.in_(['variable', 'secret']), name='check_variable_type'),
33
- UniqueConstraint('key', 'runtime', 'slug', 'created_by', name='unique_key'),
35
+ UniqueConstraint('key', 'org_id', 'runtime', 'slug', 'created_by', name='unique_key'),
34
36
  )
@@ -3,6 +3,7 @@ import os
3
3
  from .interface import SecretManager
4
4
  from .providers.aws_manager import AWSSecretsManager
5
5
  from .providers.azure_manager import AzureKeyVault
6
+ from .providers.gcp_manager import GCPSecretsManager
6
7
  from ..configuration import ConfigurationManager
7
8
 
8
9
  # A custom exception for clear error messages
@@ -17,10 +18,6 @@ def get_secret_manager() -> SecretManager:
17
18
  to determine which cloud provider's secret manager to instantiate.
18
19
  """
19
20
  try:
20
- # dataflow_config = None
21
- # if os.getenv('HOSTNAME'):
22
- # dataflow_config = ConfigurationManager('/dataflow/app/auth_config/dataflow_auth.cfg')
23
- # else:
24
21
  dataflow_config = ConfigurationManager('/dataflow/app/config/dataflow.cfg')
25
22
  except Exception as e:
26
23
  raise SecretProviderError(
@@ -49,11 +46,20 @@ def get_secret_manager() -> SecretManager:
49
46
  )
50
47
  return AzureKeyVault(vault_url=vault_url)
51
48
 
52
- # You can easily add more providers here in the future
53
- # elif provider == "gcp":
54
- # return GCPSecretManager()
49
+ elif provider == "gcp":
50
+ project_id = dataflow_config.get_config_value('cloudProvider', 'gcp_project_id')
51
+ region = dataflow_config.get_config_value('cloudProvider', 'gcp_region')
52
+ if not project_id:
53
+ raise SecretProviderError(
54
+ "GCP_PROJECT_ID must be set when using the GCP provider."
55
+ )
56
+ if not region:
57
+ raise SecretProviderError(
58
+ "GCP_REGION must be set when using the GCP provider."
59
+ )
60
+ return GCPSecretsManager(project_id=project_id, region=region)
55
61
 
56
62
  else:
57
63
  raise SecretProviderError(
58
- f"Unsupported secret provider: '{provider}'. Supported providers are: aws, azure."
64
+ f"Unsupported secret provider: '{provider}'. Supported providers are: aws, azure and gcp"
59
65
  )