Repository navigation
Add CRE Storage Service docker compose component #2869
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
08054c3
Add CRE Storage Service docker compose component
akcll 3111994
Address bot review comments
akcll d7385e0
updates from review
akcll d10429a
Disable TestLocalS3: MinIO images are no longer publicly available
akcll 292c7b6
add changeset file
akcll 6c71876
Skip TestLocalS3 in code instead of removing it from the required matrix
akcll f966235
Merge branch 'main' into axel/DEVSVCS-6421-cre-storage-component
akcll File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
book/src/framework/components/crestorageservice/cre_storage_service.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # CRE Storage Service | ||
|
|
||
| CRE Storage Service is a composite component that runs [cre-storage-service](https://github.com/smartcontractkit/cre-storage-service) with local dependencies (Postgres, LocalStack S3, a mock account registry). It is a thin `testcontainers-go` wrapper over the compose file that cre-storage-service owns, `local-stack/compose.yaml`; that file's README describes its services, images and inputs. | ||
|
|
||
| The stack reads the nodes it authorizes from a CapabilitiesRegistry, so it needs a chain with one deployed, reachable from the stack's Docker network. | ||
|
|
||
| ## Configuration | ||
|
|
||
| ```toml | ||
| [cre_storage_service] | ||
| # file://<local path>, or an http(s):// URL readable without credentials | ||
| compose_file = "file://../cre-storage-service/local-stack/compose.yaml" | ||
| # existing Docker network to join, defaults to "ctf" | ||
| # network = "ctf" | ||
| registry_rpc_url = "http://anvil-1337:8545" | ||
| registry_chain_selector = 3379446385462418246 | ||
| registry_address = "0x5FbDB2315678afecb367f032d93F642f64180aa3" | ||
| don_family = "zone-a" | ||
| ``` | ||
|
|
||
| Images default to locally built `:local` tags; `CRE_STORAGE_IMAGE`, `CRE_STORAGE_MIGRATION_IMAGE` and `MOCK_ACCOUNT_REGISTRY_IMAGE` env vars override them, and `LOG_LEVEL` sets the services' log level. | ||
|
|
||
| ## Endpoints | ||
|
|
||
| On the Docker network the services answer to role aliases; `Output` also gives host URLs on free ports. gRPC is served without TLS. | ||
|
|
||
| | Output | Role (alias) | Port | Purpose | | ||
| | --- | --- | --- | --- | | ||
| | `UserService` | `cre-deploy-api` | 50051 | workflow/artifact API for HMAC principals | | ||
| | `NodeService` | `cre-workflow-source`, `cre-artifact-api` | 50052 | node API (CSA key auth): workflow metadata, artifact URLs | | ||
| | `ArtifactStore` | `cre-artifact-store` | 4566 | S3; artifact URLs use the internal alias | | ||
|
|
||
| ## Usage | ||
|
|
||
| ```go | ||
| import storage "github.com/smartcontractkit/chainlink-testing-framework/framework/components/dockercompose/cre_storage_service" | ||
|
|
||
| out, err := storage.New(in.CREStorageService) | ||
| require.NoError(t, err) | ||
| // out.UserService.ExternalURL, out.NodeService.InternalURL, ... | ||
|
|
||
| // removes every stack started by New, with its volumes | ||
| require.NoError(t, storage.Stop()) | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| - Add CRE Storage Service component (`cre_storage_service`), a wrapper over cre-storage-service's `local-stack/compose.yaml` |
231 changes: 231 additions & 0 deletions
231
framework/components/dockercompose/cre_storage_service/cre_storage_service.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,231 @@ | ||
| package cre_storage_service | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "os" | ||
| "strconv" | ||
| "strings" | ||
| "time" | ||
|
|
||
| "github.com/pkg/errors" | ||
| "github.com/testcontainers/testcontainers-go/modules/compose" | ||
|
|
||
| "github.com/smartcontractkit/freeport" | ||
|
|
||
| "github.com/smartcontractkit/chainlink-testing-framework/framework" | ||
| "github.com/smartcontractkit/chainlink-testing-framework/framework/components/dockercompose/utils" | ||
| ) | ||
|
|
||
| // Roles the stack's services answer to on the Docker network, as defined by the compose file in cre-storage-service. | ||
| const ( | ||
| DeployAPIRole = "cre-deploy-api" | ||
| WorkflowSourceRole = "cre-workflow-source" | ||
| ArtifactAPIRole = "cre-artifact-api" | ||
| ArtifactStoreRole = "cre-artifact-store" | ||
|
|
||
| UserServicePort = "50051" | ||
| NodeServicePort = "50052" | ||
| ArtifactStorePort = "4566" | ||
| ) | ||
|
|
||
| const ( | ||
| // not a prefix of other stacks' names, e.g. cre-storage-service's standalone cre-storage-local, which Stop would remove | ||
| DEFAULT_STACK_NAME = "ctf-cre-storage" | ||
|
|
||
| userServiceName = "storage-user-service" | ||
| nodeServiceName = "storage-node-service" | ||
| artifactStoreName = "storage-s3" | ||
| ) | ||
|
|
||
| type Input struct { | ||
| // ComposeFile is local-stack/compose.yaml from cre-storage-service: a local path as file://<path>, or an http(s):// | ||
| // URL readable without credentials. | ||
| ComposeFile string `toml:"compose_file"` | ||
| // Network the stack joins; it must exist. Defaults to framework.DefaultNetworkName. | ||
| Network string `toml:"network"` | ||
| // RegistryRPCURL, RegistryChainSelector and RegistryAddress locate the CapabilitiesRegistry the stack reads tenants' | ||
| // nodes from; the RPC URL must be reachable from Network. | ||
| RegistryRPCURL string `toml:"registry_rpc_url"` | ||
| RegistryChainSelector uint64 `toml:"registry_chain_selector"` | ||
| RegistryAddress string `toml:"registry_address"` | ||
| DonFamily string `toml:"don_family"` | ||
| Output *Output `toml:"output"` | ||
| UseCache bool `toml:"use_cache"` | ||
| } | ||
|
|
||
| type Output struct { | ||
| // UserService serves cre-deploy-api (gRPC, HMAC auth) | ||
| UserService *Endpoint `toml:"user_service"` | ||
| // NodeService serves cre-workflow-source and cre-artifact-api (gRPC, CSA key auth) | ||
| NodeService *Endpoint `toml:"node_service"` | ||
| // ArtifactStore serves cre-artifact-store (S3); artifact URLs use the internal one | ||
| ArtifactStore *Endpoint `toml:"artifact_store"` | ||
| } | ||
|
|
||
| // Endpoint is reachable at InternalURL from the stack's network and at ExternalURL from the host. gRPC endpoints are | ||
| // host:port, without TLS. | ||
| type Endpoint struct { | ||
| InternalURL string `toml:"internal_url"` | ||
| ExternalURL string `toml:"external_url"` | ||
| } | ||
|
|
||
| // New starts cre-storage-service's local stack from its compose file. Image overrides (CRE_STORAGE_IMAGE, | ||
| // CRE_STORAGE_MIGRATION_IMAGE, MOCK_ACCOUNT_REGISTRY_IMAGE) and LOG_LEVEL are taken from the host process env vars. | ||
| func New(in *Input) (*Output, error) { | ||
| return NewWithContext(context.Background(), in) | ||
| } | ||
|
|
||
| func NewWithContext(ctx context.Context, in *Input) (*Output, error) { | ||
| if in == nil { | ||
| return nil, errors.New("input is nil") | ||
| } | ||
| if in.UseCache && in.Output != nil { | ||
| return in.Output, nil | ||
| } | ||
| if err := validate(in); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| composeFile, err := composeFilePath(in.ComposeFile) | ||
| if err != nil { | ||
| return nil, errors.Wrap(err, "failed to get compose file path") | ||
| } | ||
|
|
||
| identifier := framework.DefaultTCName(DEFAULT_STACK_NAME) | ||
| stack, err := compose.NewDockerComposeWith( | ||
| compose.WithStackFiles(composeFile), | ||
| compose.StackIdentifier(identifier), | ||
| ) | ||
| if err != nil { | ||
| return nil, errors.Wrap(err, "failed to create compose stack for CRE Storage Service") | ||
| } | ||
|
|
||
| hostPorts, err := freeport.Take(3) | ||
| if err != nil { | ||
| return nil, errors.Wrap(err, "failed to get free ports for CRE Storage Service") | ||
| } | ||
|
|
||
| ctx, cancel := context.WithTimeout(ctx, 5*time.Minute) | ||
| defer cancel() | ||
|
|
||
| // down removes the stack after a failed start, without ctx's deadline or cancellation, which may be what failed it | ||
| down := func() { | ||
| downCtx, downCancel := context.WithTimeout(context.WithoutCancel(ctx), time.Minute) | ||
| defer downCancel() | ||
| if downErr := stack.Down(downCtx, compose.RemoveOrphans(true), compose.RemoveVolumes(true)); downErr != nil { | ||
| framework.L.Warn().Err(downErr).Msgf("failed to remove CRE Storage Service stack %s", identifier) | ||
| } | ||
| } | ||
|
|
||
| framework.L.Info().Str("Compose file", composeFile).Msgf("Starting CRE Storage Service stack %s", identifier) | ||
| if err := stack.WithEnv(env(in, os.Environ(), hostPorts)).Up(ctx, compose.Wait(true)); err != nil { | ||
| down() | ||
| return nil, errors.Wrap(err, "failed to start stack for CRE Storage Service") | ||
| } | ||
|
|
||
| external := func(service, port string) (string, error) { | ||
|
akcll marked this conversation as resolved.
Resolved
|
||
| c, err := stack.ServiceContainer(ctx, service) | ||
| if err != nil { | ||
| return "", errors.Wrapf(err, "failed to get %s container", service) | ||
| } | ||
| host, err := utils.GetContainerHost(ctx, c) | ||
| if err != nil { | ||
| return "", errors.Wrapf(err, "failed to get host for %s", service) | ||
| } | ||
| mapped, err := utils.FindMappedPort(ctx, 20*time.Second, c, port+"/tcp") | ||
| if err != nil { | ||
| return "", errors.Wrapf(err, "failed to get mapped port for %s", service) | ||
| } | ||
| return fmt.Sprintf("%s:%s", host, mapped.Port()), nil | ||
| } | ||
|
|
||
| userExternal, err := external(userServiceName, UserServicePort) | ||
| if err != nil { | ||
| down() | ||
| return nil, err | ||
| } | ||
| nodeExternal, err := external(nodeServiceName, NodeServicePort) | ||
| if err != nil { | ||
| down() | ||
| return nil, err | ||
| } | ||
| storeExternal, err := external(artifactStoreName, ArtifactStorePort) | ||
| if err != nil { | ||
| down() | ||
| return nil, err | ||
| } | ||
|
akcll marked this conversation as resolved.
Resolved
|
||
|
|
||
| in.Output = &Output{ | ||
| UserService: &Endpoint{InternalURL: DeployAPIRole + ":" + UserServicePort, ExternalURL: userExternal}, | ||
| NodeService: &Endpoint{InternalURL: WorkflowSourceRole + ":" + NodeServicePort, ExternalURL: nodeExternal}, | ||
| ArtifactStore: &Endpoint{InternalURL: "http://" + ArtifactStoreRole + ":" + ArtifactStorePort, ExternalURL: "http://" + storeExternal}, | ||
| } | ||
| framework.L.Info().Msg("CRE Storage Service stack started") | ||
| return in.Output, nil | ||
| } | ||
|
|
||
| // Stop removes every CRE Storage Service stack started by New, with its volumes. | ||
| func Stop() error { | ||
| return framework.RemoveTestStack(DEFAULT_STACK_NAME) | ||
| } | ||
|
|
||
| // composeFilePath returns a local path to the compose file, downloading it if it's a URL. | ||
| func composeFilePath(uri string) (string, error) { | ||
| if path, ok := strings.CutPrefix(uri, "file://"); ok { | ||
| return path, nil | ||
| } | ||
| if strings.HasPrefix(uri, "http://") || strings.HasPrefix(uri, "https://") { | ||
| return utils.ComposeFilePath(uri, DEFAULT_STACK_NAME) | ||
| } | ||
| return "", fmt.Errorf("compose_file must start with 'file://', 'http://' or 'https://', but '%s' was found", uri) | ||
| } | ||
|
|
||
| func validate(in *Input) error { | ||
| var missing []string | ||
| if in.ComposeFile == "" { | ||
| missing = append(missing, "compose_file") | ||
| } | ||
| if in.RegistryRPCURL == "" { | ||
| missing = append(missing, "registry_rpc_url") | ||
| } | ||
| if in.RegistryChainSelector == 0 { | ||
| missing = append(missing, "registry_chain_selector") | ||
| } | ||
| if in.RegistryAddress == "" { | ||
| missing = append(missing, "registry_address") | ||
| } | ||
| if in.DonFamily == "" { | ||
| missing = append(missing, "don_family") | ||
| } | ||
| if len(missing) > 0 { | ||
| return fmt.Errorf("CRE Storage Service input is missing %s", strings.Join(missing, ", ")) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // env passes the host process env vars through, so they can override images and log level, and sets the inputs and | ||
| // host ports over them. | ||
| func env(in *Input, hostEnv []string, hostPorts []int) map[string]string { | ||
| vars := make(map[string]string) | ||
| for _, kv := range hostEnv { | ||
| if k, v, ok := strings.Cut(kv, "="); ok { | ||
| vars[k] = v | ||
| } | ||
| } | ||
|
|
||
| network := in.Network | ||
| if network == "" { | ||
| network = framework.DefaultNetworkName | ||
| } | ||
| vars["CRE_NETWORK"] = network | ||
| vars["CRE_NETWORK_EXTERNAL"] = "true" | ||
| vars["CRE_LOCAL_REGISTRY_RPC_URL"] = in.RegistryRPCURL | ||
| vars["CRE_LOCAL_REGISTRY_CHAIN_SELECTOR"] = strconv.FormatUint(in.RegistryChainSelector, 10) | ||
| vars["CRE_LOCAL_REGISTRY_ADDRESS"] = in.RegistryAddress | ||
| vars["CRE_LOCAL_DON_FAMILY"] = in.DonFamily | ||
| vars["USER_SERVICE_HOST_PORT"] = strconv.Itoa(hostPorts[0]) | ||
| vars["NODE_SERVICE_HOST_PORT"] = strconv.Itoa(hostPorts[1]) | ||
| vars["S3_HOST_PORT"] = strconv.Itoa(hostPorts[2]) | ||
| return vars | ||
| } | ||
53 changes: 53 additions & 0 deletions
53
framework/components/dockercompose/cre_storage_service/cre_storage_service_test.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package cre_storage_service | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestEnv(t *testing.T) { | ||
| in := &Input{ | ||
| RegistryRPCURL: "http://anvil-1337:8545", | ||
| RegistryChainSelector: 3379446385462418246, | ||
| RegistryAddress: "0xabc", | ||
| DonFamily: "test-don-family", | ||
| } | ||
| vars := env(in, []string{"CRE_STORAGE_IMAGE=custom:tag", "CRE_LOCAL_DON_FAMILY=from-host", "MALFORMED"}, []int{1, 2, 3}) | ||
|
|
||
| require.Equal(t, "custom:tag", vars["CRE_STORAGE_IMAGE"]) | ||
| require.Equal(t, "test-don-family", vars["CRE_LOCAL_DON_FAMILY"]) | ||
| require.Equal(t, "ctf", vars["CRE_NETWORK"]) | ||
| require.Equal(t, "true", vars["CRE_NETWORK_EXTERNAL"]) | ||
| require.Equal(t, "http://anvil-1337:8545", vars["CRE_LOCAL_REGISTRY_RPC_URL"]) | ||
| require.Equal(t, "3379446385462418246", vars["CRE_LOCAL_REGISTRY_CHAIN_SELECTOR"]) | ||
| require.Equal(t, "0xabc", vars["CRE_LOCAL_REGISTRY_ADDRESS"]) | ||
| require.Equal(t, "1", vars["USER_SERVICE_HOST_PORT"]) | ||
| require.Equal(t, "2", vars["NODE_SERVICE_HOST_PORT"]) | ||
| require.Equal(t, "3", vars["S3_HOST_PORT"]) | ||
| require.NotContains(t, vars, "MALFORMED") | ||
|
|
||
| in.Network = "custom" | ||
| require.Equal(t, "custom", env(in, nil, []int{1, 2, 3})["CRE_NETWORK"]) | ||
| } | ||
|
|
||
| func TestNewRejectsIncompleteInput(t *testing.T) { | ||
| _, err := New(&Input{ComposeFile: "file://compose.yaml", RegistryChainSelector: 1}) | ||
| require.ErrorContains(t, err, "missing registry_rpc_url, registry_address, don_family") | ||
|
|
||
| _, err = New(nil) | ||
| require.ErrorContains(t, err, "input is nil") | ||
| } | ||
|
|
||
| func TestComposeFilePath(t *testing.T) { | ||
| path, err := composeFilePath("file://../cre-storage-service/local-stack/compose.yaml") | ||
| require.NoError(t, err) | ||
| require.Equal(t, "../cre-storage-service/local-stack/compose.yaml", path) | ||
|
|
||
| path, err = composeFilePath("file:///abs/compose.yaml") | ||
| require.NoError(t, err) | ||
| require.Equal(t, "/abs/compose.yaml", path) | ||
|
|
||
| _, err = composeFilePath("../cre-storage-service/local-stack/compose.yaml") | ||
| require.ErrorContains(t, err, "must start with 'file://', 'http://' or 'https://'") | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.