diff --git a/go/deploy/metald/client/client.go b/go/deploy/metald/client/client.go index 030a10475e..712bd0307a 100644 --- a/go/deploy/metald/client/client.go +++ b/go/deploy/metald/client/client.go @@ -8,8 +8,8 @@ import ( "connectrpc.com/connect" "github.com/unkeyed/unkey/go/deploy/pkg/tls" - vmprovisionerv1 "github.com/unkeyed/unkey/go/gen/proto/metal/vmprovisioner/v1" - "github.com/unkeyed/unkey/go/gen/proto/metal/vmprovisioner/v1/vmprovisionerv1connect" + metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1" + "github.com/unkeyed/unkey/go/gen/proto/metald/v1/metaldv1connect" ) // This client provides a high-level interface for metald VM operations with proper authentication @@ -22,14 +22,7 @@ type Config struct { // UserID is the user identifier for authentication UserID string - // TenantID is the tenant identifier for data scoping - TenantID string - - // ProjectID identifies the tenants project - ProjectID string - - // EnvironmentID identifies the environment within the project - EnvironmentID string + DeploymentID string // TLS configuration TLSMode string // "disabled", "file", or "spiffe" @@ -46,7 +39,7 @@ type Config struct { // Client provides a high-level interface to metald services type Client struct { - vmService vmprovisionerv1connect.VmServiceClient + vmService metaldv1connect.VmServiceClient tlsProvider tls.Provider tenantID string projectID string @@ -92,25 +85,19 @@ func New(ctx context.Context, config Config) (*Client, error) { // Add authentication and tenant isolation transport httpClient.Transport = &tenantTransport{ - Base: httpClient.Transport, - ProjectID: config.ProjectID, - TenantID: config.TenantID, - EnvironmentID: config.EnvironmentID, + Base: httpClient.Transport, } // Create ConnectRPC client - vmService := vmprovisionerv1connect.NewVmServiceClient( + vmService := metaldv1connect.NewVmServiceClient( httpClient, config.ServerAddress, ) return &Client{ - vmService: vmService, - tlsProvider: tlsProvider, - tenantID: config.TenantID, - projectID: config.ProjectID, - environmentID: config.EnvironmentID, - serverAddr: config.ServerAddress, + vmService: vmService, + tlsProvider: tlsProvider, + serverAddr: config.ServerAddress, }, nil } @@ -123,167 +110,85 @@ func (c *Client) Close() error { } // CreateVM creates a new virtual machine with the specified configuration -func (c *Client) CreateVM(ctx context.Context, req *CreateVMRequest) (*CreateVMResponse, error) { - // Convert to protobuf request - pbReq := &vmprovisionerv1.CreateVmRequest{ - VmId: req.VMID, - Config: req.Config, - TenantId: c.tenantID, - } - - resp, err := c.vmService.CreateVm(ctx, connect.NewRequest(pbReq)) +func (c *Client) CreateVM(ctx context.Context, req *metaldv1.CreateVmRequest) (*metaldv1.CreateVmResponse, error) { + resp, err := c.vmService.CreateVm(ctx, connect.NewRequest(req)) if err != nil { return nil, fmt.Errorf("failed to create VM: %w", err) } - - return &CreateVMResponse{ - VMID: resp.Msg.VmId, - State: resp.Msg.State, - }, nil + return resp.Msg, nil } // BootVM starts a created virtual machine -func (c *Client) BootVM(ctx context.Context, vmID string) (*BootVMResponse, error) { - req := &vmprovisionerv1.BootVmRequest{ - VmId: vmID, - } +func (c *Client) BootVM(ctx context.Context, req *metaldv1.BootVmRequest) (*metaldv1.BootVmResponse, error) { resp, err := c.vmService.BootVm(ctx, connect.NewRequest(req)) if err != nil { return nil, fmt.Errorf("failed to boot VM: %w", err) } - - return &BootVMResponse{ - Success: resp.Msg.Success, - State: resp.Msg.State, - }, nil + return resp.Msg, nil } // ShutdownVM gracefully stops a running virtual machine -func (c *Client) ShutdownVM(ctx context.Context, req *ShutdownVMRequest) (*ShutdownVMResponse, error) { - pbReq := &vmprovisionerv1.ShutdownVmRequest{ - VmId: req.VMID, - Force: req.Force, - TimeoutSeconds: int32(req.TimeoutSeconds), - } - - resp, err := c.vmService.ShutdownVm(ctx, connect.NewRequest(pbReq)) +func (c *Client) ShutdownVM(ctx context.Context, req *metaldv1.ShutdownVmRequest) (*metaldv1.ShutdownVmResponse, error) { + resp, err := c.vmService.ShutdownVm(ctx, connect.NewRequest(req)) if err != nil { return nil, fmt.Errorf("failed to shutdown VM: %w", err) } - - return &ShutdownVMResponse{ - Success: resp.Msg.Success, - State: resp.Msg.State, - }, nil + return resp.Msg, nil } // DeleteVM removes a virtual machine -func (c *Client) DeleteVM(ctx context.Context, req *DeleteVMRequest) (*DeleteVMResponse, error) { - pbReq := &vmprovisionerv1.DeleteVmRequest{ - VmId: req.VMID, - Force: req.Force, - } - - resp, err := c.vmService.DeleteVm(ctx, connect.NewRequest(pbReq)) +func (c *Client) DeleteVM(ctx context.Context, req *metaldv1.DeleteVmRequest) (*metaldv1.DeleteVmResponse, error) { + resp, err := c.vmService.DeleteVm(ctx, connect.NewRequest(req)) if err != nil { return nil, fmt.Errorf("failed to delete VM: %w", err) } - - return &DeleteVMResponse{ - Success: resp.Msg.Success, - }, nil + return resp.Msg, nil } // GetVMInfo retrieves detailed information about a virtual machine -func (c *Client) GetVMInfo(ctx context.Context, vmID string) (*VMInfo, error) { - req := &vmprovisionerv1.GetVmInfoRequest{ - VmId: vmID, - } - +func (c *Client) GetVMInfo(ctx context.Context, req *metaldv1.GetVmInfoRequest) (*metaldv1.GetVmInfoResponse, error) { resp, err := c.vmService.GetVmInfo(ctx, connect.NewRequest(req)) if err != nil { return nil, fmt.Errorf("failed to get VM info: %w", err) } - - return &VMInfo{ - VMID: resp.Msg.VmId, - State: resp.Msg.State, - Config: resp.Msg.Config, - Metrics: resp.Msg.Metrics, - NetworkInfo: resp.Msg.NetworkInfo, - }, nil + return resp.Msg, nil } // ListVMs retrieves a list of virtual machines for the authenticated customer -func (c *Client) ListVMs(ctx context.Context, req *ListVMsRequest) (*ListVMsResponse, error) { - pbReq := &vmprovisionerv1.ListVmsRequest{ - PageSize: req.PageSize, - PageToken: req.PageToken, - } - - resp, err := c.vmService.ListVms(ctx, connect.NewRequest(pbReq)) +func (c *Client) ListVMs(ctx context.Context, req *metaldv1.ListVmsRequest) (*metaldv1.ListVmsResponse, error) { + resp, err := c.vmService.ListVms(ctx, connect.NewRequest(req)) if err != nil { return nil, fmt.Errorf("failed to list VMs: %w", err) } - - return &ListVMsResponse{ - VMs: resp.Msg.Vms, - NextPageToken: resp.Msg.NextPageToken, - TotalCount: resp.Msg.TotalCount, - }, nil + return resp.Msg, nil } // PauseVM pauses a running virtual machine -func (c *Client) PauseVM(ctx context.Context, vmID string) (*PauseVMResponse, error) { - req := &vmprovisionerv1.PauseVmRequest{ - VmId: vmID, - } - +func (c *Client) PauseVM(ctx context.Context, req *metaldv1.PauseVmRequest) (*metaldv1.PauseVmResponse, error) { resp, err := c.vmService.PauseVm(ctx, connect.NewRequest(req)) if err != nil { return nil, fmt.Errorf("failed to pause VM: %w", err) } - - return &PauseVMResponse{ - Success: resp.Msg.Success, - State: resp.Msg.State, - }, nil + return resp.Msg, nil } // ResumeVM resumes a paused virtual machine -func (c *Client) ResumeVM(ctx context.Context, vmID string) (*ResumeVMResponse, error) { - req := &vmprovisionerv1.ResumeVmRequest{ - VmId: vmID, - } - +func (c *Client) ResumeVM(ctx context.Context, req *metaldv1.ResumeVmRequest) (*metaldv1.ResumeVmResponse, error) { resp, err := c.vmService.ResumeVm(ctx, connect.NewRequest(req)) if err != nil { return nil, fmt.Errorf("failed to resume VM: %w", err) } - - return &ResumeVMResponse{ - Success: resp.Msg.Success, - State: resp.Msg.State, - }, nil + return resp.Msg, nil } // RebootVM restarts a virtual machine -func (c *Client) RebootVM(ctx context.Context, req *RebootVMRequest) (*RebootVMResponse, error) { - pbReq := &vmprovisionerv1.RebootVmRequest{ - VmId: req.VMID, - Force: req.Force, - } - - resp, err := c.vmService.RebootVm(ctx, connect.NewRequest(pbReq)) +func (c *Client) RebootVM(ctx context.Context, req *metaldv1.RebootVmRequest) (*metaldv1.RebootVmResponse, error) { + resp, err := c.vmService.RebootVm(ctx, connect.NewRequest(req)) if err != nil { return nil, fmt.Errorf("failed to reboot VM: %w", err) } - - return &RebootVMResponse{ - Success: resp.Msg.Success, - State: resp.Msg.State, - }, nil + return resp.Msg, nil } // GetTenantID returns the tenant ID associated with this client @@ -296,6 +201,42 @@ func (c *Client) GetServerAddress() string { return c.serverAddr } +// CreateDeployment creates a new deployment with multiple VMs +func (c *Client) CreateDeployment(ctx context.Context, req *metaldv1.CreateDeploymentRequest) (*metaldv1.CreateDeploymentResponse, error) { + resp, err := c.vmService.CreateDeployment(ctx, connect.NewRequest(req)) + if err != nil { + return nil, fmt.Errorf("failed to create deployment: %w", err) + } + return resp.Msg, nil +} + +// UpdateDeployment updates an existing deployment +func (c *Client) UpdateDeployment(ctx context.Context, req *metaldv1.UpdateDeploymentRequest) (*metaldv1.UpdateDeploymentResponse, error) { + resp, err := c.vmService.UpdateDeployment(ctx, connect.NewRequest(req)) + if err != nil { + return nil, fmt.Errorf("failed to update deployment: %w", err) + } + return resp.Msg, nil +} + +// DeleteDeployment deletes an existing deployment +func (c *Client) DeleteDeployment(ctx context.Context, req *metaldv1.DeleteDeploymentRequest) (*metaldv1.DeleteDeploymentResponse, error) { + resp, err := c.vmService.DeleteDeployment(ctx, connect.NewRequest(req)) + if err != nil { + return nil, fmt.Errorf("failed to delete deployment: %w", err) + } + return resp.Msg, nil +} + +// GetDeployment retrieves information about a deployment +func (c *Client) GetDeployment(ctx context.Context, req *metaldv1.GetDeploymentRequest) (*metaldv1.GetDeploymentResponse, error) { + resp, err := c.vmService.GetDeployment(ctx, connect.NewRequest(req)) + if err != nil { + return nil, fmt.Errorf("failed to get deployment: %w", err) + } + return resp.Msg, nil +} + // tenantTransport adds authentication and tenant isolation headers to all requests type tenantTransport struct { Base http.RoundTripper diff --git a/go/deploy/metald/client/cmd/metald-cli/main.go b/go/deploy/metald/client/cmd/metald-cli/main.go index f63895fbd2..440eaef58a 100644 --- a/go/deploy/metald/client/cmd/metald-cli/main.go +++ b/go/deploy/metald/client/cmd/metald-cli/main.go @@ -10,32 +10,35 @@ import ( "time" "github.com/unkeyed/unkey/go/deploy/metald/client" - vmprovisionerv1 "github.com/unkeyed/unkey/go/gen/proto/metal/vmprovisioner/v1" + metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1" +) + +var ( + serverAddr = flag.String("server", getEnvOrDefault("UNKEY_METALD_SERVER_ADDRESS", "https://localhost:8080"), "metald server address") + userID = flag.String("user", getEnvOrDefault("UNKEY_METALD_USER_ID", "cli-user"), "user ID for authentication") + tlsMode = flag.String("tls-mode", getEnvOrDefault("UNKEY_METALD_TLS_MODE", "spiffe"), "TLS mode: disabled, file, or spiffe") + spiffeSocket = flag.String("spiffe-socket", getEnvOrDefault("UNKEY_METALD_SPIFFE_SOCKET", "/var/lib/spire/agent/agent.sock"), "SPIFFE agent socket path") + tlsCert = flag.String("tls-cert", "", "TLS certificate file (for file mode)") + tlsKey = flag.String("tls-key", "", "TLS key file (for file mode)") + tlsCA = flag.String("tls-ca", "", "TLS CA file (for file mode)") + timeout = flag.Duration("timeout", 30*time.Second, "request timeout") + jsonOutput = flag.Bool("json", false, "output results as JSON") + + // VM configuration options + configFile = flag.String("config", "", "path to VM configuration file (JSON)") + template = flag.String("template", "standard", "VM template: minimal, standard, high-cpu, high-memory, development") + cpuCount = flag.Uint("cpu", 0, "number of vCPUs (overrides template)") + memoryMB = flag.Uint64("memory", 0, "memory in MB (overrides template)") + dockerImage = flag.String("docker-image", "", "Docker image to run in VM") + forceBuild = flag.Bool("force-build", false, "force rebuild assets even if cached versions exist") + + // Deployment configuration options + deploymentID = flag.String("deployment-id", "", "deployment ID for deployment operations") + image = flag.String("image", "", "container image for deployment") + vmCount = flag.Uint("vm-count", 1, "number of VMs in deployment") ) func main() { - var ( - serverAddr = flag.String("server", getEnvOrDefault("UNKEY_METALD_SERVER_ADDRESS", "https://localhost:8080"), "metald server address") - userID = flag.String("user", getEnvOrDefault("UNKEY_METALD_USER_ID", "cli-user"), "user ID for authentication") - tenantID = flag.String("tenant", getEnvOrDefault("UNKEY_METALD_TENANT_ID", "cli-tenant"), "tenant ID for data scoping") - projectID = flag.String("project-id", getEnvOrDefault("UNKEY_METALD_PROJECT_ID", "metald-cli-test"), "project ID for data scoping") - environmentID = flag.String("environment-id", getEnvOrDefault("UNKEY_METALD_ENVIRONMENT_ID", "development"), "environment ID for data scoping") - tlsMode = flag.String("tls-mode", getEnvOrDefault("UNKEY_METALD_TLS_MODE", "spiffe"), "TLS mode: disabled, file, or spiffe") - spiffeSocket = flag.String("spiffe-socket", getEnvOrDefault("UNKEY_METALD_SPIFFE_SOCKET", "/var/lib/spire/agent/agent.sock"), "SPIFFE agent socket path") - tlsCert = flag.String("tls-cert", "", "TLS certificate file (for file mode)") - tlsKey = flag.String("tls-key", "", "TLS key file (for file mode)") - tlsCA = flag.String("tls-ca", "", "TLS CA file (for file mode)") - timeout = flag.Duration("timeout", 30*time.Second, "request timeout") - jsonOutput = flag.Bool("json", false, "output results as JSON") - - // VM configuration options - configFile = flag.String("config", "", "path to VM configuration file (JSON)") - template = flag.String("template", "standard", "VM template: minimal, standard, high-cpu, high-memory, development") - cpuCount = flag.Uint("cpu", 0, "number of vCPUs (overrides template)") - memoryMB = flag.Uint64("memory", 0, "memory in MB (overrides template)") - dockerImage = flag.String("docker-image", "", "Docker image to run in VM") - forceBuild = flag.Bool("force-build", false, "force rebuild assets even if cached versions exist") - ) flag.Parse() if flag.NArg() == 0 { @@ -49,9 +52,6 @@ func main() { config := client.Config{ ServerAddress: *serverAddr, UserID: *userID, - TenantID: *tenantID, - ProjectID: *projectID, - EnvironmentID: *environmentID, TLSMode: *tlsMode, SPIFFESocketPath: *spiffeSocket, TLSCertFile: *tlsCert, @@ -99,10 +99,14 @@ func main() { handleReboot(ctx, metaldClient, *jsonOutput) case "create-and-boot": handleCreateAndBoot(ctx, metaldClient, vmConfigOptions, *jsonOutput) - case "config-gen": - handleConfigGen(vmConfigOptions, *jsonOutput) - case "config-validate": - handleConfigValidate(*configFile, *jsonOutput) + case "create-deployment": + handleCreateDeployment(ctx, metaldClient, *deploymentID, *image, uint32(*vmCount), uint32(*cpuCount), *memoryMB, *jsonOutput) + case "update-deployment": + handleUpdateDeployment(ctx, metaldClient, *deploymentID, *image, uint32(*vmCount), uint32(*cpuCount), *memoryMB, *jsonOutput) + case "delete-deployment": + handleDeleteDeployment(ctx, metaldClient, *deploymentID, *jsonOutput) + case "get-deployment": + handleGetDeployment(ctx, metaldClient, *deploymentID, *jsonOutput) default: fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command) printUsage() @@ -111,7 +115,7 @@ func main() { } func printUsage() { - fmt.Printf(`metald-cli - CLI tool for metald VM operations + fmt.Printf(`%s - CLI tool for metald VM operations Usage: %s [flags] [args...] @@ -129,47 +133,12 @@ Commands: config-gen Generate a VM configuration file config-validate Validate a VM configuration file -Environment Variables: - UNKEY_METALD_SERVER_ADDRESS Server address (default: https://localhost:8080) - UNKEY_METALD_USER_ID User ID for authentication (default: cli-user) - UNKEY_METALD_TENANT_ID Tenant ID for data scoping (default: cli-tenant) - UNKEY_METALD_TLS_MODE TLS mode (default: spiffe) - UNKEY_METALD_SPIFFE_SOCKET SPIFFE socket path (default: /var/lib/spire/agent/agent.sock) - -VM Configuration Options: - -config Use VM configuration from JSON file - -template Use built-in template (minimal, standard, high-cpu, high-memory, development) - -cpu Override CPU count from template - -memory Override memory in MB from template - -docker-image Configure VM for Docker image - -force-build Force rebuild assets even if cached versions exist - -Examples: - # Create and boot a VM with SPIFFE authentication - %s -user=prod-user-123 -tenant=prod-tenant-456 create-and-boot - - # Create VM from configuration file - %s -config=my-vm.json create - - # Create VM with template and overrides - %s -template=high-cpu -memory=4096 create-and-boot - - # Create VM for Docker image - %s -docker-image=nginx:alpine create-and-boot - - # Create VM for Docker image with force build (bypass cache) - %s -docker-image=nginx:alpine -force-build create-and-boot - - # Generate configuration file - %s -template=development config-gen > dev-vm.json - - # List VMs with disabled TLS (development) - %s -tls-mode=disabled -server=http://localhost:8080 list - - # Get VM info with JSON output - %s info vm-12345 -json - -`, os.Args[0], os.Args[0], os.Args[0], os.Args[0], os.Args[0], os.Args[0], os.Args[0], os.Args[0], os.Args[0]) + Deployment Commands: + create-deployment Create a new deployment + update-deployment Update an existing deployment + delete-deployment Delete a deployment + get-deployment Get deployment information +`, os.Args[0], os.Args[0]) } // VMConfigOptions holds options for VM configuration @@ -182,64 +151,6 @@ type VMConfigOptions struct { ForceBuild bool } -// createVMConfig creates a VM configuration from the provided options -func createVMConfig(options VMConfigOptions) (*vmprovisionerv1.VmConfig, error) { - // If config file is specified, load from file - if options.ConfigFile != "" { - configFile, err := client.LoadVMConfigFromFile(options.ConfigFile) - if err != nil { - return nil, fmt.Errorf("failed to load config file: %w", err) - } - return configFile.ToVMConfig() - } - - builder := client.NewVMConfigBuilder() - - // Apply Docker image configuration if specified - if options.DockerImage != "" { - builder.ForDockerImage(options.DockerImage) - if options.ForceBuild { - builder.ForceBuild(true) - } - } - - // Apply overrides - if options.CPUCount > 0 { - // Keep max CPU at 2x current CPU or original max, whichever is higher - maxCPU := options.CPUCount * 2 - originalMaxCPU := uint32(builder.Build().Cpu.MaxVcpuCount) - if originalMaxCPU > maxCPU { - maxCPU = originalMaxCPU - } - builder.WithCPU(options.CPUCount, maxCPU) - } - - if options.MemoryMB > 0 { - // Keep max memory at 2x current memory or original max, whichever is higher - maxMemoryMB := options.MemoryMB * 2 - originalMaxMB := uint64(builder.Build().Memory.MaxSizeBytes / (1024 * 1024)) - if originalMaxMB > maxMemoryMB { - maxMemoryMB = originalMaxMB - } - builder.WithMemoryMB(options.MemoryMB, maxMemoryMB, builder.Build().Memory.HotplugEnabled) - } - - // Add CLI metadata - builder.AddMetadata("created_by", "metald-cli") - builder.AddMetadata("creation_time", time.Now().Format(time.RFC3339)) - - // Validate configuration - config := builder.Build() - if err := client.ValidateVMConfig(config); err != nil { - return nil, fmt.Errorf("VM configuration validation failed: %w", err) - } - - fmt.Printf("DEBUG: Final VM config:\n") - outputJSON(config) - - return config, nil -} - func handleCreate(ctx context.Context, metaldClient *client.Client, options VMConfigOptions, jsonOutput bool) { vmID := "" if flag.NArg() > 1 { @@ -247,56 +158,17 @@ func handleCreate(ctx context.Context, metaldClient *client.Client, options VMCo } // Create VM configuration from options - config, err := createVMConfig(options) - if err != nil { - log.Fatalf("Failed to create VM configuration: %v", err) - } - // DEBUG: Log config right before sending - fmt.Printf("DEBUG CLIENT: Sending VM config metadata:\n") - outputJSON(config) - for i, storage := range config.Storage { - fmt.Printf("DEBUG CLIENT: Storage[%d]: id=%s, path=%s, isRoot=%v, options=%v\n", - i, storage.Id, storage.Path, storage.IsRootDevice, storage.Options) + req := &metaldv1.CreateVmRequest{ + VmId: vmID, + Config: &metaldv1.VmConfig{}, } - req := &client.CreateVMRequest{ - VMID: vmID, - Config: config, - } - - resp, err := metaldClient.CreateVM(ctx, req) + // DO STUFF + _, err := metaldClient.CreateVM(ctx, req) if err != nil { log.Fatalf("Failed to create VM: %v", err) } - - // Fetch VM info to get IP address - var ipAddress string - vmInfo, err := metaldClient.GetVMInfo(ctx, resp.VMID) - if err != nil { - // Don't fail on network info error, just log it - fmt.Fprintf(os.Stderr, "Warning: Could not fetch IP address: %v\n", err) - } else if vmInfo.NetworkInfo != nil { - ipAddress = vmInfo.NetworkInfo.IpAddress - } - - if jsonOutput { - result := map[string]any{ - "vm_id": resp.VMID, - "state": resp.State.String(), - } - if ipAddress != "" { - result["ip_address"] = ipAddress - } - outputJSON(result) - } else { - fmt.Printf("VM created successfully:\n") - fmt.Printf(" VM ID: %s\n", resp.VMID) - fmt.Printf(" State: %s\n", resp.State.String()) - if ipAddress != "" { - fmt.Printf(" IP Address: %s\n", ipAddress) - } - } } func handleBoot(ctx context.Context, metaldClient *client.Client, jsonOutput bool) { @@ -305,21 +177,22 @@ func handleBoot(ctx context.Context, metaldClient *client.Client, jsonOutput boo } vmID := flag.Arg(1) - resp, err := metaldClient.BootVM(ctx, vmID) + req := &metaldv1.BootVmRequest{ + VmId: vmID, + } + resp, err := metaldClient.BootVM(ctx, req) if err != nil { log.Fatalf("Failed to boot VM: %v", err) } if jsonOutput { outputJSON(map[string]interface{}{ - "vm_id": vmID, - "success": resp.Success, - "state": resp.State.String(), + "vm_id": vmID, + "state": resp.State.String(), }) } else { fmt.Printf("VM boot operation:\n") fmt.Printf(" VM ID: %s\n", vmID) - fmt.Printf(" Success: %v\n", resp.Success) fmt.Printf(" State: %s\n", resp.State.String()) } } @@ -335,8 +208,8 @@ func handleShutdown(ctx context.Context, metaldClient *client.Client, jsonOutput force = true } - req := &client.ShutdownVMRequest{ - VMID: vmID, + req := &metaldv1.ShutdownVmRequest{ + VmId: vmID, Force: force, TimeoutSeconds: 30, } @@ -348,15 +221,13 @@ func handleShutdown(ctx context.Context, metaldClient *client.Client, jsonOutput if jsonOutput { outputJSON(map[string]interface{}{ - "vm_id": vmID, - "success": resp.Success, - "state": resp.State.String(), - "force": force, + "vm_id": vmID, + "state": resp.State.String(), + "force": force, }) } else { fmt.Printf("VM shutdown operation:\n") fmt.Printf(" VM ID: %s\n", vmID) - fmt.Printf(" Success: %v\n", resp.Success) fmt.Printf(" State: %s\n", resp.State.String()) fmt.Printf(" Force: %v\n", force) } @@ -373,8 +244,8 @@ func handleDelete(ctx context.Context, metaldClient *client.Client, jsonOutput b force = true } - req := &client.DeleteVMRequest{ - VMID: vmID, + req := &metaldv1.DeleteVmRequest{ + VmId: vmID, Force: force, } @@ -403,7 +274,10 @@ func handleInfo(ctx context.Context, metaldClient *client.Client, jsonOutput boo } vmID := flag.Arg(1) - vmInfo, err := metaldClient.GetVMInfo(ctx, vmID) + req := &metaldv1.GetVmInfoRequest{ + VmId: vmID, + } + vmInfo, err := metaldClient.GetVMInfo(ctx, req) if err != nil { log.Fatalf("Failed to get VM info: %v", err) } @@ -412,42 +286,27 @@ func handleInfo(ctx context.Context, metaldClient *client.Client, jsonOutput boo outputJSON(vmInfo) } else { fmt.Printf("VM Information:\n") - fmt.Printf(" VM ID: %s\n", vmInfo.VMID) - fmt.Printf(" State: %s\n", vmInfo.State.String()) + fmt.Printf(" VM ID: %s\n", vmInfo.GetVmId()) + fmt.Printf(" State: %s\n", vmInfo.GetState().String()) if vmInfo.Config != nil { fmt.Printf(" Configuration:\n") - fmt.Printf(" CPUs: %d (max: %d)\n", vmInfo.Config.Cpu.VcpuCount, vmInfo.Config.Cpu.MaxVcpuCount) - fmt.Printf(" Memory: %d MB\n", vmInfo.Config.Memory.SizeBytes/(1024*1024)) - fmt.Printf(" Storage devices: %d\n", len(vmInfo.Config.Storage)) - fmt.Printf(" Network interfaces: %d\n", len(vmInfo.Config.Network)) + fmt.Printf(" CPUs: %d\n", vmInfo.Config.GetVcpuCount()) + fmt.Printf(" Memory: %d MiB\n", vmInfo.Config.GetMemorySizeMib()) + fmt.Printf(" IP: \n") // DO STUFF } if vmInfo.Metrics != nil { fmt.Printf(" Metrics:\n") fmt.Printf(" CPU usage: %.2f%%\n", vmInfo.Metrics.CpuUsagePercent) - fmt.Printf(" Memory usage: %d MB\n", vmInfo.Metrics.MemoryUsageBytes/(1024*1024)) + fmt.Printf(" Memory: %d MiB\n", vmInfo.Config.MemorySizeMib) fmt.Printf(" Uptime: %d seconds\n", vmInfo.Metrics.UptimeSeconds) } - - if vmInfo.NetworkInfo != nil { - fmt.Printf(" Network:\n") - fmt.Printf(" IP: %s\n", vmInfo.NetworkInfo.IpAddress) - fmt.Printf(" MAC: %s\n", vmInfo.NetworkInfo.MacAddress) - fmt.Printf(" TAP: %s\n", vmInfo.NetworkInfo.TapDevice) - - if len(vmInfo.NetworkInfo.PortMappings) > 0 { - fmt.Printf(" Port Mappings:\n") - for _, mapping := range vmInfo.NetworkInfo.PortMappings { - fmt.Printf(" %d:%d/%s\n", mapping.HostPort, mapping.ContainerPort, mapping.Protocol) - } - } - } } } func handleList(ctx context.Context, metaldClient *client.Client, jsonOutput bool) { - req := &client.ListVMsRequest{ + req := &metaldv1.ListVmsRequest{ PageSize: 50, } @@ -459,13 +318,12 @@ func handleList(ctx context.Context, metaldClient *client.Client, jsonOutput boo if jsonOutput { outputJSON(resp) } else { - fmt.Printf("VMs for tenant %s (total: %d):\n", metaldClient.GetTenantID(), resp.TotalCount) - for _, vm := range resp.VMs { + for _, vm := range resp.Vms { fmt.Printf(" - %s: %s (CPUs: %d, Memory: %d MB)\n", - vm.VmId, - vm.State.String(), - vm.VcpuCount, - vm.MemorySizeBytes/(1024*1024), + vm.GetVmId(), + vm.GetState().String(), + vm.GetVcpuCount(), + vm.GetMemorySizeMib(), ) } } @@ -477,21 +335,22 @@ func handlePause(ctx context.Context, metaldClient *client.Client, jsonOutput bo } vmID := flag.Arg(1) - resp, err := metaldClient.PauseVM(ctx, vmID) + req := &metaldv1.PauseVmRequest{ + VmId: vmID, + } + resp, err := metaldClient.PauseVM(ctx, req) if err != nil { log.Fatalf("Failed to pause VM: %v", err) } if jsonOutput { outputJSON(map[string]interface{}{ - "vm_id": vmID, - "success": resp.Success, - "state": resp.State.String(), + "vm_id": vmID, + "state": resp.State.String(), }) } else { fmt.Printf("VM pause operation:\n") fmt.Printf(" VM ID: %s\n", vmID) - fmt.Printf(" Success: %v\n", resp.Success) fmt.Printf(" State: %s\n", resp.State.String()) } } @@ -502,21 +361,22 @@ func handleResume(ctx context.Context, metaldClient *client.Client, jsonOutput b } vmID := flag.Arg(1) - resp, err := metaldClient.ResumeVM(ctx, vmID) + req := &metaldv1.ResumeVmRequest{ + VmId: vmID, + } + resp, err := metaldClient.ResumeVM(ctx, req) if err != nil { log.Fatalf("Failed to resume VM: %v", err) } if jsonOutput { outputJSON(map[string]any{ - "vm_id": vmID, - "success": resp.Success, - "state": resp.State.String(), + "vm_id": vmID, + "state": resp.State.String(), }) } else { fmt.Printf("VM resume operation:\n") fmt.Printf(" VM ID: %s\n", vmID) - fmt.Printf(" Success: %v\n", resp.Success) fmt.Printf(" State: %s\n", resp.State.String()) } } @@ -532,8 +392,8 @@ func handleReboot(ctx context.Context, metaldClient *client.Client, jsonOutput b force = true } - req := &client.RebootVMRequest{ - VMID: vmID, + req := &metaldv1.RebootVmRequest{ + VmId: vmID, Force: force, } @@ -544,15 +404,13 @@ func handleReboot(ctx context.Context, metaldClient *client.Client, jsonOutput b if jsonOutput { outputJSON(map[string]any{ - "vm_id": vmID, - "success": resp.Success, - "state": resp.State.String(), - "force": force, + "vm_id": vmID, + "state": resp.State.String(), + "force": force, }) } else { fmt.Printf("VM reboot operation:\n") fmt.Printf(" VM ID: %s\n", vmID) - fmt.Printf(" Success: %v\n", resp.Success) fmt.Printf(" State: %s\n", resp.State.String()) fmt.Printf(" Force: %v\n", force) } @@ -564,15 +422,9 @@ func handleCreateAndBoot(ctx context.Context, metaldClient *client.Client, optio vmID = flag.Arg(1) } - // Create VM configuration from options - config, err := createVMConfig(options) - if err != nil { - log.Fatalf("Failed to create VM configuration: %v", err) - } - - createReq := &client.CreateVMRequest{ - VMID: vmID, - Config: config, + createReq := &metaldv1.CreateVmRequest{ + VmId: vmID, + Config: &metaldv1.VmConfig{}, } log.Printf("createReq: %+v/n", createReq) createResp, err := metaldClient.CreateVM(ctx, createReq) @@ -584,195 +436,166 @@ func handleCreateAndBoot(ctx context.Context, metaldClient *client.Client, optio time.Sleep(2 * time.Second) // Boot VM - bootResp, err := metaldClient.BootVM(ctx, createResp.VMID) - if err != nil { - log.Fatalf("Failed to boot VM: %v", err) + bootReq := &metaldv1.BootVmRequest{ + VmId: createReq.GetVmId(), } - - // Wait a moment for VM to boot and get IP address - time.Sleep(3 * time.Second) - - // Fetch VM info to get IP address - var ipAddress string - vmInfo, err := metaldClient.GetVMInfo(ctx, createResp.VMID) + bootResp, err := metaldClient.BootVM(ctx, bootReq) if err != nil { - // Don't fail on network info error, just log it - fmt.Fprintf(os.Stderr, "Warning: Could not fetch IP address: %v\n", err) - } else if vmInfo.NetworkInfo != nil { - ipAddress = vmInfo.NetworkInfo.IpAddress + log.Fatalf("Failed to boot VM: %v", err) } if jsonOutput { result := map[string]any{ - "vm_id": createResp.VMID, + "vm_id": createReq.GetVmId(), "create_state": createResp.State.String(), - "boot_success": bootResp.Success, "boot_state": bootResp.State.String(), } - if ipAddress != "" { - result["ip_address"] = ipAddress - } outputJSON(result) } else { fmt.Printf("VM created and booted successfully:\n") - fmt.Printf(" VM ID: %s\n", createResp.VMID) + fmt.Printf(" VM ID: %s\n", createReq.GetVmId()) fmt.Printf(" Create State: %s\n", createResp.State.String()) - fmt.Printf(" Boot Success: %v\n", bootResp.Success) fmt.Printf(" Boot State: %s\n", bootResp.State.String()) - if ipAddress != "" { - fmt.Printf(" IP Address: %s\n", ipAddress) - } } } -func createDefaultVMConfig() *vmprovisionerv1.VmConfig { - return &vmprovisionerv1.VmConfig{ - Cpu: &vmprovisionerv1.CpuConfig{ - VcpuCount: 2, - MaxVcpuCount: 4, - }, - Memory: &vmprovisionerv1.MemoryConfig{ - SizeBytes: 1 * 1024 * 1024 * 1024, // 1GB - HotplugEnabled: true, - MaxSizeBytes: 4 * 1024 * 1024 * 1024, // 4GB max - }, - Boot: &vmprovisionerv1.BootConfig{ - KernelPath: "/opt/vm-assets/vmlinux", - KernelArgs: "console=ttyS0 reboot=k panic=1 pci=off", - }, - Storage: []*vmprovisionerv1.StorageDevice{ - { - Id: "rootfs", - Path: "/opt/vm-assets/rootfs.ext4", - ReadOnly: false, - IsRootDevice: true, - InterfaceType: "virtio-blk", - }, - }, - Network: []*vmprovisionerv1.NetworkInterface{ - { - Id: "eth0", - InterfaceType: "virtio-net", - Mode: vmprovisionerv1.NetworkMode_NETWORK_MODE_DUAL_STACK, - Ipv4Config: &vmprovisionerv1.IPv4Config{ - Dhcp: true, - }, - Ipv6Config: &vmprovisionerv1.IPv6Config{ - Slaac: true, - PrivacyExtensions: true, - }, - }, - }, - Console: &vmprovisionerv1.ConsoleConfig{ - Enabled: true, - Output: "/tmp/vm-console.log", - ConsoleType: "serial", - }, - Metadata: map[string]string{ - "purpose": "cli-created", - "environment": "development", - "tool": "metald-cli", +func handleCreateDeployment(ctx context.Context, metaldClient *client.Client, deploymentID, image string, vmCount, cpu uint32, memorySizeMB uint64, jsonOutput bool) { + if deploymentID == "" { + log.Fatal("Deployment ID is required for create-deployment command") + } + if image == "" { + log.Fatal("Image is required for create-deployment command") + } + + req := &metaldv1.CreateDeploymentRequest{ + Deployment: &metaldv1.DeploymentRequest{ + DeploymentId: deploymentID, + Image: image, + VmCount: vmCount, + Cpu: cpu, + MemorySizeMib: memorySizeMB, }, } -} -func outputJSON(data interface{}) { - encoder := json.NewEncoder(os.Stdout) - encoder.SetIndent("", " ") - if err := encoder.Encode(data); err != nil { - log.Fatalf("Failed to encode JSON: %v", err) + resp, err := metaldClient.CreateDeployment(ctx, req) + if err != nil { + log.Fatalf("Failed to create deployment: %v", err) + } + + if jsonOutput { + outputJSON(map[string]interface{}{ + "deployment_id": deploymentID, + "vm_ids": resp.VmIds, + "vm_count": len(resp.VmIds), + }) + } else { + fmt.Printf("Deployment created successfully:\n") + fmt.Printf(" Deployment ID: %s\n", deploymentID) + fmt.Printf(" VM Count: %d\n", len(resp.VmIds)) + fmt.Printf(" VM IDs:\n") + for _, vmID := range resp.VmIds { + fmt.Printf(" - %s\n", vmID) + } } } -func handleConfigGen(options VMConfigOptions, jsonOutput bool) { - // Create VM configuration - config, err := createVMConfig(options) - if err != nil { - log.Fatalf("Failed to create VM configuration: %v", err) +func handleUpdateDeployment(ctx context.Context, metaldClient *client.Client, deploymentID, image string, vmCount, cpu uint32, memorySizeMB uint64, jsonOutput bool) { + if deploymentID == "" { + log.Fatal("Deployment ID is required for update-deployment command") } - // Convert to config file format - templateName := options.Template - if templateName == "" { - templateName = "standard" + req := &metaldv1.UpdateDeploymentRequest{ + Deployment: &metaldv1.DeploymentRequest{ + DeploymentId: deploymentID, + Image: image, + VmCount: vmCount, + Cpu: cpu, + MemorySizeMib: memorySizeMB, + }, } - configFile := client.FromVMConfig(config, templateName, fmt.Sprintf("Generated %s VM configuration", templateName)) - configFile.Template = templateName - // Output as JSON - data, err := json.MarshalIndent(configFile, "", " ") + resp, err := metaldClient.UpdateDeployment(ctx, req) if err != nil { - log.Fatalf("Failed to marshal configuration: %v", err) + log.Fatalf("Failed to update deployment: %v", err) } - fmt.Printf("%s\n", data) + if jsonOutput { + outputJSON(map[string]interface{}{ + "deployment_id": deploymentID, + "vm_ids": resp.VmIds, + "vm_count": len(resp.VmIds), + }) + } else { + fmt.Printf("Deployment updated successfully:\n") + fmt.Printf(" Deployment ID: %s\n", deploymentID) + fmt.Printf(" VM Count: %d\n", len(resp.VmIds)) + fmt.Printf(" VM IDs:\n") + for _, vmID := range resp.VmIds { + fmt.Printf(" - %s\n", vmID) + } + } } -func handleConfigValidate(configFile string, jsonOutput bool) { - if configFile == "" { - log.Fatal("Configuration file path is required") +func handleDeleteDeployment(ctx context.Context, metaldClient *client.Client, deploymentID string, jsonOutput bool) { + if deploymentID == "" { + log.Fatal("Deployment ID is required for delete-deployment command") } - // Load configuration file - config, err := client.LoadVMConfigFromFile(configFile) - if err != nil { - if jsonOutput { - outputJSON(map[string]interface{}{ - "valid": false, - "error": err.Error(), - }) - } else { - fmt.Printf("Configuration validation failed: %v\n", err) - } - os.Exit(1) + req := &metaldv1.DeleteDeploymentRequest{ + DeploymentId: deploymentID, } - // Convert to VM config and validate - vmConfig, err := config.ToVMConfig() + _, err := metaldClient.DeleteDeployment(ctx, req) if err != nil { - if jsonOutput { - outputJSON(map[string]interface{}{ - "valid": false, - "error": err.Error(), - }) - } else { - fmt.Printf("Configuration conversion failed: %v\n", err) - } - os.Exit(1) + log.Fatalf("Failed to delete deployment: %v", err) } - // Validate the VM configuration - if err := client.ValidateVMConfig(vmConfig); err != nil { - if jsonOutput { - outputJSON(map[string]interface{}{ - "valid": false, - "error": err.Error(), - }) - } else { - fmt.Printf("Configuration validation failed: %v\n", err) - } - os.Exit(1) - } - - // Configuration is valid if jsonOutput { outputJSON(map[string]interface{}{ - "valid": true, - "name": config.Name, - "description": config.Description, - "template": config.Template, + "deployment_id": deploymentID, + "deleted": true, }) } else { - fmt.Printf("Configuration is valid:\n") - fmt.Printf(" Name: %s\n", config.Name) - fmt.Printf(" Description: %s\n", config.Description) - if config.Template != "" { - fmt.Printf(" Template: %s\n", config.Template) + fmt.Printf("Deployment deleted successfully:\n") + fmt.Printf(" Deployment ID: %s\n", deploymentID) + } +} + +func handleGetDeployment(ctx context.Context, metaldClient *client.Client, deploymentID string, jsonOutput bool) { + if deploymentID == "" { + log.Fatal("Deployment ID is required for get-deployment command") + } + + req := &metaldv1.GetDeploymentRequest{ + DeploymentId: deploymentID, + } + + resp, err := metaldClient.GetDeployment(ctx, req) + if err != nil { + log.Fatalf("Failed to get deployment: %v", err) + } + + if jsonOutput { + outputJSON(resp) + } else { + fmt.Printf("Deployment Information:\n") + fmt.Printf(" Deployment ID: %s\n", resp.DeploymentId) + fmt.Printf(" VM Count: %d\n", len(resp.Vms)) + fmt.Printf(" VMs:\n") + for _, vm := range resp.Vms { + fmt.Printf(" - ID: %s\n", vm.Id) + fmt.Printf(" Host: %s\n", vm.Host) + fmt.Printf(" State: %s\n", vm.State.String()) + fmt.Printf(" Port: %d\n", vm.Port) } - fmt.Printf(" CPU: %d vCPUs (max: %d)\n", config.CPU.VCPUCount, config.CPU.MaxVCPUCount) - fmt.Printf(" Memory: %d MB (max: %d MB)\n", config.Memory.SizeMB, config.Memory.MaxSizeMB) - fmt.Printf(" Storage devices: %d\n", len(config.Storage)) - fmt.Printf(" Network interfaces: %d\n", len(config.Network)) + } +} + +func outputJSON(data interface{}) { + encoder := json.NewEncoder(os.Stdout) + encoder.SetIndent("", " ") + if err := encoder.Encode(data); err != nil { + log.Fatalf("Failed to encode JSON: %v", err) } } diff --git a/go/deploy/metald/client/config.go b/go/deploy/metald/client/config.go deleted file mode 100644 index 5fdb244c66..0000000000 --- a/go/deploy/metald/client/config.go +++ /dev/null @@ -1,295 +0,0 @@ -package client - -import ( - "encoding/json" - "fmt" - "os" - - vmprovisionerv1 "github.com/unkeyed/unkey/go/gen/proto/metal/vmprovisioner/v1" -) - -// VMConfigFile represents a VM configuration that can be loaded from/saved to a file -type VMConfigFile struct { - // Name is a human-readable name for this configuration - Name string `json:"name"` - - // Description describes the purpose of this configuration - Description string `json:"description"` - - // Template is the base template to use (optional) - Template string `json:"template,omitempty"` - - // CPU configuration - CPU CPUConfig `json:"cpu"` - - // Memory configuration - Memory MemoryConfig `json:"memory"` - - // Boot configuration - Boot BootConfig `json:"boot"` - - // Storage devices - Storage []StorageConfig `json:"storage"` - - // Network interfaces - Network []NetworkConfig `json:"network"` - - // Console configuration - Console ConsoleConfig `json:"console"` - - // Metadata key-value pairs - Metadata map[string]string `json:"metadata"` -} - -// CPUConfig represents CPU configuration in a config file -type CPUConfig struct { - VCPUCount uint32 `json:"vcpu_count"` - MaxVCPUCount uint32 `json:"max_vcpu_count"` -} - -// MemoryConfig represents memory configuration in a config file -type MemoryConfig struct { - SizeMB uint64 `json:"size_mb"` - MaxSizeMB uint64 `json:"max_size_mb"` - HotplugEnabled bool `json:"hotplug_enabled"` -} - -// BootConfig represents boot configuration in a config file -type BootConfig struct { - KernelPath string `json:"kernel_path"` - InitrdPath string `json:"initrd_path,omitempty"` - KernelArgs string `json:"kernel_args"` -} - -// StorageConfig represents storage device configuration in a config file -type StorageConfig struct { - ID string `json:"id"` - Path string `json:"path"` - ReadOnly bool `json:"read_only"` - IsRootDevice bool `json:"is_root_device"` - InterfaceType string `json:"interface_type"` - Options map[string]string `json:"options,omitempty"` -} - -// NetworkConfig represents network interface configuration in a config file -type NetworkConfig struct { - ID string `json:"id"` - InterfaceType string `json:"interface_type"` - Mode string `json:"mode"` // "dual_stack", "ipv4_only", "ipv6_only" - IPv4 *IPv4Config `json:"ipv4,omitempty"` - IPv6 *IPv6Config `json:"ipv6,omitempty"` -} - -// IPv4Config represents IPv4 configuration in a config file -type IPv4Config struct { - DHCP bool `json:"dhcp"` - StaticIP string `json:"static_ip,omitempty"` - Gateway string `json:"gateway,omitempty"` - DNSServers []string `json:"dns_servers,omitempty"` -} - -// IPv6Config represents IPv6 configuration in a config file -type IPv6Config struct { - SLAAC bool `json:"slaac"` - PrivacyExtensions bool `json:"privacy_extensions"` - StaticIP string `json:"static_ip,omitempty"` - Gateway string `json:"gateway,omitempty"` - DNSServers []string `json:"dns_servers,omitempty"` -} - -// ConsoleConfig represents console configuration in a config file -type ConsoleConfig struct { - Enabled bool `json:"enabled"` - Output string `json:"output"` - ConsoleType string `json:"console_type"` -} - -// LoadVMConfigFromFile loads a VM configuration from a JSON file -func LoadVMConfigFromFile(filename string) (*VMConfigFile, error) { - data, err := os.ReadFile(filename) - if err != nil { - return nil, fmt.Errorf("failed to read config file %s: %w", filename, err) - } - - var config VMConfigFile - if err := json.Unmarshal(data, &config); err != nil { - return nil, fmt.Errorf("failed to parse config file %s: %w", filename, err) - } - - return &config, nil -} - -// ToVMConfig converts a VMConfigFile to a protobuf VmConfig -func (c *VMConfigFile) ToVMConfig() (*vmprovisionerv1.VmConfig, error) { - var builder *VMConfigBuilder - - builder = NewVMConfigBuilder() - - // Override with specific configuration - builder.WithCPU(c.CPU.VCPUCount, c.CPU.MaxVCPUCount) - builder.WithMemoryMB(c.Memory.SizeMB, c.Memory.MaxSizeMB, c.Memory.HotplugEnabled) - builder.WithBoot(c.Boot.KernelPath, c.Boot.InitrdPath, c.Boot.KernelArgs) - - // Clear storage and network from template - builder.config.Storage = []*vmprovisionerv1.StorageDevice{} - builder.config.Network = []*vmprovisionerv1.NetworkInterface{} - - // Add storage devices - for _, storage := range c.Storage { - interfaceType := storage.InterfaceType - if interfaceType == "" { - interfaceType = "virtio-blk" - } - builder.AddStorageWithOptions(storage.ID, storage.Path, storage.ReadOnly, - storage.IsRootDevice, interfaceType, storage.Options) - } - - // Add network interfaces - for _, network := range c.Network { - mode := parseNetworkMode(network.Mode) - interfaceType := network.InterfaceType - if interfaceType == "" { - interfaceType = "virtio-net" - } - - var ipv4Config *vmprovisionerv1.IPv4Config - var ipv6Config *vmprovisionerv1.IPv6Config - - if network.IPv4 != nil { - ipv4Config = &vmprovisionerv1.IPv4Config{ - Dhcp: network.IPv4.DHCP, - Address: network.IPv4.StaticIP, - Gateway: network.IPv4.Gateway, - DnsServers: network.IPv4.DNSServers, - } - } - - if network.IPv6 != nil { - ipv6Config = &vmprovisionerv1.IPv6Config{ - Slaac: network.IPv6.SLAAC, - PrivacyExtensions: network.IPv6.PrivacyExtensions, - Address: network.IPv6.StaticIP, - Gateway: network.IPv6.Gateway, - DnsServers: network.IPv6.DNSServers, - } - } - - builder.AddNetworkWithCustomConfig(network.ID, interfaceType, mode, ipv4Config, ipv6Config) - } - - // Configure console - builder.WithConsole(c.Console.Enabled, c.Console.Output, c.Console.ConsoleType) - - // Add metadata - if c.Metadata != nil { - builder.WithMetadata(c.Metadata) - } - - // Add config file metadata - builder.AddMetadata("config_name", c.Name) - builder.AddMetadata("config_description", c.Description) - - return builder.Build(), nil -} - -// FromVMConfig creates a VMConfigFile from a protobuf VmConfig -func FromVMConfig(config *vmprovisionerv1.VmConfig, name, description string) *VMConfigFile { - configFile := &VMConfigFile{ - Name: name, - Description: description, - CPU: CPUConfig{ - VCPUCount: uint32(config.Cpu.VcpuCount), - MaxVCPUCount: uint32(config.Cpu.MaxVcpuCount), - }, - Memory: MemoryConfig{ - SizeMB: uint64(config.Memory.SizeBytes / (1024 * 1024)), - MaxSizeMB: uint64(config.Memory.MaxSizeBytes / (1024 * 1024)), - HotplugEnabled: config.Memory.HotplugEnabled, - }, - Boot: BootConfig{ - KernelPath: config.Boot.KernelPath, - InitrdPath: config.Boot.InitrdPath, - KernelArgs: config.Boot.KernelArgs, - }, - Storage: []StorageConfig{}, - Network: []NetworkConfig{}, - Console: ConsoleConfig{ - Enabled: config.Console.Enabled, - Output: config.Console.Output, - ConsoleType: config.Console.ConsoleType, - }, - Metadata: config.Metadata, - } - - // Convert storage devices - for _, storage := range config.Storage { - configFile.Storage = append(configFile.Storage, StorageConfig{ - ID: storage.Id, - Path: storage.Path, - ReadOnly: storage.ReadOnly, - IsRootDevice: storage.IsRootDevice, - InterfaceType: storage.InterfaceType, - Options: storage.Options, - }) - } - - // Convert network interfaces - for _, network := range config.Network { - netConfig := NetworkConfig{ - ID: network.Id, - InterfaceType: network.InterfaceType, - Mode: formatNetworkMode(network.Mode), - } - - if network.Ipv4Config != nil { - netConfig.IPv4 = &IPv4Config{ - DHCP: network.Ipv4Config.Dhcp, - StaticIP: network.Ipv4Config.Address, - Gateway: network.Ipv4Config.Gateway, - DNSServers: network.Ipv4Config.DnsServers, - } - } - - if network.Ipv6Config != nil { - netConfig.IPv6 = &IPv6Config{ - SLAAC: network.Ipv6Config.Slaac, - PrivacyExtensions: network.Ipv6Config.PrivacyExtensions, - StaticIP: network.Ipv6Config.Address, - Gateway: network.Ipv6Config.Gateway, - DNSServers: network.Ipv6Config.DnsServers, - } - } - - configFile.Network = append(configFile.Network, netConfig) - } - - return configFile -} - -// parseNetworkMode converts string to protobuf NetworkMode -func parseNetworkMode(mode string) vmprovisionerv1.NetworkMode { - switch mode { - case "ipv4_only": - return vmprovisionerv1.NetworkMode_NETWORK_MODE_IPV4_ONLY - case "ipv6_only": - return vmprovisionerv1.NetworkMode_NETWORK_MODE_IPV6_ONLY - case "dual_stack": - return vmprovisionerv1.NetworkMode_NETWORK_MODE_DUAL_STACK - default: - return vmprovisionerv1.NetworkMode_NETWORK_MODE_DUAL_STACK - } -} - -// formatNetworkMode converts protobuf NetworkMode to string -func formatNetworkMode(mode vmprovisionerv1.NetworkMode) string { - switch mode { - case vmprovisionerv1.NetworkMode_NETWORK_MODE_IPV4_ONLY: - return "ipv4_only" - case vmprovisionerv1.NetworkMode_NETWORK_MODE_IPV6_ONLY: - return "ipv6_only" - case vmprovisionerv1.NetworkMode_NETWORK_MODE_DUAL_STACK: - return "dual_stack" - default: - return "dual_stack" - } -} diff --git a/go/deploy/metald/client/go.mod b/go/deploy/metald/client/go.mod index 9c256ce241..9057a45dc6 100644 --- a/go/deploy/metald/client/go.mod +++ b/go/deploy/metald/client/go.mod @@ -10,16 +10,16 @@ require ( require ( github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/go-jose/go-jose/v4 v4.0.5 // indirect + github.com/go-jose/go-jose/v4 v4.1.1 // indirect github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/unkeyed/unkey/go/deploy/pkg/spiffe v0.0.0-00010101000000-000000000000 // indirect github.com/zeebo/errs v1.4.0 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/net v0.41.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/grpc v1.73.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect + google.golang.org/grpc v1.74.2 // indirect google.golang.org/protobuf v1.36.8 // indirect ) diff --git a/go/deploy/metald/client/go.sum b/go/deploy/metald/client/go.sum index ab71545fb2..616a8d0678 100644 --- a/go/deploy/metald/client/go.sum +++ b/go/deploy/metald/client/go.sum @@ -4,8 +4,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= -github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -20,8 +20,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= +github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -36,20 +36,18 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/go/deploy/metald/client/types.go b/go/deploy/metald/client/types.go deleted file mode 100644 index d1e5f4c1a7..0000000000 --- a/go/deploy/metald/client/types.go +++ /dev/null @@ -1,146 +0,0 @@ -package client - -import ( - vmprovisionerv1 "github.com/unkeyed/unkey/go/gen/proto/metal/vmprovisioner/v1" -) - -// AIDEV-NOTE: Type definitions for metald client requests and responses -// These provide a cleaner interface while wrapping the underlying protobuf types - -// CreateVMRequest represents a request to create a new virtual machine -type CreateVMRequest struct { - // VMID is the unique identifier for the VM (optional, will be generated if empty) - VMID string - - // Config is the VM configuration including CPU, memory, storage, and network - Config *vmprovisionerv1.VmConfig -} - -// CreateVMResponse represents the response from creating a virtual machine -type CreateVMResponse struct { - // VMID is the unique identifier of the created VM - VMID string - - // State is the current state of the VM after creation - State vmprovisionerv1.VmState -} - -// BootVMResponse represents the response from booting a virtual machine -type BootVMResponse struct { - // Success indicates if the boot operation was successful - Success bool - - // State is the current state of the VM after boot attempt - State vmprovisionerv1.VmState -} - -// ShutdownVMRequest represents a request to shutdown a virtual machine -type ShutdownVMRequest struct { - // VMID is the unique identifier of the VM to shutdown - VMID string - - // Force indicates whether to force shutdown if graceful shutdown fails - Force bool - - // TimeoutSeconds is the timeout for graceful shutdown before forcing (0 = no timeout) - TimeoutSeconds uint32 -} - -// ShutdownVMResponse represents the response from shutting down a virtual machine -type ShutdownVMResponse struct { - // Success indicates if the shutdown operation was successful - Success bool - - // State is the current state of the VM after shutdown attempt - State vmprovisionerv1.VmState -} - -// DeleteVMRequest represents a request to delete a virtual machine -type DeleteVMRequest struct { - // VMID is the unique identifier of the VM to delete - VMID string - - // Force indicates whether to force deletion even if VM is running - Force bool -} - -// DeleteVMResponse represents the response from deleting a virtual machine -type DeleteVMResponse struct { - // Success indicates if the deletion operation was successful - Success bool -} - -// VMInfo represents detailed information about a virtual machine -type VMInfo struct { - // VMID is the unique identifier of the VM - VMID string - - // State is the current state of the VM - State vmprovisionerv1.VmState - - // Config is the VM configuration - Config *vmprovisionerv1.VmConfig - - // Metrics contains runtime metrics for the VM - Metrics *vmprovisionerv1.VmMetrics - - // NetworkInfo contains network configuration and status - NetworkInfo *vmprovisionerv1.VmNetworkInfo -} - -// ListVMsRequest represents a request to list virtual machines -type ListVMsRequest struct { - // PageSize is the maximum number of VMs to return (default: 50, max: 100) - PageSize int32 - - // PageToken is the token for pagination (empty for first page) - PageToken string -} - -// ListVMsResponse represents the response from listing virtual machines -type ListVMsResponse struct { - // VMs is the list of virtual machines for the authenticated customer - VMs []*vmprovisionerv1.VmInfo - - // NextPageToken is the token for the next page (empty if no more pages) - NextPageToken string - - // TotalCount is the total number of VMs for the customer - TotalCount int32 -} - -// PauseVMResponse represents the response from pausing a virtual machine -type PauseVMResponse struct { - // Success indicates if the pause operation was successful - Success bool - - // State is the current state of the VM after pause attempt - State vmprovisionerv1.VmState -} - -// ResumeVMResponse represents the response from resuming a virtual machine -type ResumeVMResponse struct { - // Success indicates if the resume operation was successful - Success bool - - // State is the current state of the VM after resume attempt - State vmprovisionerv1.VmState -} - -// RebootVMRequest represents a request to reboot a virtual machine -type RebootVMRequest struct { - // VMID is the unique identifier of the VM to reboot - VMID string - - // Force indicates whether to force reboot (hard reset vs graceful restart) - Force bool -} - -// RebootVMResponse represents the response from rebooting a virtual machine -type RebootVMResponse struct { - // Success indicates if the reboot operation was successful - Success bool - - // State is the current state of the VM after reboot attempt - State vmprovisionerv1.VmState -} diff --git a/go/deploy/metald/cmd/metald/main.go b/go/deploy/metald/cmd/metald/main.go index 464dd45fbd..9a1a9a806a 100644 --- a/go/deploy/metald/cmd/metald/main.go +++ b/go/deploy/metald/cmd/metald/main.go @@ -337,9 +337,6 @@ func main() { // Get default interceptors (tenant auth, metrics, logging) sharedInterceptors := interceptors.NewDefaultInterceptors("metald", interceptorOpts...) - // Add authentication interceptor first (before tenant auth) - interceptorList = append(interceptorList, service.AuthenticationInterceptor(logger)) - // Add shared interceptors (convert UnaryInterceptorFunc to Interceptor) for _, interceptor := range sharedInterceptors { interceptorList = append(interceptorList, connect.Interceptor(interceptor)) diff --git a/go/deploy/metald/go.mod b/go/deploy/metald/go.mod index 40cff85e2d..a4d40e2616 100644 --- a/go/deploy/metald/go.mod +++ b/go/deploy/metald/go.mod @@ -1,30 +1,28 @@ module github.com/unkeyed/unkey/go/deploy/metald -go 1.24.4 +go 1.25 require ( connectrpc.com/connect v1.18.1 - github.com/docker/docker v28.2.2+incompatible - github.com/docker/go-connections v0.5.0 github.com/firecracker-microvm/firecracker-go-sdk v1.0.0 github.com/mattn/go-sqlite3 v1.14.28 - github.com/prometheus/client_golang v1.22.0 - github.com/stretchr/testify v1.11.0 + github.com/prometheus/client_golang v1.23.1 + github.com/stretchr/testify v1.11.1 github.com/unkeyed/unkey/go v0.0.0-00010101000000-000000000000 github.com/unkeyed/unkey/go/deploy/pkg/health v0.0.0-00010101000000-000000000000 github.com/unkeyed/unkey/go/deploy/pkg/observability/interceptors v0.0.0-00010101000000-000000000000 github.com/unkeyed/unkey/go/deploy/pkg/tls v0.0.0-00010101000000-000000000000 github.com/vishvananda/netlink v1.3.1 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 - go.opentelemetry.io/otel v1.37.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 + go.opentelemetry.io/otel v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 go.opentelemetry.io/otel/exporters/prometheus v0.59.0 - go.opentelemetry.io/otel/metric v1.37.0 - go.opentelemetry.io/otel/sdk v1.37.0 - go.opentelemetry.io/otel/sdk/metric v1.37.0 - go.opentelemetry.io/otel/trace v1.37.0 + go.opentelemetry.io/otel/metric v1.38.0 + go.opentelemetry.io/otel/sdk v1.38.0 + go.opentelemetry.io/otel/sdk/metric v1.38.0 + go.opentelemetry.io/otel/trace v1.38.0 golang.org/x/net v0.43.0 golang.org/x/sys v0.35.0 google.golang.org/protobuf v1.36.8 @@ -34,17 +32,12 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v5 v5.0.2 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/containerd/errdefs v0.3.0 // indirect - github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/fifo v1.1.0 // indirect - github.com/containerd/log v0.1.0 // indirect github.com/containernetworking/cni v1.3.0 // indirect github.com/containernetworking/plugins v1.7.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/distribution/reference v0.6.0 // indirect - github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-jose/go-jose/v4 v4.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -59,26 +52,20 @@ require ( github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-openapi/swag v0.23.1 // indirect github.com/go-openapi/validate v0.24.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/sys/atomicwriter v0.1.0 // indirect - github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/ulid v1.3.1 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/common v0.66.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect @@ -89,14 +76,14 @@ require ( github.com/zeebo/errs v1.4.0 // indirect go.mongodb.org/mongo-driver v1.17.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/proto/otlp v1.7.1 // indirect + go.opentelemetry.io/proto/otlp v1.8.0 // indirect golang.org/x/crypto v0.41.0 // indirect golang.org/x/sync v0.16.0 // indirect golang.org/x/text v0.28.0 // indirect - golang.org/x/time v0.12.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect - google.golang.org/grpc v1.74.2 // indirect + google.golang.org/grpc v1.75.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go/deploy/metald/go.sum b/go/deploy/metald/go.sum index c1bf0f2e42..a27f9cacc7 100644 --- a/go/deploy/metald/go.sum +++ b/go/deploy/metald/go.sum @@ -25,7 +25,6 @@ connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw= connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= @@ -93,8 +92,8 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= -github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -150,10 +149,6 @@ github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cE github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= -github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= -github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= -github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= -github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= @@ -173,8 +168,6 @@ github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= @@ -239,23 +232,15 @@ github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8l github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw= -github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= @@ -386,7 +371,6 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -460,14 +444,16 @@ github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= @@ -560,25 +546,16 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= -github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= -github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= -github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -617,12 +594,9 @@ github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1 github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= -github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= @@ -645,7 +619,6 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -657,8 +630,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.1 h1:w6gXMLQGgd0jXXlote9lRHMe0nG01EbnJT+C0EJru2Y= +github.com/prometheus/client_golang v1.23.1/go.mod h1:br8j//v2eg2K5Vvna5klK8Ku5pcU5r4ll73v6ik5dIQ= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -672,8 +645,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.66.0 h1:K/rJPHrG3+AoQs50r2+0t7zMnMzek2Vbv31OFVsMeVY= +github.com/prometheus/common v0.66.0/go.mod h1:Ux6NtV1B4LatamKE63tJBntoxD++xmtI/lK0VtEplN4= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -748,8 +721,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= -github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -808,28 +781,28 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0 h1:9PgnL3QNlj10uGxExowIDIZu66aVBwWhXmbOp1pa6RA= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0/go.mod h1:0ineDcLELf6JmKfuo0wvvhAVMuxWFYvkTin2iV4ydPQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo= go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= -go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= +go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= @@ -1043,8 +1016,6 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1093,6 +1064,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -1150,8 +1123,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= -google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1193,6 +1166,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -1200,10 +1174,8 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/go/deploy/metald/internal/service/auth.go b/go/deploy/metald/internal/service/auth.go index af9e9b7608..cf18fadaf2 100644 --- a/go/deploy/metald/internal/service/auth.go +++ b/go/deploy/metald/internal/service/auth.go @@ -1,223 +1,185 @@ package service -import ( - "context" - "fmt" - "log/slog" - "strings" - - "connectrpc.com/connect" - "go.opentelemetry.io/otel/baggage" -) - -// CustomerContext holds customer information extracted from authentication -type CustomerContext struct { - UserID string - TenantID string - ProjectID string - EnvironmentID string -} - -// AuthenticationInterceptor validates API requests and enforces customer isolation -func AuthenticationInterceptor(logger *slog.Logger) connect.UnaryInterceptorFunc { - return func(next connect.UnaryFunc) connect.UnaryFunc { - return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) { - // Extract API key from Authorization header - auth := req.Header().Get("Authorization") - if auth == "" { - logger.LogAttrs(ctx, slog.LevelWarn, "missing authorization header", - slog.String("procedure", req.Spec().Procedure), - ) - return nil, connect.NewError(connect.CodeUnauthenticated, - fmt.Errorf("authorization header required")) - } - - // Parse Bearer token - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - logger.LogAttrs(ctx, slog.LevelWarn, "invalid authorization format", - slog.String("procedure", req.Spec().Procedure), - ) - return nil, connect.NewError(connect.CodeUnauthenticated, - fmt.Errorf("authorization must be 'Bearer '")) - } - - token := parts[1] - - // Validate token and extract customer context - customerCtx, err := validateToken(ctx, token) - if err != nil { - logger.LogAttrs(ctx, slog.LevelWarn, "token validation failed", - slog.String("procedure", req.Spec().Procedure), - slog.String("error", err.Error()), - ) - return nil, connect.NewError(connect.CodeUnauthenticated, err) - } - - // Extract requested tenant ID from header and validate access - requestedTenantID := req.Header().Get("X-Tenant-ID") - requestedProjectID := req.Header().Get("X-Project-ID") - requestedEnvironmentID := req.Header().Get("X-Environment-ID") - logger.LogAttrs(ctx, slog.LevelInfo, "checking tenant access", - slog.String("procedure", req.Spec().Procedure), - slog.String("user_id", customerCtx.UserID), - slog.String("requested_tenant", requestedTenantID), - slog.String("requested_project", requestedProjectID), - slog.String("requested_environment", requestedEnvironmentID), - ) - - if requestedTenantID != "" { - // Validate that authenticated user can access the requested tenant - if err := validateTenantAccess(ctx, customerCtx, requestedTenantID); err != nil { - logger.LogAttrs(ctx, slog.LevelWarn, "tenant access denied", - slog.String("procedure", req.Spec().Procedure), - slog.String("user_id", customerCtx.UserID), - slog.String("requested_tenant", requestedTenantID), - slog.String("error", err.Error()), - ) - return nil, connect.NewError(connect.CodePermissionDenied, err) - } - logger.LogAttrs(ctx, slog.LevelInfo, "tenant access granted", - slog.String("procedure", req.Spec().Procedure), - slog.String("user_id", customerCtx.UserID), - slog.String("requested_tenant", requestedTenantID), - ) - } - - // Add customer context to baggage for downstream services - ctx = addCustomerContextToBaggage(ctx, customerCtx) - - // Log authenticated request - logger.LogAttrs(ctx, slog.LevelDebug, "authenticated request", - slog.String("procedure", req.Spec().Procedure), - slog.String("user_id", customerCtx.UserID), - slog.String("tenant_id", customerCtx.TenantID), - ) - - return next(ctx, req) - } - } -} - -// validateToken validates the API token and returns customer context -// TODO: Replace with your actual authentication mechanism (JWT, API keys, etc.) -func validateToken(ctx context.Context, token string) (*CustomerContext, error) { - _ = ctx - // - - // Development mode: Accept simple bearer tokens - if strings.HasPrefix(token, "dev_user_") { - userID := strings.TrimPrefix(token, "dev_user_") - if userID == "" { - return nil, fmt.Errorf("invalid development token format") - } - - return &CustomerContext{ - UserID: userID, - TenantID: "", // Tenant determined by X-Tenant-ID header - ProjectID: userID, - EnvironmentID: userID, - }, nil - } - - // Production token validation would go here - // Example: JWT validation, API key lookup, etc. - return nil, fmt.Errorf("invalid token format - use 'dev_user_' for development") -} - -// addCustomerContextToBaggage adds customer context to OpenTelemetry baggage -func addCustomerContextToBaggage(ctx context.Context, customerCtx *CustomerContext) context.Context { - // Create baggage with customer context - bag, err := baggage.Parse(fmt.Sprintf( - "user_id=%s,project_id=%s,tenant_id=%s,environment_id=%s", - customerCtx.UserID, - customerCtx.ProjectID, - customerCtx.TenantID, - customerCtx.EnvironmentID, - )) - if err != nil { - // Log error but continue - baggage is for observability, not security - slog.Default().WarnContext(ctx, "failed to create baggage", - slog.String("error", err.Error()), - ) - return ctx - } - - return baggage.ContextWithBaggage(ctx, bag) -} - -// ExtractTenantID extracts tenant ID from request context -func ExtractTenantID(ctx context.Context) (string, error) { - if requestBaggage := baggage.FromContext(ctx); len(requestBaggage.Members()) > 0 { - tenantID := requestBaggage.Member("tenant_id").Value() - if tenantID != "" { - return tenantID, nil - } - } - return "", fmt.Errorf("tenant_id not found in context") -} - -// ExtractEnvironmentID extracts tenant ID from request context -func ExtractEnvironmentID(ctx context.Context) (string, error) { - if requestBaggage := baggage.FromContext(ctx); len(requestBaggage.Members()) > 0 { - environmentID := requestBaggage.Member("environment_id").Value() - if environmentID != "" { - return environmentID, nil - } - } - return "", fmt.Errorf("environment_id not found in context") -} - -// ExtractProjectID extracts project ID from request context -func ExtractProjectID(ctx context.Context) (string, error) { - if requestBaggage := baggage.FromContext(ctx); len(requestBaggage.Members()) > 0 { - projectID := requestBaggage.Member("project_id").Value() - if projectID != "" { - return projectID, nil - } - } - return "", fmt.Errorf("project_id not found in context") -} - -// ExtractUserID extracts tenant ID from request context -func ExtractUserID(ctx context.Context) (string, error) { - if requestBaggage := baggage.FromContext(ctx); len(requestBaggage.Members()) > 0 { - userID := requestBaggage.Member("user_id").Value() - if userID != "" { - return userID, nil - } - } - return "", fmt.Errorf("tenant_id not found in context") -} - -// validateTenantAccess validates that the authenticated user can access the requested tenant -func validateTenantAccess(ctx context.Context, customerCtx *CustomerContext, requestedTenantID string) error { - // AIDEV-BUSINESS_RULE: Tenant access validation for multi-tenant security - - // In development mode, allow any authenticated user to access any tenant - // TODO: In production, implement proper tenant-user relationship checks - // This should query a tenant membership service or database using ctx for timeouts/tracing - _ = ctx // Will be used for database queries in production implementation - - // For now, basic validation that tenant ID is not empty - if requestedTenantID == "" { - return fmt.Errorf("tenant ID cannot be empty") - } - - // Development: Simple access control for demonstration - // Block access to "restricted-tenant" unless user is "admin-user" - if requestedTenantID == "restricted-tenant" && customerCtx.UserID != "admin-user" { - return fmt.Errorf("access denied: user %s cannot access restricted tenant", customerCtx.UserID) - } - - // In production, this would check: - // 1. User has permission to access the tenant - // 2. User's role within the tenant (admin, user, etc.) - // 3. Specific resource permissions if needed - - // Example future implementation: - // tenantService := GetTenantServiceFromContext(ctx) - // return tenantService.ValidateUserAccess(customerCtx.CustomerID, requestedTenantID) - - return nil // Allow all other access in development -} +// import ( +// "context" +// "fmt" +// "log/slog" +// "strings" + +// "connectrpc.com/connect" +// "go.opentelemetry.io/otel/baggage" +// ) + +// // CustomerContext holds customer information extracted from authentication +// type CustomerContext struct { +// UserID string +// TenantID string +// ProjectID string +// EnvironmentID string +// } + +// // AuthenticationInterceptor validates API requests and enforces customer isolation +// func AuthenticationInterceptor(logger *slog.Logger) connect.UnaryInterceptorFunc { +// return func(next connect.UnaryFunc) connect.UnaryFunc { +// return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) { +// // Extract API key from Authorization header +// auth := req.Header().Get("Authorization") +// if auth == "" { +// logger.LogAttrs(ctx, slog.LevelWarn, "missing authorization header", +// slog.String("procedure", req.Spec().Procedure), +// ) +// return nil, connect.NewError(connect.CodeUnauthenticated, +// fmt.Errorf("authorization header required")) +// } + +// // Parse Bearer token +// parts := strings.SplitN(auth, " ", 2) +// if len(parts) != 2 || parts[0] != "Bearer" { +// logger.LogAttrs(ctx, slog.LevelWarn, "invalid authorization format", +// slog.String("procedure", req.Spec().Procedure), +// ) +// return nil, connect.NewError(connect.CodeUnauthenticated, +// fmt.Errorf("authorization must be 'Bearer '")) +// } + +// // Extract requested tenant ID from header and validate access +// requestedTenantID := req.Header().Get("X-Tenant-ID") +// requestedProjectID := req.Header().Get("X-Project-ID") +// requestedEnvironmentID := req.Header().Get("X-Environment-ID") +// logger.LogAttrs(ctx, slog.LevelInfo, "checking tenant access", +// slog.String("procedure", req.Spec().Procedure), +// slog.String("user_id", customerCtx.UserID), +// slog.String("requested_tenant", requestedTenantID), +// slog.String("requested_project", requestedProjectID), +// slog.String("requested_environment", requestedEnvironmentID), +// ) + +// if requestedTenantID != "" { +// // Validate that authenticated user can access the requested tenant +// if err := validateTenantAccess(ctx, customerCtx, requestedTenantID); err != nil { +// logger.LogAttrs(ctx, slog.LevelWarn, "tenant access denied", +// slog.String("procedure", req.Spec().Procedure), +// slog.String("user_id", customerCtx.UserID), +// slog.String("requested_tenant", requestedTenantID), +// slog.String("error", err.Error()), +// ) +// return nil, connect.NewError(connect.CodePermissionDenied, err) +// } +// logger.LogAttrs(ctx, slog.LevelInfo, "tenant access granted", +// slog.String("procedure", req.Spec().Procedure), +// slog.String("user_id", customerCtx.UserID), +// slog.String("requested_tenant", requestedTenantID), +// ) +// } + +// // Add customer context to baggage for downstream services +// ctx = addCustomerContextToBaggage(ctx, customerCtx) + +// // Log authenticated request +// logger.LogAttrs(ctx, slog.LevelDebug, "authenticated request", +// slog.String("procedure", req.Spec().Procedure), +// slog.String("user_id", customerCtx.UserID), +// slog.String("tenant_id", customerCtx.TenantID), +// ) + +// return next(ctx, req) +// } +// } +// } + +// // addCustomerContextToBaggage adds customer context to OpenTelemetry baggage +// func addCustomerContextToBaggage(ctx context.Context, customerCtx *CustomerContext) context.Context { +// // Create baggage with customer context +// bag, err := baggage.Parse(fmt.Sprintf( +// "user_id=%s,project_id=%s,tenant_id=%s,environment_id=%s", +// customerCtx.UserID, +// customerCtx.ProjectID, +// customerCtx.TenantID, +// customerCtx.EnvironmentID, +// )) +// if err != nil { +// // Log error but continue - baggage is for observability, not security +// slog.Default().WarnContext(ctx, "failed to create baggage", +// slog.String("error", err.Error()), +// ) +// return ctx +// } + +// return baggage.ContextWithBaggage(ctx, bag) +// } + +// // ExtractTenantID extracts tenant ID from request context +// func ExtractTenantID(ctx context.Context) (string, error) { +// if requestBaggage := baggage.FromContext(ctx); len(requestBaggage.Members()) > 0 { +// tenantID := requestBaggage.Member("tenant_id").Value() +// if tenantID != "" { +// return tenantID, nil +// } +// } +// return "", fmt.Errorf("tenant_id not found in context") +// } + +// // ExtractEnvironmentID extracts tenant ID from request context +// func ExtractEnvironmentID(ctx context.Context) (string, error) { +// if requestBaggage := baggage.FromContext(ctx); len(requestBaggage.Members()) > 0 { +// environmentID := requestBaggage.Member("environment_id").Value() +// if environmentID != "" { +// return environmentID, nil +// } +// } +// return "", fmt.Errorf("environment_id not found in context") +// } + +// // ExtractProjectID extracts project ID from request context +// func ExtractProjectID(ctx context.Context) (string, error) { +// if requestBaggage := baggage.FromContext(ctx); len(requestBaggage.Members()) > 0 { +// projectID := requestBaggage.Member("project_id").Value() +// if projectID != "" { +// return projectID, nil +// } +// } +// return "", fmt.Errorf("project_id not found in context") +// } + +// // ExtractUserID extracts tenant ID from request context +// func ExtractUserID(ctx context.Context) (string, error) { +// if requestBaggage := baggage.FromContext(ctx); len(requestBaggage.Members()) > 0 { +// userID := requestBaggage.Member("user_id").Value() +// if userID != "" { +// return userID, nil +// } +// } +// return "", fmt.Errorf("tenant_id not found in context") +// } + +// // validateTenantAccess validates that the authenticated user can access the requested tenant +// func validateTenantAccess(ctx context.Context, customerCtx *CustomerContext, requestedTenantID string) error { +// // AIDEV-BUSINESS_RULE: Tenant access validation for multi-tenant security + +// // In development mode, allow any authenticated user to access any tenant +// // TODO: In production, implement proper tenant-user relationship checks +// // This should query a tenant membership service or database using ctx for timeouts/tracing +// _ = ctx // Will be used for database queries in production implementation + +// // For now, basic validation that tenant ID is not empty +// if requestedTenantID == "" { +// return fmt.Errorf("tenant ID cannot be empty") +// } + +// // Development: Simple access control for demonstration +// // Block access to "restricted-tenant" unless user is "admin-user" +// if requestedTenantID == "restricted-tenant" && customerCtx.UserID != "admin-user" { +// return fmt.Errorf("access denied: user %s cannot access restricted tenant", customerCtx.UserID) +// } + +// // In production, this would check: +// // 1. User has permission to access the tenant +// // 2. User's role within the tenant (admin, user, etc.) +// // 3. Specific resource permissions if needed + +// // Example future implementation: +// // tenantService := GetTenantServiceFromContext(ctx) +// // return tenantService.ValidateUserAccess(customerCtx.CustomerID, requestedTenantID) + +// return nil // Allow all other access in development +// } diff --git a/go/deploy/metald/internal/service/deployment.go b/go/deploy/metald/internal/service/deployment.go new file mode 100644 index 0000000000..f71cf139c2 --- /dev/null +++ b/go/deploy/metald/internal/service/deployment.go @@ -0,0 +1,46 @@ +package service + +import ( + "context" + + "connectrpc.com/connect" + metaldv1 "github.com/unkeyed/unkey/go/gen/proto/metald/v1" +) + +// CreateDeployment allocates a network, generates IDs etc +func (s *VMService) CreateDeployment(ctx context.Context, req *connect.Request[metaldv1.CreateDeploymentRequest]) (*connect.Response[metaldv1.CreateDeploymentResponse], error) { + return connect.NewResponse(&metaldv1.CreateDeploymentResponse{ + VmIds: []string{"ud-001", "ud-002", "ud-003"}, + }), nil +} + +// GetDeployment returns all of the VMs and their state for the passed deployment_id +func (s *VMService) GetDeployment(ctx context.Context, req *connect.Request[metaldv1.GetDeploymentRequest]) (*connect.Response[metaldv1.GetDeploymentResponse], error) { + + // Sample VMs to "act" against + vms := []*metaldv1.GetDeploymentResponse_Vm{ + { + Id: "ud-001", + Host: "host01.asldkfja.unkey.app", + State: metaldv1.VmState_VM_STATE_RUNNING, + Port: 8081, + }, + { + Id: "ud-002", + Host: "host02.asldkfja.unkey.app", + State: metaldv1.VmState_VM_STATE_CREATED, + Port: 8082, + }, + { + Id: "vm-003", + Host: "host03.asldkfja.unkey.app", + State: metaldv1.VmState_VM_STATE_RUNNING, + Port: 8083, + }, + } + + return connect.NewResponse(&metaldv1.GetDeploymentResponse{ + DeploymentId: req.Msg.GetDeploymentId(), + Vms: vms, + }), nil +} diff --git a/go/deploy/metald/internal/service/vm.go b/go/deploy/metald/internal/service/vm.go index 6de248551b..069e25ea7a 100644 --- a/go/deploy/metald/internal/service/vm.go +++ b/go/deploy/metald/internal/service/vm.go @@ -44,13 +44,6 @@ func NewVMService(backend types.Backend, logger *slog.Logger, metricsCollector * } } -// CreateDeployment allocates a network, generates IDs etc -func (s *VMService) CreateDeployment(ctx context.Context, req *connect.Request[metaldv1.CreateDeploymentRequest]) (*connect.Response[metaldv1.CreateDeploymentResponse], error) { - return connect.NewResponse(&metaldv1.CreateDeploymentResponse{ - VmIds: []string{"a"}, - }), nil -} - // CreateVm creates a new VM instance func (s *VMService) CreateVm(ctx context.Context, req *connect.Request[metaldv1.CreateVmRequest]) (*connect.Response[metaldv1.CreateVmResponse], error) { ctx, span := s.tracer.Start(ctx, "metald.vm.create", diff --git a/go/deploy/pkg/observability/interceptors/client.go b/go/deploy/pkg/observability/interceptors/client.go index 4778a37f5a..62b43720be 100644 --- a/go/deploy/pkg/observability/interceptors/client.go +++ b/go/deploy/pkg/observability/interceptors/client.go @@ -41,45 +41,6 @@ func NewClientTracePropagationInterceptor(logger *slog.Logger) connect.UnaryInte } } -// NewClientTenantForwardingInterceptor creates a ConnectRPC interceptor that forwards -// tenant context from incoming requests to outgoing RPC requests. This ensures tenant -// isolation is maintained across service boundaries. -// -// AIDEV-NOTE: This interceptor extracts tenant information from the request context -// (previously stored by the server-side tenant auth interceptor) and adds it as -// headers to outgoing requests. -func NewClientTenantForwardingInterceptor(logger *slog.Logger) connect.UnaryInterceptorFunc { - return func(next connect.UnaryFunc) connect.UnaryFunc { - return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) { - // Extract tenant context from the incoming request context - if tenantCtx, ok := TenantFromContext(ctx); ok { - // Forward tenant headers to the outgoing request - if tenantCtx.TenantID != "" { - req.Header().Set("X-Tenant-ID", tenantCtx.TenantID) - } - if tenantCtx.ProjectID != "" { - req.Header().Set("X-Project-ID", tenantCtx.ProjectID) - } - if tenantCtx.EnvironmentID != "" { - req.Header().Set("X-Environment-ID", tenantCtx.EnvironmentID) - } - if tenantCtx.AuthToken != "" { - req.Header().Set("Authorization", tenantCtx.AuthToken) - } - - logger.LogAttrs(ctx, slog.LevelDebug, "forwarding tenant context", - slog.String("tenant_id", tenantCtx.TenantID), - slog.String("project_id", tenantCtx.ProjectID), - slog.String("environment_id", tenantCtx.EnvironmentID), - slog.String("procedure", req.Spec().Procedure), - ) - } - - return next(ctx, req) - } - } -} - // NewClientMetricsInterceptor creates a ConnectRPC interceptor for client-side metrics. // It creates spans for outgoing RPC calls and tracks their duration and status. // @@ -105,15 +66,6 @@ func NewClientMetricsInterceptor(serviceName string, logger *slog.Logger) connec ) defer span.End() - // Add tenant info to span if available - if tenantCtx, ok := TenantFromContext(ctx); ok && tenantCtx.TenantID != "" { - span.SetAttributes( - attribute.String("tenant.id", tenantCtx.TenantID), - attribute.String("tenant.project_id", tenantCtx.ProjectID), - attribute.String("tenant.environment_id", tenantCtx.EnvironmentID), - ) - } - // Execute the RPC call resp, err := next(ctx, req) @@ -143,6 +95,5 @@ func NewDefaultClientInterceptors(serviceName string, logger *slog.Logger) []con return []connect.UnaryInterceptorFunc{ NewClientTracePropagationInterceptor(logger), NewClientMetricsInterceptor(serviceName, logger), - NewClientTenantForwardingInterceptor(logger), } } diff --git a/go/deploy/pkg/observability/interceptors/interceptors.go b/go/deploy/pkg/observability/interceptors/interceptors.go index d56d913117..aaea772d57 100644 --- a/go/deploy/pkg/observability/interceptors/interceptors.go +++ b/go/deploy/pkg/observability/interceptors/interceptors.go @@ -66,7 +66,6 @@ func NewDefaultInterceptors(serviceName string, opts ...Option) []connect.UnaryI allOpts = append(defaultOpts, allOpts...) return []connect.UnaryInterceptorFunc{ - NewTenantAuthInterceptor(allOpts...), NewMetricsInterceptor(allOpts...), NewLoggingInterceptor(allOpts...), } diff --git a/go/deploy/pkg/observability/interceptors/logging.go b/go/deploy/pkg/observability/interceptors/logging.go index 2dbbb8e5a3..4d9d2bcb1f 100644 --- a/go/deploy/pkg/observability/interceptors/logging.go +++ b/go/deploy/pkg/observability/interceptors/logging.go @@ -66,15 +66,6 @@ func NewLoggingInterceptor(opts ...Option) connect.UnaryInterceptorFunc { slog.String("trace_id", traceID), } - // Add tenant info if available - if tenantCtx, ok := TenantFromContext(ctx); ok && tenantCtx.TenantID != "" { - requestAttrs = append(requestAttrs, - slog.String("tenant_id", tenantCtx.TenantID), - // slog.String("project_id", tenantCtx.ProjectID), - // slog.String("environment_id", tenantCtx.EnvironmentID), - ) - } - // Log request logger.LogAttrs(ctx, slog.LevelInfo, "rpc request started", requestAttrs...) @@ -99,15 +90,6 @@ func NewLoggingInterceptor(opts ...Option) connect.UnaryInterceptorFunc { slog.String("trace_id", traceID), } - // Add tenant info if available - if tenantCtx, ok := TenantFromContext(ctx); ok && tenantCtx.TenantID != "" { - responseAttrs = append(responseAttrs, - slog.String("tenant_id", tenantCtx.TenantID), - // slog.String("project_id", tenantCtx.ProjectID), - // slog.String("environment_id", tenantCtx.EnvironmentID), - ) - } - // Log response based on error status if err != nil { // Determine log level based on error type diff --git a/go/deploy/pkg/observability/interceptors/metrics.go b/go/deploy/pkg/observability/interceptors/metrics.go index 58b158a671..f03d6fcbec 100644 --- a/go/deploy/pkg/observability/interceptors/metrics.go +++ b/go/deploy/pkg/observability/interceptors/metrics.go @@ -123,16 +123,6 @@ func NewMetricsInterceptor(opts ...Option) connect.UnaryInterceptorFunc { ), ) - // Add tenant info to span if available - if tenantCtx, ok := TenantFromContext(ctx); ok && tenantCtx.TenantID != "" { - span.SetAttributes( - attribute.String("tenant.id", tenantCtx.TenantID), - // attribute.String("tenant.project_id", tenantCtx.ProjectID), - // attribute.String("tenant.environment_id", tenantCtx.EnvironmentID), - ) - } - - // AIDEV-NOTE: Critical panic recovery in metrics interceptor - preserves existing errors defer func() { if r := recover(); r != nil { // Log panic with optional stack trace @@ -235,11 +225,6 @@ func NewMetricsInterceptor(opts ...Option) connect.UnaryInterceptorFunc { attribute.String("rpc.status", statusCode), } - // Add tenant attribute if available - if tenantCtx, ok := TenantFromContext(ctx); ok && tenantCtx.TenantID != "" { - attrs = append(attrs, attribute.String("tenant.id", tenantCtx.TenantID)) - } - // Increment request counter metrics.requestCounter.Add(ctx, 1, metric.WithAttributes(attrs...)) diff --git a/go/deploy/pkg/observability/interceptors/tenant.go b/go/deploy/pkg/observability/interceptors/tenant.go deleted file mode 100644 index 8995676b94..0000000000 --- a/go/deploy/pkg/observability/interceptors/tenant.go +++ /dev/null @@ -1,151 +0,0 @@ -package interceptors - -import ( - "context" - "fmt" - "log/slog" - "slices" - - "connectrpc.com/connect" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -// TenantContext holds tenant authentication information extracted from request headers. -type TenantContext struct { - // TenantID is the unique identifier for the tenant/customer. - TenantID string - // ProjectID is the unique identifier for the project. - ProjectID string - // EnvironmentID is the environment within the project - EnvironmentID string - // UserID is the user ID making the request - UserID string - // AuthToken is the authentication token provided in the request. - AuthToken string -} - -// contextKey is a private type for context keys to avoid collisions. -type contextKey string - -const ( - tenantContextKey contextKey = "tenant_id" - projectContextKey contextKey = "project_id" - environmentContextKey contextKey = "environment_id" - userContextKey contextKey = "user_id" -) - -// WithTenantContext adds tenant authentication context to the context. -func WithTenantContext(ctx context.Context, auth TenantContext) context.Context { - return context.WithValue(ctx, tenantContextKey, auth) -} - -// TenantFromContext extracts tenant authentication context from the context. -// Returns the TenantContext and a boolean indicating if it was found. -func TenantFromContext(ctx context.Context) (TenantContext, bool) { - auth, ok := ctx.Value(tenantContextKey).(TenantContext) - return auth, ok -} - -// NewTenantAuthInterceptor creates a ConnectRPC interceptor for tenant authentication. -// This interceptor extracts tenant information from request headers, validates it, -// and adds it to the request context for use by downstream handlers. -// -// AIDEV-NOTE: All services need tenant awareness for proper isolation and billing. -func NewTenantAuthInterceptor(opts ...Option) connect.UnaryInterceptorFunc { - options := applyOptions(opts) - - return func(next connect.UnaryFunc) connect.UnaryFunc { - return func(ctx context.Context, req connect.AnyRequest) (resp connect.AnyResponse, err error) { - // Panic recovery in tenant auth interceptor prevents auth failures from crashing the service - defer func() { - if r := recover(); r != nil { - if options.Logger != nil { - options.Logger.Error("panic in tenant auth interceptor", - slog.String("service", options.ServiceName), - slog.String("procedure", req.Spec().Procedure), - slog.Any("panic", r), - slog.String("panic_type", fmt.Sprintf("%T", r)), - ) - } - // Only override err if it's not already set - if err == nil { - err = connect.NewError(connect.CodeInternal, fmt.Errorf("internal server error: %v", r)) - } - } - }() - - // Extract tenant information from headers - tenantID := req.Header().Get("X-Tenant-ID") - projectID := req.Header().Get("X-Project-ID") - environmentID := req.Header().Get("X-Environment-ID") - userID := req.Header().Get("X-User-ID") - authToken := req.Header().Get("Authorization") - - // Log request with tenant info if logger is available - if options.Logger != nil && options.Logger.Enabled(ctx, slog.LevelDebug) { - options.Logger.LogAttrs(ctx, slog.LevelDebug, "tenant auth headers", - slog.String("service", options.ServiceName), - slog.String("procedure", req.Spec().Procedure), - slog.String("tenant_id", tenantID), - slog.String("project_id", projectID), - slog.String("environment_id", environmentID), - slog.String("user_id", userID), - slog.Bool("has_auth_token", authToken != ""), - ) - } - - // Add tenant context to the request context - tenantCtx := TenantContext{ - TenantID: tenantID, - ProjectID: projectID, - EnvironmentID: environmentID, - UserID: userID, - AuthToken: authToken, - } - ctx = WithTenantContext(ctx, tenantCtx) - - // Add tenant info to span if tracing is enabled - if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() { - span.SetAttributes( - attribute.String("tenant.id", tenantID), - attribute.String("tenant.project_id", projectID), - attribute.String("tenant.environment_id", environmentID), - attribute.String("user.id", userID), - attribute.Bool("tenant.authenticated", tenantID != ""), - ) - } - - // Check if this procedure requires tenant authentication - if options.TenantAuthRequired && tenantID == "" { - // Check if this procedure is exempt from tenant auth - if !slices.Contains(options.TenantAuthExemptProcedures, req.Spec().Procedure) { - if options.Logger != nil { - options.Logger.LogAttrs(ctx, slog.LevelWarn, "missing tenant ID", - slog.String("service", options.ServiceName), - slog.String("procedure", req.Spec().Procedure), - ) - } - return nil, connect.NewError(connect.CodeUnauthenticated, - fmt.Errorf("tenant ID is required")) - } - } - - // THIS IS PROBABLY WHERE UNKEY OR SOMETHING DOES AUTH LOL - - // Log successful tenant authentication - if options.Logger != nil && tenantID != "" { - options.Logger.LogAttrs(ctx, slog.LevelDebug, "tenant authenticated", - slog.String("service", options.ServiceName), - slog.String("tenant_id", tenantID), - slog.String("project_id", projectID), - slog.String("environment_id", environmentID), - slog.String("user_id", userID), - slog.String("procedure", req.Spec().Procedure), - ) - } - - return next(ctx, req) - } - } -}