Skip to content
This repository was archived by the owner on Mar 24, 2025. It is now read-only.

Commit 07f8597

Browse files
authored
✨ Add compute-config rig-ops command to dry-run plugin configuration (#1209)
1 parent 50d800d commit 07f8597

File tree

31 files changed

+511
-38
lines changed

31 files changed

+511
-38
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package plugins
2+
3+
import (
4+
"context"
5+
"os"
6+
7+
"connectrpc.com/connect"
8+
"github.com/rigdev/rig-go-api/operator/api/v1/pipeline"
9+
"github.com/rigdev/rig/cmd/common"
10+
"github.com/rigdev/rig/cmd/rig-ops/cmd/base"
11+
"github.com/rigdev/rig/pkg/api/v1alpha2"
12+
"github.com/rigdev/rig/pkg/obj"
13+
"github.com/spf13/cobra"
14+
"gopkg.in/yaml.v3"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
16+
)
17+
18+
func (c *Cmd) computeConfig(ctx context.Context, _ *cobra.Command, args []string) error {
19+
cfg, err := base.GetOperatorConfig(ctx, c.OperatorClient, c.Scheme)
20+
if err != nil {
21+
return err
22+
}
23+
24+
var spec string
25+
var capsule v1alpha2.Capsule
26+
if len(args) > 0 {
27+
if err := c.K8s.Get(ctx, client.ObjectKey{
28+
Namespace: args[0],
29+
Name: args[1],
30+
}, &capsule); err != nil {
31+
return err
32+
}
33+
} else if specPath != "" {
34+
bytes, err := os.ReadFile(specPath)
35+
if err != nil {
36+
return err
37+
}
38+
spec = string(bytes)
39+
if err := obj.Decode([]byte(spec), &capsule); err != nil {
40+
return err
41+
}
42+
} else {
43+
capsuleList := v1alpha2.CapsuleList{}
44+
if err := c.K8s.List(ctx, &capsuleList); err != nil {
45+
return err
46+
}
47+
var choices [][]string
48+
for _, c := range capsuleList.Items {
49+
choices = append(choices, []string{c.Namespace, c.Name})
50+
}
51+
idx, err := c.Prompter.TableSelect(
52+
"Choose a capsule", choices, []string{"Namespace", "Capsule"}, common.SelectEnableFilterOpt,
53+
)
54+
if err != nil {
55+
return err
56+
}
57+
choice := choices[idx]
58+
if err := c.K8s.Get(ctx, client.ObjectKey{
59+
Namespace: choice[0],
60+
Name: choice[1],
61+
}, &capsule); err != nil {
62+
return err
63+
}
64+
}
65+
66+
cfgBytes, err := yaml.Marshal(cfg)
67+
if err != nil {
68+
return err
69+
}
70+
71+
resp, err := c.OperatorClient.Pipeline.DryRunPluginConfig(ctx, connect.NewRequest(&pipeline.DryRunPluginConfigRequest{
72+
Namespace: capsule.Namespace,
73+
Capsule: capsule.Name,
74+
OperatorConfig: string(cfgBytes),
75+
CapsuleSpec: spec,
76+
}))
77+
if err != nil {
78+
return err
79+
}
80+
81+
return common.FormatPrint(resp.Msg.GetSteps(), common.OutputTypeYAML)
82+
}

cmd/rig-ops/cmd/plugins/setup.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,21 @@ The dry run will be executed with the resulting list of plugins.`,
120120
}
121121
pluginsCmd.AddCommand(list)
122122

123+
computeConfig := &cobra.Command{
124+
Use: "compute-config",
125+
//nolint:lll
126+
Short: "Given an operator config and a capsule spec, computes the configuration generated for each plugin.",
127+
Args: func(_ *cobra.Command, args []string) error {
128+
if len(args) != 0 && len(args) != 2 {
129+
return errors.New("takes exactly 0 or 2 arguments")
130+
}
131+
return nil
132+
},
133+
RunE: cli.CtxWrap(cmd.computeConfig),
134+
}
135+
//nolint:lll
136+
computeConfig.Flags().StringVar(&specPath, "spec", "", "If given, will read the capsule spec at the path instead of using the capsule spec of an existing capsule from the platform")
137+
pluginsCmd.AddCommand(computeConfig)
138+
123139
parent.AddCommand(pluginsCmd)
124140
}

pkg/controller/plugin/external_plugin.go

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,12 @@ func (p *pluginExecutor) WatchObjectStatus(
186186
return p.pluginClient.WatchObjectStatus(ctx, namespace, capsule, callback, p.id)
187187
}
188188

189+
func (p *pluginExecutor) ComputeConfig(ctx context.Context, req pipeline.CapsuleRequest) (string, error) {
190+
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
191+
defer cancel()
192+
return p.pluginClient.ComputeConfig(ctx, req)
193+
}
194+
189195
type rigOperatorPlugin struct {
190196
plugin.NetRPCUnsupportedPlugin
191197
logger hclog.Logger
@@ -239,23 +245,10 @@ func (m *pluginClient) Initialize(ctx context.Context, pluginConfig, tag string,
239245
}
240246

241247
func (m *pluginClient) Run(ctx context.Context, req pipeline.CapsuleRequest, opts pipeline.Options) error {
242-
reqServer := &requestServer{req: req}
243-
capsuleBytes, err := obj.Encode(req.Capsule(), req.Scheme())
248+
s, brokerID, err := m.setupGRPCServer(req)
244249
if err != nil {
245250
return err
246251
}
247-
248-
c := make(chan *grpc.Server)
249-
serverFunc := func(opts []grpc.ServerOption) *grpc.Server {
250-
s := grpc.NewServer(opts...)
251-
apiplugin.RegisterRequestServiceServer(s, reqServer)
252-
c <- s
253-
return s
254-
}
255-
256-
brokerID := m.broker.NextId()
257-
go m.broker.AcceptAndServe(brokerID, serverFunc)
258-
s := <-c
259252
defer s.Stop()
260253

261254
var additionalObjects [][]byte
@@ -267,6 +260,10 @@ func (m *pluginClient) Run(ctx context.Context, req pipeline.CapsuleRequest, opt
267260
additionalObjects = append(additionalObjects, bs)
268261
}
269262

263+
capsuleBytes, err := obj.Encode(req.Capsule(), req.Scheme())
264+
if err != nil {
265+
return err
266+
}
270267
_, err = m.client.RunCapsule(ctx, &apiplugin.RunCapsuleRequest{
271268
RunServer: brokerID,
272269
CapsuleObject: capsuleBytes,
@@ -276,6 +273,21 @@ func (m *pluginClient) Run(ctx context.Context, req pipeline.CapsuleRequest, opt
276273
return err
277274
}
278275

276+
func (m *pluginClient) setupGRPCServer(req pipeline.CapsuleRequest) (*grpc.Server, uint32, error) {
277+
reqServer := &requestServer{req: req}
278+
c := make(chan *grpc.Server)
279+
serverFunc := func(opts []grpc.ServerOption) *grpc.Server {
280+
s := grpc.NewServer(opts...)
281+
apiplugin.RegisterRequestServiceServer(s, reqServer)
282+
c <- s
283+
return s
284+
}
285+
brokerID := m.broker.NextId()
286+
go m.broker.AcceptAndServe(brokerID, serverFunc)
287+
s := <-c
288+
return s, brokerID, nil
289+
}
290+
279291
func (m *pluginClient) WatchObjectStatus(
280292
ctx context.Context,
281293
namespace string,
@@ -301,6 +313,26 @@ func (m *pluginClient) WatchObjectStatus(
301313
}
302314
}
303315

316+
func (m *pluginClient) ComputeConfig(ctx context.Context, req pipeline.CapsuleRequest) (string, error) {
317+
s, brokerID, err := m.setupGRPCServer(req)
318+
if err != nil {
319+
return "", err
320+
}
321+
defer s.Stop()
322+
323+
capsuleBytes, err := obj.Encode(req.Capsule(), req.Scheme())
324+
if err != nil {
325+
return "", err
326+
}
327+
328+
resp, err := m.client.ComputeConfig(ctx, &apiplugin.ComputeConfigRequest{
329+
RunServer: brokerID,
330+
CapsuleObject: capsuleBytes,
331+
})
332+
333+
return resp.GetConfig(), err
334+
}
335+
304336
type requestServer struct {
305337
apiplugin.UnimplementedRequestServiceServer
306338

pkg/controller/plugin/helpers.go

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ package plugin
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"html/template"
67

78
"github.com/mitchellh/mapstructure"
89
"github.com/rigdev/rig/pkg/obj"
910
"github.com/rigdev/rig/pkg/pipeline"
11+
"sigs.k8s.io/yaml"
1012
)
1113

12-
type ParseStep[T any] func(config T, req pipeline.CapsuleRequest) (string, any, error)
14+
type ParseStep[T any] func(config T, req pipeline.CapsuleRequest) (map[string]any, error)
1315

1416
// ParseCapsuleTemplatedConfig parses the given data as a Go template with
1517
// the capsule as a templating context under '.capsule'
@@ -21,25 +23,30 @@ func ParseCapsuleTemplatedConfig[T any](data []byte, req pipeline.CapsuleRequest
2123
// Using this, we parse the config at every execution of the plugin.
2224
// If we get performance issues due to that we can try and optimize that.
2325
func ParseTemplatedConfig[T any](data []byte, req pipeline.CapsuleRequest, steps ...ParseStep[T]) (T, error) {
26+
if len(data) == 0 {
27+
data = []byte("{}")
28+
}
2429
var config, empty T
2530

2631
values := map[string]any{}
2732
for _, step := range steps {
28-
name, obj, err := step(config, req)
33+
m, err := step(config, req)
2934
if err != nil {
3035
return empty, err
3136
}
3237

33-
result := map[string]interface{}{}
34-
d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{TagName: "json", Result: &result})
35-
if err != nil {
36-
return empty, err
38+
for k, v := range m {
39+
result := map[string]interface{}{}
40+
d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{TagName: "json", Result: &result})
41+
if err != nil {
42+
return empty, err
43+
}
44+
if err := d.Decode(v); err != nil {
45+
return empty, err
46+
}
47+
values[k] = result
3748
}
3849

39-
if err := d.Decode(obj); err != nil {
40-
return empty, err
41-
}
42-
values[name] = result
4350
t, err := template.New("config").Parse(string(data))
4451
if err != nil {
4552
return empty, err
@@ -56,10 +63,32 @@ func ParseTemplatedConfig[T any](data []byte, req pipeline.CapsuleRequest, steps
5663
return config, nil
5764
}
5865

59-
func CapsuleStep[T any](_ T, req pipeline.CapsuleRequest) (string, any, error) {
60-
return "capsule", req.Capsule(), nil
66+
func CapsuleStep[T any](_ T, req pipeline.CapsuleRequest) (map[string]any, error) {
67+
c := req.Capsule()
68+
extensions := map[string]any{}
69+
for k, v := range c.Spec.Extensions {
70+
vv := map[string]any{}
71+
if err := json.Unmarshal(v, &vv); err != nil {
72+
return nil, err
73+
}
74+
extensions[k] = vv
75+
}
76+
c.Spec.Extensions = nil
77+
return map[string]any{
78+
"capsule": c,
79+
"capsuleExtensions": extensions,
80+
}, nil
6181
}
6282

6383
func LoadYAMLConfig(data []byte, out any) error {
6484
return obj.Decode(data, out)
6585
}
86+
87+
func ParseCapsuleTemplatedConfigToString[T any](data []byte, req pipeline.CapsuleRequest) (string, error) {
88+
obj, err := ParseCapsuleTemplatedConfig[T](data, req)
89+
if err != nil {
90+
return "", err
91+
}
92+
bs, err := yaml.Marshal(obj)
93+
return string(bs), err
94+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package plugin
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"testing"
7+
8+
"github.com/go-logr/logr"
9+
"github.com/rigdev/rig/pkg/api/v1alpha2"
10+
"github.com/rigdev/rig/pkg/pipeline"
11+
"github.com/rigdev/rig/pkg/scheme"
12+
"github.com/stretchr/testify/require"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
)
15+
16+
func Test_ParseCapsuleTemplatedConfig(t *testing.T) {
17+
name, namespace := "name", "namespace"
18+
vm := scheme.NewVersionMapperFromScheme(scheme.New())
19+
p := pipeline.NewCapsulePipeline(nil, scheme.New(), vm, logr.FromContextOrDiscard(context.Background()))
20+
21+
req := pipeline.NewCapsuleRequest(p, &v1alpha2.Capsule{
22+
ObjectMeta: metav1.ObjectMeta{
23+
Name: name,
24+
Namespace: namespace,
25+
},
26+
Spec: v1alpha2.CapsuleSpec{
27+
Extensions: map[string]json.RawMessage{
28+
"ext": json.RawMessage(`{"field": "value"}`),
29+
},
30+
Scale: v1alpha2.CapsuleScale{
31+
Horizontal: v1alpha2.HorizontalScale{
32+
Instances: v1alpha2.Instances{
33+
Min: 69,
34+
},
35+
},
36+
},
37+
},
38+
}, nil)
39+
40+
s := `hej: asdf
41+
hej2: {{ .capsuleExtensions.ext.field }}
42+
hej3: {{ .capsule.spec.scale.horizontal.instances.min }}`
43+
conf, err := ParseCapsuleTemplatedConfig[config]([]byte(s), req)
44+
require.NoError(t, err)
45+
46+
require.Equal(t, config{
47+
Hej: "asdf",
48+
Hej2: "value",
49+
Hej3: 69,
50+
}, conf)
51+
}
52+
53+
type config struct {
54+
Hej string `json:"hej"`
55+
Hej2 string `json:"hej2"`
56+
Hej3 int `json:"hej3"`
57+
}

pkg/controller/plugin/manager.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,9 @@ func (m *Manager) GetPlugins() []Info {
234234
return plugins
235235
}
236236

237-
func (m *Manager) NewStep(execCtx ExecutionContext, step v1alpha1.Step, logger logr.Logger) (*Step, error) {
237+
func (m *Manager) NewStep(
238+
execCtx ExecutionContext, step v1alpha1.Step, logger logr.Logger, name string,
239+
) (*Step, error) {
238240
var err error
239241
var ps []*pluginExecutor
240242
defer func() {
@@ -274,6 +276,7 @@ func (m *Manager) NewStep(execCtx ExecutionContext, step v1alpha1.Step, logger l
274276
logger: logger,
275277
plugins: ps,
276278
matcher: matcher,
279+
name: name,
277280
}, nil
278281
}
279282

0 commit comments

Comments
 (0)