From fe474e33c895ff6fd7522771c43593152cd761a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Mota?= Date: Tue, 5 Nov 2024 14:01:06 +0000 Subject: [PATCH] [extensions/observer/cfgardenobserver] Implement cfgardenobserver (#34513) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Description:** First Component PR: #33727 This is the second PR for adding the cfgardenobserver, with the first suggested implementation. There are definitely some decisions made that require feedback, such as adding the CloudFoundry application labels to the Endpoint labels, and the decision to use the `Container` EndpointType at all. **Link to tracking Issue:** #33618 **Testing:** Unit testing of config and extension **Documentation:** Updated readme with new configuration and endpoints --------- Co-authored-by: sam clulow Co-authored-by: sam clulow Co-authored-by: José Riguera Lopez --- .chloggen/implement-cfgardenobserver.yaml | 27 ++ extension/observer/cfgardenobserver/README.md | 49 ++- extension/observer/cfgardenobserver/config.go | 110 +++++- .../observer/cfgardenobserver/config_test.go | 192 ++++++++++- .../observer/cfgardenobserver/extension.go | 313 +++++++++++++++++- .../cfgardenobserver/extension_test.go | 240 +++++++++++++- .../observer/cfgardenobserver/factory.go | 16 +- .../generated_component_test.go | 31 -- extension/observer/cfgardenobserver/go.mod | 24 +- extension/observer/cfgardenobserver/go.sum | 56 +++- .../internal/metadata/generated_status.go | 2 +- .../observer/cfgardenobserver/metadata.yaml | 8 +- .../cfgardenobserver/testdata/config.yaml | 36 +- 13 files changed, 1004 insertions(+), 100 deletions(-) create mode 100644 .chloggen/implement-cfgardenobserver.yaml diff --git a/.chloggen/implement-cfgardenobserver.yaml b/.chloggen/implement-cfgardenobserver.yaml new file mode 100644 index 000000000000..1ddbc462ecf5 --- /dev/null +++ b/.chloggen/implement-cfgardenobserver.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: new_component + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: extensions/observer/cfgardenobserver + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Implemented the observer, the second PR in the 3 PR process for new components + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [33618] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [user] \ No newline at end of file diff --git a/extension/observer/cfgardenobserver/README.md b/extension/observer/cfgardenobserver/README.md index 3c5aa70f1576..427fcbeda666 100644 --- a/extension/observer/cfgardenobserver/README.md +++ b/extension/observer/cfgardenobserver/README.md @@ -6,7 +6,7 @@ | Stability | [development] | | Distributions | [] | | Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Aextension%2Fcfgardenobserver%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aextension%2Fcfgardenobserver) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Aextension%2Fcfgardenobserver%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Aextension%2Fcfgardenobserver) | -| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@crobert-1](https://www.github.com/crobert-1), [@cemdk](https://www.github.com/cemdk), [@tomasmota](https://www.github.com/tomasmota), [@m1rp](https://www.github.com/m1rp), [@jriguera](https://www.github.com/jriguera) | +| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@crobert-1](https://www.github.com/crobert-1), [@cemdk](https://www.github.com/cemdk), [@m1rp](https://www.github.com/m1rp), [@jriguera](https://www.github.com/jriguera) | [development]: https://github.com/open-telemetry/opentelemetry-collector#development @@ -20,17 +20,24 @@ The `cfgarden_observer` looks at the current host to discover Garden containers. ```yaml extensions: cfgarden_observer: - # url of the Garden socket, defaults to unix:///var/vcap/data/garden/garden.sock - endpoint: my/path/to/garden.sock - # determines how often to look for changes in endpoints. refresh_interval: 30s + cache_sync_interval: 10m + include_app_labels: true + garden: + endpoint: my/path/to/garden.sock + cloud_foundry: + endpoint: https://api.cf.mydomain.com + auth: + type: client_credentials + client_id: myclientid + client_secret: myclientsecret receivers: receiver_creator: watch_observers: [cfgarden_observer] receivers: prometheus_simple: - rule: type == "container" && name == "myapp" + rule: type == "container" && labels["prometheus.io/scrape"] == "true" config: metrics_path: /metrics endpoint: '`endpoint`' @@ -38,16 +45,32 @@ receivers: ### Configuration -| Name | Type | Default | Docs | -|------------------|--------|------------------------------------------|--------------------------------------------------------| -| refresh_interval | string | 60s | Determines how often to look for changes in endpoints. | -| endpoint | string | unix:///var/vcap/data/garden/garden.sock | The endpoint to connect to the Garden API. | +| Name | Type | Default | Description | +| -------------------------------- | ------ | --------------------------------------------------------- | ------------------------------------------------------------------ | +| refresh_interval | string | 1m | Determines how often to look for changes in endpoints. | +| cache_sync_interval | string | 5m | Determines how often app metadata cache is refreshed | +| include_app_labels | bool | false | Determines whether or not app labels get added to container labels | +| garden.endpoint | string | /var/vcap/data/garden/garden.sock | Path to garden socket. | +| cloud_foundry.endpoint | string | none. required when `include_app_labels` is set to `true` | CloudFoundry API endpoint | +| cloud_foundry.auth.type | string | none. required when `include_app_labels` is set to `true` | Authentication type, one of: user_pass, client_credentials, token | +| cloud_foundry.auth.username | string | none | Username (auth.type: user_pass) | +| cloud_foundry.auth.password | string | none | Password (auth.type: user_pass) | +| cloud_foundry.auth.client_id | string | none | Client ID (auth.type: client_credentials) | +| cloud_foundry.auth.client_secret | string | none | Client Secret (auth.type: client_credentials) | +| cloud_foundry.auth.access_token | string | none | Access Token (auth.type: token) | +| cloud_foundry.auth.refresh_token | string | none | Refresh Token (auth.type: token) | + ### Endpoint Variables Endpoint variables exposed by this observer are as follows. -| Variable | Description | -|-----------|--------------------------------------------------------------------------------------------| -| type | this value is always `container` | -| name | name of the Garden container associated to the port | +| Variable | Description | +| ------------ | --------------------------------------------------------------------------------- | +| type | This value is always `container` | +| name | Name of the Garden container associated to the port | +| labels | map[string]string with labels set on the log_config tags and application resource | +| port | Exposed port of the container | +| container_id | ID of the container | +| host | Hostname or IP of the underlying host the container is running on | +| transport | Transport protocol used by the endpoint (TCP or UDP) | diff --git a/extension/observer/cfgardenobserver/config.go b/extension/observer/cfgardenobserver/config.go index ea6a62539e98..67085def9fe3 100644 --- a/extension/observer/cfgardenobserver/config.go +++ b/extension/observer/cfgardenobserver/config.go @@ -4,15 +4,121 @@ package cfgardenobserver // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/cfgardenobserver" import ( + "errors" + "fmt" "time" ) // Config defines configuration for CF Garden observer. type Config struct { - // The URL of the CF Garden api. Default is "unix:///var/vcap/data/garden/garden.sock" - Endpoint string `mapstructure:"endpoint"` + // CloudFoundry API Configuration + CloudFoundry CfConfig `mapstructure:"cloud_foundry"` + + // Garden API Configuration + Garden GardenConfig `mapstructure:"garden"` // RefreshInterval determines the frequency at which the observer // needs to poll for collecting information about new processes. + // Default: "1m" RefreshInterval time.Duration `mapstructure:"refresh_interval"` + + // The time to wait before resyncing app information on cached containers + // using the CloudFoundry API. + // Default: "5m" + CacheSyncInterval time.Duration `mapstructure:"cache_sync_interval"` + + // Determines whether or not Application labels get added to the Endpoint labels. + // This requires cloud_foundry to be configured, such that API calls can be made + // Default: false + IncludeAppLabels bool `mapstructure:"include_app_labels"` +} + +// Validate overrides the embedded noop validation so that load config can trigger +// our own validation logic. +func (config *Config) Validate() error { + if !config.IncludeAppLabels { + return nil + } + + c := config.CloudFoundry + if c.Endpoint == "" { + return errors.New("CloudFoundry.Endpoint must be specified when IncludeAppLabels is set to true") + } + if c.Auth.Type == "" { + return errors.New("CloudFoundry.Auth.Type must be specified when IncludeAppLabels is set to true") + } + + switch c.Auth.Type { + case authTypeUserPass: + if c.Auth.Username == "" { + return fieldError(authTypeUserPass, "username") + } + if c.Auth.Password == "" { + return fieldError(authTypeUserPass, "password") + } + case authTypeClientCredentials: + if c.Auth.ClientID == "" { + return fieldError(authTypeClientCredentials, "client_id") + } + if c.Auth.ClientSecret == "" { + return fieldError(authTypeClientCredentials, "client_secret") + } + case authTypeToken: + if c.Auth.AccessToken == "" { + return fieldError(authTypeToken, "access_token") + } + if c.Auth.RefreshToken == "" { + return fieldError(authTypeToken, "refresh_token") + } + default: + return fmt.Errorf("configuration option `auth_type` must be set to one of the following values: [user_pass, client_credentials, token]. Specified value: %s", c.Auth.Type) + } + + return nil +} + +func fieldError(authType authType, param string) error { + return fmt.Errorf("%s is required when using auth_type: %s", param, authType) +} + +type GardenConfig struct { + // The URL of the CF Garden api. Default is "/var/vcap/data/garden/garden.sock" + Endpoint string `mapstructure:"endpoint"` } + +type CfConfig struct { + // The URL of the CloudFoundry API + Endpoint string `mapstructure:"endpoint"` + + // Authentication details + Auth CfAuth `mapstructure:"auth"` +} + +type CfAuth struct { + // Authentication method, there are 3 options + Type authType `mapstructure:"type"` + + // Used for user_pass authentication method + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + + // Used for token authentication method + AccessToken string `mapstructure:"access_token"` + RefreshToken string `mapstructure:"refresh_token"` + + // Used for client_credentials authentication method + ClientID string `mapstructure:"client_id"` + ClientSecret string `mapstructure:"client_secret"` +} + +// authType describes the type of authentication to use for the CloudFoundry API +type authType string + +const ( + // authTypeClientCredentials uses a client ID and client secret to authenticate + authTypeClientCredentials authType = "client_credentials" + // authTypeUserPass uses username and password to authenticate + authTypeUserPass authType = "user_pass" + // authTypeToken uses access token and refresh token to authenticate + authTypeToken authType = "token" +) diff --git a/extension/observer/cfgardenobserver/config_test.go b/extension/observer/cfgardenobserver/config_test.go index 4d11faf25408..d3cd919a06e9 100644 --- a/extension/observer/cfgardenobserver/config_test.go +++ b/extension/observer/cfgardenobserver/config_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/confmaptest" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/cfgardenobserver/internal/metadata" @@ -26,30 +27,197 @@ func TestLoadConfig(t *testing.T) { { id: component.NewID(metadata.Type), expected: &Config{ - Endpoint: "unix:///var/vcap/data/garden/garden.sock", - RefreshInterval: 1 * time.Minute, + RefreshInterval: 1 * time.Minute, + CacheSyncInterval: 5 * time.Minute, + IncludeAppLabels: false, + Garden: GardenConfig{ + Endpoint: "/var/vcap/data/garden/garden.sock", + }, }, }, { id: component.NewIDWithName(metadata.Type, "all_settings"), expected: &Config{ - Endpoint: "unix:///var/vcap/data/garden/custom.sock", - RefreshInterval: 20 * time.Second, + RefreshInterval: 20 * time.Second, + CacheSyncInterval: 5 * time.Second, + IncludeAppLabels: true, + Garden: GardenConfig{ + Endpoint: "/var/vcap/data/garden/custom.sock", + }, + CloudFoundry: CfConfig{ + Endpoint: "https://api.cf.mydomain.com", + Auth: CfAuth{ + Type: "user_pass", + Username: "myuser", + Password: "mypass", + }, + }, + }, + }, + { + id: component.NewIDWithName(metadata.Type, "user_pass"), + expected: &Config{ + Garden: GardenConfig{ + Endpoint: "/var/vcap/data/garden/garden.sock", + }, + RefreshInterval: 1 * time.Minute, + CacheSyncInterval: 5 * time.Minute, + IncludeAppLabels: true, + CloudFoundry: CfConfig{ + Endpoint: "https://api.cf.mydomain.com", + Auth: CfAuth{ + Type: "user_pass", + Username: "myuser", + Password: "mypass", + }, + }, + }, + }, + { + id: component.NewIDWithName(metadata.Type, "client_credentials"), + expected: &Config{ + Garden: GardenConfig{ + Endpoint: "/var/vcap/data/garden/garden.sock", + }, + RefreshInterval: 1 * time.Minute, + CacheSyncInterval: 5 * time.Minute, + IncludeAppLabels: true, + CloudFoundry: CfConfig{ + Endpoint: "https://api.cf.mydomain.com", + Auth: CfAuth{ + Type: "client_credentials", + ClientID: "myclientid", + ClientSecret: "myclientsecret", + }, + }, + }, + }, + { + id: component.NewIDWithName(metadata.Type, "token"), + expected: &Config{ + Garden: GardenConfig{ + Endpoint: "/var/vcap/data/garden/garden.sock", + }, + RefreshInterval: 1 * time.Minute, + CacheSyncInterval: 5 * time.Minute, + IncludeAppLabels: true, + CloudFoundry: CfConfig{ + Endpoint: "https://api.cf.mydomain.com", + Auth: CfAuth{ + Type: "token", + AccessToken: "myaccesstoken", + RefreshToken: "myrefreshtoken", + }, + }, }, }, } for _, tt := range tests { t.Run(tt.id.String(), func(t *testing.T) { - cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml")) - require.NoError(t, err) - factory := NewFactory() - cfg := factory.CreateDefaultConfig() - sub, err := cm.Sub(tt.id.String()) - require.NoError(t, err) - require.NoError(t, sub.Unmarshal(cfg)) - + cfg := loadConfig(t, tt.id) assert.NoError(t, component.ValidateConfig(cfg)) assert.Equal(t, tt.expected, cfg) }) } } + +func TestConfigValidate(t *testing.T) { + cases := []struct { + reason string + cfg Config + msg string + }{ + { + reason: "missing endpoint", + cfg: Config{ + IncludeAppLabels: true, + }, + msg: "CloudFoundry.Endpoint must be specified when IncludeAppLabels is set to true", + }, + { + reason: "missing cloud_foundry.auth.type", + cfg: Config{ + IncludeAppLabels: true, + CloudFoundry: CfConfig{ + Endpoint: "https://api.cf.mydomain.com", + }, + }, + msg: "CloudFoundry.Auth.Type must be specified when IncludeAppLabels is set to true", + }, + { + reason: "unknown cloud_foundry.auth.type", + cfg: Config{ + IncludeAppLabels: true, + CloudFoundry: CfConfig{ + Endpoint: "https://api.cf.mydomain.com", + Auth: CfAuth{ + Type: "unknown", + }, + }, + }, + msg: "configuration option `auth_type` must be set to one of the following values: [user_pass, client_credentials, token]. Specified value: unknown", + }, + { + reason: "missing username", + cfg: Config{ + IncludeAppLabels: true, + CloudFoundry: CfConfig{ + Endpoint: "https://api.cf.mydomain.com", + Auth: CfAuth{ + Type: authTypeUserPass, + }, + }, + }, + msg: fieldError(authTypeUserPass, "username").Error(), + }, + { + reason: "missing clientID", + cfg: Config{ + IncludeAppLabels: true, + CloudFoundry: CfConfig{ + Endpoint: "https://api.cf.mydomain.com", + Auth: CfAuth{ + Type: authTypeClientCredentials, + }, + }, + }, + msg: fieldError(authTypeClientCredentials, "client_id").Error(), + }, + { + reason: "missing AccessToken", + cfg: Config{ + IncludeAppLabels: true, + CloudFoundry: CfConfig{ + Endpoint: "https://api.cf.mydomain.com", + Auth: CfAuth{ + Type: authTypeToken, + }, + }, + }, + msg: fieldError(authTypeToken, "access_token").Error(), + }, + } + + for _, tCase := range cases { + t.Run(tCase.reason, func(t *testing.T) { + err := tCase.cfg.Validate() + require.EqualError(t, err, tCase.msg) + }) + } +} + +func loadRawConf(t testing.TB, path string, id component.ID) *confmap.Conf { + cm, err := confmaptest.LoadConf(filepath.Join("testdata", path)) + require.NoError(t, err) + sub, err := cm.Sub(id.String()) + require.NoError(t, err) + return sub +} + +func loadConfig(t testing.TB, id component.ID) *Config { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + sub := loadRawConf(t, "config.yaml", id) + require.NoError(t, sub.Unmarshal(cfg)) + return cfg.(*Config) +} diff --git a/extension/observer/cfgardenobserver/extension.go b/extension/observer/cfgardenobserver/extension.go index 67f2c616cce8..5e247ab19a1e 100644 --- a/extension/observer/cfgardenobserver/extension.go +++ b/extension/observer/cfgardenobserver/extension.go @@ -4,33 +4,330 @@ package cfgardenobserver // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/cfgardenobserver" import ( + "context" + "encoding/json" + "fmt" + "strconv" + "strings" + "sync" "time" + "code.cloudfoundry.org/garden" + gardenClient "code.cloudfoundry.org/garden/client" + gardenConnection "code.cloudfoundry.org/garden/client/connection" + "github.com/cloudfoundry/go-cfclient/v3/client" + "github.com/cloudfoundry/go-cfclient/v3/config" + "github.com/cloudfoundry/go-cfclient/v3/resource" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/extension" + "go.uber.org/zap" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" ) +const ( + propertiesAppIDKey = "network.app_id" + propertiesPortsKey = "network.ports" + propertiesLogConfigKey = "log_config" + logConfigTagsKey = "tags" + containerStateActive = "active" +) + type cfGardenObserver struct { *observer.EndpointsWatcher + config *Config + doneChan chan struct{} + logger *zap.Logger + once *sync.Once + + garden garden.Client + cf *client.Client + + containerMu sync.RWMutex + containers map[string]garden.ContainerInfo - component.StartFunc - component.ShutdownFunc + appMu sync.RWMutex + apps map[string]*resource.App } var _ extension.Extension = (*cfGardenObserver)(nil) -func newObserver(params extension.Settings, _ *Config) (extension.Extension, error) { - g := &cfGardenObserver{} - g.EndpointsWatcher = observer.NewEndpointsWatcher(g, time.Second, params.Logger) - +func newObserver(config *Config, logger *zap.Logger) (extension.Extension, error) { + g := &cfGardenObserver{ + config: config, + logger: logger, + once: &sync.Once{}, + containers: make(map[string]garden.ContainerInfo), + apps: make(map[string]*resource.App), + doneChan: make(chan struct{}), + } + g.EndpointsWatcher = observer.NewEndpointsWatcher(g, config.RefreshInterval, logger) return g, nil } +func (g *cfGardenObserver) SyncApps() error { + g.containerMu.RLock() + containers := g.containers + g.containerMu.RUnlock() + + g.appMu.Lock() + defer g.appMu.Unlock() + g.apps = make(map[string]*resource.App) + for _, info := range containers { + appID, ok := info.Properties[propertiesAppIDKey] + if !ok { + return fmt.Errorf("container properties do not have a `%s` field, required to fetch application labels", propertiesAppIDKey) + } + + if _, ok := g.apps[appID]; ok { + continue + } + + app, err := g.cf.Applications.Get(context.Background(), appID) + if err != nil { + return fmt.Errorf("error fetching application: %w", err) + } + g.apps[appID] = app + } + + return nil +} + +func (g *cfGardenObserver) App(info garden.ContainerInfo) (*resource.App, error) { + appID, ok := info.Properties[propertiesAppIDKey] + if !ok { + return nil, fmt.Errorf("container properties do not have a `%s` field, required to fetch application labels", propertiesAppIDKey) + } + + g.appMu.Lock() + defer g.appMu.Unlock() + app, ok := g.apps[appID] + if ok { + return app, nil + } + + app, err := g.cf.Applications.Get(context.Background(), appID) + if err != nil { + return nil, err + } + g.apps[appID] = app + + return app, nil +} + +func (g *cfGardenObserver) Start(_ context.Context, _ component.Host) error { + g.garden = gardenClient.New(gardenConnection.New("unix", g.config.Garden.Endpoint)) + + var err error + g.cf, err = newCfClient(g.config.CloudFoundry) + if err != nil { + return err + } + + if g.config.IncludeAppLabels { + g.once.Do( + func() { + go func() { + cacheRefreshTicker := time.NewTicker(g.config.CacheSyncInterval) + defer cacheRefreshTicker.Stop() + + for { + select { + case <-g.doneChan: + return + case <-cacheRefreshTicker.C: + err = g.SyncApps() + if err != nil { + g.logger.Error("could not sync app cache", zap.Error(err)) + } + } + } + }() + }, + ) + } + + return nil +} + +func (g *cfGardenObserver) Shutdown(_ context.Context) error { + close(g.doneChan) + return nil +} + func (g *cfGardenObserver) ListEndpoints() []observer.Endpoint { - // TODO: Implement the logic to list the endpoints. - endpoints := make([]observer.Endpoint, 0) + var endpoints []observer.Endpoint + + containers, err := g.garden.Containers(garden.Properties{}) + if err != nil { + g.logger.Error("could not list containers", zap.Error(err)) + return endpoints + } + + infos := make(map[string]garden.ContainerInfo) + for _, c := range containers { + info, err := c.Info() + if err != nil { + g.logger.Error("error getting container info", zap.String("handle", c.Handle()), zap.Error(err)) + continue + } + if info.State != containerStateActive { + continue + } + + endpoints = append(endpoints, g.containerEndpoints(c.Handle(), info)...) + infos[c.Handle()] = info + } + + go g.updateContainerCache(infos) return endpoints } + +// containerEndpoints generates a list of observer.Endpoint for a container, +// this is because a container might have more than one exposed ports +func (g *cfGardenObserver) containerEndpoints(handle string, info garden.ContainerInfo) []observer.Endpoint { + portsProp, ok := info.Properties[propertiesPortsKey] + if !ok { + g.logger.Error("could not discover container ports") + return nil + } + ports := strings.Split(portsProp, ",") + + var app *resource.App + var err error + if g.config.IncludeAppLabels { + app, err = g.App(info) + if err != nil { + g.logger.Error("error fetching application", zap.Error(err)) + return nil + } + } + + endpoints := []observer.Endpoint{} + for _, portString := range ports { + var port uint64 + port, err = strconv.ParseUint(portString, 10, 16) + if err != nil { + g.logger.Error("container port is not valid", zap.Error(err)) + continue + } + + details := &observer.Container{ + Name: handle, + ContainerID: handle, + Host: info.ContainerIP, + Port: uint16(port), + Transport: observer.ProtocolTCP, + Labels: g.containerLabels(info, app), + } + + endpoint := observer.Endpoint{ + ID: observer.EndpointID(fmt.Sprintf("%s:%d", details.ContainerID, details.Port)), + Target: fmt.Sprintf("%s:%d", details.Host, details.Port), + Details: details, + } + endpoints = append(endpoints, endpoint) + } + return endpoints +} + +func (g *cfGardenObserver) containerLabels(info garden.ContainerInfo, app *resource.App) map[string]string { + labels := make(map[string]string) + tags, err := parseTags(info) + if err != nil { + g.logger.Warn("not able to parse container tags into labels", zap.Error(err)) + return nil + } + for k, v := range tags { + labels[k] = v + } + + if app != nil { + for k, v := range app.Metadata.Labels { + labels[k] = *v + } + } + + return labels +} + +// The info.Properties contains a key called "log_config", which +// has contents that look like the following JSON encoded string: +// +// { +// "guid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", +// "index": 0, +// "source_name": "CELL", +// "tags": { +// "app_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", +// "app_name": "example-app", +// "instance_id": "0", +// "organization_id": "11111111-2222-3333-4444-555555555555", +// "organization_name": "example-org", +// "process_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", +// "process_instance_id": "abcdef12-3456-7890-abcd-ef1234567890", +// "process_type": "web", +// "source_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", +// "space_id": "99999999-8888-7777-6666-555555555555", +// "space_name": "example-space" +// } +// } +// +// We parse only the tags into a map, to be used as labels +func parseTags(info garden.ContainerInfo) (map[string]string, error) { + logConfig, ok := info.Properties[propertiesLogConfigKey] + if !ok { + return nil, fmt.Errorf("container properties do not have a `%s` field", propertiesLogConfigKey) + } + + var data map[string]any + err := json.Unmarshal([]byte(logConfig), &data) + if err != nil { + return nil, fmt.Errorf("error unmarshaling logConfig: %w", err) + } + + tags, ok := data[logConfigTagsKey].(map[string]any) + if !ok { + return nil, fmt.Errorf("expected tags field to be a map. got=%T", data[logConfigTagsKey]) + } + + result := make(map[string]string) + for key, value := range tags { + if strValue, ok := value.(string); ok { + result[key] = strValue + } + } + + return result, nil +} + +func newCfClient(cfConfig CfConfig) (*client.Client, error) { + var cfg *config.Config + var err error + + switch cfConfig.Auth.Type { + case authTypeUserPass: + cfg, err = config.New(cfConfig.Endpoint, config.UserPassword(cfConfig.Auth.Username, cfConfig.Auth.Password)) + case authTypeClientCredentials: + cfg, err = config.New(cfConfig.Endpoint, config.ClientCredentials(cfConfig.Auth.ClientID, cfConfig.Auth.ClientSecret)) + case authTypeToken: + cfg, err = config.New(cfConfig.Endpoint, config.Token(cfConfig.Auth.AccessToken, cfConfig.Auth.RefreshToken)) + } + + if err != nil { + return nil, fmt.Errorf("error creating connection to CloudFoundry API: %w", err) + } + + c, err := client.New(cfg) + if err != nil { + return nil, err + } + return c, nil +} + +func (g *cfGardenObserver) updateContainerCache(infos map[string]garden.ContainerInfo) { + g.containerMu.Lock() + defer g.containerMu.Unlock() + g.containers = infos +} diff --git a/extension/observer/cfgardenobserver/extension_test.go b/extension/observer/cfgardenobserver/extension_test.go index db7e9d753384..5bbdc5cea5d2 100644 --- a/extension/observer/cfgardenobserver/extension_test.go +++ b/extension/observer/cfgardenobserver/extension_test.go @@ -4,32 +4,244 @@ package cfgardenobserver import ( - "context" + "fmt" "testing" + "code.cloudfoundry.org/garden" + "github.com/cloudfoundry/go-cfclient/v3/resource" "github.com/stretchr/testify/require" - "go.opentelemetry.io/collector/component/componenttest" - "go.opentelemetry.io/collector/extension/extensiontest" + "go.opentelemetry.io/collector/component" + "go.uber.org/zap" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/cfgardenobserver/internal/metadata" ) -func TestStartAndStopObserver(t *testing.T) { - factory := NewFactory() - params := extensiontest.NewNopSettings() - ext, err := newObserver(params, factory.CreateDefaultConfig().(*Config)) +func strPtr(s string) *string { return &s } + +func TestContainerEndpoints(t *testing.T) { + handle := "14d91d46-6ebd-43a1-8e20-316d8e6a92a4" + ip := "1.2.3.4" + appID := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + logConfig := fmt.Sprintf(` +{ + "guid": "%s", + "index": 0, + "source_name": "CELL", + "tags": { + "app_id": "%s", + "app_name": "myapp" + } +} + `, handle, appID) + + tests := []struct { + name string + input garden.ContainerInfo + expected []observer.Endpoint + }{ + { + name: "single port", + input: garden.ContainerInfo{ + ContainerIP: ip, + Properties: map[string]string{ + "log_config": logConfig, + "network.ports": "8080", + "network.app_id": appID, + }, + }, + expected: []observer.Endpoint{ + { + ID: observer.EndpointID(fmt.Sprintf("%s:%d", handle, 8080)), + Target: fmt.Sprintf("%s:%d", ip, 8080), + Details: &observer.Container{ + Name: handle, + ContainerID: handle, + Host: ip, + Port: uint16(8080), + Transport: observer.ProtocolTCP, + Labels: map[string]string{ + "app_id": appID, + "app_name": "myapp", + }, + }, + }, + }, + }, + { + name: "multiple ports", + input: garden.ContainerInfo{ + ContainerIP: ip, + Properties: map[string]string{ + "log_config": logConfig, + "network.ports": "8080,9999", + "network.app_id": appID, + }, + }, + expected: []observer.Endpoint{ + { + ID: observer.EndpointID(fmt.Sprintf("%s:%d", handle, 8080)), + Target: fmt.Sprintf("%s:%d", ip, 8080), + Details: &observer.Container{ + Name: handle, + ContainerID: handle, + Host: ip, + Port: uint16(8080), + Transport: observer.ProtocolTCP, + Labels: map[string]string{ + "app_id": appID, + "app_name": "myapp", + }, + }, + }, + { + ID: observer.EndpointID(fmt.Sprintf("%s:%d", handle, 9999)), + Target: fmt.Sprintf("%s:%d", ip, 9999), + Details: &observer.Container{ + Name: handle, + ContainerID: handle, + Host: ip, + Port: uint16(9999), + Transport: observer.ProtocolTCP, + Labels: map[string]string{ + "app_id": appID, + "app_name": "myapp", + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + config := loadConfig(t, component.NewID(metadata.Type)) + ext, err := newObserver(config, zap.NewNop()) + require.NoError(t, err) + require.NotNil(t, ext) + + obs, ok := ext.(*cfGardenObserver) + require.True(t, ok) + require.Equal(t, tt.expected, obs.containerEndpoints(handle, tt.input)) + } +} + +func TestIncludeAppLabels(t *testing.T) { + handle := "14d91d46-6ebd-43a1-8e20-316d8e6a92a4" + ip := "1.2.3.4" + appID := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + logConfig := fmt.Sprintf(` +{ + "guid": "%s", + "index": 0, + "source_name": "CELL", + "tags": { + "app_id": "%s", + "app_name": "myapp" + } +} + `, handle, appID) + input := garden.ContainerInfo{ + ContainerIP: ip, + Properties: map[string]string{ + "log_config": logConfig, + "network.ports": "8080", + "network.app_id": appID, + }, + } + expected := []observer.Endpoint{ + { + ID: observer.EndpointID(fmt.Sprintf("%s:%d", handle, 8080)), + Target: fmt.Sprintf("%s:%d", ip, 8080), + Details: &observer.Container{ + Name: handle, + ContainerID: handle, + Host: ip, + Port: uint16(8080), + Transport: observer.ProtocolTCP, + Labels: map[string]string{ + "app_id": appID, + "app_name": "myapp", + "app_label": "app_value", + "app_label2": "app_value2", + }, + }, + }, + } + + extAllSettings := loadConfig(t, component.NewIDWithName(metadata.Type, "all_settings")) + ext, err := newObserver(extAllSettings, zap.NewNop()) require.NoError(t, err) require.NotNil(t, ext) - obvs, ok := ext.(*cfGardenObserver) + obs, ok := ext.(*cfGardenObserver) + obs.apps[appID] = &resource.App{ + Metadata: &resource.Metadata{ + Labels: map[string]*string{ + "app_label": strPtr("app_value"), + "app_label2": strPtr("app_value2"), + }, + }, + } require.True(t, ok) + require.Equal(t, expected, obs.containerEndpoints(handle, input)) +} + +func TestContainerLabels(t *testing.T) { + info := garden.ContainerInfo{ + Properties: map[string]string{ + "log_config": ` +{ + "guid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "index": 0, + "source_name": "CELL", + "tags": { + "app_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "app_name": "example-app", + "instance_id": "0", + "organization_id": "11111111-2222-3333-4444-555555555555", + "organization_name": "example-org", + "process_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "process_instance_id": "abcdef12-3456-7890-abcd-ef1234567890", + "process_type": "web", + "source_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "space_id": "99999999-8888-7777-6666-555555555555", + "space_name": "example-space" + } +} + `, + }, + } + app := &resource.App{ + Metadata: &resource.Metadata{ + Labels: map[string]*string{ + "key": strPtr("value"), + "key2": strPtr("value2"), + }, + }, + } + expected := map[string]string{ + "app_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "app_name": "example-app", + "instance_id": "0", + "organization_id": "11111111-2222-3333-4444-555555555555", + "organization_name": "example-org", + "process_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "process_instance_id": "abcdef12-3456-7890-abcd-ef1234567890", + "process_type": "web", + "source_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "space_id": "99999999-8888-7777-6666-555555555555", + "space_name": "example-space", + "key": "value", + "key2": "value2", + } - ctx := context.Background() - require.NoError(t, obvs.Start(ctx, componenttest.NewNopHost())) + factory := NewFactory() + ext, err := newObserver(factory.CreateDefaultConfig().(*Config), zap.NewNop()) + require.NoError(t, err) + require.NotNil(t, ext) - expected := obvs.ListEndpoints() - want := []observer.Endpoint{} - require.Equal(t, want, expected) + obs, ok := ext.(*cfGardenObserver) + require.True(t, ok) - require.NoError(t, obvs.Shutdown(ctx)) + require.Equal(t, expected, obs.containerLabels(info, app)) } diff --git a/extension/observer/cfgardenobserver/factory.go b/extension/observer/cfgardenobserver/factory.go index f4a40e15fbe2..2881e1029f00 100644 --- a/extension/observer/cfgardenobserver/factory.go +++ b/extension/observer/cfgardenobserver/factory.go @@ -15,10 +15,11 @@ import ( const ( defaultCollectionInterval = 1 * time.Minute - defaultEndpoint = "unix:///var/vcap/data/garden/garden.sock" + defaultCacheSyncInterval = 5 * time.Minute + defaultEndpoint = "/var/vcap/data/garden/garden.sock" ) -// NewFactory creates a factory for HostObserver extension. +// NewFactory creates a factory for CfGardenObserver extension. func NewFactory() extension.Factory { return extension.NewFactory( metadata.Type, @@ -30,15 +31,18 @@ func NewFactory() extension.Factory { func createDefaultConfig() component.Config { return &Config{ - RefreshInterval: defaultCollectionInterval, - Endpoint: defaultEndpoint, + RefreshInterval: defaultCollectionInterval, + CacheSyncInterval: defaultCacheSyncInterval, + Garden: GardenConfig{ + Endpoint: defaultEndpoint, + }, } } func createExtension( _ context.Context, - params extension.Settings, + settings extension.Settings, cfg component.Config, ) (extension.Extension, error) { - return newObserver(params, cfg.(*Config)) + return newObserver(cfg.(*Config), settings.Logger) } diff --git a/extension/observer/cfgardenobserver/generated_component_test.go b/extension/observer/cfgardenobserver/generated_component_test.go index d3581e681e1b..30f52c8571a5 100644 --- a/extension/observer/cfgardenobserver/generated_component_test.go +++ b/extension/observer/cfgardenobserver/generated_component_test.go @@ -3,13 +3,10 @@ package cfgardenobserver import ( - "context" "testing" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component/componenttest" - "go.opentelemetry.io/collector/confmap/confmaptest" - "go.opentelemetry.io/collector/extension/extensiontest" ) func TestComponentFactoryType(t *testing.T) { @@ -19,31 +16,3 @@ func TestComponentFactoryType(t *testing.T) { func TestComponentConfigStruct(t *testing.T) { require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig())) } - -func TestComponentLifecycle(t *testing.T) { - factory := NewFactory() - - cm, err := confmaptest.LoadConf("metadata.yaml") - require.NoError(t, err) - cfg := factory.CreateDefaultConfig() - sub, err := cm.Sub("tests::config") - require.NoError(t, err) - require.NoError(t, sub.Unmarshal(&cfg)) - t.Run("shutdown", func(t *testing.T) { - e, err := factory.Create(context.Background(), extensiontest.NewNopSettings(), cfg) - require.NoError(t, err) - err = e.Shutdown(context.Background()) - require.NoError(t, err) - }) - t.Run("lifecycle", func(t *testing.T) { - firstExt, err := factory.Create(context.Background(), extensiontest.NewNopSettings(), cfg) - require.NoError(t, err) - require.NoError(t, firstExt.Start(context.Background(), componenttest.NewNopHost())) - require.NoError(t, firstExt.Shutdown(context.Background())) - - secondExt, err := factory.Create(context.Background(), extensiontest.NewNopSettings(), cfg) - require.NoError(t, err) - require.NoError(t, secondExt.Start(context.Background(), componenttest.NewNopHost())) - require.NoError(t, secondExt.Shutdown(context.Background())) - }) -} diff --git a/extension/observer/cfgardenobserver/go.mod b/extension/observer/cfgardenobserver/go.mod index f89490e94264..a6fdcad4abdc 100644 --- a/extension/observer/cfgardenobserver/go.mod +++ b/extension/observer/cfgardenobserver/go.mod @@ -3,27 +3,42 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/extension/obser go 1.22.0 require ( + code.cloudfoundry.org/garden v0.0.0-20241023020423-a21e43a17f84 + github.com/cloudfoundry/go-cfclient/v3 v3.0.0-alpha.9 github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.112.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/collector/component v0.112.0 go.opentelemetry.io/collector/confmap v1.18.0 go.opentelemetry.io/collector/extension v0.112.0 go.uber.org/goleak v1.3.0 + go.uber.org/zap v1.27.0 ) require ( + code.cloudfoundry.org/lager/v3 v3.11.0 // indirect + github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f // indirect + github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/pprof v0.0.0-20241023014458-598669927662 // indirect github.com/google/uuid v1.6.0 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect github.com/knadh/koanf/providers/confmap v0.1.0 // indirect github.com/knadh/koanf/v2 v2.1.1 // indirect + github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/nxadm/tail v1.4.11 // indirect + github.com/onsi/ginkgo/v2 v2.20.2 // indirect + github.com/openzipkin/zipkin-go v0.4.3 // indirect + github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/tedsuo/rata v1.0.0 // indirect go.opentelemetry.io/collector/config/configtelemetry v0.112.0 // indirect go.opentelemetry.io/collector/pdata v1.18.0 // indirect go.opentelemetry.io/otel v1.31.0 // indirect @@ -32,11 +47,12 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - golang.org/x/net v0.28.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/tools v0.26.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect google.golang.org/grpc v1.67.1 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/extension/observer/cfgardenobserver/go.sum b/extension/observer/cfgardenobserver/go.sum index cd61dfc37999..8ec67db2534b 100644 --- a/extension/observer/cfgardenobserver/go.sum +++ b/extension/observer/cfgardenobserver/go.sum @@ -1,16 +1,35 @@ +code.cloudfoundry.org/garden v0.0.0-20241023020423-a21e43a17f84 h1:ceT1k/IxtWDe2tNSbT7THT70MIzguBbdbuo09kTREy4= +code.cloudfoundry.org/garden v0.0.0-20241023020423-a21e43a17f84/go.mod h1:NFRJ0BR30ide5DZ2hgfjiODah9mQStc/Jx3jsMIf+ww= +code.cloudfoundry.org/lager/v3 v3.11.0 h1:YjRSl1USPIz1FFZPVvwFYgXUUodSIWDdXaLVvME3jZg= +code.cloudfoundry.org/lager/v3 v3.11.0/go.mod h1:8Ot/5fRETEzdf9U3N1kCWp00oZteNwew5Cl2Ck3t2ho= +github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f h1:gOO/tNZMjjvTKZWpY7YnXC72ULNLErRtp94LountVE8= +github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= +github.com/cloudfoundry/go-cfclient/v3 v3.0.0-alpha.9 h1:HK3+nJEPgwlhc5H74aw/V4mVowqWaTKGjHONdVQQ2Vw= +github.com/cloudfoundry/go-cfclient/v3 v3.0.0-alpha.9/go.mod h1:eUjFfpsU3lRv388wKlXMmkQfsJ9pveUHZEia7AoBCPY= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20241023014458-598669927662 h1:SKMkD83p7FwUqKmBsPdLHF5dNyxq3jOWwu9w9UyH5vA= +github.com/google/pprof v0.0.0-20241023014458-598669927662/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -25,18 +44,36 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 h1:YFh+sjyJTMQSYjKwM4dFKhJPJC/wfo98tPUc17HdoYw= +github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= +github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= +github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= +github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= +github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= +github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tedsuo/rata v1.0.0 h1:Sf9aZrYy6ElSTncjnGkyC2yuVvz5YJetBIUKJ4CmeKE= +github.com/tedsuo/rata v1.0.0/go.mod h1:X47ELzhOoLbfFIY0Cql9P6yo3Cdwf2CMX3FVZxRzJPc= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/collector/component v0.112.0 h1:Hw125Tdb427yKkzFx3U/OsfPATYXsbURkc27dn19he8= @@ -74,30 +111,35 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= @@ -105,5 +147,7 @@ google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojt gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/extension/observer/cfgardenobserver/internal/metadata/generated_status.go b/extension/observer/cfgardenobserver/internal/metadata/generated_status.go index 547bd8c0b7e6..b198899f25a8 100644 --- a/extension/observer/cfgardenobserver/internal/metadata/generated_status.go +++ b/extension/observer/cfgardenobserver/internal/metadata/generated_status.go @@ -8,7 +8,7 @@ import ( var ( Type = component.MustNewType("cfgarden_observer") - ScopeName = "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/cfgardenobserver" + ScopeName = "otelcol/cfgardenobserver" ) const ( diff --git a/extension/observer/cfgardenobserver/metadata.yaml b/extension/observer/cfgardenobserver/metadata.yaml index 1db3578ed443..30afcb4e273c 100644 --- a/extension/observer/cfgardenobserver/metadata.yaml +++ b/extension/observer/cfgardenobserver/metadata.yaml @@ -1,8 +1,14 @@ type: cfgarden_observer +scope_name: otelcol/cfgardenobserver status: class: extension stability: development: [extension] codeowners: - active: [crobert-1, cemdk, tomasmota, m1rp, jriguera] + active: [crobert-1, cemdk, m1rp, jriguera] + +# We don't want to make actual connections to CloudFoundry api in our tests +tests: + skip_lifecycle: true + skip_shutdown: true diff --git a/extension/observer/cfgardenobserver/testdata/config.yaml b/extension/observer/cfgardenobserver/testdata/config.yaml index a1a9b8375759..b8d9502727f7 100644 --- a/extension/observer/cfgardenobserver/testdata/config.yaml +++ b/extension/observer/cfgardenobserver/testdata/config.yaml @@ -1,4 +1,36 @@ -cfgarden_observer: cfgarden_observer/all_settings: + cache_sync_interval: 5s refresh_interval: 20s - endpoint: unix:///var/vcap/data/garden/custom.sock + include_app_labels: true + garden: + endpoint: /var/vcap/data/garden/custom.sock + cloud_foundry: + endpoint: https://api.cf.mydomain.com + auth: + type: user_pass + username: myuser + password: mypass +cfgarden_observer/user_pass: + include_app_labels: true + cloud_foundry: + endpoint: https://api.cf.mydomain.com + auth: + type: user_pass + username: myuser + password: mypass +cfgarden_observer/client_credentials: + include_app_labels: true + cloud_foundry: + endpoint: https://api.cf.mydomain.com + auth: + type: client_credentials + client_id: myclientid + client_secret: myclientsecret +cfgarden_observer/token: + include_app_labels: true + cloud_foundry: + endpoint: https://api.cf.mydomain.com + auth: + type: token + access_token: myaccesstoken + refresh_token: myrefreshtoken