From b8f8ca0a69d38bf46a8d6774d8b1effb1b3289d2 Mon Sep 17 00:00:00 2001 From: giogam <151543+giogam@users.noreply.github.com> Date: Mon, 1 Jun 2026 18:06:22 +0200 Subject: [PATCH] feat: add datastore chain metadata changeset --- catalog/changesets/delete_chain_metadata.go | 59 +++++++++ .../changesets/delete_chain_metadata_test.go | 118 ++++++++++++++++++ catalog/operations/delete_chain_metadata.go | 49 ++++++++ 3 files changed, 226 insertions(+) create mode 100644 catalog/changesets/delete_chain_metadata.go create mode 100644 catalog/changesets/delete_chain_metadata_test.go create mode 100644 catalog/operations/delete_chain_metadata.go diff --git a/catalog/changesets/delete_chain_metadata.go b/catalog/changesets/delete_chain_metadata.go new file mode 100644 index 0000000..c148d45 --- /dev/null +++ b/catalog/changesets/delete_chain_metadata.go @@ -0,0 +1,59 @@ +package changesets + +import ( + "errors" + "fmt" + + cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore" + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + cldfops "github.com/smartcontractkit/chainlink-deployments-framework/operations" + + "github.com/smartcontractkit/cld-changesets/catalog/operations" +) + +// DeleteChainMetadataChangeset deletes chain metadata entries from the Catalog service. +type DeleteChainMetadataChangeset struct{} + +type DeleteChainMetadataChangesetInput struct { + ChainMetadataKeys []cldfdatastore.ChainMetadataKey `json:"chainMetadataKeys"` +} + +// VerifyPreconditions ensures the input is valid. +func (DeleteChainMetadataChangeset) VerifyPreconditions(e cldf.Environment, input DeleteChainMetadataChangesetInput) error { + if len(input.ChainMetadataKeys) == 0 { + return errors.New("missing chain metadata keys input") + } + if e.DataStore == nil { + return errors.New("missing datastore in environment") + } + + for _, key := range input.ChainMetadataKeys { + _, err := e.DataStore.ChainMetadata().Get(key) + if err != nil { + if errors.Is(err, cldfdatastore.ErrChainMetadataNotFound) { + return fmt.Errorf("chain metadata entry for chain selector %v does not exist", key.ChainSelector()) + } + + return fmt.Errorf("failed to retrieve chain metadata entry for chain selector %v: %w", key.ChainSelector(), err) + } + } + + return nil +} + +// Apply executes the changeset, staging the chain metadata entries to be deleted from the Catalog service or local datastore files. +func (DeleteChainMetadataChangeset) Apply(e cldf.Environment, input DeleteChainMetadataChangesetInput) (cldf.ChangesetOutput, error) { + deps := operations.DeleteChainMetadataDeps{DataStore: e.DataStore} + opInput := operations.DeleteChainMetadataInput{ChainMetadataKeys: input.ChainMetadataKeys} + + report, err := cldfops.ExecuteOperation(e.OperationsBundle, operations.DeleteChainMetadataOp, deps, opInput) + out := cldf.ChangesetOutput{ + DataStore: report.Output.DataStore, + Reports: []cldfops.Report[any, any]{report.ToGenericReport()}, + } + if err != nil { + return out, err + } + + return out, nil +} diff --git a/catalog/changesets/delete_chain_metadata_test.go b/catalog/changesets/delete_chain_metadata_test.go new file mode 100644 index 0000000..6f1923b --- /dev/null +++ b/catalog/changesets/delete_chain_metadata_test.go @@ -0,0 +1,118 @@ +package changesets + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore" + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" + cldfoperations "github.com/smartcontractkit/chainlink-deployments-framework/operations" + cldflogger "github.com/smartcontractkit/chainlink-deployments-framework/pkg/logger" +) + +func TestDeleteChainMetadataChangeset_VerifyPreconditions(t *testing.T) { + t.Parallel() + + chainMetadata1 := cldfdatastore.ChainMetadata{ChainSelector: 1234, Metadata: "value1"} + chainMetadata2 := cldfdatastore.ChainMetadata{ChainSelector: 5678, Metadata: "value2"} + + tests := []struct { + name string + env cldf.Environment + input DeleteChainMetadataChangesetInput + wantErr string + }{ + { + name: "success: valid preconditions", + env: cldf.Environment{ + DataStore: testDataStoreWithChainMetadata(t, chainMetadata1, chainMetadata2).Seal(), + }, + input: DeleteChainMetadataChangesetInput{ + ChainMetadataKeys: []cldfdatastore.ChainMetadataKey{chainMetadata1.Key(), chainMetadata2.Key()}, + }, + }, + { + name: "failure: missing datastore", + env: cldf.Environment{}, + input: DeleteChainMetadataChangesetInput{ + ChainMetadataKeys: []cldfdatastore.ChainMetadataKey{chainMetadata1.Key()}, + }, + wantErr: "missing datastore in environment", + }, + { + name: "failure: no chain metadata keys given", + env: cldf.Environment{ + DataStore: cldfdatastore.NewMemoryDataStore().Seal(), + }, + input: DeleteChainMetadataChangesetInput{ChainMetadataKeys: []cldfdatastore.ChainMetadataKey{}}, + wantErr: "missing chain metadata keys input", + }, + { + name: "failure: chain metadata entry does not exist", + env: cldf.Environment{ + DataStore: cldfdatastore.NewMemoryDataStore().Seal(), + }, + input: DeleteChainMetadataChangesetInput{ChainMetadataKeys: []cldfdatastore.ChainMetadataKey{chainMetadata2.Key()}}, + wantErr: fmt.Sprintf("chain metadata entry for chain selector %v does not exist", chainMetadata2.ChainSelector), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + err := DeleteChainMetadataChangeset{}.VerifyPreconditions(tt.env, tt.input) + if tt.wantErr == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, tt.wantErr) + } + }) + } +} + +func TestDeleteChainMetadataChangeset_Apply(t *testing.T) { + t.Parallel() + + chainMetadata1 := cldfdatastore.ChainMetadata{ChainSelector: 1234, Metadata: "value1"} + chainMetadata2 := cldfdatastore.ChainMetadata{ChainSelector: 5678, Metadata: "value2"} + + tests := []struct { + name string + env cldf.Environment + input DeleteChainMetadataChangesetInput + wantDeletedKeys []string + wantErr string + }{ + { + name: "success: stages two chain metadata entries for deletion", + env: cldf.Environment{ + DataStore: testDataStoreWithChainMetadata(t, chainMetadata1, chainMetadata2).Seal(), + OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()), + }, + input: DeleteChainMetadataChangesetInput{ + ChainMetadataKeys: []cldfdatastore.ChainMetadataKey{chainMetadata1.Key(), chainMetadata2.Key()}, + }, + wantDeletedKeys: []string{chainMetadata1.Key().String(), chainMetadata2.Key().String()}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got, err := DeleteChainMetadataChangeset{}.Apply(tt.env, tt.input) + + if tt.wantErr == "" { + require.NoError(t, err) + require.Len(t, got.Reports, 1) + memDS := got.DataStore.(*cldfdatastore.MemoryDataStore) + require.ElementsMatch(t, tt.wantDeletedKeys, memDS.ChainMetadataStore.DeletedRemoteKeys) + } else { + require.ErrorContains(t, err, tt.wantErr) + } + }) + } +} diff --git a/catalog/operations/delete_chain_metadata.go b/catalog/operations/delete_chain_metadata.go new file mode 100644 index 0000000..b6740b2 --- /dev/null +++ b/catalog/operations/delete_chain_metadata.go @@ -0,0 +1,49 @@ +package operations + +import ( + "fmt" + + "github.com/Masterminds/semver/v3" + cldfdatastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore" + cldfops "github.com/smartcontractkit/chainlink-deployments-framework/operations" +) + +// DeleteChainMetadataDeps holds non-serializable dependencies for the DeleteChainMetadataOp operation. +type DeleteChainMetadataDeps struct { + DataStore cldfdatastore.DataStore +} + +// DeleteChainMetadataInput is the serializable input of a DeleteChainMetadataOp invocation. +type DeleteChainMetadataInput struct { + ChainMetadataKeys []cldfdatastore.ChainMetadataKey +} + +// DeleteChainMetadataOutput is the serializable output of a DeleteChainMetadataOp invocation. +type DeleteChainMetadataOutput struct { + DataStore cldfdatastore.MutableDataStore +} + +// DeleteChainMetadataOp deletes chain metadata entries from the Catalog service or local datastore files. +var DeleteChainMetadataOp = cldfops.NewOperation( + "datastore-delete-chain-metadata", + semver.MustParse("1.0.0"), + "Delete chain metadata entries from the Catalog service or local datastore files", + func(b cldfops.Bundle, deps DeleteChainMetadataDeps, input DeleteChainMetadataInput) (DeleteChainMetadataOutput, error) { + dataStore := cldfdatastore.NewMemoryDataStore() + err := dataStore.Merge(deps.DataStore) + if err != nil { + return DeleteChainMetadataOutput{}, fmt.Errorf("failed to create memory data store: %w", err) + } + + for i, key := range input.ChainMetadataKeys { + err = dataStore.ChainMetadata().RemoteDelete(key) + if err != nil { + return DeleteChainMetadataOutput{}, fmt.Errorf("failed to delete chain metadata entry %d in datastore: %w", i, err) + } + } + + b.Logger.Infow("Catalog ChainMetadata successfully staged for deletion") + + return DeleteChainMetadataOutput{DataStore: dataStore}, nil + }, +)