Skip to content

Commit

Permalink
upgrade(installer): Support integrations config (DataDog#32426)
Browse files Browse the repository at this point in the history
  • Loading branch information
BaptisteFoy authored Dec 30, 2024
1 parent 055764b commit c83838f
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 1 deletion.
82 changes: 81 additions & 1 deletion pkg/fleet/internal/cdn/config_datadog_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type agentConfig struct {
datadog []byte
securityAgent []byte
systemProbe []byte
integrations []integration
}

// agentConfigLayer is a config layer that can be merged with other layers into a config.
Expand All @@ -43,6 +44,14 @@ type agentConfigLayer struct {
AgentConfig map[string]interface{} `json:"config"`
SecurityAgentConfig map[string]interface{} `json:"security_agent"`
SystemProbeConfig map[string]interface{} `json:"system_probe"`
IntegrationsConfig []integration `json:"integrations,omitempty"`
}

type integration struct {
Type string `json:"type"`
Instance map[string]interface{} `json:"instance"`
Init map[string]interface{} `json:"init_config"`
Logs []map[string]interface{} `json:"logs"`
}

// State returns the agent policies state
Expand All @@ -60,15 +69,18 @@ func newAgentConfig(orderedLayers ...[]byte) (*agentConfig, error) {
AgentConfig: map[string]interface{}{},
SecurityAgentConfig: map[string]interface{}{},
SystemProbeConfig: map[string]interface{}{},
IntegrationsConfig: []integration{},
}

for _, rawLayer := range orderedLayers {
layer := &agentConfigLayer{}
if err := json.Unmarshal(rawLayer, layer); err != nil {
log.Warnf("Failed to unmarshal layer: %v", err)
continue
}
if layer.AgentConfig == nil && layer.SecurityAgentConfig == nil && layer.SystemProbeConfig == nil {
if layer.AgentConfig == nil && layer.SecurityAgentConfig == nil && layer.SystemProbeConfig == nil && len(layer.IntegrationsConfig) == 0 {
// Only add layers that have at least one config that matches the agent

continue
}

Expand Down Expand Up @@ -97,6 +109,10 @@ func newAgentConfig(orderedLayers ...[]byte) (*agentConfig, error) {
}
compiledLayer.SystemProbeConfig = systemProbeAgentConfig.(map[string]interface{})
}

if len(layer.IntegrationsConfig) > 0 {
compiledLayer.IntegrationsConfig = append(compiledLayer.IntegrationsConfig, layer.IntegrationsConfig...)
}
}

// Report applied layers
Expand Down Expand Up @@ -130,6 +146,7 @@ func newAgentConfig(orderedLayers ...[]byte) (*agentConfig, error) {
datadog: config,
securityAgent: securityAgentConfig,
systemProbe: systemProbeConfig,
integrations: compiledLayer.IntegrationsConfig,
}, nil
}

Expand Down Expand Up @@ -176,9 +193,72 @@ func (a *agentConfig) Write(dir string) error {
}
}
}
if len(a.integrations) > 0 {
for _, integration := range a.integrations {
err = a.writeIntegration(dir, integration, ddAgentUID, ddAgentGID)
if err != nil {
return err
}
}
}
return writePolicyMetadata(a, dir)
}

