From 64af2431ede8029b8582b5d564ef21b15d67cf4f Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Mon, 1 Jun 2026 12:14:39 -0400 Subject: [PATCH 1/4] feat: expose provider region lister --- go.mod | 24 +++++----- go.sum | 52 ++++++++++----------- internal/iacserver.go | 2 + internal/region_lister.go | 27 +++++++++++ internal/region_lister_test.go | 83 ++++++++++++++++++++++++++++++++++ plugin.json | 1 + 6 files changed, 151 insertions(+), 38 deletions(-) create mode 100644 internal/region_lister.go create mode 100644 internal/region_lister_test.go diff --git a/go.mod b/go.mod index 32cb66b..3b3a94b 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis v1.0.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 - github.com/GoCodeAlone/workflow v0.64.3 + github.com/GoCodeAlone/workflow v0.68.2-0.20260601161212-52066f422dd9 google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af ) @@ -27,12 +27,12 @@ require ( github.com/BurntSushi/toml v1.6.0 // indirect github.com/DataDog/datadog-go/v5 v5.8.3 // indirect github.com/GoCodeAlone/go-plugin v1.7.0 // indirect - github.com/GoCodeAlone/modular v1.13.0 // indirect - github.com/GoCodeAlone/modular/modules/auth v1.15.0 // indirect - github.com/GoCodeAlone/modular/modules/cache v1.15.0 // indirect - github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.8.0 // indirect - github.com/GoCodeAlone/modular/modules/jsonschema v1.15.0 // indirect - github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.8.0 // indirect + github.com/GoCodeAlone/modular v1.13.4 // indirect + github.com/GoCodeAlone/modular/modules/auth v1.17.0 // indirect + github.com/GoCodeAlone/modular/modules/cache v1.17.0 // indirect + github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.10.0 // indirect + github.com/GoCodeAlone/modular/modules/jsonschema v1.17.0 // indirect + github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.10.0 // indirect github.com/GoCodeAlone/yaegi v0.17.2 // indirect github.com/IBM/sarama v1.47.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect @@ -50,7 +50,7 @@ require ( github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22 // indirect - github.com/aws/aws-sdk-go-v2/service/kinesis v1.43.4 // indirect + github.com/aws/aws-sdk-go-v2/service/kinesis v1.43.5 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.10 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.16 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20 // indirect @@ -82,7 +82,7 @@ require ( github.com/fatih/color v1.19.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/flowchartsman/retry v1.2.0 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/fxamacker/cbor/v2 v2.9.2 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -114,11 +114,11 @@ require ( github.com/hashicorp/memberlist v0.5.4 // indirect github.com/hashicorp/vault/api v1.23.0 // indirect github.com/hashicorp/yamux v0.1.2 // indirect - github.com/itchyny/gojq v0.12.18 // indirect - github.com/itchyny/timefmt-go v0.1.7 // indirect + github.com/itchyny/gojq v0.12.19 // indirect + github.com/itchyny/timefmt-go v0.1.8 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.9.1 // indirect + github.com/jackc/pgx/v5 v5.9.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect diff --git a/go.sum b/go.sum index 3248872..ba41ac9 100644 --- a/go.sum +++ b/go.sum @@ -56,20 +56,20 @@ github.com/DataDog/datadog-go/v5 v5.8.3 h1:s58CUJ9s8lezjhTNJO/SxkPBv2qZjS3ktpRSq github.com/DataDog/datadog-go/v5 v5.8.3/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= github.com/GoCodeAlone/go-plugin v1.7.0 h1:EwnhqPlXiNmp85S+MXnKKvm3YlfA6O4NzBb4+GSlEVY= github.com/GoCodeAlone/go-plugin v1.7.0/go.mod h1:HbGQRZUIa+jbDfjsaZIMJYvrz+LnxL0mJpggfynSTMk= -github.com/GoCodeAlone/modular v1.13.0 h1:UfsegfAmPWcPYQOqYZFsw/LNySBmMDcthiOQe5bscqE= -github.com/GoCodeAlone/modular v1.13.0/go.mod h1:b06Pvgcc8HsGxvl30iO39zGH2jIWz467QEj2+OQL2Do= -github.com/GoCodeAlone/modular/modules/auth v1.15.0 h1:pBSkPSf4k4GLSbUQFLuPa+nFbfoJXGzSz9q89VoapZk= -github.com/GoCodeAlone/modular/modules/auth v1.15.0/go.mod h1:vmIm/LQrcURS2p02YwaELb+CZoHPtT0XB0v1i+sj9i4= -github.com/GoCodeAlone/modular/modules/cache v1.15.0 h1:6Y2EJ5S7mb/TjyG/uN6dto5VUYJNDFYULUamRsqAKvo= -github.com/GoCodeAlone/modular/modules/cache v1.15.0/go.mod h1:PRun74dRZKfqlBM+f6QrvI9oa4joUU3j1hisiLyQ+oM= -github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.8.0 h1:buYs0TGNbAZgtTq1Qb+dfmTv3+ZOBIN0HbvVBLyNqxE= -github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.8.0/go.mod h1:329flAKmwrPq2JEwu9iltWv6A83H/Di82Xze+kvdKDw= -github.com/GoCodeAlone/modular/modules/jsonschema v1.15.0 h1:xb1mI4NZkzvNKQ2F6nkyXQvK/kEvvfs1z7FoGf3/LRA= -github.com/GoCodeAlone/modular/modules/jsonschema v1.15.0/go.mod h1:hhGouwAVsonmJ4Lain4jINZ9nZCoc9l9eF3BHbmR8eE= -github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.8.0 h1:cvdLHbM/vzvygQTcAWSJsy+dAPzzwWyjzKMmTBFcFIo= -github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.8.0/go.mod h1:/9ipMG4qM2CHQ14BfXKdVlYRJelef6M8MFI5TbZv67M= -github.com/GoCodeAlone/workflow v0.64.3 h1:r0jMoRJXJI8lz44c70mFjGcpy24IWpOTtkX7BC0/fas= -github.com/GoCodeAlone/workflow v0.64.3/go.mod h1:659GGDrw3QJ7b625y9rf8QhKIpt1VCoEG0MxKu5tGQs= +github.com/GoCodeAlone/modular v1.13.4 h1:De4p2qyJSVmstRGno/PM+fPdUCMu/7a9WgU5FUVGDa8= +github.com/GoCodeAlone/modular v1.13.4/go.mod h1:+JEPUYOxGaD332EMZ5PbJCz5rxwvFu4Tm6MrnZT0vxM= +github.com/GoCodeAlone/modular/modules/auth v1.17.0 h1:GbKG6s/2qe6N9YZ8vtvYsNon56MLWECncPxWvAsazSc= +github.com/GoCodeAlone/modular/modules/auth v1.17.0/go.mod h1:E9dDIxiAxIrXK8gn/rEhaqI5OYe6Aw/uGpRyI7iyxj8= +github.com/GoCodeAlone/modular/modules/cache v1.17.0 h1:1cColHYfF7aFZhxBjS4RuZ2wBOYWAIVr5GkJ3nk5VIA= +github.com/GoCodeAlone/modular/modules/cache v1.17.0/go.mod h1:RURzRp+vpRzKR2LjZXwQ4QGc1cvE+53SYODcxY6AR3Y= +github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.10.0 h1:2ljVafd/1LYchF47WrnA1+ji8mcmVXMJ4F5qDrhZZi4= +github.com/GoCodeAlone/modular/modules/eventbus/v2 v2.10.0/go.mod h1:AKLcRGsw5gp2Q1zhuK0TBnMJOsaRe3aJ2OKnLFE2O5w= +github.com/GoCodeAlone/modular/modules/jsonschema v1.17.0 h1:zoWioqUvuNNDfnjHA1sHixdlHfBreJdGhnnEBtxkzI8= +github.com/GoCodeAlone/modular/modules/jsonschema v1.17.0/go.mod h1:GDU/jsD6AddmXKedj0wZwieUIaQsTBSGMzuj+XHXMrw= +github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.10.0 h1:+2M/ecyCxDiXfJM4ibcERuu/BBeIbLTQNcVgRsllR64= +github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.10.0/go.mod h1:tlVH1mA5yuU8CB7R7+HXIRaBixZoNid6h+5tew5u3FU= +github.com/GoCodeAlone/workflow v0.68.2-0.20260601161212-52066f422dd9 h1:QRNvNVwlKMl6wTL9Pfzg34VOZH8kTx74jWVVEYCzalM= +github.com/GoCodeAlone/workflow v0.68.2-0.20260601161212-52066f422dd9/go.mod h1:4UwFYm1cM8a/AvGNb1CZAuob0b0gq7552sxcNMdDALA= github.com/GoCodeAlone/yaegi v0.17.2 h1:WK6Y6e0t1a6U7r+S2dN3CGWW1PizYD3zO0zneToZPxM= github.com/GoCodeAlone/yaegi v0.17.2/go.mod h1:z5Pr6Wse6QJcQvpgxTxzMAevFarH0N37TG88Y9dprx0= github.com/IBM/sarama v1.47.0 h1:GcQFEd12+KzfPYeLgN69Fh7vLCtYRhVIx0rO4TZO318= @@ -114,8 +114,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8 h1:HtOTYcb github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8/go.mod h1:VsK9abqQeGlzPgUr+isNWzPlK2vKe9INMLWnY65f5Xs= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22 h1:PUmZeJU6Y1Lbvt9WFuJ0ugUK2xn6hIWUBBbKuOWF30s= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22/go.mod h1:nO6egFBoAaoXze24a2C0NjQCvdpk8OueRoYimvEB9jo= -github.com/aws/aws-sdk-go-v2/service/kinesis v1.43.4 h1:3m9iJtMtLq75jKRAfw0kapoHUlbzi0CRVigysBN/FHA= -github.com/aws/aws-sdk-go-v2/service/kinesis v1.43.4/go.mod h1:O2L6vGm4xacEuN2otHFMgn7yXXlgzFKzxrba0fy/yk8= +github.com/aws/aws-sdk-go-v2/service/kinesis v1.43.5 h1:LxgRVyuY+5DEPSX7kmin/V7toE8MWZ9U8n2dqRtX+RE= +github.com/aws/aws-sdk-go-v2/service/kinesis v1.43.5/go.mod h1:eUebEBEqVfOwEyDDDbGauH4PNqDCuepRvTaNbJeWr5w= github.com/aws/aws-sdk-go-v2/service/signin v1.0.10 h1:a1Fq/KXn75wSzoJaPQTgZO0wHGqE9mjFnylnqEPTchA= github.com/aws/aws-sdk-go-v2/service/signin v1.0.10/go.mod h1:p6+MXNxW7IA6dMgHfTAzljuwSKD0NCm/4lbS4t6+7vI= github.com/aws/aws-sdk-go-v2/service/sso v1.30.16 h1:x6bKbmDhsgSZwv6q19wY/u3rLk/3FGjJWyqKcIRufpE= @@ -218,12 +218,12 @@ github.com/flowchartsman/retry v1.2.0 h1:qDhlw6RNufXz6RGr+IiYimFpMMkt77SUSHY5tgF github.com/flowchartsman/retry v1.2.0/go.mod h1:+sfx8OgCCiAr3t5jh2Gk+T0fRTI+k52edaYxURQxY64= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= -github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -375,16 +375,16 @@ github.com/hashicorp/vault/api v1.23.0 h1:gXgluBsSECfRWTSW9niY2jwg2e9mMJc4WoHNv4 github.com/hashicorp/vault/api v1.23.0/go.mod h1:zransKiB9ftp+kgY8ydjnvCU7Wk8i9L0DYWpXeMj9ko= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= -github.com/itchyny/gojq v0.12.18 h1:gFGHyt/MLbG9n6dqnvlliiya2TaMMh6FFaR2b1H6Drc= -github.com/itchyny/gojq v0.12.18/go.mod h1:4hPoZ/3lN9fDL1D+aK7DY1f39XZpY9+1Xpjz8atrEkg= -github.com/itchyny/timefmt-go v0.1.7 h1:xyftit9Tbw+Dc/huSSPJaEmX1TVL8lw5vxjJLK4GMMA= -github.com/itchyny/timefmt-go v0.1.7/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI= +github.com/itchyny/gojq v0.12.19 h1:ttXA0XCLEMoaLOz5lSeFOZ6u6Q3QxmG46vfgI4O0DEs= +github.com/itchyny/gojq v0.12.19/go.mod h1:5galtVPDywX8SPSOrqjGxkBeDhSxEW1gSxoy7tn1iZY= +github.com/itchyny/timefmt-go v0.1.8 h1:1YEo1JvfXeAHKdjelbYr/uCuhkybaHCeTkH8Bo791OI= +github.com/itchyny/timefmt-go v0.1.8/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= -github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= +github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= diff --git a/internal/iacserver.go b/internal/iacserver.go index 6423208..93e4b29 100644 --- a/internal/iacserver.go +++ b/internal/iacserver.go @@ -43,6 +43,7 @@ type azureIaCServer struct { pb.UnimplementedIaCProviderValidatorServer pb.UnimplementedIaCProviderDriftConfigDetectorServer pb.UnimplementedIaCProviderRequirementMapperServer + pb.UnimplementedIaCProviderRegionListerServer pb.UnimplementedResourceDriverServer pb.UnimplementedIaCStateBackendServer @@ -81,6 +82,7 @@ var ( // delegates to DetectDrift (existence-only behavior; ignores the specs map). _ pb.IaCProviderDriftDetectorServer = (*azureIaCServer)(nil) _ pb.IaCProviderRequirementMapperServer = (*azureIaCServer)(nil) + _ pb.IaCProviderRegionListerServer = (*azureIaCServer)(nil) _ pb.ResourceDriverServer = (*azureIaCServer)(nil) // azureIaCServer also SERVES the typed IaC state-backend contract // (azure_blob backend). The SDK serve hook auto-registers this via diff --git a/internal/region_lister.go b/internal/region_lister.go new file mode 100644 index 0000000..2ff8d5f --- /dev/null +++ b/internal/region_lister.go @@ -0,0 +1,27 @@ +package internal + +import ( + "context" + "sort" + + pb "github.com/GoCodeAlone/workflow/plugin/external/proto" +) + +var azureProviderRegions = []string{ + "eastus", + "southeastasia", + "westeurope", + "westus2", +} + +func (s *azureIaCServer) ListRegions(context.Context, *pb.ListRegionsRequest) (*pb.ListRegionsResponse, error) { + regions := make([]string, len(azureProviderRegions)) + copy(regions, azureProviderRegions) + sort.Strings(regions) + + out := make([]*pb.ProviderRegion, 0, len(regions)) + for _, name := range regions { + out = append(out, &pb.ProviderRegion{Name: name, DisplayName: name}) + } + return &pb.ListRegionsResponse{Regions: out}, nil +} diff --git a/internal/region_lister_test.go b/internal/region_lister_test.go new file mode 100644 index 0000000..ed66e9e --- /dev/null +++ b/internal/region_lister_test.go @@ -0,0 +1,83 @@ +package internal + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "testing" + + pb "github.com/GoCodeAlone/workflow/plugin/external/proto" + sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk" + "google.golang.org/grpc" +) + +func TestAzureIaCServer_ListRegions(t *testing.T) { + resp, err := NewIaCServer().ListRegions(context.Background(), &pb.ListRegionsRequest{EnvName: "prod"}) + if err != nil { + t.Fatalf("ListRegions: %v", err) + } + got := regionNames(resp.GetRegions()) + want := []string{"eastus", "southeastasia", "westeurope", "westus2"} + if !sameStrings(got, want) { + t.Fatalf("regions = %v, want %v", got, want) + } +} + +func TestAzureIaCServer_RegistersRegionLister(t *testing.T) { + server := grpc.NewServer() + if err := sdk.RegisterAllIaCProviderServices(server, NewIaCServer()); err != nil { + t.Fatalf("RegisterAllIaCProviderServices: %v", err) + } + if _, ok := server.GetServiceInfo()[pb.IaCProviderRegionLister_ServiceDesc.ServiceName]; !ok { + t.Fatalf("registered services missing %s", pb.IaCProviderRegionLister_ServiceDesc.ServiceName) + } +} + +func TestPluginManifestAdvertisesRegionLister(t *testing.T) { + data, err := os.ReadFile(filepath.Join(hostConformanceRepoRoot(t), "plugin.json")) + if err != nil { + t.Fatalf("read plugin.json: %v", err) + } + var manifest struct { + IaCServices []string `json:"iacServices"` + } + if err := json.Unmarshal(data, &manifest); err != nil { + t.Fatalf("parse plugin.json: %v", err) + } + if !containsString(manifest.IaCServices, pb.IaCProviderRegionLister_ServiceDesc.ServiceName) { + t.Fatalf("iacServices missing %s: %v", pb.IaCProviderRegionLister_ServiceDesc.ServiceName, manifest.IaCServices) + } +} + +func regionNames(regions []*pb.ProviderRegion) []string { + out := make([]string, 0, len(regions)) + for _, region := range regions { + out = append(out, region.GetName()) + if region.GetDisplayName() == "" { + out = append(out, "") + } + } + return out +} + +func sameStrings(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +func containsString(values []string, want string) bool { + for _, value := range values { + if value == want { + return true + } + } + return false +} diff --git a/plugin.json b/plugin.json index 09e654d..c9a2517 100644 --- a/plugin.json +++ b/plugin.json @@ -42,6 +42,7 @@ "workflow.plugin.external.iac.IaCProviderValidator", "workflow.plugin.external.iac.IaCProviderDriftConfigDetector", "workflow.plugin.external.iac.IaCProviderRequirementMapper", + "workflow.plugin.external.iac.IaCProviderRegionLister", "workflow.plugin.external.iac.ResourceDriver", "workflow.plugin.external.iac.IaCStateBackend" ], From 0c617ae633589b01a3b75e87203c84af71d96df0 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Mon, 1 Jun 2026 12:32:26 -0400 Subject: [PATCH 2/4] fix: source Azure regions --- cmd/workflow-plugin-azure/plugin.json | 1 + go.mod | 3 +- go.sum | 6 +- internal/provider.go | 11 +++ internal/region_lister.go | 126 ++++++++++++++++++++++++-- internal/region_lister_test.go | 18 +++- 6 files changed, 148 insertions(+), 17 deletions(-) diff --git a/cmd/workflow-plugin-azure/plugin.json b/cmd/workflow-plugin-azure/plugin.json index 9c7bf0f..a83200e 100644 --- a/cmd/workflow-plugin-azure/plugin.json +++ b/cmd/workflow-plugin-azure/plugin.json @@ -16,6 +16,7 @@ "workflow.plugin.external.iac.IaCProviderValidator", "workflow.plugin.external.iac.IaCProviderDriftConfigDetector", "workflow.plugin.external.iac.IaCProviderRequirementMapper", + "workflow.plugin.external.iac.IaCProviderRegionLister", "workflow.plugin.external.iac.ResourceDriver", "workflow.plugin.external.iac.IaCStateBackend" ], diff --git a/go.mod b/go.mod index 3b3a94b..b82f393 100644 --- a/go.mod +++ b/go.mod @@ -14,9 +14,10 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6 v6.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis v1.0.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 - github.com/GoCodeAlone/workflow v0.68.2-0.20260601161212-52066f422dd9 + github.com/GoCodeAlone/workflow v0.68.2 google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af ) diff --git a/go.sum b/go.sum index ba41ac9..4720e8f 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis v1.0.0 h1:n github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis v1.0.0/go.mod h1:3yjiOtnkVociBTlF7UZrwAGfJrGaOCsvtVS4HzNajxQ= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0 h1:wxQx2Bt4xzPIKvW59WQf1tJNx/ZZKPfN+EhPX3Z6CYY= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.3.0/go.mod h1:TpiwjwnW/khS0LKs4vW5UmmT9OWcxaveS8U7+tlknzo= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql v1.2.0 h1:S087deZ0kP1RUg4pU7w9U9xpUedTCbOtz+mnd0+hrkQ= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql v1.2.0/go.mod h1:B4cEyXrWBmbfMDAPnpJ1di7MAt5DKP57jPEObAvZChg= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig= @@ -68,8 +70,8 @@ github.com/GoCodeAlone/modular/modules/jsonschema v1.17.0 h1:zoWioqUvuNNDfnjHA1s github.com/GoCodeAlone/modular/modules/jsonschema v1.17.0/go.mod h1:GDU/jsD6AddmXKedj0wZwieUIaQsTBSGMzuj+XHXMrw= github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.10.0 h1:+2M/ecyCxDiXfJM4ibcERuu/BBeIbLTQNcVgRsllR64= github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.10.0/go.mod h1:tlVH1mA5yuU8CB7R7+HXIRaBixZoNid6h+5tew5u3FU= -github.com/GoCodeAlone/workflow v0.68.2-0.20260601161212-52066f422dd9 h1:QRNvNVwlKMl6wTL9Pfzg34VOZH8kTx74jWVVEYCzalM= -github.com/GoCodeAlone/workflow v0.68.2-0.20260601161212-52066f422dd9/go.mod h1:4UwFYm1cM8a/AvGNb1CZAuob0b0gq7552sxcNMdDALA= +github.com/GoCodeAlone/workflow v0.68.2 h1:U0ksQOkIwDReuw+nz4kRoCeYwahoBaItqLzwYIRm758= +github.com/GoCodeAlone/workflow v0.68.2/go.mod h1:4UwFYm1cM8a/AvGNb1CZAuob0b0gq7552sxcNMdDALA= github.com/GoCodeAlone/yaegi v0.17.2 h1:WK6Y6e0t1a6U7r+S2dN3CGWW1PizYD3zO0zneToZPxM= github.com/GoCodeAlone/yaegi v0.17.2/go.mod h1:z5Pr6Wse6QJcQvpgxTxzMAevFarH0N37TG88Y9dprx0= github.com/IBM/sarama v1.47.0 h1:GcQFEd12+KzfPYeLgN69Fh7vLCtYRhVIx0rO4TZO318= diff --git a/internal/provider.go b/internal/provider.go index 1347021..5f43f01 100644 --- a/internal/provider.go +++ b/internal/provider.go @@ -7,6 +7,7 @@ import ( "sync" "time" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/GoCodeAlone/workflow-plugin-azure/internal/driver" "github.com/GoCodeAlone/workflow/interfaces" @@ -23,6 +24,7 @@ type AzureProvider struct { subscriptionID string resourceGroup string location string + credential azcore.TokenCredential drivers map[string]interfaces.ResourceDriver } @@ -77,10 +79,19 @@ func (p *AzureProvider) Initialize(ctx context.Context, config map[string]any) e if err != nil { return fmt.Errorf("azure: init drivers: %w", err) } + p.credential = cred p.drivers = drivers return nil } +// SubscriptionSnapshot returns the initialized subscription and credential for +// typed services that need to call Azure APIs outside the resource-driver path. +func (p *AzureProvider) SubscriptionSnapshot() (string, azcore.TokenCredential, bool) { + p.mu.RLock() + defer p.mu.RUnlock() + return p.subscriptionID, p.credential, p.subscriptionID != "" && p.credential != nil +} + // Capabilities returns the resource types this provider supports. func (p *AzureProvider) Capabilities() []interfaces.IaCCapabilityDeclaration { return []interfaces.IaCCapabilityDeclaration{ diff --git a/internal/region_lister.go b/internal/region_lister.go index 2ff8d5f..88c3936 100644 --- a/internal/region_lister.go +++ b/internal/region_lister.go @@ -4,24 +4,132 @@ import ( "context" "sort" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions" pb "github.com/GoCodeAlone/workflow/plugin/external/proto" ) -var azureProviderRegions = []string{ +var azureFallbackRegions = []string{ + "australiacentral", + "australiacentral2", + "australiaeast", + "australiasoutheast", + "brazilsouth", + "brazilsoutheast", + "canadacentral", + "canadaeast", + "centralindia", + "centralus", + "centraluseuap", + "eastasia", "eastus", + "eastus2", + "eastus2euap", + "francecentral", + "francesouth", + "germanynorth", + "germanywestcentral", + "israelcentral", + "italynorth", + "japaneast", + "japanwest", + "jioindiacentral", + "jioindiawest", + "koreacentral", + "koreasouth", + "malaysiasouth", + "mexicocentral", + "newzealandnorth", + "northcentralus", + "northeurope", + "norwayeast", + "norwaywest", + "polandcentral", + "qatarcentral", + "southafricanorth", + "southafricawest", + "southcentralus", "southeastasia", + "southindia", + "spaincentral", + "swedencentral", + "switzerlandnorth", + "switzerlandwest", + "uaecentral", + "uaenorth", + "uksouth", + "ukwest", + "westcentralus", "westeurope", + "westindia", + "westus", "westus2", + "westus3", } -func (s *azureIaCServer) ListRegions(context.Context, *pb.ListRegionsRequest) (*pb.ListRegionsResponse, error) { - regions := make([]string, len(azureProviderRegions)) - copy(regions, azureProviderRegions) - sort.Strings(regions) +func (s *azureIaCServer) ListRegions(ctx context.Context, _ *pb.ListRegionsRequest) (*pb.ListRegionsResponse, error) { + if s != nil && s.provider != nil { + subscriptionID, cred, ok := s.provider.SubscriptionSnapshot() + if ok { + regions, err := listAzureRegions(ctx, subscriptionID, cred) + if err != nil { + return nil, err + } + return providerRegionsResponse(regions), nil + } + } + return providerRegionsResponse(regionEntries(azureFallbackRegions, nil)), nil +} + +func listAzureRegions(ctx context.Context, subscriptionID string, cred azcore.TokenCredential) ([]*pb.ProviderRegion, error) { + client, err := armsubscriptions.NewClient(cred, nil) + if err != nil { + return nil, err + } + pager := client.NewListLocationsPager(subscriptionID, nil) + var regions []*pb.ProviderRegion + for pager.More() { + page, err := pager.NextPage(ctx) + if err != nil { + return nil, err + } + for _, location := range page.Value { + if location == nil || location.Name == nil || *location.Name == "" { + continue + } + display := stringValue(location.DisplayName) + if display == "" { + display = *location.Name + } + regions = append(regions, &pb.ProviderRegion{Name: *location.Name, DisplayName: display}) + } + } + return regions, nil +} - out := make([]*pb.ProviderRegion, 0, len(regions)) - for _, name := range regions { - out = append(out, &pb.ProviderRegion{Name: name, DisplayName: name}) +func stringValue(value *string) string { + if value == nil { + return "" } - return &pb.ListRegionsResponse{Regions: out}, nil + return *value +} + +func regionEntries(names []string, displayNames map[string]string) []*pb.ProviderRegion { + out := make([]*pb.ProviderRegion, 0, len(names)) + for _, name := range names { + display := displayNames[name] + if display == "" { + display = name + } + out = append(out, &pb.ProviderRegion{Name: name, DisplayName: display}) + } + return out +} + +func providerRegionsResponse(regions []*pb.ProviderRegion) *pb.ListRegionsResponse { + regions = append([]*pb.ProviderRegion(nil), regions...) + sort.Slice(regions, func(i, j int) bool { + return regions[i].GetName() < regions[j].GetName() + }) + return &pb.ListRegionsResponse{Regions: regions} } diff --git a/internal/region_lister_test.go b/internal/region_lister_test.go index ed66e9e..4dd9f5b 100644 --- a/internal/region_lister_test.go +++ b/internal/region_lister_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "os" "path/filepath" + "sort" "testing" pb "github.com/GoCodeAlone/workflow/plugin/external/proto" @@ -18,7 +19,8 @@ func TestAzureIaCServer_ListRegions(t *testing.T) { t.Fatalf("ListRegions: %v", err) } got := regionNames(resp.GetRegions()) - want := []string{"eastus", "southeastasia", "westeurope", "westus2"} + want := append([]string(nil), azureFallbackRegions...) + sort.Strings(want) if !sameStrings(got, want) { t.Fatalf("regions = %v, want %v", got, want) } @@ -35,18 +37,24 @@ func TestAzureIaCServer_RegistersRegionLister(t *testing.T) { } func TestPluginManifestAdvertisesRegionLister(t *testing.T) { - data, err := os.ReadFile(filepath.Join(hostConformanceRepoRoot(t), "plugin.json")) + assertManifestAdvertisesRegionLister(t, filepath.Join(hostConformanceRepoRoot(t), "plugin.json")) + assertManifestAdvertisesRegionLister(t, filepath.Join(hostConformanceRepoRoot(t), "cmd", "workflow-plugin-azure", "plugin.json")) +} + +func assertManifestAdvertisesRegionLister(t *testing.T, path string) { + t.Helper() + data, err := os.ReadFile(path) if err != nil { - t.Fatalf("read plugin.json: %v", err) + t.Fatalf("read %s: %v", path, err) } var manifest struct { IaCServices []string `json:"iacServices"` } if err := json.Unmarshal(data, &manifest); err != nil { - t.Fatalf("parse plugin.json: %v", err) + t.Fatalf("parse %s: %v", path, err) } if !containsString(manifest.IaCServices, pb.IaCProviderRegionLister_ServiceDesc.ServiceName) { - t.Fatalf("iacServices missing %s: %v", pb.IaCProviderRegionLister_ServiceDesc.ServiceName, manifest.IaCServices) + t.Fatalf("%s iacServices missing %s: %v", path, pb.IaCProviderRegionLister_ServiceDesc.ServiceName, manifest.IaCServices) } } From 0706fcbeb4aa745a5246cc92ac497ca91aaa785a Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Mon, 1 Jun 2026 12:43:33 -0400 Subject: [PATCH 3/4] chore: require workflow v0.68.2 --- cmd/workflow-plugin-azure/plugin.json | 2 +- internal/iacserver_mapper_test.go | 4 ++-- plugin.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/workflow-plugin-azure/plugin.json b/cmd/workflow-plugin-azure/plugin.json index a83200e..0ba19c3 100644 --- a/cmd/workflow-plugin-azure/plugin.json +++ b/cmd/workflow-plugin-azure/plugin.json @@ -6,7 +6,7 @@ "license": "MIT", "type": "external", "tier": "community", - "minEngineVersion": "0.64.3", + "minEngineVersion": "0.68.2", "iacServices": [ "workflow.plugin.external.iac.IaCProviderRequired", "workflow.plugin.external.iac.IaCProviderEnumerator", diff --git a/internal/iacserver_mapper_test.go b/internal/iacserver_mapper_test.go index b39c2f4..ee31848 100644 --- a/internal/iacserver_mapper_test.go +++ b/internal/iacserver_mapper_test.go @@ -181,8 +181,8 @@ func TestPluginManifestAdvertisesRequirementMapper(t *testing.T) { if err := json.Unmarshal(data, &manifest); err != nil { t.Fatalf("parse plugin.json: %v", err) } - if manifest.MinEngineVersion != "0.64.3" { - t.Fatalf("minEngineVersion = %q, want 0.64.3", manifest.MinEngineVersion) + if manifest.MinEngineVersion != "0.68.2" { + t.Fatalf("minEngineVersion = %q, want 0.68.2", manifest.MinEngineVersion) } const mapperService = "workflow.plugin.external.iac.IaCProviderRequirementMapper" for _, svc := range manifest.IaCServices { diff --git a/plugin.json b/plugin.json index c9a2517..3db1b9b 100644 --- a/plugin.json +++ b/plugin.json @@ -6,7 +6,7 @@ "license": "MIT", "type": "external", "tier": "community", - "minEngineVersion": "0.64.3", + "minEngineVersion": "0.68.2", "required_secrets": [ { "name": "AZURE_CLIENT_ID", From b2b605b4de78fa9f5de4c56575eb8f1f320b590b Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Mon, 1 Jun 2026 12:52:15 -0400 Subject: [PATCH 4/4] fix: fall back on Azure region lookup errors --- internal/region_lister.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/region_lister.go b/internal/region_lister.go index 88c3936..0dd348c 100644 --- a/internal/region_lister.go +++ b/internal/region_lister.go @@ -72,10 +72,9 @@ func (s *azureIaCServer) ListRegions(ctx context.Context, _ *pb.ListRegionsReque subscriptionID, cred, ok := s.provider.SubscriptionSnapshot() if ok { regions, err := listAzureRegions(ctx, subscriptionID, cred) - if err != nil { - return nil, err + if err == nil { + return providerRegionsResponse(regions), nil } - return providerRegionsResponse(regions), nil } } return providerRegionsResponse(regionEntries(azureFallbackRegions, nil)), nil