Skip to content

Commit d6b4bd4

Browse files
authored
Merge pull request #15 from michalskrivanek/print
2 parents 755997b + f7676b2 commit d6b4bd4

File tree

4 files changed

+110
-37
lines changed

4 files changed

+110
-37
lines changed

cmd/apply.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ var applyCmd = &cobra.Command{
4545
debugConfigs, _ := cmd.Flags().GetBool("debug-configs")
4646
filterClients, _ := cmd.Flags().GetString("filter-clients")
4747
filterExporters, _ := cmd.Flags().GetString("filter-exporters")
48+
printCredentials, _ := cmd.Flags().GetBool("print-exporter-credentials")
4849

4950
// Determine config file path
5051
configFilePath := defaultConfigFile
@@ -97,7 +98,8 @@ var applyCmd = &cobra.Command{
9798
if err != nil {
9899
return fmt.Errorf("error applying template for %s: %w", inst.Name, err)
99100
}
100-
instanceClient, err := instance.NewInstance(instanceCopy, instanceCopy.Spec.Kubeconfig, dryRun, prune)
101+
instanceClient, err := instance.NewInstance(instanceCopy, instanceCopy.Spec.Kubeconfig, dryRun, prune,
102+
printCredentials)
101103
if err != nil {
102104
return fmt.Errorf("error creating instance for %s: %w", inst.Name, err)
103105
}
@@ -133,6 +135,7 @@ func init() {
133135
applyCmd.Flags().Bool("debug-configs", false, "Show debug configs")
134136
applyCmd.Flags().String("filter-clients", "", "Regexp pattern to filter clients by name")
135137
applyCmd.Flags().String("filter-exporters", "", "Regexp pattern to filter exporters by name")
138+
applyCmd.Flags().Bool("print-exporter-credentials", false, "Print connection details for exporters")
136139

137140
rootCmd.AddCommand(applyCmd)
138141
}

internal/instance/exporter_sync.go

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,56 @@ func (i *Instance) deleteExporter(ctx context.Context, name string) error {
205205
return i.client.Delete(ctx, exporter)
206206
}
207207

208+
// formatCredentialsYAML formats credentials in YAML format for easy copy&paste
209+
func (i *Instance) formatCredentialsYAML(exporterName string, serviceParameters *template.ServiceParameters, cfg *config.Config) (string, error) {
210+
// Get the gRPC endpoint
211+
endpoint, err := i.getGrpcEndpoint(exporterName, cfg)
212+
if err != nil {
213+
return "", err
214+
}
215+
216+
// Build YAML output
217+
yaml := fmt.Sprintf("endpoint: \"%s\"\n", endpoint)
218+
219+
// Always add TLS section
220+
if serviceParameters.TlsCA != "" {
221+
yaml += fmt.Sprintf("tls:\n ca: \"%s\"\n", serviceParameters.TlsCA)
222+
} else {
223+
yaml += "tls:\n ca: \"\"\n insecure: true\n"
224+
}
225+
226+
yaml += fmt.Sprintf("token: \"%s\"", serviceParameters.Token)
227+
228+
return yaml, nil
229+
}
230+
231+
// getGrpcEndpoint gets the gRPC endpoint for an exporter from its jumpstarter instance
232+
func (i *Instance) getGrpcEndpoint(exporterName string, cfg *config.Config) (string, error) {
233+
// Find the exporter instance in the config
234+
for _, exporterInstance := range cfg.Loaded.ExporterInstances {
235+
if exporterInstance.Name == exporterName {
236+
// Get the jumpstarter instance name
237+
jumpstarterInstanceName := exporterInstance.Spec.JumpstarterInstanceRef.Name
238+
if jumpstarterInstanceName == "" {
239+
return "", fmt.Errorf("exporter %s has no jumpstarter instance reference", exporterName)
240+
}
241+
242+
// Look up the jumpstarter instance
243+
jumpstarterInstance, exists := cfg.Loaded.GetJumpstarterInstances()[jumpstarterInstanceName]
244+
if !exists {
245+
return "", fmt.Errorf("jumpstarter instance %s not found for exporter %s", jumpstarterInstanceName, exporterName)
246+
}
247+
248+
// Return the first endpoint if available
249+
if len(jumpstarterInstance.Spec.Endpoints) > 0 {
250+
return jumpstarterInstance.Spec.Endpoints[0], nil
251+
}
252+
return "", fmt.Errorf("jumpstarter instance %s has no endpoints for exporter %s", jumpstarterInstanceName, exporterName)
253+
}
254+
}
255+
return "", fmt.Errorf("exporter instance %s not found in config", exporterName)
256+
}
257+
208258
func (i *Instance) SyncExporters(ctx context.Context, cfg *config.Config, filter *regexp.Regexp) (map[string]template.ServiceParameters, error) {
209259
serviceParametersMap := make(map[string]template.ServiceParameters)
210260
fmt.Printf("\n🔄 [%s] Syncing exporters ===========================\n\n", i.config.Name)
@@ -262,27 +312,38 @@ func (i *Instance) SyncExporters(ctx context.Context, cfg *config.Config, filter
262312

263313
// create exporters that are in config but not in instance
264314
for _, cfgExporter := range configExporterMap {
265-
// Use the helper method to get the Exporter object for this instance
266-
267315
if _, ok := instanceExporterMap[cfgExporter.Name]; !ok {
316+
// This is a new exporter - create it
268317
err := i.createExporter(ctx, &cfgExporter)
269318
if err != nil {
270319
return nil, fmt.Errorf("[%s] failed to create exporter %s: %w", i.config.Name, cfgExporter.Name, err)
271320
}
272-
}
273-
var serviceParameters *template.ServiceParameters
274-
if !i.dryRun {
275-
serviceParameters, err = i.waitExporterCredentials(ctx, &cfgExporter)
276-
if err != nil {
277-
return nil, fmt.Errorf("[%s] failed to wait for exporter credentials for %s: %w", i.config.Name, cfgExporter.Name, err)
321+
322+
var serviceParameters *template.ServiceParameters
323+
if !i.dryRun {
324+
serviceParameters, err = i.waitExporterCredentials(ctx, &cfgExporter)
325+
if err != nil {
326+
return nil, fmt.Errorf("[%s] failed to wait for exporter credentials for %s: %w", i.config.Name, cfgExporter.Name, err)
327+
}
328+
} else {
329+
serviceParameters = &template.ServiceParameters{
330+
Token: "dry-run",
331+
TlsCA: "",
332+
}
278333
}
279-
} else {
280-
serviceParameters = &template.ServiceParameters{
281-
Token: "dry-run",
282-
TlsCA: "",
334+
335+
// Print credentials when flag is set, or for new non-managed exporters (not in dry run)
336+
if i.printCredentials || (!i.dryRun && cfg.Loaded.ExporterInstances[cfgExporter.Name] != nil &&
337+
cfg.Loaded.ExporterInstances[cfgExporter.Name].Spec.ExporterHostRef.Name == "") {
338+
yamlOutput, err := i.formatCredentialsYAML(cfgExporter.Name, serviceParameters, cfg)
339+
if err != nil {
340+
return nil, fmt.Errorf("[%s] failed to format credentials for exporter %s: %w", i.config.Name, cfgExporter.Name, err)
341+
}
342+
fmt.Printf("🔍 [%s] Exporter connection details snippet for exporter %s:\n%s\n", i.config.Name, cfgExporter.Name, yamlOutput)
283343
}
344+
345+
serviceParametersMap[cfgExporter.Name] = *serviceParameters
284346
}
285-
serviceParametersMap[cfgExporter.Name] = *serviceParameters
286347
}
287348

288349
// update exporters that are in both config and instance
@@ -298,6 +359,13 @@ func (i *Instance) SyncExporters(ctx context.Context, cfg *config.Config, filter
298359
if err != nil {
299360
return nil, fmt.Errorf("[%s] failed to wait for exporter credentials for %s: %w", i.config.Name, exporterObj.Name, err)
300361
}
362+
if i.printCredentials {
363+
yamlOutput, err := i.formatCredentialsYAML(exporterObj.Name, serviceParameters, cfg)
364+
if err != nil {
365+
return nil, fmt.Errorf("[%s] failed to format credentials for exporter %s: %w", i.config.Name, exporterObj.Name, err)
366+
}
367+
fmt.Printf("🔍 [%s] Exporter connection details snippet for exporter %s:\n%s\n", i.config.Name, exporterObj.Name, yamlOutput)
368+
}
301369
serviceParametersMap[exporterObj.Name] = *serviceParameters
302370
}
303371
}

internal/instance/instance.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,17 @@ const (
2222

2323
// Instance wraps a Kubernetes client and provides methods for operating on Jumpstarter resources
2424
type Instance struct {
25-
client client.Client
26-
config *v1alphaConfig.JumpstarterInstance
27-
dryRun bool
28-
prune bool
25+
client client.Client
26+
config *v1alphaConfig.JumpstarterInstance
27+
dryRun bool
28+
prune bool
29+
printCredentials bool
2930
}
3031

3132
// NewInstance creates a new Instance from a JumpstarterInstance and optional kubeconfig string
3233
// If kubeconfigStr is empty, it will try to load from environment/standard kubeconfig file
3334
// This function ensures proper scheme registration for all custom API types
34-
func NewInstance(instance *v1alphaConfig.JumpstarterInstance, kubeconfigStr string, dryRun, prune bool) (*Instance, error) {
35+
func NewInstance(instance *v1alphaConfig.JumpstarterInstance, kubeconfigStr string, dryRun, prune, printCredentials bool) (*Instance, error) {
3536
// Validate the instance
3637
if err := validateInstance(instance); err != nil {
3738
return nil, fmt.Errorf("invalid instance: %w", err)
@@ -88,10 +89,11 @@ func NewInstance(instance *v1alphaConfig.JumpstarterInstance, kubeconfigStr stri
8889
}
8990

9091
return &Instance{
91-
client: c,
92-
config: instance,
93-
dryRun: dryRun,
94-
prune: prune,
92+
client: c,
93+
config: instance,
94+
dryRun: dryRun,
95+
prune: prune,
96+
printCredentials: printCredentials,
9597
}, nil
9698
}
9799

internal/instance/instance_test.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func TestNewInstance(t *testing.T) {
2828

2929
t.Run("with kubeconfig string", func(t *testing.T) {
3030
// This will likely fail since we don't have a real kubeconfig, but we test the flow
31-
_, err := NewInstance(instance, validKubeconfig, false, false)
31+
_, err := NewInstance(instance, validKubeconfig, false, false, false)
3232
// We expect this to fail since the context doesn't exist in our test kubeconfig
3333
if err != nil {
3434
assert.Contains(t, err.Error(), "context test-context does not exist in kubeconfig")
@@ -37,7 +37,7 @@ func TestNewInstance(t *testing.T) {
3737

3838
t.Run("without kubeconfig string", func(t *testing.T) {
3939
// This will likely fail since we don't have a real kubeconfig file, but we test the flow
40-
_, err := NewInstance(instance, "", false, false)
40+
_, err := NewInstance(instance, "", false, false, false)
4141
// We expect this to fail since the context doesn't exist in the default kubeconfig
4242
if err != nil {
4343
assert.True(t,
@@ -60,7 +60,7 @@ func TestNewInstance(t *testing.T) {
6060
},
6161
}
6262

63-
_, err := NewInstance(instanceNoContext, validKubeconfig, false, false)
63+
_, err := NewInstance(instanceNoContext, validKubeconfig, false, false, false)
6464
// This should work with our test kubeconfig since it uses the default context
6565
if err != nil {
6666
assert.True(t,
@@ -85,7 +85,7 @@ func TestInstanceMethods(t *testing.T) {
8585

8686
t.Run("GetClient and GetConfig", func(t *testing.T) {
8787
// This will likely fail, but we test the method signatures
88-
inst, err := NewInstance(instance, validKubeconfig, false, false)
88+
inst, err := NewInstance(instance, validKubeconfig, false, false, false)
8989
if err == nil {
9090
// If it succeeds, test the methods
9191
client := inst.GetClient()
@@ -110,7 +110,7 @@ func TestInstanceExporterMethods(t *testing.T) {
110110
}
111111

112112
t.Run("listExporters with namespace", func(t *testing.T) {
113-
inst, err := NewInstance(instance, validKubeconfig, false, false)
113+
inst, err := NewInstance(instance, validKubeconfig, false, false, false)
114114
if err == nil {
115115
// Test that the method exists and can be called
116116
ctx := context.Background()
@@ -143,7 +143,7 @@ func TestInstanceExporterMethods(t *testing.T) {
143143
},
144144
}
145145

146-
inst, err := NewInstance(instanceNoNamespace, validKubeconfig, false, false)
146+
inst, err := NewInstance(instanceNoNamespace, validKubeconfig, false, false, false)
147147
if err == nil {
148148
ctx := context.Background()
149149
exporters, err := inst.listExporters(ctx)
@@ -164,7 +164,7 @@ func TestInstanceExporterMethods(t *testing.T) {
164164
})
165165

166166
t.Run("GetExporterByName", func(t *testing.T) {
167-
inst, err := NewInstance(instance, validKubeconfig, false, false)
167+
inst, err := NewInstance(instance, validKubeconfig, false, false, false)
168168
if err == nil {
169169
ctx := context.Background()
170170
exporter, err := inst.GetExporterByName(ctx, "test-exporter")
@@ -196,7 +196,7 @@ func TestInstanceExporterMethods(t *testing.T) {
196196
},
197197
}
198198

199-
inst, err := NewInstance(instanceNoNamespace, validKubeconfig, false, false)
199+
inst, err := NewInstance(instanceNoNamespace, validKubeconfig, false, false, false)
200200
if err == nil {
201201
ctx := context.Background()
202202
_, err := inst.GetExporterByName(ctx, "test-exporter")
@@ -220,7 +220,7 @@ func TestInstanceClientMethods(t *testing.T) {
220220
}
221221

222222
t.Run("ListClients with namespace", func(t *testing.T) {
223-
inst, err := NewInstance(instance, validKubeconfig, false, false)
223+
inst, err := NewInstance(instance, validKubeconfig, false, false, false)
224224
if err == nil {
225225
ctx := context.Background()
226226
clients, err := inst.ListClients(ctx)
@@ -242,7 +242,7 @@ func TestInstanceClientMethods(t *testing.T) {
242242
})
243243

244244
t.Run("GetClientByName", func(t *testing.T) {
245-
inst, err := NewInstance(instance, validKubeconfig, false, false)
245+
inst, err := NewInstance(instance, validKubeconfig, false, false, false)
246246
if err == nil {
247247
ctx := context.Background()
248248
client, err := inst.GetClientByName(ctx, "test-client")
@@ -274,7 +274,7 @@ func TestInstanceClientMethods(t *testing.T) {
274274
},
275275
}
276276

277-
inst, err := NewInstance(instanceNoNamespace, validKubeconfig, false, false)
277+
inst, err := NewInstance(instanceNoNamespace, validKubeconfig, false, false, false)
278278
if err == nil {
279279
ctx := context.Background()
280280
_, err := inst.GetClientByName(ctx, "test-client")
@@ -339,7 +339,7 @@ func TestPrintDiff(t *testing.T) {
339339
}
340340

341341
// Create an instance to test the printDiff method
342-
inst, err := NewInstance(instance, validKubeconfig, false, false)
342+
inst, err := NewInstance(instance, validKubeconfig, false, false, false)
343343
if err == nil {
344344
// This should not panic and should print a diff
345345
inst.checkAndPrintDiff(oldObj, newObj, "exporter", "test-exporter", true)
@@ -356,7 +356,7 @@ func TestPrintDiff(t *testing.T) {
356356
}
357357

358358
// Create an instance to test the printDiff method
359-
inst, err := NewInstance(instance, validKubeconfig, false, false)
359+
inst, err := NewInstance(instance, validKubeconfig, false, false, false)
360360
if err == nil {
361361
// This should not panic and should indicate no changes
362362
inst.checkAndPrintDiff(obj, obj, "exporter", "test-exporter", true)
@@ -379,7 +379,7 @@ func TestCheckAndPrintDiff(t *testing.T) {
379379
}
380380

381381
// Create an instance for testing
382-
inst, err := NewInstance(instance, validKubeconfig, false, false)
382+
inst, err := NewInstance(instance, validKubeconfig, false, false, false)
383383
require.NoError(t, err)
384384

385385
t.Run("identical objects should return false", func(t *testing.T) {

0 commit comments

Comments
 (0)