Skip to content

Commit 2c7e301

Browse files
authored
Add registry list command (#369)
* Add registry list command * Clean up * gendoc
1 parent d684121 commit 2c7e301

7 files changed

Lines changed: 322 additions & 0 deletions

File tree

cmd/registry/list/list.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package list
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
8+
"github.com/smartcontractkit/cre-cli/internal/runtime"
9+
"github.com/smartcontractkit/cre-cli/internal/ui"
10+
)
11+
12+
func New(runtimeContext *runtime.Context) *cobra.Command {
13+
return &cobra.Command{
14+
Use: "list",
15+
Short: "Lists available workflow registries for the current environment",
16+
Long: `Displays the registries configured for your organization, including type and address.`,
17+
Example: `cre registry list`,
18+
Args: cobra.NoArgs,
19+
RunE: func(cmd *cobra.Command, args []string) error {
20+
if runtimeContext.TenantContext == nil {
21+
return fmt.Errorf("user context not available — run `cre login` and retry")
22+
}
23+
24+
registries := runtimeContext.TenantContext.Registries
25+
if len(registries) == 0 {
26+
ui.Warning("No registries found for this environment")
27+
return nil
28+
}
29+
30+
ui.Line()
31+
ui.Bold("Registries available to your organization")
32+
ui.Line()
33+
34+
for _, r := range registries {
35+
ui.Bold(r.Label)
36+
ui.Dim(fmt.Sprintf("ID: %s", r.ID))
37+
ui.Dim(fmt.Sprintf("Type: %s", r.Type))
38+
if r.Address != nil && *r.Address != "" {
39+
ui.Dim(fmt.Sprintf("Addr: %s", *r.Address))
40+
}
41+
ui.Line()
42+
}
43+
44+
return nil
45+
},
46+
}
47+
}

