laniakea-api-server 0.2.8__tar.gz → 0.3.1__tar.gz

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 (24) hide show
  1. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/PKG-INFO +1 -1
  2. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/models.py +1 -0
  3. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/queue.py +36 -5
  4. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/routers/agent.py +5 -0
  5. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/routers/credentials.py +35 -3
  6. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/pyproject.toml +1 -1
  7. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/.github/workflows/publish.yml +0 -0
  8. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/.gitignore +0 -0
  9. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/LICENSE +0 -0
  10. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/README.md +0 -0
  11. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/__init__.py +0 -0
  12. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/auth.py +0 -0
  13. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/cli.py +0 -0
  14. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/config.py +0 -0
  15. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/credential_parser.py +0 -0
  16. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/database.py +0 -0
  17. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/installer.py +0 -0
  18. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/main.py +0 -0
  19. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/routers/.health.py.swp +0 -0
  20. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/routers/__init__.py +0 -0
  21. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/routers/agents.py +0 -0
  22. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/routers/deployments.py +0 -0
  23. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/routers/health.py +0 -0
  24. {laniakea_api_server-0.2.8 → laniakea_api_server-0.3.1}/laniakea_api/routers/tfstate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: laniakea-api-server
3
- Version: 0.2.8
3
+ Version: 0.3.1
4
4
  Summary: Laniakea Queue API — OIDC-authenticated gateway for cloud deployment orchestration
5
5
  Project-URL: Homepage, https://github.com/laniakea/laniakea-api
6
6
  Project-URL: Issues, https://github.com/laniakea/laniakea-api/issues
@@ -65,6 +65,7 @@ class DeploymentRequest(BaseModel):
65
65
  selected_provider: str # OpenStack | AWS
66
66
  service_type: Optional[str] = "galaxy"
67
67
  cloud_providers: dict
68
+ credentials_name: Optional[str] = "" # NOTE: remove here if no cred selection
68
69
 
69
70
 
70
71
  class JobResponse(BaseModel):
@@ -119,8 +119,20 @@ def _sc_path(user_sub: str, name: str = "") -> str:
119
119
  base = f"{user_sub}/service_creds"
120
120
  return f"{base}/{name}" if name else base
121
121
 
122
+ def _infer_service_type(data: dict) -> str:
123
+ """
124
+ Derive the credential type from the field names themselves —
125
+ nothing but secrets is stored in Vault.
126
+ """
127
+ if any(k.startswith("aws_") for k in data):
128
+ return "aws"
129
+ if any(k.startswith("openstack_") for k in data):
130
+ return "openstack"
131
+ return "unknown"
132
+
133
+
122
134
  def vault_list_service_creds(user_sub: str) -> list:
123
- client = vault_client()
135
+ client = vault_client
124
136
  try:
125
137
  resp = client.secrets.kv.v2.list_secrets(path=_sc_path(user_sub), mount_point=VAULT_MOUNT)
126
138
  names = [k.rstrip("/") for k in resp["data"]["keys"]]
@@ -129,11 +141,11 @@ def vault_list_service_creds(user_sub: str) -> list:
129
141
  out = []
130
142
  for n in names:
131
143
  data = vault_read_service_creds(user_sub, n)
132
- out.append({"name": n, "service_type": data.get("service_type", "openstack")})
144
+ out.append({"name": n, "service_type": _infer_service_type(data)})
133
145
  return out
134
146
 
135
147
  def vault_read_service_creds(user_sub: str, name: str) -> dict:
136
- client = vault_client()
148
+ client = vault_client
137
149
  try:
138
150
  resp = client.secrets.kv.v2.read_secret_version(path=_sc_path(user_sub, name), mount_point=VAULT_MOUNT)
139
151
  return resp["data"]["data"] or {}
@@ -141,11 +153,30 @@ def vault_read_service_creds(user_sub: str, name: str) -> dict:
141
153
  return {}
142
154
 
143
155
  def vault_write_service_creds(user_sub: str, name: str, data: dict) -> None:
144
- client = vault_client()
156
+ client = vault_client
145
157
  client.secrets.kv.v2.create_or_update_secret(
146
158
  path=_sc_path(user_sub, name), secret=data, mount_point=VAULT_MOUNT)
147
159
 
148
160
  def vault_delete_service_creds(user_sub: str, name: str) -> None:
149
- client = vault_client()
161
+ client = vault_client
150
162
  client.secrets.kv.v2.delete_metadata_and_all_versions(
151
163
  path=_sc_path(user_sub, name), mount_point=VAULT_MOUNT)
