feat(ws-server): validate compute.provider vs cloud-provider SSOT (switch-provider PR1) #2420
@@ -41,6 +41,20 @@ var workspaceComputeInstanceAllowlist = map[string]struct{}{
|
||||
"c6i.xlarge": {},
|
||||
}
|
||||
|
||||
// workspaceComputeProviderAllowlist mirrors the controlplane cloud-provider SSOT
|
||||
// (controlplane internal/cloudprovider.Supported = {aws, hetzner, gcp}).
|
||||
// ws-server lives in a different repo and cannot import that package, so this is
|
||||
// a DELIBERATE mirror; TestValidateWorkspaceCompute_Provider pins the exact set
|
||||
// and this doc-comment names the SSOT, so a CP-side change forces a matching
|
||||
// change here (and the CP itself fail-closes an unwired provider with a 422).
|
||||
// "" = default (AWS) and is always accepted. This is the gate the switch-provider
|
||||
// flow reuses to reject a bad provider with a clean 400 before any CP round-trip.
|
||||
var workspaceComputeProviderAllowlist = map[string]struct{}{
|
||||
"aws": {},
|
||||
"gcp": {},
|
||||
"hetzner": {},
|
||||
}
|
||||
|
||||
func validateWorkspaceCompute(compute models.WorkspaceCompute) error {
|
||||
if compute.InstanceType != "" {
|
||||
if _, ok := workspaceComputeInstanceAllowlist[compute.InstanceType]; !ok {
|
||||
@@ -73,6 +87,15 @@ func validateWorkspaceCompute(compute models.WorkspaceCompute) error {
|
||||
default:
|
||||
return fmt.Errorf("unsupported compute.data_persistence (want persist|ephemeral)")
|
||||
}
|
||||
// Cloud backend for the box (multi-provider). "" = default (AWS). CP fail-
|
||||
// closes an unwired provider with a 422 (PROVIDER_UNAVAILABLE); validating
|
||||
// here gives a clean 400 before the round-trip and is the gate reused by the
|
||||
// switch-provider flow. Mirrors the controlplane cloudprovider SSOT.
|
||||
if compute.Provider != "" {
|
||||
if _, ok := workspaceComputeProviderAllowlist[compute.Provider]; !ok {
|
||||
return fmt.Errorf("unsupported compute.provider (want aws|gcp|hetzner)")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,34 @@ func TestValidateWorkspaceCompute_RejectsUnknownInstanceType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-provider: compute.provider must be "" (default AWS) or one of the wired
|
||||
// cloud backends. Pins the allowlist to the controlplane cloudprovider SSOT
|
||||
// (Supported = {aws, hetzner, gcp}); if the SSOT changes, update both sides.
|
||||
func TestValidateWorkspaceCompute_Provider(t *testing.T) {
|
||||
for _, ok := range []string{"", "aws", "gcp", "hetzner"} {
|
||||
c := models.WorkspaceCompute{Provider: ok}
|
||||
if err := validateWorkspaceCompute(c); err != nil {
|
||||
t.Errorf("provider=%q must be accepted: %v", ok, err)
|
||||
}
|
||||
}
|
||||
for _, bad := range []string{"AWS", "azure", "digitalocean", "ec2", "google", "hetzner-cloud"} {
|
||||
c := models.WorkspaceCompute{Provider: bad}
|
||||
if err := validateWorkspaceCompute(c); err == nil {
|
||||
t.Errorf("provider=%q must be rejected", bad)
|
||||
}
|
||||
}
|
||||
// Pin the exact SSOT-mirrored set so a silent drift fails here.
|
||||
want := map[string]struct{}{"aws": {}, "gcp": {}, "hetzner": {}}
|
||||
if len(workspaceComputeProviderAllowlist) != len(want) {
|
||||
t.Fatalf("provider allowlist drifted from SSOT {aws,gcp,hetzner}: %v", workspaceComputeProviderAllowlist)
|
||||
}
|
||||
for p := range want {
|
||||
if _, ok := workspaceComputeProviderAllowlist[p]; !ok {
|
||||
t.Fatalf("provider allowlist missing %q (SSOT drift)", p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// internal#734: data_persistence enum. "" (auto), "persist", "ephemeral" are
|
||||
// the only accepted values; anything else is a clear 400 before the CP call.
|
||||
func TestValidateWorkspaceCompute_DataPersistence(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user