Skip to content

add new generator and helpers to return values for boolean pointers #637

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

Merged
merged 5 commits into from
Oct 14, 2021
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,8 @@ echo "Generating JsonSchemas"

generator/build/generator "schemas" "output:schemas:artifacts:config=schemas" "paths=./pkg/apis/workspaces/v1alpha2"

echo "Generating Getter Implementations"

generator/build/generator "getters" "paths=./pkg/apis/workspaces/v1alpha2"

echo "Finished generation of required GO sources, K8S CRDs, and Json Schemas"
134 changes: 134 additions & 0 deletions generator/getters/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package getters

import (
"bytes"
"fmt"
"github.com/devfile/api/generator/genutils"
"github.com/elliotchance/orderedmap"
"go/ast"
"sigs.k8s.io/controller-tools/pkg/genall"
"sigs.k8s.io/controller-tools/pkg/loader"
"sigs.k8s.io/controller-tools/pkg/markers"
"strconv"
)

//go:generate go run sigs.k8s.io/controller-tools/cmd/helpgen generate:headerFile=../header.go.txt,year=2021 paths=.

var (
// GetterTypeMarker is associated with a type that's used as the pointer receiver of the getter method
GetterTypeMarker = markers.Must(markers.MakeDefinition("devfile:getter:generate", markers.DescribesType, struct{}{}))
// DefaultFieldMarker is associated with a boolean pointer field to indicate the default boolean value
DefaultFieldMarker = markers.Must(markers.MakeDefinition("devfile:default:value", markers.DescribesField, ""))
)

// +controllertools:marker:generateHelp

// Generator generates getter methods that are used to return values for the boolean pointer fields.
//
// The pointer receiver is determined from the `devfile:getter:generate` annotated type. The method will return the value of the
// field if it's been set, otherwise it will return the default value specified by the devfile:default:value annotation.
type Generator struct{}

// RegisterMarkers registers the markers of the Generator
func (Generator) RegisterMarkers(into *markers.Registry) error {
if err := markers.RegisterAll(into, GetterTypeMarker, DefaultFieldMarker); err != nil {
return err
}
into.AddHelp(GetterTypeMarker,
markers.SimpleHelp("Devfile", "indicates the type that's used as the pointer receiver of the getter method"))
into.AddHelp(DefaultFieldMarker,
markers.SimpleHelp("Devfile", "indicates the default value of a boolean pointer field"))
return genutils.RegisterUnionMarkers(into)

}

func (Generator) CheckFilter() loader.NodeFilter {
return func(node ast.Node) bool {
// ignore interfaces
_, isIface := node.(*ast.InterfaceType)
return !isIface
}
}

// getterInfo stores the info to generate the getter method
type getterInfo struct {
funcName string
defaultVal string
}

