Skip to content

Commit

Permalink
CRD generation: remove status before writing
Browse files Browse the repository at this point in the history
It's tricky to get the existing CRD types to not include the status,
so we instead allow for filtering of the map just before writing.

Issue #456
  • Loading branch information
justinsb committed Feb 25, 2022
1 parent 99e7fbc commit 0fba82b
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 12 deletions.
9 changes: 8 additions & 1 deletion pkg/crd/gen.go
Expand Up @@ -85,6 +85,13 @@ func (Generator) CheckFilter() loader.NodeFilter {
func (Generator) RegisterMarkers(into *markers.Registry) error {
return crdmarkers.Register(into)
}

// transformRemoveCRDStatus ensures we do not write the CRD status field.
func transformRemoveCRDStatus(obj map[string]interface{}) error {
delete(obj, "status")
return nil
}

func (g Generator) Generate(ctx *genall.GenerationContext) error {
parser := &Parser{
Collector: ctx.Collector,
Expand Down Expand Up @@ -145,7 +152,7 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
} else {
fileName = fmt.Sprintf("%s_%s.%s.yaml", crdRaw.Spec.Group, crdRaw.Spec.Names.Plural, crdVersions[i])
}
if err := ctx.WriteYAML(fileName, crd); err != nil {
if err := ctx.WriteYAML(fileName, []interface{}{crd}, genall.WithTransform(transformRemoveCRDStatus)); err != nil {
return err
}
}
Expand Down
6 changes: 0 additions & 6 deletions pkg/crd/spec.go
Expand Up @@ -164,11 +164,5 @@ func (p *Parser) NeedCRDFor(groupKind schema.GroupKind, maxDescLen *int) {
packages[0].AddError(fmt.Errorf("CRD for %s with version(s) %v does not serve any version", groupKind, crd.Spec.Versions))
}

// NB(directxman12): CRD's status doesn't have omitempty markers, which means things
// get serialized as null, which causes the validator to freak out. Manually set
// these to empty till we get a better solution.
crd.Status.Conditions = []apiext.CustomResourceDefinitionCondition{}
crd.Status.StoredVersions = []string{}

p.CustomResourceDefinitions[groupKind] = crd
}
54 changes: 51 additions & 3 deletions pkg/genall/genall.go
Expand Up @@ -17,13 +17,14 @@ limitations under the License.
package genall

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"

"golang.org/x/tools/go/packages"
"sigs.k8s.io/yaml"
rawyaml "gopkg.in/yaml.v2"

"sigs.k8s.io/controller-tools/pkg/loader"
"sigs.k8s.io/controller-tools/pkg/markers"
Expand Down Expand Up @@ -120,18 +121,30 @@ type GenerationContext struct {
InputRule
}

// WriteYAMLOptions implements the Options Pattern for WriteYAML.
type WriteYAMLOptions struct {
transform func(obj map[string]interface{}) error
}

// WithTransform applies a transformation to objects just before writing them.
func WithTransform(transform func(obj map[string]interface{}) error) *WriteYAMLOptions {
return &WriteYAMLOptions{
transform: transform,
}
}

// WriteYAML writes the given objects out, serialized as YAML, using the
// context's OutputRule. Objects are written as separate documents, separated
// from each other by `---` (as per the YAML spec).
func (g GenerationContext) WriteYAML(itemPath string, objs ...interface{}) error {
func (g GenerationContext) WriteYAML(itemPath string, objs []interface{}, options ...*WriteYAMLOptions) error {
out, err := g.Open(nil, itemPath)
if err != nil {
return err
}
defer out.Close()

for _, obj := range objs {
yamlContent, err := yaml.Marshal(obj)
yamlContent, err := yamlMarshal(obj, options...)
if err != nil {
return err
}
Expand All @@ -147,6 +160,41 @@ func (g GenerationContext) WriteYAML(itemPath string, objs ...interface{}) error
return nil
}

// yamlMarshal is based on sigs.k8s.io/yaml.Marshal, but allows for transforming the final data before writing.
func yamlMarshal(o interface{}, options ...*WriteYAMLOptions) ([]byte, error) {
j, err := json.Marshal(o)
if err != nil {
return nil, fmt.Errorf("error marshaling into JSON: %v", err)
}

return yamlJSONToYAMLWithFilter(j, options...)
}

// yamlJSONToYAMLWithFilter is based on sigs.k8s.io/yaml.JSONToYAML, but allows for transforming the final data before writing.
func yamlJSONToYAMLWithFilter(j []byte, options ...*WriteYAMLOptions) ([]byte, error) {
// Convert the JSON to an object.
var jsonObj map[string]interface{}
// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
// Go JSON library doesn't try to pick the right number type (int, float,
// etc.) when unmarshalling to interface{}, it just picks float64
// universally. go-yaml does go through the effort of picking the right
// number type, so we can preserve number type throughout this process.
if err := rawyaml.Unmarshal(j, &jsonObj); err != nil {
return nil, err
}

for _, option := range options {
if option.transform != nil {
if err := option.transform(jsonObj); err != nil {
return nil, err
}
}
}

// Marshal this object into YAML.
return rawyaml.Marshal(jsonObj)
}

// ReadFile reads the given boilerplate artifact using the context's InputRule.
func (g GenerationContext) ReadFile(path string) ([]byte, error) {
file, err := g.OpenForRead(path)
Expand Down
2 changes: 1 addition & 1 deletion pkg/rbac/parser.go
Expand Up @@ -263,5 +263,5 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
return nil
}

return ctx.WriteYAML("role.yaml", objs...)
return ctx.WriteYAML("role.yaml", objs)
}
2 changes: 1 addition & 1 deletion pkg/webhook/parser.go
Expand Up @@ -389,7 +389,7 @@ func (Generator) Generate(ctx *genall.GenerationContext) error {
} else {
fileName = fmt.Sprintf("manifests.%s.yaml", k)
}
if err := ctx.WriteYAML(fileName, v...); err != nil {
if err := ctx.WriteYAML(fileName, v); err != nil {
return err
}
}
Expand Down

0 comments on commit 0fba82b

Please sign in to comment.