diff --git a/aenv/src/aenv/client/scheduler_client.py b/aenv/src/aenv/client/scheduler_client.py index 50cd798..bace5ff 100644 --- a/aenv/src/aenv/client/scheduler_client.py +++ b/aenv/src/aenv/client/scheduler_client.py @@ -612,7 +612,6 @@ async def update_env_service( self, service_id: str, replicas: Optional[int] = None, - image: Optional[str] = None, environment_variables: Optional[Dict[str, str]] = None, ) -> "EnvService": """ @@ -621,7 +620,6 @@ async def update_env_service( Args: service_id: Environment service ID replicas: Optional number of replicas - image: Optional container image environment_variables: Optional environment variables Returns: @@ -638,7 +636,6 @@ async def update_env_service( request = EnvServiceUpdateRequest( replicas=replicas, - image=image, environment_variables=environment_variables, ) diff --git a/aenv/src/aenv/core/environment.py b/aenv/src/aenv/core/environment.py index 3268f5e..7628cd0 100644 --- a/aenv/src/aenv/core/environment.py +++ b/aenv/src/aenv/core/environment.py @@ -477,8 +477,8 @@ async def _call_function( if ensure_initialized: await self._ensure_initialized() - logger.info( - f"{self._log_prefix()} Executing function in environment {self.env_name} with url={function_url} proxy_headers={self.proxy_headers}, timeout={timeout}" + logger.debug( + f"{self._log_prefix()} Executing function in environment {self.env_name} with url={function_url}" ) try: @@ -514,8 +514,8 @@ async def _call_function( if not result.get("success", False): raise EnvironmentError(result.get("error", "Unknown error")) - logger.info( - f"{self._log_prefix()} Function '{function_url}' executed successfully with result={result}" + logger.debug( + f"{self._log_prefix()} Function '{function_url}' executed successfully" ) return result.get("data", {}) @@ -724,8 +724,8 @@ async def _wait_for_healthy(self, timeout: float = 300.0) -> None: result = "" while True: - logger.info( - f"{self._log_prefix()} check {self.env_name} health at round {times} with url: {self.aenv_health_url}, last_check_result={result}" + logger.debug( + f"{self._log_prefix()} check {self.env_name} health at round {times} with url: {self.aenv_health_url}" ) try: @@ -745,13 +745,13 @@ async def _wait_for_healthy(self, timeout: float = 300.0) -> None: or result.get("status") == "healthy" ): logger.info( - f"{self._log_prefix()} Environment {self.env_name} is healthy" + f"{self._log_prefix()} Environment {self.env_name} is healthy after {times + 1} attempts" ) return except Exception as e: logger.debug( - f"{self._log_prefix()} Health check failed: {str(e)}, retrying..." + f"{self._log_prefix()} Health check attempt {times + 1} failed: {str(e)}, retrying..." ) if asyncio.get_event_loop().time() - start_time > timeout: diff --git a/aenv/src/aenv/core/models.py b/aenv/src/aenv/core/models.py index 93d4a4b..5e05f06 100644 --- a/aenv/src/aenv/core/models.py +++ b/aenv/src/aenv/core/models.py @@ -166,7 +166,6 @@ class EnvServiceUpdateRequest(BaseModel): """Request to update an environment service.""" replicas: Optional[int] = Field(None, description="Number of replicas") - image: Optional[str] = Field(None, description="Container image") environment_variables: Optional[Dict[str, str]] = Field( None, description="Environment variables" ) diff --git a/aenv/src/cli/cmds/service.py b/aenv/src/cli/cmds/service.py index 3a507c3..00b2e2a 100644 --- a/aenv/src/cli/cmds/service.py +++ b/aenv/src/cli/cmds/service.py @@ -20,7 +20,7 @@ - service list: List running services - service get: Get detailed service information - service delete: Delete a service -- service update: Update service (replicas, image, env vars) +- service update: Update service (replicas, env vars) """ import asyncio import json @@ -157,11 +157,13 @@ def create( 1. CLI parameters (--replicas, --port, --enable-storage) 2. config.json's deployConfig.service (new structure) 3. config.json's deployConfig (legacy flat structure, for backward compatibility) - 4. System defaults + 4. API Service will fetch missing parameters from envhub if not provided in CLI/config + 5. System defaults Storage creation behavior: - Use --enable-storage flag to enable persistent storage - - Storage configuration (storageSize, storageName, mountPath) is read from config.json's deployConfig.service + - Storage configuration (storageSize, storageName, mountPath) can be provided in CLI options or config.json + - If not provided, API Service will use values from envhub's deployConfig.service - When storage is created, replicas must be 1 (enforced by backend) - storageClass is configured in helm values.yaml deployment, not in config.json @@ -169,7 +171,7 @@ def create( - replicas: Number of replicas (default: 1) - port: Service port (default: 8080) - enableStorage: Enable storage by default (default: false, CLI --enable-storage overrides) - - storageSize: Storage size like "10Gi", "20Gi" (required when --enable-storage is used) + - storageSize: Storage size like "10Gi", "20Gi" (used when --enable-storage is set) - storageName: Storage name (default: environment name) - mountPath: Mount path (default: /home/admin/data) @@ -188,13 +190,13 @@ def create( # Create using config.json in current directory aenv service create - # Create with explicit environment name + # Create with explicit environment name (API Service will fetch config from envhub) aenv service create myapp@1.0.0 # Create with 3 replicas and custom port (no storage) aenv service create myapp@1.0.0 --replicas 3 --port 8000 - # Create with storage enabled (storageSize must be in config.json) + # Create with storage enabled (storageSize from config.json or envhub) aenv service create myapp@1.0.0 --enable-storage # Create with environment variables @@ -259,10 +261,11 @@ def create( final_storage_size = service_config.get("storageSize") if not final_storage_size: console.print( - "[red]Error:[/red] Storage is enabled but 'storageSize' is not found in config.json's deployConfig.service.\n" - "Please add 'storageSize' (e.g., '10Gi', '20Gi') to deployConfig.service in config.json." + "[yellow]⚠️ Warning:[/yellow] Storage is enabled but 'storageSize' is not found in local config.json.\n" + " API Service will attempt to fetch storageSize from envhub.\n" + " If not found in envhub either, the service creation will fail." ) - raise click.Abort() + # Note: Don't abort here, let API Service try to fetch from envhub final_storage_name = service_config.get("storageName") final_mount_path = service_config.get("mountPath") @@ -730,11 +733,6 @@ async def _delete(): type=int, help="Update number of replicas", ) -@click.option( - "--image", - type=str, - help="Update container image", -) @click.option( "--env", "-e", @@ -754,32 +752,28 @@ def update_service( cfg: Config, service_id: str, replicas: Optional[int], - image: Optional[str], environment_variables: tuple, output: str, ): """Update a running service - Can update replicas, image, and environment variables. + Can update replicas and environment variables. Examples: # Scale to 5 replicas aenv service update myapp-svc-abc123 --replicas 5 - # Update image - aenv service update myapp-svc-abc123 --image myapp:2.0.0 - # Update environment variables aenv service update myapp-svc-abc123 -e DB_HOST=newhost -e DB_PORT=3306 # Update multiple things at once - aenv service update myapp-svc-abc123 --replicas 3 --image myapp:2.0.0 + aenv service update myapp-svc-abc123 --replicas 3 -e DB_HOST=newhost """ console = cfg.console.console() - if not replicas and not image and not environment_variables: + if not replicas and not environment_variables: console.print( - "[red]Error:[/red] At least one of --replicas, --image, or --env must be provided" + "[red]Error:[/red] At least one of --replicas or --env must be provided" ) raise click.Abort() @@ -800,8 +794,6 @@ def update_service( console.print(f"[cyan]🔄 Updating service:[/cyan] {service_id}") if replicas is not None: console.print(f" Replicas: {replicas}") - if image: - console.print(f" Image: {image}") if env_vars: console.print(f" Environment Variables: {len(env_vars)} variables") console.print() @@ -814,7 +806,6 @@ async def _update(): return await client.update_env_service( service_id=service_id, replicas=replicas, - image=image, environment_variables=env_vars, ) diff --git a/api-service/controller/env_service.go b/api-service/controller/env_service.go index 56ab586..819bc69 100644 --- a/api-service/controller/env_service.go +++ b/api-service/controller/env_service.go @@ -110,10 +110,24 @@ func (ctrl *EnvServiceController) CreateEnvService(c *gin.Context) { if backendEnv.DeployConfig == nil { backendEnv.DeployConfig = make(map[string]interface{}) } + + // Get service config from envhub metadata (support both new nested structure and legacy flat structure) + var serviceConfig map[string]interface{} + if svc, ok := backendEnv.DeployConfig["service"].(map[string]interface{}); ok { + serviceConfig = svc + } else { + // For backward compatibility, fall back to root deployConfig if service config is empty + serviceConfig = backendEnv.DeployConfig + } + + // Merge environment variables: CLI request overrides envhub config if req.EnvironmentVariables != nil { backendEnv.DeployConfig["environment_variables"] = req.EnvironmentVariables } + + // Override replicas from request, otherwise use envhub config backendEnv.DeployConfig["replicas"] = req.Replicas + if req.Owner != "" { backendEnv.DeployConfig["owner"] = req.Owner } @@ -122,24 +136,34 @@ func (ctrl *EnvServiceController) CreateEnvService(c *gin.Context) { backendEnv.DeployConfig["serviceName"] = req.ServiceName } - // Storage configuration + // Storage configuration: request overrides envhub config + // If request doesn't provide storageSize but envhub has it in service config, use envhub's value + if req.StorageSize != "" { + backendEnv.DeployConfig["storageSize"] = req.StorageSize + } else if storageSize, ok := serviceConfig["storageSize"].(string); ok { + backendEnv.DeployConfig["storageSize"] = storageSize + } + if req.PVCName != "" { backendEnv.DeployConfig["pvcName"] = req.PVCName + } else if pvcName, ok := serviceConfig["storageName"].(string); ok { + backendEnv.DeployConfig["pvcName"] = pvcName } + if req.MountPath != "" { backendEnv.DeployConfig["mountPath"] = req.MountPath - } - // storageClass is now configured in helm values.yaml, not passed via API - if req.StorageSize != "" { - backendEnv.DeployConfig["storageSize"] = req.StorageSize + } else if mountPath, ok := serviceConfig["mountPath"].(string); ok { + backendEnv.DeployConfig["mountPath"] = mountPath } - // Service configuration + // Service configuration: request overrides envhub config if req.Port > 0 { backendEnv.DeployConfig["port"] = req.Port + } else if port, ok := serviceConfig["port"].(float64); ok { + backendEnv.DeployConfig["port"] = int32(port) } - // Resource configuration + // Resource configuration: request overrides envhub config if req.CPURequest != "" { backendEnv.DeployConfig["cpuRequest"] = req.CPURequest } @@ -216,7 +240,6 @@ func (ctrl *EnvServiceController) DeleteEnvService(c *gin.Context) { // UpdateEnvServiceRequest represents the request body for updating an EnvService type UpdateEnvServiceRequest struct { Replicas *int32 `json:"replicas,omitempty"` - Image *string `json:"image,omitempty"` EnvironmentVariables *map[string]string `json:"environment_variables,omitempty"` } @@ -238,7 +261,6 @@ func (ctrl *EnvServiceController) UpdateEnvService(c *gin.Context) { // Build update request updateReq := &service.UpdateServiceRequest{ Replicas: req.Replicas, - Image: req.Image, EnvironmentVariables: req.EnvironmentVariables, } diff --git a/api-service/service/schedule_client.go b/api-service/service/schedule_client.go index 1541e3e..83ce79a 100644 --- a/api-service/service/schedule_client.go +++ b/api-service/service/schedule_client.go @@ -346,7 +346,6 @@ func (c *ScheduleClient) DeleteService(serviceName string, deleteStorage bool) ( // UpdateServiceRequest represents the request body for updating a service type UpdateServiceRequest struct { Replicas *int32 `json:"replicas,omitempty"` - Image *string `json:"image,omitempty"` EnvironmentVariables *map[string]string `json:"environment_variables,omitempty"` } diff --git a/controller/pkg/aenvhub_http_server/aenv_service_handler.go b/controller/pkg/aenvhub_http_server/aenv_service_handler.go index 243a18f..6c36dba 100644 --- a/controller/pkg/aenvhub_http_server/aenv_service_handler.go +++ b/controller/pkg/aenvhub_http_server/aenv_service_handler.go @@ -553,11 +553,10 @@ func (h *AEnvServiceHandler) deleteService(serviceName string, w http.ResponseWr } } -// updateService updates a service (replicas, image, env vars) +// updateService updates a service (replicas, env vars) func (h *AEnvServiceHandler) updateService(serviceName string, w http.ResponseWriter, r *http.Request) { var updateReq struct { Replicas *int32 `json:"replicas,omitempty"` - Image *string `json:"image,omitempty"` EnvironmentVariables *map[string]string `json:"environment_variables,omitempty"` } @@ -584,13 +583,6 @@ func (h *AEnvServiceHandler) updateService(serviceName string, w http.ResponseWr deployment.Spec.Replicas = updateReq.Replicas } - // Update image - if updateReq.Image != nil && *updateReq.Image != "" { - for i := range deployment.Spec.Template.Spec.Containers { - deployment.Spec.Template.Spec.Containers[i].Image = *updateReq.Image - } - } - // Update environment variables if updateReq.EnvironmentVariables != nil { for i := range deployment.Spec.Template.Spec.Containers { diff --git a/docs/guide/cli.md b/docs/guide/cli.md index a88df6a..11d0545 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -726,14 +726,11 @@ Update a running service's configuration. # Scale to 5 replicas aenv service update myapp-svc-abc123 --replicas 5 -# Update image -aenv service update myapp-svc-abc123 --image myapp:2.0.0 - # Update environment variables aenv service update myapp-svc-abc123 -e DB_HOST=newhost -e DB_PORT=3306 # Update multiple things at once -aenv service update myapp-svc-abc123 --replicas 3 --image myapp:2.0.0 +aenv service update myapp-svc-abc123 --replicas 3 -e DB_HOST=newhost ``` **Options:** @@ -741,13 +738,12 @@ aenv service update myapp-svc-abc123 --replicas 3 --image myapp:2.0.0 | Option | Short | Description | |---|---|---| | `--replicas` | `-r` | Update number of replicas | -| `--image` | | Update container image | | `--env` | `-e` | Environment variables (KEY=VALUE) | | `--output` | `-o` | Output format (table/json) | **Important Notes:** -- At least one of `--replicas`, `--image`, or `--env` must be provided +- At least one of `--replicas` or `--env` must be provided - Environment variable updates merge with existing variables ### `aenv get` - Get Environment Details