From af0dc440500da20e163c666f6d5333866d8bb573 Mon Sep 17 00:00:00 2001 From: Alper Rifat Ulucinar Date: Tue, 14 Dec 2021 09:26:00 +0300 Subject: [PATCH 1/3] Add example manifest generation pipeline Signed-off-by: Alper Rifat Ulucinar --- go.mod | 1 + pkg/config/provider.go | 28 ++++ pkg/pipeline/crd.go | 3 + pkg/pipeline/example.go | 297 ++++++++++++++++++++++++++++++++++++ pkg/pipeline/run.go | 21 ++- pkg/types/builder.go | 56 +++++-- pkg/types/reference.go | 4 +- pkg/types/reference_test.go | 2 +- 8 files changed, 393 insertions(+), 19 deletions(-) create mode 100644 pkg/pipeline/example.go diff --git a/go.mod b/go.mod index eb9233f9..297e2ea8 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( k8s.io/apimachinery v0.21.3 k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471 sigs.k8s.io/controller-runtime v0.9.6 + sigs.k8s.io/yaml v1.2.0 ) // This is a temporary workaround until https://github.com/crossplane/terrajet/issues/131 diff --git a/pkg/config/provider.go b/pkg/config/provider.go index 6ef48d1f..6cb5e8ee 100644 --- a/pkg/config/provider.go +++ b/pkg/config/provider.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package config import ( @@ -93,6 +109,10 @@ type Provider struct { // resource name. Resources map[string]*Resource + // ProviderMetadataPath is the scraped provider metadata file path + // from Terraform registry + ProviderMetadataPath string + // resourceConfigurators is a map holding resource configurators where key // is Terraform resource name. resourceConfigurators map[string]ResourceConfiguratorChain @@ -143,6 +163,14 @@ func WithDefaultResourceFn(f DefaultResourceFn) ProviderOption { } } +// WithProviderMetadata configures the Terraform metadata file scraped +// from the Terraform registry +func WithProviderMetadata(metadataPath string) ProviderOption { + return func(p *Provider) { + p.ProviderMetadataPath = metadataPath + } +} + // NewProvider builds and returns a new Provider. func NewProvider(resourceMap map[string]*schema.Resource, prefix string, modulePath string, opts ...ProviderOption) *Provider { p := &Provider{ diff --git a/pkg/pipeline/crd.go b/pkg/pipeline/crd.go index e95ca214..3ae3202b 100644 --- a/pkg/pipeline/crd.go +++ b/pkg/pipeline/crd.go @@ -54,6 +54,7 @@ type CRDGenerator struct { Group string ProviderShortName string LicenseHeaderPath string + Generated *tjtypes.Generated pkg *types.Package } @@ -77,6 +78,8 @@ func (cg *CRDGenerator) Generate(cfg *config.Resource) (string, error) { if err != nil { return "", errors.Wrapf(err, "cannot build types for %s", cfg.Kind) } + cg.Generated = &gen + // TODO(muvaf): TypePrinter uses the given scope to see if the type exists // before printing. We should ideally load the package in file system but // loading the local package will result in error if there is diff --git a/pkg/pipeline/example.go b/pkg/pipeline/example.go new file mode 100644 index 00000000..dc4dbf26 --- /dev/null +++ b/pkg/pipeline/example.go @@ -0,0 +1,297 @@ +/* +Copyright 2021 The Crossplane Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pipeline + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/crossplane/crossplane-runtime/pkg/fieldpath" + "github.com/pkg/errors" + "sigs.k8s.io/yaml" + + "github.com/crossplane/terrajet/pkg/config" + "github.com/crossplane/terrajet/pkg/resource/json" + tjtypes "github.com/crossplane/terrajet/pkg/types" +) + +var ( + reRef = regexp.MustCompile(`\${(.+)}`) +) + +type pavedWithManifest struct { + manifestPath string + paved *fieldpath.Paved +} + +// ResourceExample represents the scraped example HCL configuration +// for a Terraform resource +type ResourceExample struct { + Manifest string `yaml:"manifest"` + References map[string]string `yaml:"references,omitempty"` +} + +// Resource represents the scraped metadata for a Terraform resource +type Resource struct { + SubCategory string `yaml:"subCategory"` + Description string `yaml:"description,omitempty"` + Name string `yaml:"name"` + TitleName string `yaml:"titleName"` + Examples []ResourceExample `yaml:"examples,omitempty"` + ArgumentDocs map[string]string `yaml:"argumentDocs"` + ImportStatements []string `yaml:"importStatements"` +} + +// ProviderMetadata metadata for a Terraform native provider +type ProviderMetadata struct { + Name string `yaml:"name"` + Resources map[string]*Resource `yaml:"resources"` +} + +// NewProviderMetadataFromFile loads metadata from the specified YAML-formatted file +func NewProviderMetadataFromFile(path string) (*ProviderMetadata, error) { + buff, err := ioutil.ReadFile(filepath.Clean(path)) + if err != nil { + return nil, errors.Wrapf(err, "failed to read metadata file %q", path) + } + + metadata := &ProviderMetadata{} + return metadata, errors.Wrap(yaml.Unmarshal(buff, metadata), "failed to unmarshal provider metadata") +} + +// ExampleGenerator represents a pipeline for generating example manifests. +// Generates example manifests for Terraform resources under examples-generated. +type ExampleGenerator struct { + rootDir string + resourceMeta map[string]*Resource + resources map[string]*pavedWithManifest +} + +// NewExampleGenerator returns a configured ExampleGenerator +func NewExampleGenerator(rootDir string, resourceMeta map[string]*Resource) *ExampleGenerator { + return &ExampleGenerator{ + rootDir: rootDir, + resourceMeta: resourceMeta, + resources: make(map[string]*pavedWithManifest), + } +} + +// StoreExamples stores the generated example manifests under examples-generated in +// their respective API groups. +func (eg *ExampleGenerator) StoreExamples() error { + for n, pm := range eg.resources { + if err := eg.resolveReferences(pm.paved.UnstructuredContent()); err != nil { + return errors.Wrapf(err, "cannot resolve references for resource: %s", n) + } + buff, err := yaml.Marshal(pm.paved.UnstructuredContent()) + if err != nil { + return errors.Wrapf(err, "cannot marshal example manifest for resource: %s", n) + } + manifestDir := filepath.Dir(pm.manifestPath) + if err := os.MkdirAll(manifestDir, 0750); err != nil { + return errors.Wrapf(err, "cannot mkdir %s", manifestDir) + } + + b := bytes.Buffer{} + b.WriteString("# This example manifest is auto-generated, and has not been tested.\n") + b.WriteString("# Please make the necessary adjustments before using it.\n") + b.Write(buff) + // no sensitive info in the example manifest + if err := ioutil.WriteFile(pm.manifestPath, b.Bytes(), 0644); err != nil { // nolint:gosec + return errors.Wrapf(err, "cannot write example manifest file %s for resource %s", pm.manifestPath, n) + } + } + return nil +} + +func (eg *ExampleGenerator) resolveReferences(params map[string]interface{}) error { // nolint:gocyclo + for k, v := range params { + switch t := v.(type) { + case map[string]interface{}: + if err := eg.resolveReferences(t); err != nil { + return err + } + + case []interface{}: + for _, e := range t { + eM, ok := e.(map[string]interface{}) + if !ok { + continue + } + if err := eg.resolveReferences(eM); err != nil { + return err + } + } + + case string: + g := reRef.FindStringSubmatch(t) + if len(g) != 2 { + continue + } + path := strings.Split(g[1], ".") + // expected reference format is .. + if len(path) < 3 { + continue + } + pm := eg.resources[path[0]] + if pm == nil || pm.paved == nil { + continue + } + pathStr := strings.Join(append([]string{"spec", "forProvider"}, path[2:]...), ".") + s, err := pm.paved.GetString(pathStr) + if fieldpath.IsNotFound(err) { + continue + } + if err != nil { + return errors.Wrapf(err, "cannot get string value from paved: %s", pathStr) + } + params[k] = s + } + } + return nil +} + +// Generate generates an example manifest for the specified Terraform resource. +func (eg *ExampleGenerator) Generate(group, version string, r *config.Resource, fieldTransformations map[string]tjtypes.Transformation) error { + rm := eg.resourceMeta[r.Name] + if rm == nil || len(rm.Examples) == 0 { + return nil + } + var exampleParams map[string]interface{} + if err := json.TFParser.Unmarshal([]byte(rm.Examples[0].Manifest), &exampleParams); err != nil { + return errors.Wrapf(err, "cannot unmarshal example manifest for resource: %s", r.Name) + } + transformRefFields(exampleParams, r.ExternalName.OmittedFields, fieldTransformations, "") + + example := map[string]interface{}{ + "apiVersion": fmt.Sprintf("%s/%s", group, version), + "kind": r.Kind, + "metadata": map[string]interface{}{ + "name": "example", + }, + "spec": map[string]interface{}{ + "forProvider": exampleParams, + "providerConfigRef": map[string]interface{}{ + "name": "example", + }, + }, + } + manifestDir := filepath.Join(eg.rootDir, "examples-generated", strings.ToLower(strings.Split(group, ".")[0])) + eg.resources[r.Name] = &pavedWithManifest{ + manifestPath: filepath.Join(manifestDir, fmt.Sprintf("%s.yaml", strings.ToLower(r.Kind))), + paved: fieldpath.Pave(example), + } + return nil +} + +func getHierarchicalName(prefix, name string) string { + if prefix == "" { + return name + } + return fmt.Sprintf("%s.%s", prefix, name) +} + +func transformRefFields(params map[string]interface{}, omittedFields []string, t map[string]tjtypes.Transformation, namePrefix string) { // nolint:gocyclo + for _, hn := range omittedFields { + for n := range params { + if hn == getHierarchicalName(namePrefix, n) { + delete(params, n) + break + } + } + } + + for n, v := range params { + switch pT := v.(type) { + case map[string]interface{}: + transformRefFields(pT, omittedFields, t, getHierarchicalName(namePrefix, n)) + + case []interface{}: + for _, e := range pT { + eM, ok := e.(map[string]interface{}) + if !ok { + continue + } + transformRefFields(eM, omittedFields, t, getHierarchicalName(namePrefix, n)) + } + } + } + + for hn, transform := range t { + for n, v := range params { + if hn == getHierarchicalName(namePrefix, n) { + delete(params, n) + if transform.IsRef { + if !transform.IsSensitive { + params[transform.TransformedName] = getRefField(v, + map[string]interface{}{ + "name": "example", + }) + } else { + secretName, secretKey := getSecretRef(v) + params[transform.TransformedName] = getRefField(v, + map[string]interface{}{ + "name": secretName, + "namespace": "crossplane-system", + "key": secretKey, + }) + } + } else { + params[transform.TransformedName] = v + } + break + } + } + } +} + +func getRefField(v interface{}, ref map[string]interface{}) interface{} { + switch v.(type) { + case []interface{}: + return []interface{}{ + ref, + } + + default: + return ref + } +} + +func getSecretRef(v interface{}) (string, string) { + secretName := "example-secret" + secretKey := "example-key" + s, ok := v.(string) + if !ok { + return secretName, secretKey + } + g := reRef.FindStringSubmatch(s) + if len(g) != 2 { + return secretName, secretKey + } + parts := strings.Split(g[1], ".") + if len(parts) < 3 { + return secretName, secretKey + } + secretName = fmt.Sprintf("example-%s", strings.Join(strings.Split(parts[0], "_")[1:], "-")) + secretKey = fmt.Sprintf("attribute.%s", strings.Join(parts[2:], ".")) + return secretName, secretKey +} diff --git a/pkg/pipeline/run.go b/pkg/pipeline/run.go index d2fc6bcc..3602ec02 100644 --- a/pkg/pipeline/run.go +++ b/pkg/pipeline/run.go @@ -23,9 +23,9 @@ import ( "sort" "strings" - "github.com/crossplane/terrajet/pkg/config" - "github.com/crossplane/crossplane-runtime/pkg/errors" + + "github.com/crossplane/terrajet/pkg/config" ) // Run runs the Terrajet code generation pipelines. @@ -52,6 +52,15 @@ func Run(pc *config.Provider, rootDir string) { // nolint:gocyclo resourcesGroups[group][resource.Version][name] = resource } + metaResources := make(map[string]*Resource) + if pc.ProviderMetadataPath != "" { + providerMetadata, err := NewProviderMetadataFromFile(filepath.Join(rootDir, pc.ProviderMetadataPath)) + if err != nil { + panic(errors.Wrap(err, "cannot read Terraform provider metadata")) + } + metaResources = providerMetadata.Resources + } + // Add ProviderConfig API package to the list of API version packages. apiVersionPkgList := make([]string, 0) for _, p := range pc.BasePackages.APIVersion { @@ -63,6 +72,7 @@ func Run(pc *config.Provider, rootDir string) { // nolint:gocyclo controllerPkgList = append(controllerPkgList, filepath.Join(pc.ModulePath, p)) } count := 0 + exampleGen := NewExampleGenerator(rootDir, metaResources) for group, versions := range resourcesGroups { for version, resources := range versions { versionGen := NewVersionGenerator(rootDir, pc.ModulePath, group, version) @@ -83,6 +93,9 @@ func Run(pc *config.Provider, rootDir string) { // nolint:gocyclo panic(errors.Wrapf(err, "cannot generate controller for resource %s", name)) } controllerPkgList = append(controllerPkgList, ctrlPkgPath) + if err := exampleGen.Generate(group, version, resources[name], crdGen.Generated.FieldTransformations); err != nil { + panic(errors.Wrapf(err, "cannot generate example manifest for resource %s", name)) + } count++ } @@ -93,6 +106,10 @@ func Run(pc *config.Provider, rootDir string) { // nolint:gocyclo } } + if err := exampleGen.StoreExamples(); err != nil { + panic(errors.Wrapf(err, "cannot store examples")) + } + if err := NewRegisterGenerator(rootDir, pc.ModulePath).Generate(apiVersionPkgList); err != nil { panic(errors.Wrap(err, "cannot generate register file")) } diff --git a/pkg/types/builder.go b/pkg/types/builder.go index 5c3d0bd4..4391d268 100644 --- a/pkg/types/builder.go +++ b/pkg/types/builder.go @@ -45,6 +45,15 @@ func NewBuilder(pkg *types.Package) *Builder { } } +// Transformation represents a transformation applied to a +// Terraform resource attribute. It's used to record any +// transformations applied by the CRD generation pipeline. +type Transformation struct { + TransformedName string + IsRef bool + IsSensitive bool +} + // Generated is a struct that holds generated types type Generated struct { Types []*types.Named @@ -52,6 +61,8 @@ type Generated struct { ForProviderType *types.Named AtProviderType *types.Named + + FieldTransformations map[string]Transformation } // Builder is used to generate Go type equivalence of given Terraform schema. @@ -64,16 +75,18 @@ type Builder struct { // Build returns parameters and observation types built out of Terraform schema. func (g *Builder) Build(cfg *config.Resource) (Generated, error) { - fp, ap, err := g.buildResource(cfg.TerraformResource, cfg, nil, nil, cfg.Kind) + fieldTransformations := make(map[string]Transformation) + fp, ap, err := g.buildResource(cfg.TerraformResource, cfg, nil, nil, fieldTransformations, cfg.Kind) return Generated{ - Types: g.genTypes, - Comments: g.comments, - ForProviderType: fp, - AtProviderType: ap, + Types: g.genTypes, + Comments: g.comments, + ForProviderType: fp, + AtProviderType: ap, + FieldTransformations: fieldTransformations, }, errors.Wrapf(err, "cannot build the Types") } -func (g *Builder) buildResource(res *schema.Resource, cfg *config.Resource, tfPath []string, xpPath []string, names ...string) (*types.Named, *types.Named, error) { //nolint:gocyclo +func (g *Builder) buildResource(res *schema.Resource, cfg *config.Resource, tfPath []string, xpPath []string, t map[string]Transformation, names ...string) (*types.Named, *types.Named, error) { //nolint:gocyclo // NOTE(muvaf): There can be fields in the same CRD with same name but in // different types. Since we generate the type using the field name, there // can be collisions. In order to be able to generate unique names consistently, @@ -134,7 +147,7 @@ func (g *Builder) buildResource(res *schema.Resource, cfg *config.Resource, tfPa } } - fieldType, err := g.buildSchema(sch, cfg, tfPaths, xpPaths, append(names, fieldName.Camel)) + fieldType, err := g.buildSchema(sch, cfg, tfPaths, xpPaths, t, append(names, fieldName.Camel)) if err != nil { return nil, nil, errors.Wrapf(err, "cannot infer type from schema of field %s", fieldName.Snake) } @@ -144,6 +157,7 @@ func (g *Builder) buildResource(res *schema.Resource, cfg *config.Resource, tfPa sch.Optional = true } + fPath := fieldPath(tfPaths) fieldNameCamel := fieldName.Camel if sch.Sensitive { if isObservation(sch) { @@ -153,7 +167,13 @@ func (g *Builder) buildResource(res *schema.Resource, cfg *config.Resource, tfPa continue } sfx := "SecretRef" - cfg.Sensitive.AddFieldPath(fieldPathWithWildcard(tfPaths), "spec.forProvider."+fieldPathWithWildcard(xpPaths)+sfx) + paramSecretRefField := fieldPathWithWildcard(xpPaths) + sfx + cfg.Sensitive.AddFieldPath(fieldPathWithWildcard(tfPaths), "spec.forProvider."+paramSecretRefField) + t[fPath] = Transformation{ + TransformedName: xpPaths[len(xpPaths)-1] + sfx, + IsRef: true, + IsSensitive: true, + } // todo(turkenh): do we need to support other field types as sensitive? if fieldType.String() != "string" && fieldType.String() != "*string" { return nil, nil, fmt.Errorf(`got type %q for field %q, only types "string" and "*string" supported as sensitive`, fieldType.String(), fieldNameCamel) @@ -178,7 +198,6 @@ func (g *Builder) buildResource(res *schema.Resource, cfg *config.Resource, tfPa if comment.TerrajetOptions.FieldJSONTag != nil { jsonTag = *comment.TerrajetOptions.FieldJSONTag } - // NOTE(muvaf): If a field is not optional but computed, then it's // definitely an observation field. // If it's optional but also computed, then it means the field has a server @@ -199,11 +218,20 @@ func (g *Builder) buildResource(res *schema.Resource, cfg *config.Resource, tfPa req := !sch.Optional comment.Required = &req paramFields = append(paramFields, field) + if _, ok := t[fPath]; !ok { + t[fPath] = Transformation{ + TransformedName: fieldName.LowerCamelComputed, + } + } } - if ref, ok := cfg.References[fieldPath(tfPaths)]; ok { - refFields, refTags := g.generateReferenceFields(paramName, field, ref) + if ref, ok := cfg.References[fPath]; ok { + refFields, refTags, names := g.generateReferenceFields(paramName, field, ref) paramTags = append(paramTags, refTags...) paramFields = append(paramFields, refFields...) + t[fPath] = Transformation{ + TransformedName: names[0].LowerCamelComputed, + IsRef: true, + } } g.comments.AddFieldComment(paramName, fieldNameCamel, comment.Build()) @@ -225,7 +253,7 @@ func (g *Builder) buildResource(res *schema.Resource, cfg *config.Resource, tfPa return paramType, obsType, nil } -func (g *Builder) buildSchema(sch *schema.Schema, cfg *config.Resource, tfPath []string, xpPath []string, names []string) (types.Type, error) { // nolint:gocyclo +func (g *Builder) buildSchema(sch *schema.Schema, cfg *config.Resource, tfPath []string, xpPath []string, t map[string]Transformation, names []string) (types.Type, error) { // nolint:gocyclo switch sch.Type { case schema.TypeBool: return types.NewPointer(types.Universe.Lookup("bool").Type()), nil @@ -255,14 +283,14 @@ func (g *Builder) buildSchema(sch *schema.Schema, cfg *config.Resource, tfPath [ return nil, errors.Errorf("element type of %s is basic but not one of known basic types", fieldPath(names)) } case *schema.Schema: - elemType, err = g.buildSchema(et, cfg, tfPath, xpPath, names) + elemType, err = g.buildSchema(et, cfg, tfPath, xpPath, t, names) if err != nil { return nil, errors.Wrapf(err, "cannot infer type from schema of element type of %s", fieldPath(names)) } case *schema.Resource: // TODO(muvaf): We skip the other type once we choose one of param // or obs types. This might cause some fields to be completely omitted. - paramType, obsType, err := g.buildResource(et, cfg, tfPath, xpPath, names...) + paramType, obsType, err := g.buildResource(et, cfg, tfPath, xpPath, t, names...) if err != nil { return nil, errors.Wrapf(err, "cannot infer type from resource schema of element type of %s", fieldPath(names)) } diff --git a/pkg/types/reference.go b/pkg/types/reference.go index 33f3cccf..e6be19ac 100644 --- a/pkg/types/reference.go +++ b/pkg/types/reference.go @@ -27,7 +27,7 @@ var typeSelectorField types.Type var typeSecretKeySelector types.Type var commentOptional *comments.Comment -func (g *Builder) generateReferenceFields(t *types.TypeName, f *types.Var, r config.Reference) (fields []*types.Var, tags []string) { +func (g *Builder) generateReferenceFields(t *types.TypeName, f *types.Var, r config.Reference) (fields []*types.Var, tags []string, names []name.Name) { _, isSlice := f.Type().(*types.Slice) rfn := r.RefFieldName @@ -59,7 +59,7 @@ func (g *Builder) generateReferenceFields(t *types.TypeName, f *types.Var, r con g.comments.AddFieldComment(t, rfn, commentOptional.Build()) g.comments.AddFieldComment(t, sfn, commentOptional.Build()) - return []*types.Var{ref, sel}, []string{refTag, selTag} + return []*types.Var{ref, sel}, []string{refTag, selTag}, []name.Name{rn, sn} } func init() { diff --git a/pkg/types/reference_test.go b/pkg/types/reference_test.go index 1bbeef66..b41a296f 100644 --- a/pkg/types/reference_test.go +++ b/pkg/types/reference_test.go @@ -124,7 +124,7 @@ func TestBuilder_generateReferenceFields(t *testing.T) { g := &Builder{ comments: twtypes.Comments{}, } - gotFields, gotTags := g.generateReferenceFields(tc.args.t, tc.args.f, tc.args.r) + gotFields, gotTags, _ := g.generateReferenceFields(tc.args.t, tc.args.f, tc.args.r) if diff := cmp.Diff(tc.want.outFields, gotFields, cmp.Comparer(func(a, b *types.Var) bool { return a.String() == b.String() })); diff != "" { From 0ad041887b062973f041c7d5cf16939000af4418 Mon Sep 17 00:00:00 2001 From: Alper Rifat Ulucinar Date: Thu, 6 Jan 2022 19:10:59 +0300 Subject: [PATCH 2/3] Comment out all lines in the generated example manifests Signed-off-by: Alper Rifat Ulucinar --- pkg/pipeline/example.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pkg/pipeline/example.go b/pkg/pipeline/example.go index dc4dbf26..52788b75 100644 --- a/pkg/pipeline/example.go +++ b/pkg/pipeline/example.go @@ -114,7 +114,7 @@ func (eg *ExampleGenerator) StoreExamples() error { b := bytes.Buffer{} b.WriteString("# This example manifest is auto-generated, and has not been tested.\n") b.WriteString("# Please make the necessary adjustments before using it.\n") - b.Write(buff) + b.Write(commentOut(buff)) // no sensitive info in the example manifest if err := ioutil.WriteFile(pm.manifestPath, b.Bytes(), 0644); err != nil { // nolint:gosec return errors.Wrapf(err, "cannot write example manifest file %s for resource %s", pm.manifestPath, n) @@ -123,6 +123,22 @@ func (eg *ExampleGenerator) StoreExamples() error { return nil } +func commentOut(buff []byte) []byte { + lines := strings.Split(string(buff), "\n") + commentedOutLines := make([]string, 0, len(lines)) + for _, l := range lines { + trimmed := strings.TrimSpace(l) + if len(trimmed) == 0 { + continue + } + if !strings.HasPrefix(trimmed, "#") { + l = "#" + l + } + commentedOutLines = append(commentedOutLines, l) + } + return []byte(strings.Join(commentedOutLines, "\n")) +} + func (eg *ExampleGenerator) resolveReferences(params map[string]interface{}) error { // nolint:gocyclo for k, v := range params { switch t := v.(type) { From 0f9468d385e58b1fb2a0772aece828842f878a43 Mon Sep 17 00:00:00 2001 From: Alper Rifat Ulucinar Date: Fri, 21 Jan 2022 04:33:57 +0300 Subject: [PATCH 3/3] Remove spec.forProvider.depends_on if exists Signed-off-by: Alper Rifat Ulucinar --- pkg/pipeline/example.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/pipeline/example.go b/pkg/pipeline/example.go index 52788b75..38b6792e 100644 --- a/pkg/pipeline/example.go +++ b/pkg/pipeline/example.go @@ -102,7 +102,9 @@ func (eg *ExampleGenerator) StoreExamples() error { if err := eg.resolveReferences(pm.paved.UnstructuredContent()); err != nil { return errors.Wrapf(err, "cannot resolve references for resource: %s", n) } - buff, err := yaml.Marshal(pm.paved.UnstructuredContent()) + u := pm.paved.UnstructuredContent() + delete(u["spec"].(map[string]interface{})["forProvider"].(map[string]interface{}), "depends_on") + buff, err := yaml.Marshal(u) if err != nil { return errors.Wrapf(err, "cannot marshal example manifest for resource: %s", n) }