Break up main.go
Add new files for abspath, credential, and env functions.
This commit is contained in:
parent
ff51ca92dc
commit
8fa652dafa
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// absPath is an absolute path string. This type is intended to make it clear
|
||||||
|
// when strings are absolute paths vs something else. This does not verify or
|
||||||
|
// mutate the input, so careless callers could make instances of this type that
|
||||||
|
// are not actually absolute paths, or even "".
|
||||||
|
type absPath string
|
||||||
|
|
||||||
|
// String returns abs as a string.
|
||||||
|
func (abs absPath) String() string {
|
||||||
|
return string(abs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canonical returns a canonicalized form of abs, similar to filepath.Abs
|
||||||
|
// (including filepath.Clean). Unlike filepath.Clean, this preserves "" as a
|
||||||
|
// special case.
|
||||||
|
func (abs absPath) Canonical() (absPath, error) {
|
||||||
|
if abs == "" {
|
||||||
|
return abs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := filepath.Abs(abs.String())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return absPath(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join appends more path elements to abs, like filepath.Join.
|
||||||
|
func (abs absPath) Join(elems ...string) absPath {
|
||||||
|
all := make([]string, 0, 1+len(elems))
|
||||||
|
all = append(all, abs.String())
|
||||||
|
all = append(all, elems...)
|
||||||
|
return absPath(filepath.Join(all...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split breaks abs into stem and leaf parts (often directory and file, but not
|
||||||
|
// necessarily), similar to filepath.Split. Unlike filepath.Split, the
|
||||||
|
// resulting stem part does not have any trailing path separators.
|
||||||
|
func (abs absPath) Split() (string, string) {
|
||||||
|
if abs == "" {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// filepath.Split promises that dir+base == input, but trailing slashes on
|
||||||
|
// the dir is confusing and ugly.
|
||||||
|
pathSep := string(os.PathSeparator)
|
||||||
|
dir, base := filepath.Split(strings.TrimRight(abs.String(), pathSep))
|
||||||
|
dir = strings.TrimRight(dir, pathSep)
|
||||||
|
if len(dir) == 0 {
|
||||||
|
dir = string(os.PathSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir, base
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dir returns the stem part of abs without the leaf, like filepath.Dir.
|
||||||
|
func (abs absPath) Dir() string {
|
||||||
|
dir, _ := abs.Split()
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base returns the leaf part of abs without the stem, like filepath.Base.
|
||||||
|
func (abs absPath) Base() string {
|
||||||
|
_, base := abs.Split()
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,233 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAbsPathString(t *testing.T) {
|
||||||
|
testCases := []string{
|
||||||
|
"",
|
||||||
|
"/",
|
||||||
|
"//",
|
||||||
|
"/dir",
|
||||||
|
"/dir/",
|
||||||
|
"/dir//",
|
||||||
|
"/dir/sub",
|
||||||
|
"/dir/sub/",
|
||||||
|
"/dir//sub",
|
||||||
|
"/dir//sub/",
|
||||||
|
"dir",
|
||||||
|
"dir/sub",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
if want, got := tc, absPath(tc).String(); want != got {
|
||||||
|
t.Errorf("expected %q, got %q", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAbsPathCanonical(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
in absPath
|
||||||
|
exp absPath
|
||||||
|
}{{
|
||||||
|
in: "",
|
||||||
|
exp: "",
|
||||||
|
}, {
|
||||||
|
in: "/",
|
||||||
|
exp: "/",
|
||||||
|
}, {
|
||||||
|
in: "/one",
|
||||||
|
exp: "/one",
|
||||||
|
}, {
|
||||||
|
in: "/one/two",
|
||||||
|
exp: "/one/two",
|
||||||
|
}, {
|
||||||
|
in: "/one/two/",
|
||||||
|
exp: "/one/two",
|
||||||
|
}, {
|
||||||
|
in: "/one//two",
|
||||||
|
exp: "/one/two",
|
||||||
|
}, {
|
||||||
|
in: "/one/two/../three",
|
||||||
|
exp: "/one/three",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
want := tc.exp
|
||||||
|
got, err := tc.in.Canonical()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%q: unexpected error: %v", tc.in, err)
|
||||||
|
} else if want != got {
|
||||||
|
t.Errorf("%q: expected %q, got %q", tc.in, want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAbsPathJoin(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
base absPath
|
||||||
|
more []string
|
||||||
|
expect absPath
|
||||||
|
}{{
|
||||||
|
base: "/dir",
|
||||||
|
more: nil,
|
||||||
|
expect: "/dir",
|
||||||
|
}, {
|
||||||
|
base: "/dir",
|
||||||
|
more: []string{"one"},
|
||||||
|
expect: "/dir/one",
|
||||||
|
}, {
|
||||||
|
base: "/dir",
|
||||||
|
more: []string{"one", "two"},
|
||||||
|
expect: "/dir/one/two",
|
||||||
|
}, {
|
||||||
|
base: "/dir",
|
||||||
|
more: []string{"one", "two", "three"},
|
||||||
|
expect: "/dir/one/two/three",
|
||||||
|
}, {
|
||||||
|
base: "/dir",
|
||||||
|
more: []string{"with/slash"},
|
||||||
|
expect: "/dir/with/slash",
|
||||||
|
}, {
|
||||||
|
base: "/dir",
|
||||||
|
more: []string{"with/trailingslash/"},
|
||||||
|
expect: "/dir/with/trailingslash",
|
||||||
|
}, {
|
||||||
|
base: "/dir",
|
||||||
|
more: []string{"with//twoslash"},
|
||||||
|
expect: "/dir/with/twoslash",
|
||||||
|
}, {
|
||||||
|
base: "/dir",
|
||||||
|
more: []string{"one/1", "two/2", "three/3"},
|
||||||
|
expect: "/dir/one/1/two/2/three/3",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
if want, got := tc.expect, tc.base.Join(tc.more...); want != got {
|
||||||
|
t.Errorf("(%q, %q): expected %q, got %q", tc.base, tc.more, want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAbsPathSplit(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
in absPath
|
||||||
|
expDir string
|
||||||
|
expBase string
|
||||||
|
}{{
|
||||||
|
in: "",
|
||||||
|
expDir: "",
|
||||||
|
expBase: "",
|
||||||
|
}, {
|
||||||
|
in: "/",
|
||||||
|
expDir: "/",
|
||||||
|
expBase: "",
|
||||||
|
}, {
|
||||||
|
in: "//",
|
||||||
|
expDir: "/",
|
||||||
|
expBase: "",
|
||||||
|
}, {
|
||||||
|
in: "/one",
|
||||||
|
expDir: "/",
|
||||||
|
expBase: "one",
|
||||||
|
}, {
|
||||||
|
in: "/one/two",
|
||||||
|
expDir: "/one",
|
||||||
|
expBase: "two",
|
||||||
|
}, {
|
||||||
|
in: "/one/two/",
|
||||||
|
expDir: "/one",
|
||||||
|
expBase: "two",
|
||||||
|
}, {
|
||||||
|
in: "/one//two",
|
||||||
|
expDir: "/one",
|
||||||
|
expBase: "two",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
wantDir, wantBase := tc.expDir, tc.expBase
|
||||||
|
if gotDir, gotBase := tc.in.Split(); wantDir != gotDir || wantBase != gotBase {
|
||||||
|
t.Errorf("%q: expected (%q, %q), got (%q, %q)", tc.in, wantDir, wantBase, gotDir, gotBase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAbsPathDir(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
in absPath
|
||||||
|
exp string
|
||||||
|
}{{
|
||||||
|
in: "",
|
||||||
|
exp: "",
|
||||||
|
}, {
|
||||||
|
in: "/",
|
||||||
|
exp: "/",
|
||||||
|
}, {
|
||||||
|
in: "/one",
|
||||||
|
exp: "/",
|
||||||
|
}, {
|
||||||
|
in: "/one/two",
|
||||||
|
exp: "/one",
|
||||||
|
}, {
|
||||||
|
in: "/one/two/",
|
||||||
|
exp: "/one",
|
||||||
|
}, {
|
||||||
|
in: "/one//two",
|
||||||
|
exp: "/one",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
if want, got := tc.exp, tc.in.Dir(); want != got {
|
||||||
|
t.Errorf("%q: expected %q, got %q", tc.in, want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAbsPathBase(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
in absPath
|
||||||
|
exp string
|
||||||
|
}{{
|
||||||
|
in: "",
|
||||||
|
exp: "",
|
||||||
|
}, {
|
||||||
|
in: "/",
|
||||||
|
exp: "",
|
||||||
|
}, {
|
||||||
|
in: "/one",
|
||||||
|
exp: "one",
|
||||||
|
}, {
|
||||||
|
in: "/one/two",
|
||||||
|
exp: "two",
|
||||||
|
}, {
|
||||||
|
in: "/one/two/",
|
||||||
|
exp: "two",
|
||||||
|
}, {
|
||||||
|
in: "/one//two",
|
||||||
|
exp: "two",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
if want, got := tc.exp, tc.in.Base(); want != got {
|
||||||
|
t.Errorf("%q: expected %q, got %q", tc.in, want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
type credential struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
PasswordFile string `json:"password-file,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c credential) String() string {
|
||||||
|
jb, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("<encoding error: %v>", err)
|
||||||
|
}
|
||||||
|
return string(jb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// credentialSliceValue is for flags.
|
||||||
|
type credentialSliceValue struct {
|
||||||
|
value []credential
|
||||||
|
changed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ pflag.Value = &credentialSliceValue{}
|
||||||
|
var _ pflag.SliceValue = &credentialSliceValue{}
|
||||||
|
|
||||||
|
// pflagCredentialSlice is like pflag.StringSlice()
|
||||||
|
func pflagCredentialSlice(name, def, usage string) *[]credential {
|
||||||
|
p := &credentialSliceValue{}
|
||||||
|
_ = p.Set(def)
|
||||||
|
pflag.Var(p, name, usage)
|
||||||
|
return &p.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshal is like json.Unmarshal, but fails on unknown fields.
|
||||||
|
func (cs credentialSliceValue) unmarshal(val string, out any) error {
|
||||||
|
dec := json.NewDecoder(strings.NewReader(val))
|
||||||
|
dec.DisallowUnknownFields()
|
||||||
|
return dec.Decode(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeList handles a string-encoded JSON object.
|
||||||
|
func (cs credentialSliceValue) decodeObject(val string) (credential, error) {
|
||||||
|
var cred credential
|
||||||
|
if err := cs.unmarshal(val, &cred); err != nil {
|
||||||
|
return credential{}, err
|
||||||
|
}
|
||||||
|
return cred, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeList handles a string-encoded JSON list.
|
||||||
|
func (cs credentialSliceValue) decodeList(val string) ([]credential, error) {
|
||||||
|
var creds []credential
|
||||||
|
if err := cs.unmarshal(val, &creds); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return creds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode handles a string-encoded JSON object or list.
|
||||||
|
func (cs credentialSliceValue) decode(val string) ([]credential, error) {
|
||||||
|
s := strings.TrimSpace(val)
|
||||||
|
if s == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// If it tastes like an object...
|
||||||
|
if s[0] == '{' {
|
||||||
|
cred, err := cs.decodeObject(s)
|
||||||
|
return []credential{cred}, err
|
||||||
|
}
|
||||||
|
// If it tastes like a list...
|
||||||
|
if s[0] == '[' {
|
||||||
|
return cs.decodeList(s)
|
||||||
|
}
|
||||||
|
// Otherwise, bad
|
||||||
|
return nil, fmt.Errorf("not a JSON object or list")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *credentialSliceValue) Set(val string) error {
|
||||||
|
v, err := cs.decode(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cs.changed {
|
||||||
|
cs.value = v
|
||||||
|
} else {
|
||||||
|
cs.value = append(cs.value, v...)
|
||||||
|
}
|
||||||
|
cs.changed = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs credentialSliceValue) Type() string {
|
||||||
|
return "credentialSlice"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs credentialSliceValue) String() string {
|
||||||
|
if len(cs.value) == 0 {
|
||||||
|
return "[]"
|
||||||
|
}
|
||||||
|
jb, err := json.Marshal(cs.value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("<encoding error: %v>", err)
|
||||||
|
}
|
||||||
|
return string(jb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *credentialSliceValue) Append(val string) error {
|
||||||
|
v, err := cs.decodeObject(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cs.value = append(cs.value, v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *credentialSliceValue) Replace(val []string) error {
|
||||||
|
creds := []credential{}
|
||||||
|
for _, s := range val {
|
||||||
|
v, err := cs.decodeObject(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
creds = append(creds, v)
|
||||||
|
}
|
||||||
|
cs.value = creds
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs credentialSliceValue) GetSlice() []string {
|
||||||
|
if len(cs.value) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ret := []string{}
|
||||||
|
for _, cred := range cs.value {
|
||||||
|
ret = append(ret, cred.String())
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func envString(def string, key string, alts ...string) string {
|
||||||
|
if val := os.Getenv(key); val != "" {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
for _, alt := range alts {
|
||||||
|
if val := os.Getenv(alt); val != "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
func envStringArray(def string, key string, alts ...string) []string {
|
||||||
|
parse := func(s string) []string {
|
||||||
|
return strings.Split(s, ":")
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := os.Getenv(key); val != "" {
|
||||||
|
return parse(val)
|
||||||
|
}
|
||||||
|
for _, alt := range alts {
|
||||||
|
if val := os.Getenv(alt); val != "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
|
||||||
|
return parse(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parse(def)
|
||||||
|
}
|
||||||
|
|
||||||
|
func envBoolOrError(def bool, key string, alts ...string) (bool, error) {
|
||||||
|
parse := func(key, val string) (bool, error) {
|
||||||
|
parsed, err := strconv.ParseBool(val)
|
||||||
|
if err == nil {
|
||||||
|
return parsed, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("ERROR: invalid bool env %s=%q: %w", key, val, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := os.Getenv(key); val != "" {
|
||||||
|
return parse(key, val)
|
||||||
|
}
|
||||||
|
for _, alt := range alts {
|
||||||
|
if val := os.Getenv(alt); val != "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
|
||||||
|
return parse(alt, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return def, nil
|
||||||
|
}
|
||||||
|
func envBool(def bool, key string, alts ...string) bool {
|
||||||
|
val, err := envBoolOrError(def, key, alts...)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func envIntOrError(def int, key string, alts ...string) (int, error) {
|
||||||
|
parse := func(key, val string) (int, error) {
|
||||||
|
parsed, err := strconv.ParseInt(val, 0, 0)
|
||||||
|
if err == nil {
|
||||||
|
return int(parsed), nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("ERROR: invalid int env %s=%q: %w", key, val, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := os.Getenv(key); val != "" {
|
||||||
|
return parse(key, val)
|
||||||
|
}
|
||||||
|
for _, alt := range alts {
|
||||||
|
if val := os.Getenv(alt); val != "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
|
||||||
|
return parse(alt, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return def, nil
|
||||||
|
}
|
||||||
|
func envInt(def int, key string, alts ...string) int {
|
||||||
|
val, err := envIntOrError(def, key, alts...)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func envFloatOrError(def float64, key string, alts ...string) (float64, error) {
|
||||||
|
parse := func(key, val string) (float64, error) {
|
||||||
|
parsed, err := strconv.ParseFloat(val, 64)
|
||||||
|
if err == nil {
|
||||||
|
return parsed, nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("ERROR: invalid float env %s=%q: %w", key, val, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := os.Getenv(key); val != "" {
|
||||||
|
return parse(key, val)
|
||||||
|
}
|
||||||
|
for _, alt := range alts {
|
||||||
|
if val := os.Getenv(alt); val != "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
|
||||||
|
return parse(alt, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return def, nil
|
||||||
|
}
|
||||||
|
func envFloat(def float64, key string, alts ...string) float64 {
|
||||||
|
val, err := envFloatOrError(def, key, alts...)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func envDurationOrError(def time.Duration, key string, alts ...string) (time.Duration, error) {
|
||||||
|
parse := func(key, val string) (time.Duration, error) {
|
||||||
|
parsed, err := time.ParseDuration(val)
|
||||||
|
if err == nil {
|
||||||
|
return parsed, nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("ERROR: invalid duration env %s=%q: %w", key, val, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := os.Getenv(key); val != "" {
|
||||||
|
return parse(key, val)
|
||||||
|
}
|
||||||
|
for _, alt := range alts {
|
||||||
|
if val := os.Getenv(alt); val != "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
|
||||||
|
return parse(alt, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return def, nil
|
||||||
|
}
|
||||||
|
func envDuration(def time.Duration, key string, alts ...string) time.Duration {
|
||||||
|
val, err := envDurationOrError(def, key, alts...)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testKey = "KEY"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEnvBool(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
value string
|
||||||
|
def bool
|
||||||
|
exp bool
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{"true", true, true, false},
|
||||||
|
{"true", false, true, false},
|
||||||
|
{"", true, true, false},
|
||||||
|
{"", false, false, false},
|
||||||
|
{"false", true, false, false},
|
||||||
|
{"false", false, false, false},
|
||||||
|
{"", true, true, false},
|
||||||
|
{"", false, false, false},
|
||||||
|
{"no true", false, false, true},
|
||||||
|
{"no false", false, false, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range cases {
|
||||||
|
os.Setenv(testKey, testCase.value)
|
||||||
|
val, err := envBoolOrError(testCase.def, testKey)
|
||||||
|
if err != nil && !testCase.err {
|
||||||
|
t.Fatalf("%q: unexpected error: %v", testCase.value, err)
|
||||||
|
}
|
||||||
|
if err == nil && testCase.err {
|
||||||
|
t.Fatalf("%q: unexpected success", testCase.value)
|
||||||
|
}
|
||||||
|
if val != testCase.exp {
|
||||||
|
t.Fatalf("%q: expected %v but %v returned", testCase.value, testCase.exp, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvString(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
value string
|
||||||
|
def string
|
||||||
|
exp string
|
||||||
|
}{
|
||||||
|
{"true", "true", "true"},
|
||||||
|
{"true", "false", "true"},
|
||||||
|
{"", "true", "true"},
|
||||||
|
{"", "false", "false"},
|
||||||
|
{"false", "true", "false"},
|
||||||
|
{"false", "false", "false"},
|
||||||
|
{"", "true", "true"},
|
||||||
|
{"", "false", "false"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range cases {
|
||||||
|
os.Setenv(testKey, testCase.value)
|
||||||
|
val := envString(testCase.def, testKey)
|
||||||
|
if val != testCase.exp {
|
||||||
|
t.Fatalf("%q: expected %v but %v returned", testCase.value, testCase.exp, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvInt(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
value string
|
||||||
|
def int
|
||||||
|
exp int
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{"0", 1, 0, false},
|
||||||
|
{"", 0, 0, false},
|
||||||
|
{"-1", 0, -1, false},
|
||||||
|
{"abcd", 0, 0, true},
|
||||||
|
{"abcd", 0, 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range cases {
|
||||||
|
os.Setenv(testKey, testCase.value)
|
||||||
|
val, err := envIntOrError(testCase.def, testKey)
|
||||||
|
if err != nil && !testCase.err {
|
||||||
|
t.Fatalf("%q: unexpected error: %v", testCase.value, err)
|
||||||
|
}
|
||||||
|
if err == nil && testCase.err {
|
||||||
|
t.Fatalf("%q: unexpected success", testCase.value)
|
||||||
|
}
|
||||||
|
if val != testCase.exp {
|
||||||
|
t.Fatalf("%q: expected %v but %v returned", testCase.value, testCase.exp, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvFloat(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
value string
|
||||||
|
def float64
|
||||||
|
exp float64
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{"0.5", 0, 0.5, false},
|
||||||
|
{"", 0.5, 0.5, false},
|
||||||
|
{"-0.5", 0, -0.5, false},
|
||||||
|
{"abcd", 0, 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range cases {
|
||||||
|
os.Setenv(testKey, testCase.value)
|
||||||
|
val, err := envFloatOrError(testCase.def, testKey)
|
||||||
|
if err != nil && !testCase.err {
|
||||||
|
t.Fatalf("%q: unexpected error: %v", testCase.value, err)
|
||||||
|
}
|
||||||
|
if err == nil && testCase.err {
|
||||||
|
t.Fatalf("%q: unexpected success", testCase.value)
|
||||||
|
}
|
||||||
|
if val != testCase.exp {
|
||||||
|
t.Fatalf("%q: expected %v but %v returned", testCase.value, testCase.exp, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvDuration(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
value string
|
||||||
|
def time.Duration
|
||||||
|
exp time.Duration
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{"1s", 0, time.Second, false},
|
||||||
|
{"", time.Minute, time.Minute, false},
|
||||||
|
{"1h", 0, time.Hour, false},
|
||||||
|
{"abcd", 0, 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range cases {
|
||||||
|
os.Setenv(testKey, testCase.value)
|
||||||
|
val, err := envDurationOrError(testCase.def, testKey)
|
||||||
|
if err != nil && !testCase.err {
|
||||||
|
t.Fatalf("%q: unexpected error: %v", testCase.value, err)
|
||||||
|
}
|
||||||
|
if err == nil && testCase.err {
|
||||||
|
t.Fatalf("%q: unexpected success", testCase.value)
|
||||||
|
}
|
||||||
|
if val != testCase.exp {
|
||||||
|
t.Fatalf("%q: expected %v but %v returned", testCase.value, testCase.exp, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
357
main.go
357
main.go
|
|
@ -21,7 +21,6 @@ package main // import "k8s.io/git-sync/cmd/git-sync"
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -106,362 +105,6 @@ const (
|
||||||
|
|
||||||
const defaultDirMode = os.FileMode(0775) // subject to umask
|
const defaultDirMode = os.FileMode(0775) // subject to umask
|
||||||
|
|
||||||
type credential struct {
|
|
||||||
URL string `json:"url"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password,omitempty"`
|
|
||||||
PasswordFile string `json:"password-file,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c credential) String() string {
|
|
||||||
jb, err := json.Marshal(c)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Sprintf("<encoding error: %v>", err)
|
|
||||||
}
|
|
||||||
return string(jb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// credentialSliceValue is for flags.
|
|
||||||
type credentialSliceValue struct {
|
|
||||||
value []credential
|
|
||||||
changed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ pflag.Value = &credentialSliceValue{}
|
|
||||||
var _ pflag.SliceValue = &credentialSliceValue{}
|
|
||||||
|
|
||||||
// pflagCredentialSlice is like pflag.StringSlice()
|
|
||||||
func pflagCredentialSlice(name, def, usage string) *[]credential {
|
|
||||||
p := &credentialSliceValue{}
|
|
||||||
_ = p.Set(def)
|
|
||||||
pflag.Var(p, name, usage)
|
|
||||||
return &p.value
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmarshal is like json.Unmarshal, but fails on unknown fields.
|
|
||||||
func (cs credentialSliceValue) unmarshal(val string, out any) error {
|
|
||||||
dec := json.NewDecoder(strings.NewReader(val))
|
|
||||||
dec.DisallowUnknownFields()
|
|
||||||
return dec.Decode(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeList handles a string-encoded JSON object.
|
|
||||||
func (cs credentialSliceValue) decodeObject(val string) (credential, error) {
|
|
||||||
var cred credential
|
|
||||||
if err := cs.unmarshal(val, &cred); err != nil {
|
|
||||||
return credential{}, err
|
|
||||||
}
|
|
||||||
return cred, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeList handles a string-encoded JSON list.
|
|
||||||
func (cs credentialSliceValue) decodeList(val string) ([]credential, error) {
|
|
||||||
var creds []credential
|
|
||||||
if err := cs.unmarshal(val, &creds); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return creds, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode handles a string-encoded JSON object or list.
|
|
||||||
func (cs credentialSliceValue) decode(val string) ([]credential, error) {
|
|
||||||
s := strings.TrimSpace(val)
|
|
||||||
if s == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
// If it tastes like an object...
|
|
||||||
if s[0] == '{' {
|
|
||||||
cred, err := cs.decodeObject(s)
|
|
||||||
return []credential{cred}, err
|
|
||||||
}
|
|
||||||
// If it tastes like a list...
|
|
||||||
if s[0] == '[' {
|
|
||||||
return cs.decodeList(s)
|
|
||||||
}
|
|
||||||
// Otherwise, bad
|
|
||||||
return nil, fmt.Errorf("not a JSON object or list")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *credentialSliceValue) Set(val string) error {
|
|
||||||
v, err := cs.decode(val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cs.changed {
|
|
||||||
cs.value = v
|
|
||||||
} else {
|
|
||||||
cs.value = append(cs.value, v...)
|
|
||||||
}
|
|
||||||
cs.changed = true
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs credentialSliceValue) Type() string {
|
|
||||||
return "credentialSlice"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs credentialSliceValue) String() string {
|
|
||||||
if len(cs.value) == 0 {
|
|
||||||
return "[]"
|
|
||||||
}
|
|
||||||
jb, err := json.Marshal(cs.value)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Sprintf("<encoding error: %v>", err)
|
|
||||||
}
|
|
||||||
return string(jb)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *credentialSliceValue) Append(val string) error {
|
|
||||||
v, err := cs.decodeObject(val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cs.value = append(cs.value, v)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *credentialSliceValue) Replace(val []string) error {
|
|
||||||
creds := []credential{}
|
|
||||||
for _, s := range val {
|
|
||||||
v, err := cs.decodeObject(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
creds = append(creds, v)
|
|
||||||
}
|
|
||||||
cs.value = creds
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs credentialSliceValue) GetSlice() []string {
|
|
||||||
if len(cs.value) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
ret := []string{}
|
|
||||||
for _, cred := range cs.value {
|
|
||||||
ret = append(ret, cred.String())
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func envString(def string, key string, alts ...string) string {
|
|
||||||
if val := os.Getenv(key); val != "" {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
for _, alt := range alts {
|
|
||||||
if val := os.Getenv(alt); val != "" {
|
|
||||||
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return def
|
|
||||||
}
|
|
||||||
|
|
||||||
func envStringArray(def string, key string, alts ...string) []string {
|
|
||||||
parse := func(s string) []string {
|
|
||||||
return strings.Split(s, ":")
|
|
||||||
}
|
|
||||||
|
|
||||||
if val := os.Getenv(key); val != "" {
|
|
||||||
return parse(val)
|
|
||||||
}
|
|
||||||
for _, alt := range alts {
|
|
||||||
if val := os.Getenv(alt); val != "" {
|
|
||||||
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
|
|
||||||
return parse(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return parse(def)
|
|
||||||
}
|
|
||||||
|
|
||||||
func envBoolOrError(def bool, key string, alts ...string) (bool, error) {
|
|
||||||
parse := func(key, val string) (bool, error) {
|
|
||||||
parsed, err := strconv.ParseBool(val)
|
|
||||||
if err == nil {
|
|
||||||
return parsed, nil
|
|
||||||
}
|
|
||||||
return false, fmt.Errorf("ERROR: invalid bool env %s=%q: %w", key, val, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if val := os.Getenv(key); val != "" {
|
|
||||||
return parse(key, val)
|
|
||||||
}
|
|
||||||
for _, alt := range alts {
|
|
||||||
if val := os.Getenv(alt); val != "" {
|
|
||||||
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
|
|
||||||
return parse(alt, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return def, nil
|
|
||||||
}
|
|
||||||
func envBool(def bool, key string, alts ...string) bool {
|
|
||||||
val, err := envBoolOrError(def, key, alts...)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(1)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
func envIntOrError(def int, key string, alts ...string) (int, error) {
|
|
||||||
parse := func(key, val string) (int, error) {
|
|
||||||
parsed, err := strconv.ParseInt(val, 0, 0)
|
|
||||||
if err == nil {
|
|
||||||
return int(parsed), nil
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("ERROR: invalid int env %s=%q: %w", key, val, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if val := os.Getenv(key); val != "" {
|
|
||||||
return parse(key, val)
|
|
||||||
}
|
|
||||||
for _, alt := range alts {
|
|
||||||
if val := os.Getenv(alt); val != "" {
|
|
||||||
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
|
|
||||||
return parse(alt, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return def, nil
|
|
||||||
}
|
|
||||||
func envInt(def int, key string, alts ...string) int {
|
|
||||||
val, err := envIntOrError(def, key, alts...)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(1)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
func envFloatOrError(def float64, key string, alts ...string) (float64, error) {
|
|
||||||
parse := func(key, val string) (float64, error) {
|
|
||||||
parsed, err := strconv.ParseFloat(val, 64)
|
|
||||||
if err == nil {
|
|
||||||
return parsed, nil
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("ERROR: invalid float env %s=%q: %w", key, val, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if val := os.Getenv(key); val != "" {
|
|
||||||
return parse(key, val)
|
|
||||||
}
|
|
||||||
for _, alt := range alts {
|
|
||||||
if val := os.Getenv(alt); val != "" {
|
|
||||||
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
|
|
||||||
return parse(alt, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return def, nil
|
|
||||||
}
|
|
||||||
func envFloat(def float64, key string, alts ...string) float64 {
|
|
||||||
val, err := envFloatOrError(def, key, alts...)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(1)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
func envDurationOrError(def time.Duration, key string, alts ...string) (time.Duration, error) {
|
|
||||||
parse := func(key, val string) (time.Duration, error) {
|
|
||||||
parsed, err := time.ParseDuration(val)
|
|
||||||
if err == nil {
|
|
||||||
return parsed, nil
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("ERROR: invalid duration env %s=%q: %w", key, val, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if val := os.Getenv(key); val != "" {
|
|
||||||
return parse(key, val)
|
|
||||||
}
|
|
||||||
for _, alt := range alts {
|
|
||||||
if val := os.Getenv(alt); val != "" {
|
|
||||||
fmt.Fprintf(os.Stderr, "env %s has been deprecated, use %s instead\n", alt, key)
|
|
||||||
return parse(alt, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return def, nil
|
|
||||||
}
|
|
||||||
func envDuration(def time.Duration, key string, alts ...string) time.Duration {
|
|
||||||
val, err := envDurationOrError(def, key, alts...)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(1)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// absPath is an absolute path string. This type is intended to make it clear
|
|
||||||
// when strings are absolute paths vs something else. This does not verify or
|
|
||||||
// mutate the input, so careless callers could make instances of this type that
|
|
||||||
// are not actually absolute paths, or even "".
|
|
||||||
type absPath string
|
|
||||||
|
|
||||||
// String returns abs as a string.
|
|
||||||
func (abs absPath) String() string {
|
|
||||||
return string(abs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Canonical returns a canonicalized form of abs, similar to filepath.Abs
|
|
||||||
// (including filepath.Clean). Unlike filepath.Clean, this preserves "" as a
|
|
||||||
// special case.
|
|
||||||
func (abs absPath) Canonical() (absPath, error) {
|
|
||||||
if abs == "" {
|
|
||||||
return abs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := filepath.Abs(abs.String())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return absPath(result), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join appends more path elements to abs, like filepath.Join.
|
|
||||||
func (abs absPath) Join(elems ...string) absPath {
|
|
||||||
all := make([]string, 0, 1+len(elems))
|
|
||||||
all = append(all, abs.String())
|
|
||||||
all = append(all, elems...)
|
|
||||||
return absPath(filepath.Join(all...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split breaks abs into stem and leaf parts (often directory and file, but not
|
|
||||||
// necessarily), similar to filepath.Split. Unlike filepath.Split, the
|
|
||||||
// resulting stem part does not have any trailing path separators.
|
|
||||||
func (abs absPath) Split() (string, string) {
|
|
||||||
if abs == "" {
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// filepath.Split promises that dir+base == input, but trailing slashes on
|
|
||||||
// the dir is confusing and ugly.
|
|
||||||
pathSep := string(os.PathSeparator)
|
|
||||||
dir, base := filepath.Split(strings.TrimRight(abs.String(), pathSep))
|
|
||||||
dir = strings.TrimRight(dir, pathSep)
|
|
||||||
if len(dir) == 0 {
|
|
||||||
dir = string(os.PathSeparator)
|
|
||||||
}
|
|
||||||
|
|
||||||
return dir, base
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dir returns the stem part of abs without the leaf, like filepath.Dir.
|
|
||||||
func (abs absPath) Dir() string {
|
|
||||||
dir, _ := abs.Split()
|
|
||||||
return dir
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base returns the leaf part of abs without the stem, like filepath.Base.
|
|
||||||
func (abs absPath) Base() string {
|
|
||||||
_, base := abs.Split()
|
|
||||||
return base
|
|
||||||
}
|
|
||||||
|
|
||||||
// repoSync represents the remote repo and the local sync of it.
|
// repoSync represents the remote repo and the local sync of it.
|
||||||
type repoSync struct {
|
type repoSync struct {
|
||||||
cmd string // the git command to run
|
cmd string // the git command to run
|
||||||
|
|
|
||||||
360
main_test.go
360
main_test.go
|
|
@ -27,154 +27,6 @@ import (
|
||||||
"go.uber.org/goleak"
|
"go.uber.org/goleak"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
testKey = "KEY"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEnvBool(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
value string
|
|
||||||
def bool
|
|
||||||
exp bool
|
|
||||||
err bool
|
|
||||||
}{
|
|
||||||
{"true", true, true, false},
|
|
||||||
{"true", false, true, false},
|
|
||||||
{"", true, true, false},
|
|
||||||
{"", false, false, false},
|
|
||||||
{"false", true, false, false},
|
|
||||||
{"false", false, false, false},
|
|
||||||
{"", true, true, false},
|
|
||||||
{"", false, false, false},
|
|
||||||
{"no true", false, false, true},
|
|
||||||
{"no false", false, false, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range cases {
|
|
||||||
os.Setenv(testKey, testCase.value)
|
|
||||||
val, err := envBoolOrError(testCase.def, testKey)
|
|
||||||
if err != nil && !testCase.err {
|
|
||||||
t.Fatalf("%q: unexpected error: %v", testCase.value, err)
|
|
||||||
}
|
|
||||||
if err == nil && testCase.err {
|
|
||||||
t.Fatalf("%q: unexpected success", testCase.value)
|
|
||||||
}
|
|
||||||
if val != testCase.exp {
|
|
||||||
t.Fatalf("%q: expected %v but %v returned", testCase.value, testCase.exp, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnvString(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
value string
|
|
||||||
def string
|
|
||||||
exp string
|
|
||||||
}{
|
|
||||||
{"true", "true", "true"},
|
|
||||||
{"true", "false", "true"},
|
|
||||||
{"", "true", "true"},
|
|
||||||
{"", "false", "false"},
|
|
||||||
{"false", "true", "false"},
|
|
||||||
{"false", "false", "false"},
|
|
||||||
{"", "true", "true"},
|
|
||||||
{"", "false", "false"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range cases {
|
|
||||||
os.Setenv(testKey, testCase.value)
|
|
||||||
val := envString(testCase.def, testKey)
|
|
||||||
if val != testCase.exp {
|
|
||||||
t.Fatalf("%q: expected %v but %v returned", testCase.value, testCase.exp, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnvInt(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
value string
|
|
||||||
def int
|
|
||||||
exp int
|
|
||||||
err bool
|
|
||||||
}{
|
|
||||||
{"0", 1, 0, false},
|
|
||||||
{"", 0, 0, false},
|
|
||||||
{"-1", 0, -1, false},
|
|
||||||
{"abcd", 0, 0, true},
|
|
||||||
{"abcd", 0, 0, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range cases {
|
|
||||||
os.Setenv(testKey, testCase.value)
|
|
||||||
val, err := envIntOrError(testCase.def, testKey)
|
|
||||||
if err != nil && !testCase.err {
|
|
||||||
t.Fatalf("%q: unexpected error: %v", testCase.value, err)
|
|
||||||
}
|
|
||||||
if err == nil && testCase.err {
|
|
||||||
t.Fatalf("%q: unexpected success", testCase.value)
|
|
||||||
}
|
|
||||||
if val != testCase.exp {
|
|
||||||
t.Fatalf("%q: expected %v but %v returned", testCase.value, testCase.exp, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnvFloat(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
value string
|
|
||||||
def float64
|
|
||||||
exp float64
|
|
||||||
err bool
|
|
||||||
}{
|
|
||||||
{"0.5", 0, 0.5, false},
|
|
||||||
{"", 0.5, 0.5, false},
|
|
||||||
{"-0.5", 0, -0.5, false},
|
|
||||||
{"abcd", 0, 0, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range cases {
|
|
||||||
os.Setenv(testKey, testCase.value)
|
|
||||||
val, err := envFloatOrError(testCase.def, testKey)
|
|
||||||
if err != nil && !testCase.err {
|
|
||||||
t.Fatalf("%q: unexpected error: %v", testCase.value, err)
|
|
||||||
}
|
|
||||||
if err == nil && testCase.err {
|
|
||||||
t.Fatalf("%q: unexpected success", testCase.value)
|
|
||||||
}
|
|
||||||
if val != testCase.exp {
|
|
||||||
t.Fatalf("%q: expected %v but %v returned", testCase.value, testCase.exp, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnvDuration(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
value string
|
|
||||||
def time.Duration
|
|
||||||
exp time.Duration
|
|
||||||
err bool
|
|
||||||
}{
|
|
||||||
{"1s", 0, time.Second, false},
|
|
||||||
{"", time.Minute, time.Minute, false},
|
|
||||||
{"1h", 0, time.Hour, false},
|
|
||||||
{"abcd", 0, 0, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range cases {
|
|
||||||
os.Setenv(testKey, testCase.value)
|
|
||||||
val, err := envDurationOrError(testCase.def, testKey)
|
|
||||||
if err != nil && !testCase.err {
|
|
||||||
t.Fatalf("%q: unexpected error: %v", testCase.value, err)
|
|
||||||
}
|
|
||||||
if err == nil && testCase.err {
|
|
||||||
t.Fatalf("%q: unexpected success", testCase.value)
|
|
||||||
}
|
|
||||||
if val != testCase.exp {
|
|
||||||
t.Fatalf("%q: expected %v but %v returned", testCase.value, testCase.exp, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeAbsPath(t *testing.T) {
|
func TestMakeAbsPath(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
path string
|
path string
|
||||||
|
|
@ -396,218 +248,6 @@ func TestParseGitConfigs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAbsPathString(t *testing.T) {
|
|
||||||
testCases := []string{
|
|
||||||
"",
|
|
||||||
"/",
|
|
||||||
"//",
|
|
||||||
"/dir",
|
|
||||||
"/dir/",
|
|
||||||
"/dir//",
|
|
||||||
"/dir/sub",
|
|
||||||
"/dir/sub/",
|
|
||||||
"/dir//sub",
|
|
||||||
"/dir//sub/",
|
|
||||||
"dir",
|
|
||||||
"dir/sub",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
if want, got := tc, absPath(tc).String(); want != got {
|
|
||||||
t.Errorf("expected %q, got %q", want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAbsPathCanonical(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
in absPath
|
|
||||||
exp absPath
|
|
||||||
}{{
|
|
||||||
in: "",
|
|
||||||
exp: "",
|
|
||||||
}, {
|
|
||||||
in: "/",
|
|
||||||
exp: "/",
|
|
||||||
}, {
|
|
||||||
in: "/one",
|
|
||||||
exp: "/one",
|
|
||||||
}, {
|
|
||||||
in: "/one/two",
|
|
||||||
exp: "/one/two",
|
|
||||||
}, {
|
|
||||||
in: "/one/two/",
|
|
||||||
exp: "/one/two",
|
|
||||||
}, {
|
|
||||||
in: "/one//two",
|
|
||||||
exp: "/one/two",
|
|
||||||
}, {
|
|
||||||
in: "/one/two/../three",
|
|
||||||
exp: "/one/three",
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
want := tc.exp
|
|
||||||
got, err := tc.in.Canonical()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%q: unexpected error: %v", tc.in, err)
|
|
||||||
} else if want != got {
|
|
||||||
t.Errorf("%q: expected %q, got %q", tc.in, want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAbsPathJoin(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
base absPath
|
|
||||||
more []string
|
|
||||||
expect absPath
|
|
||||||
}{{
|
|
||||||
base: "/dir",
|
|
||||||
more: nil,
|
|
||||||
expect: "/dir",
|
|
||||||
}, {
|
|
||||||
base: "/dir",
|
|
||||||
more: []string{"one"},
|
|
||||||
expect: "/dir/one",
|
|
||||||
}, {
|
|
||||||
base: "/dir",
|
|
||||||
more: []string{"one", "two"},
|
|
||||||
expect: "/dir/one/two",
|
|
||||||
}, {
|
|
||||||
base: "/dir",
|
|
||||||
more: []string{"one", "two", "three"},
|
|
||||||
expect: "/dir/one/two/three",
|
|
||||||
}, {
|
|
||||||
base: "/dir",
|
|
||||||
more: []string{"with/slash"},
|
|
||||||
expect: "/dir/with/slash",
|
|
||||||
}, {
|
|
||||||
base: "/dir",
|
|
||||||
more: []string{"with/trailingslash/"},
|
|
||||||
expect: "/dir/with/trailingslash",
|
|
||||||
}, {
|
|
||||||
base: "/dir",
|
|
||||||
more: []string{"with//twoslash"},
|
|
||||||
expect: "/dir/with/twoslash",
|
|
||||||
}, {
|
|
||||||
base: "/dir",
|
|
||||||
more: []string{"one/1", "two/2", "three/3"},
|
|
||||||
expect: "/dir/one/1/two/2/three/3",
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
if want, got := tc.expect, tc.base.Join(tc.more...); want != got {
|
|
||||||
t.Errorf("(%q, %q): expected %q, got %q", tc.base, tc.more, want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAbsPathSplit(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
in absPath
|
|
||||||
expDir string
|
|
||||||
expBase string
|
|
||||||
}{{
|
|
||||||
in: "",
|
|
||||||
expDir: "",
|
|
||||||
expBase: "",
|
|
||||||
}, {
|
|
||||||
in: "/",
|
|
||||||
expDir: "/",
|
|
||||||
expBase: "",
|
|
||||||
}, {
|
|
||||||
in: "//",
|
|
||||||
expDir: "/",
|
|
||||||
expBase: "",
|
|
||||||
}, {
|
|
||||||
in: "/one",
|
|
||||||
expDir: "/",
|
|
||||||
expBase: "one",
|
|
||||||
}, {
|
|
||||||
in: "/one/two",
|
|
||||||
expDir: "/one",
|
|
||||||
expBase: "two",
|
|
||||||
}, {
|
|
||||||
in: "/one/two/",
|
|
||||||
expDir: "/one",
|
|
||||||
expBase: "two",
|
|
||||||
}, {
|
|
||||||
in: "/one//two",
|
|
||||||
expDir: "/one",
|
|
||||||
expBase: "two",
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
wantDir, wantBase := tc.expDir, tc.expBase
|
|
||||||
if gotDir, gotBase := tc.in.Split(); wantDir != gotDir || wantBase != gotBase {
|
|
||||||
t.Errorf("%q: expected (%q, %q), got (%q, %q)", tc.in, wantDir, wantBase, gotDir, gotBase)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAbsPathDir(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
in absPath
|
|
||||||
exp string
|
|
||||||
}{{
|
|
||||||
in: "",
|
|
||||||
exp: "",
|
|
||||||
}, {
|
|
||||||
in: "/",
|
|
||||||
exp: "/",
|
|
||||||
}, {
|
|
||||||
in: "/one",
|
|
||||||
exp: "/",
|
|
||||||
}, {
|
|
||||||
in: "/one/two",
|
|
||||||
exp: "/one",
|
|
||||||
}, {
|
|
||||||
in: "/one/two/",
|
|
||||||
exp: "/one",
|
|
||||||
}, {
|
|
||||||
in: "/one//two",
|
|
||||||
exp: "/one",
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
if want, got := tc.exp, tc.in.Dir(); want != got {
|
|
||||||
t.Errorf("%q: expected %q, got %q", tc.in, want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAbsPathBase(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
in absPath
|
|
||||||
exp string
|
|
||||||
}{{
|
|
||||||
in: "",
|
|
||||||
exp: "",
|
|
||||||
}, {
|
|
||||||
in: "/",
|
|
||||||
exp: "",
|
|
||||||
}, {
|
|
||||||
in: "/one",
|
|
||||||
exp: "one",
|
|
||||||
}, {
|
|
||||||
in: "/one/two",
|
|
||||||
exp: "two",
|
|
||||||
}, {
|
|
||||||
in: "/one/two/",
|
|
||||||
exp: "two",
|
|
||||||
}, {
|
|
||||||
in: "/one//two",
|
|
||||||
exp: "two",
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
if want, got := tc.exp, tc.in.Base(); want != got {
|
|
||||||
t.Errorf("%q: expected %q, got %q", tc.in, want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDirIsEmpty(t *testing.T) {
|
func TestDirIsEmpty(t *testing.T) {
|
||||||
root := absPath(t.TempDir())
|
root := absPath(t.TempDir())
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue