Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strongly type ApiVersion and SdkVersion #3753

Merged
merged 4 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
15 changes: 15 additions & 0 deletions provider/pkg/collections/maps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package collections

import (
"cmp"
"slices"
)

func OrderedKeys[T cmp.Ordered](m map[T]any) []T {
keys := make([]T, 0, len(m))
for key := range m {
keys = append(keys, key)
}
slices.Sort(keys)
return keys
}
44 changes: 44 additions & 0 deletions provider/pkg/collections/orderableSet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package collections

import (
"cmp"
"slices"
)

type OrderableSet[T cmp.Ordered] struct {
m map[T]struct{}
}

func NewOrderableSet[T cmp.Ordered](values ...T) *OrderableSet[T] {
newSet := &OrderableSet[T]{m: make(map[T]struct{})}
for _, value := range values {
newSet.Add(value)
}
return newSet
}

func (s *OrderableSet[T]) Add(value T) {
s.m[value] = struct{}{}
}

func (s *OrderableSet[T]) Has(value T) bool {
_, ok := s.m[value]
return ok
}

func (s *OrderableSet[T]) Remove(value T) {
delete(s.m, value)
}

func (s *OrderableSet[T]) Count() int {
return len(s.m)
}

func (s *OrderableSet[T]) SortedValues() []T {
values := make([]T, 0, len(s.m))
for value := range s.m {
values = append(values, value)
}
slices.Sort(values)
return values
}
4 changes: 2 additions & 2 deletions provider/pkg/gen/gen_dashboard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ func TestPortalDashboardGen(t *testing.T) {
if err != nil {
t.Fatal(err)
}
providers = openapi.ApplyProvidersTransformations(providers, map[string]map[string]string{
providers = openapi.ApplyProvidersTransformations(providers, openapi.DefaultVersionLock{
"Portal": {
"Dashboard": "2020-09-01-preview",
},
}, map[string]map[string]string{}, map[string][]string{}, map[string][]string{})
}, openapi.DefaultVersionLock{}, nil, nil)
generationResult, err := PulumiSchema(rootDir, providers, versioningStub{}, 2)
if err != nil {
t.Fatal(err)
Expand Down
70 changes: 41 additions & 29 deletions provider/pkg/gen/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"net/url"
"os"
"path/filepath"
"slices"
"sort"
"strings"

Expand Down Expand Up @@ -49,7 +50,7 @@ type ResourceDeprecation struct {
}

type Versioning interface {
ShouldInclude(provider string, version string, typeName, token string) bool
ShouldInclude(provider string, version *openapi.ApiVersion, typeName, token string) bool
GetDeprecation(token string) (ResourceDeprecation, bool)
GetAllVersions(openapi.ProviderName, openapi.ResourceName) []openapi.ApiVersion
}
Expand Down Expand Up @@ -285,19 +286,29 @@ func PulumiSchema(rootDir string, providerMap openapi.AzureProviders, versioning
exampleMap := make(map[string][]resources.AzureAPIExample)
for _, providerName := range providers {
versionMap := providerMap[providerName]
var versions []string
var versions []openapi.SdkVersion
for version := range versionMap {
versions = append(versions, version)
}
sort.Strings(versions)
slices.Sort(versions)

for _, version := range versions {
for _, sdkVersion := range versions {
// Attempt to convert back to an API version for use elsewhere
var apiVersion *openapi.ApiVersion
danielrbradley marked this conversation as resolved.
Show resolved Hide resolved
if sdkVersion != "" {
apiVersionConverted, err := openapi.SdkToApiVersion(sdkVersion)
if err != nil {
return nil, fmt.Errorf("failed to convert SDK version %s back to API version: %v", sdkVersion, err)
}
apiVersion = &apiVersionConverted
}
gen := packageGenerator{
pkg: &pkg,
metadata: &metadata,
provider: providerName,
apiVersion: version,
allApiVersions: versions,
apiVersion: apiVersion,
sdkVersion: sdkVersion,
allSdkVersions: versions,
examples: exampleMap,
versioning: versioning,
caseSensitiveTypes: caseSensitiveTypes,
Expand All @@ -311,16 +322,16 @@ func PulumiSchema(rootDir string, providerMap openapi.AzureProviders, versioning
module := gen.moduleName()
csharpNamespaces[strings.ToLower(providerName)] = providerName
javaPackages[module] = strings.ToLower(providerName)
if version != "" {
csVersion := strings.Title(csharpVersionReplacer.Replace(version))
if sdkVersion != "" {
csVersion := strings.Title(csharpVersionReplacer.Replace(string(sdkVersion)))
csharpNamespaces[module] = fmt.Sprintf("%s.%s", providerName, csVersion)
javaPackages[module] = fmt.Sprintf("%s.%s", strings.ToLower(providerName), version)
javaPackages[module] = fmt.Sprintf("%s.%s", strings.ToLower(providerName), sdkVersion)
}
pythonModuleNames[module] = module
golangImportAliases[filepath.Join(goModuleRepoPath, gen.versionedModuleName())] = strings.ToLower(providerName)

// Populate resources and get invokes.
items := versionMap[version]
items := versionMap[sdkVersion]
var resources []string
for resource := range items.Resources {
resources = append(resources, resource)
Expand Down Expand Up @@ -626,8 +637,9 @@ type packageGenerator struct {
metadata *resources.AzureAPIMetadata
provider openapi.ProviderName
examples map[string][]resources.AzureAPIExample
apiVersion string
allApiVersions []openapi.ApiVersion
apiVersion *openapi.ApiVersion
sdkVersion openapi.SdkVersion
allSdkVersions []openapi.SdkVersion
versioning Versioning
caseSensitiveTypes caseSensitiveTokens
warnings []string
Expand Down Expand Up @@ -769,7 +781,7 @@ func (g *packageGenerator) findResourceVariants(resource *openapi.ResourceSpec)
return result, nil
}

func (g *packageGenerator) makeTypeAlias(alias, apiVersion string) pschema.AliasSpec {
func (g *packageGenerator) makeTypeAlias(alias string, apiVersion openapi.SdkVersion) pschema.AliasSpec {
fqAlias := fmt.Sprintf("%s:%s:%s", g.pkg.Name, g.providerApiToModule(apiVersion), alias)
return pschema.AliasSpec{Type: &fqAlias}
}
Expand Down Expand Up @@ -847,7 +859,7 @@ func (g *packageGenerator) genResourceVariant(apiSpec *openapi.ResourceSpec, res

resourceSpec := pschema.ResourceSpec{
ObjectTypeSpec: pschema.ObjectTypeSpec{
Description: g.formatDescription(resourceResponse.description, resource.typeName, swagger.Info.Version, apiSpec.PreviousVersion, additionalDocs),
Description: g.formatDescription(resourceResponse.description, resource.typeName, openapi.ApiVersion(swagger.Info.Version), apiSpec.PreviousVersion, additionalDocs),
Type: "object",
Properties: resourceResponse.specs,
Required: resourceResponse.requiredSpecs.SortedValues(),
Expand Down Expand Up @@ -962,7 +974,7 @@ func (g *packageGenerator) generateAliases(resource *resourceVariant, typeNameAl
var aliases []pschema.AliasSpec

for _, alias := range typeNameAliases {
aliases = append(aliases, g.makeTypeAlias(alias, g.apiVersion))
aliases = append(aliases, g.makeTypeAlias(alias, g.sdkVersion))
}

// Add an alias for each API version that has the same path in it.
Expand Down Expand Up @@ -1050,7 +1062,7 @@ func (g *packageGenerator) genFunctions(typeName, path string, specParams []spec
}

// Generate the function to get this resource.
functionTok := g.generateTok(typeName, g.apiVersion)
functionTok := g.generateTok(typeName, g.sdkVersion)
if !g.shouldInclude(typeName, functionTok, g.apiVersion) {
return
}
Expand Down Expand Up @@ -1098,31 +1110,31 @@ func (g *packageGenerator) genFunctions(typeName, path string, specParams []spec
g.metadata.Invokes[functionTok] = f
}

// moduleName produces the module name from the provider name and the API version (e.g. (`Compute`, `2020-07-01` => `compute/v20200701`).
// moduleName produces the module name from the provider name and the version e.g. `compute/v20200701`.
func (g *packageGenerator) moduleName() string {
return g.providerApiToModule(g.apiVersion)
return g.providerApiToModule(g.sdkVersion)
}

func (g *packageGenerator) versionedModuleName() string {
versionedModule := strings.ToLower(g.provider) + goModuleVersion
if g.apiVersion == "" {
if g.sdkVersion == "" {
return versionedModule
}
return fmt.Sprintf("%s/%s", versionedModule, g.apiVersion)
return fmt.Sprintf("%s/%s", versionedModule, g.sdkVersion)
}

func (g *packageGenerator) providerApiToModule(apiVersion string) string {
func (g *packageGenerator) providerApiToModule(apiVersion openapi.SdkVersion) string {
if apiVersion == "" {
return strings.ToLower(g.provider)
}
return fmt.Sprintf("%s/%s", strings.ToLower(g.provider), apiVersion)
}

func (g *packageGenerator) generateTok(typeName string, apiVersion string) string {
func (g *packageGenerator) generateTok(typeName string, apiVersion openapi.SdkVersion) string {
return fmt.Sprintf(`%s:%s:%s`, g.pkg.Name, g.providerApiToModule(apiVersion), typeName)
}

func (g *packageGenerator) shouldInclude(typeName, tok, version string) bool {
func (g *packageGenerator) shouldInclude(typeName, tok string, version *openapi.ApiVersion) bool {
return g.versioning.ShouldInclude(g.provider, version, typeName, tok)
}

Expand All @@ -1131,30 +1143,30 @@ func (g *packageGenerator) formatFunctionDescription(op *spec.Operation, typeNam
if op.Description != "" {
desc = op.Description
}
return g.formatDescription(desc, typeName, info.Version, "", nil)
return g.formatDescription(desc, typeName, openapi.ApiVersion(info.Version), "", nil)
}

func (g *packageGenerator) formatDescription(desc string, typeName string, defaultVersion, previousDefaultVersion string, additionalDocs *string) string {
func (g *packageGenerator) formatDescription(desc string, typeName string, defaultVersion, previousDefaultVersion openapi.ApiVersion, additionalDocs *string) string {
var b strings.Builder
b.WriteString(desc)

if g.apiVersion == "" {
if g.sdkVersion == "" {
fmt.Fprintf(&b, "\nAzure REST API version: %s.", defaultVersion)
if previousDefaultVersion != "" {
fmt.Fprintf(&b, " Prior API version in Azure Native 1.x: %s.", previousDefaultVersion)
}

// List other available API versions, if any.
allVersions := g.versioning.GetAllVersions(g.provider, typeName)
includedVersions := []openapi.ApiVersion{}
includedVersions := []string{}
for _, v := range allVersions {
// Don't list the default version twice.
if v == defaultVersion {
continue
}
tok := g.generateTok(typeName, openapi.ApiToSdkVersion(v))
if g.shouldInclude(typeName, tok, v) {
includedVersions = append(includedVersions, v)
if g.shouldInclude(typeName, tok, &v) {
includedVersions = append(includedVersions, string(v))
}
}

Expand Down
16 changes: 8 additions & 8 deletions provider/pkg/gen/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
func TestTypeAliasFormatting(t *testing.T) {
generator := packageGenerator{
pkg: &pschema.PackageSpec{Name: "azure-native"},
apiVersion: "v20220222",
sdkVersion: "v20220222",
provider: "Compute",
}

Expand All @@ -34,12 +34,12 @@ func TestTypeAliasFormatting(t *testing.T) {
var _ Versioning = (*versioningStub)(nil)

type versioningStub struct {
shouldInclude func(provider string, version string, typeName, token string) bool
shouldInclude func(provider string, version *openapi.ApiVersion, typeName, token string) bool
getDeprecations func(token string) (ResourceDeprecation, bool)
getAllVersions func(provider, resource string) []string
getAllVersions func(provider, resource string) []openapi.ApiVersion
}

func (v versioningStub) ShouldInclude(provider string, version string, typeName, token string) bool {
func (v versioningStub) ShouldInclude(provider string, version *openapi.ApiVersion, typeName, token string) bool {
if v.shouldInclude != nil {
return v.shouldInclude(provider, version, typeName, token)
}
Expand All @@ -53,25 +53,25 @@ func (v versioningStub) GetDeprecation(token string) (ResourceDeprecation, bool)
return ResourceDeprecation{}, false
}

func (v versioningStub) GetAllVersions(provider, resource string) []string {
func (v versioningStub) GetAllVersions(provider, resource string) []openapi.ApiVersion {
if v.getAllVersions != nil {
return v.getAllVersions(provider, resource)
}
return []string{}
return []openapi.ApiVersion{}
}

func TestAliases(t *testing.T) {
generator := packageGenerator{
pkg: &pschema.PackageSpec{Name: "azure-native"},
apiVersion: "v20220222",
sdkVersion: "v20220222",
versioning: versioningStub{},
provider: "Insights",
majorVersion: 2,
}

resource := &resourceVariant{
ResourceSpec: &openapi.ResourceSpec{
CompatibleVersions: []string{"v20210111"},
CompatibleVersions: []openapi.SdkVersion{"v20210111"},
},
typeName: "PrivateLinkForAzureAd",
}
Expand Down
11 changes: 4 additions & 7 deletions provider/pkg/openapi/apiVersion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,17 @@ import (

func TestApiVersionToDate(t *testing.T) {
t.Run("simple", func(t *testing.T) {
apiVersion := "2020-01-01"
date, err := ApiVersionToDate(apiVersion)
date, err := ApiVersionToDate("2020-01-01")
assert.NoError(t, err)
actual := date.Format("2006-01-02")
assert.Equal(t, apiVersion, actual)
assert.Equal(t, "2020-01-01", actual)
})

t.Run("preview", func(t *testing.T) {
apiVersion := "2020-01-01-preview"
date, err := ApiVersionToDate(apiVersion)
date, err := ApiVersionToDate("2020-01-01-preview")
assert.NoError(t, err)
expected := "2020-01-01"
actual := date.Format("2006-01-02")
assert.Equal(t, expected, actual)
assert.Equal(t, "2020-01-01", actual)
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ func TestAllDefaultStatesConvertable(t *testing.T) {
resourceTokens, found := resourcesByNormalisedPath[pathWithoutVersion]
require.Truef(t, found, "Resource not found in test data: %s", pathWithoutVersion)
for _, resourceToken := range resourceTokens {
var apiVersion string
var apiVersion openapi.ApiVersion
sdkVersionMatch := resourceTokenVersionMatcher.FindStringSubmatch(resourceToken)
if len(sdkVersionMatch) > 1 {
apiVersion, err = openapi.SdkToApiVersion(sdkVersionMatch[1])
apiVersion, err = openapi.SdkToApiVersion(openapi.SdkVersion(sdkVersionMatch[1]))
require.Nil(t, err, "Failed to convert SDK version to API version: %s", sdkVersionMatch[1])
}
defaultState := defaults.GetDefaultResourceState(path, apiVersion)
defaultState := defaults.GetDefaultResourceState(path, string(apiVersion))
if defaultState == nil || defaultState.SkipDelete {
return
}
Expand Down
Loading
Loading