diff --git a/artifactory/system.go b/artifactory/system.go index f21aa4c..3d5aa98 100644 --- a/artifactory/system.go +++ b/artifactory/system.go @@ -9,9 +9,10 @@ import ( ) const ( - pingEndpoint = "system/ping" - versionEndpoint = "system/version" - licenseEndpoint = "system/license" + pingEndpoint = "system/ping" + versionEndpoint = "system/version" + licenseEndpoint = "system/license" + licensesEndpoint = "system/licenses" ) type HealthStatus struct { @@ -65,7 +66,7 @@ func (c *Client) FetchBuildInfo() (BuildInfo, error) { return buildInfo, nil } -// LicenseInfo represents API respond from license endpoint +// LicenseInfo represents API response from license endpoint type LicenseInfo struct { Type string `json:"type"` ValidThrough string `json:"validThrough"` @@ -117,7 +118,7 @@ func (c *Client) FetchLicense() (LicenseInfo, error) { } licenseInfo.NodeId = resp.NodeId if err := json.Unmarshal(resp.Body, &licenseInfo); err != nil { - c.logger.Error("There was an issue when try to unmarshal licenseInfo respond") + c.logger.Error("There was an issue when trying to unmarshal licenseInfo response") return licenseInfo, &UnmarshalError{ message: err.Error(), endpoint: licenseEndpoint, @@ -125,3 +126,32 @@ func (c *Client) FetchLicense() (LicenseInfo, error) { } return licenseInfo, nil } + +// LicensesInfo represents API response from licenses endpoint +type LicensesInfo struct { + Licenses []struct { + LicenseInfo + NodeId string `json:"nodeId"` + NodeUrl string `json:"nodeUrl"` + LicenseHash string `json:"licenseHash"` + Expired bool `json:"expired"` + } `json:"licenses"` +} + +// FetchLicenses makes the API call to licenses endpoint and returns LicensesInfo +func (c *Client) FetchLicenses() (LicensesInfo, error) { + var licensesInfo LicensesInfo + c.logger.Debug("Fetching HA licenses stats") + resp, err := c.FetchHTTP(licensesEndpoint) + if err != nil { + return licensesInfo, err + } + if err := json.Unmarshal(resp.Body, &licensesInfo); err != nil { + c.logger.Error("There was an issue when trying to unmarshal licensesInfo response") + return licensesInfo, &UnmarshalError{ + message: err.Error(), + endpoint: licensesEndpoint, + } + } + return licensesInfo, nil +} diff --git a/collector/collector.go b/collector/collector.go index f8f075e..cfdb3f0 100755 --- a/collector/collector.go +++ b/collector/collector.go @@ -51,9 +51,10 @@ var ( } systemMetrics = metrics{ - "healthy": newMetric("healthy", "system", "Is Artifactory working properly (1 = healthy).", defaultLabelNames), - "version": newMetric("version", "system", "Version and revision of Artifactory as labels.", append([]string{"version", "revision"}, defaultLabelNames...)), - "license": newMetric("license", "system", "License type and expiry as labels, seconds to expiration as value", append([]string{"type", "licensed_to", "expires"}, defaultLabelNames...)), + "healthy": newMetric("healthy", "system", "Is Artifactory working properly (1 = healthy).", defaultLabelNames), + "version": newMetric("version", "system", "Version and revision of Artifactory as labels.", append([]string{"version", "revision"}, defaultLabelNames...)), + "license": newMetric("license", "system", "License type and expiry as labels, seconds to expiration as value", append([]string{"type", "licensed_to", "expires"}, defaultLabelNames...)), + "licenses": newMetric("licenses", "system", "License type and expiry as labels, seconds to expiration as value", append([]string{"type", "valid_through", "licensed_to", "node_url", "license_hash", "expires"}, defaultLabelNames...)), } artifactsMetrics = metrics{ @@ -145,6 +146,11 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) (up float64) { return 0 } + // Collect and export system HA licenses metrics + if err := e.exportSystemHALicenses(ch); err != nil { + return 0 + } + // Fetch Storage Info stats and register them storageInfo, err := e.client.FetchStorageInfo() if err != nil { diff --git a/collector/system.go b/collector/system.go index 33c1c2c..45f2478 100644 --- a/collector/system.go +++ b/collector/system.go @@ -1,6 +1,8 @@ package collector import ( + "strconv" + "github.com/prometheus/client_golang/prometheus" ) @@ -79,3 +81,40 @@ func (e *Exporter) exportSystem(ch chan<- prometheus.Metric) error { return nil } + +func (e *Exporter) exportSystemHALicenses(ch chan<- prometheus.Metric) error { + licensesInfo, err := e.client.FetchLicenses() + if err != nil { + e.logger.Error( + "Couldn't scrape Artifactory when fetching system/licenses", + "err", err.Error(), + ) + e.totalAPIErrors.Inc() + return err + } + + for _, licenseInfo := range licensesInfo.Licenses { + licenseValSec, err := licenseInfo.ValidSeconds() + if err != nil { + e.logger.Warn( + "Couldn't get Artifactory license validity", + "err", err.Error(), + ) // To preserve the operation, we do nothing but log the event, + } + metric := systemMetrics["licenses"] + ch <- prometheus.MustNewConstMetric( + metric, + prometheus.GaugeValue, + float64(licenseValSec), // Prometheus expects a float type. + licenseInfo.TypeNormalized(), + licenseInfo.ValidThrough, + licenseInfo.LicensedTo, + licenseInfo.NodeUrl, + licenseInfo.LicenseHash, + strconv.FormatBool(licenseInfo.Expired), + licenseInfo.NodeId, + ) + } + + return nil +}