Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upgrade(installer): Report and store policies metadata #31474

Merged
merged 11 commits into from
Nov 29, 2024
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
Loading