func (a *agentConfig) writeIntegration(dir string, i integration, ddAgentUID, ddAgentGID int) error {
// Create the integration directory if it doesn't exist
integrationDir := filepath.Join(dir, "conf.d", fmt.Sprintf("%s.d", i.Type))
if _, err := os.Stat(integrationDir); os.IsNotExist(err) {
err = os.MkdirAll(integrationDir, 0755)
if err != nil {
return fmt.Errorf("could not create integration directory %s: %w", integrationDir, err)
}
// Chown the directory to the dd-agent user
if runtime.GOOS != "windows" {
err = os.Chown(integrationDir, ddAgentUID, ddAgentGID)
if err != nil {
return fmt.Errorf("could not chown %s: %w", integrationDir, err)
}
}
} else if err != nil {
return fmt.Errorf("could not stat integration directory %s: %w", integrationDir, err)
}

// Hash the integration instance and init_config to create a unique filename
hash := sha256.New()
json, err := json.Marshal(i)
if err != nil {
return fmt.Errorf("could not marshal integration: %w", err)
}
hash.Write(json)
integrationPath := filepath.Join(integrationDir, fmt.Sprintf("%x.yaml", hash.Sum(nil)))

content := map[string]interface{}{}
if i.Instance != nil {
content["instances"] = []interface{}{i.Instance}
}
if i.Init != nil {
content["init_config"] = i.Init
}
if i.Logs != nil {
content["logs"] = i.Logs
}
yamlContent, err := marshalYAMLConfig(content)
if err != nil {
return fmt.Errorf("could not marshal integration content: %w", err)
}
err = os.WriteFile(integrationPath, yamlContent, 0640)
if err != nil {
return fmt.Errorf("could not write integration %s: %w", integrationPath, err)
}
if runtime.GOOS != "windows" {
err = os.Chown(integrationPath, ddAgentUID, ddAgentGID)
if err != nil {
return fmt.Errorf("could not chown %s: %w", integrationPath, err)
}
}
return nil
}

// getAgentIDs returns the UID and GID of the dd-agent user and group.
func getAgentIDs() (uid, gid int, err error) {
ddAgentUser, err := user.Lookup("dd-agent")
Expand Down
86 changes: 86 additions & 0 deletions pkg/fleet/internal/cdn/config_datadog_agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package cdn

import (
"encoding/json"
"os"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -52,3 +53,88 @@ fleet_layers:
`
assert.Equal(t, expectedConfig, string(config.datadog))
}

func TestAgentConfigWithIntegrations(t *testing.T) {
layers := []*agentConfigLayer{
{
ID: "layer1",
AgentConfig: map[string]interface{}{
"api_key": "1234",
},
IntegrationsConfig: []integration{
{
Type: "apache",
Instance: map[string]interface{}{
"status_url": "http://localhost:1234/server-status",
},
Init: map[string]interface{}{
"apache_status_url": "http://localhost:1234/server-status",
},
},
},
},
{
ID: "layer2",
IntegrationsConfig: []integration{
{
Type: "apache",
Instance: map[string]interface{}{
"status_url": "http://localhost:5678/server-status",
},
Init: map[string]interface{}{
"apache_status_url": "http://localhost:5678/server-status",
},
},
},
},
}
rawLayers := make([][]byte, 0, len(layers))
for _, layer := range layers {
rawLayer, err := json.Marshal(layer)
assert.NoError(t, err)
rawLayers = append(rawLayers, rawLayer)
}

config, err := newAgentConfig(rawLayers...)
assert.NoError(t, err)
expectedAgentConfig := doNotEditDisclaimer + `
api_key: "1234"
fleet_layers:
- layer1
- layer2
`
assert.Equal(t, expectedAgentConfig, string(config.datadog))

expectedIntegrationsConfig := []integration{
{
Type: "apache",
Instance: map[string]interface{}{
"status_url": "http://localhost:1234/server-status",
},
Init: map[string]interface{}{
"apache_status_url": "http://localhost:1234/server-status",
},
},
{
Type: "apache",
Instance: map[string]interface{}{
"status_url": "http://localhost:5678/server-status",
},
Init: map[string]interface{}{
"apache_status_url": "http://localhost:5678/server-status",
},
},
}
assert.Equal(t, expectedIntegrationsConfig, config.integrations)

tmpDir := t.TempDir()
err = config.writeIntegration(tmpDir, config.integrations[0], os.Getuid(), os.Getgid())
assert.NoError(t, err)
err = config.writeIntegration(tmpDir, config.integrations[1], os.Getuid(), os.Getgid())
assert.NoError(t, err)

// Check that the integrations are written to the correct folder
files, err := os.ReadDir(tmpDir + "/conf.d/apache.d")
assert.NoError(t, err)
assert.Len(t, files, 2)
}

0 comments on commit c83838f

Please sign in to comment.