cmd/registry/list/list_test.go

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package list_test
2+
3+
import (
4+
"io"
5+
"os"
6+
"strings"
7+
"testing"
8+
9+
"github.com/rs/zerolog"
10+
11+
"github.com/smartcontractkit/cre-cli/cmd/registry/list"
12+
"github.com/smartcontractkit/cre-cli/internal/environments"
13+
"github.com/smartcontractkit/cre-cli/internal/runtime"
14+
"github.com/smartcontractkit/cre-cli/internal/tenantctx"
15+
)
16+
17+
func strPtr(s string) *string { return &s }
18+
19+
func TestList_NoTenantContext(t *testing.T) {
20+
logger := zerolog.New(io.Discard)
21+
rtCtx := &runtime.Context{
22+
Logger: &logger,
23+
EnvironmentSet: &environments.EnvironmentSet{EnvName: "STAGING"},
24+
TenantContext: nil,
25+
}
26+
27+
cmd := list.New(rtCtx)
28+
cmd.SetArgs([]string{})
29+
err := cmd.Execute()
30+
if err == nil {
31+
t.Fatal("expected error when TenantContext is nil")
32+
}
33+
if !strings.Contains(err.Error(), "user context not available") {
34+
t.Errorf("unexpected error: %v", err)
35+
}
36+
}
37+
38+
func TestList_EmptyRegistries(t *testing.T) {
39+
logger := zerolog.New(io.Discard)
40+
rtCtx := &runtime.Context{
41+
Logger: &logger,
42+
EnvironmentSet: &environments.EnvironmentSet{EnvName: "STAGING"},
43+
TenantContext: &tenantctx.EnvironmentContext{
44+
Registries: []*tenantctx.Registry{},
45+
},
46+
}
47+
48+
cmd := list.New(rtCtx)
49+
cmd.SetArgs([]string{})
50+
51+
// suppress stderr (ui.Warning writes there)
52+
oldStderr := os.Stderr
53+
os.Stderr, _ = os.Open(os.DevNull)
54+
defer func() { os.Stderr = oldStderr }()
55+
56+
if err := cmd.Execute(); err != nil {
57+
t.Fatalf("unexpected error: %v", err)
58+
}
59+
}
60+
61+
func TestList_OnChainAndOffChain(t *testing.T) {
62+
logger := zerolog.New(io.Discard)
63+
rtCtx := &runtime.Context{
64+
Logger: &logger,
65+
EnvironmentSet: &environments.EnvironmentSet{EnvName: "STAGING"},
66+
TenantContext: &tenantctx.EnvironmentContext{
67+
Registries: []*tenantctx.Registry{
68+
{
69+
ID: "onchain:ethereum-testnet-sepolia",
70+
Label: "ethereum-testnet-sepolia (0xaE55...1135)",
71+
Type: "on-chain",
72+
Address: strPtr("0xaE55eB3EDAc48a1163EE2cbb1205bE1e90Ea1135"),
73+
},
74+
{
75+
ID: "private",
76+
Label: "Private (Chainlink-hosted)",
77+
Type: "off-chain",
78+
},
79+
},
80+
},
81+
}
82+
83+
cmd := list.New(rtCtx)
84+
cmd.SetArgs([]string{})
85+
86+
oldStdout := os.Stdout
87+
r, w, _ := os.Pipe()
88+
os.Stdout = w
89+
90+
if err := cmd.Execute(); err != nil {
91+
w.Close()
92+
os.Stdout = oldStdout
93+
t.Fatalf("unexpected error: %v", err)
94+
}
95+
96+
w.Close()
97+
os.Stdout = oldStdout
98+
var buf strings.Builder
99+
_, _ = io.Copy(&buf, r)
100+
output := buf.String()
101+
102+
for _, want := range []string{
103+
"Registries available to your organization",
104+
"onchain:ethereum-testnet-sepolia",
105+
"on-chain",
106+
"0xaE55eB3EDAc48a1163EE2cbb1205bE1e90Ea1135",
107+
"private",
108+
"off-chain",
109+
"Private (Chainlink-hosted)",
110+
} {
111+
if !strings.Contains(output, want) {
112+
t.Errorf("output missing %q; full output:\n%s", want, output)
113+
}
114+
}
115+
}
116+
117+
func TestList_OffChainNoAddress(t *testing.T) {
118+
logger := zerolog.New(io.Discard)
119+
rtCtx := &runtime.Context{
120+
Logger: &logger,
121+
EnvironmentSet: &environments.EnvironmentSet{EnvName: "STAGING"},
122+
TenantContext: &tenantctx.EnvironmentContext{
123+
Registries: []*tenantctx.Registry{
124+
{
125+
ID: "private",
126+
Label: "Private",
127+
Type: "off-chain",
128+
},
129+
},
130+
},
131+
}
132+
133+
cmd := list.New(rtCtx)
134+
cmd.SetArgs([]string{})
135+
136+
oldStdout := os.Stdout
137+
r, w, _ := os.Pipe()
138+
os.Stdout = w
139+
140+
if err := cmd.Execute(); err != nil {
141+
w.Close()
142+
os.Stdout = oldStdout
143+
t.Fatalf("unexpected error: %v", err)
144+
}
145+
146+
w.Close()
147+
os.Stdout = oldStdout
148+
var buf strings.Builder
149+
_, _ = io.Copy(&buf, r)
150+
output := buf.String()
151+
152+
if strings.Contains(output, "Addr:") {
153+
t.Errorf("expected no Addr line for off-chain registry; output:\n%s", output)
154+
}
155+
}
156+
157+
func TestList_RejectsArgs(t *testing.T) {
158+
logger := zerolog.New(io.Discard)
159+
rtCtx := &runtime.Context{
160+
Logger: &logger,
161+
EnvironmentSet: &environments.EnvironmentSet{},
162+
TenantContext: &tenantctx.EnvironmentContext{},
163+
}
164+
165+
cmd := list.New(rtCtx)
166+
cmd.SetArgs([]string{"extra"})
167+
168+
// cobra prints usage to stderr on arg errors; suppress
169+
cmd.SilenceUsage = true
170+
cmd.SilenceErrors = true
171+
172+
if err := cmd.Execute(); err == nil {
173+
t.Fatal("expected error when extra args provided")
174+
}
175+
}

cmd/registry/registry.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package registry
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
6+
"github.com/smartcontractkit/cre-cli/cmd/registry/list"
7+
"github.com/smartcontractkit/cre-cli/internal/runtime"
8+
)
9+
10+
func New(runtimeContext *runtime.Context) *cobra.Command {
11+
registryCmd := &cobra.Command{
12+
Use: "registry",
13+
Short: "Manages workflow registries",
14+
Long: `The registry command lets you view and inspect the workflow registries available for your organization.`,
15+
}
16+
17+
registryCmd.AddCommand(list.New(runtimeContext))
18+
19+
return registryCmd
20+
}

