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
8 changes: 8 additions & 0 deletions .changeset/sunny-donkeys-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"chainlink-deployments-framework": patch
---

fix(input): remove handling of object format

object format for input yaml is no longer supported,
only array format is supported.
6 changes: 3 additions & 3 deletions engine/cld/commands/pipeline/input_generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ func TestInputGenerateCmd_WithOutputFile(t *testing.T) {
inputsContent := `environment: testnet
domain: test
changesets:
0001_cs:
payload:
x: 1`
- 0001_cs:
payload:
x: 1`
require.NoError(t, os.WriteFile(filepath.Join(inputsDir, "in.yaml"), []byte(inputsContent), 0o644)) //nolint:gosec

originalWd, _ := os.Getwd()
Expand Down
109 changes: 24 additions & 85 deletions engine/cld/legacy/cli/commands/durable-pipelines.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,37 +322,11 @@ func (c Commands) newDurablePipelineInputGenerate(
}
}

// Resolve every changeset in the file
var orderedChangesets []map[string]any // For both formats to preserve order and duplicates
// Resolve every changeset in the file.
Copy link
Collaborator Author

@graham-chainlink graham-chainlink Mar 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is a duplication of the other changed file generate.go, is it part of the migration being done by Adrian, eventually this file will be deleted in favour of the other one, but i made the same change in both places anyway to be safe.

var orderedChangesets []map[string]any

// Handle both object and array formats for changesets
//nolint:exhaustive // Only handling MappingNode and SequenceNode cases for changesets
//nolint:exhaustive // SequenceNode is the only valid format for changesets
switch dpFile.Changesets.Kind {
case yaml.MappingNode:
// Object format: changesets: { key1: {payload: ...}, key2: {payload: ...} }
orderedChangesets = make([]map[string]any, 0, len(dpFile.Changesets.Content)/2)
// yaml.Node for a mapping has Content with alternating key-value pairs
for i := 0; i < len(dpFile.Changesets.Content); i += 2 {
keyNode := dpFile.Changesets.Content[i]
valueNode := dpFile.Changesets.Content[i+1]

csName := keyNode.Value
resolver, ok := resolverByKey[csName]
if !ok {
resolver = nil // No resolver registered for this changeset
}

resolvedCfg, err2 := resolveChangesetConfig(valueNode, csName, resolver)
if err2 != nil {
return err2
}

// For object format, store each changeset as a separate item (same as array format)
changesetItem := map[string]any{
csName: map[string]any{"payload": resolvedCfg},
}
orderedChangesets = append(orderedChangesets, changesetItem)
}
case yaml.SequenceNode:
// Array format: changesets: [ { key1: {payload: ...} }, { key2: {payload: ...} } ]
orderedChangesets = make([]map[string]any, 0, len(dpFile.Changesets.Content))
Expand Down Expand Up @@ -383,73 +357,38 @@ func (c Commands) newDurablePipelineInputGenerate(
orderedChangesets = append(orderedChangesets, changesetItem)
}
default:
return fmt.Errorf("changesets must be either an object (mapping) or an array (sequence), got %v", dpFile.Changesets.Kind)
return fmt.Errorf("changesets must be an array (sequence), got %v", dpFile.Changesets.Kind)
}

// Build ordered output structure using yaml.Node to preserve order and original format
var changesetsNode *yaml.Node
changesetsNode := &yaml.Node{
Kind: yaml.SequenceNode,
}

if dpFile.Changesets.Kind == yaml.MappingNode {
// Object format: preserve as object
changesetsNode = &yaml.Node{
for _, changesetItem := range orderedChangesets {
// Create a mapping node for each changeset item.
itemNode := &yaml.Node{
Kind: yaml.MappingNode,
}

for _, changesetItem := range orderedChangesets {
// Each changesetItem has one key-value pair
for csName, csConfig := range changesetItem {
// Add key node
keyNode := &yaml.Node{
Kind: yaml.ScalarNode,
Value: csName,
}
changesetsNode.Content = append(changesetsNode.Content, keyNode)

// Add value node
valueNode := &yaml.Node{}
err = valueNode.Encode(csConfig)
if err != nil {
return fmt.Errorf("encode changeset value for %s: %w", csName, err)
}
changesetsNode.Content = append(changesetsNode.Content, valueNode)

break // Only one key-value pair per item
}
}
} else {
// Array format: preserve as array
changesetsNode = &yaml.Node{
Kind: yaml.SequenceNode,
}

for _, changesetItem := range orderedChangesets {
// Create a mapping node for each changeset item
itemNode := &yaml.Node{
Kind: yaml.MappingNode,
// Each changesetItem has one key-value pair.
for csName, csConfig := range changesetItem {
keyNode := &yaml.Node{
Kind: yaml.ScalarNode,
Value: csName,
}
itemNode.Content = append(itemNode.Content, keyNode)

// Each changesetItem has one key-value pair
for csName, csConfig := range changesetItem {
// Add key node
keyNode := &yaml.Node{
Kind: yaml.ScalarNode,
Value: csName,
}
itemNode.Content = append(itemNode.Content, keyNode)

// Add value node
valueNode := &yaml.Node{}
err = valueNode.Encode(csConfig)
if err != nil {
return fmt.Errorf("encode changeset value for %s: %w", csName, err)
}
itemNode.Content = append(itemNode.Content, valueNode)

break // Only one key-value pair per item
valueNode := &yaml.Node{}
err = valueNode.Encode(csConfig)
if err != nil {
return fmt.Errorf("encode changeset value for %s: %w", csName, err)
}
itemNode.Content = append(itemNode.Content, valueNode)

changesetsNode.Content = append(changesetsNode.Content, itemNode)
break // Only one key-value pair per item.
}

changesetsNode.Content = append(changesetsNode.Content, itemNode)
}

// Create the final output structure
Expand Down
12 changes: 0 additions & 12 deletions engine/cld/legacy/cli/commands/durable-pipelines_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,18 +212,6 @@ func TestNewDurablePipelineInputGenerateCmd(t *testing.T) {
inputsFileName string
mockInputContent string
}{
{
name: "object format",
formatDescription: "legacy object format",
inputsFileName: "test-inputs-object.yaml",
mockInputContent: `environment: testnet
domain: test
changesets:
0001_test_changeset:
payload:
chain: optimism_sepolia
value: 100`,
},
{
name: "array format",
formatDescription: "new array format",
Expand Down
69 changes: 15 additions & 54 deletions engine/cld/pipeline/input/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,27 +70,8 @@ func Generate(opts GenerateOptions) (string, error) {

var orderedChangesets []map[string]any

//nolint:exhaustive // Only MappingNode and SequenceNode are valid for changesets
//nolint:exhaustive // SequenceNode is the only valid format for changesets
switch dpFile.Changesets.Kind {
case yaml.MappingNode:
orderedChangesets = make([]map[string]any, 0, len(dpFile.Changesets.Content)/2)
for i := 0; i < len(dpFile.Changesets.Content); i += 2 {
keyNode := dpFile.Changesets.Content[i]
valueNode := dpFile.Changesets.Content[i+1]

csName := keyNode.Value
resolver := resolverByKey[csName]

resolvedCfg, resolveErr := ResolveChangesetConfig(valueNode, csName, resolver)
if resolveErr != nil {
return "", resolveErr
}

changesetItem := map[string]any{
csName: map[string]any{"payload": resolvedCfg},
}
orderedChangesets = append(orderedChangesets, changesetItem)
}
case yaml.SequenceNode:
orderedChangesets = make([]map[string]any, 0, len(dpFile.Changesets.Content))
for _, itemNode := range dpFile.Changesets.Content {
Expand All @@ -115,45 +96,25 @@ func Generate(opts GenerateOptions) (string, error) {
orderedChangesets = append(orderedChangesets, changesetItem)
}
default:
return "", fmt.Errorf("changesets must be either an object (mapping) or an array (sequence), got %v", dpFile.Changesets.Kind)
return "", fmt.Errorf("changesets must be an array (sequence), got %v", dpFile.Changesets.Kind)
}

var changesetsNode *yaml.Node

if dpFile.Changesets.Kind == yaml.MappingNode {
changesetsNode = &yaml.Node{Kind: yaml.MappingNode}
for _, changesetItem := range orderedChangesets {
for csName, csConfig := range changesetItem {
keyNode := &yaml.Node{Kind: yaml.ScalarNode, Value: csName}
changesetsNode.Content = append(changesetsNode.Content, keyNode)
changesetsNode := &yaml.Node{Kind: yaml.SequenceNode}
for _, changesetItem := range orderedChangesets {
itemNode := &yaml.Node{Kind: yaml.MappingNode}
for csName, csConfig := range changesetItem {
keyNode := &yaml.Node{Kind: yaml.ScalarNode, Value: csName}
itemNode.Content = append(itemNode.Content, keyNode)

valueNode, nodeErr := anyToYAMLNode(csConfig)
if nodeErr != nil {
return "", fmt.Errorf("encode changeset value for %s: %w", csName, nodeErr)
}
changesetsNode.Content = append(changesetsNode.Content, valueNode)

break
valueNode, nodeErr := anyToYAMLNode(csConfig)
if nodeErr != nil {
return "", fmt.Errorf("encode changeset value for %s: %w", csName, nodeErr)
}
itemNode.Content = append(itemNode.Content, valueNode)

break
}
} else {
changesetsNode = &yaml.Node{Kind: yaml.SequenceNode}
for _, changesetItem := range orderedChangesets {
itemNode := &yaml.Node{Kind: yaml.MappingNode}
for csName, csConfig := range changesetItem {
keyNode := &yaml.Node{Kind: yaml.ScalarNode, Value: csName}
itemNode.Content = append(itemNode.Content, keyNode)

valueNode, nodeErr := anyToYAMLNode(csConfig)
if nodeErr != nil {
return "", fmt.Errorf("encode changeset value for %s: %w", csName, nodeErr)
}
itemNode.Content = append(itemNode.Content, valueNode)

break
}
changesetsNode.Content = append(changesetsNode.Content, itemNode)
}
changesetsNode.Content = append(changesetsNode.Content, itemNode)
}

finalOutputNode := &yaml.Node{
Expand Down
69 changes: 18 additions & 51 deletions engine/cld/pipeline/input/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,51 +24,6 @@ func (g *generateStubChangeset) VerifyPreconditions(_ fdeployment.Environment, _

var _ fdeployment.ChangeSetV2[any] = (*generateStubChangeset)(nil)

func generateTestResolver(m map[string]any) (any, error) {
return map[string]any{"resolved": true, "v": m["v"]}, nil
}

//nolint:paralleltest
func TestGenerate_ObjectFormat(t *testing.T) {
dir := t.TempDir()
require.NoError(t, os.MkdirAll(filepath.Join(dir, "domains"), 0o755))
inputsDir := filepath.Join(dir, "domains", "mydomain", "testnet", "durable_pipelines", "inputs")
require.NoError(t, os.MkdirAll(inputsDir, 0o755))

inputsContent := `environment: testnet
domain: mydomain
changesets:
0001_cs1:
payload:
v: 1
`
require.NoError(t, os.WriteFile(filepath.Join(inputsDir, "in.yaml"), []byte(inputsContent), 0o644)) //nolint:gosec

originalWd, _ := os.Getwd()
require.NoError(t, os.Chdir(dir))
t.Cleanup(func() { _ = os.Chdir(originalWd) })

rm := fresolvers.NewConfigResolverManager()
rm.Register(generateTestResolver, fresolvers.ResolverInfo{Description: "X"})

reg := cs.NewChangesetsRegistry()
reg.Add("0001_cs1", cs.Configure(&generateStubChangeset{}).WithConfigResolver(generateTestResolver))

dom := domain.NewDomain(dir, "mydomain")
opts := GenerateOptions{
InputsFileName: "in.yaml",
Domain: dom,
EnvKey: "testnet",
Registry: reg,
ResolverManager: rm,
FormatAsJSON: false,
}

got, err := Generate(opts)
require.NoError(t, err)
require.Equal(t, "environment: testnet\ndomain: mydomain\nchangesets:\n 0001_cs1:\n payload:\n resolved: true\n v: 1\n", got)
}

//nolint:paralleltest
func TestGenerate_ArrayFormat(t *testing.T) {
dir := t.TempDir()
Expand Down Expand Up @@ -108,7 +63,19 @@ changesets:

got, err := Generate(opts)
require.NoError(t, err)
require.JSONEq(t, "{\n \"changesets\": [\n {\n \"0001_cs1\": {\n \"payload\": {\n \"x\": 1\n }\n }\n }\n ],\n \"domain\": \"mydomain\",\n \"environment\": \"testnet\"\n}", got)
require.JSONEq(t, `{
"changesets": [
{
"0001_cs1": {
"payload": {
"x": 1
}
}
}
],
"domain": "mydomain",
"environment": "testnet"
}`, got)
}

//nolint:paralleltest
Expand All @@ -120,7 +87,7 @@ func TestGenerate_InvalidChangesetsFormat(t *testing.T) {

inputsContent := `environment: testnet
domain: mydomain
changesets: "not-object-or-array"
changesets: "invalid-changesets-format"
`
require.NoError(t, os.WriteFile(filepath.Join(inputsDir, "in.yaml"), []byte(inputsContent), 0o644)) //nolint:gosec

Expand All @@ -140,7 +107,7 @@ changesets: "not-object-or-array"

_, err := Generate(opts)
require.Error(t, err)
require.Equal(t, "changesets must be either an object (mapping) or an array (sequence), got 8", err.Error())
require.ErrorContains(t, err, "changesets must be an array (sequence)")
}

//nolint:paralleltest
Expand All @@ -153,9 +120,9 @@ func TestGenerate_ResolverNotRegistered(t *testing.T) {
inputsContent := `environment: testnet
domain: mydomain
changesets:
0001_cs1:
payload:
x: 1
- 0001_cs1:
payload:
x: 1
`
require.NoError(t, os.WriteFile(filepath.Join(inputsDir, "in.yaml"), []byte(inputsContent), 0o644)) //nolint:gosec

Expand Down
Loading