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