Skip to content

Commit

Permalink
Add the --api-group-override command-line option to the resolver tran…
Browse files Browse the repository at this point in the history
…sformation

to be able to override the generated API group names.

- Add the --api-resolver-package command-line option to the resolver transformation
  to properly configure the package of the GetManagedResource function.

Signed-off-by: Alper Rifat Ulucinar <[email protected]>
  • Loading branch information
ulucinar committed Jan 26, 2024
1 parent 994c07a commit bbac0a6
Show file tree
Hide file tree
Showing 4 changed files with 1,107 additions and 6 deletions.
4 changes: 3 additions & 1 deletion cmd/resolver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ func main() {
var (
app = kingpin.New(filepath.Base(os.Args[0]), "Transformer for the generated resolvers by the crossplane-tools so that cross API-group imports are removed.").DefaultEnvars()
apiGroupSuffix = app.Flag("apiGroupSuffix", "Resource API group suffix, such as aws.upbound.io. The resource API group names are suffixed with this to get the canonical API group name.").Short('g').Required().String()
apiGroupOverride = app.Flag("apiGroupOverride", "API group overrides").Short('o').StringMap()
apiResolverPackage = app.Flag("apiResolverPackage", "The package that contains the implementation for the GetManagedResource function, such as github.com/upbound/provider-aws/internal/apis.").Short('a').Required().String()
pattern = app.Flag("pattern", "List patterns for the packages to process, such as ./...").Short('p').Default("./...").Strings()
resolverFilePattern = app.Flag("resolver", "Name of the generated resolver files to process.").Short('r').Default("zz_generated.resolvers.go").String()
ignorePackageLoadErrors = app.Flag("ignoreLoadErrors", "Ignore errors encountered while loading the packages.").Short('s').Bool()
)
kingpin.MustParse(app.Parse(os.Args[1:]))
logger := logging.NewLogrLogger(zap.New().WithName("transformer-resolver"))
r := transformers.NewResolver(afero.NewOsFs(), *apiGroupSuffix, *ignorePackageLoadErrors, logger)
r := transformers.NewResolver(afero.NewOsFs(), *apiGroupSuffix, *apiResolverPackage, *ignorePackageLoadErrors, logger, transformers.WithAPIGroupOverrides(*apiGroupOverride))
kingpin.FatalIfError(r.TransformPackages(*resolverFilePattern, *pattern...), "Failed to transform the resolver files in the specified packages.")
}
51 changes: 47 additions & 4 deletions pkg/transformers/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,21 @@ type Resolver struct {
// managed resources, such as aws.upbound.io. Then a sample
// API group for a resource is ec2.aws.upbound.io.
apiGroupSuffix string
// API group overrides for the provider. Certain providers need
// to rename the short API group names they use, breaking the
// convention that the short group name matches the package name.
// An example is upbound/provider-azure, where the ResourceGroup.azure
// resource's short API group is the empty string. This map allows such
// providers to control the names of the generated API group names by this
// Resolver transformer.
apiGroupOverrides map[string]string
// the API resolver package that contains the
// `GetManagedResource("group", "version", "kind", "listKind")`
// function. This function is used to initialize a managed resource and
// its list type, owned by the provider, with the given API group, version,
// kind and list kind. Signature of the resolver function is as follows:
// func GetManagedResource(group, version, kind, listKind string) (xpresource.Managed, xpresource.ManagedList, error)
apiResolverPackage string
// When set, any errors encountered while loading the source packages is
// silently ignored if a logger is not configured,
// or logged via the configured logger.
Expand All @@ -54,13 +69,14 @@ type Resolver struct {
}

// NewResolver initializes a new Resolver with the specified configuration.
func NewResolver(fs afero.Fs, apiGroupSuffix string, ignorePackageLoadErrors bool, logger logging.Logger, opts ...ResolverOption) *Resolver {
func NewResolver(fs afero.Fs, apiGroupSuffix, apiResolverPackage string, ignorePackageLoadErrors bool, logger logging.Logger, opts ...ResolverOption) *Resolver {
if logger == nil {
logger = logging.NewNopLogger()
}
r := &Resolver{
fs: fs,
apiGroupSuffix: apiGroupSuffix,
apiResolverPackage: apiResolverPackage,
ignorePackageLoadErrors: ignorePackageLoadErrors,
logger: logger,
config: &packages.Config{
Expand All @@ -83,6 +99,17 @@ func WithLoaderConfig(c *packages.Config) ResolverOption {
}
}

// WithAPIGroupOverrides configures the API group overrides for a Resolver.
// Certain providers need to rename the short API group names they use,
// breaking the convention that the short group name matches the package name.
// An example is upbound/provider-azure, where the ResourceGroup.azure
// resource's short API group is the empty string.
func WithAPIGroupOverrides(overrides map[string]string) ResolverOption {
return func(r *Resolver) {
r.apiGroupOverrides = overrides
}
}

// TransformPackages applies the dynamic resolver transformation to
// the resolver modules loaded from the specified patterns and
// implemented in the specified resolver files. If `r.ignorePackageLoadErrors`
Expand Down Expand Up @@ -324,6 +351,8 @@ func (r *Resolver) transformResolverFile(fset *token.FileSet, node *ast.File, fi
version = tokens[len(tokens)-1]
// e.g., ec2.aws.upbound.io
group = fmt.Sprintf("%s.%s", tokens[len(tokens)-2], apiGroupSuffix)
// apply any configured group name overrides
group = r.overrideGroupName(group)
// extract the kind and list kind names from the field
// selector.
if sexpr.Sel != nil {
Expand All @@ -346,6 +375,8 @@ func (r *Resolver) transformResolverFile(fset *token.FileSet, node *ast.File, fi
version = tokens[len(tokens)-2]
// e.g., cur.aws.upbound.io
group = fmt.Sprintf("%s.%s", tokens[len(tokens)-3], apiGroupSuffix)
// apply any configured group name overrides
group = r.overrideGroupName(group)
if ident, ok := cl.Type.(*ast.Ident); ok {
if key == "List" {
listKind = ident.Name
Expand Down Expand Up @@ -389,7 +420,7 @@ func (r *Resolver) transformResolverFile(fset *token.FileSet, node *ast.File, fi

// get the statements including the import statements we need to make
// calls to the type registry.
mrImports, stmts := getManagedResourceStatements(group, version, kind, listKind)
mrImports, stmts := getManagedResourceStatements(group, version, kind, listKind, r.apiResolverPackage)
// insert the statements that implement type registry lookups
if !insertStatements(stmts, block, assign) {
inspectErr = errors.Errorf("failed to insert the type registry lookup statements for Group: %q, Version: %q, Kind: %q, List Kind: %q", group, version, kind, listKind)
Expand Down Expand Up @@ -532,7 +563,7 @@ func addMRVariableDeclarations(f *ast.File) map[string]string {
}
}

func getManagedResourceStatements(group, version, kind, listKind string) (map[string]string, []ast.Stmt) {
func getManagedResourceStatements(group, version, kind, listKind, apiResolverPackage string) (map[string]string, []ast.Stmt) {
// prepare the assignment statement:
// `m, l, err = apisresolver.GetManagedResource("group", "version", "kind", "listKind")`
assignStmt := &ast.AssignStmt{
Expand Down Expand Up @@ -590,6 +621,18 @@ func getManagedResourceStatements(group, version, kind, listKind string) (map[st
},
}
return map[string]string{
`"github.com/upbound/provider-aws/internal/apis"`: "apisresolver",
// TODO: we may need to parameterize the import alias in the future, if
// any provider that uses the transformer has an import alias collision
// which is not very likely.
fmt.Sprintf(`"%s"`, apiResolverPackage): "apisresolver",
}, []ast.Stmt{assignStmt, ifStmt}
}

func (r *Resolver) overrideGroupName(group string) string {
g, ok := r.apiGroupOverrides[group]
// we need to allow overrides with an empty string
if !ok {
return group
}
return g
}
26 changes: 25 additions & 1 deletion pkg/transformers/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ func TestTransformPackages(t *testing.T) {
// args struct to define input arguments for each test case
type args struct {
apiGroupSuffix string
apiResolverPackage string
apiGroupOverrides map[string]string
resolverFilePattern string
ignorePackageLoadErrors bool
patterns []string
Expand All @@ -44,6 +46,7 @@ func TestTransformPackages(t *testing.T) {
reason: "Transformation of the source file that has been generated by crossplane-tool's angryjet succeeds with the expected transformed file.",
args: args{
apiGroupSuffix: "aws.upbound.io",
apiResolverPackage: "github.com/upbound/provider-aws/internal/apis",
resolverFilePattern: "zz_generated.resolvers.go",
inputFilePath: "testdata/apigatewayv2.resolvers.go.txt",
ignorePackageLoadErrors: true,
Expand All @@ -57,6 +60,7 @@ func TestTransformPackages(t *testing.T) {
reason: "The applied transformation is idempotent, i.e., applying the transformer on an already transformed file does not change the transformed file.",
args: args{
apiGroupSuffix: "aws.upbound.io",
apiResolverPackage: "github.com/upbound/provider-aws/internal/apis",
resolverFilePattern: "zz_generated.resolvers.go",
inputFilePath: "testdata/apigatewayv2.resolvers.transformed.go.txt",
ignorePackageLoadErrors: true,
Expand All @@ -70,6 +74,7 @@ func TestTransformPackages(t *testing.T) {
reason: "The transformation source is not a valid angryjet-generated resolver file: List type is missing for a resolution source.",
args: args{
apiGroupSuffix: "aws.upbound.io",
apiResolverPackage: "github.com/upbound/provider-aws/internal/apis",
resolverFilePattern: "zz_generated.resolvers.go",
inputFilePath: "testdata/missing_list_type.resolvers.go.txt",
ignorePackageLoadErrors: true,
Expand All @@ -85,6 +90,23 @@ func TestTransformPackages(t *testing.T) {
},
},
},
"SuccessfulTransformationWithGroupOverrides": {
reason: "Transformation of the source file with a group overrides configuration succeeds with the expected transformed file.",
args: args{
apiGroupSuffix: "aws.upbound.io",
apiResolverPackage: "github.com/upbound/provider-aws/internal/resolver",
apiGroupOverrides: map[string]string{
"ec2.aws.upbound.io": "replacedec2.aws.upbound.io",
},
resolverFilePattern: "zz_generated.resolvers.go",
inputFilePath: "testdata/apigatewayv2.resolvers.go.txt",
ignorePackageLoadErrors: true,
patterns: []string{"./testdata"},
},
want: want{
transformedPath: "testdata/apigatewayv2.resolvers.withoverrides.go.txt",
},
},
}

for name, tc := range cases {
Expand All @@ -101,7 +123,9 @@ func TestTransformPackages(t *testing.T) {
transformedFilePath := filepath.Join(exported.Temp(), "fake", "testdata", tc.args.resolverFilePattern)
writeFile(t, memFS, transformedFilePath, []byte(inputFileContents), tc.reason)

r := NewResolver(memFS, tc.args.apiGroupSuffix, tc.args.ignorePackageLoadErrors, nil, WithLoaderConfig(exported.Config))
r := NewResolver(memFS, tc.args.apiGroupSuffix, tc.args.apiResolverPackage, tc.args.ignorePackageLoadErrors, nil,
WithLoaderConfig(exported.Config),
WithAPIGroupOverrides(tc.args.apiGroupOverrides))
err := r.TransformPackages("zz_generated.resolvers.go", tc.args.patterns...)
var wantErr error
if tc.want.errFunc != nil {
Expand Down
Loading

0 comments on commit bbac0a6

Please sign in to comment.