diff options
Diffstat (limited to '')
15 files changed, 1203 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/dockerhub/README.md b/src/go/collectors/go.d.plugin/modules/dockerhub/README.md new file mode 120000 index 000000000..703add4ed --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/dockerhub/README.md @@ -0,0 +1 @@ +integrations/docker_hub_repository.md
\ No newline at end of file diff --git a/src/go/collectors/go.d.plugin/modules/dockerhub/apiclient.go b/src/go/collectors/go.d.plugin/modules/dockerhub/apiclient.go new file mode 100644 index 000000000..fa6e1c805 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/dockerhub/apiclient.go @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dockerhub + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + + "github.com/netdata/netdata/go/go.d.plugin/pkg/web" +) + +type repository struct { + User string + Name string + Status int + StarCount int `json:"star_count"` + PullCount int `json:"pull_count"` + LastUpdated string `json:"last_updated"` +} + +func newAPIClient(client *http.Client, request web.Request) *apiClient { + return &apiClient{httpClient: client, request: request} +} + +type apiClient struct { + httpClient *http.Client + request web.Request +} + +func (a apiClient) getRepository(repoName string) (*repository, error) { + req, err := a.createRequest(repoName) + if err != nil { + return nil, fmt.Errorf("error on creating http request : %v", err) + } + + resp, err := a.doRequestOK(req) + defer closeBody(resp) + if err != nil { + return nil, err + } + + var repo repository + if err := json.NewDecoder(resp.Body).Decode(&repo); err != nil { + return nil, fmt.Errorf("error on parsing response from %s : %v", req.URL, err) + } + + return &repo, nil +} + +func (a apiClient) doRequestOK(req *http.Request) (*http.Response, error) { + resp, err := a.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("error on request: %v", err) + } + + if resp.StatusCode != http.StatusOK { + return resp, fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode) + } + return resp, nil +} + +func (a apiClient) createRequest(urlPath string) (*http.Request, error) { + req := a.request.Copy() + u, err := url.Parse(req.URL) + if err != nil { + return nil, err + } + + u.Path = path.Join(u.Path, urlPath) + req.URL = u.String() + return web.NewHTTPRequest(req) +} + +func closeBody(resp *http.Response) { + if resp != nil && resp.Body != nil { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + } +} diff --git a/src/go/collectors/go.d.plugin/modules/dockerhub/charts.go b/src/go/collectors/go.d.plugin/modules/dockerhub/charts.go new file mode 100644 index 000000000..07ba8e18b --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/dockerhub/charts.go @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dockerhub + +import ( + "strings" + + "github.com/netdata/netdata/go/go.d.plugin/agent/module" +) + +type ( + // Charts is an alias for module.Charts + Charts = module.Charts + // Dims is an alias for module.Dims + Dims = module.Dims + // Dim is an alias for module.Dim + Dim = module.Dim +) + +var charts = Charts{ + { + ID: "pulls_sum", + Title: "Pulls Summary", + Units: "pulls", + Fam: "pulls", + Dims: Dims{ + {ID: "pull_sum", Name: "sum"}, + }, + }, + { + ID: "pulls", + Title: "Pulls", + Units: "pulls", + Fam: "pulls", + Type: module.Stacked, + }, + { + ID: "pulls_rate", + Title: "Pulls Rate", + Units: "pulls/s", + Fam: "pulls", + Type: module.Stacked, + }, + { + ID: "stars", + Title: "Stars", + Units: "stars", + Fam: "stars", + Type: module.Stacked, + }, + { + ID: "status", + Title: "Current Status", + Units: "status", + Fam: "status", + }, + { + ID: "last_updated", + Title: "Time Since Last Updated", + Units: "seconds", + Fam: "last updated", + }, +} + +func addReposToCharts(repositories []string, cs *Charts) { + for _, name := range repositories { + dimName := strings.Replace(name, "/", "_", -1) + _ = cs.Get("pulls").AddDim(&Dim{ + ID: "pull_count_" + name, + Name: dimName, + }) + _ = cs.Get("pulls_rate").AddDim(&Dim{ + ID: "pull_count_" + name, + Name: dimName, + Algo: module.Incremental, + }) + _ = cs.Get("stars").AddDim(&Dim{ + ID: "star_count_" + name, + Name: dimName, + }) + _ = cs.Get("status").AddDim(&Dim{ + ID: "status_" + name, + Name: dimName, + }) + _ = cs.Get("last_updated").AddDim(&Dim{ + ID: "last_updated_" + name, + Name: dimName, + }) + } +} diff --git a/src/go/collectors/go.d.plugin/modules/dockerhub/collect.go b/src/go/collectors/go.d.plugin/modules/dockerhub/collect.go new file mode 100644 index 000000000..211c1ea7c --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/dockerhub/collect.go @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dockerhub + +import ( + "fmt" + "time" +) + +func (dh *DockerHub) collect() (map[string]int64, error) { + var ( + reposNum = len(dh.Repositories) + ch = make(chan *repository, reposNum) + mx = make(map[string]int64) + ) + + for _, name := range dh.Repositories { + go dh.collectRepo(name, ch) + } + + var ( + parsed int + pullSum int + ) + + for i := 0; i < reposNum; i++ { + repo := <-ch + if repo == nil { + continue + } + if err := parseRepoTo(repo, mx); err != nil { + dh.Errorf("error on parsing %s/%s : %v", repo.User, repo.Name, err) + continue + } + pullSum += repo.PullCount + parsed++ + } + close(ch) + + if parsed == reposNum { + mx["pull_sum"] = int64(pullSum) + } + + return mx, nil +} + +func (dh *DockerHub) collectRepo(repoName string, ch chan *repository) { + repo, err := dh.client.getRepository(repoName) + if err != nil { + dh.Error(err) + } + ch <- repo +} + +func parseRepoTo(repo *repository, mx map[string]int64) error { + t, err := time.Parse(time.RFC3339Nano, repo.LastUpdated) + if err != nil { + return err + } + mx[fmt.Sprintf("last_updated_%s/%s", repo.User, repo.Name)] = int64(time.Since(t).Seconds()) + mx[fmt.Sprintf("star_count_%s/%s", repo.User, repo.Name)] = int64(repo.StarCount) + mx[fmt.Sprintf("pull_count_%s/%s", repo.User, repo.Name)] = int64(repo.PullCount) + mx[fmt.Sprintf("status_%s/%s", repo.User, repo.Name)] = int64(repo.Status) + return nil +} diff --git a/src/go/collectors/go.d.plugin/modules/dockerhub/config_schema.json b/src/go/collectors/go.d.plugin/modules/dockerhub/config_schema.json new file mode 100644 index 000000000..47842fd9b --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/dockerhub/config_schema.json @@ -0,0 +1,197 @@ +{ + "jsonSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DockerHub collector configuration.", + "type": "object", + "properties": { + "update_every": { + "title": "Update every", + "description": "Data collection interval, measured in seconds.", + "type": "integer", + "minimum": 1, + "default": 5 + }, + "url": { + "title": "URL", + "description": "The URL of the DockerHub repositories endpoint.", + "type": "string", + "default": "https://hub.docker.com/v2/repositories", + "format": "uri" + }, + "timeout": { + "title": "Timeout", + "description": "The timeout in seconds for the HTTP request.", + "type": "number", + "minimum": 0.5, + "default": 2 + }, + "repositories": { + "title": "Repositories", + "description": "List of repositories to monitor.", + "type": [ + "array", + "null" + ], + "items": { + "title": "Name", + "description": "The name of the repository.", + "type": "string" + }, + "uniqueItems": true, + "minItems": 1 + }, + "not_follow_redirects": { + "title": "Not follow redirects", + "description": "If set, the client will not follow HTTP redirects automatically.", + "type": "boolean" + }, + "username": { + "title": "Username", + "description": "The username for basic authentication.", + "type": "string", + "sensitive": true + }, + "password": { + "title": "Password", + "description": "The password for basic authentication.", + "type": "string", + "sensitive": true + }, + "proxy_url": { + "title": "Proxy URL", + "description": "The URL of the proxy server.", + "type": "string" + }, + "proxy_username": { + "title": "Proxy username", + "description": "The username for proxy authentication.", + "type": "string", + "sensitive": true + }, + "proxy_password": { + "title": "Proxy password", + "description": "The password for proxy authentication.", + "type": "string", + "sensitive": true + }, + "headers": { + "title": "Headers", + "description": "Additional HTTP headers to include in the request.", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "tls_skip_verify": { + "title": "Skip TLS verification", + "description": "If set, TLS certificate verification will be skipped.", + "type": "boolean" + }, + "tls_ca": { + "title": "TLS CA", + "description": "The path to the CA certificate file for TLS verification.", + "type": "string", + "pattern": "^$|^/" + }, + "tls_cert": { + "title": "TLS certificate", + "description": "The path to the client certificate file for TLS authentication.", + "type": "string", + "pattern": "^$|^/" + }, + "tls_key": { + "title": "TLS key", + "description": "The path to the client key file for TLS authentication.", + "type": "string", + "pattern": "^$|^/" + }, + "body": { + "title": "Body", + "type": "string" + }, + "method": { + "title": "Method", + "type": "string" + } + }, + "required": [ + "url", + "repositories" + ], + "additionalProperties": false, + "patternProperties": { + "^name$": {} + } + }, + "uiSchema": { + "ui:flavour": "tabs", + "ui:options": { + "tabs": [ + { + "title": "Base", + "fields": [ + "update_every", + "url", + "timeout", + "repositories", + "not_follow_redirects" + ] + }, + { + "title": "Auth", + "fields": [ + "username", + "password" + ] + }, + { + "title": "TLS", + "fields": [ + "tls_skip_verify", + "tls_ca", + "tls_cert", + "tls_key" + ] + }, + { + "title": "Proxy", + "fields": [ + "proxy_url", + "proxy_username", + "proxy_password" + ] + }, + { + "title": "Headers", + "fields": [ + "headers" + ] + } + ] + }, + "uiOptions": { + "fullPage": true + }, + "body": { + "ui:widget": "hidden" + }, + "method": { + "ui:widget": "hidden" + }, + "timeout": { + "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)." + }, + "repositories": { + "ui:listFlavour": "list" + }, + "password": { + "ui:widget": "password" + }, + "proxy_password": { + "ui:widget": "password" + } + } +} diff --git a/src/go/collectors/go.d.plugin/modules/dockerhub/dockerhub.go b/src/go/collectors/go.d.plugin/modules/dockerhub/dockerhub.go new file mode 100644 index 000000000..54fcf7dce --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/dockerhub/dockerhub.go @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dockerhub + +import ( + _ "embed" + "errors" + "time" + + "github.com/netdata/netdata/go/go.d.plugin/agent/module" + "github.com/netdata/netdata/go/go.d.plugin/pkg/web" +) + +//go:embed "config_schema.json" +var configSchema string + +func init() { + module.Register("dockerhub", module.Creator{ + JobConfigSchema: configSchema, + Defaults: module.Defaults{ + UpdateEvery: 5, + }, + Create: func() module.Module { return New() }, + Config: func() any { return &Config{} }, + }) +} + +func New() *DockerHub { + return &DockerHub{ + Config: Config{ + HTTP: web.HTTP{ + Request: web.Request{ + URL: "https://hub.docker.com/v2/repositories", + }, + Client: web.Client{ + Timeout: web.Duration(time.Second * 2), + }, + }, + }, + } +} + +type Config struct { + UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"` + web.HTTP `yaml:",inline" json:""` + Repositories []string `yaml:"repositories" json:"repositories"` +} + +type DockerHub struct { + module.Base + Config `yaml:",inline" json:""` + + client *apiClient +} + +func (dh *DockerHub) Configuration() any { + return dh.Config +} + +func (dh *DockerHub) Init() error { + if err := dh.validateConfig(); err != nil { + dh.Errorf("config validation: %v", err) + return err + } + + client, err := dh.initApiClient() + if err != nil { + dh.Error(err) + return err + } + dh.client = client + + return nil +} + +func (dh *DockerHub) Check() error { + mx, err := dh.collect() + if err != nil { + dh.Error(err) + return err + } + if len(mx) == 0 { + return errors.New("no metrics collected") + + } + return nil +} + +func (dh *DockerHub) Charts() *Charts { + cs := charts.Copy() + addReposToCharts(dh.Repositories, cs) + return cs +} + +func (dh *DockerHub) Collect() map[string]int64 { + mx, err := dh.collect() + + if err != nil { + dh.Error(err) + return nil + } + + return mx +} + +func (dh *DockerHub) Cleanup() { + if dh.client != nil && dh.client.httpClient != nil { + dh.client.httpClient.CloseIdleConnections() + } +} diff --git a/src/go/collectors/go.d.plugin/modules/dockerhub/dockerhub_test.go b/src/go/collectors/go.d.plugin/modules/dockerhub/dockerhub_test.go new file mode 100644 index 000000000..7036ff7a7 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/dockerhub/dockerhub_test.go @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dockerhub + +import ( + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + + "github.com/netdata/netdata/go/go.d.plugin/agent/module" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + dataConfigJSON, _ = os.ReadFile("testdata/config.json") + dataConfigYAML, _ = os.ReadFile("testdata/config.yaml") + + dataRepo1, _ = os.ReadFile("testdata/repo1.txt") + dataRepo2, _ = os.ReadFile("testdata/repo2.txt") + dataRepo3, _ = os.ReadFile("testdata/repo3.txt") +) + +func Test_testDataIsValid(t *testing.T) { + for name, data := range map[string][]byte{ + "dataConfigJSON": dataConfigJSON, + "dataConfigYAML": dataConfigYAML, + "dataRepo1": dataRepo1, + "dataRepo2": dataRepo2, + "dataRepo3": dataRepo3, + } { + require.NotNil(t, data, name) + } +} + +func TestDockerHub_ConfigurationSerialize(t *testing.T) { + module.TestConfigurationSerialize(t, &DockerHub{}, dataConfigJSON, dataConfigYAML) +} + +func TestDockerHub_Charts(t *testing.T) { assert.NotNil(t, New().Charts()) } + +func TestDockerHub_Cleanup(t *testing.T) { New().Cleanup() } + +func TestDockerHub_Init(t *testing.T) { + job := New() + job.Repositories = []string{"name/repo"} + assert.NoError(t, job.Init()) + assert.NotNil(t, job.client) +} + +func TestDockerHub_InitNG(t *testing.T) { + assert.Error(t, New().Init()) +} + +func TestDockerHub_Check(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.URL.Path, "name1/repo1"): + _, _ = w.Write(dataRepo1) + case strings.HasSuffix(r.URL.Path, "name2/repo2"): + _, _ = w.Write(dataRepo2) + case strings.HasSuffix(r.URL.Path, "name3/repo3"): + _, _ = w.Write(dataRepo3) + } + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + job.Repositories = []string{"name1/repo1", "name2/repo2", "name3/repo3"} + require.NoError(t, job.Init()) + assert.NoError(t, job.Check()) +} + +func TestDockerHub_CheckNG(t *testing.T) { + job := New() + job.URL = "http://127.0.0.1:38001/metrics" + job.Repositories = []string{"name1/repo1", "name2/repo2", "name3/repo3"} + require.NoError(t, job.Init()) + assert.Error(t, job.Check()) +} + +func TestDockerHub_Collect(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.URL.Path, "name1/repo1"): + _, _ = w.Write(dataRepo1) + case strings.HasSuffix(r.URL.Path, "name2/repo2"): + _, _ = w.Write(dataRepo2) + case strings.HasSuffix(r.URL.Path, "name3/repo3"): + _, _ = w.Write(dataRepo3) + } + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + job.Repositories = []string{"name1/repo1", "name2/repo2", "name3/repo3"} + require.NoError(t, job.Init()) + require.NoError(t, job.Check()) + + expected := map[string]int64{ + "star_count_user1/name1": 45, + "pull_count_user1/name1": 18540191, + "status_user1/name1": 1, + "star_count_user2/name2": 45, + "pull_count_user2/name2": 18540192, + "status_user2/name2": 1, + "star_count_user3/name3": 45, + "pull_count_user3/name3": 18540193, + "status_user3/name3": 1, + "pull_sum": 55620576, + } + + collected := job.Collect() + + for k := range collected { + if strings.HasPrefix(k, "last") { + delete(collected, k) + } + } + assert.Equal(t, expected, collected) +} + +func TestDockerHub_InvalidData(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte("hello and goodbye")) + })) + defer ts.Close() + + job := New() + job.URL = ts.URL + job.Repositories = []string{"name1/repo1", "name2/repo2", "name3/repo3"} + require.NoError(t, job.Init()) + assert.Error(t, job.Check()) +} + +func TestDockerHub_404(t *testing.T) { + ts := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + defer ts.Close() + + job := New() + job.Repositories = []string{"name1/repo1", "name2/repo2", "name3/repo3"} + require.NoError(t, job.Init()) + assert.Error(t, job.Check()) +} diff --git a/src/go/collectors/go.d.plugin/modules/dockerhub/init.go b/src/go/collectors/go.d.plugin/modules/dockerhub/init.go new file mode 100644 index 000000000..245bee1cb --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/dockerhub/init.go @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package dockerhub + +import ( + "errors" + "github.com/netdata/netdata/go/go.d.plugin/pkg/web" +) + +func (dh *DockerHub) validateConfig() error { + if dh.URL == "" { + return errors.New("url not set") + } + if len(dh.Repositories) == 0 { + return errors.New("repositories not set") + } + return nil +} + +func (dh *DockerHub) initApiClient() (*apiClient, error) { + client, err := web.NewHTTPClient(dh.Client) + if err != nil { + return nil, err + } + return newAPIClient(client, dh.Request), nil +} diff --git a/src/go/collectors/go.d.plugin/modules/dockerhub/integrations/docker_hub_repository.md b/src/go/collectors/go.d.plugin/modules/dockerhub/integrations/docker_hub_repository.md new file mode 100644 index 000000000..2d833d3c0 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/dockerhub/integrations/docker_hub_repository.md @@ -0,0 +1,174 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/dockerhub/README.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/dockerhub/metadata.yaml" +sidebar_label: "Docker Hub repository" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/Containers and VMs" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# Docker Hub repository + + +<img src="https://netdata.cloud/img/docker.svg" width="150"/> + + +Plugin: go.d.plugin +Module: dockerhub + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +This collector keeps track of DockerHub repositories statistics such as the number of stars, pulls, current status, and more. + + + + +This collector is supported on all platforms. + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + + +### Default Behavior + +#### Auto-Detection + +This integration doesn't support auto-detection. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +The default configuration for this integration is not expected to impose a significant performance impact on the system. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per Docker Hub repository instance + +These metrics refer to the entire monitored application. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| dockerhub.pulls_sum | sum | pulls | +| dockerhub.pulls | a dimension per repository | pulls | +| dockerhub.pulls_rate | a dimension per repository | pulls/s | +| dockerhub.stars | a dimension per repository | stars | +| dockerhub.status | a dimension per repository | status | +| dockerhub.last_updated | a dimension per repository | seconds | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +No action required. + +### Configuration + +#### File + +The configuration file name for this integration is `go.d/dockerhub.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config go.d/dockerhub.conf +``` +#### Options + +The following options can be defined globally: update_every, autodetection_retry. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update_every | Data collection frequency. | 1 | no | +| autodetection_retry | Recheck interval in seconds. Zero means no recheck will be scheduled. | 0 | no | +| url | DockerHub URL. | https://hub.docker.com/v2/repositories | yes | +| repositories | List of repositories to monitor. | | yes | +| timeout | HTTP request timeout. | 1 | no | +| username | Username for basic HTTP authentication. | | no | +| password | Password for basic HTTP authentication. | | no | +| proxy_url | Proxy URL. | | no | +| proxy_username | Username for proxy basic HTTP authentication. | | no | +| proxy_password | Password for proxy basic HTTP authentication. | | no | +| method | HTTP request method. | GET | no | +| body | HTTP request body. | | no | +| headers | HTTP request headers. | | no | +| not_follow_redirects | Redirect handling policy. Controls whether the client follows redirects. | no | no | +| tls_skip_verify | Server certificate chain and hostname validation policy. Controls whether the client performs this check. | no | no | +| tls_ca | Certification authority that the client uses when verifying the server's certificates. | | no | +| tls_cert | Client TLS certificate. | | no | +| tls_key | Client TLS key. | | no | + +</details> + +#### Examples + +##### Basic + +A basic example configuration. + +```yaml +jobs: + - name: dockerhub + repositories: + - 'user1/name1' + - 'user2/name2' + - 'user3/name3' + +``` + + +## Troubleshooting + +### Debug Mode + +To troubleshoot issues with the `dockerhub` collector, run the `go.d.plugin` with the debug option enabled. The output +should give you clues as to why the collector isn't working. + +- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on + your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`. + + ```bash + cd /usr/libexec/netdata/plugins.d/ + ``` + +- Switch to the `netdata` user. + + ```bash + sudo -u netdata -s + ``` + +- Run the `go.d.plugin` to debug the collector: + + ```bash + ./go.d.plugin -d -m dockerhub + ``` + + diff --git a/src/go/collectors/go.d.plugin/modules/dockerhub/metadata.yaml b/src/go/collectors/go.d.plugin/modules/dockerhub/metadata.yaml new file mode 100644 index 000000000..605d6c1cb --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/dockerhub/metadata.yaml @@ -0,0 +1,190 @@ +plugin_name: go.d.plugin +modules: + - meta: + id: collector-go.d.plugin-dockerhub + plugin_name: go.d.plugin + module_name: dockerhub + monitored_instance: + name: Docker Hub repository + link: https://hub.docker.com/ + icon_filename: docker.svg + categories: + - data-collection.containers-and-vms # FIXME + keywords: + - dockerhub + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + most_popular: false + overview: + data_collection: + metrics_description: | + This collector keeps track of DockerHub repositories statistics such as the number of stars, pulls, current status, and more. + method_description: "" + supported_platforms: + include: [] + exclude: [] + multi_instance: true + additional_permissions: + description: "" + default_behavior: + auto_detection: + description: "" + limits: + description: "" + performance_impact: + description: "" + setup: + prerequisites: + list: [] + configuration: + file: + name: go.d/dockerhub.conf + options: + description: | + The following options can be defined globally: update_every, autodetection_retry. + folding: + title: Config options + enabled: true + list: + - name: update_every + description: Data collection frequency. + default_value: 1 + required: false + - name: autodetection_retry + description: Recheck interval in seconds. Zero means no recheck will be scheduled. + default_value: 0 + required: false + - name: url + description: DockerHub URL. + default_value: https://hub.docker.com/v2/repositories + required: true + - name: repositories + description: List of repositories to monitor. + default_value: "" + required: true + - name: timeout + description: HTTP request timeout. + default_value: 1 + required: false + - name: username + description: Username for basic HTTP authentication. + default_value: "" + required: false + - name: password + description: Password for basic HTTP authentication. + default_value: "" + required: false + - name: proxy_url + description: Proxy URL. + default_value: "" + required: false + - name: proxy_username + description: Username for proxy basic HTTP authentication. + default_value: "" + required: false + - name: proxy_password + description: Password for proxy basic HTTP authentication. + default_value: "" + required: false + - name: method + description: HTTP request method. + default_value: "GET" + required: false + - name: body + description: HTTP request body. + default_value: "" + required: false + - name: headers + description: HTTP request headers. + default_value: "" + required: false + - name: not_follow_redirects + description: Redirect handling policy. Controls whether the client follows redirects. + default_value: no + required: false + - name: tls_skip_verify + description: Server certificate chain and hostname validation policy. Controls whether the client performs this check. + default_value: no + required: false + - name: tls_ca + description: Certification authority that the client uses when verifying the server's certificates. + default_value: "" + required: false + - name: tls_cert + description: Client TLS certificate. + default_value: "" + required: false + - name: tls_key + description: Client TLS key. + default_value: "" + required: false + examples: + folding: + title: Config + enabled: true + list: + - name: Basic + folding: + enabled: false + description: A basic example configuration. + config: | + jobs: + - name: dockerhub + repositories: + - 'user1/name1' + - 'user2/name2' + - 'user3/name3' + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: These metrics refer to the entire monitored application. + labels: [] + metrics: + - name: dockerhub.pulls_sum + description: Pulls Summary + unit: pulls + chart_type: line + dimensions: + - name: sum + - name: dockerhub.pulls + description: Pulls + unit: pulls + chart_type: stacked + dimensions: + - name: a dimension per repository + - name: dockerhub.pulls_rate + description: Pulls Rate + unit: pulls/s + chart_type: stacked + dimensions: + - name: a dimension per repository + - name: dockerhub.stars + description: Stars + unit: stars + chart_type: stacked + dimensions: + - name: a dimension per repository + - name: dockerhub.status + description: Current Status + unit: status + chart_type: line + dimensions: + - name: a dimension per repository + - name: dockerhub.last_updated + description: Time Since Last Updated + unit: seconds + chart_type: line + dimensions: + - name: a dimension per repository diff --git a/src/go/collectors/go.d.plugin/modules/dockerhub/testdata/config.json b/src/go/collectors/go.d.plugin/modules/dockerhub/testdata/config.json new file mode 100644 index 000000000..3496e747c --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/dockerhub/testdata/config.json @@ -0,0 +1,23 @@ +{ + "update_every": 123, + "url": "ok", + "body": "ok", + "method": "ok", + "headers": { + "ok": "ok" + }, + "username": "ok", + "password": "ok", + "proxy_url": "ok", + "proxy_username": "ok", + "proxy_password": "ok", + "timeout": 123.123, + "not_follow_redirects": true, + "tls_ca": "ok", + "tls_cert": "ok", + "tls_key": "ok", + "tls_skip_verify": true, + "repositories": [ + "ok" + ] +} diff --git a/src/go/collectors/go.d.plugin/modules/dockerhub/testdata/config.yaml b/src/go/collectors/go.d.plugin/modules/dockerhub/testdata/config.yaml new file mode 100644 index 000000000..20c4ba61b --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/dockerhub/testdata/config.yaml @@ -0,0 +1,19 @@ +update_every: 123 +url: "ok" +body: "ok" +method: "ok" +headers: + ok: "ok" +username: "ok" +password: "ok" +proxy_url: "ok" +proxy_username: "ok" +proxy_password: "ok" +timeout: 123.123 +not_follow_redirects: yes +tls_ca: "ok" +tls_cert: "ok" +tls_key: "ok" +tls_skip_verify: yes +repositories: + - "ok" diff --git a/src/go/collectors/go.d.plugin/modules/dockerhub/testdata/repo1.txt b/src/go/collectors/go.d.plugin/modules/dockerhub/testdata/repo1.txt new file mode 100644 index 000000000..b67e2f382 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/dockerhub/testdata/repo1.txt @@ -0,0 +1,22 @@ +{ + "user": "user1", + "name": "name1", + "namespace": "namespace", + "repository_type": "image", + "status": 1, + "description": "Description.", + "is_private": false, + "is_automated": false, + "can_edit": false, + "star_count": 45, + "pull_count": 18540191, + "last_updated": "2019-03-28T21:26:05.527650Z", + "is_migrated": false, + "has_starred": false, + "affiliation": null, + "permissions": { + "read": true, + "write": false, + "admin": false + } +}
\ No newline at end of file diff --git a/src/go/collectors/go.d.plugin/modules/dockerhub/testdata/repo2.txt b/src/go/collectors/go.d.plugin/modules/dockerhub/testdata/repo2.txt new file mode 100644 index 000000000..e84ba989b --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/dockerhub/testdata/repo2.txt @@ -0,0 +1,22 @@ +{ + "user": "user2", + "name": "name2", + "namespace": "namespace", + "repository_type": "image", + "status": 1, + "description": "Description.", + "is_private": false, + "is_automated": false, + "can_edit": false, + "star_count": 45, + "pull_count": 18540192, + "last_updated": "2019-03-28T21:26:05.527650Z", + "is_migrated": false, + "has_starred": false, + "affiliation": null, + "permissions": { + "read": true, + "write": false, + "admin": false + } +}
\ No newline at end of file diff --git a/src/go/collectors/go.d.plugin/modules/dockerhub/testdata/repo3.txt b/src/go/collectors/go.d.plugin/modules/dockerhub/testdata/repo3.txt new file mode 100644 index 000000000..1fc64a9c3 --- /dev/null +++ b/src/go/collectors/go.d.plugin/modules/dockerhub/testdata/repo3.txt @@ -0,0 +1,22 @@ +{ + "user": "user3", + "name": "name3", + "namespace": "namespace", + "repository_type": "image", + "status": 1, + "description": "Description.", + "is_private": false, + "is_automated": false, + "can_edit": false, + "star_count": 45, + "pull_count": 18540193, + "last_updated": "2019-03-28T21:26:05.527650Z", + "is_migrated": false, + "has_starred": false, + "affiliation": null, + "permissions": { + "read": true, + "write": false, + "admin": false + } +}
\ No newline at end of file |