LookupEnv

Minio 支持两种类型的环境变量:本地和远程。当需要一个环境变量配置时,首先从本地环境变量中获取 key 对应的取值 value。如果 value 以 env 开头,则使用 value 作为远端服务器地址,执行远程获取操作;如果从远程失败,则检查本地是否有 **_key 开始的环境变量,如果有,返回;如果远程获取成功,更新本地 _key** 对应环境变量。

  1. func LookupEnv(key string) (string, string, string, bool) {
  2. v, ok := os.LookupEnv(key)
  3. if ok && strings.HasPrefix(v, webEnvScheme) {
  4. // If env value starts with `env*://`
  5. // continue to parse and fetch from remote
  6. var err error
  7. v, user, pwd, err := getEnvValueFromHTTP(strings.TrimSpace(v), key)
  8. if err != nil {
  9. env, eok := os.LookupEnv("_" + key)
  10. if eok {
  11. // fallback to cached value if-any.
  12. return env, user, pwd, eok
  13. }
  14. }
  15. // Set the ENV value to _env value,
  16. // this value is a fallback in-case of
  17. // server restarts when webhook server
  18. // is down.
  19. os.Setenv("_"+key, v)
  20. return v, user, pwd, true
  21. }
  22. return v, "", "", ok
  23. }

Get From Remote

getEnvValueFromHTTP 方法尝试从远端获取环境变量的取值,传入参数位本地环境变量 key 对应的取值 value。第一步,从 value 中解析 URL

  1. u, err := url.Parse(urlStr)
  2. if err != nil {
  3. return "", "", "", err
  4. }
  5. switch u.Scheme {
  6. case webEnvScheme:
  7. u.Scheme = "http"
  8. case webEnvSchemeSecure:
  9. u.Scheme = "https"
  10. default:
  11. return "", "", "", errors.New("invalid arguments")
  12. }

接下来,从 URL 中获取用户名、密码等信息

  1. username, password, envURL, err := fetchHTTPConstituentParts(u)
  2. if err != nil {
  3. return "", "", "", err
  4. }

获取用户名、密码和实际 URL 的操作比较简单,通过正则匹配即可,L14 拼接真实 URL

  1. var (
  2. hostKeys = regexp.MustCompile("^(https?://)(.*?):(.*?)@(.*?)$")
  3. )
  4. func fetchHTTPConstituentParts(u *url.URL) (username string, password string, envURL string, err error) {
  5. envURL = u.String()
  6. if hostKeys.MatchString(envURL) {
  7. parts := hostKeys.FindStringSubmatch(envURL)
  8. if len(parts) != 5 {
  9. return "", "", "", errors.New("invalid arguments")
  10. }
  11. username = parts[2]
  12. password = parts[3]
  13. envURL = fmt.Sprintf("%s%s", parts[1], parts[4])
  14. }
  15. if username == "" && password == "" && u.User != nil {
  16. username = u.User.Username()
  17. password, _ = u.User.Password()
  18. }
  19. return username, password, envURL, nil
  20. }

然后构建请求内容

  1. req, err := http.NewRequestWithContext(ctx, http.MethodGet, envURL+"?key="+envKey, nil)
  2. if err != nil {
  3. return "", "", "", err
  4. }

设置身份鉴定

  1. skey, err := jwk.New([]byte(password))
  2. if err != nil {
  3. return "", "", "", err
  4. }
  5. skey.Set(jwk.AlgorithmKey, jwa.HS512)
  6. skey.Set(jwk.KeyIDKey, "minio")
  7. token := jwt.New()
  8. t := time.Now().Add(15 * time.Minute)
  9. if err = token.Set(jwt.IssuerKey, username); err != nil {
  10. return "", "", "", err
  11. }
  12. if err = token.Set(jwt.SubjectKey, envKey); err != nil {
  13. return "", "", "", err
  14. }
  15. if err = token.Set(jwt.ExpirationKey, t.Unix()); err != nil {
  16. return "", "", "", err
  17. }
  18. signed, err := jwt.Sign(token, jwa.HS512, skey)
  19. if err != nil {
  20. return "", "", "", err
  21. }
  22. req.Header.Set("Authorization", "Bearer "+string(signed))

最后,执行请求,并返回。

  1. clnt := &http.Client{
  2. Transport: &http.Transport{
  3. Proxy: http.ProxyFromEnvironment,
  4. DialContext: (&net.Dialer{
  5. Timeout: 3 * time.Second,
  6. KeepAlive: 5 * time.Second,
  7. }).DialContext,
  8. ResponseHeaderTimeout: 3 * time.Second,
  9. TLSHandshakeTimeout: 3 * time.Second,
  10. ExpectContinueTimeout: 3 * time.Second,
  11. TLSClientConfig: &tls.Config{
  12. RootCAs: globalRootCAs,
  13. },
  14. // Go net/http automatically unzip if content-type is
  15. // gzip disable this feature, as we are always interested
  16. // in raw stream.
  17. DisableCompression: true,
  18. },
  19. }
  20. resp, err := clnt.Do(req)
  21. if err != nil {
  22. return "", "", "", err
  23. }
  24. envValueBytes, err := ioutil.ReadAll(resp.Body)
  25. if err != nil {
  26. return "", "", "", err
  27. }
  28. return string(envValueBytes), username, password, nil