-
Notifications
You must be signed in to change notification settings - Fork 1
/
builder.go
204 lines (171 loc) · 6.35 KB
/
builder.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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/*
Copyright 2021.
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 reconcile
import (
"context"
"os"
"strings"
ot "github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/v3/secrets/b64"
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
snbackend "github.com/streamnative/pulumi-controller-runtime/pkg/backend"
snplugin "github.com/streamnative/pulumi-controller-runtime/pkg/plugin"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/controller-runtime/pkg/manager"
)
type reconcilerBuilder struct {
mgr manager.Manager
forInput ForInput
programInput ProgramInput
optsInput OptsInput
}
// ForInput represents the information set by For method.
type ForInput struct {
object client.Object
err error
}
type ProgramInput struct {
program pulumi.RunFunc
runtimeOptions map[string]interface{}
}
// ProgramOption is some configuration that modifies options for a program.
type ProgramOption func(*ProgramInput)
type OptsInput struct {
finalizerName string
}
type Option func(input *OptsInput)
// NewReconcilerManagedBy builds a Pulumi reconciler based on the given manager.
func NewReconcilerManagedBy(mgr manager.Manager) *reconcilerBuilder {
return &reconcilerBuilder{mgr: mgr}
}
// For sets the object type.
func (b *reconcilerBuilder) For(object client.Object) *reconcilerBuilder {
b.forInput = ForInput{
object: object,
}
return b
}
// WithProgram sets the Pulumi program information.
func (b *reconcilerBuilder) WithProgram(program pulumi.RunFunc, opts ...ProgramOption) *reconcilerBuilder {
b.programInput = ProgramInput{program: program}
for _, opt := range opts {
opt(&b.programInput)
}
return b
}
// RuntimeOptions the runtime options for the program.
func RuntimeOptions(runtimeOptions map[string]interface{}) ProgramOption {
return func(i *ProgramInput) {
i.runtimeOptions = runtimeOptions
}
}
// WithOptions sets general reconciler options.
func (b *reconcilerBuilder) WithOptions(opts ...Option) *reconcilerBuilder {
for _, opt := range opts {
opt(&b.optsInput)
}
return b
}
// FinalizerName the finalizer name to use.
func FinalizerName(name string) Option {
return func(i *OptsInput) {
i.finalizerName = name
}
}
func (b *reconcilerBuilder) Build() (PulumiReconciler, error) {
r := PulumiReconciler{
Client: b.mgr.GetClient(),
FinalizerName: b.optsInput.finalizerName,
}
proj, err := b.createProject()
if err != nil {
return PulumiReconciler{}, err
}
r.proj = proj
backend, err := b.createBackend(proj)
if err != nil {
return PulumiReconciler{}, err
}
r.backend = backend
r.createPluginContext = func(ctx context.Context) (*plugin.Context, error) {
return b.createPluginHost(ctx, proj, b.programInput.program)
}
return r, nil
}
func makeProjectName(gvk schema.GroupVersionKind) tokens.PackageName {
return tokens.PackageName(strings.ToLower(gvk.Kind) + "." + strings.ToLower(gvk.Group))
}
func (b *reconcilerBuilder) createProject() (*workspace.Project, error) {
// Retrieve the GVK from the object we're reconciling
// to generate an appropriate project name.
gvk, err := apiutil.GVKForObject(b.forInput.object, b.mgr.GetScheme())
if err != nil {
return nil, err
}
return &workspace.Project{
Name: tokens.PackageName(makeProjectName(gvk)),
Runtime: workspace.NewProjectRuntimeInfo(RuntimeEmbedded, b.programInput.runtimeOptions),
}, nil
}
func (b *reconcilerBuilder) createBackend(proj *workspace.Project) (snbackend.Backend, error) {
secretsManager := b64.NewBase64SecretsManager()
backend := snbackend.NewDefaultBackend(
snbackend.NewSecretSnapshotter(b.mgr.GetClient(), secretsManager),
secretsManager,
proj)
return backend, nil
}
type CreatePluginContextFunc func(ctx context.Context) (*plugin.Context, error)
// createPluginHost creates a plugin host for engine operations.
// ctx is a top-level context for the plugin interactions.
func (b *reconcilerBuilder) createPluginHost(ctx context.Context, proj *workspace.Project, program pulumi.RunFunc) (*plugin.Context, error) {
// Start a tracing span for the lifespan of the plugin context.
// This span is the parent for calls to plugins and for callbacks from plugin to host.
// The span will finish when the plugin context is closed.
tracingSpan, _ := ot.StartSpanFromContext(ctx, "pulumi-plugin")
// Create an embedded Pulumi language runtime to execute the given Pulumi Go SDK program function.
// The supplied context becomes the parent for program executions.
runtime := snplugin.NewEmbeddedLanguageRuntime(ctx, program)
// Create a plugin host as required by the Pulumi engine to obtain resource providers, language runtimes, analyzers, etc.
sink := diag.DefaultSink(os.Stdout, os.Stderr, diag.FormatOptions{
Color: colors.Never,
})
host, err := snplugin.NewHost(sink, sink, tracingSpan,
snplugin.SimpleLanguageRuntimeLoader{RuntimeEmbedded: runtime},
snplugin.NewPulumiProviderLoader(proj.Runtime.Options()))
if err != nil {
return nil, errors.Wrapf(err, "creating Pulumi plugin host")
}
// Create a plugin context for the plugins loaded by the host.
// Note that an separate plugin context ('pulumi-plan') will be created by the Pulumi engine.
cwd, err := os.Getwd()
if err != nil {
return nil, err
}
pluginCtx, err := plugin.NewContext(sink, sink, host, nil, cwd, nil, false, tracingSpan)
if err != nil {
_ = host.Close()
return nil, errors.Wrapf(err, "creating Pulumi plugin context")
}
host.SetPluginContext(pluginCtx)
// Return the context to be able to conveniently close the host and associated trace span
return pluginCtx, nil
}