/
main.go
160 lines (137 loc) · 4.96 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package main
import (
"context"
"fmt"
"os"
"strings"
"github.com/GoogleContainerTools/kpt-functions-catalog/functions/go/set-gcp-resource-ids/pkg/kpt"
"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var (
Folder = schema.GroupVersionKind{Group: "resourcemanager.cnrm.cloud.google.com", Version: "v1beta1", Kind: "Folder"}
GCPProject = schema.GroupVersionKind{Group: "resourcemanager.cnrm.cloud.google.com", Version: "v1beta1", Kind: "Project"}
ConfigControllerContext = schema.GroupVersionKind{Group: "core.cnrm.cloud.google.com", Version: "v1beta1", Kind: "ConfigConnectorContext"}
)
func main() {
// TODO: fn.AsMain should support an "easy mode" where it runs against a directory
processor := fn.WithContext(context.Background(), &SetGCPProject{})
if err := fn.AsMain(processor); err != nil {
os.Exit(1)
}
}
var _ fn.Runner = &SetGCPProject{}
type SetGCPProject struct {
ProjectID string `json:"projectID,omitempty"`
Ctx fn.Context
}
func (p *SetGCPProject) GenerateProjectID(objects fn.KubeObjects) (string, error) {
packageContext, err := kpt.FindPackageContext(objects)
if err != nil {
return "", err
}
projects := objects.Where(fn.IsGroupVersionKind(GCPProject))
if len(projects) == 0 {
return "", fmt.Errorf("did not find any Project objects in package, cannot generate project id")
}
if len(projects) != 1 {
// TODO: We could probably support this...
return "", fmt.Errorf("found multiple Project objects in package, cannot generate project id")
}
project := projects[0]
projectID, err := GenerateProjectID(project.GetName(), packageContext.ParentPath)
if err != nil {
return "", err
}
return projectID, nil
}
func (p *SetGCPProject) Run(ctx *fn.Context, _ *fn.KubeObject, objects fn.KubeObjects, results *fn.Results) bool {
projectID := p.ProjectID
if projectID == "" {
// TODO: Only if we need a project id (though there aren't many cases where we don't)
var err error
if projectID, err = p.GenerateProjectID(objects); err != nil {
results.ErrorE(err)
return false
}
}
packageContext, err := kpt.FindPackageContext(objects)
if err != nil {
results.ErrorE(err)
return false
}
for _, object := range objects {
if object.IsLocalConfig() {
continue
}
if kpt.IsResourceGroup(object) {
continue // Should ResourceGroup be marked as local config?
}
name := object.GetName()
if object.IsGroupVersionKind(Folder) {
displayName := name
if err = object.SetNestedString(displayName, "spec", "displayName"); err != nil {
results.ErrorE(err)
return false
}
// resourceID should be left unset to create a new resource
}
if object.IsGroupVersionKind(GCPProject) {
// https://cloud.google.com/resource-manager/docs/creating-managing-projects
// A project name can contain only letters, numbers, single quotes, hyphens, spaces,
// or exclamation points, and must be between 4 and 30 characters.
displayName := name
if packageContext.ParentPath != "" {
// TODO: Move to helper
parentPathTokens := strings.Split(packageContext.ParentPath, "/")
parentPathTokens = reverse(parentPathTokens)
displayName += "-" + strings.Join(parentPathTokens, "-")
}
displayName = strings.ReplaceAll(displayName, ".", "-")
if len(displayName) > 30 {
displayName = displayName[:30]
}
// name is the display name
if err = object.SetNestedString(displayName, "spec", "name"); err != nil {
results.ErrorE(err)
return false
}
// resourceID is the project ID (must be unique)
if err = object.SetNestedString(projectID, "spec", "resourceID"); err != nil {
results.ErrorE(err)
return false
}
}
if object.GetAnnotation("cnrm.cloud.google.com/project-id") != "" {
if err = object.SetAnnotation( "cnrm.cloud.google.com/project-id", projectID); err != nil {
results.ErrorE(err)
return false
}
}
if object.IsGroupVersionKind(ConfigControllerContext) {
// TODO: ConfigConnectorContext should accept a serviceAccountRef
googleServiceAccount, _, _ := object.NestedString("spec", "googleServiceAccount")
if googleServiceAccount != "" {
tokens := strings.Split(googleServiceAccount, "@")
if len(tokens) != 2 {
results.Errorf("error parsing spec.googleServiceAccount=%q", googleServiceAccount)
return false
}
if strings.HasSuffix(tokens[1], ".iam.gserviceaccount.com") {
tokens[1] = projectID + ".iam.gserviceaccount.com"
} else {
results.Errorf("unexpected value for spec.googleServiceAccount=%q (expected .iam.gserviceaccount.com suffix)", googleServiceAccount)
return false
}
googleServiceAccount = strings.Join(tokens, "@")
if err = object.SetNestedString(googleServiceAccount, "spec", "googleServiceAccount"); err!= nil {
results.ErrorE(err)
return false
}
}
}
// ContainerNodePool has something sort of similar ... the resourceID should be the name without the prefix
// This is better enforced via a "should" rule, I think
}
return true
}