164
+
165
+
166
+ def vault_read_global(user_sub: str) -> dict:
167
+ client = vault_client
168
+ try:
169
+ resp = client.secrets.kv.v2.read_secret_version(
170
+ path=f"{user_sub}/credentials", mount_point=VAULT_MOUNT)
171
+ return resp["data"]["data"] or {}
172
+ except Exception:
173
+ return {}
174
+
175
+ def vault_strip_global_keys(user_sub: str, keys: list) -> None:
176
+ """Remove specific fields from the global path, keep the rest."""
177
+ data = vault_read_global(user_sub)
178
+ for k in keys:
179
+ data.pop(k, None)
180
+ client = vault_client
181
+ client.secrets.kv.v2.create_or_update_secret(
182
+ path=f"{user_sub}/credentials", secret=data, mount_point=VAULT_MOUNT)
@@ -78,6 +78,11 @@ async def agent_update_status(
78
78
  if not updated:
79
79
  raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="DB update failed.")
80
80
 
81
+ # Deployment lifecycle ends here: drop the record and its tf state.
82
+ if new_status == "DELETE_COMPLETE":
83
+ db.tfstate_delete(uuid)
84
+ db.delete_deployment(uuid)
85
+
81
86
  return {
82
87
  "deployment_uuid": uuid,
83
88
  "previous_status": current,
@@ -13,6 +13,8 @@ from laniakea_api.models import (OIDCLoginRequest, SessionTokenResponse,UserCred
13
13
  from laniakea_api.queue import vault_write_credentials, VAULT_MOUNT
14
14
  from laniakea_api.queue import (vault_list_service_creds, vault_read_service_creds,
15
15
  vault_write_service_creds, vault_delete_service_creds)
16
+ from laniakea_api.queue import vault_read_global, vault_strip_global_keys
17
+
16
18
 
17
19
  router = APIRouter()
18
20
 
@@ -108,9 +110,12 @@ async def read_service_creds(name: str, caller: dict = Depends(verify_session_to
108
110
 
109
111
  @router.put("/profile/service_creds/{name}")
110
112
  async def write_service_creds(name: str, body: dict, caller: dict = Depends(verify_session_token)):
111
- if not name or "/" in name:
112
- raise HTTPException(status_code=400, detail="Invalid credential name.")
113
- body = {k: v for k, v in body.items() if v not in (None, "") and k != "name"}
113
+ import re
114
+ if not re.fullmatch(r"[A-Za-z0-9_-]+", name or ""):
115
+ raise HTTPException(status_code=400,
116
+ detail="Invalid name: only letters, digits, '_' and '-' (e.g. openstack_garr).")
117
+ body = {k: v for k, v in body.items()
118
+ if v not in (None, "") and k not in ("name", "service_type")}
114
119
  vault_write_service_creds(caller["sub"], name, body) # KV2: ogni write = nuova versione
115
120
  return {"name": name, "saved": True}
116
121
 
@@ -118,3 +123,30 @@ async def write_service_creds(name: str, body: dict, caller: dict = Depends(veri
118
123
  async def delete_service_creds(name: str, caller: dict = Depends(verify_session_token)):
119
124
  vault_delete_service_creds(caller["sub"], name)
120
125
  return {"name": name, "deleted": True}
126
+
127
+
128
+ @router.get("/profile/ssh_key")
129
+ async def get_ssh_key(caller: dict = Depends(verify_session_token)):
130
+ data = vault_read_global(caller["sub"])
131
+ return {"ssh_key": data.get("ssh_key", "")}
132
+
133
+ @router.get("/profile/ssh_key/private")
134
+ async def get_ssh_private_key(caller: dict = Depends(verify_session_token)):
135
+ data = vault_read_global(caller["sub"])
136
+ if not data.get("ssh_private_key"):
137
+ raise HTTPException(status_code=404, detail="No private key stored.")
138
+ return {"ssh_private_key": data["ssh_private_key"]}
139
+
140
+ @router.put("/profile/ssh_key")
141
+ async def put_ssh_key(body: dict, caller: dict = Depends(verify_session_token)):
142
+ fields = {k: v for k, v in body.items()
143
+ if k in ("ssh_key", "ssh_private_key") and v}
144
+ if not fields:
145
+ raise HTTPException(status_code=400, detail="Nothing to store.")
146
+ vault_write_credentials(caller["sub"], fields) # merge-write sul path globale
147
+ return {"saved": sorted(fields.keys())}
148
+
149
+ @router.delete("/profile/ssh_key")
150
+ async def delete_ssh_key(caller: dict = Depends(verify_session_token)):
151
+ vault_strip_global_keys(caller["sub"], ["ssh_key", "ssh_private_key"])
152
+ return {"deleted": True}
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "laniakea-api-server"
7
- version = "0.2.8"
7
+ version = "0.3.1"
8
8
  description = "Laniakea Queue API — OIDC-authenticated gateway for cloud deployment orchestration"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }