1// Copyright (c) 2015-2021 MinIO, Inc.
2//
3// This file is part of MinIO Object Storage stack
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU Affero General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU Affero General Public License for more details.
14//
15// You should have received a copy of the GNU Affero General Public License
16// along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18package cmd
19
20import (
21	"fmt"
22	"net/url"
23	"time"
24
25	"github.com/fatih/color"
26	"github.com/minio/cli"
27	"github.com/minio/mc/pkg/probe"
28	"github.com/minio/pkg/console"
29
30	jwtgo "github.com/golang-jwt/jwt"
31	json "github.com/minio/colorjson"
32	yaml "gopkg.in/yaml.v2"
33)
34
35const (
36	defaultJobName     = "minio-job"
37	defaultMetricsPath = "/minio/v2/metrics/cluster"
38)
39
40var prometheusFlags = []cli.Flag{
41	cli.BoolFlag{
42		Name:  "public",
43		Usage: "disable bearer token generation for scrape_configs",
44	},
45}
46
47var adminPrometheusGenerateCmd = cli.Command{
48	Name:            "generate",
49	Usage:           "generates prometheus config",
50	Action:          mainAdminPrometheusGenerate,
51	OnUsageError:    onUsageError,
52	Before:          setGlobalsFromContext,
53	Flags:           append(prometheusFlags, globalFlags...),
54	HideHelpCommand: true,
55	CustomHelpTemplate: `NAME:
56  {{.HelpName}} - {{.Usage}}
57
58USAGE:
59  {{.HelpName}} TARGET
60
61FLAGS:
62  {{range .VisibleFlags}}{{.}}
63  {{end}}
64EXAMPLES:
65  1. Generate a default prometheus config.
66     {{.Prompt}} {{.HelpName}} myminio
67
68`,
69}
70
71// PrometheusConfig - container to hold the top level scrape config.
72type PrometheusConfig struct {
73	ScrapeConfigs []ScrapeConfig `yaml:"scrape_configs,omitempty"`
74}
75
76// String colorized prometheus config yaml.
77func (c PrometheusConfig) String() string {
78	b, err := yaml.Marshal(c)
79	if err != nil {
80		return fmt.Sprintf("error creating config string: %s", err)
81	}
82	return console.Colorize("yaml", string(b))
83}
84
85// JSON jsonified prometheus config.
86func (c PrometheusConfig) JSON() string {
87	jsonMessageBytes, e := json.MarshalIndent(c.ScrapeConfigs[0], "", " ")
88	fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
89	return string(jsonMessageBytes)
90}
91
92// StatConfig - container to hold the targets config.
93type StatConfig struct {
94	Targets []string `yaml:",flow" json:"targets"`
95}
96
97// String colorized stat config yaml.
98func (t StatConfig) String() string {
99	b, err := yaml.Marshal(t)
100	if err != nil {
101		return fmt.Sprintf("error creating config string: %s", err)
102	}
103	return console.Colorize("yaml", string(b))
104}
105
106// JSON jsonified stat config.
107func (t StatConfig) JSON() string {
108	jsonMessageBytes, e := json.MarshalIndent(t.Targets, "", " ")
109	fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
110	return string(jsonMessageBytes)
111}
112
113// ScrapeConfig configures a scraping unit for Prometheus.
114type ScrapeConfig struct {
115	JobName       string       `yaml:"job_name" json:"jobName"`
116	BearerToken   string       `yaml:"bearer_token,omitempty" json:"bearerToken,omitempty"`
117	MetricsPath   string       `yaml:"metrics_path,omitempty" json:"metricsPath"`
118	Scheme        string       `yaml:"scheme,omitempty" json:"scheme"`
119	StaticConfigs []StatConfig `yaml:"static_configs,omitempty" json:"staticConfigs"`
120}
121
122const (
123	defaultPrometheusJWTExpiry = 100 * 365 * 24 * time.Hour
124)
125
126var defaultConfig = PrometheusConfig{
127	ScrapeConfigs: []ScrapeConfig{
128		{
129			JobName:     defaultJobName,
130			MetricsPath: defaultMetricsPath,
131			StaticConfigs: []StatConfig{
132				{
133					Targets: []string{""},
134				},
135			},
136		},
137	},
138}
139
140// checkAdminPrometheusSyntax - validate all the passed arguments
141func checkAdminPrometheusSyntax(ctx *cli.Context) {
142	if len(ctx.Args()) != 1 {
143		cli.ShowCommandHelpAndExit(ctx, "generate", 1) // last argument is exit code
144	}
145}
146
147func generatePrometheusConfig(ctx *cli.Context) error {
148	// Get the alias parameter from cli
149	args := ctx.Args()
150	alias := cleanAlias(args.Get(0))
151
152	if !isValidAlias(alias) {
153		fatalIf(errInvalidAlias(alias), "Invalid alias.")
154	}
155
156	hostConfig := mustGetHostConfig(alias)
157	if hostConfig == nil {
158		fatalIf(errInvalidAliasedURL(alias), "No such alias `"+alias+"` found.")
159		return nil
160	}
161
162	u, e := url.Parse(hostConfig.URL)
163	if e != nil {
164		return e
165	}
166
167	if !ctx.Bool("public") {
168		jwt := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.StandardClaims{
169			ExpiresAt: UTCNow().Add(defaultPrometheusJWTExpiry).Unix(),
170			Subject:   hostConfig.AccessKey,
171			Issuer:    "prometheus",
172		})
173
174		token, e := jwt.SignedString([]byte(hostConfig.SecretKey))
175		if e != nil {
176			return e
177		}
178
179		// Setting the values
180		defaultConfig.ScrapeConfigs[0].BearerToken = token
181	}
182	defaultConfig.ScrapeConfigs[0].Scheme = u.Scheme
183	defaultConfig.ScrapeConfigs[0].StaticConfigs[0].Targets[0] = u.Host
184
185	printMsg(defaultConfig)
186
187	return nil
188}
189
190// mainAdminPrometheus is the handle for "mc admin prometheus generate" sub-command.
191func mainAdminPrometheusGenerate(ctx *cli.Context) error {
192
193	console.SetColor("yaml", color.New(color.FgGreen))
194
195	checkAdminPrometheusSyntax(ctx)
196
197	if err := generatePrometheusConfig(ctx); err != nil {
198		return nil
199	}
200
201	return nil
202}
203