Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions internal/registrytype/registrytype.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package registrytype

import (
"fmt"

"github.com/rs/zerolog"
)

// Type is the normalised registry type stored in context.yaml.
type Type string

const (
OnChain Type = "on-chain"
OffChain Type = "off-chain"
Unknown Type = "unknown"
)

const (
GQLOnChain = "ON_CHAIN"
GQLOffChain = "OFF_CHAIN"
)

// FromGQL maps a GraphQL registry type to the normalised context.yaml value.
func FromGQL(gqlType string, log *zerolog.Logger) Type {
switch gqlType {
case GQLOnChain:
return OnChain
case GQLOffChain:
return OffChain
default:
log.Warn().Str("type", gqlType).Msg("unrecognised registry type from server")
return Unknown
}
}

// Parse converts a raw type string from context.yaml to a Type.
// Only the canonical values written by FromGQL are accepted; anything else errors.
func Parse(raw string) (Type, error) {
switch Type(raw) {
case OnChain, OffChain, Unknown:
return Type(raw), nil
default:
return "", fmt.Errorf("unrecognised registry type %q", raw)
}
}
53 changes: 53 additions & 0 deletions internal/registrytype/registrytype_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package registrytype

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/smartcontractkit/cre-cli/internal/testutil"
)

func TestFromGQL(t *testing.T) {
log := testutil.NewTestLogger()
tests := []struct {
input string
want Type
}{
{GQLOnChain, OnChain},
{GQLOffChain, OffChain},
{"FUTURE_TYPE", Unknown},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
assert.Equal(t, tt.want, FromGQL(tt.input, log))
})
}
}

