1// Copyright 2018 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package push
15
16// This file contains only deprecated code. Remove after v0.9 is released.
17
18import (
19	"bytes"
20	"fmt"
21	"io/ioutil"
22	"net/http"
23	"net/url"
24	"os"
25	"strings"
26
27	"github.com/prometheus/common/expfmt"
28	"github.com/prometheus/common/model"
29
30	"github.com/prometheus/client_golang/prometheus"
31)
32
33// FromGatherer triggers a metric collection by the provided Gatherer (which is
34// usually implemented by a prometheus.Registry) and pushes all gathered metrics
35// to the Pushgateway specified by url, using the provided job name and the
36// (optional) further grouping labels (the grouping map may be nil). See the
37// Pushgateway documentation for detailed implications of the job and other
38// grouping labels. Neither the job name nor any grouping label value may
39// contain a "/". The metrics pushed must not contain a job label of their own
40// nor any of the grouping labels.
41//
42// You can use just host:port or ip:port as url, in which case 'http://' is
43// added automatically. You can also include the schema in the URL. However, do
44// not include the '/metrics/jobs/...' part.
45//
46// Note that all previously pushed metrics with the same job and other grouping
47// labels will be replaced with the metrics pushed by this call. (It uses HTTP
48// method 'PUT' to push to the Pushgateway.)
49//
50// Deprecated: Please use a Pusher created with New instead.
51func FromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error {
52	return push(job, grouping, url, g, "PUT")
53}
54
55// AddFromGatherer works like FromGatherer, but only previously pushed metrics
56// with the same name (and the same job and other grouping labels) will be
57// replaced. (It uses HTTP method 'POST' to push to the Pushgateway.)
58//
59// Deprecated: Please use a Pusher created with New instead.
60func AddFromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error {
61	return push(job, grouping, url, g, "POST")
62}
63
64func push(job string, grouping map[string]string, pushURL string, g prometheus.Gatherer, method string) error {
65	if !strings.Contains(pushURL, "://") {
66		pushURL = "http://" + pushURL
67	}
68	if strings.HasSuffix(pushURL, "/") {
69		pushURL = pushURL[:len(pushURL)-1]
70	}
71
72	if strings.Contains(job, "/") {
73		return fmt.Errorf("job contains '/': %s", job)
74	}
75	urlComponents := []string{url.QueryEscape(job)}
76	for ln, lv := range grouping {
77		if !model.LabelName(ln).IsValid() {
78			return fmt.Errorf("grouping label has invalid name: %s", ln)
79		}
80		if strings.Contains(lv, "/") {
81			return fmt.Errorf("value of grouping label %s contains '/': %s", ln, lv)
82		}
83		urlComponents = append(urlComponents, ln, lv)
84	}
85	pushURL = fmt.Sprintf("%s/metrics/job/%s", pushURL, strings.Join(urlComponents, "/"))
86
87	mfs, err := g.Gather()
88	if err != nil {
89		return err
90	}
91	buf := &bytes.Buffer{}
92	enc := expfmt.NewEncoder(buf, expfmt.FmtProtoDelim)
93	// Check for pre-existing grouping labels:
94	for _, mf := range mfs {
95		for _, m := range mf.GetMetric() {
96			for _, l := range m.GetLabel() {
97				if l.GetName() == "job" {
98					return fmt.Errorf("pushed metric %s (%s) already contains a job label", mf.GetName(), m)
99				}
100				if _, ok := grouping[l.GetName()]; ok {
101					return fmt.Errorf(
102						"pushed metric %s (%s) already contains grouping label %s",
103						mf.GetName(), m, l.GetName(),
104					)
105				}
106			}
107		}
108		enc.Encode(mf)
109	}
110	req, err := http.NewRequest(method, pushURL, buf)
111	if err != nil {
112		return err
113	}
114	req.Header.Set(contentTypeHeader, string(expfmt.FmtProtoDelim))
115	resp, err := http.DefaultClient.Do(req)
116	if err != nil {
117		return err
118	}
119	defer resp.Body.Close()
120	if resp.StatusCode != 202 {
121		body, _ := ioutil.ReadAll(resp.Body) // Ignore any further error as this is for an error message only.
122		return fmt.Errorf("unexpected status code %d while pushing to %s: %s", resp.StatusCode, pushURL, body)
123	}
124	return nil
125}
126
127// Collectors works like FromGatherer, but it does not use a Gatherer. Instead,
128// it collects from the provided collectors directly. It is a convenient way to
129// push only a few metrics.
130//
131// Deprecated: Please use a Pusher created with New instead.
132func Collectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error {
133	return pushCollectors(job, grouping, url, "PUT", collectors...)
134}
135
136// AddCollectors works like AddFromGatherer, but it does not use a Gatherer.
137// Instead, it collects from the provided collectors directly. It is a
138// convenient way to push only a few metrics.
139//
140// Deprecated: Please use a Pusher created with New instead.
141func AddCollectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error {
142	return pushCollectors(job, grouping, url, "POST", collectors...)
143}
144
145func pushCollectors(job string, grouping map[string]string, url, method string, collectors ...prometheus.Collector) error {
146	r := prometheus.NewRegistry()
147	for _, collector := range collectors {
148		if err := r.Register(collector); err != nil {
149			return err
150		}
151	}
152	return push(job, grouping, url, r, method)
153}
154
155// HostnameGroupingKey returns a label map with the only entry
156// {instance="<hostname>"}. This can be conveniently used as the grouping
157// parameter if metrics should be pushed with the hostname as label. The
158// returned map is created upon each call so that the caller is free to add more
159// labels to the map.
160//
161// Deprecated: Usually, metrics pushed to the Pushgateway should not be
162// host-centric. (You would use https://github.com/prometheus/node_exporter in
163// that case.) If you have the need to add the hostname to the grouping key, you
164// are probably doing something wrong. See
165// https://prometheus.io/docs/practices/pushing/ for details.
166func HostnameGroupingKey() map[string]string {
167	hostname, err := os.Hostname()
168	if err != nil {
169		return map[string]string{"instance": "unknown"}
170	}
171	return map[string]string{"instance": hostname}
172}
173