cmd/root.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
generatebindings "github.com/smartcontractkit/cre-cli/cmd/generate-bindings"
2020
"github.com/smartcontractkit/cre-cli/cmd/login"
2121
"github.com/smartcontractkit/cre-cli/cmd/logout"
22+
"github.com/smartcontractkit/cre-cli/cmd/registry"
2223
"github.com/smartcontractkit/cre-cli/cmd/secrets"
2324
"github.com/smartcontractkit/cre-cli/cmd/templates"
2425
"github.com/smartcontractkit/cre-cli/cmd/update"
@@ -389,17 +390,20 @@ func newRootCommand() *cobra.Command {
389390
whoamiCmd := whoami.New(runtimeContext)
390391
updateCmd := update.New(runtimeContext)
391392
templatesCmd := templates.New(runtimeContext)
393+
registryCmd := registry.New(runtimeContext)
392394

393395
secretsCmd.RunE = helpRunE
394396
workflowCmd.RunE = helpRunE
395397
accountCmd.RunE = helpRunE
396398
templatesCmd.RunE = helpRunE
399+
registryCmd.RunE = helpRunE
397400

398401
// Define groups (order controls display order)
399402
rootCmd.AddGroup(&cobra.Group{ID: "getting-started", Title: "Getting Started"})
400403
rootCmd.AddGroup(&cobra.Group{ID: "account", Title: "Account"})
401404
rootCmd.AddGroup(&cobra.Group{ID: "workflow", Title: "Workflow"})
402405
rootCmd.AddGroup(&cobra.Group{ID: "secret", Title: "Secret"})
406+
rootCmd.AddGroup(&cobra.Group{ID: "registry", Title: "Registry"})
403407

404408
initCmd.GroupID = "getting-started"
405409
templatesCmd.GroupID = "getting-started"
@@ -411,6 +415,7 @@ func newRootCommand() *cobra.Command {
411415

412416
secretsCmd.GroupID = "secret"
413417
workflowCmd.GroupID = "workflow"
418+
registryCmd.GroupID = "registry"
414419

415420
rootCmd.AddCommand(
416421
initCmd,
@@ -421,6 +426,7 @@ func newRootCommand() *cobra.Command {
421426
whoamiCmd,
422427
secretsCmd,
423428
workflowCmd,
429+
registryCmd,
424430
genBindingsCmd,
425431
updateCmd,
426432
templatesCmd,
@@ -457,6 +463,8 @@ func isLoadSettings(cmd *cobra.Command) bool {
457463
"cre templates list": {},
458464
"cre templates add": {},
459465
"cre templates remove": {},
466+
"cre registry": {},
467+
"cre registry list": {},
460468
"cre": {},
461469
}
462470

docs/cre.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ cre [optional flags]
2828
* [cre init](cre_init.md) - Initialize a new cre project (recommended starting point)
2929
* [cre login](cre_login.md) - Start authentication flow
3030
* [cre logout](cre_logout.md) - Revoke authentication tokens and remove local credentials
31+
* [cre registry](cre_registry.md) - Manages workflow registries
3132
* [cre secrets](cre_secrets.md) - Handles secrets management
3233
* [cre templates](cre_templates.md) - Manages template repository sources
3334
* [cre update](cre_update.md) - Update the cre CLI to the latest version

docs/cre_registry.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
## cre registry
2+
3+
Manages workflow registries
4+
5+
### Synopsis
6+
7+
The registry command lets you view and inspect the workflow registries available for your organization.
8+
9+
```
10+
cre registry [optional flags]
11+
```
12+
13+
### Options
14+
15+
```
16+
-h, --help help for registry
17+
```
18+
19+
### Options inherited from parent commands
20+
21+
```
22+
-e, --env string Path to .env file which contains sensitive info
23+
-R, --project-root string Path to the project root
24+
-E, --public-env string Path to .env.public file which contains shared, non-sensitive build config
25+
-T, --target string Use target settings from YAML config
26+
-v, --verbose Run command in VERBOSE mode
27+
```
28+
29+
### SEE ALSO
30+
31+
* [cre](cre.md) - CRE CLI tool
32+
* [cre registry list](cre_registry_list.md) - Lists available workflow registries for the current environment
33+

docs/cre_registry_list.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
## cre registry list
2+
3+
Lists available workflow registries for the current environment
4+
5+
### Synopsis
6+
7+
Displays the registries configured for your organization, including type and address.
8+
9+
```
10+
cre registry list [optional flags]
11+
```
12+
13+
### Examples
14+
15+
```
16+
cre registry list
17+
```
18+
19+
### Options
20+
21+
```
22+
-h, --help help for list
23+
```
24+
25+
### Options inherited from parent commands
26+
27+
```
28+
-e, --env string Path to .env file which contains sensitive info
29+
-R, --project-root string Path to the project root
30+
-E, --public-env string Path to .env.public file which contains shared, non-sensitive build config
31+
-T, --target string Use target settings from YAML config
32+
-v, --verbose Run command in VERBOSE mode
33+
```
34+
35+
### SEE ALSO
36+
37+
* [cre registry](cre_registry.md) - Manages workflow registries
38+

0 commit comments

Comments
 (0)