Skip to content

Commit 8d94782

Browse files
authored
Merge pull request #52 from newrelic/feat/bindings
Add bindings support
2 parents f498417 + 99a5764 commit 8d94782

5 files changed

Lines changed: 135 additions & 0 deletions

File tree

cmd/agent-metadata-action/main.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,25 @@ func runAgentFlow(ctx context.Context, client metadataClient, workspace, agentTy
227227
logging.Noticef(ctx, "Loaded %d agent control definitions", len(agentControl))
228228
}
229229

230+
// Load agent definition (optional)
231+
agentDef, err := loader.ReadAgentDefinition(ctx, workspace)
232+
if err != nil {
233+
logging.Warnf(ctx, "Unable to load agent definition: %v - continuing without it", err)
234+
agentDef = nil
235+
} else if agentDef != nil {
236+
logging.Notice(ctx, "Loaded agent definition")
237+
}
238+
230239
// Build metadata
231240
metadata := models.AgentMetadata{
232241
ConfigurationDefinitions: configs,
233242
Metadata: loader.LoadMetadataForAgents(agentVersion),
234243
AgentControlDefinitions: agentControl,
235244
}
245+
if agentDef != nil {
246+
metadata.Bindings = agentDef.Bindings
247+
metadata.BreakingChange = agentDef.BreakingChange
248+
}
236249

237250
printJSON(ctx, "Agent Metadata", metadata)
238251

internal/config/dirs.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ func GetAgentControlDefinitionsFilename() string {
3333
return "agentControlDefinitions.yml"
3434
}
3535

36+
// GetAgentDefinitionFilepath returns the path to the optional agentDefinition.yml file
37+
func GetAgentDefinitionFilepath() string {
38+
return filepath.Join(GetRootFolderForAgentRepo(), "agentDefinition.yml")
39+
}
40+
3641
func GetReleaseNotesDirectory() string {
3742
return "src/content/docs/release-notes"
3843
}

internal/loader/definitions.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,27 @@ func ReadAgentControlDefinitions(ctx context.Context, workspacePath string) ([]m
103103
return result, nil
104104
}
105105

106+
// ReadAgentDefinition reads the optional agentDefinition.yml file.
107+
// Returns nil, nil if the file does not exist (the file is optional).
108+
func ReadAgentDefinition(ctx context.Context, workspacePath string) (*models.AgentDefinition, error) {
109+
fullPath := filepath.Join(workspacePath, config.GetAgentDefinitionFilepath())
110+
111+
data, err := os.ReadFile(fullPath)
112+
if err != nil {
113+
if os.IsNotExist(err) {
114+
logging.Debug(ctx, "agentDefinition.yml not found - skipping")
115+
return nil, nil
116+
}
117+
return nil, fmt.Errorf("failed to read agentDefinition.yml: %w", err)
118+
}
119+
120+
var def models.AgentDefinition
121+
if err := yaml.Unmarshal(data, &def); err != nil {
122+
return nil, fmt.Errorf("failed to parse agentDefinition.yml: %w", err)
123+
}
124+
return &def, nil
125+
}
126+
106127
// readDefinitionsFile reads a YAML file and extracts the first array it finds at the top level.
107128
// This is a generic function that works for both configurationDefinitions and agentControlDefinitions files.
108129
// It returns the array of definitions as []map[string]interface{}.

