Skip to content

Commit 9d4037b

Browse files
authored
add new generator and helpers to return values for boolean pointers (#637)
* add new generator and helpers to return values for boolean pointers * update type marker * updated type marker name in marker help * update to use getter methods based on PR review * update formatting per review
1 parent adf74d8 commit 9d4037b

File tree

11 files changed

+248
-3
lines changed

11 files changed

+248
-3
lines changed

build.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,8 @@ echo "Generating JsonSchemas"
6161

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

64+
echo "Generating Getter Implementations"
65+
66+
generator/build/generator "getters" "paths=./pkg/apis/workspaces/v1alpha2"
67+
6468
echo "Finished generation of required GO sources, K8S CRDs, and Json Schemas"

generator/getters/gen.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package getters
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"github.com/devfile/api/generator/genutils"
7+
"github.com/elliotchance/orderedmap"
8+
"go/ast"
9+
"sigs.k8s.io/controller-tools/pkg/genall"
10+
"sigs.k8s.io/controller-tools/pkg/loader"
11+
"sigs.k8s.io/controller-tools/pkg/markers"
12+
"strconv"
13+
)
14+
15+
//go:generate go run sigs.k8s.io/controller-tools/cmd/helpgen generate:headerFile=../header.go.txt,year=2021 paths=.
16+
17+
var (
18+
// GetterTypeMarker is associated with a type that's used as the pointer receiver of the getter method
19+
GetterTypeMarker = markers.Must(markers.MakeDefinition("devfile:getter:generate", markers.DescribesType, struct{}{}))
20+
// DefaultFieldMarker is associated with a boolean pointer field to indicate the default boolean value
21+
DefaultFieldMarker = markers.Must(markers.MakeDefinition("devfile:default:value", markers.DescribesField, ""))
22+
)
23+
24+
// +controllertools:marker:generateHelp
25+
26+
// Generator generates getter methods that are used to return values for the boolean pointer fields.
27+
//
28+
// The pointer receiver is determined from the `devfile:getter:generate` annotated type. The method will return the value of the
29+
// field if it's been set, otherwise it will return the default value specified by the devfile:default:value annotation.
30+
type Generator struct{}
31+
32+
// RegisterMarkers registers the markers of the Generator
33+
func (Generator) RegisterMarkers(into *markers.Registry) error {
34+
if err := markers.RegisterAll(into, GetterTypeMarker, DefaultFieldMarker); err != nil {
35+
return err
36+
}
37+
into.AddHelp(GetterTypeMarker,
38+
markers.SimpleHelp("Devfile", "indicates the type that's used as the pointer receiver of the getter method"))
39+
into.AddHelp(DefaultFieldMarker,
40+
markers.SimpleHelp("Devfile", "indicates the default value of a boolean pointer field"))
41+
return genutils.RegisterUnionMarkers(into)
42+
43+
}
44+
45+
func (Generator) CheckFilter() loader.NodeFilter {
46+
return func(node ast.Node) bool {
47+
// ignore interfaces
48+
_, isIface := node.(*ast.InterfaceType)
49+
return !isIface
50+
}
51+
}
52+
53+
// getterInfo stores the info to generate the getter method
54+
type getterInfo struct {
55+
funcName string
56+
defaultVal string
57+
}
58+
59+
// Generate generates the artifacts
60+
func (g Generator) Generate(ctx *genall.GenerationContext) error {
61+
for _, root := range ctx.Roots {
62+
ctx.Checker.Check(root)
63+
root.NeedTypesInfo()
64+
65+
typesToProcess := orderedmap.NewOrderedMap()
66+
if err := markers.EachType(ctx.Collector, root, func(info *markers.TypeInfo) {
67+
if info.Markers.Get(GetterTypeMarker.Name) != nil {
68+
var getters []getterInfo
69+
for _, field := range info.Fields {
70+
defaultVal := field.Markers.Get(DefaultFieldMarker.Name)
71+
if defaultVal != nil {
72+
if _, err := strconv.ParseBool(defaultVal.(string)); err != nil {
73+
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)))
74+
}
75+
76+
//look for boolean pointers
77+
if ptr, isPtr := field.RawField.Type.(*ast.StarExpr); isPtr {
78+
if ident, ok := ptr.X.(*ast.Ident); ok {
79+
if ident.Name == "bool" {
80+
getters = append(getters, getterInfo{
81+
field.Name,
82+
defaultVal.(string),
83+
})
84+
} else {
85+
root.AddError(fmt.Errorf("devfile:default:value marker is specified on %s/%s which is not a boolean pointer", info.Name, field.Name))
86+
}
87+
}
88+
} else {
89+
root.AddError(fmt.Errorf("devfile:default:value marker is specified on %s/%s which is not a boolean pointer", info.Name, field.Name))
90+
}
91+
92+
}
93+
}
94+
if len(getters) > 0 {
95+
typesToProcess.Set(info, getters)
96+
} else {
97+
root.AddError(fmt.Errorf("type %s does not have the field marker, devfile:default:value specified on a boolean pointer field", info.Name))
98+
}
99+
return
100+
}
101+
102+
}); err != nil {
103+
root.AddError(err)
104+
return nil
105+
}
106+
107+
genutils.WriteFormattedSourceFile("getters", ctx, root, func(buf *bytes.Buffer) {
108+
for elt := typesToProcess.Front(); elt != nil; elt = elt.Next() {
109+
cmd := elt.Key.(*markers.TypeInfo)
110+
fields := elt.Value.([]getterInfo)
111+
for _, getter := range fields {
112+
fName := getter.funcName
113+
defaultVal := getter.defaultVal
114+
getterMethod := fmt.Sprintf(`
115+
// Get%[1]s returns the value of the boolean property. If unset, it's the default value specified in the devfile:default:value marker
116+
func (in *%[2]s) Get%[1]s() bool {
117+
return getBoolOrDefault(in.%[1]s, %[3]s)}`, fName, cmd.Name, defaultVal)
118+
buf.WriteString(getterMethod)
119+
}
120+
}
121+
122+
internalHelper := `
123+
124+
func getBoolOrDefault(input *bool, defaultVal bool) bool {
125+
if input != nil {
126+
return *input
127+
}
128+
return defaultVal }`
129+
buf.WriteString(internalHelper)
130+
})
131+
}
132+
133+
return nil
134+
}

