-
Notifications
You must be signed in to change notification settings - Fork 486
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add component discovery.dockerswarm to flow (#5181)
* Add component discovery.dockerswarm to flow * minor cleanup * Update docs/sources/flow/reference/components/discovery.dockerswarm.md Co-authored-by: Robert Fratto <[email protected]> * improve doc --------- Co-authored-by: Robert Fratto <[email protected]>
- Loading branch information
Showing
14 changed files
with
657 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package dockerswarm | ||
|
||
import ( | ||
"fmt" | ||
"net/url" | ||
"time" | ||
|
||
"github.com/grafana/agent/component" | ||
"github.com/grafana/agent/component/common/config" | ||
"github.com/grafana/agent/component/discovery" | ||
"github.com/prometheus/common/model" | ||
prom_discovery "github.com/prometheus/prometheus/discovery/moby" | ||
) | ||
|
||
func init() { | ||
component.Register(component.Registration{ | ||
Name: "discovery.dockerswarm", | ||
Args: Arguments{}, | ||
Exports: discovery.Exports{}, | ||
|
||
Build: func(opts component.Options, args component.Arguments) (component.Component, error) { | ||
return New(opts, args.(Arguments)) | ||
}, | ||
}) | ||
} | ||
|
||
type Arguments struct { | ||
Host string `river:"host,attr"` | ||
Role string `river:"role,attr"` | ||
Port int `river:"port,attr,optional"` | ||
Filters []Filter `river:"filter,block,optional"` | ||
RefreshInterval time.Duration `river:"refresh_interval,attr,optional"` | ||
HTTPClientConfig config.HTTPClientConfig `river:",squash"` | ||
} | ||
|
||
type Filter struct { | ||
Name string `river:"name,attr"` | ||
Values []string `river:"values,attr"` | ||
} | ||
|
||
var DefaultArguments = Arguments{ | ||
RefreshInterval: 60 * time.Second, | ||
Port: 80, | ||
HTTPClientConfig: config.DefaultHTTPClientConfig, | ||
} | ||
|
||
// SetToDefault implements river.Defaulter. | ||
func (a *Arguments) SetToDefault() { | ||
*a = DefaultArguments | ||
} | ||
|
||
// Validate implements river.Validator. | ||
func (a *Arguments) Validate() error { | ||
if _, err := url.Parse(a.Host); err != nil { | ||
return err | ||
} | ||
if a.RefreshInterval <= 0 { | ||
return fmt.Errorf("refresh_interval must be greater than 0") | ||
} | ||
switch a.Role { | ||
case "services", "nodes", "tasks": | ||
default: | ||
return fmt.Errorf("invalid role %s, expected tasks, services, or nodes", a.Role) | ||
} | ||
return a.HTTPClientConfig.Validate() | ||
} | ||
|
||
// Convert converts Arguments into the SDConfig type. | ||
func (a *Arguments) Convert() *prom_discovery.DockerSwarmSDConfig { | ||
return &prom_discovery.DockerSwarmSDConfig{ | ||
Host: a.Host, | ||
Role: a.Role, | ||
Port: a.Port, | ||
Filters: convertFilters(a.Filters), | ||
RefreshInterval: model.Duration(a.RefreshInterval), | ||
HTTPClientConfig: *a.HTTPClientConfig.Convert(), | ||
} | ||
} | ||
|
||
func convertFilters(filters []Filter) []prom_discovery.Filter { | ||
promFilters := make([]prom_discovery.Filter, len(filters)) | ||
for i, filter := range filters { | ||
promFilters[i] = filter.convert() | ||
} | ||
return promFilters | ||
} | ||
|
||
func (f *Filter) convert() prom_discovery.Filter { | ||
values := make([]string, len(f.Values)) | ||
copy(values, f.Values) | ||
|
||
return prom_discovery.Filter{ | ||
Name: f.Name, | ||
Values: values, | ||
} | ||
} | ||
|
||
// New returns a new instance of discovery.dockerswarm component. | ||
func New(opts component.Options, args Arguments) (*discovery.Component, error) { | ||
return discovery.New(opts, args, func(args component.Arguments) (discovery.Discoverer, error) { | ||
newArgs := args.(Arguments) | ||
return prom_discovery.NewDiscovery(newArgs.Convert(), opts.Logger) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package dockerswarm | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/grafana/agent/component/common/config" | ||
"github.com/grafana/river" | ||
"github.com/grafana/river/rivertypes" | ||
promConfig "github.com/prometheus/common/config" | ||
"github.com/prometheus/common/model" | ||
"github.com/stretchr/testify/require" | ||
"gotest.tools/assert" | ||
) | ||
|
||
func TestRiverUnmarshal(t *testing.T) { | ||
riverCfg := ` | ||
host = "unix:///var/run/docker.sock" | ||
role = "nodes" | ||
port = 81 | ||
filter { | ||
name = "n1" | ||
values = ["v11", "v12"] | ||
} | ||
filter { | ||
name = "n2" | ||
values = ["v21"] | ||
} | ||
refresh_interval = "12s" | ||
basic_auth { | ||
username = "username" | ||
password = "pass" | ||
} | ||
` | ||
|
||
var args Arguments | ||
err := river.Unmarshal([]byte(riverCfg), &args) | ||
require.NoError(t, err) | ||
require.ElementsMatch(t, []Filter{{"n1", []string{"v11", "v12"}}, {"n2", []string{"v21"}}}, args.Filters) | ||
assert.Equal(t, "unix:///var/run/docker.sock", args.Host) | ||
assert.Equal(t, "nodes", args.Role) | ||
assert.Equal(t, 81, args.Port) | ||
assert.Equal(t, 12*time.Second, args.RefreshInterval) | ||
assert.Equal(t, "username", args.HTTPClientConfig.BasicAuth.Username) | ||
assert.Equal(t, rivertypes.Secret("pass"), args.HTTPClientConfig.BasicAuth.Password) | ||
} | ||
|
||
func TestConvert(t *testing.T) { | ||
riverArgs := Arguments{ | ||
Host: "host", | ||
Role: "nodes", | ||
Port: 81, | ||
Filters: []Filter{{"n1", []string{"v11", "v12"}}, {"n2", []string{"v21"}}}, | ||
RefreshInterval: time.Minute, | ||
HTTPClientConfig: config.HTTPClientConfig{ | ||
BasicAuth: &config.BasicAuth{ | ||
Username: "username", | ||
Password: "pass", | ||
}, | ||
}, | ||
} | ||
|
||
promArgs := riverArgs.Convert() | ||
assert.Equal(t, 2, len(promArgs.Filters)) | ||
assert.Equal(t, "n1", promArgs.Filters[0].Name) | ||
require.ElementsMatch(t, []string{"v11", "v12"}, promArgs.Filters[0].Values) | ||
assert.Equal(t, "n2", promArgs.Filters[1].Name) | ||
require.ElementsMatch(t, []string{"v21"}, promArgs.Filters[1].Values) | ||
assert.Equal(t, "host", promArgs.Host) | ||
assert.Equal(t, "nodes", promArgs.Role) | ||
assert.Equal(t, 81, promArgs.Port) | ||
assert.Equal(t, model.Duration(time.Minute), promArgs.RefreshInterval) | ||
assert.Equal(t, "username", promArgs.HTTPClientConfig.BasicAuth.Username) | ||
assert.Equal(t, promConfig.Secret("pass"), promArgs.HTTPClientConfig.BasicAuth.Password) | ||
} | ||
|
||
func TestValidateRole(t *testing.T) { | ||
riverArgs := Arguments{ | ||
Host: "host", | ||
Role: "nodes", | ||
RefreshInterval: time.Second, | ||
} | ||
err := riverArgs.Validate() | ||
require.NoError(t, err) | ||
|
||
riverArgs.Role = "services" | ||
err = riverArgs.Validate() | ||
require.NoError(t, err) | ||
|
||
riverArgs.Role = "tasks" | ||
err = riverArgs.Validate() | ||
require.NoError(t, err) | ||
|
||
riverArgs.Role = "wrong" | ||
err = riverArgs.Validate() | ||
assert.Error(t, err, "invalid role wrong, expected tasks, services, or nodes") | ||
} | ||
|
||
func TestValidateUrl(t *testing.T) { | ||
riverArgs := Arguments{ | ||
Host: "::", | ||
Role: "nodes", | ||
RefreshInterval: time.Second, | ||
} | ||
err := riverArgs.Validate() | ||
assert.Error(t, err, "parse \"::\": missing protocol scheme") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package prometheusconvert | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/grafana/agent/component/discovery" | ||
"github.com/grafana/agent/component/discovery/dockerswarm" | ||
"github.com/grafana/agent/converter/diag" | ||
"github.com/grafana/agent/converter/internal/common" | ||
prom_moby "github.com/prometheus/prometheus/discovery/moby" | ||
) | ||
|
||
func appendDiscoveryDockerswarm(pb *prometheusBlocks, label string, sdConfig *prom_moby.DockerSwarmSDConfig) discovery.Exports { | ||
discoveryDockerswarmArgs := toDiscoveryDockerswarm(sdConfig) | ||
name := []string{"discovery", "dockerswarm"} | ||
block := common.NewBlockWithOverride(name, label, discoveryDockerswarmArgs) | ||
pb.discoveryBlocks = append(pb.discoveryBlocks, newPrometheusBlock(block, name, label, "", "")) | ||
return NewDiscoveryExports("discovery.dockerswarm." + label + ".targets") | ||
} | ||
|
||
func validateDiscoveryDockerswarm(sdConfig *prom_moby.DockerSwarmSDConfig) diag.Diagnostics { | ||
return ValidateHttpClientConfig(&sdConfig.HTTPClientConfig) | ||
} | ||
|
||
func toDiscoveryDockerswarm(sdConfig *prom_moby.DockerSwarmSDConfig) *dockerswarm.Arguments { | ||
if sdConfig == nil { | ||
return nil | ||
} | ||
|
||
return &dockerswarm.Arguments{ | ||
Host: sdConfig.Host, | ||
Role: sdConfig.Role, | ||
Port: sdConfig.Port, | ||
Filters: convertFilters(sdConfig.Filters), | ||
RefreshInterval: time.Duration(sdConfig.RefreshInterval), | ||
HTTPClientConfig: *ToHttpClientConfig(&sdConfig.HTTPClientConfig), | ||
} | ||
} | ||
|
||
func convertFilters(mobyFilters []prom_moby.Filter) []dockerswarm.Filter { | ||
riverFilters := make([]dockerswarm.Filter, len(mobyFilters)) | ||
for i, mobyFilter := range mobyFilters { | ||
riverFilters[i] = convertFilter(&mobyFilter) | ||
} | ||
return riverFilters | ||
} | ||
|
||
func convertFilter(mobyFilter *prom_moby.Filter) dockerswarm.Filter { | ||
values := make([]string, len(mobyFilter.Values)) | ||
copy(values, mobyFilter.Values) | ||
|
||
return dockerswarm.Filter{ | ||
Name: mobyFilter.Name, | ||
Values: values, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
converter/internal/prometheusconvert/testdata/dockerswarm.river
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
discovery.dockerswarm "prometheus1" { | ||
host = "http://localhost:8081" | ||
role = "nodes" | ||
port = 81 | ||
|
||
filter { | ||
name = "n1" | ||
values = ["v1", "v2"] | ||
} | ||
|
||
filter { | ||
name = "n2" | ||
values = ["v3"] | ||
} | ||
refresh_interval = "5m0s" | ||
|
||
basic_auth { | ||
username = "username" | ||
password = "password" | ||
} | ||
} | ||
|
||
discovery.dockerswarm "prometheus2" { | ||
host = "http://localhost:8080" | ||
role = "services" | ||
} | ||
|
||
prometheus.scrape "prometheus1" { | ||
targets = concat( | ||
discovery.dockerswarm.prometheus1.targets, | ||
[{ | ||
__address__ = "localhost:9090", | ||
}], | ||
) | ||
forward_to = [prometheus.remote_write.default.receiver] | ||
job_name = "prometheus1" | ||
} | ||
|
||
prometheus.scrape "prometheus2" { | ||
targets = discovery.dockerswarm.prometheus2.targets | ||
forward_to = [prometheus.remote_write.default.receiver] | ||
job_name = "prometheus2" | ||
} | ||
|
||
prometheus.remote_write "default" { | ||
endpoint { | ||
name = "remote1" | ||
url = "http://remote-write-url1" | ||
|
||
queue_config { } | ||
|
||
metadata_config { } | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
converter/internal/prometheusconvert/testdata/dockerswarm.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
scrape_configs: | ||
- job_name: "prometheus1" | ||
static_configs: | ||
- targets: ["localhost:9090"] | ||
dockerswarm_sd_configs: | ||
- host : "http://localhost:8081" | ||
role: "nodes" | ||
port: 81 | ||
filters: | ||
- name: "n1" | ||
values: | ||
- "v1" | ||
- "v2" | ||
- name: "n2" | ||
values: | ||
- "v3" | ||
refresh_interval: 5m | ||
basic_auth: | ||
username: "username" | ||
password: "password" | ||
|
||
- job_name: "prometheus2" | ||
dockerswarm_sd_configs: | ||
- host : "http://localhost:8080" | ||
role: "services" | ||
|
||
remote_write: | ||
- name: "remote1" | ||
url: "http://remote-write-url1" |
Oops, something went wrong.