// Generate generates the artifacts
func (g Generator) Generate(ctx *genall.GenerationContext) error {
for _, root := range ctx.Roots {
ctx.Checker.Check(root)
root.NeedTypesInfo()

typesToProcess := orderedmap.NewOrderedMap()
if err := markers.EachType(ctx.Collector, root, func(info *markers.TypeInfo) {
if info.Markers.Get(GetterTypeMarker.Name) != nil {
var getters []getterInfo
for _, field := range info.Fields {
defaultVal := field.Markers.Get(DefaultFieldMarker.Name)
if defaultVal != nil {
if _, err := strconv.ParseBool(defaultVal.(string)); err != nil {
root.AddError(fmt.Errorf("devfile:default:value marker specified on %s/%s does not have a true or false value. Value is %s", info.Name, field.Name, defaultVal.(string)))
}

//look for boolean pointers
if ptr, isPtr := field.RawField.Type.(*ast.StarExpr); isPtr {
if ident, ok := ptr.X.(*ast.Ident); ok {
if ident.Name == "bool" {
getters = append(getters, getterInfo{
field.Name,
defaultVal.(string),
})
} else {
root.AddError(fmt.Errorf("devfile:default:value marker is specified on %s/%s which is not a boolean pointer", info.Name, field.Name))
}
}
} else {
root.AddError(fmt.Errorf("devfile:default:value marker is specified on %s/%s which is not a boolean pointer", info.Name, field.Name))
}

}
}
if len(getters) > 0 {
typesToProcess.Set(info, getters)
} else {
root.AddError(fmt.Errorf("type %s does not have the field marker, devfile:default:value specified on a boolean pointer field", info.Name))
}
return
}

}); err != nil {
root.AddError(err)
return nil
}

genutils.WriteFormattedSourceFile("getters", ctx, root, func(buf *bytes.Buffer) {
for elt := typesToProcess.Front(); elt != nil; elt = elt.Next() {
cmd := elt.Key.(*markers.TypeInfo)
fields := elt.Value.([]getterInfo)
for _, getter := range fields {
fName := getter.funcName
defaultVal := getter.defaultVal
getterMethod := fmt.Sprintf(`
// Get%[1]s returns the value of the boolean property. If unset, it's the default value specified in the devfile:default:value marker
func (in *%[2]s) Get%[1]s() bool {
return getBoolOrDefault(in.%[1]s, %[3]s)}`, fName, cmd.Name, defaultVal)
buf.WriteString(getterMethod)
}
}

internalHelper := `

func getBoolOrDefault(input *bool, defaultVal bool) bool {
if input != nil {
return *input
}
return defaultVal }`
buf.WriteString(internalHelper)
})
}

return nil
}
22 changes: 22 additions & 0 deletions generator/getters/zz_generated.markerhelp.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions generator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"encoding/json"
"fmt"
"github.com/devfile/api/generator/getters"
"io"
"os"
"strings"
Expand Down Expand Up @@ -34,6 +35,7 @@ var (
"deepcopy": deepcopy.Generator{},
"schemas": schemas.Generator{},
"validate": validate.Generator{},
"getters": getters.Generator{},
}

// allOutputRules defines the list of all known output rules, giving
Expand Down Expand Up @@ -122,6 +124,9 @@ generator overrides:isForPluginOverrides=false paths=./pkg/apis/workspaces/v1alp
# Generate Interface Implementations based on the workspaces/v1alpha2 K8S API
generator interfaces paths=./pkg/apis/workspaces/v1alpha2

# Generate Boolean Getter implementations based on the workspaces/v1alpha2 K8S API
generator getters paths=./pkg/apis/workspaces/v1alpha2

# Generate K8S CRDs based on the workspaces/v1alpha2 K8S API
generator crds output:crds:artifacts:config=crds paths=./pkg/apis/workspaces/v1alpha2

Expand Down
13 changes: 13 additions & 0 deletions generator/overrides/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,12 @@ func (g Generator) createOverride(newTypeToProcess typeToProcess, packageTypes m
)
}

overrideGenDecl.Doc = updateComments(
overrideGenDecl, overrideGenDecl.Doc,
`.*`,
` *`+regexp.QuoteMeta("+devfile:getter:generate")+`.*`,
)

