Skip to content

Commit

Permalink
upgrade(installer): Report and store policies metadata (#31474)
Browse files Browse the repository at this point in the history
  • Loading branch information
BaptisteFoy authored Nov 29, 2024
1 parent 102d8e4 commit daad6bc
Show file tree
Hide file tree
Showing 17 changed files with 1,033 additions and 486 deletions.
22 changes: 11 additions & 11 deletions pkg/fleet/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ type daemonImpl struct {
env *env.Env
installer installer.Installer
rc *remoteConfig
cdn cdn.CDN
cdn *cdn.CDN
catalog catalog
requests chan remoteAPIRequest
requestsWG sync.WaitGroup
Expand Down Expand Up @@ -105,7 +105,7 @@ func NewDaemon(rcFetcher client.ConfigFetcher, config config.Reader) (Daemon, er
return newDaemon(rc, installer, env, cdn), nil
}

func newDaemon(rc *remoteConfig, installer installer.Installer, env *env.Env, cdn cdn.CDN) *daemonImpl {
func newDaemon(rc *remoteConfig, installer installer.Installer, env *env.Env, cdn *cdn.CDN) *daemonImpl {
i := &daemonImpl{
env: env,
rc: rc,
Expand Down Expand Up @@ -556,15 +556,15 @@ func setRequestDone(ctx context.Context, err error) {
}
}

func (d *daemonImpl) resolveRemoteConfigVersion(ctx context.Context, pkg string) (string, error) {
func (d *daemonImpl) resolveRemoteConfigVersion(ctx context.Context, pkg string) (*pbgo.PoliciesState, error) {
if !d.env.RemotePolicies {
return "", nil
return nil, nil
}
config, err := d.cdn.Get(ctx, pkg)
if err != nil {
return "", err
return nil, err
}
return config.Version(), nil
return config.State(), nil
}

func (d *daemonImpl) refreshState(ctx context.Context) {
Expand Down Expand Up @@ -597,13 +597,13 @@ func (d *daemonImpl) refreshState(ctx context.Context) {
}
cs, hasConfig := configState[pkg]
if hasConfig {
p.StableConfigVersion = cs.Stable
p.ExperimentConfigVersion = cs.Experiment
p.StableConfigState = cs.StablePoliciesState
p.ExperimentConfigState = cs.ExperimentPoliciesState
}

configVersion, err := d.resolveRemoteConfigVersion(ctx, pkg)
if err == nil {
p.RemoteConfigVersion = configVersion
configState, err := d.resolveRemoteConfigVersion(ctx, pkg)
if err == nil && configState != nil {
p.RemoteConfigState = configState
} else if err != cdn.ErrProductNotSupported {
log.Warnf("could not get remote config version: %v", err)
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/fleet/installer/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ type installerImpl struct {
m sync.Mutex

env *fleetEnv.Env
cdn cdn.CDN
cdn *cdn.CDN
db *db.PackagesDB
downloader *oci.Downloader
packages *repository.Repositories
Expand Down Expand Up @@ -308,8 +308,8 @@ func (i *installerImpl) InstallConfigExperiment(ctx context.Context, pkg string,
if err != nil {
return fmt.Errorf("could not get cdn config: %w", err)
}
if config.Version() != version {
return fmt.Errorf("version mismatch: expected %s, got %s", config.Version(), version)
if config.State().GetVersion() != version {
return fmt.Errorf("version mismatch: expected %s, got %s", config.State().GetVersion(), version)
}

tmpDir, err := i.packages.MkdirTemp()
Expand Down Expand Up @@ -619,7 +619,7 @@ func (i *installerImpl) configurePackage(ctx context.Context, pkg string) (err e
if err != nil {
return fmt.Errorf("could not write %s config: %w", pkg, err)
}
err = i.configs.Create(pkg, config.Version(), tmpDir)
err = i.configs.Create(pkg, config.State().GetVersion(), tmpDir)
if err != nil {
return fmt.Errorf("could not create %s repository: %w", pkg, err)
}
Expand Down
44 changes: 44 additions & 0 deletions pkg/fleet/installer/repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package repository

import (
"encoding/json"
"errors"
"fmt"
"io/fs"
Expand All @@ -16,6 +17,7 @@ import (

"github.com/DataDog/gopsutil/process"

pbgo "github.com/DataDog/datadog-agent/pkg/proto/pbgo/core"
"github.com/DataDog/datadog-agent/pkg/util/log"
)

Expand Down Expand Up @@ -62,6 +64,9 @@ type Repository struct {
type State struct {
Stable string
Experiment string

StablePoliciesState *pbgo.PoliciesState
ExperimentPoliciesState *pbgo.PoliciesState
}

// HasStable returns true if the repository has a stable package.
Expand Down Expand Up @@ -98,12 +103,51 @@ func (r *Repository) GetState() (State, error) {
if experiment == stable {
experiment = ""
}

// Load the policies state
stablePoliciesState, err := r.loadPoliciesMetadata(stable)
if err != nil {
return State{}, fmt.Errorf("could not load stable policies state: %w", err)
}

experimentPoliciesState, err := r.loadPoliciesMetadata(experiment)
if err != nil {
return State{}, fmt.Errorf("could not load experiment policies state: %w", err)
}

return State{
Stable: stable,
Experiment: experiment,

StablePoliciesState: stablePoliciesState,
ExperimentPoliciesState: experimentPoliciesState,
}, nil
}

func (r *Repository) loadPoliciesMetadata(version string) (*pbgo.PoliciesState, error) {
if version == "" {
return nil, nil
}

statePath := filepath.Join(r.rootPath, version, "policy.metadata")
stateFile, err := os.ReadFile(statePath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
return nil, fmt.Errorf("could not read policies state: %w", err)
}

state := &pbgo.PoliciesState{}
err = json.Unmarshal(stateFile, state)
if err != nil {
return nil, fmt.Errorf("could not unmarshal policies state: %w", err)
}
state.Version = version

return state, nil
}

// Create creates a fresh new repository at the given root path
// and moves the given stable source path to the repository as the first stable.
// If a repository already exists at the given path, it is fully removed.
Expand Down
131 changes: 121 additions & 10 deletions pkg/fleet/internal/cdn/cdn.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,59 +8,170 @@ package cdn

import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"

"github.com/DataDog/datadog-agent/pkg/fleet/env"
pbgo "github.com/DataDog/datadog-agent/pkg/proto/pbgo/core"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
)

const policyMetadataFilename = "policy.metadata"

var (
// ErrProductNotSupported is returned when the product is not supported.
ErrProductNotSupported = errors.New("product not supported")
)

// Config represents a configuration.
type Config interface {
Version() string
State() *pbgo.PoliciesState
Write(dir string) error
}

// fetcher provides access to the Remote Config CDN.
type fetcher interface {
get(ctx context.Context) ([][]byte, error)
close() error
}

// CDN provides access to the Remote Config CDN.
type CDN interface {
Get(ctx context.Context, pkg string) (Config, error)
Close() error
type CDN struct {
fetcher fetcher
hostTagsGetter hostTagsGetter
}

// New creates a new CDN and chooses the implementation depending
// on the environment
func New(env *env.Env, configDBPath string) (CDN, error) {
func New(env *env.Env, configDBPath string) (*CDN, error) {
cdn := CDN{
hostTagsGetter: newHostTagsGetter(env),
}

if runtime.GOOS == "windows" {
// There's an assumption on windows that some directories are already there
// but they are in fact created by the regular CDN implementation. Until
// there is a fix on windows we keep the previous CDN behaviour for them
return newCDNHTTP(env, configDBPath)
fetcher, err := newHTTPFetcher(env, configDBPath)
if err != nil {
return nil, err
}
cdn.fetcher = fetcher
return &cdn, nil
}

if !env.RemotePolicies {
// Remote policies are not enabled -- we don't need the CDN
// and we don't want to create the directories that the CDN
// implementation would create. We return a no-op CDN to avoid
// nil pointer dereference.
return newCDNNoop()
fetcher, err := newNoopFetcher()
if err != nil {
return nil, err
}
cdn.fetcher = fetcher
return &cdn, nil
}

if env.CDNLocalDirPath != "" {
// Mock the CDN for local development or testing
return newCDNLocal(env)
fetcher, err := newLocalFetcher(env)
if err != nil {
return nil, err
}
cdn.fetcher = fetcher
return &cdn, nil
}

if !env.CDNEnabled {
// Remote policies are enabled but we don't want to use the CDN
// as it's still in development. We use standard remote config calls
// instead (dubbed "direct" CDN).
return newCDNRC(env, configDBPath)
fetcher, err := newRCFetcher(env, configDBPath)
if err != nil {
return nil, err
}
cdn.fetcher = fetcher
return &cdn, nil
}

// Regular CDN with the cloudfront distribution
return newCDNHTTP(env, configDBPath)
fetcher, err := newHTTPFetcher(env, configDBPath)
if err != nil {
return nil, err
}
cdn.fetcher = fetcher
return &cdn, nil
}

// Get fetches the configuration for the given package.
func (c *CDN) Get(ctx context.Context, pkg string) (cfg Config, err error) {
span, _ := tracer.StartSpanFromContext(ctx, "cdn.Get")
defer func() {
spanErr := err
if spanErr == ErrProductNotSupported {
spanErr = nil
}
span.Finish(tracer.WithError(spanErr))
}()

switch pkg {
case "datadog-agent":
orderedLayers, err := c.fetcher.get(ctx)
if err != nil {
return nil, err
}
cfg, err = newAgentConfig(orderedLayers...)
if err != nil {
return nil, err
}
case "datadog-apm-inject":
orderedLayers, err := c.fetcher.get(ctx)
if err != nil {
return nil, err
}
cfg, err = newAPMConfig(c.hostTagsGetter.get(), orderedLayers...)
if err != nil {
return nil, err
}
default:
return nil, ErrProductNotSupported
}

return cfg, nil
}

// Close closes the CDN.
func (c *CDN) Close() error {
return c.fetcher.close()
}

// writePolicyMetadata writes the policy metadata to the given directory
// and makes it readable to dd-agent
func writePolicyMetadata(config Config, dir string) error {
ddAgentUID, ddAgentGID, err := getAgentIDs()
if err != nil {
return fmt.Errorf("error getting dd-agent user and group IDs: %w", err)
}

state := config.State()
stateBytes, err := json.Marshal(state)
if err != nil {
return fmt.Errorf("could not marshal state: %w", err)
}
err = os.WriteFile(filepath.Join(dir, policyMetadataFilename), stateBytes, 0440)
if err != nil {
return fmt.Errorf("could not write %s: %w", policyMetadataFilename, err)
}
if runtime.GOOS != "windows" {
err = os.Chown(filepath.Join(dir, policyMetadataFilename), ddAgentUID, ddAgentGID)
if err != nil {
return fmt.Errorf("could not chown %s: %w", policyMetadataFilename, err)
}
}
return nil
}
Loading

0 comments on commit daad6bc

Please sign in to comment.