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