pangolin_exporter
238 строк · 5.5 Кб
1// Copyright 2022 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 config
15
16import (
17"fmt"
18"net/url"
19"regexp"
20"strings"
21"unicode"
22)
23
24// DSN represents a parsed datasource. It contains fields for the individual connection components.
25type DSN struct {
26scheme string
27username string
28password string
29host string
30path string
31query url.Values
32}
33
34// String makes a dsn safe to print by excluding any passwords. This allows dsn to be used in
35// strings and log messages without needing to call a redaction function first.
36func (d DSN) String() string {
37if d.password != "" {
38return fmt.Sprintf("%s://%s:******@%s%s?%s", d.scheme, d.username, d.host, d.path, d.query.Encode())
39}
40
41if d.username != "" {
42return fmt.Sprintf("%s://%s@%s%s?%s", d.scheme, d.username, d.host, d.path, d.query.Encode())
43}
44
45return fmt.Sprintf("%s://%s%s?%s", d.scheme, d.host, d.path, d.query.Encode())
46}
47
48// GetConnectionString returns the URL to pass to the driver for database connections. This value should not be logged.
49func (d DSN) GetConnectionString() string {
50u := url.URL{
51Scheme: d.scheme,
52Host: d.host,
53Path: d.path,
54RawQuery: d.query.Encode(),
55}
56
57// Username and Password
58if d.username != "" {
59u.User = url.UserPassword(d.username, d.password)
60}
61
62return u.String()
63}
64
65// dsnFromString parses a connection string into a dsn. It will attempt to parse the string as
66// a URL and as a set of key=value pairs. If both attempts fail, dsnFromString will return an error.
67func dsnFromString(in string) (DSN, error) {
68if strings.HasPrefix(in, "postgresql://") || strings.HasPrefix(in, "postgres://") {
69return dsnFromURL(in)
70}
71
72// Try to parse as key=value pairs
73d, err := dsnFromKeyValue(in)
74if err == nil {
75return d, nil
76}
77
78// Parse the string as a URL, with the scheme prefixed
79d, err = dsnFromURL(fmt.Sprintf("postgresql://%s", in))
80if err == nil {
81return d, nil
82}
83
84return DSN{}, fmt.Errorf("could not understand DSN")
85}
86
87// dsnFromURL parses the input as a URL and returns the dsn representation.
88func dsnFromURL(in string) (DSN, error) {
89u, err := url.Parse(in)
90if err != nil {
91return DSN{}, err
92}
93pass, _ := u.User.Password()
94user := u.User.Username()
95
96query := u.Query()
97
98if queryPass := query.Get("password"); queryPass != "" {
99if pass == "" {
100pass = queryPass
101}
102}
103query.Del("password")
104
105if queryUser := query.Get("user"); queryUser != "" {
106if user == "" {
107user = queryUser
108}
109}
110query.Del("user")
111
112d := DSN{
113scheme: u.Scheme,
114username: user,
115password: pass,
116host: u.Host,
117path: u.Path,
118query: query,
119}
120
121return d, nil
122}
123
124// dsnFromKeyValue parses the input as a set of key=value pairs and returns the dsn representation.
125func dsnFromKeyValue(in string) (DSN, error) {
126// Attempt to confirm at least one key=value pair before starting the rune parser
127connstringRe := regexp.MustCompile(`^ *[a-zA-Z0-9]+ *= *[^= ]+`)
128if !connstringRe.MatchString(in) {
129return DSN{}, fmt.Errorf("input is not a key-value DSN")
130}
131
132// Anything other than known fields should be part of the querystring
133query := url.Values{}
134
135pairs, err := parseKeyValue(in)
136if err != nil {
137return DSN{}, fmt.Errorf("failed to parse key-value DSN: %v", err)
138}
139
140// Build the dsn from the key=value pairs
141d := DSN{
142scheme: "postgresql",
143}
144
145hostname := ""
146port := ""
147
148for k, v := range pairs {
149switch k {
150case "host":
151hostname = v
152case "port":
153port = v
154case "user":
155d.username = v
156case "password":
157d.password = v
158default:
159query.Set(k, v)
160}
161}
162
163if hostname == "" {
164hostname = "localhost"
165}
166
167if port == "" {
168d.host = hostname
169} else {
170d.host = fmt.Sprintf("%s:%s", hostname, port)
171}
172
173d.query = query
174
175return d, nil
176}
177
178// parseKeyValue is a key=value parser. It loops over each rune to split out keys and values
179// and attempting to honor quoted values. parseKeyValue will return an error if it is unable
180// to properly parse the input.
181func parseKeyValue(in string) (map[string]string, error) {
182out := map[string]string{}
183
184inPart := false
185inQuote := false
186part := []rune{}
187key := ""
188for _, c := range in {
189switch {
190case unicode.In(c, unicode.Quotation_Mark):
191if inQuote {
192inQuote = false
193} else {
194inQuote = true
195}
196case unicode.In(c, unicode.White_Space):
197if inPart {
198if inQuote {
199part = append(part, c)
200} else {
201// Are we finishing a key=value?
202if key == "" {
203return out, fmt.Errorf("invalid input")
204}
205out[key] = string(part)
206inPart = false
207part = []rune{}
208}
209} else {
210// Are we finishing a key=value?
211if key == "" {
212return out, fmt.Errorf("invalid input")
213}
214out[key] = string(part)
215inPart = false
216part = []rune{}
217// Do something with the value
218}
219case c == '=':
220if inPart {
221inPart = false
222key = string(part)
223part = []rune{}
224} else {
225return out, fmt.Errorf("invalid input")
226}
227default:
228inPart = true
229part = append(part, c)
230}
231}
232
233if key != "" && len(part) > 0 {
234out[key] = string(part)
235}
236
237return out, nil
238}
239