Skip to content

Commit 6ef6875

Browse files
[CLD-586]: feat(engine): migrate loadCatalogStore & loadOffchainClient (#358)
In order to load environment, we need to load the catalog store first and also the offchain client. These will be used when we load environment. - added tests JIRA: https://smartcontract-it.atlassian.net/browse/CLD-586
1 parent 63d8f65 commit 6ef6875

File tree

6 files changed

+348
-0
lines changed

6 files changed

+348
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chainlink-deployments-framework": patch
3+
---
4+
5+
Migrate LoadCatalogStore and LoadJDClient to engine

engine/cld/catalog/catalog.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package catalog
2+
3+
import (
4+
"context"
5+
6+
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
7+
"github.com/smartcontractkit/chainlink-deployments-framework/datastore/catalog/remote"
8+
"github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/domain"
9+
"github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/environment"
10+
"github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/internal"
11+
)
12+
13+
// LoadCatalog loads a catalog data store for the specified domain and environment.
14+
func LoadCatalog(ctx context.Context, env string,
15+
config *environment.Config, domain domain.Domain) (datastore.CatalogStore, error) {
16+
catalogClient, err := loadCatalogClient(ctx, env, config.Env.Catalog.GRPC)
17+
if err != nil {
18+
return nil, err
19+
}
20+
21+
catalogDataStore := remote.NewCatalogDataStore(remote.CatalogDataStoreConfig{
22+
Domain: domain.Key(),
23+
Environment: env,
24+
Client: catalogClient,
25+
})
26+
27+
return catalogDataStore, nil
28+
}
29+
30+
// loadCatalogClient initializes a Catalogue client using the grpc and gap config.
31+
func loadCatalogClient(
32+
ctx context.Context, env string, url string,
33+
) (*remote.CatalogClient, error) {
34+
creds := internal.GetCredsForEnv(env)
35+
36+
client, err := remote.NewCatalogClient(ctx, remote.CatalogConfig{
37+
GRPC: url,
38+
Creds: creds,
39+
})
40+
if err != nil {
41+
return nil, err
42+
}
43+
44+
return client, nil
45+
}

engine/cld/catalog/catalog_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package catalog
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
8+
"github.com/smartcontractkit/chainlink-deployments-framework/datastore/catalog/remote"
9+
config_env "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/config/env"
10+
"github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/domain"
11+
"github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/environment"
12+
)
13+
14+
func TestLoadCatalog(t *testing.T) {
15+
t.Parallel()
16+
17+
tests := []struct {
18+
name string
19+
env string
20+
config *environment.Config
21+
domain domain.Domain
22+
wantErr string
23+
}{
24+
{
25+
name: "successful catalog loading",
26+
env: "testnet",
27+
config: &environment.Config{
28+
Env: &config_env.Config{
29+
Catalog: config_env.CatalogConfig{
30+
GRPC: "localhost:50051",
31+
},
32+
},
33+
},
34+
domain: domain.NewDomain("test-root", "test-domain"),
35+
},
36+
{
37+
name: "valid config with different grpc url",
38+
env: "testnet",
39+
config: &environment.Config{
40+
Env: &config_env.Config{
41+
Catalog: config_env.CatalogConfig{
42+
GRPC: "grpc.example.com:443",
43+
},
44+
},
45+
},
46+
domain: domain.NewDomain("test-root", "test-domain"),
47+
},
48+
}
49+
50+
for _, tt := range tests {
51+
t.Run(tt.name, func(t *testing.T) {
52+
t.Parallel()
53+
54+
ctx := t.Context()
55+
56+
result, err := LoadCatalog(ctx, tt.env, tt.config, tt.domain)
57+
58+
if tt.wantErr != "" {
59+
require.Error(t, err)
60+
require.ErrorContains(t, err, tt.wantErr)
61+
require.Nil(t, result)
62+
} else {
63+
// For successful cases, we expect the function to create a catalog store
64+
// even if it can't connect to the actual gRPC service
65+
require.NoError(t, err)
66+
require.NotNil(t, result)
67+
}
68+
})
69+
}
70+
}
71+
72+
func TestLoadCatalogClient(t *testing.T) {
73+
t.Parallel()
74+
75+
tests := []struct {
76+
name string
77+
env string
78+
url string
79+
wantErr string
80+
}{
81+
{
82+
name: "successful client creation with local env",
83+
env: "local",
84+
url: "localhost:50051",
85+
},
86+
{
87+
name: "successful client creation with non-local env",
88+
env: "testnet",
89+
url: "grpc.example.com:443",
90+
},
91+
{
92+
name: "empty url - should still create client",
93+
env: "testnet",
94+
url: "",
95+
},
96+
}
97+
98+
for _, tt := range tests {
99+
t.Run(tt.name, func(t *testing.T) {
100+
t.Parallel()
101+
102+
ctx := t.Context()
103+
104+
result, err := loadCatalogClient(ctx, tt.env, tt.url)
105+
106+
if tt.wantErr != "" {
107+
require.Error(t, err)
108+
require.ErrorContains(t, err, tt.wantErr)
109+
require.Nil(t, result)
110+
} else {
111+
// For successful cases, we expect a client to be created
112+
// even if it can't actually connect to the service
113+
require.NoError(t, err)
114+
require.NotNil(t, result)
115+
require.IsType(t, &remote.CatalogClient{}, result)
116+
}
117+
})
118+
}
119+
}

