|
7 | 7 | import time |
8 | 8 | from simplejson.errors import JSONDecodeError |
9 | 9 |
|
| 10 | +import kubernetes |
| 11 | +import kubernetes.dynamic.exceptions as kexc |
| 12 | + |
10 | 13 | from coldfront_plugin_cloud import attributes, base, utils |
11 | 14 |
|
| 15 | +API_PROJECT = "project.openshift.io/v1" |
| 16 | +API_USER = "user.openshift.io/v1" |
| 17 | +API_RBAC = "rbac.authorization.k8s.io/v1" |
| 18 | +API_CORE = "v1" |
| 19 | +IGNORED_ATTRIBUTES = [ |
| 20 | + "resourceVersion", |
| 21 | + "creationTimestamp", |
| 22 | + "uid", |
| 23 | +] |
| 24 | + |
| 25 | +def clean_openshift_metadata(obj): |
| 26 | + if "metadata" in obj: |
| 27 | + for attr in IGNORED_ATTRIBUTES: |
| 28 | + if attr in obj["metadata"]: |
| 29 | + del obj["metadata"][attr] |
| 30 | + |
| 31 | + return obj |
| 32 | + |
12 | 33 | QUOTA_KEY_MAPPING = { |
13 | 34 | attributes.QUOTA_LIMITS_CPU: lambda x: {":limits.cpu": f"{x * 1000}m"}, |
14 | 35 | attributes.QUOTA_LIMITS_MEMORY: lambda x: {":limits.memory": f"{x}Mi"}, |
@@ -38,6 +59,41 @@ class OpenShiftResourceAllocator(base.ResourceAllocator): |
38 | 59 |
|
39 | 60 | project_name_max_length = 63 |
40 | 61 |
|
| 62 | + logger = logging.getLogger() |
| 63 | + |
| 64 | + def __init__(self, resource, allocation): |
| 65 | + super().__init__(resource, allocation) |
| 66 | + self.safe_resource_name = utils.env_safe_name(resource.name) |
| 67 | + self.id_provider = resource.get_attribute(attributes.RESOURCE_IDENTITY_NAME) |
| 68 | + self.apis = {} |
| 69 | + |
| 70 | + self.functional_tests = os.environ.get("FUNCTIONAL_TESTS", "").lower() |
| 71 | + self.verify = os.getenv(f"OPENSHIFT_{self.safe_resource_name}_VERIFY", "").lower() |
| 72 | + |
| 73 | + if self.functional_tests == "true" or self.verify == "false": |
| 74 | + self.logger = logging.getLogger() |
| 75 | + else: |
| 76 | + self.logger = logging.getLogger("django") |
| 77 | + |
| 78 | + @functools.cached_property |
| 79 | + def k8_client(self): |
| 80 | + # Load Endpoint URL and Auth token for new k8 client |
| 81 | + openshift_token = os.getenv(f"OPENSHIFT_{self.safe_resource_name}_TOKEN") |
| 82 | + openshift_url = self.resource.get_attribute(attributes.RESOURCE_AUTH_URL) |
| 83 | + |
| 84 | + k8_config = kubernetes.client.Configuration() |
| 85 | + k8_config.api_key["authorization"] = openshift_token |
| 86 | + k8_config.api_key_prefix["authorization"] = "Bearer" |
| 87 | + k8_config.host = openshift_url |
| 88 | + |
| 89 | + if self.functional_tests == "true" or self.verify == "false": |
| 90 | + k8_config.verify_ssl = False |
| 91 | + else: |
| 92 | + k8_config.verify_ssl = True |
| 93 | + |
| 94 | + k8s_client = kubernetes.client.ApiClient(configuration=k8_config) |
| 95 | + return kubernetes.dynamic.DynamicClient(k8s_client) |
| 96 | + |
41 | 97 | @functools.cached_property |
42 | 98 | def session(self): |
43 | 99 | var_name = utils.env_safe_name(self.resource.name) |
@@ -71,6 +127,18 @@ def check_response(response: requests.Response): |
71 | 127 | raise Conflict(f"{response.status_code}: {response.text}") |
72 | 128 | else: |
73 | 129 | raise ApiException(f"{response.status_code}: {response.text}") |
| 130 | + |
| 131 | + def qualified_id_user(self, id_user): |
| 132 | + return f"{self.id_provider}:{id_user}" |
| 133 | + |
| 134 | + def get_resource_api(self, api_version: str, kind: str): |
| 135 | + """Either return the cached resource api from self.apis, or fetch a |
| 136 | + new one, store it in self.apis, and return it.""" |
| 137 | + k = f"{api_version}:{kind}" |
| 138 | + api = self.apis.setdefault( |
| 139 | + k, self.k8_client.resources.get(api_version=api_version, kind=kind) |
| 140 | + ) |
| 141 | + return api |
74 | 142 |
|
75 | 143 | def create_project(self, suggested_project_name): |
76 | 144 | sanitized_project_name = utils.get_sanitized_project_name(suggested_project_name) |
@@ -113,13 +181,14 @@ def reactivate_project(self, project_id): |
113 | 181 | pass |
114 | 182 |
|
115 | 183 | def get_federated_user(self, username): |
116 | | - url = f"{self.auth_url}/users/{username}" |
117 | | - try: |
118 | | - r = self.session.get(url) |
119 | | - self.check_response(r) |
| 184 | + if ( |
| 185 | + self._openshift_user_exists(username) |
| 186 | + and self._openshift_get_identity(username) |
| 187 | + and self._openshift_useridentitymapping_exists(username, username) |
| 188 | + ): |
120 | 189 | return {'username': username} |
121 | | - except NotFound: |
122 | | - pass |
| 190 | + |
| 191 | + self.logger.info("404: " + f"user ({username}) does not exist") |
123 | 192 |
|
124 | 193 | def create_federated_user(self, unique_id): |
125 | 194 | url = f"{self.auth_url}/users/{unique_id}" |
@@ -183,3 +252,39 @@ def get_users(self, project_id): |
183 | 252 | url = f"{self.auth_url}/projects/{project_id}/users" |
184 | 253 | r = self.session.get(url) |
185 | 254 | return set(self.check_response(r)) |
| 255 | + |
| 256 | + def _openshift_get_user(self, username): |
| 257 | + api = self.get_resource_api(API_USER, "User") |
| 258 | + return clean_openshift_metadata(api.get(name=username).to_dict()) |
| 259 | + |
| 260 | + def _openshift_get_identity(self, id_user): |
| 261 | + api = self.get_resource_api(API_USER, "Identity") |
| 262 | + return clean_openshift_metadata( |
| 263 | + api.get(name=self.qualified_id_user(id_user)).to_dict() |
| 264 | + ) |
| 265 | + |
| 266 | + def _openshift_user_exists(self, user_name): |
| 267 | + try: |
| 268 | + self._openshift_get_user(user_name) |
| 269 | + except kexc.NotFoundError: |
| 270 | + return False |
| 271 | + return True |
| 272 | + |
| 273 | + def _openshift_identity_exists(self, id_user): |
| 274 | + try: |
| 275 | + self._openshift_get_identity(id_user) |
| 276 | + except kexc.NotFoundError: |
| 277 | + return False |
| 278 | + return True |
| 279 | + |
| 280 | + def _openshift_useridentitymapping_exists(self, user_name, id_user): |
| 281 | + try: |
| 282 | + user = self._openshift_get_user(user_name) |
| 283 | + except kexc.NotFoundError: |
| 284 | + return False |
| 285 | + |
| 286 | + return any( |
| 287 | + identity == self.qualified_id_user(id_user) |
| 288 | + for identity in user.get("identities", []) |
| 289 | + ) |
| 290 | + |
0 commit comments