func TestParse(t *testing.T) {
valid := []struct {
input string
want Type
}{
{"on-chain", OnChain},
{"off-chain", OffChain},
{"unknown", Unknown},
}
for _, tt := range valid {
t.Run(tt.input, func(t *testing.T) {
got, err := Parse(tt.input)
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}

invalid := []string{"ON-CHAIN", "OFF-CHAIN", "on_chain", "off_chain", "ON_CHAIN", "OFF_CHAIN", "on-chian", ""}
for _, input := range invalid {
t.Run(input, func(t *testing.T) {
_, err := Parse(input)
assert.Error(t, err)
assert.Contains(t, err.Error(), "unrecognised registry type")
})
}
}
73 changes: 44 additions & 29 deletions internal/settings/registry_resolution.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import (
"strings"

"github.com/smartcontractkit/cre-cli/internal/environments"
"github.com/smartcontractkit/cre-cli/internal/registrytype"
"github.com/smartcontractkit/cre-cli/internal/tenantctx"
)

// RegistryType distinguishes between on-chain and off-chain workflow registries.
type RegistryType string
type RegistryType = registrytype.Type

const (
RegistryTypeOnChain RegistryType = "on-chain"
RegistryTypeOffChain RegistryType = "off-chain"
RegistryTypeOnChain = registrytype.OnChain
RegistryTypeOffChain = registrytype.OffChain
RegistryTypeUnknown = registrytype.Unknown
)

// ResolvedRegistry is the interface implemented by both OnChainRegistry and
Expand Down Expand Up @@ -104,38 +106,47 @@ func ResolveRegistry(
deploymentRegistry, availableIDs(tenantCtx.Registries))
}

if ParseRegistryType(reg.Type) == RegistryTypeOffChain {
return NewOffChainRegistry(reg.ID, EffectiveDonFamily(envSet, tenantCtx)), nil
}

if reg.Address == nil || *reg.Address == "" {
return nil, fmt.Errorf("on-chain registry %q has no address in user context", reg.ID)
}

if reg.ChainSelector == nil {
return nil, fmt.Errorf("on-chain registry %q has no chain_selector in user context", reg.ID)
}
chainName, err := ChainNameFromSelectorString(*reg.ChainSelector)
regType, err := ParseRegistryType(reg.Type)
if err != nil {
return nil, fmt.Errorf("registry %q: %w", reg.ID, err)
}

return NewOnChainRegistry(
reg.ID,
*reg.Address,
chainName,
EffectiveDonFamily(envSet, tenantCtx),
envSet.WorkflowRegistryChainExplorerURL,
), nil
}
switch regType {
case RegistryTypeOffChain:
return NewOffChainRegistry(reg.ID, EffectiveDonFamily(envSet, tenantCtx)), nil
case RegistryTypeOnChain:
if reg.Address == nil || *reg.Address == "" {
return nil, fmt.Errorf("on-chain registry %q has no address in user context", reg.ID)
}

// ParseRegistryType converts a raw type string from user context to a
// RegistryType. Unknown values default to on-chain.
func ParseRegistryType(raw string) RegistryType {
if strings.EqualFold(raw, string(RegistryTypeOffChain)) || strings.EqualFold(raw, "off_chain") {
return RegistryTypeOffChain
if reg.ChainSelector == nil {
return nil, fmt.Errorf("on-chain registry %q has no chain_selector in user context", reg.ID)
}
chainName, err := ChainNameFromSelectorString(*reg.ChainSelector)
if err != nil {
return nil, fmt.Errorf("registry %q: %w", reg.ID, err)
}

return NewOnChainRegistry(
reg.ID,
*reg.Address,
chainName,
EffectiveDonFamily(envSet, tenantCtx),
envSet.WorkflowRegistryChainExplorerURL,
), nil
case RegistryTypeUnknown:
return nil, fmt.Errorf(
"registry %q is not supported by this CLI version (unrecognised type from server); run `cre login` after upgrading or choose a different deployment-registry",
reg.ID,
)
default:
return nil, fmt.Errorf("registry %q: %w", reg.ID, fmt.Errorf("unrecognised registry type %q", regType))
}
return RegistryTypeOnChain
}

// ParseRegistryType converts a raw type string from user context to a RegistryType.
func ParseRegistryType(raw string) (RegistryType, error) {
return registrytype.Parse(raw)
}

func defaultFromEnvironmentSet(envSet *environments.EnvironmentSet, tenantCtx *tenantctx.EnvironmentContext) *OnChainRegistry {
Expand All @@ -160,6 +171,10 @@ func findRegistry(registries []*tenantctx.Registry, id string) *tenantctx.Regist
func availableIDs(registries []*tenantctx.Registry) string {
ids := make([]string, 0, len(registries))
for _, r := range registries {
regType, err := ParseRegistryType(r.Type)
if err != nil || regType == RegistryTypeUnknown {
continue
}
ids = append(ids, r.ID)
}
return strings.Join(ids, ", ")
Expand Down
59 changes: 52 additions & 7 deletions internal/settings/registry_resolution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,21 +164,66 @@ func TestResolveRegistry_OnChainMissingChainSelector(t *testing.T) {
assert.Contains(t, err.Error(), "has no chain_selector")
}

func TestResolveRegistry_UnknownType(t *testing.T) {
ctx := &tenantctx.EnvironmentContext{
DefaultDonFamily: "zone-a",
Registries: []*tenantctx.Registry{
{
ID: "future-registry",
Type: "unknown",
},
},
}
_, err := ResolveRegistry("future-registry", ctx, stagingEnvSet())
assert.Error(t, err)
assert.Contains(t, err.Error(), "future-registry")
assert.Contains(t, err.Error(), "not supported by this CLI version")
}

func TestResolveRegistry_UnknownExcludedFromAvailable(t *testing.T) {
ctx := &tenantctx.EnvironmentContext{
DefaultDonFamily: "zone-a",
Registries: []*tenantctx.Registry{
{
ID: "private",
Type: "off-chain",
},
{
ID: "future-registry",
Type: "unknown",
},
},
}
_, err := ResolveRegistry("does-not-exist", ctx, stagingEnvSet())
assert.Error(t, err)
assert.Contains(t, err.Error(), "not found in user context")
assert.Contains(t, err.Error(), "private")
assert.NotContains(t, err.Error(), "future-registry")
}

func TestParseRegistryType(t *testing.T) {
tests := []struct {
valid := []struct {
input string
want RegistryType
}{
{"on-chain", RegistryTypeOnChain},
{"off-chain", RegistryTypeOffChain},
{"ON-CHAIN", RegistryTypeOnChain},
{"OFF-CHAIN", RegistryTypeOffChain},
{"off_chain", RegistryTypeOffChain},
{"unknown", RegistryTypeOnChain},
{"unknown", RegistryTypeUnknown},
}
for _, tt := range tests {
for _, tt := range valid {
t.Run(tt.input, func(t *testing.T) {
assert.Equal(t, tt.want, ParseRegistryType(tt.input))
got, err := ParseRegistryType(tt.input)
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}

invalid := []string{"ON-CHAIN", "on_chain", "on-chian", ""}
for _, input := range invalid {
t.Run(input, func(t *testing.T) {
_, err := ParseRegistryType(input)
assert.Error(t, err)
assert.Contains(t, err.Error(), "unrecognised registry type")
})
}
}
Expand Down
19 changes: 4 additions & 15 deletions internal/tenantctx/tenantctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/smartcontractkit/cre-cli/internal/creconfig"
"github.com/smartcontractkit/cre-cli/internal/credentials"
"github.com/smartcontractkit/cre-cli/internal/environments"
"github.com/smartcontractkit/cre-cli/internal/registrytype"
)

// ContextFile is the filename for the local registry manifest.
Expand Down Expand Up @@ -107,11 +108,11 @@ func FetchAndWriteContext(ctx context.Context, gqlClient *graphqlclient.Client,

registries := make([]*Registry, 0, len(tc.Registries))
for _, r := range tc.Registries {
regType := mapRegistryType(r.Type, log)
regType := registrytype.FromGQL(r.Type, log)
id := r.ID
label := r.Label

if regType == "on-chain" {
if regType == registrytype.OnChain {
id = "onchain:" + r.ID
if r.Address != nil {
label = fmt.Sprintf("%s (%s)", r.ID, abbreviateAddress(*r.Address))
Expand All @@ -121,7 +122,7 @@ func FetchAndWriteContext(ctx context.Context, gqlClient *graphqlclient.Client,
registries = append(registries, &Registry{
ID: id,
Label: label,
Type: regType,
Type: string(regType),
ChainSelector: r.ChainSelector,
Address: r.Address,
SecretsAuthFlows: mapSecretsAuthFlows(r.SecretsAuthFlows, log),
Expand Down Expand Up @@ -158,18 +159,6 @@ func FetchAndWriteContext(ctx context.Context, gqlClient *graphqlclient.Client,
return writeContextFile(contextMap, log)
}

func mapRegistryType(gqlType string, log *zerolog.Logger) string {
switch gqlType {
case "ON_CHAIN":
return "on-chain"
case "OFF_CHAIN":
return "off-chain"
default:
log.Warn().Str("type", gqlType).Msg("unknown registry type, skipping")
return "unknown"
}
}

func mapSecretsAuthFlows(gqlFlows []string, log *zerolog.Logger) []string {
flows := make([]string, 0, len(gqlFlows))
for _, f := range gqlFlows {
Expand Down
62 changes: 50 additions & 12 deletions internal/tenantctx/tenantctx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,19 +423,57 @@ func TestEnsureContext_DefaultsToProduction(t *testing.T) {

// --- Helper functions ---

func TestMapRegistryType(t *testing.T) {
tests := []struct {
input string
want string
}{
{"ON_CHAIN", "on-chain"},
{"OFF_CHAIN", "off-chain"},
{"UNKNOWN", "unknown"},
func TestFetchAndWriteContext_PersistsUnknownRegistryType(t *testing.T) {
response := map[string]any{
"data": map[string]any{
"getTenantConfig": map[string]any{
"tenantId": "42",
"defaultDonFamily": "zone-a",
"vaultGatewayUrl": "https://gateway.example.com/",
"registries": []any{
map[string]any{
"id": "private",
"label": "Private (Chainlink-hosted)",
"type": "OFF_CHAIN",
"secretsAuthFlows": []any{"BROWSER"},
},
map[string]any{
"id": "future-registry",
"label": "Future Registry",
"type": "FUTURE_TYPE",
"secretsAuthFlows": []any{},
},
},
"forwarders": []any{},
},
},
}
for _, tt := range tests {
if got := mapRegistryType(tt.input, testutil.NewTestLogger()); got != tt.want {
t.Errorf("mapRegistryType(%q) = %q, want %q", tt.input, got, tt.want)
}
srv := newMockGQLServer(t, response)
defer srv.Close()

t.Setenv("HOME", t.TempDir())
log := testutil.NewTestLogger()
client := newGQLClient(t, srv.URL)

if err := FetchAndWriteContext(context.Background(), client, "STAGING", log); err != nil {
t.Fatalf("unexpected error: %v", err)
}

envCtx, err := LoadContext("STAGING")
if err != nil {
t.Fatalf("failed to load written context: %v", err)
}
if len(envCtx.Registries) != 2 {
t.Fatalf("expected 2 registries (including unknown), got %d", len(envCtx.Registries))
}
if envCtx.Registries[0].ID != "private" {
t.Errorf("first registry ID = %q, want %q", envCtx.Registries[0].ID, "private")
}
if envCtx.Registries[1].ID != "future-registry" {
t.Errorf("second registry ID = %q, want %q", envCtx.Registries[1].ID, "future-registry")
}
if envCtx.Registries[1].Type != "unknown" {
t.Errorf("unknown registry type = %q, want %q", envCtx.Registries[1].Type, "unknown")
}
}

Expand Down
Loading