datacrunch 1.7.1__py3-none-any.whl → 1.8.0__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.
datacrunch/__version__.py CHANGED
@@ -1 +1 @@
1
- VERSION = '1.7.1'
1
+ VERSION = '1.8.0'
@@ -0,0 +1,30 @@
1
+ from .containers import (
2
+ EnvVar,
3
+ EnvVarType,
4
+ ContainerRegistryType,
5
+ ContainerDeploymentStatus,
6
+ HealthcheckSettings,
7
+ EntrypointOverridesSettings,
8
+ VolumeMount,
9
+ VolumeMountType,
10
+ Container,
11
+ ContainerRegistryCredentials,
12
+ ContainerRegistrySettings,
13
+ ComputeResource,
14
+ ScalingPolicy,
15
+ QueueLoadScalingTrigger,
16
+ UtilizationScalingTrigger,
17
+ ScalingTriggers,
18
+ ScalingOptions,
19
+ Deployment,
20
+ ReplicaInfo,
21
+ Secret,
22
+ RegistryCredential,
23
+ ContainersService,
24
+ BaseRegistryCredentials,
25
+ DockerHubCredentials,
26
+ GithubCredentials,
27
+ GCRCredentials,
28
+ AWSECRCredentials,
29
+ CustomRegistryCredentials,
30
+ )
@@ -0,0 +1,673 @@
1
+ from dataclasses import dataclass
2
+ from dataclasses_json import dataclass_json, Undefined # type: ignore
3
+ from typing import List, Optional, Dict
4
+ from enum import Enum
5
+
6
+
7
+ # API endpoints
8
+ CONTAINER_DEPLOYMENTS_ENDPOINT = '/container-deployments'
9
+ SERVERLESS_COMPUTE_RESOURCES_ENDPOINT = '/serverless-compute-resources'
10
+ CONTAINER_REGISTRY_CREDENTIALS_ENDPOINT = '/container-registry-credentials'
11
+ SECRETS_ENDPOINT = '/secrets'
12
+
13
+
14
+ class EnvVarType(str, Enum):
15
+ PLAIN = "plain"
16
+ SECRET = "secret"
17
+
18
+
19
+ class VolumeMountType(str, Enum):
20
+ SCRATCH = "scratch"
21
+ SECRET = "secret"
22
+
23
+
24
+ class ContainerRegistryType(str, Enum):
25
+ GCR = "gcr"
26
+ DOCKERHUB = "dockerhub"
27
+ GITHUB = "ghcr"
28
+ AWS_ECR = "aws-ecr"
29
+ CUSTOM = "custom"
30
+
31
+
32
+ class ContainerDeploymentStatus(str, Enum):
33
+ INITIALIZING = "initializing"
34
+ HEALTHY = "healthy"
35
+ DEGRADED = "degraded"
36
+ UNHEALTHY = "unhealthy"
37
+ PAUSED = "paused"
38
+ QUOTA_REACHED = "quota_reached"
39
+ IMAGE_PULLING = "image_pulling"
40
+ VERSION_UPDATING = "version_updating"
41
+
42
+
43
+ @dataclass_json
44
+ @dataclass
45
+ class HealthcheckSettings:
46
+ """Settings for container health checking.
47
+
48
+ :param enabled: Whether health checking is enabled
49
+ :param port: Port number to perform health check on
50
+ :param path: HTTP path to perform health check on
51
+ """
52
+ enabled: bool = True
53
+ port: Optional[int] = None
54
+ path: Optional[str] = None
55
+
56
+
57
+ @dataclass_json
58
+ @dataclass
59
+ class EntrypointOverridesSettings:
60
+ """Settings for overriding container entrypoint and command.
61
+
62
+ :param enabled: Whether entrypoint overrides are enabled
63
+ :param entrypoint: List of strings forming the entrypoint command
64
+ :param cmd: List of strings forming the command arguments
65
+ """
66
+ enabled: bool = True
67
+ entrypoint: Optional[List[str]] = None
68
+ cmd: Optional[List[str]] = None
69
+
70
+
71
+ @dataclass_json
72
+ @dataclass
73
+ class EnvVar:
74
+ """Environment variable configuration for containers.
75
+
76
+ :param name: Name of the environment variable
77
+ :param value_or_reference_to_secret: Direct value or reference to a secret
78
+ :param type: Type of the environment variable
79
+ """
80
+ name: str
81
+ value_or_reference_to_secret: str
82
+ type: EnvVarType
83
+
84
+
85
+ @dataclass_json
86
+ @dataclass
87
+ class VolumeMount:
88
+ """Volume mount configuration for containers.
89
+
90
+ :param type: Type of volume mount
91
+ :param mount_path: Path where the volume should be mounted in the container
92
+ """
93
+ type: VolumeMountType
94
+ mount_path: str
95
+
96
+
97
+ @dataclass_json
98
+ @dataclass
99
+ class Container:
100
+ """Container configuration for deployments.
101
+
102
+ :param name: Name of the container
103
+ :param image: Container image to use
104
+ :param exposed_port: Port to expose from the container
105
+ :param healthcheck: Optional health check configuration
106
+ :param entrypoint_overrides: Optional entrypoint override settings
107
+ :param env: Optional list of environment variables
108
+ :param volume_mounts: Optional list of volume mounts
109
+ """
110
+ name: str
111
+ image: str
112
+ exposed_port: int
113
+ healthcheck: Optional[HealthcheckSettings] = None
114
+ entrypoint_overrides: Optional[EntrypointOverridesSettings] = None
115
+ env: Optional[List[EnvVar]] = None
116
+ volume_mounts: Optional[List[VolumeMount]] = None
117
+
118
+
119
+ @dataclass_json
120
+ @dataclass
121
+ class ContainerRegistryCredentials:
122
+ """Credentials for accessing a container registry.
123
+
124
+ :param name: Name of the credentials
125
+ """
126
+ name: str
127
+
128
+
129
+ @dataclass_json
130
+ @dataclass
131
+ class ContainerRegistrySettings:
132
+ """Settings for container registry access.
133
+
134
+ :param is_private: Whether the registry is private
135
+ :param credentials: Optional credentials for accessing private registry
136
+ """
137
+ is_private: bool
138
+ credentials: Optional[ContainerRegistryCredentials] = None
139
+
140
+
141
+ @dataclass_json
142
+ @dataclass
143
+ class ComputeResource:
144
+ """Compute resource configuration.
145
+
146
+ :param name: Name of the compute resource
147
+ :param size: Size of the compute resource
148
+ :param is_available: Whether the compute resource is currently available
149
+ """
150
+ name: str
151
+ size: int
152
+ # Made optional since it's only used in API responses
153
+ is_available: Optional[bool] = None
154
+
155
+
156
+ @dataclass_json
157
+ @dataclass
158
+ class ScalingPolicy:
159
+ """Policy for controlling scaling behavior.
160
+
161
+ :param delay_seconds: Number of seconds to wait before applying scaling action
162
+ """
163
+ delay_seconds: int
164
+
165
+
166
+ @dataclass_json
167
+ @dataclass
168
+ class QueueLoadScalingTrigger:
169
+ """Trigger for scaling based on queue load.
170
+
171
+ :param threshold: Queue load threshold that triggers scaling
172
+ """
173
+ threshold: float
174
+
175
+
176
+ @dataclass_json
177
+ @dataclass
178
+ class UtilizationScalingTrigger:
179
+ """Trigger for scaling based on resource utilization.
180
+
181
+ :param enabled: Whether this trigger is enabled
182
+ :param threshold: Utilization threshold that triggers scaling
183
+ """
184
+ enabled: bool
185
+ threshold: Optional[float] = None
186
+
187
+
188
+ @dataclass_json
189
+ @dataclass
190
+ class ScalingTriggers:
191
+ """Collection of triggers that can cause scaling actions.
192
+
193
+ :param queue_load: Optional trigger based on queue load
194
+ :param cpu_utilization: Optional trigger based on CPU utilization
195
+ :param gpu_utilization: Optional trigger based on GPU utilization
196
+ """
197
+ queue_load: Optional[QueueLoadScalingTrigger] = None
198
+ cpu_utilization: Optional[UtilizationScalingTrigger] = None
199
+ gpu_utilization: Optional[UtilizationScalingTrigger] = None
200
+
201
+
202
+ @dataclass_json
203
+ @dataclass
204
+ class ScalingOptions:
205
+ """Configuration for automatic scaling behavior.
206
+
207
+ :param min_replica_count: Minimum number of replicas to maintain
208
+ :param max_replica_count: Maximum number of replicas allowed
209
+ :param scale_down_policy: Policy for scaling down replicas
210
+ :param scale_up_policy: Policy for scaling up replicas
211
+ :param queue_message_ttl_seconds: Time-to-live for queue messages in seconds
212
+ :param concurrent_requests_per_replica: Number of concurrent requests each replica can handle
213
+ :param scaling_triggers: Configuration for various scaling triggers
214
+ """
215
+ min_replica_count: int
216
+ max_replica_count: int
217
+ scale_down_policy: ScalingPolicy
218
+ scale_up_policy: ScalingPolicy
219
+ queue_message_ttl_seconds: int
220
+ concurrent_requests_per_replica: int
221
+ scaling_triggers: ScalingTriggers
222
+
223
+
224
+ @dataclass_json(undefined=Undefined.EXCLUDE)
225
+ @dataclass
226
+ class Deployment:
227
+ """Configuration for a container deployment.
228
+
229
+ :param name: Name of the deployment
230
+ :param container_registry_settings: Settings for accessing container registry
231
+ :param containers: List of containers in the deployment
232
+ :param compute: Compute resource configuration
233
+ :param is_spot: Whether is spot deployment
234
+ :param endpoint_base_url: Optional base URL for the deployment endpoint
235
+ :param scaling: Optional scaling configuration
236
+ :param created_at: Timestamp when the deployment was created
237
+ """
238
+ name: str
239
+ container_registry_settings: ContainerRegistrySettings
240
+ containers: List[Container]
241
+ compute: ComputeResource
242
+ is_spot: bool = False
243
+ endpoint_base_url: Optional[str] = None
244
+ scaling: Optional[ScalingOptions] = None
245
+ created_at: Optional[str] = None
246
+
247
+
248
+ @dataclass_json
249
+ @dataclass
250
+ class ReplicaInfo:
251
+ """Information about a deployment replica.
252
+
253
+ :param id: Unique identifier of the replica
254
+ :param status: Current status of the replica
255
+ :param started_at: Timestamp when the replica was started
256
+ """
257
+ id: str
258
+ status: str
259
+ started_at: str
260
+
261
+
262
+ @dataclass_json
263
+ @dataclass
264
+ class Secret:
265
+ """A secret model class"""
266
+ name: str
267
+ created_at: str
268
+
269
+
270
+ @dataclass_json
271
+ @dataclass
272
+ class RegistryCredential:
273
+ """A container registry credential model class"""
274
+ name: str
275
+ created_at: str
276
+
277
+
278
+ @dataclass_json
279
+ @dataclass
280
+ class BaseRegistryCredentials:
281
+ """Base class for registry credentials"""
282
+ name: str
283
+ type: ContainerRegistryType
284
+
285
+
286
+ @dataclass_json
287
+ @dataclass
288
+ class DockerHubCredentials(BaseRegistryCredentials):
289
+ """Credentials for DockerHub registry"""
290
+ username: str
291
+ access_token: str
292
+
293
+ def __init__(self, name: str, username: str, access_token: str):
294
+ super().__init__(name=name, type=ContainerRegistryType.DOCKERHUB)
295
+ self.username = username
296
+ self.access_token = access_token
297
+
298
+
299
+ @dataclass_json
300
+ @dataclass
301
+ class GithubCredentials(BaseRegistryCredentials):
302
+ """Credentials for GitHub Container Registry"""
303
+ username: str
304
+ access_token: str
305
+
306
+ def __init__(self, name: str, username: str, access_token: str):
307
+ super().__init__(name=name, type=ContainerRegistryType.GITHUB)
308
+ self.username = username
309
+ self.access_token = access_token
310
+
311
+
312
+ @dataclass_json
313
+ @dataclass
314
+ class GCRCredentials(BaseRegistryCredentials):
315
+ """Credentials for Google Container Registry"""
316
+ service_account_key: str
317
+
318
+ def __init__(self, name: str, service_account_key: str):
319
+ super().__init__(name=name, type=ContainerRegistryType.GCR)
320
+ self.service_account_key = service_account_key
321
+
322
+
323
+ @dataclass_json
324
+ @dataclass
325
+ class AWSECRCredentials(BaseRegistryCredentials):
326
+ """Credentials for AWS Elastic Container Registry"""
327
+ access_key_id: str
328
+ secret_access_key: str
329
+ region: str
330
+ ecr_repo: str
331
+
332
+ def __init__(self, name: str, access_key_id: str, secret_access_key: str, region: str, ecr_repo: str):
333
+ super().__init__(name=name, type=ContainerRegistryType.AWS_ECR)
334
+ self.access_key_id = access_key_id
335
+ self.secret_access_key = secret_access_key
336
+ self.region = region
337
+ self.ecr_repo = ecr_repo
338
+
339
+
340
+ @dataclass_json
341
+ @dataclass
342
+ class CustomRegistryCredentials(BaseRegistryCredentials):
343
+ """Credentials for custom container registries"""
344
+ docker_config_json: str
345
+
346
+ def __init__(self, name: str, docker_config_json: str):
347
+ super().__init__(name=name, type=ContainerRegistryType.CUSTOM)
348
+ self.docker_config_json = docker_config_json
349
+
350
+
351
+ class ContainersService:
352
+ """Service for managing container deployments"""
353
+
354
+ def __init__(self, http_client) -> None:
355
+ """Initialize the containers service
356
+
357
+ :param http_client: HTTP client for making API requests
358
+ :type http_client: Any
359
+ """
360
+ self.client = http_client
361
+
362
+ def get_deployments(self) -> List[Deployment]:
363
+ """Get all deployments
364
+
365
+ :return: list of deployments
366
+ :rtype: List[Deployment]
367
+ """
368
+ response = self.client.get(CONTAINER_DEPLOYMENTS_ENDPOINT)
369
+ return [Deployment.from_dict(deployment, infer_missing=True) for deployment in response.json()]
370
+
371
+ def get_deployment_by_name(self, deployment_name: str) -> Deployment:
372
+ """Get a deployment by name
373
+
374
+ :param deployment_name: name of the deployment
375
+ :type deployment_name: str
376
+ :return: deployment
377
+ :rtype: Deployment
378
+ """
379
+ response = self.client.get(
380
+ f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}")
381
+ return Deployment.from_dict(response.json(), infer_missing=True)
382
+
383
+ def create_deployment(
384
+ self,
385
+ deployment: Deployment
386
+ ) -> Deployment:
387
+ """Create a new deployment
388
+
389
+ :param deployment: deployment configuration
390
+ :type deployment: Deployment
391
+ :return: created deployment
392
+ :rtype: Deployment
393
+ """
394
+ response = self.client.post(
395
+ CONTAINER_DEPLOYMENTS_ENDPOINT,
396
+ deployment.to_dict()
397
+ )
398
+ return Deployment.from_dict(response.json(), infer_missing=True)
399
+
400
+ def update_deployment(self, deployment_name: str, deployment: Deployment) -> Deployment:
401
+ """Update an existing deployment
402
+
403
+ :param deployment_name: name of the deployment to update
404
+ :type deployment_name: str
405
+ :param deployment: updated deployment
406
+ :type deployment: Deployment
407
+ :return: updated deployment
408
+ :rtype: Deployment
409
+ """
410
+ response = self.client.patch(
411
+ f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}",
412
+ deployment.to_dict()
413
+ )
414
+ return Deployment.from_dict(response.json(), infer_missing=True)
415
+
416
+ def delete_deployment(self, deployment_name: str) -> None:
417
+ """Delete a deployment
418
+
419
+ :param deployment_name: name of the deployment to delete
420
+ :type deployment_name: str
421
+ """
422
+ self.client.delete(
423
+ f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}")
424
+
425
+ def get_deployment_status(self, deployment_name: str) -> ContainerDeploymentStatus:
426
+ """Get deployment status
427
+
428
+ :param deployment_name: name of the deployment
429
+ :type deployment_name: str
430
+ :return: deployment status
431
+ :rtype: ContainerDeploymentStatus
432
+ """
433
+ response = self.client.get(
434
+ f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/status")
435
+ return ContainerDeploymentStatus(response.json()["status"])
436
+
437
+ def restart_deployment(self, deployment_name: str) -> None:
438
+ """Restart a deployment
439
+
440
+ :param deployment_name: name of the deployment to restart
441
+ :type deployment_name: str
442
+ """
443
+ self.client.post(
444
+ f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/restart")
445
+
446
+ def get_deployment_scaling_options(self, deployment_name: str) -> ScalingOptions:
447
+ """Get deployment scaling options
448
+
449
+ :param deployment_name: name of the deployment
450
+ :type deployment_name: str
451
+ :return: scaling options
452
+ :rtype: ScalingOptions
453
+ """
454
+ response = self.client.get(
455
+ f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/scaling")
456
+ return ScalingOptions.from_dict(response.json())
457
+
458
+ def update_deployment_scaling_options(self, deployment_name: str, scaling_options: ScalingOptions) -> ScalingOptions:
459
+ """Update deployment scaling options
460
+
461
+ :param deployment_name: name of the deployment
462
+ :type deployment_name: str
463
+ :param scaling_options: new scaling options
464
+ :type scaling_options: ScalingOptions
465
+ :return: updated scaling options
466
+ :rtype: ScalingOptions
467
+ """
468
+ response = self.client.patch(
469
+ f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/scaling",
470
+ scaling_options.to_dict()
471
+ )
472
+ return ScalingOptions.from_dict(response.json())
473
+
474
+ def get_deployment_replicas(self, deployment_name: str) -> List[ReplicaInfo]:
475
+ """Get deployment replicas
476
+
477
+ :param deployment_name: name of the deployment
478
+ :type deployment_name: str
479
+ :return: list of replicas information
480
+ :rtype: List[ReplicaInfo]
481
+ """
482
+ response = self.client.get(
483
+ f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/replicas")
484
+ return [ReplicaInfo.from_dict(replica) for replica in response.json()["list"]]
485
+
486
+ def purge_deployment_queue(self, deployment_name: str) -> None:
487
+ """Purge deployment queue
488
+
489
+ :param deployment_name: name of the deployment
490
+ :type deployment_name: str
491
+ """
492
+ self.client.post(
493
+ f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/purge-queue")
494
+
495
+ def pause_deployment(self, deployment_name: str) -> None:
496
+ """Pause a deployment
497
+
498
+ :param deployment_name: name of the deployment to pause
499
+ :type deployment_name: str
500
+ """
501
+ self.client.post(
502
+ f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/pause")
503
+
504
+ def resume_deployment(self, deployment_name: str) -> None:
505
+ """Resume a deployment
506
+
507
+ :param deployment_name: name of the deployment to resume
508
+ :type deployment_name: str
509
+ """
510
+ self.client.post(
511
+ f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/resume")
512
+
513
+ def get_deployment_environment_variables(self, deployment_name: str) -> Dict[str, List[EnvVar]]:
514
+ """Get deployment environment variables
515
+
516
+ :param deployment_name: name of the deployment
517
+ :type deployment_name: str
518
+ :return: dictionary mapping container names to their environment variables
519
+ :rtype: Dict[str, List[EnvVar]]
520
+ """
521
+ response = self.client.get(
522
+ f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/environment-variables")
523
+ result = {}
524
+ for item in response.json():
525
+ container_name = item["container_name"]
526
+ env_vars = item["env"]
527
+ result[container_name] = [EnvVar.from_dict(
528
+ env_var) for env_var in env_vars]
529
+ return result
530
+
531
+ def add_deployment_environment_variables(self, deployment_name: str, container_name: str, env_vars: List[EnvVar]) -> Dict[str, List[EnvVar]]:
532
+ """Add environment variables to a container
533
+
534
+ :param deployment_name: name of the deployment
535
+ :type deployment_name: str
536
+ :param container_name: name of the container
537
+ :type container_name: str
538
+ :param env_vars: environment variables to add
539
+ :type env_vars: List[EnvVar]
540
+ :return: updated environment variables
541
+ :rtype: Dict[str, List[EnvVar]]
542
+ """
543
+ response = self.client.post(
544
+ f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/environment-variables",
545
+ {"container_name": container_name, "env": [
546
+ env_var.to_dict() for env_var in env_vars]}
547
+ )
548
+ result = {}
549
+ for item in response.json():
550
+ container_name = item["container_name"]
551
+ env_vars = item["env"]
552
+ result[container_name] = [EnvVar.from_dict(
553
+ env_var) for env_var in env_vars]
554
+ return result
555
+
556
+ def update_deployment_environment_variables(self, deployment_name: str, container_name: str, env_vars: List[EnvVar]) -> Dict[str, List[EnvVar]]:
557
+ """Update environment variables of a container
558
+
559
+ :param deployment_name: name of the deployment
560
+ :type deployment_name: str
561
+ :param container_name: name of the container
562
+ :type container_name: str
563
+ :param env_vars: updated environment variables
564
+ :type env_vars: List[EnvVar]
565
+ :return: updated environment variables
566
+ :rtype: Dict[str, List[EnvVar]]
567
+ """
568
+ response = self.client.patch(
569
+ f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/environment-variables",
570
+ {"container_name": container_name, "env": [
571
+ env_var.to_dict() for env_var in env_vars]}
572
+ )
573
+ result = {}
574
+ item = response.json()
575
+ container_name = item["container_name"]
576
+ env_vars = item["env"]
577
+ result[container_name] = [EnvVar.from_dict(
578
+ env_var) for env_var in env_vars]
579
+ return result
580
+
581
+ def delete_deployment_environment_variables(self, deployment_name: str, container_name: str, env_var_names: List[str]) -> Dict[str, List[EnvVar]]:
582
+ """Delete environment variables from a container
583
+
584
+ :param deployment_name: name of the deployment
585
+ :type deployment_name: str
586
+ :param container_name: name of the container
587
+ :type container_name: str
588
+ :param env_var_names: names of environment variables to delete
589
+ :type env_var_names: List[str]
590
+ :return: remaining environment variables
591
+ :rtype: Dict[str, List[EnvVar]]
592
+ """
593
+ response = self.client.delete(
594
+ f"{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/environment-variables",
595
+ {"container_name": container_name, "env": env_var_names}
596
+ )
597
+ result = {}
598
+ for item in response.json():
599
+ container_name = item["container_name"]
600
+ env_vars = item["env"]
601
+ result[container_name] = [EnvVar.from_dict(
602
+ env_var) for env_var in env_vars]
603
+ return result
604
+
605
+ def get_compute_resources(self) -> List[ComputeResource]:
606
+ """Get available compute resources
607
+
608
+ :return: list of compute resources
609
+ :rtype: List[ComputeResource]
610
+ """
611
+ response = self.client.get(SERVERLESS_COMPUTE_RESOURCES_ENDPOINT)
612
+ resources = []
613
+ for resource_group in response.json():
614
+ for resource in resource_group:
615
+ resources.append(ComputeResource.from_dict(resource))
616
+ return resources
617
+
618
+ def get_secrets(self) -> List[Secret]:
619
+ """Get all secrets
620
+
621
+ :return: list of secrets
622
+ :rtype: List[Secret]
623
+ """
624
+ response = self.client.get(SECRETS_ENDPOINT)
625
+ return [Secret.from_dict(secret) for secret in response.json()]
626
+
627
+ def create_secret(self, name: str, value: str) -> None:
628
+ """Create a new secret
629
+
630
+ :param name: name of the secret
631
+ :type name: str
632
+ :param value: value of the secret
633
+ :type value: str
634
+ """
635
+ self.client.post(SECRETS_ENDPOINT, {"name": name, "value": value})
636
+
637
+ def delete_secret(self, secret_name: str, force: bool = False) -> None:
638
+ """Delete a secret
639
+
640
+ :param secret_name: name of the secret to delete
641
+ :type secret_name: str
642
+ :param force: force delete even if secret is in use
643
+ :type force: bool
644
+ """
645
+ self.client.delete(
646
+ f"{SECRETS_ENDPOINT}/{secret_name}", params={"force": str(force).lower()})
647
+
648
+ def get_registry_credentials(self) -> List[RegistryCredential]:
649
+ """Get all registry credentials
650
+
651
+ :return: list of registry credentials
652
+ :rtype: List[RegistryCredential]
653
+ """
654
+ response = self.client.get(CONTAINER_REGISTRY_CREDENTIALS_ENDPOINT)
655
+ return [RegistryCredential.from_dict(credential) for credential in response.json()]
656
+
657
+ def add_registry_credentials(self, credentials: BaseRegistryCredentials) -> None:
658
+ """Add registry credentials
659
+
660
+ :param credentials: Registry credentials object
661
+ :type credentials: BaseRegistryCredentials
662
+ """
663
+ data = credentials.to_dict()
664
+ self.client.post(CONTAINER_REGISTRY_CREDENTIALS_ENDPOINT, data)
665
+
666
+ def delete_registry_credentials(self, credentials_name: str) -> None:
667
+ """Delete registry credentials
668
+
669
+ :param credentials_name: name of the credentials to delete
670
+ :type credentials_name: str
671
+ """
672
+ self.client.delete(
673
+ f"{CONTAINER_REGISTRY_CREDENTIALS_ENDPOINT}/{credentials_name}")