overrideGenDecl.Doc = updateComments(
overrideGenDecl, overrideGenDecl.Doc,
`.*`,
Expand Down Expand Up @@ -318,6 +324,13 @@ func (g Generator) createOverride(newTypeToProcess typeToProcess, packageTypes m
` *`+regexp.QuoteMeta("+kubebuilder:default")+` *=.*`,
)

//remove the +devfile:default:values for overrides
astField.Doc = updateComments(
astField, astField.Doc,
`.*`,
` *`+regexp.QuoteMeta("+devfile:default:value")+` *=.*`,
)

processFieldType := func(ident *ast.Ident) *typeToProcess {
typeToOverride, existsInPackage := packageTypes[ident.Name]
if !existsInPackage {
Expand Down
6 changes: 6 additions & 0 deletions pkg/apis/workspaces/v1alpha2/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ const (
DeployCommandGroupKind CommandGroupKind = "deploy"
)

// +devfile:getter:generate
type CommandGroup struct {
// Kind of group the command is part of
Kind CommandGroupKind `json:"kind"`

// +optional
// Identifies the default command for a given group kind
// +devfile:default:value=false
IsDefault *bool `json:"isDefault,omitempty"`
}

Expand Down Expand Up @@ -107,6 +109,7 @@ type CommandUnion struct {
Custom *CustomCommand `json:"custom,omitempty"`
}

// +devfile:getter:generate
type ExecCommand struct {
LabeledCommand `json:",inline"`

Expand Down Expand Up @@ -145,6 +148,7 @@ type ExecCommand struct {
// If set to `true` the command won't be restarted and it is expected to handle file changes on its own.
//
// Default value is `false`
// +devfile:default:value=false
HotReloadCapable *bool `json:"hotReloadCapable,omitempty"`
}

Expand All @@ -156,6 +160,7 @@ type ApplyCommand struct {
Component string `json:"component"`
}

// +devfile:getter:generate
type CompositeCommand struct {
LabeledCommand `json:",inline"`

Expand All @@ -164,6 +169,7 @@ type CompositeCommand struct {

// Indicates if the sub-commands should be executed concurrently
// +optional
// +devfile:default:value=false
Parallel *bool `json:"parallel,omitempty"`
}

Expand Down
14 changes: 14 additions & 0 deletions pkg/apis/workspaces/v1alpha2/component_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type ContainerComponent struct {
Endpoints []Endpoint `json:"endpoints,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
}

// +devfile:getter:generate
type Container struct {
Image string `json:"image"`

Expand Down Expand Up @@ -69,9 +70,22 @@ type Container struct {
//
// Default value is `false`
// +optional
// +devfile:default:value=false
DedicatedPod *bool `json:"dedicatedPod,omitempty"`
}

//GetMountSources returns the value of the boolean property. If it's unset, the default value is true for all component types except plugins and components that set `dedicatedPod` to true.
func (in *Container) GetMountSources() bool {
if in.MountSources != nil {
return *in.MountSources
} else {
if in.GetDedicatedPod() {
return false
}
return true
}
}

type EnvVar struct {
Name string `json:"name" yaml:"name"`
Value string `json:"value" yaml:"value"`
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/workspaces/v1alpha2/component_image_dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type DockerfileSrc struct {
Git *DockerfileGitProjectSource `json:"git,omitempty"`
}

// +devfile:getter:generate
type Dockerfile struct {
// Path of source directory to establish build context. Defaults to ${PROJECT_ROOT} in the container
// +optional
Expand All @@ -54,6 +55,7 @@ type Dockerfile struct {
//
// Default value is `false`
// +optional
// +devfile:default:value=false
RootRequired *bool `json:"rootRequired,omitempty"`
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/workspaces/v1alpha2/component_volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type VolumeComponent struct {
}

// Volume that should be mounted to a component container
// +devfile:getter:generate
type Volume struct {
// +optional
// Size of the volume
Expand All @@ -15,5 +16,6 @@ type Volume struct {
// +optional
// Ephemeral volumes are not stored persistently across restarts. Defaults
// to false
// +devfile:default:value=false
Ephemeral *bool `json:"ephemeral,omitempty"`
}
6 changes: 3 additions & 3 deletions pkg/apis/workspaces/v1alpha2/endpoint.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package v1alpha2

import (
attributes "github.com/devfile/api/v2/pkg/attributes"
)
import "github.com/devfile/api/v2/pkg/attributes"

// EndpointProtocol defines the application and transport protocols of the traffic that will go through this endpoint.
// Only one of the following protocols may be specified: http, ws, tcp, udp.
Expand Down Expand Up @@ -46,6 +44,7 @@ const (
NoneEndpointExposure EndpointExposure = "none"
)

// +devfile:getter:generate
type Endpoint struct {
// +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
// +kubebuilder:validation:MaxLength=63
Expand Down Expand Up @@ -94,6 +93,7 @@ type Endpoint struct {
// Describes whether the endpoint should be secured and protected by some
// authentication process. This requires a protocol of `https` or `wss`.
// +optional
// +devfile:default:value=false
Secure *bool `json:"secure,omitempty"`

// Path of the endpoint URL
Expand Down
43 changes: 43 additions & 0 deletions pkg/apis/workspaces/v1alpha2/zz_generated.getters.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.