engine/cld/internal/credentials.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package internal
2+
3+
import (
4+
"crypto/tls"
5+
6+
"google.golang.org/grpc/credentials"
7+
"google.golang.org/grpc/credentials/insecure"
8+
9+
"github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/environment"
10+
)
11+
12+
// GetCredsForEnv returns the appropriate gRPC transport credentials based on the environment.
13+
func GetCredsForEnv(env string) credentials.TransportCredentials {
14+
if env == environment.Local {
15+
return insecure.NewCredentials()
16+
}
17+
18+
return credentials.NewTLS(&tls.Config{
19+
MinVersion: tls.VersionTLS12,
20+
})
21+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package internal
2+
3+
import (
4+
"crypto/tls"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
"google.golang.org/grpc/credentials"
10+
"google.golang.org/grpc/credentials/insecure"
11+
12+
"github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/environment"
13+
)
14+
15+
func TestGetCredsForEnv(t *testing.T) {
16+
t.Parallel()
17+
18+
tests := []struct {
19+
name string
20+
env string
21+
wantInsecure bool
22+
wantTLS bool
23+
}{
24+
{
25+
name: "local environment returns insecure credentials",
26+
env: environment.Local,
27+
wantInsecure: true,
28+
wantTLS: false,
29+
},
30+
{
31+
name: "empty environment returns TLS credentials",
32+
env: "",
33+
wantInsecure: false,
34+
wantTLS: true,
35+
},
36+
{
37+
name: "non local environment returns TLS credentials",
38+
env: "custom-env",
39+
wantInsecure: false,
40+
wantTLS: true,
41+
},
42+
}
43+
44+
for _, tt := range tests {
45+
t.Run(tt.name, func(t *testing.T) {
46+
t.Parallel()
47+
48+
creds := GetCredsForEnv(tt.env)
49+
require.NotNil(t, creds)
50+
51+
if tt.wantInsecure {
52+
// For insecure credentials, check that it's the insecure type
53+
insecureCreds := insecure.NewCredentials()
54+
assert.IsType(t, insecureCreds, creds)
55+
}
56+
57+
if tt.wantTLS {
58+
// For TLS credentials, verify it's a TLS type and check configuration
59+
info := creds.Info()
60+
assert.Equal(t, "tls", info.SecurityProtocol)
61+
62+
// Create a reference TLS credential to compare behavior
63+
expectedCreds := credentials.NewTLS(&tls.Config{
64+
MinVersion: tls.VersionTLS12,
65+
})
66+
expectedInfo := expectedCreds.Info()
67+
68+
// Both should have the same security protocol and server name behavior
69+
assert.Equal(t, expectedInfo.SecurityProtocol, info.SecurityProtocol)
70+
assert.Equal(t, expectedInfo.ServerName, info.ServerName)
71+
}
72+
})
73+
}
74+
}

engine/cld/offchain/offchain.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package offchain
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/smartcontractkit/chainlink-common/pkg/logger"
8+
csav1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/csa"
9+
"golang.org/x/oauth2"
10+
11+
cldf_config_env "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/config/env"
12+
cldf_domain "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/domain"
13+
"github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/internal"
14+
cldf_offchain "github.com/smartcontractkit/chainlink-deployments-framework/offchain"
15+
offchain_jd "github.com/smartcontractkit/chainlink-deployments-framework/offchain/jd"
16+
offchain_jd_provider "github.com/smartcontractkit/chainlink-deployments-framework/offchain/jd/provider"
17+
)
18+
19+
// LoadOffchainClient loads an offchain client for the specified domain and environment.
20+
func LoadOffchainClient(
21+
ctx context.Context,
22+
domain cldf_domain.Domain,
23+
env string,
24+
config *cldf_config_env.Config,
25+
lggr logger.Logger,
26+
useRealBackends bool,
27+
) (cldf_offchain.Client, error) {
28+
var jd cldf_offchain.Client
29+
30+
endpoints := config.Offchain.JobDistributor.Endpoints
31+
auth := config.Offchain.JobDistributor.Auth
32+
33+
if domain.Key() == cldf_domain.MustGetDomain("keystone").Key() && endpoints.WSRPC == "" {
34+
lggr.Warn("Skipping JD initialization for Keystone, fallback to CLO data")
35+
} else if endpoints.WSRPC == "" || endpoints.GRPC == "" {
36+
lggr.Warn("Skipping JD initialization no JD config for WS or gRPC")
37+
} else if endpoints.WSRPC != "" && endpoints.GRPC != "" {
38+
lggr.Info("Initializing JD client")
39+
var oauth oauth2.TokenSource
40+
if config.Offchain.JobDistributor.Auth != nil {
41+
source := offchain_jd.NewCognitoTokenSource(offchain_jd.CognitoAuth{
42+
AppClientID: auth.CognitoAppClientID,
43+
AppClientSecret: auth.CognitoAppClientSecret,
44+
Username: auth.Username,
45+
Password: auth.Password,
46+
AWSRegion: auth.AWSRegion,
47+
})
48+
if err := source.Authenticate(ctx); err != nil {
49+
return nil, err
50+
}
51+
52+
oauth = source
53+
}
54+
creds := internal.GetCredsForEnv(env)
55+
56+
var offchainOptions []offchain_jd_provider.ClientProviderOption
57+
if !useRealBackends {
58+
lggr.Infow("Using a dry-run JD client")
59+
offchainOptions = append(offchainOptions, offchain_jd_provider.WithDryRun(lggr))
60+
}
61+
62+
provider := offchain_jd_provider.NewClientOffchainProvider(offchain_jd_provider.ClientOffchainProviderConfig{
63+
GRPC: endpoints.GRPC,
64+
WSRPC: endpoints.WSRPC,
65+
Creds: creds,
66+
Auth: oauth,
67+
}, offchainOptions...)
68+
69+
var err error
70+
jd, err = provider.Initialize(ctx)
71+
if err != nil {
72+
return nil, err
73+
}
74+
75+
var kp *csav1.ListKeypairsResponse
76+
kp, err = jd.ListKeypairs(ctx, &csav1.ListKeypairsRequest{})
77+
if err != nil {
78+
return jd, fmt.Errorf("unable to reach the JD instance %s. Are you on the VPN? %w", endpoints.GRPC, err)
79+
}
80+
lggr.Debugw("JD CSA Key", "key", kp.Keypairs[0].PublicKey)
81+
}
82+
83+
return jd, nil
84+
}

0 commit comments

Comments
 (0)