internal/loader/definitions_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,3 +1002,90 @@ func TestReadAgentControlDefinitions_DropsBrokenContentField(t *testing.T) {
10021002
})
10031003
}
10041004
}
1005+
1006+
func TestReadAgentDefinition_FileNotFound(t *testing.T) {
1007+
tmpDir := t.TempDir()
1008+
configDir := filepath.Join(tmpDir, config.GetRootFolderForAgentRepo())
1009+
require.NoError(t, os.MkdirAll(configDir, 0755))
1010+
1011+
result, err := ReadAgentDefinition(context.Background(), tmpDir)
1012+
require.NoError(t, err)
1013+
assert.Nil(t, result)
1014+
}
1015+
1016+
func TestReadAgentDefinition_ValidFile(t *testing.T) {
1017+
tmpDir := t.TempDir()
1018+
configDir := filepath.Join(tmpDir, config.GetRootFolderForAgentRepo())
1019+
require.NoError(t, os.MkdirAll(configDir, 0755))
1020+
1021+
yamlContent := `bindings:
1022+
- type: REQUIRES
1023+
targetType: AGENT
1024+
target: NrInfra
1025+
versions:
1026+
- ">=1.0.0"
1027+
breakingChange: "2.0.0"`
1028+
defFile := filepath.Join(configDir, "agentDefinition.yml")
1029+
require.NoError(t, os.WriteFile(defFile, []byte(yamlContent), 0644))
1030+
1031+
result, err := ReadAgentDefinition(context.Background(), tmpDir)
1032+
require.NoError(t, err)
1033+
require.NotNil(t, result)
1034+
1035+
require.NotNil(t, result.BreakingChange)
1036+
assert.Equal(t, "2.0.0", *result.BreakingChange)
1037+
1038+
require.Len(t, result.Bindings, 1)
1039+
binding := result.Bindings[0].(map[string]interface{})
1040+
assert.Equal(t, "REQUIRES", binding["type"])
1041+
assert.Equal(t, "AGENT", binding["targetType"])
1042+
assert.Equal(t, "NrInfra", binding["target"])
1043+
}
1044+
1045+
func TestReadAgentDefinition_BreakingChangeOnly(t *testing.T) {
1046+
tmpDir := t.TempDir()
1047+
configDir := filepath.Join(tmpDir, config.GetRootFolderForAgentRepo())
1048+
require.NoError(t, os.MkdirAll(configDir, 0755))
1049+
1050+
yamlContent := `breakingChange: "1.5.0"`
1051+
defFile := filepath.Join(configDir, "agentDefinition.yml")
1052+
require.NoError(t, os.WriteFile(defFile, []byte(yamlContent), 0644))
1053+
1054+
result, err := ReadAgentDefinition(context.Background(), tmpDir)
1055+
require.NoError(t, err)
1056+
require.NotNil(t, result)
1057+
require.NotNil(t, result.BreakingChange)
1058+
assert.Equal(t, "1.5.0", *result.BreakingChange)
1059+
assert.Nil(t, result.Bindings)
1060+
}
1061+
1062+
func TestReadAgentDefinition_BreakingChangeUnquotedFloat(t *testing.T) {
1063+
tmpDir := t.TempDir()
1064+
configDir := filepath.Join(tmpDir, config.GetRootFolderForAgentRepo())
1065+
require.NoError(t, os.MkdirAll(configDir, 0755))
1066+
1067+
// Unquoted float — go-yaml coerces to string representation
1068+
yamlContent := `breakingChange: 2.0`
1069+
defFile := filepath.Join(configDir, "agentDefinition.yml")
1070+
require.NoError(t, os.WriteFile(defFile, []byte(yamlContent), 0644))
1071+
1072+
result, err := ReadAgentDefinition(context.Background(), tmpDir)
1073+
require.NoError(t, err)
1074+
require.NotNil(t, result)
1075+
require.NotNil(t, result.BreakingChange)
1076+
assert.Equal(t, "2.0", *result.BreakingChange)
1077+
}
1078+
1079+
func TestReadAgentDefinition_InvalidYAML(t *testing.T) {
1080+
tmpDir := t.TempDir()
1081+
configDir := filepath.Join(tmpDir, config.GetRootFolderForAgentRepo())
1082+
require.NoError(t, os.MkdirAll(configDir, 0755))
1083+
1084+
defFile := filepath.Join(configDir, "agentDefinition.yml")
1085+
require.NoError(t, os.WriteFile(defFile, []byte(":\tinvalid: yaml: ["), 0644))
1086+
1087+
result, err := ReadAgentDefinition(context.Background(), tmpDir)
1088+
require.Error(t, err)
1089+
assert.Nil(t, result)
1090+
assert.Contains(t, err.Error(), "failed to parse agentDefinition.yml")
1091+
}

internal/models/models.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ type AgentMetadata struct {
55
ConfigurationDefinitions []ConfigurationDefinition `json:"configurationDefinitions"`
66
Metadata Metadata `json:"metadata"`
77
AgentControlDefinitions []AgentControlDefinition `json:"agentControlDefinitions"`
8+
Bindings []interface{} `json:"bindings,omitempty"`
9+
BreakingChange *string `json:"breakingChange,omitempty"`
810
}
911

1012
// ConfigurationDefinition represents a configuration that can be read from YAML and sent as JSON.
@@ -22,6 +24,13 @@ type Metadata map[string]interface{}
2224
// YAML fields are automatically translated to JSON.
2325
type AgentControlDefinition map[string]interface{}
2426

27+
// AgentDefinition is a typed view of agentDefinition.yml used for parsing.
28+
// Fields are promoted flat onto AgentMetadata for JSON serialization.
29+
type AgentDefinition struct {
30+
Bindings []interface{} `yaml:"bindings"`
31+
BreakingChange *string `yaml:"breakingChange"`
32+
}
33+
2534
// ConfigFile represents the YAML file structure containing multiple configs
2635
type ConfigFile struct {
2736
Configs []ConfigurationDefinition `yaml:"configurationDefinitions"`

0 commit comments

Comments
 (0)