Skip to content

Commit

Permalink
Include Nextcloud update availability information
Browse files Browse the repository at this point in the history
This commit introduces new option and metric about nextcloud
update availabilty information.

A new option was introduced called `--enable-info-update`
which will append the to the Nextcloud serverinfo-URL a
`&skipUpdate=false`.

In response, the update information is returned and provided in a new
metric called `nextcloud_system_update_available`.

Fixes #115
  • Loading branch information
monofox committed Apr 13, 2024
1 parent 313865b commit c41b1b5
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 52 deletions.
66 changes: 35 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Usage of nextcloud-exporter:
--auth-token string Authentication token. Can replace username and password when using Nextcloud 22 or newer.
-c, --config-file string Path to YAML configuration file.
--enable-info-apps Enable gathering of apps-related metrics.
--enable-info-update Enable gathering of system update-related metrics.
--login Use interactive login to create app password.
-p, --password string Password for connecting to Nextcloud.
-s, --server string URL to Nextcloud server.
Expand Down Expand Up @@ -115,16 +116,17 @@ There are three methods of configuring the nextcloud-exporter (higher methods ta

All settings can also be specified through environment variables:

| Environment variable | Flag equivalent |
|----------------------------:|:-------------------|
| `NEXTCLOUD_SERVER` | --server |
| `NEXTCLOUD_USERNAME` | --username |
| `NEXTCLOUD_PASSWORD` | --password |
| `NEXTCLOUD_AUTH_TOKEN` | --auth-token |
| `NEXTCLOUD_LISTEN_ADDRESS` | --addr |
| `NEXTCLOUD_TIMEOUT` | --timeout |
| `NEXTCLOUD_TLS_SKIP_VERIFY` | --tls-skip-verify |
| `NEXTCLOUD_INFO_APPS` | --enable-info-apps |
| Environment variable | Flag equivalent |
|----------------------------:|:---------------------|
| `NEXTCLOUD_SERVER` | --server |
| `NEXTCLOUD_USERNAME` | --username |
| `NEXTCLOUD_PASSWORD` | --password |
| `NEXTCLOUD_AUTH_TOKEN` | --auth-token |
| `NEXTCLOUD_LISTEN_ADDRESS` | --addr |
| `NEXTCLOUD_TIMEOUT` | --timeout |
| `NEXTCLOUD_TLS_SKIP_VERIFY` | --tls-skip-verify |
| `NEXTCLOUD_INFO_APPS` | --enable-info-apps |
| `NEXTCLOUD_INFO_UPDATE` | --enable-info-update |

#### Configuration file

Expand All @@ -144,6 +146,7 @@ timeout: "5s"
tlsSkipVerify: false
info:
apps: false
update: false
```
### Loading Credentials from Files
Expand Down Expand Up @@ -191,24 +194,25 @@ scrape_configs:
These metrics are exported by `nextcloud-exporter`:

| name | description |
|----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| nextcloud_active_users_daily_total | Number of active users in the last 24 hours |
| nextcloud_active_users_hourly_total | Number of active users in the last hour |
| nextcloud_active_users_total | Number of active users for the last five minutes |
| nextcloud_apps_installed_total | Number of currently installed apps |
| nextcloud_apps_updates_available_total | Number of apps that have available updates |
| nextcloud_database_info | Contains meta information about the database as labels. Value is always 1. |
| nextcloud_database_size_bytes | Size of database in bytes as reported from engine |
| nextcloud_exporter_info | Contains meta information of the exporter. Value is always 1. |
| nextcloud_files_total | Number of files served by the instance |
| nextcloud_free_space_bytes | Free disk space in data directory in bytes |
| nextcloud_php_info | Contains meta information about PHP as labels. Value is always 1. |
| nextcloud_php_memory_limit_bytes | Configured PHP memory limit in bytes |
| nextcloud_php_upload_max_size_bytes | Configured maximum upload size in bytes |
| nextcloud_scrape_errors_total | Counts the number of scrape errors by this collector |
| nextcloud_shares_federated_total | Number of federated shares by direction `sent` / `received` |
| nextcloud_shares_total | Number of shares by type: <br> `authlink`: shared password protected links <br> `group`: shared groups <br>`link`: all shared links <br> `user`: shared users <br> `mail`: shared by mail <br> `room`: shared with room |
| nextcloud_system_info | Contains meta information about Nextcloud as labels. Value is always 1. |
| nextcloud_up | Indicates if the metrics could be scraped by the exporter: <br>`1`: successful<br>`0`: unsuccessful (server down, server/endpoint not reachable, invalid credentials, ...) |
| nextcloud_users_total | Number of users of the instance |
| name | description |
|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| nextcloud_active_users_daily_total | Number of active users in the last 24 hours |
| nextcloud_active_users_hourly_total | Number of active users in the last hour |
| nextcloud_active_users_total | Number of active users for the last five minutes |
| nextcloud_apps_installed_total | Number of currently installed apps |
| nextcloud_apps_updates_available_total | Number of apps that have available updates |
| nextcloud_database_info | Contains meta information about the database as labels. Value is always 1. |
| nextcloud_database_size_bytes | Size of database in bytes as reported from engine |
| nextcloud_exporter_info | Contains meta information of the exporter. Value is always 1. |
| nextcloud_files_total | Number of files served by the instance |
| nextcloud_free_space_bytes | Free disk space in data directory in bytes |
| nextcloud_php_info | Contains meta information about PHP as labels. Value is always 1. |
| nextcloud_php_memory_limit_bytes | Configured PHP memory limit in bytes |
| nextcloud_php_upload_max_size_bytes | Configured maximum upload size in bytes |
| nextcloud_scrape_errors_total | Counts the number of scrape errors by this collector |
| nextcloud_shares_federated_total | Number of federated shares by direction `sent` / `received` |
| nextcloud_shares_total | Number of shares by type: <br> `authlink`: shared password protected links <br> `group`: shared groups <br>`link`: all shared links <br> `user`: shared users <br> `mail`: shared by mail <br> `room`: shared with room |
| nextcloud_system_info | Contains meta information about Nextcloud as labels. Value is always 1. |
| nextcloud_system_update_available | Contains information whether a system update is available: <br>`0`: no update available<br>`1`: nextcloud update available<br>In case of 1=yes, `available_version` label contains the new version. This metric is only available if activated. |
| nextcloud_up | Indicates if the metrics could be scraped by the exporter: <br>`1`: successful<br>`0`: unsuccessful (server down, server/endpoint not reachable, invalid credentials, ...) |
| nextcloud_users_total | Number of users of the instance |
21 changes: 19 additions & 2 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const (
envAuthToken = envPrefix + "AUTH_TOKEN"
envTLSSkipVerify = envPrefix + "TLS_SKIP_VERIFY"
envInfoApps = envPrefix + "INFO_APPS"
envInfoUpdate = envPrefix + "INFO_UPDATE"
)

// RunMode signals what the main application should do after parsing the options.
Expand Down Expand Up @@ -68,7 +69,8 @@ type Config struct {

// InfoConfig contains configuration related to what information is read from serverinfo.
type InfoConfig struct {
Apps bool `yaml:"apps"`
Apps bool `yaml:"apps"`
Update bool `yaml:"update"`
}

var (
Expand Down Expand Up @@ -170,6 +172,7 @@ func loadConfigFromFlags(args []string) (result Config, configFile string, err e
flags.StringVar(&result.AuthToken, "auth-token", defaults.AuthToken, "Authentication token. Can replace username and password when using Nextcloud 22 or newer.")
flags.BoolVar(&result.TLSSkipVerify, "tls-skip-verify", defaults.TLSSkipVerify, "Skip certificate verification of Nextcloud server.")
flags.BoolVar(&result.Info.Apps, "enable-info-apps", defaults.Info.Apps, "Enable gathering of apps-related metrics.")
flags.BoolVar(&result.Info.Update, "enable-info-update", defaults.Info.Update, "Enable gathering of system update-related metrics.")
modeLogin := flags.Bool("login", false, "Use interactive login to create app password.")
modeVersion := flags.BoolP("version", "V", false, "Show version information and exit.")

Expand Down Expand Up @@ -229,6 +232,15 @@ func loadConfigFromEnv(getEnv func(string) string) (Config, error) {
infoApps = value
}

infoUpdate := false
if rawValue := getEnv(envInfoUpdate); rawValue != "" {
value, err := strconv.ParseBool(rawValue)
if err != nil {
return Config{}, fmt.Errorf("can not parse value for %q: %s", envInfoUpdate, rawValue)
}
infoUpdate = value
}

result := Config{
ListenAddr: getEnv(envListenAddress),
ServerURL: getEnv(envServerURL),
Expand All @@ -237,7 +249,8 @@ func loadConfigFromEnv(getEnv func(string) string) (Config, error) {
AuthToken: getEnv(envAuthToken),
TLSSkipVerify: tlsSkipVerify,
Info: InfoConfig{
Apps: infoApps,
Apps: infoApps,
Update: infoUpdate,
},
}

Expand Down Expand Up @@ -287,6 +300,10 @@ func mergeConfig(base, override Config) Config {
result.Info.Apps = override.Info.Apps
}

if override.Info.Update {
result.Info.Update = override.Info.Update
}

return result
}

Expand Down
51 changes: 42 additions & 9 deletions internal/metrics/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ var (
metricPrefix+"system_info",
"Contains meta information about Nextcloud as labels. Value is always 1.",
[]string{"version"}, nil)
systemUpdateAvailableDesc = prometheus.NewDesc(
metricPrefix+"system_update_available",
"Contains information whether a system update is available (0 = no, 1 = yes). In case of 1=yes, available_version label contains the new version.",
[]string{"available_version"}, nil)
appsInstalledDesc = prometheus.NewDesc(
metricPrefix+"apps_installed_total",
"Number of currently installed apps",
Expand Down Expand Up @@ -85,19 +89,21 @@ var (
)

type nextcloudCollector struct {
log logrus.FieldLogger
infoClient client.InfoClient
appsMetrics bool
log logrus.FieldLogger
infoClient client.InfoClient
appsMetrics bool
updateMetrics bool

upMetric prometheus.Gauge
scrapeErrorsMetric *prometheus.CounterVec
}

func RegisterCollector(log logrus.FieldLogger, infoClient client.InfoClient, appsMetrics bool) error {
func RegisterCollector(log logrus.FieldLogger, infoClient client.InfoClient, appsMetrics bool, updateMetrics bool) error {
c := &nextcloudCollector{
log: log,
infoClient: infoClient,
appsMetrics: appsMetrics,
log: log,
infoClient: infoClient,
appsMetrics: appsMetrics,
updateMetrics: updateMetrics,

upMetric: prometheus.NewGauge(prometheus.GaugeOpts{
Name: metricPrefix + "up",
Expand Down Expand Up @@ -151,14 +157,20 @@ func (c *nextcloudCollector) collectNextcloud(ch chan<- prometheus.Metric) error
return err
}

return readMetrics(ch, status, c.appsMetrics)
return readMetrics(ch, status, c.appsMetrics, c.updateMetrics)
}

func readMetrics(ch chan<- prometheus.Metric, status *serverinfo.ServerInfo, appsMetrics bool) error {
func readMetrics(ch chan<- prometheus.Metric, status *serverinfo.ServerInfo, appsMetrics bool, updateMetrics bool) error {
if err := collectSimpleMetrics(ch, status, appsMetrics); err != nil {
return err
}

if updateMetrics {
if err := collectUpdate(ch, status); err != nil {
return err
}
}

if err := collectShares(ch, status.Data.Nextcloud.Shares); err != nil {
return err
}
Expand Down Expand Up @@ -261,6 +273,27 @@ func collectSimpleMetrics(ch chan<- prometheus.Metric, status *serverinfo.Server
return nil
}

func collectUpdate(ch chan<- prometheus.Metric, status *serverinfo.ServerInfo) error {
var updateAvailable float64
updateInfo := []string{}
// Fix small bug: its indicated as "true" even if there is no real update available.
if status.Data.Nextcloud.System.Update.Available && status.Data.Nextcloud.System.Version != status.Data.Nextcloud.System.Update.AvailableVersion {
updateAvailable = 1
updateInfo = append(updateInfo, status.Data.Nextcloud.System.Update.AvailableVersion)
} else {
updateAvailable = 0
updateInfo = append(updateInfo, "")
}

metric, err := prometheus.NewConstMetric(systemUpdateAvailableDesc, prometheus.GaugeValue, updateAvailable, updateInfo...)
if err != nil {
return fmt.Errorf("error creating metric for %s: %w", systemUpdateAvailableDesc, err)
}
ch <- metric

return nil
}

func collectShares(ch chan<- prometheus.Metric, shares serverinfo.Shares) error {
values := make(map[string]float64)
values["user"] = float64(shares.SharesUser)
Expand Down
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,14 @@ func main() {
log.Infof("Nextcloud server: %s Authentication using token.", cfg.ServerURL)
}

infoURL := serverinfo.InfoURL(cfg.ServerURL, !cfg.Info.Apps)
infoURL := serverinfo.InfoURL(cfg.ServerURL, !cfg.Info.Apps, !cfg.Info.Update)

if cfg.TLSSkipVerify {
log.Warn("HTTPS certificate verification is disabled.")
}

infoClient := client.New(infoURL, cfg.Username, cfg.Password, cfg.AuthToken, cfg.Timeout, userAgent, cfg.TLSSkipVerify)
if err := metrics.RegisterCollector(log, infoClient, cfg.Info.Apps); err != nil {
if err := metrics.RegisterCollector(log, infoClient, cfg.Info.Apps, cfg.Info.Update); err != nil {
log.Fatalf("Failed to register collector: %s", err)
}

Expand Down
Loading

0 comments on commit c41b1b5

Please sign in to comment.