diff --git a/go/apps/ctrl/run.go b/go/apps/ctrl/run.go index ad8e2ef17f..0d97485509 100644 --- a/go/apps/ctrl/run.go +++ b/go/apps/ctrl/run.go @@ -108,13 +108,19 @@ func Run(ctx context.Context, cfg Config) error { if cfg.SPIFFESocketPath != "" { // Use SPIRE authentication when socket path is provided tlsConfig := deployTLS.Config{ - Mode: deployTLS.ModeSPIFFE, - SPIFFESocketPath: cfg.SPIFFESocketPath, + Mode: deployTLS.ModeSPIFFE, + SPIFFESocketPath: cfg.SPIFFESocketPath, + CertFile: "", + KeyFile: "", + CAFile: "", + SPIFFETimeout: "", + EnableCertCaching: false, + CertCacheTTL: 0, } - tlsProvider, err := deployTLS.NewProvider(ctx, tlsConfig) - if err != nil { - return fmt.Errorf("failed to create TLS provider for metald: %w", err) + tlsProvider, tlsErr := deployTLS.NewProvider(ctx, tlsConfig) + if tlsErr != nil { + return fmt.Errorf("failed to create TLS provider for metald: %w", tlsErr) } httpClient = tlsProvider.HTTPClient() diff --git a/go/apps/ctrl/services/version/create_version.go b/go/apps/ctrl/services/version/create_version.go index 77080ea3fa..936ed6661d 100644 --- a/go/apps/ctrl/services/version/create_version.go +++ b/go/apps/ctrl/services/version/create_version.go @@ -21,7 +21,7 @@ func (s *Service) CreateVersion( _, err := db.Query.FindWorkspaceByID(ctx, s.db.RO(), req.Msg.GetWorkspaceId()) if err != nil { if err == sql.ErrNoRows { - return nil, connect.NewError(connect.CodeNotFound, + return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("workspace not found: %s", req.Msg.GetWorkspaceId())) } return nil, connect.NewError(connect.CodeInternal, err) @@ -31,7 +31,7 @@ func (s *Service) CreateVersion( project, err := db.Query.FindProjectById(ctx, s.db.RO(), req.Msg.GetProjectId()) if err != nil { if err == sql.ErrNoRows { - return nil, connect.NewError(connect.CodeNotFound, + return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("project not found: %s", req.Msg.GetProjectId())) } return nil, connect.NewError(connect.CodeInternal, err) @@ -39,8 +39,8 @@ func (s *Service) CreateVersion( // Verify project belongs to the specified workspace if project.WorkspaceID != req.Msg.GetWorkspaceId() { - return nil, connect.NewError(connect.CodeInvalidArgument, - fmt.Errorf("project %s does not belong to workspace %s", + return nil, connect.NewError(connect.CodeInvalidArgument, + fmt.Errorf("project %s does not belong to workspace %s", req.Msg.GetProjectId(), req.Msg.GetWorkspaceId())) } @@ -71,7 +71,7 @@ func (s *Service) CreateVersion( UpdatedAt: sql.NullInt64{Int64: time.Now().UnixMilli(), Valid: true}, }) if err != nil { - return nil, connect.NewError(connect.CodeInternal, + return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to create branch: %w", err)) } s.logger.Info("created new branch", "branch_id", branchID, "name", branchName, "project_id", req.Msg.GetProjectId()) diff --git a/go/apps/ctrl/services/version/deploy_workflow.go b/go/apps/ctrl/services/version/deploy_workflow.go index 1029a8192e..a971925aaf 100644 --- a/go/apps/ctrl/services/version/deploy_workflow.go +++ b/go/apps/ctrl/services/version/deploy_workflow.go @@ -102,7 +102,7 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro VersionID: req.VersionID, Status: "pending", Message: sql.NullString{String: "Version queued and ready to start", Valid: true}, - ErrorMessage: sql.NullString{}, + ErrorMessage: sql.NullString{String: "", Valid: false}, CreatedAt: time.Now().UnixMilli(), }) }) @@ -176,7 +176,7 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro VersionID: req.VersionID, Status: "downloading_docker_image", Message: sql.NullString{String: fmt.Sprintf("Downloading Docker image: %s", req.DockerImage), Valid: true}, - ErrorMessage: sql.NullString{}, + ErrorMessage: sql.NullString{String: "", Valid: false}, CreatedAt: time.Now().UnixMilli(), }) }) @@ -195,15 +195,20 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro Cpu: &vmprovisionerv1.CpuConfig{ VcpuCount: 2, MaxVcpuCount: 4, + Topology: nil, + Features: nil, }, Memory: &vmprovisionerv1.MemoryConfig{ SizeBytes: 2 * 1024 * 1024 * 1024, // 2GB MaxSizeBytes: 8 * 1024 * 1024 * 1024, // 8GB HotplugEnabled: true, + Backing: nil, }, Boot: &vmprovisionerv1.BootConfig{ - KernelPath: "/opt/vm-assets/vmlinux", - KernelArgs: "console=ttyS0 reboot=k panic=1 pci=off", + KernelPath: "/opt/vm-assets/vmlinux", + KernelArgs: "console=ttyS0 reboot=k panic=1 pci=off", + InitrdPath: "", + BootOptions: nil, }, Storage: []*vmprovisionerv1.StorageDevice{{ Id: "rootfs", @@ -221,16 +226,26 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro InterfaceType: "virtio-net", Mode: vmprovisionerv1.NetworkMode_NETWORK_MODE_DUAL_STACK, Ipv4Config: &vmprovisionerv1.IPv4Config{ - Dhcp: true, + Dhcp: true, + Address: "", + Netmask: "", + Gateway: "", + DnsServers: nil, }, Ipv6Config: &vmprovisionerv1.IPv6Config{ Slaac: true, PrivacyExtensions: true, + Address: "", + PrefixLength: 0, + Gateway: "", + DnsServers: nil, + LinkLocal: "", }, }}, Console: &vmprovisionerv1.ConsoleConfig{ Enabled: true, Output: "/tmp/standard-vm-console.log", + Input: "", ConsoleType: "serial", }, Metadata: map[string]string{ @@ -248,19 +263,17 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro // MOCK: Bypassing metald CreateVm call due to missing VM infrastructure // TODO: Remove this mock and use real metald call once VM assets are available w.logger.Info("MOCK: Simulating VM creation request", "docker_image", req.DockerImage) - + // Generate realistic mock VM ID and response mockVMID := uid.New("vm") // Generate mock VM ID resp := &vmprovisionerv1.CreateVmResponse{ VmId: mockVMID, State: vmprovisionerv1.VmState_VM_STATE_CREATED, -======= - } - + w.logger.Info("MOCK: VM creation simulated successfully", "vm_id", mockVMID, "docker_image", req.DockerImage) - w.logger.Info("VM created successfully", "vm_id", resp.VmId, "state", resp.State.String(), "docker_image", req.DockerImage) + w.logger.Info("VM created successfully", "vm_id", resp.GetVmId(), "state", resp.GetState().String(), "docker_image", req.DockerImage) return resp, nil }) @@ -269,7 +282,7 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro return err } - w.logger.Info("VM creation completed", "vm_id", createResult.VmId, "state", createResult.State.String()) + w.logger.Info("VM creation completed", "vm_id", createResult.GetVmId(), "state", createResult.GetState().String()) // Step 8: Log building rootfs err = hydra.StepVoid(ctx, "log-building-rootfs", func(stepCtx context.Context) error { @@ -277,7 +290,7 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro VersionID: req.VersionID, Status: "building_rootfs", Message: sql.NullString{String: fmt.Sprintf("Building rootfs from Docker image: %s", req.DockerImage), Valid: true}, - ErrorMessage: sql.NullString{}, + ErrorMessage: sql.NullString{String: "", Valid: false}, CreatedAt: time.Now().UnixMilli(), }) }) @@ -292,7 +305,7 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro VersionID: req.VersionID, Status: "uploading_rootfs", Message: sql.NullString{String: "Uploading rootfs image to storage", Valid: true}, - ErrorMessage: sql.NullString{}, + ErrorMessage: sql.NullString{String: "", Valid: false}, CreatedAt: time.Now().UnixMilli(), }) }) @@ -325,7 +338,7 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro VersionID: req.VersionID, Status: "creating_vm", Message: sql.NullString{String: fmt.Sprintf("Creating VM for version: %s", req.VersionID), Valid: true}, - ErrorMessage: sql.NullString{}, + ErrorMessage: sql.NullString{String: "", Valid: false}, CreatedAt: time.Now().UnixMilli(), }) }) @@ -354,42 +367,47 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro } // Step 13: Poll VM status (network calls to metald) - w.logger.Info("starting VM status polling", "vm_id", createResult.VmId, "max_attempts", 30) + w.logger.Info("starting VM status polling", "vm_id", createResult.GetVmId(), "max_attempts", 30) _, err = hydra.Step(ctx, "metald-poll-vm-status", func(stepCtx context.Context) (*struct{}, error) { for attempt := 1; attempt <= 30; attempt++ { - w.logger.Info("checking VM status", "vm_id", createResult.VmId, "attempt", attempt) + w.logger.Info("checking VM status", "vm_id", createResult.GetVmId(), "attempt", attempt) // MOCK: Bypassing metald GetVmInfo call - simulating realistic VM preparation // TODO: Remove this mock and use real metald call once VM assets are available - w.logger.Info("MOCK: Simulating VM status request", "vm_id", createResult.VmId, "attempt", attempt) + w.logger.Info("MOCK: Simulating VM status request", "vm_id", createResult.GetVmId(), "attempt", attempt) + // Simulate realistic VM preparation progression var mockState vmprovisionerv1.VmState if attempt <= 2 { mockState = vmprovisionerv1.VmState_VM_STATE_UNSPECIFIED // Use UNSPECIFIED to simulate building state - w.logger.Info("MOCK: VM still building", "vm_id", createResult.VmId, "attempt", attempt) + w.logger.Info("MOCK: VM still building", "vm_id", createResult.GetVmId(), "attempt", attempt) } else { mockState = vmprovisionerv1.VmState_VM_STATE_CREATED - w.logger.Info("MOCK: VM preparation complete", "vm_id", createResult.VmId, "attempt", attempt) + w.logger.Info("MOCK: VM preparation complete", "vm_id", createResult.GetVmId(), "attempt", attempt) } - + resp := &vmprovisionerv1.GetVmInfoResponse{ - VmId: createResult.VmId, - State: mockState, + VmId: createResult.GetVmId(), + State: mockState, + Config: nil, + Metrics: nil, + BackendInfo: nil, + NetworkInfo: nil, } - w.logger.Info("VM status check", "vm_id", createResult.VmId, "state", resp.State.String(), "attempt", attempt) + w.logger.Info("VM status check", "vm_id", createResult.GetVmId(), "state", resp.GetState().String(), "attempt", attempt) // Check if VM is ready for boot - if resp.State == vmprovisionerv1.VmState_VM_STATE_CREATED || - resp.State == vmprovisionerv1.VmState_VM_STATE_RUNNING { - w.logger.Info("VM is ready", "vm_id", createResult.VmId, "state", resp.State.String()) + if resp.GetState() == vmprovisionerv1.VmState_VM_STATE_CREATED || + resp.GetState() == vmprovisionerv1.VmState_VM_STATE_RUNNING { + w.logger.Info("VM is ready", "vm_id", createResult.GetVmId(), "state", resp.GetState().String()) return &struct{}{}, nil } // Sleep before next attempt (except on last attempt) if attempt < 30 { - w.logger.Info("VM not ready yet, sleeping before next check", "vm_id", createResult.VmId, "state", resp.State.String(), "attempt", attempt, "sleep_duration", "1s") + w.logger.Info("VM not ready yet, sleeping before next check", "vm_id", createResult.GetVmId(), "state", resp.GetState().String(), "attempt", attempt, "sleep_duration", "1s") time.Sleep(1 * time.Second) } } @@ -398,48 +416,48 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro return nil, fmt.Errorf("VM polling timed out after 30 attempts (30 seconds)") }) if err != nil { - w.logger.Error("VM status polling failed", "error", err, "vm_id", createResult.VmId) + w.logger.Error("VM status polling failed", "error", err, "vm_id", createResult.GetVmId()) return err } // Step 14: Boot VM (network call to metald) _, err = hydra.Step(ctx, "metald-boot-vm", func(stepCtx context.Context) (*vmprovisionerv1.BootVmResponse, error) { - w.logger.Info("booting VM", "vm_id", createResult.VmId) + w.logger.Info("booting VM", "vm_id", createResult.GetVmId()) // MOCK: Bypassing metald BootVm call - simulating successful boot // TODO: Remove this mock and use real metald call once VM assets are available - w.logger.Info("MOCK: Simulating VM boot request", "vm_id", createResult.VmId) - + w.logger.Info("MOCK: Simulating VM boot request", "vm_id", createResult.GetVmId()) + // Simulate successful VM boot resp := &vmprovisionerv1.BootVmResponse{ Success: true, State: vmprovisionerv1.VmState_VM_STATE_RUNNING, } - - w.logger.Info("MOCK: VM boot simulated successfully", "vm_id", createResult.VmId) - if !resp.Success { - w.logger.Error("VM boot was not successful", "vm_id", createResult.VmId, "state", resp.State.String()) - return nil, fmt.Errorf("VM boot was not successful, state: %s", resp.State.String()) + w.logger.Info("MOCK: VM boot simulated successfully", "vm_id", createResult.GetVmId()) + + if !resp.GetSuccess() { + w.logger.Error("VM boot was not successful", "vm_id", createResult.GetVmId(), "state", resp.GetState().String()) + return nil, fmt.Errorf("VM boot was not successful, state: %s", resp.GetState().String()) } - w.logger.Info("VM booted successfully", "vm_id", createResult.VmId, "state", resp.State.String()) + w.logger.Info("VM booted successfully", "vm_id", createResult.GetVmId(), "state", resp.GetState().String()) return resp, nil }) if err != nil { - w.logger.Error("VM boot failed", "error", err, "vm_id", createResult.VmId) + w.logger.Error("VM boot failed", "error", err, "vm_id", createResult.GetVmId()) return err } - w.logger.Info("VM boot completed successfully", "vm_id", createResult.VmId) + w.logger.Info("VM boot completed successfully", "vm_id", createResult.GetVmId()) // Step 16: Log booting VM err = hydra.StepVoid(ctx, "log-booting-vm", func(stepCtx context.Context) error { return db.Query.InsertVersionStep(stepCtx, w.db.RW(), db.InsertVersionStepParams{ VersionID: req.VersionID, Status: "booting_vm", - Message: sql.NullString{String: fmt.Sprintf("VM booted successfully: %s", createResult.VmId), Valid: true}, - ErrorMessage: sql.NullString{}, + Message: sql.NullString{String: fmt.Sprintf("VM booted successfully: %s", createResult.GetVmId()), Valid: true}, + ErrorMessage: sql.NullString{String: "", Valid: false}, CreatedAt: time.Now().UnixMilli(), }) }) @@ -455,9 +473,9 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro // Generate hostnames for this deployment // Use Git info for hostname generation gitInfo := git.GetInfo() - branch := "main" // Default branch + branch := "main" // Default branch identifier := req.VersionID // Use full version ID as identifier - + if gitInfo.IsRepo { if gitInfo.Branch != "" { branch = gitInfo.Branch @@ -471,7 +489,6 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro // Replace underscores with dashes for valid hostname format cleanIdentifier := strings.ReplaceAll(identifier, "_", "-") hostname := fmt.Sprintf("%s-%s-%s.unkey.app", branch, cleanIdentifier, req.WorkspaceID) - // Create route entry routeID := uid.New("route") insertErr := db.Query.InsertHostnameRoute(stepCtx, w.db.RW(), db.InsertHostnameRouteParams{ @@ -509,7 +526,7 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro VersionID: req.VersionID, Status: "assigning_domains", Message: sql.NullString{String: message, Valid: true}, - ErrorMessage: sql.NullString{}, + ErrorMessage: sql.NullString{String: "", Valid: false}, CreatedAt: time.Now().UnixMilli(), }) }) @@ -550,7 +567,7 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro VersionID: req.VersionID, Status: "completed", Message: sql.NullString{String: "Version deployment completed successfully", Valid: true}, - ErrorMessage: sql.NullString{}, + ErrorMessage: sql.NullString{String: "", Valid: false}, CreatedAt: time.Now().UnixMilli(), }) }) @@ -559,7 +576,7 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro return err } - w.logger.Info("deployment workflow stage completed successfully", "version_id", req.VersionID, "vm_id", createResult.VmId) + w.logger.Info("deployment workflow stage completed successfully", "version_id", req.VersionID, "vm_id", createResult.GetVmId()) w.logger.Info("deployment workflow completed", "execution_id", ctx.ExecutionID(), diff --git a/go/apps/ctrl/services/version/get_version.go b/go/apps/ctrl/services/version/get_version.go index 2e9bb3703e..6a8cb1e149 100644 --- a/go/apps/ctrl/services/version/get_version.go +++ b/go/apps/ctrl/services/version/get_version.go @@ -66,8 +66,10 @@ func (s *Service) GetVersion( protoSteps := make([]*ctrlv1.VersionStep, len(versionSteps)) for i, step := range versionSteps { protoSteps[i] = &ctrlv1.VersionStep{ - Status: string(step.Status), - CreatedAt: step.CreatedAt, + Status: string(step.Status), + CreatedAt: step.CreatedAt, + Message: "", + ErrorMessage: "", } if step.Message.Valid { protoSteps[i].Message = step.Message.String diff --git a/go/cmd/version/main.go b/go/cmd/version/main.go index b1b86906f6..3df75e423f 100644 --- a/go/cmd/version/main.go +++ b/go/cmd/version/main.go @@ -126,7 +126,7 @@ func createAction(ctx context.Context, cmd *cli.Command) error { return runDeploymentSteps(ctx, cmd, workspaceID, projectID, branch, dockerImage, dockerfile, buildContext, commit, logger) } -func printDeploymentComplete(versionID, workspace, branch, commit string) { +func printDeploymentComplete(versionID, workspace, branch string) { // Use actual Git info for hostname generation gitInfo := git.GetInfo() identifier := versionID @@ -259,7 +259,9 @@ func runDeploymentSteps(ctx context.Context, cmd *cli.Command, workspace, projec fmt.Printf(" %s\n", line) } } - return fmt.Errorf("docker push failed: %w", err) + + fmt.Println("ignore push errors for now") + // return err } } @@ -326,7 +328,7 @@ func runDeploymentSteps(ctx context.Context, cmd *cli.Command, workspace, projec return fmt.Errorf("deployment failed: %w", err) } - printDeploymentComplete(versionID, workspace, branch, commit) + printDeploymentComplete(versionID, workspace, branch) return nil } @@ -389,10 +391,12 @@ func pollVersionStatus(ctx context.Context, logger logging.Logger, client ctrlv1 // displayVersionStep shows a version step with appropriate formatting func displayVersionStep(step *ctrlv1.VersionStep) { message := step.GetMessage() + // Display only the actual message from the database, indented under "Creating Version" if message != "" { fmt.Printf(" %s\n", message) } + // Show error message if present if step.GetErrorMessage() != "" { fmt.Printf(" Error: %s\n", step.GetErrorMessage())