generator/getters/zz_generated.markerhelp.go

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

generator/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"encoding/json"
55
"fmt"
6+
"github.com/devfile/api/generator/getters"
67
"io"
78
"os"
89
"strings"
@@ -34,6 +35,7 @@ var (
3435
"deepcopy": deepcopy.Generator{},
3536
"schemas": schemas.Generator{},
3637
"validate": validate.Generator{},
38+
"getters": getters.Generator{},
3739
}
3840

3941
// allOutputRules defines the list of all known output rules, giving
@@ -122,6 +124,9 @@ generator overrides:isForPluginOverrides=false paths=./pkg/apis/workspaces/v1alp
122124
# Generate Interface Implementations based on the workspaces/v1alpha2 K8S API
123125
generator interfaces paths=./pkg/apis/workspaces/v1alpha2
124126
127+
# Generate Boolean Getter implementations based on the workspaces/v1alpha2 K8S API
128+
generator getters paths=./pkg/apis/workspaces/v1alpha2
129+
125130
# Generate K8S CRDs based on the workspaces/v1alpha2 K8S API
126131
generator crds output:crds:artifacts:config=crds paths=./pkg/apis/workspaces/v1alpha2
127132

generator/overrides/gen.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,12 @@ func (g Generator) createOverride(newTypeToProcess typeToProcess, packageTypes m
227227
)
228228
}
229229

230+
overrideGenDecl.Doc = updateComments(
231+
overrideGenDecl, overrideGenDecl.Doc,
232+
`.*`,
233+
` *`+regexp.QuoteMeta("+devfile:getter:generate")+`.*`,
234+
)
235+
230236
overrideGenDecl.Doc = updateComments(
231237
overrideGenDecl, overrideGenDecl.Doc,
232238
`.*`,
@@ -318,6 +324,13 @@ func (g Generator) createOverride(newTypeToProcess typeToProcess, packageTypes m
318324
` *`+regexp.QuoteMeta("+kubebuilder:default")+` *=.*`,
319325
)
320326

327+
//remove the +devfile:default:values for overrides
328+
astField.Doc = updateComments(
329+
astField, astField.Doc,
330+
`.*`,
331+
` *`+regexp.QuoteMeta("+devfile:default:value")+` *=.*`,
332+
)
333+
321334
processFieldType := func(ident *ast.Ident) *typeToProcess {
322335
typeToOverride, existsInPackage := packageTypes[ident.Name]
323336
if !existsInPackage {

pkg/apis/workspaces/v1alpha2/commands.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ const (
2929
DeployCommandGroupKind CommandGroupKind = "deploy"
3030
)
3131

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

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

@@ -107,6 +109,7 @@ type CommandUnion struct {
107109
Custom *CustomCommand `json:"custom,omitempty"`
108110
}
109111

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

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

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

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

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

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

pkg/apis/workspaces/v1alpha2/component_container.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ type ContainerComponent struct {
77
Endpoints []Endpoint `json:"endpoints,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
88
}
99

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

@@ -69,9 +70,22 @@ type Container struct {
6970
//
7071
// Default value is `false`
7172
// +optional
73+
// +devfile:default:value=false
7274
DedicatedPod *bool `json:"dedicatedPod,omitempty"`
7375
}
7476

77+
//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.
78+
func (in *Container) GetMountSources() bool {
79+
if in.MountSources != nil {
80+
return *in.MountSources
81+
} else {
82+
if in.GetDedicatedPod() {
83+
return false
84+
}
85+
return true
86+
}
87+
}
88+
7589
type EnvVar struct {
7690
Name string `json:"name" yaml:"name"`
7791
Value string `json:"value" yaml:"value"`

pkg/apis/workspaces/v1alpha2/component_image_dockerfile.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type DockerfileSrc struct {
4141
Git *DockerfileGitProjectSource `json:"git,omitempty"`
4242
}
4343

44+
// +devfile:getter:generate
4445
type Dockerfile struct {
4546
// Path of source directory to establish build context. Defaults to ${PROJECT_ROOT} in the container
4647
// +optional
@@ -54,6 +55,7 @@ type Dockerfile struct {
5455
//
5556
// Default value is `false`
5657
// +optional
58+
// +devfile:default:value=false
5759
RootRequired *bool `json:"rootRequired,omitempty"`
5860
}
5961

pkg/apis/workspaces/v1alpha2/component_volume.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ type VolumeComponent struct {
77
}
88

99
// Volume that should be mounted to a component container
10+
// +devfile:getter:generate
1011
type Volume struct {
1112
// +optional
1213
// Size of the volume
@@ -15,5 +16,6 @@ type Volume struct {
1516
// +optional
1617
// Ephemeral volumes are not stored persistently across restarts. Defaults
1718
// to false
19+
// +devfile:default:value=false
1820
Ephemeral *bool `json:"ephemeral,omitempty"`
1921
}

pkg/apis/workspaces/v1alpha2/endpoint.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package v1alpha2
22

3-
import (
4-
attributes "github.com/devfile/api/v2/pkg/attributes"
5-
)
3+
import "github.com/devfile/api/v2/pkg/attributes"
64

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

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

9999
// Path of the endpoint URL

0 commit comments

Comments
 (0)