mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-11 17:12:51 +00:00
manually add dependency on go-getter
This commit is contained in:
88
Gopkg.lock
generated
88
Gopkg.lock
generated
@@ -17,6 +17,14 @@
|
|||||||
pruneopts = ""
|
pruneopts = ""
|
||||||
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
|
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:98e84060475ed245c3b355042afd43a74aa7d32efe50658f4f995977916f9fc3"
|
||||||
|
name = "github.com/bgentry/go-netrc"
|
||||||
|
packages = ["netrc"]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "9fd32a8b3d3d3f9d43c341bfe098430e07609480"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
|
digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
|
||||||
name = "github.com/davecgh/go-spew"
|
name = "github.com/davecgh/go-spew"
|
||||||
@@ -52,6 +60,14 @@
|
|||||||
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
|
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
|
||||||
version = "v1.0.0"
|
version = "v1.0.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:858b7fe7b0f4bc7ef9953926828f2816ea52d01a88d72d1c45bc8c108f23c356"
|
||||||
|
name = "github.com/go-ini/ini"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "358ee7663966325963d4e8b2e1fbd570c5195153"
|
||||||
|
version = "v1.38.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:e116a4866bffeec941056a1fcfd37e520fad1ee60e4e3579719f19a43c392e10"
|
digest = "1:e116a4866bffeec941056a1fcfd37e520fad1ee60e4e3579719f19a43c392e10"
|
||||||
@@ -137,6 +153,41 @@
|
|||||||
revision = "ee43cbb60db7bd22502942cccbc39059117352ab"
|
revision = "ee43cbb60db7bd22502942cccbc39059117352ab"
|
||||||
version = "v0.1.0"
|
version = "v0.1.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:f5d25fd7bdda08e39e01193ef94a1ebf7547b1b931bcdec785d08050598f306c"
|
||||||
|
name = "github.com/hashicorp/go-cleanhttp"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "d5fe4b57a186c716b0e00b8c301cbd9b4182694d"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:fd15b3f6aac9d0fe68c6e38922282e0d2e88cd77b927ac3dd842e363645522c0"
|
||||||
|
name = "github.com/hashicorp/go-getter"
|
||||||
|
packages = [
|
||||||
|
".",
|
||||||
|
"helper/url",
|
||||||
|
]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "4bda8fa99001c61db3cad96b421d4c12a81f256d"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:2cf6c60c74eacadd31652674364af55c8d54a86b8ea193548f1c37f8c9af8f9c"
|
||||||
|
name = "github.com/hashicorp/go-safetemp"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "b1a1dbde6fdc11e3ae79efd9039009e22d4ae240"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:139bdc2c89779b8ff8b1150be28f889b0ed964e6da96f32cbc9035bd4642881c"
|
||||||
|
name = "github.com/hashicorp/go-version"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "270f2f71b1ee587f3b609f00f422b76a6b28f348"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
||||||
name = "github.com/inconshreveable/mousetrap"
|
name = "github.com/inconshreveable/mousetrap"
|
||||||
@@ -145,6 +196,13 @@
|
|||||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||||
version = "v1.0"
|
version = "v1.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:6f49eae0c1e5dab1dafafee34b207aeb7a42303105960944828c2079b92fc88e"
|
||||||
|
name = "github.com/jmespath/go-jmespath"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "0b12d6b5"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:9eab2325abbed0ebcee9d44bb3660a69d5d10e42d5ac4a0e77f7a6ea22bfce88"
|
digest = "1:9eab2325abbed0ebcee9d44bb3660a69d5d10e42d5ac4a0e77f7a6ea22bfce88"
|
||||||
name = "github.com/json-iterator/go"
|
name = "github.com/json-iterator/go"
|
||||||
@@ -165,6 +223,22 @@
|
|||||||
pruneopts = ""
|
pruneopts = ""
|
||||||
revision = "3fdea8d05856a0c8df22ed4bc71b3219245e4485"
|
revision = "3fdea8d05856a0c8df22ed4bc71b3219245e4485"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:83854f6b1d2ce047b69657e3a87ba7602f4c5505e8bdfd02ab857db8e983bde1"
|
||||||
|
name = "github.com/mitchellh/go-homedir"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "58046073cbffe2f25d425fe1331102f55cf719de"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:51c98e2c9a8d0a724a69f46421876af14e12132cb02f1d0e144785d752247162"
|
||||||
|
name = "github.com/mitchellh/go-testing-interface"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "a61a99592b77c9ba629d254a693acffaeb4b7e28"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:0c0ff2a89c1bb0d01887e1dac043ad7efbf3ec77482ef058ac423d13497e16fd"
|
digest = "1:0c0ff2a89c1bb0d01887e1dac043ad7efbf3ec77482ef058ac423d13497e16fd"
|
||||||
name = "github.com/modern-go/concurrent"
|
name = "github.com/modern-go/concurrent"
|
||||||
@@ -205,6 +279,19 @@
|
|||||||
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
||||||
version = "v1.0.1"
|
version = "v1.0.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:ee723e6a1962a196eeba1b24f82af61a4f60f8821d7aa96d48e787f8337bcffc"
|
||||||
|
name = "github.com/ulikunitz/xz"
|
||||||
|
packages = [
|
||||||
|
".",
|
||||||
|
"internal/hash",
|
||||||
|
"internal/xlog",
|
||||||
|
"lzma",
|
||||||
|
]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "0c6b41e72360850ca4f98dc341fd999726ea007f"
|
||||||
|
version = "v0.5.4"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:9e548233d0dc00e74be262e54a9d1bbe7e4c19e5951083520261740e37daeb02"
|
digest = "1:9e548233d0dc00e74be262e54a9d1bbe7e4c19e5951083520261740e37daeb02"
|
||||||
@@ -362,6 +449,7 @@
|
|||||||
"github.com/evanphx/json-patch",
|
"github.com/evanphx/json-patch",
|
||||||
"github.com/ghodss/yaml",
|
"github.com/ghodss/yaml",
|
||||||
"github.com/golang/glog",
|
"github.com/golang/glog",
|
||||||
|
"github.com/hashicorp/go-getter",
|
||||||
"github.com/pkg/errors",
|
"github.com/pkg/errors",
|
||||||
"github.com/spf13/cobra",
|
"github.com/spf13/cobra",
|
||||||
"k8s.io/api/core/v1",
|
"k8s.io/api/core/v1",
|
||||||
|
|||||||
3
vendor/github.com/bgentry/go-netrc/.hgignore
generated
vendored
Normal file
3
vendor/github.com/bgentry/go-netrc/.hgignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
syntax: glob
|
||||||
|
*.8
|
||||||
|
*.a
|
||||||
20
vendor/github.com/bgentry/go-netrc/LICENSE
generated
vendored
Normal file
20
vendor/github.com/bgentry/go-netrc/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Original version Copyright © 2010 Fazlul Shahriar <fshahriar@gmail.com>. Newer
|
||||||
|
portions Copyright © 2014 Blake Gentry <blakesgentry@gmail.com>.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
9
vendor/github.com/bgentry/go-netrc/README.md
generated
vendored
Normal file
9
vendor/github.com/bgentry/go-netrc/README.md
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# go-netrc
|
||||||
|
|
||||||
|
A Golang package for reading and writing netrc files. This package can parse netrc
|
||||||
|
files, make changes to them, and then serialize them back to netrc format, while
|
||||||
|
preserving any whitespace that was present in the source file.
|
||||||
|
|
||||||
|
[][godoc]
|
||||||
|
|
||||||
|
[godoc]: https://godoc.org/github.com/bgentry/go-netrc "go-netrc on Godoc.org"
|
||||||
13
vendor/github.com/bgentry/go-netrc/netrc/examples/bad_default_order.netrc
generated
vendored
Normal file
13
vendor/github.com/bgentry/go-netrc/netrc/examples/bad_default_order.netrc
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# I am a comment
|
||||||
|
machine mail.google.com
|
||||||
|
login joe@gmail.com
|
||||||
|
account gmail
|
||||||
|
password somethingSecret
|
||||||
|
# I am another comment
|
||||||
|
|
||||||
|
default
|
||||||
|
login anonymous
|
||||||
|
password joe@example.com
|
||||||
|
|
||||||
|
machine ray login demo password mypassword
|
||||||
|
|
||||||
22
vendor/github.com/bgentry/go-netrc/netrc/examples/good.netrc
generated
vendored
Normal file
22
vendor/github.com/bgentry/go-netrc/netrc/examples/good.netrc
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# I am a comment
|
||||||
|
machine mail.google.com
|
||||||
|
login joe@gmail.com
|
||||||
|
account justagmail #end of line comment with trailing space
|
||||||
|
password somethingSecret
|
||||||
|
# I am another comment
|
||||||
|
|
||||||
|
macdef allput
|
||||||
|
put src/*
|
||||||
|
|
||||||
|
macdef allput2
|
||||||
|
put src/*
|
||||||
|
put src2/*
|
||||||
|
|
||||||
|
machine ray login demo password mypassword
|
||||||
|
|
||||||
|
machine weirdlogin login uname password pass#pass
|
||||||
|
|
||||||
|
default
|
||||||
|
login anonymous
|
||||||
|
password joe@example.com
|
||||||
|
|
||||||
510
vendor/github.com/bgentry/go-netrc/netrc/netrc.go
generated
vendored
Normal file
510
vendor/github.com/bgentry/go-netrc/netrc/netrc.go
generated
vendored
Normal file
@@ -0,0 +1,510 @@
|
|||||||
|
package netrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tkType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
tkMachine tkType = iota
|
||||||
|
tkDefault
|
||||||
|
tkLogin
|
||||||
|
tkPassword
|
||||||
|
tkAccount
|
||||||
|
tkMacdef
|
||||||
|
tkComment
|
||||||
|
tkWhitespace
|
||||||
|
)
|
||||||
|
|
||||||
|
var keywords = map[string]tkType{
|
||||||
|
"machine": tkMachine,
|
||||||
|
"default": tkDefault,
|
||||||
|
"login": tkLogin,
|
||||||
|
"password": tkPassword,
|
||||||
|
"account": tkAccount,
|
||||||
|
"macdef": tkMacdef,
|
||||||
|
"#": tkComment,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Netrc struct {
|
||||||
|
tokens []*token
|
||||||
|
machines []*Machine
|
||||||
|
macros Macros
|
||||||
|
updateLock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindMachine returns the Machine in n named by name. If a machine named by
|
||||||
|
// name exists, it is returned. If no Machine with name name is found and there
|
||||||
|
// is a ``default'' machine, the ``default'' machine is returned. Otherwise, nil
|
||||||
|
// is returned.
|
||||||
|
func (n *Netrc) FindMachine(name string) (m *Machine) {
|
||||||
|
// TODO(bgentry): not safe for concurrency
|
||||||
|
var def *Machine
|
||||||
|
for _, m = range n.machines {
|
||||||
|
if m.Name == name {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
if m.IsDefault() {
|
||||||
|
def = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if def == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText implements the encoding.TextMarshaler interface to encode a
|
||||||
|
// Netrc into text format.
|
||||||
|
func (n *Netrc) MarshalText() (text []byte, err error) {
|
||||||
|
// TODO(bgentry): not safe for concurrency
|
||||||
|
for i := range n.tokens {
|
||||||
|
switch n.tokens[i].kind {
|
||||||
|
case tkComment, tkDefault, tkWhitespace: // always append these types
|
||||||
|
text = append(text, n.tokens[i].rawkind...)
|
||||||
|
default:
|
||||||
|
if n.tokens[i].value != "" { // skip empty-value tokens
|
||||||
|
text = append(text, n.tokens[i].rawkind...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n.tokens[i].kind == tkMacdef {
|
||||||
|
text = append(text, ' ')
|
||||||
|
text = append(text, n.tokens[i].macroName...)
|
||||||
|
}
|
||||||
|
text = append(text, n.tokens[i].rawvalue...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Netrc) NewMachine(name, login, password, account string) *Machine {
|
||||||
|
n.updateLock.Lock()
|
||||||
|
defer n.updateLock.Unlock()
|
||||||
|
|
||||||
|
prefix := "\n"
|
||||||
|
if len(n.tokens) == 0 {
|
||||||
|
prefix = ""
|
||||||
|
}
|
||||||
|
m := &Machine{
|
||||||
|
Name: name,
|
||||||
|
Login: login,
|
||||||
|
Password: password,
|
||||||
|
Account: account,
|
||||||
|
|
||||||
|
nametoken: &token{
|
||||||
|
kind: tkMachine,
|
||||||
|
rawkind: []byte(prefix + "machine"),
|
||||||
|
value: name,
|
||||||
|
rawvalue: []byte(" " + name),
|
||||||
|
},
|
||||||
|
logintoken: &token{
|
||||||
|
kind: tkLogin,
|
||||||
|
rawkind: []byte("\n\tlogin"),
|
||||||
|
value: login,
|
||||||
|
rawvalue: []byte(" " + login),
|
||||||
|
},
|
||||||
|
passtoken: &token{
|
||||||
|
kind: tkPassword,
|
||||||
|
rawkind: []byte("\n\tpassword"),
|
||||||
|
value: password,
|
||||||
|
rawvalue: []byte(" " + password),
|
||||||
|
},
|
||||||
|
accounttoken: &token{
|
||||||
|
kind: tkAccount,
|
||||||
|
rawkind: []byte("\n\taccount"),
|
||||||
|
value: account,
|
||||||
|
rawvalue: []byte(" " + account),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
n.insertMachineTokensBeforeDefault(m)
|
||||||
|
for i := range n.machines {
|
||||||
|
if n.machines[i].IsDefault() {
|
||||||
|
n.machines = append(append(n.machines[:i], m), n.machines[i:]...)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.machines = append(n.machines, m)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Netrc) insertMachineTokensBeforeDefault(m *Machine) {
|
||||||
|
newtokens := []*token{m.nametoken}
|
||||||
|
if m.logintoken.value != "" {
|
||||||
|
newtokens = append(newtokens, m.logintoken)
|
||||||
|
}
|
||||||
|
if m.passtoken.value != "" {
|
||||||
|
newtokens = append(newtokens, m.passtoken)
|
||||||
|
}
|
||||||
|
if m.accounttoken.value != "" {
|
||||||
|
newtokens = append(newtokens, m.accounttoken)
|
||||||
|
}
|
||||||
|
for i := range n.tokens {
|
||||||
|
if n.tokens[i].kind == tkDefault {
|
||||||
|
// found the default, now insert tokens before it
|
||||||
|
n.tokens = append(n.tokens[:i], append(newtokens, n.tokens[i:]...)...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// didn't find a default, just add the newtokens to the end
|
||||||
|
n.tokens = append(n.tokens, newtokens...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Netrc) RemoveMachine(name string) {
|
||||||
|
n.updateLock.Lock()
|
||||||
|
defer n.updateLock.Unlock()
|
||||||
|
|
||||||
|
for i := range n.machines {
|
||||||
|
if n.machines[i] != nil && n.machines[i].Name == name {
|
||||||
|
m := n.machines[i]
|
||||||
|
for _, t := range []*token{
|
||||||
|
m.nametoken, m.logintoken, m.passtoken, m.accounttoken,
|
||||||
|
} {
|
||||||
|
n.removeToken(t)
|
||||||
|
}
|
||||||
|
n.machines = append(n.machines[:i], n.machines[i+1:]...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Netrc) removeToken(t *token) {
|
||||||
|
if t != nil {
|
||||||
|
for i := range n.tokens {
|
||||||
|
if n.tokens[i] == t {
|
||||||
|
n.tokens = append(n.tokens[:i], n.tokens[i+1:]...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Machine contains information about a remote machine.
|
||||||
|
type Machine struct {
|
||||||
|
Name string
|
||||||
|
Login string
|
||||||
|
Password string
|
||||||
|
Account string
|
||||||
|
|
||||||
|
nametoken *token
|
||||||
|
logintoken *token
|
||||||
|
passtoken *token
|
||||||
|
accounttoken *token
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDefault returns true if the machine is a "default" token, denoted by an
|
||||||
|
// empty name.
|
||||||
|
func (m *Machine) IsDefault() bool {
|
||||||
|
return m.Name == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePassword sets the password for the Machine m.
|
||||||
|
func (m *Machine) UpdatePassword(newpass string) {
|
||||||
|
m.Password = newpass
|
||||||
|
updateTokenValue(m.passtoken, newpass)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLogin sets the login for the Machine m.
|
||||||
|
func (m *Machine) UpdateLogin(newlogin string) {
|
||||||
|
m.Login = newlogin
|
||||||
|
updateTokenValue(m.logintoken, newlogin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAccount sets the login for the Machine m.
|
||||||
|
func (m *Machine) UpdateAccount(newaccount string) {
|
||||||
|
m.Account = newaccount
|
||||||
|
updateTokenValue(m.accounttoken, newaccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTokenValue(t *token, value string) {
|
||||||
|
oldvalue := t.value
|
||||||
|
t.value = value
|
||||||
|
newraw := make([]byte, len(t.rawvalue))
|
||||||
|
copy(newraw, t.rawvalue)
|
||||||
|
t.rawvalue = append(
|
||||||
|
bytes.TrimSuffix(newraw, []byte(oldvalue)),
|
||||||
|
[]byte(value)...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Macros contains all the macro definitions in a netrc file.
|
||||||
|
type Macros map[string]string
|
||||||
|
|
||||||
|
type token struct {
|
||||||
|
kind tkType
|
||||||
|
macroName string
|
||||||
|
value string
|
||||||
|
rawkind []byte
|
||||||
|
rawvalue []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error represents a netrc file parse error.
|
||||||
|
type Error struct {
|
||||||
|
LineNum int // Line number
|
||||||
|
Msg string // Error message
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns a string representation of error e.
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
return fmt.Sprintf("line %d: %s", e.LineNum, e.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) BadDefaultOrder() bool {
|
||||||
|
return e.Msg == errBadDefaultOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
const errBadDefaultOrder = "default token must appear after all machine tokens"
|
||||||
|
|
||||||
|
// scanLinesKeepPrefix is a split function for a Scanner that returns each line
|
||||||
|
// of text. The returned token may include newlines if they are before the
|
||||||
|
// first non-space character. The returned line may be empty. The end-of-line
|
||||||
|
// marker is one optional carriage return followed by one mandatory newline. In
|
||||||
|
// regular expression notation, it is `\r?\n`. The last non-empty line of
|
||||||
|
// input will be returned even if it has no newline.
|
||||||
|
func scanLinesKeepPrefix(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
|
if atEOF && len(data) == 0 {
|
||||||
|
return 0, nil, nil
|
||||||
|
}
|
||||||
|
// Skip leading spaces.
|
||||||
|
start := 0
|
||||||
|
for width := 0; start < len(data); start += width {
|
||||||
|
var r rune
|
||||||
|
r, width = utf8.DecodeRune(data[start:])
|
||||||
|
if !unicode.IsSpace(r) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i := bytes.IndexByte(data[start:], '\n'); i >= 0 {
|
||||||
|
// We have a full newline-terminated line.
|
||||||
|
return start + i, data[0 : start+i], nil
|
||||||
|
}
|
||||||
|
// If we're at EOF, we have a final, non-terminated line. Return it.
|
||||||
|
if atEOF {
|
||||||
|
return len(data), data, nil
|
||||||
|
}
|
||||||
|
// Request more data.
|
||||||
|
return 0, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanWordsKeepPrefix is a split function for a Scanner that returns each
|
||||||
|
// space-separated word of text, with prefixing spaces included. It will never
|
||||||
|
// return an empty string. The definition of space is set by unicode.IsSpace.
|
||||||
|
//
|
||||||
|
// Adapted from bufio.ScanWords().
|
||||||
|
func scanTokensKeepPrefix(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
|
// Skip leading spaces.
|
||||||
|
start := 0
|
||||||
|
for width := 0; start < len(data); start += width {
|
||||||
|
var r rune
|
||||||
|
r, width = utf8.DecodeRune(data[start:])
|
||||||
|
if !unicode.IsSpace(r) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if atEOF && len(data) == 0 || start == len(data) {
|
||||||
|
return len(data), data, nil
|
||||||
|
}
|
||||||
|
if len(data) > start && data[start] == '#' {
|
||||||
|
return scanLinesKeepPrefix(data, atEOF)
|
||||||
|
}
|
||||||
|
// Scan until space, marking end of word.
|
||||||
|
for width, i := 0, start; i < len(data); i += width {
|
||||||
|
var r rune
|
||||||
|
r, width = utf8.DecodeRune(data[i:])
|
||||||
|
if unicode.IsSpace(r) {
|
||||||
|
return i, data[:i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we're at EOF, we have a final, non-empty, non-terminated word. Return it.
|
||||||
|
if atEOF && len(data) > start {
|
||||||
|
return len(data), data, nil
|
||||||
|
}
|
||||||
|
// Request more data.
|
||||||
|
return 0, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newToken(rawb []byte) (*token, error) {
|
||||||
|
_, tkind, err := bufio.ScanWords(rawb, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
t := token{rawkind: rawb}
|
||||||
|
t.kind, ok = keywords[string(tkind)]
|
||||||
|
if !ok {
|
||||||
|
trimmed := strings.TrimSpace(string(tkind))
|
||||||
|
if trimmed == "" {
|
||||||
|
t.kind = tkWhitespace // whitespace-only, should happen only at EOF
|
||||||
|
return &t, nil
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(trimmed, "#") {
|
||||||
|
t.kind = tkComment // this is a comment
|
||||||
|
return &t, nil
|
||||||
|
}
|
||||||
|
return &t, fmt.Errorf("keyword expected; got " + string(tkind))
|
||||||
|
}
|
||||||
|
return &t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanValue(scanner *bufio.Scanner, pos int) ([]byte, string, int, error) {
|
||||||
|
if scanner.Scan() {
|
||||||
|
raw := scanner.Bytes()
|
||||||
|
pos += bytes.Count(raw, []byte{'\n'})
|
||||||
|
return raw, strings.TrimSpace(string(raw)), pos, nil
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, "", pos, &Error{pos, err.Error()}
|
||||||
|
}
|
||||||
|
return nil, "", pos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(r io.Reader, pos int) (*Netrc, error) {
|
||||||
|
b, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nrc := Netrc{machines: make([]*Machine, 0, 20), macros: make(Macros, 10)}
|
||||||
|
|
||||||
|
defaultSeen := false
|
||||||
|
var currentMacro *token
|
||||||
|
var m *Machine
|
||||||
|
var t *token
|
||||||
|
scanner := bufio.NewScanner(bytes.NewReader(b))
|
||||||
|
scanner.Split(scanTokensKeepPrefix)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
rawb := scanner.Bytes()
|
||||||
|
if len(rawb) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pos += bytes.Count(rawb, []byte{'\n'})
|
||||||
|
t, err = newToken(rawb)
|
||||||
|
if err != nil {
|
||||||
|
if currentMacro == nil {
|
||||||
|
return nil, &Error{pos, err.Error()}
|
||||||
|
}
|
||||||
|
currentMacro.rawvalue = append(currentMacro.rawvalue, rawb...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentMacro != nil && bytes.Contains(rawb, []byte{'\n', '\n'}) {
|
||||||
|
// if macro rawvalue + rawb would contain \n\n, then macro def is over
|
||||||
|
currentMacro.value = strings.TrimLeft(string(currentMacro.rawvalue), "\r\n")
|
||||||
|
nrc.macros[currentMacro.macroName] = currentMacro.value
|
||||||
|
currentMacro = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t.kind {
|
||||||
|
case tkMacdef:
|
||||||
|
if _, t.macroName, pos, err = scanValue(scanner, pos); err != nil {
|
||||||
|
return nil, &Error{pos, err.Error()}
|
||||||
|
}
|
||||||
|
currentMacro = t
|
||||||
|
case tkDefault:
|
||||||
|
if defaultSeen {
|
||||||
|
return nil, &Error{pos, "multiple default token"}
|
||||||
|
}
|
||||||
|
if m != nil {
|
||||||
|
nrc.machines, m = append(nrc.machines, m), nil
|
||||||
|
}
|
||||||
|
m = new(Machine)
|
||||||
|
m.Name = ""
|
||||||
|
defaultSeen = true
|
||||||
|
case tkMachine:
|
||||||
|
if defaultSeen {
|
||||||
|
return nil, &Error{pos, errBadDefaultOrder}
|
||||||
|
}
|
||||||
|
if m != nil {
|
||||||
|
nrc.machines, m = append(nrc.machines, m), nil
|
||||||
|
}
|
||||||
|
m = new(Machine)
|
||||||
|
if t.rawvalue, m.Name, pos, err = scanValue(scanner, pos); err != nil {
|
||||||
|
return nil, &Error{pos, err.Error()}
|
||||||
|
}
|
||||||
|
t.value = m.Name
|
||||||
|
m.nametoken = t
|
||||||
|
case tkLogin:
|
||||||
|
if m == nil || m.Login != "" {
|
||||||
|
return nil, &Error{pos, "unexpected token login "}
|
||||||
|
}
|
||||||
|
if t.rawvalue, m.Login, pos, err = scanValue(scanner, pos); err != nil {
|
||||||
|
return nil, &Error{pos, err.Error()}
|
||||||
|
}
|
||||||
|
t.value = m.Login
|
||||||
|
m.logintoken = t
|
||||||
|
case tkPassword:
|
||||||
|
if m == nil || m.Password != "" {
|
||||||
|
return nil, &Error{pos, "unexpected token password"}
|
||||||
|
}
|
||||||
|
if t.rawvalue, m.Password, pos, err = scanValue(scanner, pos); err != nil {
|
||||||
|
return nil, &Error{pos, err.Error()}
|
||||||
|
}
|
||||||
|
t.value = m.Password
|
||||||
|
m.passtoken = t
|
||||||
|
case tkAccount:
|
||||||
|
if m == nil || m.Account != "" {
|
||||||
|
return nil, &Error{pos, "unexpected token account"}
|
||||||
|
}
|
||||||
|
if t.rawvalue, m.Account, pos, err = scanValue(scanner, pos); err != nil {
|
||||||
|
return nil, &Error{pos, err.Error()}
|
||||||
|
}
|
||||||
|
t.value = m.Account
|
||||||
|
m.accounttoken = t
|
||||||
|
}
|
||||||
|
|
||||||
|
nrc.tokens = append(nrc.tokens, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if m != nil {
|
||||||
|
nrc.machines, m = append(nrc.machines, m), nil
|
||||||
|
}
|
||||||
|
return &nrc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFile opens the file at filename and then passes its io.Reader to
|
||||||
|
// Parse().
|
||||||
|
func ParseFile(filename string) (*Netrc, error) {
|
||||||
|
fd, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
return Parse(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses from the the Reader r as a netrc file and returns the set of
|
||||||
|
// machine information and macros defined in it. The ``default'' machine,
|
||||||
|
// which is intended to be used when no machine name matches, is identified
|
||||||
|
// by an empty machine name. There can be only one ``default'' machine.
|
||||||
|
//
|
||||||
|
// If there is a parsing error, an Error is returned.
|
||||||
|
func Parse(r io.Reader) (*Netrc, error) {
|
||||||
|
return parse(r, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindMachine parses the netrc file identified by filename and returns the
|
||||||
|
// Machine named by name. If a problem occurs parsing the file at filename, an
|
||||||
|
// error is returned. If a machine named by name exists, it is returned. If no
|
||||||
|
// Machine with name name is found and there is a ``default'' machine, the
|
||||||
|
// ``default'' machine is returned. Otherwise, nil is returned.
|
||||||
|
func FindMachine(filename, name string) (m *Machine, err error) {
|
||||||
|
n, err := ParseFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return n.FindMachine(name), nil
|
||||||
|
}
|
||||||
559
vendor/github.com/bgentry/go-netrc/netrc/netrc_test.go
generated
vendored
Normal file
559
vendor/github.com/bgentry/go-netrc/netrc/netrc_test.go
generated
vendored
Normal file
@@ -0,0 +1,559 @@
|
|||||||
|
// Copyright © 2010 Fazlul Shahriar <fshahriar@gmail.com> and
|
||||||
|
// Copyright © 2014 Blake Gentry <blakesgentry@gmail.com>.
|
||||||
|
// See LICENSE file for license details.
|
||||||
|
|
||||||
|
package netrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var expectedMachines = []*Machine{
|
||||||
|
&Machine{Name: "mail.google.com", Login: "joe@gmail.com", Password: "somethingSecret", Account: "justagmail"},
|
||||||
|
&Machine{Name: "ray", Login: "demo", Password: "mypassword", Account: ""},
|
||||||
|
&Machine{Name: "weirdlogin", Login: "uname", Password: "pass#pass", Account: ""},
|
||||||
|
&Machine{Name: "", Login: "anonymous", Password: "joe@example.com", Account: ""},
|
||||||
|
}
|
||||||
|
var expectedMacros = Macros{
|
||||||
|
"allput": "put src/*",
|
||||||
|
"allput2": " put src/*\nput src2/*",
|
||||||
|
}
|
||||||
|
|
||||||
|
func eqMachine(a *Machine, b *Machine) bool {
|
||||||
|
return a.Name == b.Name &&
|
||||||
|
a.Login == b.Login &&
|
||||||
|
a.Password == b.Password &&
|
||||||
|
a.Account == b.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExpected(n *Netrc, t *testing.T) {
|
||||||
|
if len(expectedMachines) != len(n.machines) {
|
||||||
|
t.Errorf("expected %d machines, got %d", len(expectedMachines), len(n.machines))
|
||||||
|
} else {
|
||||||
|
for i, e := range expectedMachines {
|
||||||
|
if !eqMachine(e, n.machines[i]) {
|
||||||
|
t.Errorf("bad machine; expected %v, got %v\n", e, n.machines[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(expectedMacros) != len(n.macros) {
|
||||||
|
t.Errorf("expected %d macros, got %d", len(expectedMacros), len(n.macros))
|
||||||
|
} else {
|
||||||
|
for k, v := range expectedMacros {
|
||||||
|
if v != n.macros[k] {
|
||||||
|
t.Errorf("bad macro for %s; expected %q, got %q\n", k, v, n.macros[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var newTokenTests = []struct {
|
||||||
|
rawkind string
|
||||||
|
tkind tkType
|
||||||
|
}{
|
||||||
|
{"machine", tkMachine},
|
||||||
|
{"\n\n\tmachine", tkMachine},
|
||||||
|
{"\n machine", tkMachine},
|
||||||
|
{"default", tkDefault},
|
||||||
|
{"login", tkLogin},
|
||||||
|
{"password", tkPassword},
|
||||||
|
{"account", tkAccount},
|
||||||
|
{"macdef", tkMacdef},
|
||||||
|
{"\n # comment stuff ", tkComment},
|
||||||
|
{"\n # I am another comment", tkComment},
|
||||||
|
{"\n\t\n ", tkWhitespace},
|
||||||
|
}
|
||||||
|
|
||||||
|
var newTokenInvalidTests = []string{
|
||||||
|
" junk",
|
||||||
|
"sdfdsf",
|
||||||
|
"account#unspaced comment",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewToken(t *testing.T) {
|
||||||
|
for _, tktest := range newTokenTests {
|
||||||
|
tok, err := newToken([]byte(tktest.rawkind))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if tok.kind != tktest.tkind {
|
||||||
|
t.Errorf("expected tok.kind %d, got %d", tktest.tkind, tok.kind)
|
||||||
|
}
|
||||||
|
if string(tok.rawkind) != tktest.rawkind {
|
||||||
|
t.Errorf("expected tok.rawkind %q, got %q", tktest.rawkind, string(tok.rawkind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tktest := range newTokenInvalidTests {
|
||||||
|
_, err := newToken([]byte(tktest))
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected error with %q, got none", tktest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
r := netrcReader("examples/good.netrc", t)
|
||||||
|
n, err := Parse(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testExpected(n, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFile(t *testing.T) {
|
||||||
|
n, err := ParseFile("examples/good.netrc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testExpected(n, t)
|
||||||
|
|
||||||
|
_, err = ParseFile("examples/bad_default_order.netrc")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected an error parsing bad_default_order.netrc, got none")
|
||||||
|
} else if !err.(*Error).BadDefaultOrder() {
|
||||||
|
t.Error("expected BadDefaultOrder() to be true, got false")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = ParseFile("examples/this_file_doesnt_exist.netrc")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected an error loading this_file_doesnt_exist.netrc, got none")
|
||||||
|
} else if _, ok := err.(*os.PathError); !ok {
|
||||||
|
t.Errorf("expected *os.Error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindMachine(t *testing.T) {
|
||||||
|
m, err := FindMachine("examples/good.netrc", "ray")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !eqMachine(m, expectedMachines[1]) {
|
||||||
|
t.Errorf("bad machine; expected %v, got %v\n", expectedMachines[1], m)
|
||||||
|
}
|
||||||
|
if m.IsDefault() {
|
||||||
|
t.Errorf("expected m.IsDefault() to be false")
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err = FindMachine("examples/good.netrc", "non.existent")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !eqMachine(m, expectedMachines[3]) {
|
||||||
|
t.Errorf("bad machine; expected %v, got %v\n", expectedMachines[3], m)
|
||||||
|
}
|
||||||
|
if !m.IsDefault() {
|
||||||
|
t.Errorf("expected m.IsDefault() to be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetrcFindMachine(t *testing.T) {
|
||||||
|
n, err := ParseFile("examples/good.netrc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := n.FindMachine("ray")
|
||||||
|
if !eqMachine(m, expectedMachines[1]) {
|
||||||
|
t.Errorf("bad machine; expected %v, got %v\n", expectedMachines[1], m)
|
||||||
|
}
|
||||||
|
if m.IsDefault() {
|
||||||
|
t.Errorf("expected def to be false")
|
||||||
|
}
|
||||||
|
|
||||||
|
n = &Netrc{}
|
||||||
|
m = n.FindMachine("nonexistent")
|
||||||
|
if m != nil {
|
||||||
|
t.Errorf("expected nil, got %v", m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalText(t *testing.T) {
|
||||||
|
// load up expected netrc Marshal output
|
||||||
|
expected, err := ioutil.ReadAll(netrcReader("examples/good.netrc", t))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := ParseFile("examples/good.netrc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := n.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(result) != string(expected) {
|
||||||
|
t.Errorf("expected:\n%q\ngot:\n%q", string(expected), string(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure tokens w/ no value are not serialized
|
||||||
|
m := n.FindMachine("mail.google.com")
|
||||||
|
m.UpdatePassword("")
|
||||||
|
result, err = n.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if strings.Contains(string(result), "\tpassword \n") {
|
||||||
|
fmt.Println(string(result))
|
||||||
|
t.Errorf("expected zero-value password token to not be serialzed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var newMachineTests = []struct {
|
||||||
|
name string
|
||||||
|
login string
|
||||||
|
password string
|
||||||
|
account string
|
||||||
|
}{
|
||||||
|
{"heroku.com", "dodging-samurai-42@heroku.com", "octocatdodgeballchampions", "2011+2013"},
|
||||||
|
{"bgentry.io", "special@test.com", "noacct", ""},
|
||||||
|
{"github.io", "2@test.com", "", "acctwithnopass"},
|
||||||
|
{"someotherapi.com", "", "passonly", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewMachine(t *testing.T) {
|
||||||
|
n, err := ParseFile("examples/good.netrc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
testNewMachine(t, n)
|
||||||
|
n = &Netrc{}
|
||||||
|
testNewMachine(t, n)
|
||||||
|
|
||||||
|
// make sure that tokens without a value are not serialized at all
|
||||||
|
for _, test := range newMachineTests {
|
||||||
|
n = &Netrc{}
|
||||||
|
_ = n.NewMachine(test.name, test.login, test.password, test.account)
|
||||||
|
|
||||||
|
bodyb, _ := n.MarshalText()
|
||||||
|
body := string(bodyb)
|
||||||
|
|
||||||
|
// ensure desired values are present when they should be
|
||||||
|
if !strings.Contains(body, "machine") {
|
||||||
|
t.Errorf("NewMachine() %s missing keyword 'machine'", test.name)
|
||||||
|
}
|
||||||
|
if !strings.Contains(body, test.name) {
|
||||||
|
t.Errorf("NewMachine() %s missing value %q", test.name, test.name)
|
||||||
|
}
|
||||||
|
if test.login != "" && !strings.Contains(body, "login "+test.login) {
|
||||||
|
t.Errorf("NewMachine() %s missing value %q", test.name, "login "+test.login)
|
||||||
|
}
|
||||||
|
if test.password != "" && !strings.Contains(body, "password "+test.password) {
|
||||||
|
t.Errorf("NewMachine() %s missing value %q", test.name, "password "+test.password)
|
||||||
|
}
|
||||||
|
if test.account != "" && !strings.Contains(body, "account "+test.account) {
|
||||||
|
t.Errorf("NewMachine() %s missing value %q", test.name, "account "+test.account)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure undesired values are not present when they shouldn't be
|
||||||
|
if test.login == "" && strings.Contains(body, "login") {
|
||||||
|
t.Errorf("NewMachine() %s contains unexpected value %q", test.name, "login")
|
||||||
|
}
|
||||||
|
if test.password == "" && strings.Contains(body, "password") {
|
||||||
|
t.Errorf("NewMachine() %s contains unexpected value %q", test.name, "password")
|
||||||
|
}
|
||||||
|
if test.account == "" && strings.Contains(body, "account") {
|
||||||
|
t.Errorf("NewMachine() %s contains unexpected value %q", test.name, "account")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNewMachine(t *testing.T, n *Netrc) {
|
||||||
|
for _, test := range newMachineTests {
|
||||||
|
mcount := len(n.machines)
|
||||||
|
// sanity check
|
||||||
|
bodyb, _ := n.MarshalText()
|
||||||
|
body := string(bodyb)
|
||||||
|
for _, value := range []string{test.name, test.login, test.password, test.account} {
|
||||||
|
if value != "" && strings.Contains(body, value) {
|
||||||
|
t.Errorf("MarshalText() before NewMachine() contained unexpected %q", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// test prefix for machine token
|
||||||
|
prefix := "\n"
|
||||||
|
if len(n.tokens) == 0 {
|
||||||
|
prefix = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
m := n.NewMachine(test.name, test.login, test.password, test.account)
|
||||||
|
if m == nil {
|
||||||
|
t.Fatalf("NewMachine() returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(n.machines) != mcount+1 {
|
||||||
|
t.Errorf("n.machines count expected %d, got %d", mcount+1, len(n.machines))
|
||||||
|
}
|
||||||
|
// check values
|
||||||
|
if m.Name != test.name {
|
||||||
|
t.Errorf("m.Name expected %q, got %q", test.name, m.Name)
|
||||||
|
}
|
||||||
|
if m.Login != test.login {
|
||||||
|
t.Errorf("m.Login expected %q, got %q", test.login, m.Login)
|
||||||
|
}
|
||||||
|
if m.Password != test.password {
|
||||||
|
t.Errorf("m.Password expected %q, got %q", test.password, m.Password)
|
||||||
|
}
|
||||||
|
if m.Account != test.account {
|
||||||
|
t.Errorf("m.Account expected %q, got %q", test.account, m.Account)
|
||||||
|
}
|
||||||
|
// check tokens
|
||||||
|
checkToken(t, "nametoken", m.nametoken, tkMachine, prefix+"machine", test.name)
|
||||||
|
checkToken(t, "logintoken", m.logintoken, tkLogin, "\n\tlogin", test.login)
|
||||||
|
checkToken(t, "passtoken", m.passtoken, tkPassword, "\n\tpassword", test.password)
|
||||||
|
checkToken(t, "accounttoken", m.accounttoken, tkAccount, "\n\taccount", test.account)
|
||||||
|
// check marshal output
|
||||||
|
bodyb, _ = n.MarshalText()
|
||||||
|
body = string(bodyb)
|
||||||
|
for _, value := range []string{test.name, test.login, test.password, test.account} {
|
||||||
|
if !strings.Contains(body, value) {
|
||||||
|
t.Errorf("MarshalText() after NewMachine() did not include %q as expected", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkToken(t *testing.T, name string, tok *token, kind tkType, rawkind, value string) {
|
||||||
|
if tok == nil {
|
||||||
|
t.Errorf("%s not defined", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tok.kind != kind {
|
||||||
|
t.Errorf("%s expected kind %d, got %d", name, kind, tok.kind)
|
||||||
|
}
|
||||||
|
if string(tok.rawkind) != rawkind {
|
||||||
|
t.Errorf("%s expected rawkind %q, got %q", name, rawkind, string(tok.rawkind))
|
||||||
|
}
|
||||||
|
if tok.value != value {
|
||||||
|
t.Errorf("%s expected value %q, got %q", name, value, tok.value)
|
||||||
|
}
|
||||||
|
if tok.value != value {
|
||||||
|
t.Errorf("%s expected value %q, got %q", name, value, tok.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewMachineGoesBeforeDefault(t *testing.T) {
|
||||||
|
n, err := ParseFile("examples/good.netrc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
m := n.NewMachine("mymachine", "mylogin", "mypassword", "myaccount")
|
||||||
|
if m2 := n.machines[len(n.machines)-2]; m2 != m {
|
||||||
|
t.Errorf("expected machine %v, got %v", m, m2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveMachine(t *testing.T) {
|
||||||
|
n, err := ParseFile("examples/good.netrc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []string{"mail.google.com", "weirdlogin"}
|
||||||
|
|
||||||
|
for _, name := range tests {
|
||||||
|
mcount := len(n.machines)
|
||||||
|
// sanity check
|
||||||
|
m := n.FindMachine(name)
|
||||||
|
if m == nil {
|
||||||
|
t.Fatalf("machine %q not found", name)
|
||||||
|
}
|
||||||
|
if m.IsDefault() {
|
||||||
|
t.Fatalf("expected machine %q, got default instead", name)
|
||||||
|
}
|
||||||
|
n.RemoveMachine(name)
|
||||||
|
|
||||||
|
if len(n.machines) != mcount-1 {
|
||||||
|
t.Errorf("n.machines count expected %d, got %d", mcount-1, len(n.machines))
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure Machine is no longer returned by FindMachine()
|
||||||
|
if m2 := n.FindMachine(name); m2 != nil && !m2.IsDefault() {
|
||||||
|
t.Errorf("Machine %q not removed from Machines list", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure tokens are not present in tokens list
|
||||||
|
for _, token := range []*token{m.nametoken, m.logintoken, m.passtoken, m.accounttoken} {
|
||||||
|
if token != nil {
|
||||||
|
for _, tok2 := range n.tokens {
|
||||||
|
if tok2 == token {
|
||||||
|
t.Errorf("token not removed from tokens list: %v", token)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyb, _ := n.MarshalText()
|
||||||
|
body := string(bodyb)
|
||||||
|
for _, value := range []string{m.Name, m.Login, m.Password, m.Account} {
|
||||||
|
if value != "" && strings.Contains(body, value) {
|
||||||
|
t.Errorf("MarshalText() after RemoveMachine() contained unexpected %q", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateLogin(t *testing.T) {
|
||||||
|
n, err := ParseFile("examples/good.netrc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
exists bool
|
||||||
|
name string
|
||||||
|
oldlogin string
|
||||||
|
newlogin string
|
||||||
|
}{
|
||||||
|
{true, "mail.google.com", "joe@gmail.com", "joe2@gmail.com"},
|
||||||
|
{false, "heroku.com", "", "dodging-samurai-42@heroku.com"},
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyb, _ := n.MarshalText()
|
||||||
|
body := string(bodyb)
|
||||||
|
for _, test := range tests {
|
||||||
|
if strings.Contains(body, test.newlogin) {
|
||||||
|
t.Errorf("MarshalText() before UpdateLogin() contained unexpected %q", test.newlogin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
m := n.FindMachine(test.name)
|
||||||
|
if m.IsDefault() == test.exists {
|
||||||
|
t.Errorf("expected machine %s to not exist, but it did", test.name)
|
||||||
|
} else {
|
||||||
|
if !test.exists {
|
||||||
|
m = n.NewMachine(test.name, test.newlogin, "", "")
|
||||||
|
}
|
||||||
|
if m == nil {
|
||||||
|
t.Errorf("machine %s was nil", test.name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m.UpdateLogin(test.newlogin)
|
||||||
|
m := n.FindMachine(test.name)
|
||||||
|
if m.Login != test.newlogin {
|
||||||
|
t.Errorf("expected new login %q, got %q", test.newlogin, m.Login)
|
||||||
|
}
|
||||||
|
if m.logintoken.value != test.newlogin {
|
||||||
|
t.Errorf("expected m.logintoken %q, got %q", test.newlogin, m.logintoken.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyb, _ = n.MarshalText()
|
||||||
|
body = string(bodyb)
|
||||||
|
for _, test := range tests {
|
||||||
|
if test.exists && strings.Contains(body, test.oldlogin) {
|
||||||
|
t.Errorf("MarshalText() after UpdateLogin() contained unexpected %q", test.oldlogin)
|
||||||
|
}
|
||||||
|
if !strings.Contains(body, test.newlogin) {
|
||||||
|
t.Errorf("MarshalText after UpdatePassword did not contain %q as expected", test.newlogin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdatePassword(t *testing.T) {
|
||||||
|
n, err := ParseFile("examples/good.netrc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
exists bool
|
||||||
|
name string
|
||||||
|
oldpassword string
|
||||||
|
newpassword string
|
||||||
|
}{
|
||||||
|
{true, "ray", "mypassword", "supernewpass"},
|
||||||
|
{false, "heroku.com", "", "octocatdodgeballchampions"},
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyb, _ := n.MarshalText()
|
||||||
|
body := string(bodyb)
|
||||||
|
for _, test := range tests {
|
||||||
|
if test.exists && !strings.Contains(body, test.oldpassword) {
|
||||||
|
t.Errorf("MarshalText() before UpdatePassword() did not include %q as expected", test.oldpassword)
|
||||||
|
}
|
||||||
|
if strings.Contains(body, test.newpassword) {
|
||||||
|
t.Errorf("MarshalText() before UpdatePassword() contained unexpected %q", test.newpassword)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
m := n.FindMachine(test.name)
|
||||||
|
if m.IsDefault() == test.exists {
|
||||||
|
t.Errorf("expected machine %s to not exist, but it did", test.name)
|
||||||
|
} else {
|
||||||
|
if !test.exists {
|
||||||
|
m = n.NewMachine(test.name, "", test.newpassword, "")
|
||||||
|
}
|
||||||
|
if m == nil {
|
||||||
|
t.Errorf("machine %s was nil", test.name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m.UpdatePassword(test.newpassword)
|
||||||
|
m = n.FindMachine(test.name)
|
||||||
|
if m.Password != test.newpassword {
|
||||||
|
t.Errorf("expected new password %q, got %q", test.newpassword, m.Password)
|
||||||
|
}
|
||||||
|
if m.passtoken.value != test.newpassword {
|
||||||
|
t.Errorf("expected m.passtoken %q, got %q", test.newpassword, m.passtoken.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyb, _ = n.MarshalText()
|
||||||
|
body = string(bodyb)
|
||||||
|
for _, test := range tests {
|
||||||
|
if test.exists && strings.Contains(body, test.oldpassword) {
|
||||||
|
t.Errorf("MarshalText() after UpdatePassword() contained unexpected %q", test.oldpassword)
|
||||||
|
}
|
||||||
|
if !strings.Contains(body, test.newpassword) {
|
||||||
|
t.Errorf("MarshalText() after UpdatePassword() did not contain %q as expected", test.newpassword)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewFile(t *testing.T) {
|
||||||
|
var n Netrc
|
||||||
|
|
||||||
|
result, err := n.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(result) != "" {
|
||||||
|
t.Errorf("expected empty result=\"\", got %q", string(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
n.NewMachine("netrctest.heroku.com", "auser", "apassword", "")
|
||||||
|
|
||||||
|
result, err = n.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := `machine netrctest.heroku.com
|
||||||
|
login auser
|
||||||
|
password apassword`
|
||||||
|
|
||||||
|
if string(result) != expected {
|
||||||
|
t.Errorf("expected result:\n%q\ngot:\n%q", expected, string(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func netrcReader(filename string, t *testing.T) io.Reader {
|
||||||
|
b, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return bytes.NewReader(b)
|
||||||
|
}
|
||||||
5
vendor/github.com/go-ini/ini/.github/ISSUE_TEMPLATE.md
generated
vendored
Normal file
5
vendor/github.com/go-ini/ini/.github/ISSUE_TEMPLATE.md
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
### Please give general description of the problem
|
||||||
|
|
||||||
|
### Please provide code snippets to reproduce the problem described above
|
||||||
|
|
||||||
|
### Do you have any suggestion to fix the problem?
|
||||||
3
vendor/github.com/go-ini/ini/.github/PULL_REQUEST_TEMPLATE.md
generated
vendored
Normal file
3
vendor/github.com/go-ini/ini/.github/PULL_REQUEST_TEMPLATE.md
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
### What problem should be fixed?
|
||||||
|
|
||||||
|
### Have you added test cases to catch the problem?
|
||||||
6
vendor/github.com/go-ini/ini/.gitignore
generated
vendored
Normal file
6
vendor/github.com/go-ini/ini/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
testdata/conf_out.ini
|
||||||
|
ini.sublime-project
|
||||||
|
ini.sublime-workspace
|
||||||
|
testdata/conf_reflect.ini
|
||||||
|
.idea
|
||||||
|
/.vscode
|
||||||
16
vendor/github.com/go-ini/ini/.travis.yml
generated
vendored
Normal file
16
vendor/github.com/go-ini/ini/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.6.x
|
||||||
|
- 1.7.x
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/smartystreets/goconvey
|
||||||
|
- mkdir -p $HOME/gopath/src/gopkg.in
|
||||||
|
- ln -s $HOME/gopath/src/github.com/go-ini/ini $HOME/gopath/src/gopkg.in/ini.v1
|
||||||
|
- cd $HOME/gopath/src/gopkg.in/ini.v1
|
||||||
|
- go test -v -cover -race
|
||||||
191
vendor/github.com/go-ini/ini/LICENSE
generated
vendored
Normal file
191
vendor/github.com/go-ini/ini/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction, and
|
||||||
|
distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||||
|
owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||||
|
that control, are controlled by, or are under common control with that entity.
|
||||||
|
For the purposes of this definition, "control" means (i) the power, direct or
|
||||||
|
indirect, to cause the direction or management of such entity, whether by
|
||||||
|
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||||
|
permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications, including
|
||||||
|
but not limited to software source code, documentation source, and configuration
|
||||||
|
files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical transformation or
|
||||||
|
translation of a Source form, including but not limited to compiled object code,
|
||||||
|
generated documentation, and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||||
|
available under the License, as indicated by a copyright notice that is included
|
||||||
|
in or attached to the work (an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||||
|
is based on (or derived from) the Work and for which the editorial revisions,
|
||||||
|
annotations, elaborations, or other modifications represent, as a whole, an
|
||||||
|
original work of authorship. For the purposes of this License, Derivative Works
|
||||||
|
shall not include works that remain separable from, or merely link (or bind by
|
||||||
|
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including the original version
|
||||||
|
of the Work and any modifications or additions to that Work or Derivative Works
|
||||||
|
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||||
|
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||||
|
on behalf of the copyright owner. For the purposes of this definition,
|
||||||
|
"submitted" means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems, and
|
||||||
|
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||||
|
the purpose of discussing and improving the Work, but excluding communication
|
||||||
|
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||||
|
owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||||
|
of whom a Contribution has been received by Licensor and subsequently
|
||||||
|
incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License.
|
||||||
|
|
||||||
|
Subject to the terms and conditions of this License, each Contributor hereby
|
||||||
|
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||||
|
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||||
|
Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License.
|
||||||
|
|
||||||
|
Subject to the terms and conditions of this License, each Contributor hereby
|
||||||
|
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||||
|
irrevocable (except as stated in this section) patent license to make, have
|
||||||
|
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||||
|
such license applies only to those patent claims licensable by such Contributor
|
||||||
|
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||||
|
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||||
|
submitted. If You institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||||
|
Contribution incorporated within the Work constitutes direct or contributory
|
||||||
|
patent infringement, then any patent licenses granted to You under this License
|
||||||
|
for that Work shall terminate as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution.
|
||||||
|
|
||||||
|
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||||
|
in any medium, with or without modifications, and in Source or Object form,
|
||||||
|
provided that You meet the following conditions:
|
||||||
|
|
||||||
|
You must give any other recipients of the Work or Derivative Works a copy of
|
||||||
|
this License; and
|
||||||
|
You must cause any modified files to carry prominent notices stating that You
|
||||||
|
changed the files; and
|
||||||
|
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||||
|
all copyright, patent, trademark, and attribution notices from the Source form
|
||||||
|
of the Work, excluding those notices that do not pertain to any part of the
|
||||||
|
Derivative Works; and
|
||||||
|
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||||
|
Derivative Works that You distribute must include a readable copy of the
|
||||||
|
attribution notices contained within such NOTICE file, excluding those notices
|
||||||
|
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||||
|
following places: within a NOTICE text file distributed as part of the
|
||||||
|
Derivative Works; within the Source form or documentation, if provided along
|
||||||
|
with the Derivative Works; or, within a display generated by the Derivative
|
||||||
|
Works, if and wherever such third-party notices normally appear. The contents of
|
||||||
|
the NOTICE file are for informational purposes only and do not modify the
|
||||||
|
License. You may add Your own attribution notices within Derivative Works that
|
||||||
|
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||||
|
provided that such additional attribution notices cannot be construed as
|
||||||
|
modifying the License.
|
||||||
|
You may add Your own copyright statement to Your modifications and may provide
|
||||||
|
additional or different license terms and conditions for use, reproduction, or
|
||||||
|
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||||
|
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||||
|
with the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions.
|
||||||
|
|
||||||
|
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||||
|
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||||
|
conditions of this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||||
|
any separate license agreement you may have executed with Licensor regarding
|
||||||
|
such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks.
|
||||||
|
|
||||||
|
This License does not grant permission to use the trade names, trademarks,
|
||||||
|
service marks, or product names of the Licensor, except as required for
|
||||||
|
reasonable and customary use in describing the origin of the Work and
|
||||||
|
reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||||
|
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||||
|
including, without limitation, any warranties or conditions of TITLE,
|
||||||
|
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||||
|
solely responsible for determining the appropriateness of using or
|
||||||
|
redistributing the Work and assume any risks associated with Your exercise of
|
||||||
|
permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability.
|
||||||
|
|
||||||
|
In no event and under no legal theory, whether in tort (including negligence),
|
||||||
|
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||||
|
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special, incidental,
|
||||||
|
or consequential damages of any character arising as a result of this License or
|
||||||
|
out of the use or inability to use the Work (including but not limited to
|
||||||
|
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||||
|
any and all other commercial damages or losses), even if such Contributor has
|
||||||
|
been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability.
|
||||||
|
|
||||||
|
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||||
|
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||||
|
other liability obligations and/or rights consistent with this License. However,
|
||||||
|
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||||
|
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||||
|
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason of your
|
||||||
|
accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following boilerplate
|
||||||
|
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||||
|
identifying information. (Don't include the brackets!) The text should be
|
||||||
|
enclosed in the appropriate comment syntax for the file format. We also
|
||||||
|
recommend that a file or class name and description of purpose be included on
|
||||||
|
the same "printed page" as the copyright notice for easier identification within
|
||||||
|
third-party archives.
|
||||||
|
|
||||||
|
Copyright 2014 Unknwon
|
||||||
|
|
||||||
|
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.
|
||||||
15
vendor/github.com/go-ini/ini/Makefile
generated
vendored
Normal file
15
vendor/github.com/go-ini/ini/Makefile
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
.PHONY: build test bench vet coverage
|
||||||
|
|
||||||
|
build: vet bench
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test -v -cover -race
|
||||||
|
|
||||||
|
bench:
|
||||||
|
go test -v -cover -race -test.bench=. -test.benchmem
|
||||||
|
|
||||||
|
vet:
|
||||||
|
go vet
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out
|
||||||
44
vendor/github.com/go-ini/ini/README.md
generated
vendored
Normal file
44
vendor/github.com/go-ini/ini/README.md
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
INI [](https://travis-ci.org/go-ini/ini) [](https://sourcegraph.com/github.com/go-ini/ini)
|
||||||
|
===
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Package ini provides INI file read and write functionality in Go.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Load from multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites.
|
||||||
|
- Read with recursion values.
|
||||||
|
- Read with parent-child sections.
|
||||||
|
- Read with auto-increment key names.
|
||||||
|
- Read with multiple-line values.
|
||||||
|
- Read with tons of helper methods.
|
||||||
|
- Read and convert values to Go types.
|
||||||
|
- Read and **WRITE** comments of sections and keys.
|
||||||
|
- Manipulate sections, keys and comments with ease.
|
||||||
|
- Keep sections and keys in order as you parse and save.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
To use a tagged revision:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ go get gopkg.in/ini.v1
|
||||||
|
```
|
||||||
|
|
||||||
|
To use with latest changes:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ go get github.com/go-ini/ini
|
||||||
|
```
|
||||||
|
|
||||||
|
Please add `-u` flag to update in the future.
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
- [Getting Started](https://ini.unknwon.io/docs/intro/getting_started)
|
||||||
|
- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
|
||||||
118
vendor/github.com/go-ini/ini/bench_test.go
generated
vendored
Normal file
118
vendor/github.com/go-ini/ini/bench_test.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
// Copyright 2017 Unknwon
|
||||||
|
//
|
||||||
|
// 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 ini_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTestFile(block bool) *ini.File {
|
||||||
|
c, _ := ini.Load([]byte(_CONF_DATA))
|
||||||
|
c.BlockMode = block
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Key_Value(b *testing.B) {
|
||||||
|
c := newTestFile(true)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
c.Section("").Key("NAME").Value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Key_Value_NonBlock(b *testing.B) {
|
||||||
|
c := newTestFile(false)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
c.Section("").Key("NAME").Value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Key_Value_ViaSection(b *testing.B) {
|
||||||
|
c := newTestFile(true)
|
||||||
|
sec := c.Section("")
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
sec.Key("NAME").Value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Key_Value_ViaSection_NonBlock(b *testing.B) {
|
||||||
|
c := newTestFile(false)
|
||||||
|
sec := c.Section("")
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
sec.Key("NAME").Value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Key_Value_Direct(b *testing.B) {
|
||||||
|
c := newTestFile(true)
|
||||||
|
key := c.Section("").Key("NAME")
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
key.Value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Key_Value_Direct_NonBlock(b *testing.B) {
|
||||||
|
c := newTestFile(false)
|
||||||
|
key := c.Section("").Key("NAME")
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
key.Value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Key_String(b *testing.B) {
|
||||||
|
c := newTestFile(true)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = c.Section("").Key("NAME").String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Key_String_NonBlock(b *testing.B) {
|
||||||
|
c := newTestFile(false)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = c.Section("").Key("NAME").String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Key_String_ViaSection(b *testing.B) {
|
||||||
|
c := newTestFile(true)
|
||||||
|
sec := c.Section("")
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = sec.Key("NAME").String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Key_String_ViaSection_NonBlock(b *testing.B) {
|
||||||
|
c := newTestFile(false)
|
||||||
|
sec := c.Section("")
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = sec.Key("NAME").String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Key_SetValue(b *testing.B) {
|
||||||
|
c := newTestFile(true)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
c.Section("").Key("NAME").SetValue("10")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Key_SetValue_VisSection(b *testing.B) {
|
||||||
|
c := newTestFile(true)
|
||||||
|
sec := c.Section("")
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
sec.Key("NAME").SetValue("10")
|
||||||
|
}
|
||||||
|
}
|
||||||
32
vendor/github.com/go-ini/ini/error.go
generated
vendored
Normal file
32
vendor/github.com/go-ini/ini/error.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2016 Unknwon
|
||||||
|
//
|
||||||
|
// 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 ini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrDelimiterNotFound struct {
|
||||||
|
Line string
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsErrDelimiterNotFound(err error) bool {
|
||||||
|
_, ok := err.(ErrDelimiterNotFound)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrDelimiterNotFound) Error() string {
|
||||||
|
return fmt.Sprintf("key-value delimiter not found: %s", err.Line)
|
||||||
|
}
|
||||||
407
vendor/github.com/go-ini/ini/file.go
generated
vendored
Normal file
407
vendor/github.com/go-ini/ini/file.go
generated
vendored
Normal file
@@ -0,0 +1,407 @@
|
|||||||
|
// Copyright 2017 Unknwon
|
||||||
|
//
|
||||||
|
// 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 ini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File represents a combination of a or more INI file(s) in memory.
|
||||||
|
type File struct {
|
||||||
|
options LoadOptions
|
||||||
|
dataSources []dataSource
|
||||||
|
|
||||||
|
// Should make things safe, but sometimes doesn't matter.
|
||||||
|
BlockMode bool
|
||||||
|
lock sync.RWMutex
|
||||||
|
|
||||||
|
// To keep data in order.
|
||||||
|
sectionList []string
|
||||||
|
// Actual data is stored here.
|
||||||
|
sections map[string]*Section
|
||||||
|
|
||||||
|
NameMapper
|
||||||
|
ValueMapper
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFile initializes File object with given data sources.
|
||||||
|
func newFile(dataSources []dataSource, opts LoadOptions) *File {
|
||||||
|
return &File{
|
||||||
|
BlockMode: true,
|
||||||
|
dataSources: dataSources,
|
||||||
|
sections: make(map[string]*Section),
|
||||||
|
sectionList: make([]string, 0, 10),
|
||||||
|
options: opts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty returns an empty file object.
|
||||||
|
func Empty() *File {
|
||||||
|
// Ignore error here, we sure our data is good.
|
||||||
|
f, _ := Load([]byte(""))
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSection creates a new section.
|
||||||
|
func (f *File) NewSection(name string) (*Section, error) {
|
||||||
|
if len(name) == 0 {
|
||||||
|
return nil, errors.New("error creating new section: empty section name")
|
||||||
|
} else if f.options.Insensitive && name != DEFAULT_SECTION {
|
||||||
|
name = strings.ToLower(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.BlockMode {
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if inSlice(name, f.sectionList) {
|
||||||
|
return f.sections[name], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f.sectionList = append(f.sectionList, name)
|
||||||
|
f.sections[name] = newSection(f, name)
|
||||||
|
return f.sections[name], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRawSection creates a new section with an unparseable body.
|
||||||
|
func (f *File) NewRawSection(name, body string) (*Section, error) {
|
||||||
|
section, err := f.NewSection(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
section.isRawSection = true
|
||||||
|
section.rawBody = body
|
||||||
|
return section, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSections creates a list of sections.
|
||||||
|
func (f *File) NewSections(names ...string) (err error) {
|
||||||
|
for _, name := range names {
|
||||||
|
if _, err = f.NewSection(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSection returns section by given name.
|
||||||
|
func (f *File) GetSection(name string) (*Section, error) {
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = DEFAULT_SECTION
|
||||||
|
}
|
||||||
|
if f.options.Insensitive {
|
||||||
|
name = strings.ToLower(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.BlockMode {
|
||||||
|
f.lock.RLock()
|
||||||
|
defer f.lock.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
sec := f.sections[name]
|
||||||
|
if sec == nil {
|
||||||
|
return nil, fmt.Errorf("section '%s' does not exist", name)
|
||||||
|
}
|
||||||
|
return sec, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section assumes named section exists and returns a zero-value when not.
|
||||||
|
func (f *File) Section(name string) *Section {
|
||||||
|
sec, err := f.GetSection(name)
|
||||||
|
if err != nil {
|
||||||
|
// Note: It's OK here because the only possible error is empty section name,
|
||||||
|
// but if it's empty, this piece of code won't be executed.
|
||||||
|
sec, _ = f.NewSection(name)
|
||||||
|
return sec
|
||||||
|
}
|
||||||
|
return sec
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section returns list of Section.
|
||||||
|
func (f *File) Sections() []*Section {
|
||||||
|
if f.BlockMode {
|
||||||
|
f.lock.RLock()
|
||||||
|
defer f.lock.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
sections := make([]*Section, len(f.sectionList))
|
||||||
|
for i, name := range f.sectionList {
|
||||||
|
sections[i] = f.sections[name]
|
||||||
|
}
|
||||||
|
return sections
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChildSections returns a list of child sections of given section name.
|
||||||
|
func (f *File) ChildSections(name string) []*Section {
|
||||||
|
return f.Section(name).ChildSections()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SectionStrings returns list of section names.
|
||||||
|
func (f *File) SectionStrings() []string {
|
||||||
|
list := make([]string, len(f.sectionList))
|
||||||
|
copy(list, f.sectionList)
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSection deletes a section.
|
||||||
|
func (f *File) DeleteSection(name string) {
|
||||||
|
if f.BlockMode {
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = DEFAULT_SECTION
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, s := range f.sectionList {
|
||||||
|
if s == name {
|
||||||
|
f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
|
||||||
|
delete(f.sections, name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) reload(s dataSource) error {
|
||||||
|
r, err := s.ReadCloser()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
return f.parse(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload reloads and parses all data sources.
|
||||||
|
func (f *File) Reload() (err error) {
|
||||||
|
for _, s := range f.dataSources {
|
||||||
|
if err = f.reload(s); err != nil {
|
||||||
|
// In loose mode, we create an empty default section for nonexistent files.
|
||||||
|
if os.IsNotExist(err) && f.options.Loose {
|
||||||
|
f.parse(bytes.NewBuffer(nil))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append appends one or more data sources and reloads automatically.
|
||||||
|
func (f *File) Append(source interface{}, others ...interface{}) error {
|
||||||
|
ds, err := parseDataSource(source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.dataSources = append(f.dataSources, ds)
|
||||||
|
for _, s := range others {
|
||||||
|
ds, err = parseDataSource(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.dataSources = append(f.dataSources, ds)
|
||||||
|
}
|
||||||
|
return f.Reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
|
||||||
|
equalSign := "="
|
||||||
|
if PrettyFormat || PrettyEqual {
|
||||||
|
equalSign = " = "
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use buffer to make sure target is safe until finish encoding.
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
for i, sname := range f.sectionList {
|
||||||
|
sec := f.Section(sname)
|
||||||
|
if len(sec.Comment) > 0 {
|
||||||
|
if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
|
||||||
|
sec.Comment = "; " + sec.Comment
|
||||||
|
} else {
|
||||||
|
sec.Comment = sec.Comment[:1] + " " + strings.TrimSpace(sec.Comment[1:])
|
||||||
|
}
|
||||||
|
if _, err := buf.WriteString(sec.Comment + LineBreak); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > 0 || DefaultHeader {
|
||||||
|
if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Write nothing if default section is empty
|
||||||
|
if len(sec.keyList) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sec.isRawSection {
|
||||||
|
if _, err := buf.WriteString(sec.rawBody); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if PrettySection {
|
||||||
|
// Put a line between sections
|
||||||
|
if _, err := buf.WriteString(LineBreak); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count and generate alignment length and buffer spaces using the
|
||||||
|
// longest key. Keys may be modifed if they contain certain characters so
|
||||||
|
// we need to take that into account in our calculation.
|
||||||
|
alignLength := 0
|
||||||
|
if PrettyFormat {
|
||||||
|
for _, kname := range sec.keyList {
|
||||||
|
keyLength := len(kname)
|
||||||
|
// First case will surround key by ` and second by """
|
||||||
|
if strings.ContainsAny(kname, "\"=:") {
|
||||||
|
keyLength += 2
|
||||||
|
} else if strings.Contains(kname, "`") {
|
||||||
|
keyLength += 6
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyLength > alignLength {
|
||||||
|
alignLength = keyLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
alignSpaces := bytes.Repeat([]byte(" "), alignLength)
|
||||||
|
|
||||||
|
KEY_LIST:
|
||||||
|
for _, kname := range sec.keyList {
|
||||||
|
key := sec.Key(kname)
|
||||||
|
if len(key.Comment) > 0 {
|
||||||
|
if len(indent) > 0 && sname != DEFAULT_SECTION {
|
||||||
|
buf.WriteString(indent)
|
||||||
|
}
|
||||||
|
if key.Comment[0] != '#' && key.Comment[0] != ';' {
|
||||||
|
key.Comment = "; " + key.Comment
|
||||||
|
} else {
|
||||||
|
key.Comment = key.Comment[:1] + " " + strings.TrimSpace(key.Comment[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Support multiline comments
|
||||||
|
key.Comment = strings.Replace(key.Comment, "\n", "\n; ", -1)
|
||||||
|
|
||||||
|
if _, err := buf.WriteString(key.Comment + LineBreak); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(indent) > 0 && sname != DEFAULT_SECTION {
|
||||||
|
buf.WriteString(indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case key.isAutoIncrement:
|
||||||
|
kname = "-"
|
||||||
|
case strings.ContainsAny(kname, "\"=:"):
|
||||||
|
kname = "`" + kname + "`"
|
||||||
|
case strings.Contains(kname, "`"):
|
||||||
|
kname = `"""` + kname + `"""`
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, val := range key.ValueWithShadows() {
|
||||||
|
if _, err := buf.WriteString(kname); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.isBooleanType {
|
||||||
|
if kname != sec.keyList[len(sec.keyList)-1] {
|
||||||
|
buf.WriteString(LineBreak)
|
||||||
|
}
|
||||||
|
continue KEY_LIST
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write out alignment spaces before "=" sign
|
||||||
|
if PrettyFormat {
|
||||||
|
buf.Write(alignSpaces[:alignLength-len(kname)])
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case key value contains "\n", "`", "\"", "#" or ";"
|
||||||
|
if strings.ContainsAny(val, "\n`") {
|
||||||
|
val = `"""` + val + `"""`
|
||||||
|
} else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
|
||||||
|
val = "`" + val + "`"
|
||||||
|
}
|
||||||
|
if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, val := range key.nestedValues {
|
||||||
|
if _, err := buf.WriteString(indent + " " + val + LineBreak); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if PrettySection {
|
||||||
|
// Put a line between sections
|
||||||
|
if _, err := buf.WriteString(LineBreak); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteToIndent writes content into io.Writer with given indention.
|
||||||
|
// If PrettyFormat has been set to be true,
|
||||||
|
// it will align "=" sign with spaces under each section.
|
||||||
|
func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) {
|
||||||
|
buf, err := f.writeToBuffer(indent)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return buf.WriteTo(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo writes file content into io.Writer.
|
||||||
|
func (f *File) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
return f.WriteToIndent(w, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveToIndent writes content to file system with given value indention.
|
||||||
|
func (f *File) SaveToIndent(filename, indent string) error {
|
||||||
|
// Note: Because we are truncating with os.Create,
|
||||||
|
// so it's safer to save to a temporary file location and rename afte done.
|
||||||
|
buf, err := f.writeToBuffer(indent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ioutil.WriteFile(filename, buf.Bytes(), 0666)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveTo writes content to file system.
|
||||||
|
func (f *File) SaveTo(filename string) error {
|
||||||
|
return f.SaveToIndent(filename, "")
|
||||||
|
}
|
||||||
294
vendor/github.com/go-ini/ini/file_test.go
generated
vendored
Normal file
294
vendor/github.com/go-ini/ini/file_test.go
generated
vendored
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
// Copyright 2017 Unknwon
|
||||||
|
//
|
||||||
|
// 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 ini_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEmpty(t *testing.T) {
|
||||||
|
Convey("Create an empty object", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// Should only have the default section
|
||||||
|
So(len(f.Sections()), ShouldEqual, 1)
|
||||||
|
|
||||||
|
// Default section should not contain any key
|
||||||
|
So(len(f.Section("").Keys()), ShouldBeZeroValue)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile_NewSection(t *testing.T) {
|
||||||
|
Convey("Create a new section", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
sec, err := f.NewSection("author")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(sec, ShouldNotBeNil)
|
||||||
|
So(sec.Name(), ShouldEqual, "author")
|
||||||
|
|
||||||
|
So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "author"})
|
||||||
|
|
||||||
|
Convey("With duplicated name", func() {
|
||||||
|
sec, err := f.NewSection("author")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(sec, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// Does nothing if section already exists
|
||||||
|
So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "author"})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("With empty string", func() {
|
||||||
|
_, err := f.NewSection("")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile_NewRawSection(t *testing.T) {
|
||||||
|
Convey("Create a new raw section", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000
|
||||||
|
111111111111111111100000000000111000000000`)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(sec, ShouldNotBeNil)
|
||||||
|
So(sec.Name(), ShouldEqual, "comments")
|
||||||
|
|
||||||
|
So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "comments"})
|
||||||
|
So(f.Section("comments").Body(), ShouldEqual, `1111111111111111111000000000000000001110000
|
||||||
|
111111111111111111100000000000111000000000`)
|
||||||
|
|
||||||
|
Convey("With duplicated name", func() {
|
||||||
|
sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000`)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(sec, ShouldNotBeNil)
|
||||||
|
So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "comments"})
|
||||||
|
|
||||||
|
// Overwrite previous existed section
|
||||||
|
So(f.Section("comments").Body(), ShouldEqual, `1111111111111111111000000000000000001110000`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("With empty string", func() {
|
||||||
|
_, err := f.NewRawSection("", "")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile_NewSections(t *testing.T) {
|
||||||
|
Convey("Create new sections", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(f.NewSections("package", "author"), ShouldBeNil)
|
||||||
|
So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "package", "author"})
|
||||||
|
|
||||||
|
Convey("With duplicated name", func() {
|
||||||
|
So(f.NewSections("author", "features"), ShouldBeNil)
|
||||||
|
|
||||||
|
// Ignore section already exists
|
||||||
|
So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "package", "author", "features"})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("With empty string", func() {
|
||||||
|
So(f.NewSections("", ""), ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile_GetSection(t *testing.T) {
|
||||||
|
Convey("Get a section", t, func() {
|
||||||
|
f, err := ini.Load(_FULL_CONF)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
sec, err := f.GetSection("author")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(sec, ShouldNotBeNil)
|
||||||
|
So(sec.Name(), ShouldEqual, "author")
|
||||||
|
|
||||||
|
Convey("Section not exists", func() {
|
||||||
|
_, err := f.GetSection("404")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile_Section(t *testing.T) {
|
||||||
|
Convey("Get a section", t, func() {
|
||||||
|
f, err := ini.Load(_FULL_CONF)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
sec := f.Section("author")
|
||||||
|
So(sec, ShouldNotBeNil)
|
||||||
|
So(sec.Name(), ShouldEqual, "author")
|
||||||
|
|
||||||
|
Convey("Section not exists", func() {
|
||||||
|
sec := f.Section("404")
|
||||||
|
So(sec, ShouldNotBeNil)
|
||||||
|
So(sec.Name(), ShouldEqual, "404")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get default section in lower case with insensitive load", t, func() {
|
||||||
|
f, err := ini.InsensitiveLoad([]byte(`
|
||||||
|
[default]
|
||||||
|
NAME = ini
|
||||||
|
VERSION = v1`))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(f.Section("").Key("name").String(), ShouldEqual, "ini")
|
||||||
|
So(f.Section("").Key("version").String(), ShouldEqual, "v1")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile_Sections(t *testing.T) {
|
||||||
|
Convey("Get all sections", t, func() {
|
||||||
|
f, err := ini.Load(_FULL_CONF)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
secs := f.Sections()
|
||||||
|
names := []string{ini.DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"}
|
||||||
|
So(len(secs), ShouldEqual, len(names))
|
||||||
|
for i, name := range names {
|
||||||
|
So(secs[i].Name(), ShouldEqual, name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile_ChildSections(t *testing.T) {
|
||||||
|
Convey("Get child sections by parent name", t, func() {
|
||||||
|
f, err := ini.Load([]byte(`
|
||||||
|
[node]
|
||||||
|
[node.biz1]
|
||||||
|
[node.biz2]
|
||||||
|
[node.biz3]
|
||||||
|
[node.bizN]
|
||||||
|
`))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
children := f.ChildSections("node")
|
||||||
|
names := []string{"node.biz1", "node.biz2", "node.biz3", "node.bizN"}
|
||||||
|
So(len(children), ShouldEqual, len(names))
|
||||||
|
for i, name := range names {
|
||||||
|
So(children[i].Name(), ShouldEqual, name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile_SectionStrings(t *testing.T) {
|
||||||
|
Convey("Get all section names", t, func() {
|
||||||
|
f, err := ini.Load(_FULL_CONF)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile_DeleteSection(t *testing.T) {
|
||||||
|
Convey("Delete a section", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
f.NewSections("author", "package", "features")
|
||||||
|
f.DeleteSection("features")
|
||||||
|
f.DeleteSection("")
|
||||||
|
So(f.SectionStrings(), ShouldResemble, []string{"author", "package"})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile_Append(t *testing.T) {
|
||||||
|
Convey("Append a data source", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(f.Append(_MINIMAL_CONF, []byte(`
|
||||||
|
[author]
|
||||||
|
NAME = Unknwon`)), ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("With bad input", func() {
|
||||||
|
So(f.Append(123), ShouldNotBeNil)
|
||||||
|
So(f.Append(_MINIMAL_CONF, 123), ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile_WriteTo(t *testing.T) {
|
||||||
|
Convey("Write content to somewhere", t, func() {
|
||||||
|
f, err := ini.Load(_FULL_CONF)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
f.Section("author").Comment = `Information about package author
|
||||||
|
# Bio can be written in multiple lines.`
|
||||||
|
f.Section("author").Key("NAME").Comment = "This is author name"
|
||||||
|
f.Section("note").NewBooleanKey("boolean_key")
|
||||||
|
f.Section("note").NewKey("more", "notes")
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err = f.WriteTo(&buf)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
golden := "testdata/TestFile_WriteTo.golden"
|
||||||
|
if *update {
|
||||||
|
ioutil.WriteFile(golden, buf.Bytes(), 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected, err := ioutil.ReadFile(golden)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(buf.String(), ShouldEqual, string(expected))
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Support multiline comments", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
f.Section("").Key("test").Comment = "Multiline\nComment"
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err := f.WriteTo(&buf)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(buf.String(), ShouldEqual, `; Multiline
|
||||||
|
; Comment
|
||||||
|
test =
|
||||||
|
|
||||||
|
`)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile_SaveTo(t *testing.T) {
|
||||||
|
Convey("Write content to somewhere", t, func() {
|
||||||
|
f, err := ini.Load(_FULL_CONF)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(f.SaveTo("testdata/conf_out.ini"), ShouldBeNil)
|
||||||
|
So(f.SaveToIndent("testdata/conf_out.ini", "\t"), ShouldBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
211
vendor/github.com/go-ini/ini/ini.go
generated
vendored
Normal file
211
vendor/github.com/go-ini/ini/ini.go
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
// +build go1.6
|
||||||
|
|
||||||
|
// Copyright 2014 Unknwon
|
||||||
|
//
|
||||||
|
// 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 ini provides INI file read and write functionality in Go.
|
||||||
|
package ini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Name for default section. You can use this constant or the string literal.
|
||||||
|
// In most of cases, an empty string is all you need to access the section.
|
||||||
|
DEFAULT_SECTION = "DEFAULT"
|
||||||
|
|
||||||
|
// Maximum allowed depth when recursively substituing variable names.
|
||||||
|
_DEPTH_VALUES = 99
|
||||||
|
_VERSION = "1.38.1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version returns current package version literal.
|
||||||
|
func Version() string {
|
||||||
|
return _VERSION
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Delimiter to determine or compose a new line.
|
||||||
|
// This variable will be changed to "\r\n" automatically on Windows
|
||||||
|
// at package init time.
|
||||||
|
LineBreak = "\n"
|
||||||
|
|
||||||
|
// Variable regexp pattern: %(variable)s
|
||||||
|
varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
|
||||||
|
|
||||||
|
// Indicate whether to align "=" sign with spaces to produce pretty output
|
||||||
|
// or reduce all possible spaces for compact format.
|
||||||
|
PrettyFormat = true
|
||||||
|
|
||||||
|
// Place spaces around "=" sign even when PrettyFormat is false
|
||||||
|
PrettyEqual = false
|
||||||
|
|
||||||
|
// Explicitly write DEFAULT section header
|
||||||
|
DefaultHeader = false
|
||||||
|
|
||||||
|
// Indicate whether to put a line between sections
|
||||||
|
PrettySection = true
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
LineBreak = "\r\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func inSlice(str string, s []string) bool {
|
||||||
|
for _, v := range s {
|
||||||
|
if str == v {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// dataSource is an interface that returns object which can be read and closed.
|
||||||
|
type dataSource interface {
|
||||||
|
ReadCloser() (io.ReadCloser, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sourceFile represents an object that contains content on the local file system.
|
||||||
|
type sourceFile struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
|
||||||
|
return os.Open(s.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sourceData represents an object that contains content in memory.
|
||||||
|
type sourceData struct {
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
|
||||||
|
return ioutil.NopCloser(bytes.NewReader(s.data)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sourceReadCloser represents an input stream with Close method.
|
||||||
|
type sourceReadCloser struct {
|
||||||
|
reader io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) {
|
||||||
|
return s.reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDataSource(source interface{}) (dataSource, error) {
|
||||||
|
switch s := source.(type) {
|
||||||
|
case string:
|
||||||
|
return sourceFile{s}, nil
|
||||||
|
case []byte:
|
||||||
|
return &sourceData{s}, nil
|
||||||
|
case io.ReadCloser:
|
||||||
|
return &sourceReadCloser{s}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("error parsing data source: unknown type '%s'", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoadOptions struct {
|
||||||
|
// Loose indicates whether the parser should ignore nonexistent files or return error.
|
||||||
|
Loose bool
|
||||||
|
// Insensitive indicates whether the parser forces all section and key names to lowercase.
|
||||||
|
Insensitive bool
|
||||||
|
// IgnoreContinuation indicates whether to ignore continuation lines while parsing.
|
||||||
|
IgnoreContinuation bool
|
||||||
|
// IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
|
||||||
|
IgnoreInlineComment bool
|
||||||
|
// SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs.
|
||||||
|
SkipUnrecognizableLines bool
|
||||||
|
// AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
|
||||||
|
// This type of keys are mostly used in my.cnf.
|
||||||
|
AllowBooleanKeys bool
|
||||||
|
// AllowShadows indicates whether to keep track of keys with same name under same section.
|
||||||
|
AllowShadows bool
|
||||||
|
// AllowNestedValues indicates whether to allow AWS-like nested values.
|
||||||
|
// Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values
|
||||||
|
AllowNestedValues bool
|
||||||
|
// AllowPythonMultilineValues indicates whether to allow Python-like multi-line values.
|
||||||
|
// Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
|
||||||
|
// Relevant quote: Values can also span multiple lines, as long as they are indented deeper
|
||||||
|
// than the first line of the value.
|
||||||
|
AllowPythonMultilineValues bool
|
||||||
|
// SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value.
|
||||||
|
// Docs: https://docs.python.org/2/library/configparser.html
|
||||||
|
// Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names.
|
||||||
|
// In the latter case, they need to be preceded by a whitespace character to be recognized as a comment.
|
||||||
|
SpaceBeforeInlineComment bool
|
||||||
|
// UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format
|
||||||
|
// when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value"
|
||||||
|
UnescapeValueDoubleQuotes bool
|
||||||
|
// UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format
|
||||||
|
// when value is NOT surrounded by any quotes.
|
||||||
|
// Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all.
|
||||||
|
UnescapeValueCommentSymbols bool
|
||||||
|
// UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise
|
||||||
|
// conform to key/value pairs. Specify the names of those blocks here.
|
||||||
|
UnparseableSections []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
|
||||||
|
sources := make([]dataSource, len(others)+1)
|
||||||
|
sources[0], err = parseDataSource(source)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i := range others {
|
||||||
|
sources[i+1], err = parseDataSource(others[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f := newFile(sources, opts)
|
||||||
|
if err = f.Reload(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads and parses from INI data sources.
|
||||||
|
// Arguments can be mixed of file name with string type, or raw data in []byte.
|
||||||
|
// It will return error if list contains nonexistent files.
|
||||||
|
func Load(source interface{}, others ...interface{}) (*File, error) {
|
||||||
|
return LoadSources(LoadOptions{}, source, others...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LooseLoad has exactly same functionality as Load function
|
||||||
|
// except it ignores nonexistent files instead of returning error.
|
||||||
|
func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
|
||||||
|
return LoadSources(LoadOptions{Loose: true}, source, others...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsensitiveLoad has exactly same functionality as Load function
|
||||||
|
// except it forces all section and key names to be lowercased.
|
||||||
|
func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
|
||||||
|
return LoadSources(LoadOptions{Insensitive: true}, source, others...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsensitiveLoad has exactly same functionality as Load function
|
||||||
|
// except it allows have shadow keys.
|
||||||
|
func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
|
||||||
|
return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
|
||||||
|
}
|
||||||
35
vendor/github.com/go-ini/ini/ini_internal_test.go
generated
vendored
Normal file
35
vendor/github.com/go-ini/ini/ini_internal_test.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2017 Unknwon
|
||||||
|
//
|
||||||
|
// 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 ini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Version(t *testing.T) {
|
||||||
|
Convey("Get version", t, func() {
|
||||||
|
So(Version(), ShouldEqual, _VERSION)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_isSlice(t *testing.T) {
|
||||||
|
Convey("Check if a string is in the slice", t, func() {
|
||||||
|
ss := []string{"a", "b", "c"}
|
||||||
|
So(inSlice("a", ss), ShouldBeTrue)
|
||||||
|
So(inSlice("d", ss), ShouldBeFalse)
|
||||||
|
})
|
||||||
|
}
|
||||||
1233
vendor/github.com/go-ini/ini/ini_test.go
generated
vendored
Normal file
1233
vendor/github.com/go-ini/ini/ini_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
751
vendor/github.com/go-ini/ini/key.go
generated
vendored
Normal file
751
vendor/github.com/go-ini/ini/key.go
generated
vendored
Normal file
@@ -0,0 +1,751 @@
|
|||||||
|
// Copyright 2014 Unknwon
|
||||||
|
//
|
||||||
|
// 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 ini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key represents a key under a section.
|
||||||
|
type Key struct {
|
||||||
|
s *Section
|
||||||
|
Comment string
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
isAutoIncrement bool
|
||||||
|
isBooleanType bool
|
||||||
|
|
||||||
|
isShadow bool
|
||||||
|
shadows []*Key
|
||||||
|
|
||||||
|
nestedValues []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// newKey simply return a key object with given values.
|
||||||
|
func newKey(s *Section, name, val string) *Key {
|
||||||
|
return &Key{
|
||||||
|
s: s,
|
||||||
|
name: name,
|
||||||
|
value: val,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) addShadow(val string) error {
|
||||||
|
if k.isShadow {
|
||||||
|
return errors.New("cannot add shadow to another shadow key")
|
||||||
|
} else if k.isAutoIncrement || k.isBooleanType {
|
||||||
|
return errors.New("cannot add shadow to auto-increment or boolean key")
|
||||||
|
}
|
||||||
|
|
||||||
|
shadow := newKey(k.s, k.name, val)
|
||||||
|
shadow.isShadow = true
|
||||||
|
k.shadows = append(k.shadows, shadow)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddShadow adds a new shadow key to itself.
|
||||||
|
func (k *Key) AddShadow(val string) error {
|
||||||
|
if !k.s.f.options.AllowShadows {
|
||||||
|
return errors.New("shadow key is not allowed")
|
||||||
|
}
|
||||||
|
return k.addShadow(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) addNestedValue(val string) error {
|
||||||
|
if k.isAutoIncrement || k.isBooleanType {
|
||||||
|
return errors.New("cannot add nested value to auto-increment or boolean key")
|
||||||
|
}
|
||||||
|
|
||||||
|
k.nestedValues = append(k.nestedValues, val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) AddNestedValue(val string) error {
|
||||||
|
if !k.s.f.options.AllowNestedValues {
|
||||||
|
return errors.New("nested value is not allowed")
|
||||||
|
}
|
||||||
|
return k.addNestedValue(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
|
||||||
|
type ValueMapper func(string) string
|
||||||
|
|
||||||
|
// Name returns name of key.
|
||||||
|
func (k *Key) Name() string {
|
||||||
|
return k.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns raw value of key for performance purpose.
|
||||||
|
func (k *Key) Value() string {
|
||||||
|
return k.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValueWithShadows returns raw values of key and its shadows if any.
|
||||||
|
func (k *Key) ValueWithShadows() []string {
|
||||||
|
if len(k.shadows) == 0 {
|
||||||
|
return []string{k.value}
|
||||||
|
}
|
||||||
|
vals := make([]string, len(k.shadows)+1)
|
||||||
|
vals[0] = k.value
|
||||||
|
for i := range k.shadows {
|
||||||
|
vals[i+1] = k.shadows[i].value
|
||||||
|
}
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// NestedValues returns nested values stored in the key.
|
||||||
|
// It is possible returned value is nil if no nested values stored in the key.
|
||||||
|
func (k *Key) NestedValues() []string {
|
||||||
|
return k.nestedValues
|
||||||
|
}
|
||||||
|
|
||||||
|
// transformValue takes a raw value and transforms to its final string.
|
||||||
|
func (k *Key) transformValue(val string) string {
|
||||||
|
if k.s.f.ValueMapper != nil {
|
||||||
|
val = k.s.f.ValueMapper(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail-fast if no indicate char found for recursive value
|
||||||
|
if !strings.Contains(val, "%") {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
for i := 0; i < _DEPTH_VALUES; i++ {
|
||||||
|
vr := varPattern.FindString(val)
|
||||||
|
if len(vr) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take off leading '%(' and trailing ')s'.
|
||||||
|
noption := strings.TrimLeft(vr, "%(")
|
||||||
|
noption = strings.TrimRight(noption, ")s")
|
||||||
|
|
||||||
|
// Search in the same section.
|
||||||
|
nk, err := k.s.GetKey(noption)
|
||||||
|
if err != nil || k == nk {
|
||||||
|
// Search again in default section.
|
||||||
|
nk, _ = k.s.f.Section("").GetKey(noption)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Substitute by new value and take off leading '%(' and trailing ')s'.
|
||||||
|
val = strings.Replace(val, vr, nk.value, -1)
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns string representation of value.
|
||||||
|
func (k *Key) String() string {
|
||||||
|
return k.transformValue(k.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate accepts a validate function which can
|
||||||
|
// return modifed result as key value.
|
||||||
|
func (k *Key) Validate(fn func(string) string) string {
|
||||||
|
return fn(k.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseBool returns the boolean value represented by the string.
|
||||||
|
//
|
||||||
|
// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
|
||||||
|
// 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
|
||||||
|
// Any other value returns an error.
|
||||||
|
func parseBool(str string) (value bool, err error) {
|
||||||
|
switch str {
|
||||||
|
case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
|
||||||
|
return true, nil
|
||||||
|
case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns bool type value.
|
||||||
|
func (k *Key) Bool() (bool, error) {
|
||||||
|
return parseBool(k.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 returns float64 type value.
|
||||||
|
func (k *Key) Float64() (float64, error) {
|
||||||
|
return strconv.ParseFloat(k.String(), 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns int type value.
|
||||||
|
func (k *Key) Int() (int, error) {
|
||||||
|
return strconv.Atoi(k.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 returns int64 type value.
|
||||||
|
func (k *Key) Int64() (int64, error) {
|
||||||
|
return strconv.ParseInt(k.String(), 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint returns uint type valued.
|
||||||
|
func (k *Key) Uint() (uint, error) {
|
||||||
|
u, e := strconv.ParseUint(k.String(), 10, 64)
|
||||||
|
return uint(u), e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 returns uint64 type value.
|
||||||
|
func (k *Key) Uint64() (uint64, error) {
|
||||||
|
return strconv.ParseUint(k.String(), 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration returns time.Duration type value.
|
||||||
|
func (k *Key) Duration() (time.Duration, error) {
|
||||||
|
return time.ParseDuration(k.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeFormat parses with given format and returns time.Time type value.
|
||||||
|
func (k *Key) TimeFormat(format string) (time.Time, error) {
|
||||||
|
return time.Parse(format, k.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time parses with RFC3339 format and returns time.Time type value.
|
||||||
|
func (k *Key) Time() (time.Time, error) {
|
||||||
|
return k.TimeFormat(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustString returns default value if key value is empty.
|
||||||
|
func (k *Key) MustString(defaultVal string) string {
|
||||||
|
val := k.String()
|
||||||
|
if len(val) == 0 {
|
||||||
|
k.value = defaultVal
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustBool always returns value without error,
|
||||||
|
// it returns false if error occurs.
|
||||||
|
func (k *Key) MustBool(defaultVal ...bool) bool {
|
||||||
|
val, err := k.Bool()
|
||||||
|
if len(defaultVal) > 0 && err != nil {
|
||||||
|
k.value = strconv.FormatBool(defaultVal[0])
|
||||||
|
return defaultVal[0]
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustFloat64 always returns value without error,
|
||||||
|
// it returns 0.0 if error occurs.
|
||||||
|
func (k *Key) MustFloat64(defaultVal ...float64) float64 {
|
||||||
|
val, err := k.Float64()
|
||||||
|
if len(defaultVal) > 0 && err != nil {
|
||||||
|
k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
|
||||||
|
return defaultVal[0]
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustInt always returns value without error,
|
||||||
|
// it returns 0 if error occurs.
|
||||||
|
func (k *Key) MustInt(defaultVal ...int) int {
|
||||||
|
val, err := k.Int()
|
||||||
|
if len(defaultVal) > 0 && err != nil {
|
||||||
|
k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
|
||||||
|
return defaultVal[0]
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustInt64 always returns value without error,
|
||||||
|
// it returns 0 if error occurs.
|
||||||
|
func (k *Key) MustInt64(defaultVal ...int64) int64 {
|
||||||
|
val, err := k.Int64()
|
||||||
|
if len(defaultVal) > 0 && err != nil {
|
||||||
|
k.value = strconv.FormatInt(defaultVal[0], 10)
|
||||||
|
return defaultVal[0]
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustUint always returns value without error,
|
||||||
|
// it returns 0 if error occurs.
|
||||||
|
func (k *Key) MustUint(defaultVal ...uint) uint {
|
||||||
|
val, err := k.Uint()
|
||||||
|
if len(defaultVal) > 0 && err != nil {
|
||||||
|
k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
|
||||||
|
return defaultVal[0]
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustUint64 always returns value without error,
|
||||||
|
// it returns 0 if error occurs.
|
||||||
|
func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
|
||||||
|
val, err := k.Uint64()
|
||||||
|
if len(defaultVal) > 0 && err != nil {
|
||||||
|
k.value = strconv.FormatUint(defaultVal[0], 10)
|
||||||
|
return defaultVal[0]
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustDuration always returns value without error,
|
||||||
|
// it returns zero value if error occurs.
|
||||||
|
func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
|
||||||
|
val, err := k.Duration()
|
||||||
|
if len(defaultVal) > 0 && err != nil {
|
||||||
|
k.value = defaultVal[0].String()
|
||||||
|
return defaultVal[0]
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustTimeFormat always parses with given format and returns value without error,
|
||||||
|
// it returns zero value if error occurs.
|
||||||
|
func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
|
||||||
|
val, err := k.TimeFormat(format)
|
||||||
|
if len(defaultVal) > 0 && err != nil {
|
||||||
|
k.value = defaultVal[0].Format(format)
|
||||||
|
return defaultVal[0]
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustTime always parses with RFC3339 format and returns value without error,
|
||||||
|
// it returns zero value if error occurs.
|
||||||
|
func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
|
||||||
|
return k.MustTimeFormat(time.RFC3339, defaultVal...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// In always returns value without error,
|
||||||
|
// it returns default value if error occurs or doesn't fit into candidates.
|
||||||
|
func (k *Key) In(defaultVal string, candidates []string) string {
|
||||||
|
val := k.String()
|
||||||
|
for _, cand := range candidates {
|
||||||
|
if val == cand {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// InFloat64 always returns value without error,
|
||||||
|
// it returns default value if error occurs or doesn't fit into candidates.
|
||||||
|
func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
|
||||||
|
val := k.MustFloat64()
|
||||||
|
for _, cand := range candidates {
|
||||||
|
if val == cand {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// InInt always returns value without error,
|
||||||
|
// it returns default value if error occurs or doesn't fit into candidates.
|
||||||
|
func (k *Key) InInt(defaultVal int, candidates []int) int {
|
||||||
|
val := k.MustInt()
|
||||||
|
for _, cand := range candidates {
|
||||||
|
if val == cand {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// InInt64 always returns value without error,
|
||||||
|
// it returns default value if error occurs or doesn't fit into candidates.
|
||||||
|
func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
|
||||||
|
val := k.MustInt64()
|
||||||
|
for _, cand := range candidates {
|
||||||
|
if val == cand {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// InUint always returns value without error,
|
||||||
|
// it returns default value if error occurs or doesn't fit into candidates.
|
||||||
|
func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
|
||||||
|
val := k.MustUint()
|
||||||
|
for _, cand := range candidates {
|
||||||
|
if val == cand {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// InUint64 always returns value without error,
|
||||||
|
// it returns default value if error occurs or doesn't fit into candidates.
|
||||||
|
func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
|
||||||
|
val := k.MustUint64()
|
||||||
|
for _, cand := range candidates {
|
||||||
|
if val == cand {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// InTimeFormat always parses with given format and returns value without error,
|
||||||
|
// it returns default value if error occurs or doesn't fit into candidates.
|
||||||
|
func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
|
||||||
|
val := k.MustTimeFormat(format)
|
||||||
|
for _, cand := range candidates {
|
||||||
|
if val == cand {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// InTime always parses with RFC3339 format and returns value without error,
|
||||||
|
// it returns default value if error occurs or doesn't fit into candidates.
|
||||||
|
func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
|
||||||
|
return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeFloat64 checks if value is in given range inclusively,
|
||||||
|
// and returns default value if it's not.
|
||||||
|
func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
|
||||||
|
val := k.MustFloat64()
|
||||||
|
if val < min || val > max {
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeInt checks if value is in given range inclusively,
|
||||||
|
// and returns default value if it's not.
|
||||||
|
func (k *Key) RangeInt(defaultVal, min, max int) int {
|
||||||
|
val := k.MustInt()
|
||||||
|
if val < min || val > max {
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeInt64 checks if value is in given range inclusively,
|
||||||
|
// and returns default value if it's not.
|
||||||
|
func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
|
||||||
|
val := k.MustInt64()
|
||||||
|
if val < min || val > max {
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeTimeFormat checks if value with given format is in given range inclusively,
|
||||||
|
// and returns default value if it's not.
|
||||||
|
func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
|
||||||
|
val := k.MustTimeFormat(format)
|
||||||
|
if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeTime checks if value with RFC3339 format is in given range inclusively,
|
||||||
|
// and returns default value if it's not.
|
||||||
|
func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
|
||||||
|
return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings returns list of string divided by given delimiter.
|
||||||
|
func (k *Key) Strings(delim string) []string {
|
||||||
|
str := k.String()
|
||||||
|
if len(str) == 0 {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
runes := []rune(str)
|
||||||
|
vals := make([]string, 0, 2)
|
||||||
|
var buf bytes.Buffer
|
||||||
|
escape := false
|
||||||
|
idx := 0
|
||||||
|
for {
|
||||||
|
if escape {
|
||||||
|
escape = false
|
||||||
|
if runes[idx] != '\\' && !strings.HasPrefix(string(runes[idx:]), delim) {
|
||||||
|
buf.WriteRune('\\')
|
||||||
|
}
|
||||||
|
buf.WriteRune(runes[idx])
|
||||||
|
} else {
|
||||||
|
if runes[idx] == '\\' {
|
||||||
|
escape = true
|
||||||
|
} else if strings.HasPrefix(string(runes[idx:]), delim) {
|
||||||
|
idx += len(delim) - 1
|
||||||
|
vals = append(vals, strings.TrimSpace(buf.String()))
|
||||||
|
buf.Reset()
|
||||||
|
} else {
|
||||||
|
buf.WriteRune(runes[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
idx += 1
|
||||||
|
if idx == len(runes) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
vals = append(vals, strings.TrimSpace(buf.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringsWithShadows returns list of string divided by given delimiter.
|
||||||
|
// Shadows will also be appended if any.
|
||||||
|
func (k *Key) StringsWithShadows(delim string) []string {
|
||||||
|
vals := k.ValueWithShadows()
|
||||||
|
results := make([]string, 0, len(vals)*2)
|
||||||
|
for i := range vals {
|
||||||
|
if len(vals) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
results = append(results, strings.Split(vals[i], delim)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range results {
|
||||||
|
results[i] = k.transformValue(strings.TrimSpace(results[i]))
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value.
|
||||||
|
func (k *Key) Float64s(delim string) []float64 {
|
||||||
|
vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value.
|
||||||
|
func (k *Key) Ints(delim string) []int {
|
||||||
|
vals, _ := k.parseInts(k.Strings(delim), true, false)
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value.
|
||||||
|
func (k *Key) Int64s(delim string) []int64 {
|
||||||
|
vals, _ := k.parseInt64s(k.Strings(delim), true, false)
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value.
|
||||||
|
func (k *Key) Uints(delim string) []uint {
|
||||||
|
vals, _ := k.parseUints(k.Strings(delim), true, false)
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value.
|
||||||
|
func (k *Key) Uint64s(delim string) []uint64 {
|
||||||
|
vals, _ := k.parseUint64s(k.Strings(delim), true, false)
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimesFormat parses with given format and returns list of time.Time divided by given delimiter.
|
||||||
|
// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
|
||||||
|
func (k *Key) TimesFormat(format, delim string) []time.Time {
|
||||||
|
vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// Times parses with RFC3339 format and returns list of time.Time divided by given delimiter.
|
||||||
|
// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
|
||||||
|
func (k *Key) Times(delim string) []time.Time {
|
||||||
|
return k.TimesFormat(time.RFC3339, delim)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
|
||||||
|
// it will not be included to result list.
|
||||||
|
func (k *Key) ValidFloat64s(delim string) []float64 {
|
||||||
|
vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
|
||||||
|
// not be included to result list.
|
||||||
|
func (k *Key) ValidInts(delim string) []int {
|
||||||
|
vals, _ := k.parseInts(k.Strings(delim), false, false)
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer,
|
||||||
|
// then it will not be included to result list.
|
||||||
|
func (k *Key) ValidInt64s(delim string) []int64 {
|
||||||
|
vals, _ := k.parseInt64s(k.Strings(delim), false, false)
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer,
|
||||||
|
// then it will not be included to result list.
|
||||||
|
func (k *Key) ValidUints(delim string) []uint {
|
||||||
|
vals, _ := k.parseUints(k.Strings(delim), false, false)
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned
|
||||||
|
// integer, then it will not be included to result list.
|
||||||
|
func (k *Key) ValidUint64s(delim string) []uint64 {
|
||||||
|
vals, _ := k.parseUint64s(k.Strings(delim), false, false)
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
|
||||||
|
func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
|
||||||
|
vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
|
||||||
|
func (k *Key) ValidTimes(delim string) []time.Time {
|
||||||
|
return k.ValidTimesFormat(time.RFC3339, delim)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
|
||||||
|
func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
|
||||||
|
return k.parseFloat64s(k.Strings(delim), false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrictInts returns list of int divided by given delimiter or error on first invalid input.
|
||||||
|
func (k *Key) StrictInts(delim string) ([]int, error) {
|
||||||
|
return k.parseInts(k.Strings(delim), false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
|
||||||
|
func (k *Key) StrictInt64s(delim string) ([]int64, error) {
|
||||||
|
return k.parseInt64s(k.Strings(delim), false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrictUints returns list of uint divided by given delimiter or error on first invalid input.
|
||||||
|
func (k *Key) StrictUints(delim string) ([]uint, error) {
|
||||||
|
return k.parseUints(k.Strings(delim), false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
|
||||||
|
func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
|
||||||
|
return k.parseUint64s(k.Strings(delim), false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
|
||||||
|
// or error on first invalid input.
|
||||||
|
func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
|
||||||
|
return k.parseTimesFormat(format, k.Strings(delim), false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
|
||||||
|
// or error on first invalid input.
|
||||||
|
func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
|
||||||
|
return k.StrictTimesFormat(time.RFC3339, delim)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseFloat64s transforms strings to float64s.
|
||||||
|
func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
|
||||||
|
vals := make([]float64, 0, len(strs))
|
||||||
|
for _, str := range strs {
|
||||||
|
val, err := strconv.ParseFloat(str, 64)
|
||||||
|
if err != nil && returnOnInvalid {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err == nil || addInvalid {
|
||||||
|
vals = append(vals, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vals, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseInts transforms strings to ints.
|
||||||
|
func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
|
||||||
|
vals := make([]int, 0, len(strs))
|
||||||
|
for _, str := range strs {
|
||||||
|
val, err := strconv.Atoi(str)
|
||||||
|
if err != nil && returnOnInvalid {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err == nil || addInvalid {
|
||||||
|
vals = append(vals, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vals, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseInt64s transforms strings to int64s.
|
||||||
|
func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
|
||||||
|
vals := make([]int64, 0, len(strs))
|
||||||
|
for _, str := range strs {
|
||||||
|
val, err := strconv.ParseInt(str, 10, 64)
|
||||||
|
if err != nil && returnOnInvalid {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err == nil || addInvalid {
|
||||||
|
vals = append(vals, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vals, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseUints transforms strings to uints.
|
||||||
|
func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
|
||||||
|
vals := make([]uint, 0, len(strs))
|
||||||
|
for _, str := range strs {
|
||||||
|
val, err := strconv.ParseUint(str, 10, 0)
|
||||||
|
if err != nil && returnOnInvalid {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err == nil || addInvalid {
|
||||||
|
vals = append(vals, uint(val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vals, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseUint64s transforms strings to uint64s.
|
||||||
|
func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
|
||||||
|
vals := make([]uint64, 0, len(strs))
|
||||||
|
for _, str := range strs {
|
||||||
|
val, err := strconv.ParseUint(str, 10, 64)
|
||||||
|
if err != nil && returnOnInvalid {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err == nil || addInvalid {
|
||||||
|
vals = append(vals, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vals, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTimesFormat transforms strings to times in given format.
|
||||||
|
func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
|
||||||
|
vals := make([]time.Time, 0, len(strs))
|
||||||
|
for _, str := range strs {
|
||||||
|
val, err := time.Parse(format, str)
|
||||||
|
if err != nil && returnOnInvalid {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err == nil || addInvalid {
|
||||||
|
vals = append(vals, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vals, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetValue changes key value.
|
||||||
|
func (k *Key) SetValue(v string) {
|
||||||
|
if k.s.f.BlockMode {
|
||||||
|
k.s.f.lock.Lock()
|
||||||
|
defer k.s.f.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
k.value = v
|
||||||
|
k.s.keysHash[k.name] = v
|
||||||
|
}
|
||||||
523
vendor/github.com/go-ini/ini/key_test.go
generated
vendored
Normal file
523
vendor/github.com/go-ini/ini/key_test.go
generated
vendored
Normal file
@@ -0,0 +1,523 @@
|
|||||||
|
// Copyright 2014 Unknwon
|
||||||
|
//
|
||||||
|
// 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 ini_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKey_AddShadow(t *testing.T) {
|
||||||
|
Convey("Add shadow to a key", t, func() {
|
||||||
|
f, err := ini.ShadowLoad([]byte(`
|
||||||
|
[notes]
|
||||||
|
-: note1`))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err := f.Section("").NewKey("NAME", "ini")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(k.AddShadow("ini.v1"), ShouldBeNil)
|
||||||
|
So(k.ValueWithShadows(), ShouldResemble, []string{"ini", "ini.v1"})
|
||||||
|
|
||||||
|
Convey("Add shadow to boolean key", func() {
|
||||||
|
k, err := f.Section("").NewBooleanKey("published")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
So(k.AddShadow("beta"), ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Add shadow to auto-increment key", func() {
|
||||||
|
So(f.Section("notes").Key("#1").AddShadow("beta"), ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Shadow is not allowed", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err := f.Section("").NewKey("NAME", "ini")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(k.AddShadow("ini.v1"), ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers for slice tests.
|
||||||
|
func float64sEqual(values []float64, expected ...float64) {
|
||||||
|
So(values, ShouldHaveLength, len(expected))
|
||||||
|
for i, v := range expected {
|
||||||
|
So(values[i], ShouldEqual, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func intsEqual(values []int, expected ...int) {
|
||||||
|
So(values, ShouldHaveLength, len(expected))
|
||||||
|
for i, v := range expected {
|
||||||
|
So(values[i], ShouldEqual, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func int64sEqual(values []int64, expected ...int64) {
|
||||||
|
So(values, ShouldHaveLength, len(expected))
|
||||||
|
for i, v := range expected {
|
||||||
|
So(values[i], ShouldEqual, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func uintsEqual(values []uint, expected ...uint) {
|
||||||
|
So(values, ShouldHaveLength, len(expected))
|
||||||
|
for i, v := range expected {
|
||||||
|
So(values[i], ShouldEqual, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func uint64sEqual(values []uint64, expected ...uint64) {
|
||||||
|
So(values, ShouldHaveLength, len(expected))
|
||||||
|
for i, v := range expected {
|
||||||
|
So(values[i], ShouldEqual, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func timesEqual(values []time.Time, expected ...time.Time) {
|
||||||
|
So(values, ShouldHaveLength, len(expected))
|
||||||
|
for i, v := range expected {
|
||||||
|
So(values[i].String(), ShouldEqual, v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKey_Helpers(t *testing.T) {
|
||||||
|
Convey("Getting and setting values", t, func() {
|
||||||
|
f, err := ini.Load(_FULL_CONF)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
Convey("Get string representation", func() {
|
||||||
|
sec := f.Section("")
|
||||||
|
So(sec, ShouldNotBeNil)
|
||||||
|
So(sec.Key("NAME").Value(), ShouldEqual, "ini")
|
||||||
|
So(sec.Key("NAME").String(), ShouldEqual, "ini")
|
||||||
|
So(sec.Key("NAME").Validate(func(in string) string {
|
||||||
|
return in
|
||||||
|
}), ShouldEqual, "ini")
|
||||||
|
So(sec.Key("NAME").Comment, ShouldEqual, "; Package name")
|
||||||
|
So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1")
|
||||||
|
|
||||||
|
Convey("With ValueMapper", func() {
|
||||||
|
f.ValueMapper = func(in string) string {
|
||||||
|
if in == "gopkg.in/%(NAME)s.%(VERSION)s" {
|
||||||
|
return "github.com/go-ini/ini"
|
||||||
|
}
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "github.com/go-ini/ini")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get values in non-default section", func() {
|
||||||
|
sec := f.Section("author")
|
||||||
|
So(sec, ShouldNotBeNil)
|
||||||
|
So(sec.Key("NAME").String(), ShouldEqual, "Unknwon")
|
||||||
|
So(sec.Key("GITHUB").String(), ShouldEqual, "https://github.com/Unknwon")
|
||||||
|
|
||||||
|
sec = f.Section("package")
|
||||||
|
So(sec, ShouldNotBeNil)
|
||||||
|
So(sec.Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get auto-increment key names", func() {
|
||||||
|
keys := f.Section("features").Keys()
|
||||||
|
for i, k := range keys {
|
||||||
|
So(k.Name(), ShouldEqual, fmt.Sprintf("#%d", i+1))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get parent-keys that are available to the child section", func() {
|
||||||
|
parentKeys := f.Section("package.sub").ParentKeys()
|
||||||
|
for _, k := range parentKeys {
|
||||||
|
So(k.Name(), ShouldEqual, "CLONE_URL")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get overwrite value", func() {
|
||||||
|
So(f.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get sections", func() {
|
||||||
|
sections := f.Sections()
|
||||||
|
for i, name := range []string{ini.DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"} {
|
||||||
|
So(sections[i].Name(), ShouldEqual, name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get parent section value", func() {
|
||||||
|
So(f.Section("package.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
|
||||||
|
So(f.Section("package.fake.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get multiple line value", func() {
|
||||||
|
So(f.Section("author").Key("BIO").String(), ShouldEqual, "Gopher.\nCoding addict.\nGood man.\n")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get values with type", func() {
|
||||||
|
sec := f.Section("types")
|
||||||
|
v1, err := sec.Key("BOOL").Bool()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(v1, ShouldBeTrue)
|
||||||
|
|
||||||
|
v1, err = sec.Key("BOOL_FALSE").Bool()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(v1, ShouldBeFalse)
|
||||||
|
|
||||||
|
v2, err := sec.Key("FLOAT64").Float64()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(v2, ShouldEqual, 1.25)
|
||||||
|
|
||||||
|
v3, err := sec.Key("INT").Int()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(v3, ShouldEqual, 10)
|
||||||
|
|
||||||
|
v4, err := sec.Key("INT").Int64()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(v4, ShouldEqual, 10)
|
||||||
|
|
||||||
|
v5, err := sec.Key("UINT").Uint()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(v5, ShouldEqual, 3)
|
||||||
|
|
||||||
|
v6, err := sec.Key("UINT").Uint64()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(v6, ShouldEqual, 3)
|
||||||
|
|
||||||
|
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
v7, err := sec.Key("TIME").Time()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(v7.String(), ShouldEqual, t.String())
|
||||||
|
|
||||||
|
Convey("Must get values with type", func() {
|
||||||
|
So(sec.Key("STRING").MustString("404"), ShouldEqual, "str")
|
||||||
|
So(sec.Key("BOOL").MustBool(), ShouldBeTrue)
|
||||||
|
So(sec.Key("FLOAT64").MustFloat64(), ShouldEqual, 1.25)
|
||||||
|
So(sec.Key("INT").MustInt(), ShouldEqual, 10)
|
||||||
|
So(sec.Key("INT").MustInt64(), ShouldEqual, 10)
|
||||||
|
So(sec.Key("UINT").MustUint(), ShouldEqual, 3)
|
||||||
|
So(sec.Key("UINT").MustUint64(), ShouldEqual, 3)
|
||||||
|
So(sec.Key("TIME").MustTime().String(), ShouldEqual, t.String())
|
||||||
|
|
||||||
|
dur, err := time.ParseDuration("2h45m")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(sec.Key("DURATION").MustDuration().Seconds(), ShouldEqual, dur.Seconds())
|
||||||
|
|
||||||
|
Convey("Must get values with default value", func() {
|
||||||
|
So(sec.Key("STRING_404").MustString("404"), ShouldEqual, "404")
|
||||||
|
So(sec.Key("BOOL_404").MustBool(true), ShouldBeTrue)
|
||||||
|
So(sec.Key("FLOAT64_404").MustFloat64(2.5), ShouldEqual, 2.5)
|
||||||
|
So(sec.Key("INT_404").MustInt(15), ShouldEqual, 15)
|
||||||
|
So(sec.Key("INT64_404").MustInt64(15), ShouldEqual, 15)
|
||||||
|
So(sec.Key("UINT_404").MustUint(6), ShouldEqual, 6)
|
||||||
|
So(sec.Key("UINT64_404").MustUint64(6), ShouldEqual, 6)
|
||||||
|
|
||||||
|
t, err := time.Parse(time.RFC3339, "2014-01-01T20:17:05Z")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(sec.Key("TIME_404").MustTime(t).String(), ShouldEqual, t.String())
|
||||||
|
|
||||||
|
So(sec.Key("DURATION_404").MustDuration(dur).Seconds(), ShouldEqual, dur.Seconds())
|
||||||
|
|
||||||
|
Convey("Must should set default as key value", func() {
|
||||||
|
So(sec.Key("STRING_404").String(), ShouldEqual, "404")
|
||||||
|
So(sec.Key("BOOL_404").String(), ShouldEqual, "true")
|
||||||
|
So(sec.Key("FLOAT64_404").String(), ShouldEqual, "2.5")
|
||||||
|
So(sec.Key("INT_404").String(), ShouldEqual, "15")
|
||||||
|
So(sec.Key("INT64_404").String(), ShouldEqual, "15")
|
||||||
|
So(sec.Key("UINT_404").String(), ShouldEqual, "6")
|
||||||
|
So(sec.Key("UINT64_404").String(), ShouldEqual, "6")
|
||||||
|
So(sec.Key("TIME_404").String(), ShouldEqual, "2014-01-01T20:17:05Z")
|
||||||
|
So(sec.Key("DURATION_404").String(), ShouldEqual, "2h45m0s")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get value with candidates", func() {
|
||||||
|
sec := f.Section("types")
|
||||||
|
So(sec.Key("STRING").In("", []string{"str", "arr", "types"}), ShouldEqual, "str")
|
||||||
|
So(sec.Key("FLOAT64").InFloat64(0, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
|
||||||
|
So(sec.Key("INT").InInt(0, []int{10, 20, 30}), ShouldEqual, 10)
|
||||||
|
So(sec.Key("INT").InInt64(0, []int64{10, 20, 30}), ShouldEqual, 10)
|
||||||
|
So(sec.Key("UINT").InUint(0, []uint{3, 6, 9}), ShouldEqual, 3)
|
||||||
|
So(sec.Key("UINT").InUint64(0, []uint64{3, 6, 9}), ShouldEqual, 3)
|
||||||
|
|
||||||
|
zt, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(sec.Key("TIME").InTime(zt, []time.Time{t, time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
|
||||||
|
|
||||||
|
Convey("Get value with candidates and default value", func() {
|
||||||
|
So(sec.Key("STRING_404").In("str", []string{"str", "arr", "types"}), ShouldEqual, "str")
|
||||||
|
So(sec.Key("FLOAT64_404").InFloat64(1.25, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
|
||||||
|
So(sec.Key("INT_404").InInt(10, []int{10, 20, 30}), ShouldEqual, 10)
|
||||||
|
So(sec.Key("INT64_404").InInt64(10, []int64{10, 20, 30}), ShouldEqual, 10)
|
||||||
|
So(sec.Key("UINT_404").InUint(3, []uint{3, 6, 9}), ShouldEqual, 3)
|
||||||
|
So(sec.Key("UINT_404").InUint64(3, []uint64{3, 6, 9}), ShouldEqual, 3)
|
||||||
|
So(sec.Key("TIME_404").InTime(t, []time.Time{time.Now(), time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get values in range", func() {
|
||||||
|
sec := f.Section("types")
|
||||||
|
So(sec.Key("FLOAT64").RangeFloat64(0, 1, 2), ShouldEqual, 1.25)
|
||||||
|
So(sec.Key("INT").RangeInt(0, 10, 20), ShouldEqual, 10)
|
||||||
|
So(sec.Key("INT").RangeInt64(0, 10, 20), ShouldEqual, 10)
|
||||||
|
|
||||||
|
minT, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
midT, err := time.Parse(time.RFC3339, "2013-01-01T01:00:00Z")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
maxT, err := time.Parse(time.RFC3339, "9999-01-01T01:00:00Z")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(sec.Key("TIME").RangeTime(t, minT, maxT).String(), ShouldEqual, t.String())
|
||||||
|
|
||||||
|
Convey("Get value in range with default value", func() {
|
||||||
|
So(sec.Key("FLOAT64").RangeFloat64(5, 0, 1), ShouldEqual, 5)
|
||||||
|
So(sec.Key("INT").RangeInt(7, 0, 5), ShouldEqual, 7)
|
||||||
|
So(sec.Key("INT").RangeInt64(7, 0, 5), ShouldEqual, 7)
|
||||||
|
So(sec.Key("TIME").RangeTime(t, minT, midT).String(), ShouldEqual, t.String())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get values into slice", func() {
|
||||||
|
sec := f.Section("array")
|
||||||
|
So(strings.Join(sec.Key("STRINGS").Strings(","), ","), ShouldEqual, "en,zh,de")
|
||||||
|
So(len(sec.Key("STRINGS_404").Strings(",")), ShouldEqual, 0)
|
||||||
|
|
||||||
|
vals1 := sec.Key("FLOAT64S").Float64s(",")
|
||||||
|
float64sEqual(vals1, 1.1, 2.2, 3.3)
|
||||||
|
|
||||||
|
vals2 := sec.Key("INTS").Ints(",")
|
||||||
|
intsEqual(vals2, 1, 2, 3)
|
||||||
|
|
||||||
|
vals3 := sec.Key("INTS").Int64s(",")
|
||||||
|
int64sEqual(vals3, 1, 2, 3)
|
||||||
|
|
||||||
|
vals4 := sec.Key("UINTS").Uints(",")
|
||||||
|
uintsEqual(vals4, 1, 2, 3)
|
||||||
|
|
||||||
|
vals5 := sec.Key("UINTS").Uint64s(",")
|
||||||
|
uint64sEqual(vals5, 1, 2, 3)
|
||||||
|
|
||||||
|
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
vals6 := sec.Key("TIMES").Times(",")
|
||||||
|
timesEqual(vals6, t, t, t)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Test string slice escapes", func() {
|
||||||
|
sec := f.Section("string escapes")
|
||||||
|
So(sec.Key("key1").Strings(","), ShouldResemble, []string{"value1", "value2", "value3"})
|
||||||
|
So(sec.Key("key2").Strings(","), ShouldResemble, []string{"value1, value2"})
|
||||||
|
So(sec.Key("key3").Strings(","), ShouldResemble, []string{`val\ue1`, "value2"})
|
||||||
|
So(sec.Key("key4").Strings(","), ShouldResemble, []string{`value1\`, `value\\2`})
|
||||||
|
So(sec.Key("key5").Strings(",,"), ShouldResemble, []string{"value1,, value2"})
|
||||||
|
So(sec.Key("key6").Strings(" "), ShouldResemble, []string{"aaa", "bbb and space", "ccc"})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get valid values into slice", func() {
|
||||||
|
sec := f.Section("array")
|
||||||
|
vals1 := sec.Key("FLOAT64S").ValidFloat64s(",")
|
||||||
|
float64sEqual(vals1, 1.1, 2.2, 3.3)
|
||||||
|
|
||||||
|
vals2 := sec.Key("INTS").ValidInts(",")
|
||||||
|
intsEqual(vals2, 1, 2, 3)
|
||||||
|
|
||||||
|
vals3 := sec.Key("INTS").ValidInt64s(",")
|
||||||
|
int64sEqual(vals3, 1, 2, 3)
|
||||||
|
|
||||||
|
vals4 := sec.Key("UINTS").ValidUints(",")
|
||||||
|
uintsEqual(vals4, 1, 2, 3)
|
||||||
|
|
||||||
|
vals5 := sec.Key("UINTS").ValidUint64s(",")
|
||||||
|
uint64sEqual(vals5, 1, 2, 3)
|
||||||
|
|
||||||
|
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
vals6 := sec.Key("TIMES").ValidTimes(",")
|
||||||
|
timesEqual(vals6, t, t, t)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get values one type into slice of another type", func() {
|
||||||
|
sec := f.Section("array")
|
||||||
|
vals1 := sec.Key("STRINGS").ValidFloat64s(",")
|
||||||
|
So(vals1, ShouldBeEmpty)
|
||||||
|
|
||||||
|
vals2 := sec.Key("STRINGS").ValidInts(",")
|
||||||
|
So(vals2, ShouldBeEmpty)
|
||||||
|
|
||||||
|
vals3 := sec.Key("STRINGS").ValidInt64s(",")
|
||||||
|
So(vals3, ShouldBeEmpty)
|
||||||
|
|
||||||
|
vals4 := sec.Key("STRINGS").ValidUints(",")
|
||||||
|
So(vals4, ShouldBeEmpty)
|
||||||
|
|
||||||
|
vals5 := sec.Key("STRINGS").ValidUint64s(",")
|
||||||
|
So(vals5, ShouldBeEmpty)
|
||||||
|
|
||||||
|
vals6 := sec.Key("STRINGS").ValidTimes(",")
|
||||||
|
So(vals6, ShouldBeEmpty)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get valid values into slice without errors", func() {
|
||||||
|
sec := f.Section("array")
|
||||||
|
vals1, err := sec.Key("FLOAT64S").StrictFloat64s(",")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
float64sEqual(vals1, 1.1, 2.2, 3.3)
|
||||||
|
|
||||||
|
vals2, err := sec.Key("INTS").StrictInts(",")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
intsEqual(vals2, 1, 2, 3)
|
||||||
|
|
||||||
|
vals3, err := sec.Key("INTS").StrictInt64s(",")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
int64sEqual(vals3, 1, 2, 3)
|
||||||
|
|
||||||
|
vals4, err := sec.Key("UINTS").StrictUints(",")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
uintsEqual(vals4, 1, 2, 3)
|
||||||
|
|
||||||
|
vals5, err := sec.Key("UINTS").StrictUint64s(",")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
uint64sEqual(vals5, 1, 2, 3)
|
||||||
|
|
||||||
|
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
vals6, err := sec.Key("TIMES").StrictTimes(",")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
timesEqual(vals6, t, t, t)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Get invalid values into slice", func() {
|
||||||
|
sec := f.Section("array")
|
||||||
|
vals1, err := sec.Key("STRINGS").StrictFloat64s(",")
|
||||||
|
So(vals1, ShouldBeEmpty)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
vals2, err := sec.Key("STRINGS").StrictInts(",")
|
||||||
|
So(vals2, ShouldBeEmpty)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
vals3, err := sec.Key("STRINGS").StrictInt64s(",")
|
||||||
|
So(vals3, ShouldBeEmpty)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
vals4, err := sec.Key("STRINGS").StrictUints(",")
|
||||||
|
So(vals4, ShouldBeEmpty)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
vals5, err := sec.Key("STRINGS").StrictUint64s(",")
|
||||||
|
So(vals5, ShouldBeEmpty)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
vals6, err := sec.Key("STRINGS").StrictTimes(",")
|
||||||
|
So(vals6, ShouldBeEmpty)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKey_StringsWithShadows(t *testing.T) {
|
||||||
|
Convey("Get strings of shadows of a key", t, func() {
|
||||||
|
f, err := ini.ShadowLoad([]byte(""))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err := f.Section("").NewKey("NUMS", "1,2")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
k, err = f.Section("").NewKey("NUMS", "4,5,6")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(k.StringsWithShadows(","), ShouldResemble, []string{"1", "2", "4", "5", "6"})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKey_SetValue(t *testing.T) {
|
||||||
|
Convey("Set value of key", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err := f.Section("").NewKey("NAME", "ini")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
So(k.Value(), ShouldEqual, "ini")
|
||||||
|
|
||||||
|
k.SetValue("ini.v1")
|
||||||
|
So(k.Value(), ShouldEqual, "ini.v1")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKey_NestedValues(t *testing.T) {
|
||||||
|
Convey("Read and write nested values", t, func() {
|
||||||
|
f, err := ini.LoadSources(ini.LoadOptions{
|
||||||
|
AllowNestedValues: true,
|
||||||
|
}, []byte(`
|
||||||
|
aws_access_key_id = foo
|
||||||
|
aws_secret_access_key = bar
|
||||||
|
region = us-west-2
|
||||||
|
s3 =
|
||||||
|
max_concurrent_requests=10
|
||||||
|
max_queue_size=1000`))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(f.Section("").Key("s3").NestedValues(), ShouldResemble, []string{"max_concurrent_requests=10", "max_queue_size=1000"})
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err = f.WriteTo(&buf)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(buf.String(), ShouldEqual, `aws_access_key_id = foo
|
||||||
|
aws_secret_access_key = bar
|
||||||
|
region = us-west-2
|
||||||
|
s3 =
|
||||||
|
max_concurrent_requests=10
|
||||||
|
max_queue_size=1000
|
||||||
|
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRecursiveValues(t *testing.T) {
|
||||||
|
Convey("Recursive values should not reflect on same key", t, func() {
|
||||||
|
f, err := ini.Load([]byte(`
|
||||||
|
NAME = ini
|
||||||
|
[package]
|
||||||
|
NAME = %(NAME)s`))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
So(f.Section("package").Key("NAME").String(), ShouldEqual, "ini")
|
||||||
|
})
|
||||||
|
}
|
||||||
494
vendor/github.com/go-ini/ini/parser.go
generated
vendored
Normal file
494
vendor/github.com/go-ini/ini/parser.go
generated
vendored
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
// Copyright 2015 Unknwon
|
||||||
|
//
|
||||||
|
// 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 ini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pythonMultiline = regexp.MustCompile("^(\\s+)([^\n]+)")
|
||||||
|
|
||||||
|
type tokenType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
_TOKEN_INVALID tokenType = iota
|
||||||
|
_TOKEN_COMMENT
|
||||||
|
_TOKEN_SECTION
|
||||||
|
_TOKEN_KEY
|
||||||
|
)
|
||||||
|
|
||||||
|
type parser struct {
|
||||||
|
buf *bufio.Reader
|
||||||
|
isEOF bool
|
||||||
|
count int
|
||||||
|
comment *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newParser(r io.Reader) *parser {
|
||||||
|
return &parser{
|
||||||
|
buf: bufio.NewReader(r),
|
||||||
|
count: 1,
|
||||||
|
comment: &bytes.Buffer{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format.
|
||||||
|
// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
|
||||||
|
func (p *parser) BOM() error {
|
||||||
|
mask, err := p.buf.Peek(2)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return err
|
||||||
|
} else if len(mask) < 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case mask[0] == 254 && mask[1] == 255:
|
||||||
|
fallthrough
|
||||||
|
case mask[0] == 255 && mask[1] == 254:
|
||||||
|
p.buf.Read(mask)
|
||||||
|
case mask[0] == 239 && mask[1] == 187:
|
||||||
|
mask, err := p.buf.Peek(3)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return err
|
||||||
|
} else if len(mask) < 3 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if mask[2] == 191 {
|
||||||
|
p.buf.Read(mask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) readUntil(delim byte) ([]byte, error) {
|
||||||
|
data, err := p.buf.ReadBytes(delim)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
p.isEOF = true
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanComment(in []byte) ([]byte, bool) {
|
||||||
|
i := bytes.IndexAny(in, "#;")
|
||||||
|
if i == -1 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return in[i:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func readKeyName(in []byte) (string, int, error) {
|
||||||
|
line := string(in)
|
||||||
|
|
||||||
|
// Check if key name surrounded by quotes.
|
||||||
|
var keyQuote string
|
||||||
|
if line[0] == '"' {
|
||||||
|
if len(line) > 6 && string(line[0:3]) == `"""` {
|
||||||
|
keyQuote = `"""`
|
||||||
|
} else {
|
||||||
|
keyQuote = `"`
|
||||||
|
}
|
||||||
|
} else if line[0] == '`' {
|
||||||
|
keyQuote = "`"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get out key name
|
||||||
|
endIdx := -1
|
||||||
|
if len(keyQuote) > 0 {
|
||||||
|
startIdx := len(keyQuote)
|
||||||
|
// FIXME: fail case -> """"""name"""=value
|
||||||
|
pos := strings.Index(line[startIdx:], keyQuote)
|
||||||
|
if pos == -1 {
|
||||||
|
return "", -1, fmt.Errorf("missing closing key quote: %s", line)
|
||||||
|
}
|
||||||
|
pos += startIdx
|
||||||
|
|
||||||
|
// Find key-value delimiter
|
||||||
|
i := strings.IndexAny(line[pos+startIdx:], "=:")
|
||||||
|
if i < 0 {
|
||||||
|
return "", -1, ErrDelimiterNotFound{line}
|
||||||
|
}
|
||||||
|
endIdx = pos + i
|
||||||
|
return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
endIdx = strings.IndexAny(line, "=:")
|
||||||
|
if endIdx < 0 {
|
||||||
|
return "", -1, ErrDelimiterNotFound{line}
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
|
||||||
|
for {
|
||||||
|
data, err := p.readUntil('\n')
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
next := string(data)
|
||||||
|
|
||||||
|
pos := strings.LastIndex(next, valQuote)
|
||||||
|
if pos > -1 {
|
||||||
|
val += next[:pos]
|
||||||
|
|
||||||
|
comment, has := cleanComment([]byte(next[pos:]))
|
||||||
|
if has {
|
||||||
|
p.comment.Write(bytes.TrimSpace(comment))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
val += next
|
||||||
|
if p.isEOF {
|
||||||
|
return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) readContinuationLines(val string) (string, error) {
|
||||||
|
for {
|
||||||
|
data, err := p.readUntil('\n')
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
next := strings.TrimSpace(string(data))
|
||||||
|
|
||||||
|
if len(next) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
val += next
|
||||||
|
if val[len(val)-1] != '\\' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
val = val[:len(val)-1]
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasSurroundedQuote check if and only if the first and last characters
|
||||||
|
// are quotes \" or \'.
|
||||||
|
// It returns false if any other parts also contain same kind of quotes.
|
||||||
|
func hasSurroundedQuote(in string, quote byte) bool {
|
||||||
|
return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
|
||||||
|
strings.IndexByte(in[1:], quote) == len(in)-2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) readValue(in []byte,
|
||||||
|
parserBufferSize int,
|
||||||
|
ignoreContinuation, ignoreInlineComment, unescapeValueDoubleQuotes, unescapeValueCommentSymbols, allowPythonMultilines, spaceBeforeInlineComment bool) (string, error) {
|
||||||
|
|
||||||
|
line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
|
||||||
|
if len(line) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var valQuote string
|
||||||
|
if len(line) > 3 && string(line[0:3]) == `"""` {
|
||||||
|
valQuote = `"""`
|
||||||
|
} else if line[0] == '`' {
|
||||||
|
valQuote = "`"
|
||||||
|
} else if unescapeValueDoubleQuotes && line[0] == '"' {
|
||||||
|
valQuote = `"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(valQuote) > 0 {
|
||||||
|
startIdx := len(valQuote)
|
||||||
|
pos := strings.LastIndex(line[startIdx:], valQuote)
|
||||||
|
// Check for multi-line value
|
||||||
|
if pos == -1 {
|
||||||
|
return p.readMultilines(line, line[startIdx:], valQuote)
|
||||||
|
}
|
||||||
|
|
||||||
|
if unescapeValueDoubleQuotes && valQuote == `"` {
|
||||||
|
return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil
|
||||||
|
}
|
||||||
|
return line[startIdx : pos+startIdx], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lastChar := line[len(line)-1]
|
||||||
|
// Won't be able to reach here if value only contains whitespace
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
trimmedLastChar := line[len(line)-1]
|
||||||
|
|
||||||
|
// Check continuation lines when desired
|
||||||
|
if !ignoreContinuation && trimmedLastChar == '\\' {
|
||||||
|
return p.readContinuationLines(line[:len(line)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if ignore inline comment
|
||||||
|
if !ignoreInlineComment {
|
||||||
|
var i int
|
||||||
|
if spaceBeforeInlineComment {
|
||||||
|
i = strings.Index(line, " #")
|
||||||
|
if i == -1 {
|
||||||
|
i = strings.Index(line, " ;")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
i = strings.IndexAny(line, "#;")
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > -1 {
|
||||||
|
p.comment.WriteString(line[i:])
|
||||||
|
line = strings.TrimSpace(line[:i])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim single and double quotes
|
||||||
|
if hasSurroundedQuote(line, '\'') ||
|
||||||
|
hasSurroundedQuote(line, '"') {
|
||||||
|
line = line[1 : len(line)-1]
|
||||||
|
} else if len(valQuote) == 0 && unescapeValueCommentSymbols {
|
||||||
|
if strings.Contains(line, `\;`) {
|
||||||
|
line = strings.Replace(line, `\;`, ";", -1)
|
||||||
|
}
|
||||||
|
if strings.Contains(line, `\#`) {
|
||||||
|
line = strings.Replace(line, `\#`, "#", -1)
|
||||||
|
}
|
||||||
|
} else if allowPythonMultilines && lastChar == '\n' {
|
||||||
|
parserBufferPeekResult, _ := p.buf.Peek(parserBufferSize)
|
||||||
|
peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
|
||||||
|
|
||||||
|
identSize := -1
|
||||||
|
val := line
|
||||||
|
|
||||||
|
for {
|
||||||
|
peekData, peekErr := peekBuffer.ReadBytes('\n')
|
||||||
|
if peekErr != nil {
|
||||||
|
if peekErr == io.EOF {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
return "", peekErr
|
||||||
|
}
|
||||||
|
|
||||||
|
peekMatches := pythonMultiline.FindStringSubmatch(string(peekData))
|
||||||
|
if len(peekMatches) != 3 {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIdentSize := len(peekMatches[1])
|
||||||
|
// NOTE: Return if not a python-ini multi-line value.
|
||||||
|
if currentIdentSize < 0 {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
identSize = currentIdentSize
|
||||||
|
|
||||||
|
// NOTE: Just advance the parser reader (buffer) in-sync with the peek buffer.
|
||||||
|
_, err := p.readUntil('\n')
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
val += fmt.Sprintf("\n%s", peekMatches[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: If it was a Python multi-line value,
|
||||||
|
// return the appended value.
|
||||||
|
if identSize > 0 {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return line, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse parses data through an io.Reader.
|
||||||
|
func (f *File) parse(reader io.Reader) (err error) {
|
||||||
|
p := newParser(reader)
|
||||||
|
if err = p.BOM(); err != nil {
|
||||||
|
return fmt.Errorf("BOM: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore error because default section name is never empty string.
|
||||||
|
name := DEFAULT_SECTION
|
||||||
|
if f.options.Insensitive {
|
||||||
|
name = strings.ToLower(DEFAULT_SECTION)
|
||||||
|
}
|
||||||
|
section, _ := f.NewSection(name)
|
||||||
|
|
||||||
|
// This "last" is not strictly equivalent to "previous one" if current key is not the first nested key
|
||||||
|
var isLastValueEmpty bool
|
||||||
|
var lastRegularKey *Key
|
||||||
|
|
||||||
|
var line []byte
|
||||||
|
var inUnparseableSection bool
|
||||||
|
|
||||||
|
// NOTE: Iterate and increase `currentPeekSize` until
|
||||||
|
// the size of the parser buffer is found.
|
||||||
|
// TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`.
|
||||||
|
parserBufferSize := 0
|
||||||
|
// NOTE: Peek 1kb at a time.
|
||||||
|
currentPeekSize := 1024
|
||||||
|
|
||||||
|
if f.options.AllowPythonMultilineValues {
|
||||||
|
for {
|
||||||
|
peekBytes, _ := p.buf.Peek(currentPeekSize)
|
||||||
|
peekBytesLength := len(peekBytes)
|
||||||
|
|
||||||
|
if parserBufferSize >= peekBytesLength {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPeekSize *= 2
|
||||||
|
parserBufferSize = peekBytesLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for !p.isEOF {
|
||||||
|
line, err = p.readUntil('\n')
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.options.AllowNestedValues &&
|
||||||
|
isLastValueEmpty && len(line) > 0 {
|
||||||
|
if line[0] == ' ' || line[0] == '\t' {
|
||||||
|
lastRegularKey.addNestedValue(string(bytes.TrimSpace(line)))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
|
||||||
|
if len(line) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comments
|
||||||
|
if line[0] == '#' || line[0] == ';' {
|
||||||
|
// Note: we do not care ending line break,
|
||||||
|
// it is needed for adding second line,
|
||||||
|
// so just clean it once at the end when set to value.
|
||||||
|
p.comment.Write(line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section
|
||||||
|
if line[0] == '[' {
|
||||||
|
// Read to the next ']' (TODO: support quoted strings)
|
||||||
|
closeIdx := bytes.LastIndexByte(line, ']')
|
||||||
|
if closeIdx == -1 {
|
||||||
|
return fmt.Errorf("unclosed section: %s", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
name := string(line[1:closeIdx])
|
||||||
|
section, err = f.NewSection(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
comment, has := cleanComment(line[closeIdx+1:])
|
||||||
|
if has {
|
||||||
|
p.comment.Write(comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
section.Comment = strings.TrimSpace(p.comment.String())
|
||||||
|
|
||||||
|
// Reset aotu-counter and comments
|
||||||
|
p.comment.Reset()
|
||||||
|
p.count = 1
|
||||||
|
|
||||||
|
inUnparseableSection = false
|
||||||
|
for i := range f.options.UnparseableSections {
|
||||||
|
if f.options.UnparseableSections[i] == name ||
|
||||||
|
(f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) {
|
||||||
|
inUnparseableSection = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if inUnparseableSection {
|
||||||
|
section.isRawSection = true
|
||||||
|
section.rawBody += string(line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
kname, offset, err := readKeyName(line)
|
||||||
|
if err != nil {
|
||||||
|
// Treat as boolean key when desired, and whole line is key name.
|
||||||
|
if IsErrDelimiterNotFound(err) {
|
||||||
|
switch {
|
||||||
|
case f.options.AllowBooleanKeys:
|
||||||
|
kname, err := p.readValue(line,
|
||||||
|
parserBufferSize,
|
||||||
|
f.options.IgnoreContinuation,
|
||||||
|
f.options.IgnoreInlineComment,
|
||||||
|
f.options.UnescapeValueDoubleQuotes,
|
||||||
|
f.options.UnescapeValueCommentSymbols,
|
||||||
|
f.options.AllowPythonMultilineValues,
|
||||||
|
f.options.SpaceBeforeInlineComment)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key, err := section.NewBooleanKey(kname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key.Comment = strings.TrimSpace(p.comment.String())
|
||||||
|
p.comment.Reset()
|
||||||
|
continue
|
||||||
|
|
||||||
|
case f.options.SkipUnrecognizableLines:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto increment.
|
||||||
|
isAutoIncr := false
|
||||||
|
if kname == "-" {
|
||||||
|
isAutoIncr = true
|
||||||
|
kname = "#" + strconv.Itoa(p.count)
|
||||||
|
p.count++
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := p.readValue(line[offset:],
|
||||||
|
parserBufferSize,
|
||||||
|
f.options.IgnoreContinuation,
|
||||||
|
f.options.IgnoreInlineComment,
|
||||||
|
f.options.UnescapeValueDoubleQuotes,
|
||||||
|
f.options.UnescapeValueCommentSymbols,
|
||||||
|
f.options.AllowPythonMultilineValues,
|
||||||
|
f.options.SpaceBeforeInlineComment)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
isLastValueEmpty = len(value) == 0
|
||||||
|
|
||||||
|
key, err := section.NewKey(kname, value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key.isAutoIncrement = isAutoIncr
|
||||||
|
key.Comment = strings.TrimSpace(p.comment.String())
|
||||||
|
p.comment.Reset()
|
||||||
|
lastRegularKey = key
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
77
vendor/github.com/go-ini/ini/parser_test.go
generated
vendored
Normal file
77
vendor/github.com/go-ini/ini/parser_test.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2016 Unknwon
|
||||||
|
//
|
||||||
|
// 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 ini_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBOM(t *testing.T) {
|
||||||
|
Convey("Test handling BOM", t, func() {
|
||||||
|
Convey("UTF-8-BOM", func() {
|
||||||
|
f, err := ini.Load("testdata/UTF-8-BOM.ini")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(f.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("UTF-16-LE-BOM", func() {
|
||||||
|
f, err := ini.Load("testdata/UTF-16-LE-BOM.ini")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("UTF-16-BE-BOM", func() {
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadLoad(t *testing.T) {
|
||||||
|
Convey("Load with bad data", t, func() {
|
||||||
|
Convey("Bad section name", func() {
|
||||||
|
_, err := ini.Load([]byte("[]"))
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
_, err = ini.Load([]byte("["))
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Bad keys", func() {
|
||||||
|
_, err := ini.Load([]byte(`"""name`))
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
_, err = ini.Load([]byte(`"""name"""`))
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
_, err = ini.Load([]byte(`""=1`))
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
_, err = ini.Load([]byte(`=`))
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
_, err = ini.Load([]byte(`name`))
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Bad values", func() {
|
||||||
|
_, err := ini.Load([]byte(`name="""Unknwon`))
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
258
vendor/github.com/go-ini/ini/section.go
generated
vendored
Normal file
258
vendor/github.com/go-ini/ini/section.go
generated
vendored
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
// Copyright 2014 Unknwon
|
||||||
|
//
|
||||||
|
// 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 ini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Section represents a config section.
|
||||||
|
type Section struct {
|
||||||
|
f *File
|
||||||
|
Comment string
|
||||||
|
name string
|
||||||
|
keys map[string]*Key
|
||||||
|
keyList []string
|
||||||
|
keysHash map[string]string
|
||||||
|
|
||||||
|
isRawSection bool
|
||||||
|
rawBody string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSection(f *File, name string) *Section {
|
||||||
|
return &Section{
|
||||||
|
f: f,
|
||||||
|
name: name,
|
||||||
|
keys: make(map[string]*Key),
|
||||||
|
keyList: make([]string, 0, 10),
|
||||||
|
keysHash: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns name of Section.
|
||||||
|
func (s *Section) Name() string {
|
||||||
|
return s.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body returns rawBody of Section if the section was marked as unparseable.
|
||||||
|
// It still follows the other rules of the INI format surrounding leading/trailing whitespace.
|
||||||
|
func (s *Section) Body() string {
|
||||||
|
return strings.TrimSpace(s.rawBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBody updates body content only if section is raw.
|
||||||
|
func (s *Section) SetBody(body string) {
|
||||||
|
if !s.isRawSection {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.rawBody = body
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKey creates a new key to given section.
|
||||||
|
func (s *Section) NewKey(name, val string) (*Key, error) {
|
||||||
|
if len(name) == 0 {
|
||||||
|
return nil, errors.New("error creating new key: empty key name")
|
||||||
|
} else if s.f.options.Insensitive {
|
||||||
|
name = strings.ToLower(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.f.BlockMode {
|
||||||
|
s.f.lock.Lock()
|
||||||
|
defer s.f.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if inSlice(name, s.keyList) {
|
||||||
|
if s.f.options.AllowShadows {
|
||||||
|
if err := s.keys[name].addShadow(val); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.keys[name].value = val
|
||||||
|
s.keysHash[name] = val
|
||||||
|
}
|
||||||
|
return s.keys[name], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.keyList = append(s.keyList, name)
|
||||||
|
s.keys[name] = newKey(s, name, val)
|
||||||
|
s.keysHash[name] = val
|
||||||
|
return s.keys[name], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBooleanKey creates a new boolean type key to given section.
|
||||||
|
func (s *Section) NewBooleanKey(name string) (*Key, error) {
|
||||||
|
key, err := s.NewKey(name, "true")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
key.isBooleanType = true
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKey returns key in section by given name.
|
||||||
|
func (s *Section) GetKey(name string) (*Key, error) {
|
||||||
|
// FIXME: change to section level lock?
|
||||||
|
if s.f.BlockMode {
|
||||||
|
s.f.lock.RLock()
|
||||||
|
}
|
||||||
|
if s.f.options.Insensitive {
|
||||||
|
name = strings.ToLower(name)
|
||||||
|
}
|
||||||
|
key := s.keys[name]
|
||||||
|
if s.f.BlockMode {
|
||||||
|
s.f.lock.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if key == nil {
|
||||||
|
// Check if it is a child-section.
|
||||||
|
sname := s.name
|
||||||
|
for {
|
||||||
|
if i := strings.LastIndex(sname, "."); i > -1 {
|
||||||
|
sname = sname[:i]
|
||||||
|
sec, err := s.f.GetSection(sname)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return sec.GetKey(name)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name)
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasKey returns true if section contains a key with given name.
|
||||||
|
func (s *Section) HasKey(name string) bool {
|
||||||
|
key, _ := s.GetKey(name)
|
||||||
|
return key != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Haskey is a backwards-compatible name for HasKey.
|
||||||
|
// TODO: delete me in v2
|
||||||
|
func (s *Section) Haskey(name string) bool {
|
||||||
|
return s.HasKey(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasValue returns true if section contains given raw value.
|
||||||
|
func (s *Section) HasValue(value string) bool {
|
||||||
|
if s.f.BlockMode {
|
||||||
|
s.f.lock.RLock()
|
||||||
|
defer s.f.lock.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range s.keys {
|
||||||
|
if value == k.value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key assumes named Key exists in section and returns a zero-value when not.
|
||||||
|
func (s *Section) Key(name string) *Key {
|
||||||
|
key, err := s.GetKey(name)
|
||||||
|
if err != nil {
|
||||||
|
// It's OK here because the only possible error is empty key name,
|
||||||
|
// but if it's empty, this piece of code won't be executed.
|
||||||
|
key, _ = s.NewKey(name, "")
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys returns list of keys of section.
|
||||||
|
func (s *Section) Keys() []*Key {
|
||||||
|
keys := make([]*Key, len(s.keyList))
|
||||||
|
for i := range s.keyList {
|
||||||
|
keys[i] = s.Key(s.keyList[i])
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParentKeys returns list of keys of parent section.
|
||||||
|
func (s *Section) ParentKeys() []*Key {
|
||||||
|
var parentKeys []*Key
|
||||||
|
sname := s.name
|
||||||
|
for {
|
||||||
|
if i := strings.LastIndex(sname, "."); i > -1 {
|
||||||
|
sname = sname[:i]
|
||||||
|
sec, err := s.f.GetSection(sname)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parentKeys = append(parentKeys, sec.Keys()...)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return parentKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyStrings returns list of key names of section.
|
||||||
|
func (s *Section) KeyStrings() []string {
|
||||||
|
list := make([]string, len(s.keyList))
|
||||||
|
copy(list, s.keyList)
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeysHash returns keys hash consisting of names and values.
|
||||||
|
func (s *Section) KeysHash() map[string]string {
|
||||||
|
if s.f.BlockMode {
|
||||||
|
s.f.lock.RLock()
|
||||||
|
defer s.f.lock.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := map[string]string{}
|
||||||
|
for key, value := range s.keysHash {
|
||||||
|
hash[key] = value
|
||||||
|
}
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteKey deletes a key from section.
|
||||||
|
func (s *Section) DeleteKey(name string) {
|
||||||
|
if s.f.BlockMode {
|
||||||
|
s.f.lock.Lock()
|
||||||
|
defer s.f.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, k := range s.keyList {
|
||||||
|
if k == name {
|
||||||
|
s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
|
||||||
|
delete(s.keys, name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChildSections returns a list of child sections of current section.
|
||||||
|
// For example, "[parent.child1]" and "[parent.child12]" are child sections
|
||||||
|
// of section "[parent]".
|
||||||
|
func (s *Section) ChildSections() []*Section {
|
||||||
|
prefix := s.name + "."
|
||||||
|
children := make([]*Section, 0, 3)
|
||||||
|
for _, name := range s.f.sectionList {
|
||||||
|
if strings.HasPrefix(name, prefix) {
|
||||||
|
children = append(children, s.f.sections[name])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
315
vendor/github.com/go-ini/ini/section_test.go
generated
vendored
Normal file
315
vendor/github.com/go-ini/ini/section_test.go
generated
vendored
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
// Copyright 2014 Unknwon
|
||||||
|
//
|
||||||
|
// 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 ini_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSection_SetBody(t *testing.T) {
|
||||||
|
Convey("Set body of raw section", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000
|
||||||
|
111111111111111111100000000000111000000000`)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(sec, ShouldNotBeNil)
|
||||||
|
So(sec.Body(), ShouldEqual, `1111111111111111111000000000000000001110000
|
||||||
|
111111111111111111100000000000111000000000`)
|
||||||
|
|
||||||
|
sec.SetBody("1111111111111111111000000000000000001110000")
|
||||||
|
So(sec.Body(), ShouldEqual, `1111111111111111111000000000000000001110000`)
|
||||||
|
|
||||||
|
Convey("Set for non-raw section", func() {
|
||||||
|
sec, err := f.NewSection("author")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(sec, ShouldNotBeNil)
|
||||||
|
So(sec.Body(), ShouldBeEmpty)
|
||||||
|
|
||||||
|
sec.SetBody("1111111111111111111000000000000000001110000")
|
||||||
|
So(sec.Body(), ShouldBeEmpty)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSection_NewKey(t *testing.T) {
|
||||||
|
Convey("Create a new key", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err := f.Section("").NewKey("NAME", "ini")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
So(k.Name(), ShouldEqual, "NAME")
|
||||||
|
So(k.Value(), ShouldEqual, "ini")
|
||||||
|
|
||||||
|
Convey("With duplicated name", func() {
|
||||||
|
k, err := f.Section("").NewKey("NAME", "ini.v1")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
|
||||||
|
// Overwrite previous existed key
|
||||||
|
So(k.Value(), ShouldEqual, "ini.v1")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("With empty string", func() {
|
||||||
|
_, err := f.Section("").NewKey("", "")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Create keys with same name and allow shadow", t, func() {
|
||||||
|
f, err := ini.ShadowLoad([]byte(""))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err := f.Section("").NewKey("NAME", "ini")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
k, err = f.Section("").NewKey("NAME", "ini.v1")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(k.ValueWithShadows(), ShouldResemble, []string{"ini", "ini.v1"})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSection_NewBooleanKey(t *testing.T) {
|
||||||
|
Convey("Create a new boolean key", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err := f.Section("").NewBooleanKey("start-ssh-server")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
So(k.Name(), ShouldEqual, "start-ssh-server")
|
||||||
|
So(k.Value(), ShouldEqual, "true")
|
||||||
|
|
||||||
|
Convey("With empty string", func() {
|
||||||
|
_, err := f.Section("").NewBooleanKey("")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSection_GetKey(t *testing.T) {
|
||||||
|
Convey("Get a key", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err := f.Section("").NewKey("NAME", "ini")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err = f.Section("").GetKey("NAME")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
So(k.Name(), ShouldEqual, "NAME")
|
||||||
|
So(k.Value(), ShouldEqual, "ini")
|
||||||
|
|
||||||
|
Convey("Key not exists", func() {
|
||||||
|
_, err := f.Section("").GetKey("404")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Key exists in parent section", func() {
|
||||||
|
k, err := f.Section("parent").NewKey("AGE", "18")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err = f.Section("parent.child.son").GetKey("AGE")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
So(k.Value(), ShouldEqual, "18")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSection_HasKey(t *testing.T) {
|
||||||
|
Convey("Check if a key exists", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err := f.Section("").NewKey("NAME", "ini")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(f.Section("").HasKey("NAME"), ShouldBeTrue)
|
||||||
|
So(f.Section("").Haskey("NAME"), ShouldBeTrue)
|
||||||
|
So(f.Section("").HasKey("404"), ShouldBeFalse)
|
||||||
|
So(f.Section("").Haskey("404"), ShouldBeFalse)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSection_HasValue(t *testing.T) {
|
||||||
|
Convey("Check if contains a value in any key", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err := f.Section("").NewKey("NAME", "ini")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(f.Section("").HasValue("ini"), ShouldBeTrue)
|
||||||
|
So(f.Section("").HasValue("404"), ShouldBeFalse)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSection_Key(t *testing.T) {
|
||||||
|
Convey("Get a key", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err := f.Section("").NewKey("NAME", "ini")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k = f.Section("").Key("NAME")
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
So(k.Name(), ShouldEqual, "NAME")
|
||||||
|
So(k.Value(), ShouldEqual, "ini")
|
||||||
|
|
||||||
|
Convey("Key not exists", func() {
|
||||||
|
k := f.Section("").Key("404")
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
So(k.Name(), ShouldEqual, "404")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Key exists in parent section", func() {
|
||||||
|
k, err := f.Section("parent").NewKey("AGE", "18")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k = f.Section("parent.child.son").Key("AGE")
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
So(k.Value(), ShouldEqual, "18")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSection_Keys(t *testing.T) {
|
||||||
|
Convey("Get all keys in a section", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err := f.Section("").NewKey("NAME", "ini")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
k, err = f.Section("").NewKey("VERSION", "v1")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
k, err = f.Section("").NewKey("IMPORT_PATH", "gopkg.in/ini.v1")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
|
||||||
|
keys := f.Section("").Keys()
|
||||||
|
names := []string{"NAME", "VERSION", "IMPORT_PATH"}
|
||||||
|
So(len(keys), ShouldEqual, len(names))
|
||||||
|
for i, name := range names {
|
||||||
|
So(keys[i].Name(), ShouldEqual, name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSection_ParentKeys(t *testing.T) {
|
||||||
|
Convey("Get all keys of parent sections", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err := f.Section("package").NewKey("NAME", "ini")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
k, err = f.Section("package").NewKey("VERSION", "v1")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
k, err = f.Section("package").NewKey("IMPORT_PATH", "gopkg.in/ini.v1")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
|
||||||
|
keys := f.Section("package.sub.sub2").ParentKeys()
|
||||||
|
names := []string{"NAME", "VERSION", "IMPORT_PATH"}
|
||||||
|
So(len(keys), ShouldEqual, len(names))
|
||||||
|
for i, name := range names {
|
||||||
|
So(keys[i].Name(), ShouldEqual, name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSection_KeyStrings(t *testing.T) {
|
||||||
|
Convey("Get all key names in a section", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err := f.Section("").NewKey("NAME", "ini")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
k, err = f.Section("").NewKey("VERSION", "v1")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
k, err = f.Section("").NewKey("IMPORT_PATH", "gopkg.in/ini.v1")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(f.Section("").KeyStrings(), ShouldResemble, []string{"NAME", "VERSION", "IMPORT_PATH"})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSection_KeyHash(t *testing.T) {
|
||||||
|
Convey("Get clone of key hash", t, func() {
|
||||||
|
f, err := ini.Load([]byte(`
|
||||||
|
key = one
|
||||||
|
[log]
|
||||||
|
name = app
|
||||||
|
file = a.log
|
||||||
|
`), []byte(`
|
||||||
|
key = two
|
||||||
|
[log]
|
||||||
|
name = app2
|
||||||
|
file = b.log
|
||||||
|
`))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(f.Section("").Key("key").String(), ShouldEqual, "two")
|
||||||
|
|
||||||
|
hash := f.Section("log").KeysHash()
|
||||||
|
relation := map[string]string{
|
||||||
|
"name": "app2",
|
||||||
|
"file": "b.log",
|
||||||
|
}
|
||||||
|
for k, v := range hash {
|
||||||
|
So(v, ShouldEqual, relation[k])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSection_DeleteKey(t *testing.T) {
|
||||||
|
Convey("Delete a key", t, func() {
|
||||||
|
f := ini.Empty()
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
k, err := f.Section("").NewKey("NAME", "ini")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(k, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(f.Section("").HasKey("NAME"), ShouldBeTrue)
|
||||||
|
f.Section("").DeleteKey("NAME")
|
||||||
|
So(f.Section("").HasKey("NAME"), ShouldBeFalse)
|
||||||
|
})
|
||||||
|
}
|
||||||
512
vendor/github.com/go-ini/ini/struct.go
generated
vendored
Normal file
512
vendor/github.com/go-ini/ini/struct.go
generated
vendored
Normal file
@@ -0,0 +1,512 @@
|
|||||||
|
// Copyright 2014 Unknwon
|
||||||
|
//
|
||||||
|
// 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 ini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NameMapper represents a ini tag name mapper.
|
||||||
|
type NameMapper func(string) string
|
||||||
|
|
||||||
|
// Built-in name getters.
|
||||||
|
var (
|
||||||
|
// AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
|
||||||
|
AllCapsUnderscore NameMapper = func(raw string) string {
|
||||||
|
newstr := make([]rune, 0, len(raw))
|
||||||
|
for i, chr := range raw {
|
||||||
|
if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
|
||||||
|
if i > 0 {
|
||||||
|
newstr = append(newstr, '_')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newstr = append(newstr, unicode.ToUpper(chr))
|
||||||
|
}
|
||||||
|
return string(newstr)
|
||||||
|
}
|
||||||
|
// TitleUnderscore converts to format title_underscore.
|
||||||
|
TitleUnderscore NameMapper = func(raw string) string {
|
||||||
|
newstr := make([]rune, 0, len(raw))
|
||||||
|
for i, chr := range raw {
|
||||||
|
if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
|
||||||
|
if i > 0 {
|
||||||
|
newstr = append(newstr, '_')
|
||||||
|
}
|
||||||
|
chr -= ('A' - 'a')
|
||||||
|
}
|
||||||
|
newstr = append(newstr, chr)
|
||||||
|
}
|
||||||
|
return string(newstr)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Section) parseFieldName(raw, actual string) string {
|
||||||
|
if len(actual) > 0 {
|
||||||
|
return actual
|
||||||
|
}
|
||||||
|
if s.f.NameMapper != nil {
|
||||||
|
return s.f.NameMapper(raw)
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDelim(actual string) string {
|
||||||
|
if len(actual) > 0 {
|
||||||
|
return actual
|
||||||
|
}
|
||||||
|
return ","
|
||||||
|
}
|
||||||
|
|
||||||
|
var reflectTime = reflect.TypeOf(time.Now()).Kind()
|
||||||
|
|
||||||
|
// setSliceWithProperType sets proper values to slice based on its type.
|
||||||
|
func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
|
||||||
|
var strs []string
|
||||||
|
if allowShadow {
|
||||||
|
strs = key.StringsWithShadows(delim)
|
||||||
|
} else {
|
||||||
|
strs = key.Strings(delim)
|
||||||
|
}
|
||||||
|
|
||||||
|
numVals := len(strs)
|
||||||
|
if numVals == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var vals interface{}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
sliceOf := field.Type().Elem().Kind()
|
||||||
|
switch sliceOf {
|
||||||
|
case reflect.String:
|
||||||
|
vals = strs
|
||||||
|
case reflect.Int:
|
||||||
|
vals, err = key.parseInts(strs, true, false)
|
||||||
|
case reflect.Int64:
|
||||||
|
vals, err = key.parseInt64s(strs, true, false)
|
||||||
|
case reflect.Uint:
|
||||||
|
vals, err = key.parseUints(strs, true, false)
|
||||||
|
case reflect.Uint64:
|
||||||
|
vals, err = key.parseUint64s(strs, true, false)
|
||||||
|
case reflect.Float64:
|
||||||
|
vals, err = key.parseFloat64s(strs, true, false)
|
||||||
|
case reflectTime:
|
||||||
|
vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported type '[]%s'", sliceOf)
|
||||||
|
}
|
||||||
|
if err != nil && isStrict {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := reflect.MakeSlice(field.Type(), numVals, numVals)
|
||||||
|
for i := 0; i < numVals; i++ {
|
||||||
|
switch sliceOf {
|
||||||
|
case reflect.String:
|
||||||
|
slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
|
||||||
|
case reflect.Int:
|
||||||
|
slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
|
||||||
|
case reflect.Int64:
|
||||||
|
slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
|
||||||
|
case reflect.Uint:
|
||||||
|
slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
|
||||||
|
case reflect.Uint64:
|
||||||
|
slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
|
||||||
|
case reflect.Float64:
|
||||||
|
slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
|
||||||
|
case reflectTime:
|
||||||
|
slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
field.Set(slice)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapStrictError(err error, isStrict bool) error {
|
||||||
|
if isStrict {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setWithProperType sets proper value to field based on its type,
|
||||||
|
// but it does not return error for failing parsing,
|
||||||
|
// because we want to use default value that is already assigned to strcut.
|
||||||
|
func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
if len(key.String()) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
field.SetString(key.String())
|
||||||
|
case reflect.Bool:
|
||||||
|
boolVal, err := key.Bool()
|
||||||
|
if err != nil {
|
||||||
|
return wrapStrictError(err, isStrict)
|
||||||
|
}
|
||||||
|
field.SetBool(boolVal)
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
durationVal, err := key.Duration()
|
||||||
|
// Skip zero value
|
||||||
|
if err == nil && int64(durationVal) > 0 {
|
||||||
|
field.Set(reflect.ValueOf(durationVal))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
intVal, err := key.Int64()
|
||||||
|
if err != nil {
|
||||||
|
return wrapStrictError(err, isStrict)
|
||||||
|
}
|
||||||
|
field.SetInt(intVal)
|
||||||
|
// byte is an alias for uint8, so supporting uint8 breaks support for byte
|
||||||
|
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
durationVal, err := key.Duration()
|
||||||
|
// Skip zero value
|
||||||
|
if err == nil && int(durationVal) > 0 {
|
||||||
|
field.Set(reflect.ValueOf(durationVal))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uintVal, err := key.Uint64()
|
||||||
|
if err != nil {
|
||||||
|
return wrapStrictError(err, isStrict)
|
||||||
|
}
|
||||||
|
field.SetUint(uintVal)
|
||||||
|
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
floatVal, err := key.Float64()
|
||||||
|
if err != nil {
|
||||||
|
return wrapStrictError(err, isStrict)
|
||||||
|
}
|
||||||
|
field.SetFloat(floatVal)
|
||||||
|
case reflectTime:
|
||||||
|
timeVal, err := key.Time()
|
||||||
|
if err != nil {
|
||||||
|
return wrapStrictError(err, isStrict)
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(timeVal))
|
||||||
|
case reflect.Slice:
|
||||||
|
return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported type '%s'", t)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool) {
|
||||||
|
opts := strings.SplitN(tag, ",", 3)
|
||||||
|
rawName = opts[0]
|
||||||
|
if len(opts) > 1 {
|
||||||
|
omitEmpty = opts[1] == "omitempty"
|
||||||
|
}
|
||||||
|
if len(opts) > 2 {
|
||||||
|
allowShadow = opts[2] == "allowshadow"
|
||||||
|
}
|
||||||
|
return rawName, omitEmpty, allowShadow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Section) mapTo(val reflect.Value, isStrict bool) error {
|
||||||
|
if val.Kind() == reflect.Ptr {
|
||||||
|
val = val.Elem()
|
||||||
|
}
|
||||||
|
typ := val.Type()
|
||||||
|
|
||||||
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
|
field := val.Field(i)
|
||||||
|
tpField := typ.Field(i)
|
||||||
|
|
||||||
|
tag := tpField.Tag.Get("ini")
|
||||||
|
if tag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rawName, _, allowShadow := parseTagOptions(tag)
|
||||||
|
fieldName := s.parseFieldName(tpField.Name, rawName)
|
||||||
|
if len(fieldName) == 0 || !field.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
isAnonymous := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
|
||||||
|
isStruct := tpField.Type.Kind() == reflect.Struct
|
||||||
|
if isAnonymous {
|
||||||
|
field.Set(reflect.New(tpField.Type.Elem()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAnonymous || isStruct {
|
||||||
|
if sec, err := s.f.GetSection(fieldName); err == nil {
|
||||||
|
if err = sec.mapTo(field, isStrict); err != nil {
|
||||||
|
return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if key, err := s.GetKey(fieldName); err == nil {
|
||||||
|
delim := parseDelim(tpField.Tag.Get("delim"))
|
||||||
|
if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
|
||||||
|
return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapTo maps section to given struct.
|
||||||
|
func (s *Section) MapTo(v interface{}) error {
|
||||||
|
typ := reflect.TypeOf(v)
|
||||||
|
val := reflect.ValueOf(v)
|
||||||
|
if typ.Kind() == reflect.Ptr {
|
||||||
|
typ = typ.Elem()
|
||||||
|
val = val.Elem()
|
||||||
|
} else {
|
||||||
|
return errors.New("cannot map to non-pointer struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.mapTo(val, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapTo maps section to given struct in strict mode,
|
||||||
|
// which returns all possible error including value parsing error.
|
||||||
|
func (s *Section) StrictMapTo(v interface{}) error {
|
||||||
|
typ := reflect.TypeOf(v)
|
||||||
|
val := reflect.ValueOf(v)
|
||||||
|
if typ.Kind() == reflect.Ptr {
|
||||||
|
typ = typ.Elem()
|
||||||
|
val = val.Elem()
|
||||||
|
} else {
|
||||||
|
return errors.New("cannot map to non-pointer struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.mapTo(val, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapTo maps file to given struct.
|
||||||
|
func (f *File) MapTo(v interface{}) error {
|
||||||
|
return f.Section("").MapTo(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapTo maps file to given struct in strict mode,
|
||||||
|
// which returns all possible error including value parsing error.
|
||||||
|
func (f *File) StrictMapTo(v interface{}) error {
|
||||||
|
return f.Section("").StrictMapTo(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapTo maps data sources to given struct with name mapper.
|
||||||
|
func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
|
||||||
|
cfg, err := Load(source, others...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg.NameMapper = mapper
|
||||||
|
return cfg.MapTo(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrictMapToWithMapper maps data sources to given struct with name mapper in strict mode,
|
||||||
|
// which returns all possible error including value parsing error.
|
||||||
|
func StrictMapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
|
||||||
|
cfg, err := Load(source, others...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg.NameMapper = mapper
|
||||||
|
return cfg.StrictMapTo(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapTo maps data sources to given struct.
|
||||||
|
func MapTo(v, source interface{}, others ...interface{}) error {
|
||||||
|
return MapToWithMapper(v, nil, source, others...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrictMapTo maps data sources to given struct in strict mode,
|
||||||
|
// which returns all possible error including value parsing error.
|
||||||
|
func StrictMapTo(v, source interface{}, others ...interface{}) error {
|
||||||
|
return StrictMapToWithMapper(v, nil, source, others...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reflectSliceWithProperType does the opposite thing as setSliceWithProperType.
|
||||||
|
func reflectSliceWithProperType(key *Key, field reflect.Value, delim string) error {
|
||||||
|
slice := field.Slice(0, field.Len())
|
||||||
|
if field.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
sliceOf := field.Type().Elem().Kind()
|
||||||
|
for i := 0; i < field.Len(); i++ {
|
||||||
|
switch sliceOf {
|
||||||
|
case reflect.String:
|
||||||
|
buf.WriteString(slice.Index(i).String())
|
||||||
|
case reflect.Int, reflect.Int64:
|
||||||
|
buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
|
||||||
|
case reflect.Uint, reflect.Uint64:
|
||||||
|
buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
|
||||||
|
case reflect.Float64:
|
||||||
|
buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
|
||||||
|
case reflectTime:
|
||||||
|
buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported type '[]%s'", sliceOf)
|
||||||
|
}
|
||||||
|
buf.WriteString(delim)
|
||||||
|
}
|
||||||
|
key.SetValue(buf.String()[:buf.Len()-1])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// reflectWithProperType does the opposite thing as setWithProperType.
|
||||||
|
func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
key.SetValue(field.String())
|
||||||
|
case reflect.Bool:
|
||||||
|
key.SetValue(fmt.Sprint(field.Bool()))
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
key.SetValue(fmt.Sprint(field.Int()))
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
key.SetValue(fmt.Sprint(field.Uint()))
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
key.SetValue(fmt.Sprint(field.Float()))
|
||||||
|
case reflectTime:
|
||||||
|
key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
|
||||||
|
case reflect.Slice:
|
||||||
|
return reflectSliceWithProperType(key, field, delim)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported type '%s'", t)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CR: copied from encoding/json/encode.go with modifications of time.Time support.
|
||||||
|
// TODO: add more test coverage.
|
||||||
|
func isEmptyValue(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
return v.Len() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
return !v.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float() == 0
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
return v.IsNil()
|
||||||
|
case reflectTime:
|
||||||
|
t, ok := v.Interface().(time.Time)
|
||||||
|
return ok && t.IsZero()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Section) reflectFrom(val reflect.Value) error {
|
||||||
|
if val.Kind() == reflect.Ptr {
|
||||||
|
val = val.Elem()
|
||||||
|
}
|
||||||
|
typ := val.Type()
|
||||||
|
|
||||||
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
|
field := val.Field(i)
|
||||||
|
tpField := typ.Field(i)
|
||||||
|
|
||||||
|
tag := tpField.Tag.Get("ini")
|
||||||
|
if tag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := strings.SplitN(tag, ",", 2)
|
||||||
|
if len(opts) == 2 && opts[1] == "omitempty" && isEmptyValue(field) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldName := s.parseFieldName(tpField.Name, opts[0])
|
||||||
|
if len(fieldName) == 0 || !field.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) ||
|
||||||
|
(tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
|
||||||
|
// Note: The only error here is section doesn't exist.
|
||||||
|
sec, err := s.f.GetSection(fieldName)
|
||||||
|
if err != nil {
|
||||||
|
// Note: fieldName can never be empty here, ignore error.
|
||||||
|
sec, _ = s.f.NewSection(fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add comment from comment tag
|
||||||
|
if len(sec.Comment) == 0 {
|
||||||
|
sec.Comment = tpField.Tag.Get("comment")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = sec.reflectFrom(field); err != nil {
|
||||||
|
return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Same reason as secion.
|
||||||
|
key, err := s.GetKey(fieldName)
|
||||||
|
if err != nil {
|
||||||
|
key, _ = s.NewKey(fieldName, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add comment from comment tag
|
||||||
|
if len(key.Comment) == 0 {
|
||||||
|
key.Comment = tpField.Tag.Get("comment")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
|
||||||
|
return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReflectFrom reflects secion from given struct.
|
||||||
|
func (s *Section) ReflectFrom(v interface{}) error {
|
||||||
|
typ := reflect.TypeOf(v)
|
||||||
|
val := reflect.ValueOf(v)
|
||||||
|
if typ.Kind() == reflect.Ptr {
|
||||||
|
typ = typ.Elem()
|
||||||
|
val = val.Elem()
|
||||||
|
} else {
|
||||||
|
return errors.New("cannot reflect from non-pointer struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.reflectFrom(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReflectFrom reflects file from given struct.
|
||||||
|
func (f *File) ReflectFrom(v interface{}) error {
|
||||||
|
return f.Section("").ReflectFrom(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReflectFrom reflects data sources from given struct with name mapper.
|
||||||
|
func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
|
||||||
|
cfg.NameMapper = mapper
|
||||||
|
return cfg.ReflectFrom(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReflectFrom reflects data sources from given struct.
|
||||||
|
func ReflectFrom(cfg *File, v interface{}) error {
|
||||||
|
return ReflectFromWithMapper(cfg, v, nil)
|
||||||
|
}
|
||||||
387
vendor/github.com/go-ini/ini/struct_test.go
generated
vendored
Normal file
387
vendor/github.com/go-ini/ini/struct_test.go
generated
vendored
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
// Copyright 2014 Unknwon
|
||||||
|
//
|
||||||
|
// 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 ini_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testNested struct {
|
||||||
|
Cities []string `delim:"|"`
|
||||||
|
Visits []time.Time
|
||||||
|
Years []int
|
||||||
|
Numbers []int64
|
||||||
|
Ages []uint
|
||||||
|
Populations []uint64
|
||||||
|
Coordinates []float64
|
||||||
|
Note string
|
||||||
|
Unused int `ini:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestEmbeded struct {
|
||||||
|
GPA float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
Name string `ini:"NAME"`
|
||||||
|
Age int
|
||||||
|
Male bool
|
||||||
|
Money float64
|
||||||
|
Born time.Time
|
||||||
|
Time time.Duration `ini:"Duration"`
|
||||||
|
Others testNested
|
||||||
|
*TestEmbeded `ini:"grade"`
|
||||||
|
Unused int `ini:"-"`
|
||||||
|
Unsigned uint
|
||||||
|
Omitted bool `ini:"omitthis,omitempty"`
|
||||||
|
Shadows []string `ini:",,allowshadow"`
|
||||||
|
ShadowInts []int `ini:"Shadows,,allowshadow"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const _CONF_DATA_STRUCT = `
|
||||||
|
NAME = Unknwon
|
||||||
|
Age = 21
|
||||||
|
Male = true
|
||||||
|
Money = 1.25
|
||||||
|
Born = 1993-10-07T20:17:05Z
|
||||||
|
Duration = 2h45m
|
||||||
|
Unsigned = 3
|
||||||
|
omitthis = true
|
||||||
|
Shadows = 1, 2
|
||||||
|
Shadows = 3, 4
|
||||||
|
|
||||||
|
[Others]
|
||||||
|
Cities = HangZhou|Boston
|
||||||
|
Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
|
||||||
|
Years = 1993,1994
|
||||||
|
Numbers = 10010,10086
|
||||||
|
Ages = 18,19
|
||||||
|
Populations = 12345678,98765432
|
||||||
|
Coordinates = 192.168,10.11
|
||||||
|
Note = Hello world!
|
||||||
|
|
||||||
|
[grade]
|
||||||
|
GPA = 2.8
|
||||||
|
|
||||||
|
[foo.bar]
|
||||||
|
Here = there
|
||||||
|
When = then
|
||||||
|
`
|
||||||
|
|
||||||
|
type unsupport struct {
|
||||||
|
Byte byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type unsupport2 struct {
|
||||||
|
Others struct {
|
||||||
|
Cities byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Unsupport3 struct {
|
||||||
|
Cities byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type unsupport4 struct {
|
||||||
|
*Unsupport3 `ini:"Others"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultValue struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
Male bool
|
||||||
|
Money float64
|
||||||
|
Born time.Time
|
||||||
|
Cities []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type fooBar struct {
|
||||||
|
Here, When string
|
||||||
|
}
|
||||||
|
|
||||||
|
const _INVALID_DATA_CONF_STRUCT = `
|
||||||
|
Name =
|
||||||
|
Age = age
|
||||||
|
Male = 123
|
||||||
|
Money = money
|
||||||
|
Born = nil
|
||||||
|
Cities =
|
||||||
|
`
|
||||||
|
|
||||||
|
func Test_MapToStruct(t *testing.T) {
|
||||||
|
Convey("Map to struct", t, func() {
|
||||||
|
Convey("Map file to struct", func() {
|
||||||
|
ts := new(testStruct)
|
||||||
|
So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
|
||||||
|
|
||||||
|
So(ts.Name, ShouldEqual, "Unknwon")
|
||||||
|
So(ts.Age, ShouldEqual, 21)
|
||||||
|
So(ts.Male, ShouldBeTrue)
|
||||||
|
So(ts.Money, ShouldEqual, 1.25)
|
||||||
|
So(ts.Unsigned, ShouldEqual, 3)
|
||||||
|
|
||||||
|
t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(ts.Born.String(), ShouldEqual, t.String())
|
||||||
|
|
||||||
|
dur, err := time.ParseDuration("2h45m")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(ts.Time.Seconds(), ShouldEqual, dur.Seconds())
|
||||||
|
|
||||||
|
So(strings.Join(ts.Others.Cities, ","), ShouldEqual, "HangZhou,Boston")
|
||||||
|
So(ts.Others.Visits[0].String(), ShouldEqual, t.String())
|
||||||
|
So(fmt.Sprint(ts.Others.Years), ShouldEqual, "[1993 1994]")
|
||||||
|
So(fmt.Sprint(ts.Others.Numbers), ShouldEqual, "[10010 10086]")
|
||||||
|
So(fmt.Sprint(ts.Others.Ages), ShouldEqual, "[18 19]")
|
||||||
|
So(fmt.Sprint(ts.Others.Populations), ShouldEqual, "[12345678 98765432]")
|
||||||
|
So(fmt.Sprint(ts.Others.Coordinates), ShouldEqual, "[192.168 10.11]")
|
||||||
|
So(ts.Others.Note, ShouldEqual, "Hello world!")
|
||||||
|
So(ts.TestEmbeded.GPA, ShouldEqual, 2.8)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Map section to struct", func() {
|
||||||
|
foobar := new(fooBar)
|
||||||
|
f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(f.Section("foo.bar").MapTo(foobar), ShouldBeNil)
|
||||||
|
So(foobar.Here, ShouldEqual, "there")
|
||||||
|
So(foobar.When, ShouldEqual, "then")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Map to non-pointer struct", func() {
|
||||||
|
f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
So(f.MapTo(testStruct{}), ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Map to unsupported type", func() {
|
||||||
|
f, err := ini.Load([]byte(_CONF_DATA_STRUCT))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(f, ShouldNotBeNil)
|
||||||
|
|
||||||
|
f.NameMapper = func(raw string) string {
|
||||||
|
if raw == "Byte" {
|
||||||
|
return "NAME"
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
So(f.MapTo(&unsupport{}), ShouldNotBeNil)
|
||||||
|
So(f.MapTo(&unsupport2{}), ShouldNotBeNil)
|
||||||
|
So(f.MapTo(&unsupport4{}), ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Map to omitempty field", func() {
|
||||||
|
ts := new(testStruct)
|
||||||
|
So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
|
||||||
|
|
||||||
|
So(ts.Omitted, ShouldEqual, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Map with shadows", func() {
|
||||||
|
f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true}, []byte(_CONF_DATA_STRUCT))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
ts := new(testStruct)
|
||||||
|
So(f.MapTo(ts), ShouldBeNil)
|
||||||
|
|
||||||
|
So(strings.Join(ts.Shadows, " "), ShouldEqual, "1 2 3 4")
|
||||||
|
So(fmt.Sprintf("%v", ts.ShadowInts), ShouldEqual, "[1 2 3 4]")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Map from invalid data source", func() {
|
||||||
|
So(ini.MapTo(&testStruct{}, "hi"), ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Map to wrong types and gain default values", func() {
|
||||||
|
f, err := ini.Load([]byte(_INVALID_DATA_CONF_STRUCT))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
dv := &defaultValue{"Joe", 10, true, 1.25, t, []string{"HangZhou", "Boston"}}
|
||||||
|
So(f.MapTo(dv), ShouldBeNil)
|
||||||
|
So(dv.Name, ShouldEqual, "Joe")
|
||||||
|
So(dv.Age, ShouldEqual, 10)
|
||||||
|
So(dv.Male, ShouldBeTrue)
|
||||||
|
So(dv.Money, ShouldEqual, 1.25)
|
||||||
|
So(dv.Born.String(), ShouldEqual, t.String())
|
||||||
|
So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Map to struct in strict mode", t, func() {
|
||||||
|
f, err := ini.Load([]byte(`
|
||||||
|
name=bruce
|
||||||
|
age=a30`))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
type Strict struct {
|
||||||
|
Name string `ini:"name"`
|
||||||
|
Age int `ini:"age"`
|
||||||
|
}
|
||||||
|
s := new(Strict)
|
||||||
|
|
||||||
|
So(f.Section("").StrictMapTo(s), ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Map slice in strict mode", t, func() {
|
||||||
|
f, err := ini.Load([]byte(`
|
||||||
|
names=alice, bruce`))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
type Strict struct {
|
||||||
|
Names []string `ini:"names"`
|
||||||
|
}
|
||||||
|
s := new(Strict)
|
||||||
|
|
||||||
|
So(f.Section("").StrictMapTo(s), ShouldBeNil)
|
||||||
|
So(fmt.Sprint(s.Names), ShouldEqual, "[alice bruce]")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ReflectFromStruct(t *testing.T) {
|
||||||
|
Convey("Reflect from struct", t, func() {
|
||||||
|
type Embeded struct {
|
||||||
|
Dates []time.Time `delim:"|" comment:"Time data"`
|
||||||
|
Places []string
|
||||||
|
Years []int
|
||||||
|
Numbers []int64
|
||||||
|
Ages []uint
|
||||||
|
Populations []uint64
|
||||||
|
Coordinates []float64
|
||||||
|
None []int
|
||||||
|
}
|
||||||
|
type Author struct {
|
||||||
|
Name string `ini:"NAME"`
|
||||||
|
Male bool
|
||||||
|
Age int `comment:"Author's age"`
|
||||||
|
Height uint
|
||||||
|
GPA float64
|
||||||
|
Date time.Time
|
||||||
|
NeverMind string `ini:"-"`
|
||||||
|
*Embeded `ini:"infos" comment:"Embeded section"`
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
a := &Author{"Unknwon", true, 21, 100, 2.8, t, "",
|
||||||
|
&Embeded{
|
||||||
|
[]time.Time{t, t},
|
||||||
|
[]string{"HangZhou", "Boston"},
|
||||||
|
[]int{1993, 1994},
|
||||||
|
[]int64{10010, 10086},
|
||||||
|
[]uint{18, 19},
|
||||||
|
[]uint64{12345678, 98765432},
|
||||||
|
[]float64{192.168, 10.11},
|
||||||
|
[]int{},
|
||||||
|
}}
|
||||||
|
cfg := ini.Empty()
|
||||||
|
So(ini.ReflectFrom(cfg, a), ShouldBeNil)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err = cfg.WriteTo(&buf)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(buf.String(), ShouldEqual, `NAME = Unknwon
|
||||||
|
Male = true
|
||||||
|
; Author's age
|
||||||
|
Age = 21
|
||||||
|
Height = 100
|
||||||
|
GPA = 2.8
|
||||||
|
Date = 1993-10-07T20:17:05Z
|
||||||
|
|
||||||
|
; Embeded section
|
||||||
|
[infos]
|
||||||
|
; Time data
|
||||||
|
Dates = 1993-10-07T20:17:05Z|1993-10-07T20:17:05Z
|
||||||
|
Places = HangZhou,Boston
|
||||||
|
Years = 1993,1994
|
||||||
|
Numbers = 10010,10086
|
||||||
|
Ages = 18,19
|
||||||
|
Populations = 12345678,98765432
|
||||||
|
Coordinates = 192.168,10.11
|
||||||
|
None =
|
||||||
|
|
||||||
|
`)
|
||||||
|
|
||||||
|
Convey("Reflect from non-point struct", func() {
|
||||||
|
So(ini.ReflectFrom(cfg, Author{}), ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Reflect from struct with omitempty", func() {
|
||||||
|
cfg := ini.Empty()
|
||||||
|
type SpecialStruct struct {
|
||||||
|
FirstName string `ini:"first_name"`
|
||||||
|
LastName string `ini:"last_name"`
|
||||||
|
JustOmitMe string `ini:"omitempty"`
|
||||||
|
LastLogin time.Time `ini:"last_login,omitempty"`
|
||||||
|
LastLogin2 time.Time `ini:",omitempty"`
|
||||||
|
NotEmpty int `ini:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
So(ini.ReflectFrom(cfg, &SpecialStruct{FirstName: "John", LastName: "Doe", NotEmpty: 9}), ShouldBeNil)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err = cfg.WriteTo(&buf)
|
||||||
|
So(buf.String(), ShouldEqual, `first_name = John
|
||||||
|
last_name = Doe
|
||||||
|
omitempty = 9
|
||||||
|
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type testMapper struct {
|
||||||
|
PackageName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_NameGetter(t *testing.T) {
|
||||||
|
Convey("Test name mappers", t, func() {
|
||||||
|
So(ini.MapToWithMapper(&testMapper{}, ini.TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil)
|
||||||
|
|
||||||
|
cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(cfg, ShouldNotBeNil)
|
||||||
|
|
||||||
|
cfg.NameMapper = ini.AllCapsUnderscore
|
||||||
|
tg := new(testMapper)
|
||||||
|
So(cfg.MapTo(tg), ShouldBeNil)
|
||||||
|
So(tg.PackageName, ShouldEqual, "ini")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type testDurationStruct struct {
|
||||||
|
Duration time.Duration `ini:"Duration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Duration(t *testing.T) {
|
||||||
|
Convey("Duration less than 16m50s", t, func() {
|
||||||
|
ds := new(testDurationStruct)
|
||||||
|
So(ini.MapTo(ds, []byte("Duration=16m49s")), ShouldBeNil)
|
||||||
|
|
||||||
|
dur, err := time.ParseDuration("16m49s")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(ds.Duration.Seconds(), ShouldEqual, dur.Seconds())
|
||||||
|
})
|
||||||
|
}
|
||||||
86
vendor/github.com/go-ini/ini/testdata/TestFile_WriteTo.golden
generated
vendored
Normal file
86
vendor/github.com/go-ini/ini/testdata/TestFile_WriteTo.golden
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
; Package name
|
||||||
|
NAME = ini
|
||||||
|
; Package version
|
||||||
|
VERSION = v1
|
||||||
|
; Package import path
|
||||||
|
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
|
||||||
|
|
||||||
|
; Information about package author
|
||||||
|
# Bio can be written in multiple lines.
|
||||||
|
[author]
|
||||||
|
; This is author name
|
||||||
|
NAME = Unknwon
|
||||||
|
E-MAIL = u@gogs.io
|
||||||
|
GITHUB = https://github.com/%(NAME)s
|
||||||
|
# Succeeding comment
|
||||||
|
BIO = """Gopher.
|
||||||
|
Coding addict.
|
||||||
|
Good man.
|
||||||
|
"""
|
||||||
|
|
||||||
|
[package]
|
||||||
|
CLONE_URL = https://%(IMPORT_PATH)s
|
||||||
|
|
||||||
|
[package.sub]
|
||||||
|
UNUSED_KEY = should be deleted
|
||||||
|
|
||||||
|
[features]
|
||||||
|
- = Support read/write comments of keys and sections
|
||||||
|
- = Support auto-increment of key names
|
||||||
|
- = Support load multiple files to overwrite key values
|
||||||
|
|
||||||
|
[types]
|
||||||
|
STRING = str
|
||||||
|
BOOL = true
|
||||||
|
BOOL_FALSE = false
|
||||||
|
FLOAT64 = 1.25
|
||||||
|
INT = 10
|
||||||
|
TIME = 2015-01-01T20:17:05Z
|
||||||
|
DURATION = 2h45m
|
||||||
|
UINT = 3
|
||||||
|
|
||||||
|
[array]
|
||||||
|
STRINGS = en, zh, de
|
||||||
|
FLOAT64S = 1.1, 2.2, 3.3
|
||||||
|
INTS = 1, 2, 3
|
||||||
|
UINTS = 1, 2, 3
|
||||||
|
TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
|
||||||
|
|
||||||
|
[note]
|
||||||
|
empty_lines = next line is empty
|
||||||
|
boolean_key
|
||||||
|
more = notes
|
||||||
|
|
||||||
|
; Comment before the section
|
||||||
|
; This is a comment for the section too
|
||||||
|
[comments]
|
||||||
|
; Comment before key
|
||||||
|
key = value
|
||||||
|
; This is a comment for key2
|
||||||
|
key2 = value2
|
||||||
|
key3 = "one", "two", "three"
|
||||||
|
|
||||||
|
[string escapes]
|
||||||
|
key1 = value1, value2, value3
|
||||||
|
key2 = value1\, value2
|
||||||
|
key3 = val\ue1, value2
|
||||||
|
key4 = value1\\, value\\\\2
|
||||||
|
key5 = value1\,, value2
|
||||||
|
key6 = aaa bbb\ and\ space ccc
|
||||||
|
|
||||||
|
[advance]
|
||||||
|
value with quotes = some value
|
||||||
|
value quote2 again = some value
|
||||||
|
includes comment sign = `my#password`
|
||||||
|
includes comment sign2 = `my;password`
|
||||||
|
true = 2+3=5
|
||||||
|
`1+1=2` = true
|
||||||
|
`6+1=7` = true
|
||||||
|
"""`5+5`""" = 10
|
||||||
|
`"6+6"` = 12
|
||||||
|
`7-2=4` = false
|
||||||
|
ADDRESS = """404 road,
|
||||||
|
NotFound, State, 50000"""
|
||||||
|
two_lines = how about continuation lines?
|
||||||
|
lots_of_lines = 1 2 3 4
|
||||||
|
|
||||||
BIN
vendor/github.com/go-ini/ini/testdata/UTF-16-BE-BOM.ini
generated
vendored
Normal file
BIN
vendor/github.com/go-ini/ini/testdata/UTF-16-BE-BOM.ini
generated
vendored
Normal file
Binary file not shown.
BIN
vendor/github.com/go-ini/ini/testdata/UTF-16-LE-BOM.ini
generated
vendored
Normal file
BIN
vendor/github.com/go-ini/ini/testdata/UTF-16-LE-BOM.ini
generated
vendored
Normal file
Binary file not shown.
2
vendor/github.com/go-ini/ini/testdata/UTF-8-BOM.ini
generated
vendored
Normal file
2
vendor/github.com/go-ini/ini/testdata/UTF-8-BOM.ini
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[author]
|
||||||
|
E-MAIL = u@gogs.io
|
||||||
83
vendor/github.com/go-ini/ini/testdata/full.ini
generated
vendored
Normal file
83
vendor/github.com/go-ini/ini/testdata/full.ini
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
; Package name
|
||||||
|
NAME = ini
|
||||||
|
; Package version
|
||||||
|
VERSION = v1
|
||||||
|
; Package import path
|
||||||
|
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
|
||||||
|
|
||||||
|
# Information about package author
|
||||||
|
# Bio can be written in multiple lines.
|
||||||
|
[author]
|
||||||
|
NAME = Unknwon
|
||||||
|
E-MAIL = u@gogs.io
|
||||||
|
GITHUB = https://github.com/%(NAME)s
|
||||||
|
BIO = """Gopher.
|
||||||
|
Coding addict.
|
||||||
|
Good man.
|
||||||
|
""" # Succeeding comment
|
||||||
|
|
||||||
|
[package]
|
||||||
|
CLONE_URL = https://%(IMPORT_PATH)s
|
||||||
|
|
||||||
|
[package.sub]
|
||||||
|
UNUSED_KEY = should be deleted
|
||||||
|
|
||||||
|
[features]
|
||||||
|
-: Support read/write comments of keys and sections
|
||||||
|
-: Support auto-increment of key names
|
||||||
|
-: Support load multiple files to overwrite key values
|
||||||
|
|
||||||
|
[types]
|
||||||
|
STRING = str
|
||||||
|
BOOL = true
|
||||||
|
BOOL_FALSE = false
|
||||||
|
FLOAT64 = 1.25
|
||||||
|
INT = 10
|
||||||
|
TIME = 2015-01-01T20:17:05Z
|
||||||
|
DURATION = 2h45m
|
||||||
|
UINT = 3
|
||||||
|
|
||||||
|
[array]
|
||||||
|
STRINGS = en, zh, de
|
||||||
|
FLOAT64S = 1.1, 2.2, 3.3
|
||||||
|
INTS = 1, 2, 3
|
||||||
|
UINTS = 1, 2, 3
|
||||||
|
TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
|
||||||
|
|
||||||
|
[note]
|
||||||
|
empty_lines = next line is empty\
|
||||||
|
|
||||||
|
; Comment before the section
|
||||||
|
[comments] ; This is a comment for the section too
|
||||||
|
; Comment before key
|
||||||
|
key = "value"
|
||||||
|
key2 = "value2" ; This is a comment for key2
|
||||||
|
key3 = "one", "two", "three"
|
||||||
|
|
||||||
|
[string escapes]
|
||||||
|
key1 = value1, value2, value3
|
||||||
|
key2 = value1\, value2
|
||||||
|
key3 = val\ue1, value2
|
||||||
|
key4 = value1\\, value\\\\2
|
||||||
|
key5 = value1\,, value2
|
||||||
|
key6 = aaa bbb\ and\ space ccc
|
||||||
|
|
||||||
|
[advance]
|
||||||
|
value with quotes = "some value"
|
||||||
|
value quote2 again = 'some value'
|
||||||
|
includes comment sign = `my#password`
|
||||||
|
includes comment sign2 = `my;password`
|
||||||
|
true = 2+3=5
|
||||||
|
"1+1=2" = true
|
||||||
|
"""6+1=7""" = true
|
||||||
|
"""`5+5`""" = 10
|
||||||
|
`"6+6"` = 12
|
||||||
|
`7-2=4` = false
|
||||||
|
ADDRESS = `404 road,
|
||||||
|
NotFound, State, 50000`
|
||||||
|
two_lines = how about \
|
||||||
|
continuation lines?
|
||||||
|
lots_of_lines = 1 \
|
||||||
|
2 \
|
||||||
|
3 \
|
||||||
|
4 \
|
||||||
2
vendor/github.com/go-ini/ini/testdata/minimal.ini
generated
vendored
Normal file
2
vendor/github.com/go-ini/ini/testdata/minimal.ini
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[author]
|
||||||
|
E-MAIL = u@gogs.io
|
||||||
363
vendor/github.com/hashicorp/go-cleanhttp/LICENSE
generated
vendored
Normal file
363
vendor/github.com/hashicorp/go-cleanhttp/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
Mozilla Public License, version 2.0
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
|
||||||
|
means each individual or legal entity that creates, contributes to the
|
||||||
|
creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
|
||||||
|
means the combination of the Contributions of others (if any) used by a
|
||||||
|
Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
|
||||||
|
means Source Code Form to which the initial Contributor has attached the
|
||||||
|
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||||
|
Modifications of such Source Code Form, in each case including portions
|
||||||
|
thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
a. that the initial Contributor has attached the notice described in
|
||||||
|
Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
b. that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the terms of
|
||||||
|
a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
|
||||||
|
means a work that combines Covered Software with other material, in a
|
||||||
|
separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
|
||||||
|
means having the right to grant, to the maximum extent possible, whether
|
||||||
|
at the time of the initial grant or subsequently, any and all of the
|
||||||
|
rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
a. any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered Software; or
|
||||||
|
|
||||||
|
b. any new file in Source Code Form that contains any Covered Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the License,
|
||||||
|
by the making, using, selling, offering for sale, having made, import,
|
||||||
|
or transfer of either its Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||||
|
General Public License, Version 2.1, the GNU Affero General Public
|
||||||
|
License, Version 3.0, or any later versions of those licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that controls, is
|
||||||
|
controlled by, or is under common control with You. For purposes of this
|
||||||
|
definition, "control" means (a) the power, direct or indirect, to cause
|
||||||
|
the direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||||
|
outstanding shares or beneficial ownership of such entity.
|
||||||
|
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
a. under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||||
|
sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
a. for any code that a Contributor has removed from Covered Software; or
|
||||||
|
|
||||||
|
b. for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
c. under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights to
|
||||||
|
grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||||
|
Section 2.1.
|
||||||
|
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
a. such Covered Software must also be made available in Source Code Form,
|
||||||
|
as described in Section 3.1, and You must inform recipients of the
|
||||||
|
Executable Form how they can obtain a copy of such Source Code Form by
|
||||||
|
reasonable means in a timely manner, at a charge no more than the cost
|
||||||
|
of distribution to the recipient; and
|
||||||
|
|
||||||
|
b. You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter the
|
||||||
|
recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty, or
|
||||||
|
limitations of liability) contained within the Source Code Form of the
|
||||||
|
Covered Software, except that You may alter any license notices to the
|
||||||
|
extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this License
|
||||||
|
with respect to some or all of the Covered Software due to statute,
|
||||||
|
judicial order, or regulation then You must: (a) comply with the terms of
|
||||||
|
this License to the maximum extent possible; and (b) describe the
|
||||||
|
limitations and the code they affect. Such description must be placed in a
|
||||||
|
text file included with all distributions of the Covered Software under
|
||||||
|
this License. Except to the extent prohibited by statute or regulation,
|
||||||
|
such description must be sufficiently detailed for a recipient of ordinary
|
||||||
|
skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically if You
|
||||||
|
fail to comply with any of its terms. However, if You become compliant,
|
||||||
|
then the rights granted under this License from a particular Contributor
|
||||||
|
are reinstated (a) provisionally, unless and until such Contributor
|
||||||
|
explicitly and finally terminates Your grants, and (b) on an ongoing
|
||||||
|
basis, if such Contributor fails to notify You of the non-compliance by
|
||||||
|
some reasonable means prior to 60 days after You have come back into
|
||||||
|
compliance. Moreover, Your grants from a particular Contributor are
|
||||||
|
reinstated on an ongoing basis if such Contributor notifies You of the
|
||||||
|
non-compliance by some reasonable means, this is the first time You have
|
||||||
|
received notice of non-compliance with this License from such
|
||||||
|
Contributor, and You become compliant prior to 30 days after Your receipt
|
||||||
|
of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||||
|
license agreements (excluding distributors and resellers) which have been
|
||||||
|
validly granted by You or Your distributors under this License prior to
|
||||||
|
termination shall survive termination.
|
||||||
|
|
||||||
|
6. Disclaimer of Warranty
|
||||||
|
|
||||||
|
Covered Software is provided under this License on an "as is" basis,
|
||||||
|
without warranty of any kind, either expressed, implied, or statutory,
|
||||||
|
including, without limitation, warranties that the Covered Software is free
|
||||||
|
of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||||
|
The entire risk as to the quality and performance of the Covered Software
|
||||||
|
is with You. Should any Covered Software prove defective in any respect,
|
||||||
|
You (not any Contributor) assume the cost of any necessary servicing,
|
||||||
|
repair, or correction. This disclaimer of warranty constitutes an essential
|
||||||
|
part of this License. No use of any Covered Software is authorized under
|
||||||
|
this License except under this disclaimer.
|
||||||
|
|
||||||
|
7. Limitation of Liability
|
||||||
|
|
||||||
|
Under no circumstances and under no legal theory, whether tort (including
|
||||||
|
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||||
|
distributes Covered Software as permitted above, be liable to You for any
|
||||||
|
direct, indirect, special, incidental, or consequential damages of any
|
||||||
|
character including, without limitation, damages for lost profits, loss of
|
||||||
|
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses, even if such party shall have been
|
||||||
|
informed of the possibility of such damages. This limitation of liability
|
||||||
|
shall not apply to liability for death or personal injury resulting from
|
||||||
|
such party's negligence to the extent applicable law prohibits such
|
||||||
|
limitation. Some jurisdictions do not allow the exclusion or limitation of
|
||||||
|
incidental or consequential damages, so this exclusion and limitation may
|
||||||
|
not apply to You.
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the courts
|
||||||
|
of a jurisdiction where the defendant maintains its principal place of
|
||||||
|
business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions. Nothing
|
||||||
|
in this Section shall prevent a party's ability to bring cross-claims or
|
||||||
|
counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides that
|
||||||
|
the language of a contract shall be construed against the drafter shall not
|
||||||
|
be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses If You choose to distribute Source Code Form that is
|
||||||
|
Incompatible With Secondary Licenses under the terms of this version of
|
||||||
|
the License, the notice described in Exhibit B of this License must be
|
||||||
|
attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
|
||||||
|
This Source Code Form is subject to the
|
||||||
|
terms of the Mozilla Public License, v.
|
||||||
|
2.0. If a copy of the MPL was not
|
||||||
|
distributed with this file, You can
|
||||||
|
obtain one at
|
||||||
|
http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular file,
|
||||||
|
then You may include the notice in a location (such as a LICENSE file in a
|
||||||
|
relevant directory) where a recipient would be likely to look for such a
|
||||||
|
notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible
|
||||||
|
With Secondary Licenses", as defined by
|
||||||
|
the Mozilla Public License, v. 2.0.
|
||||||
|
|
||||||
30
vendor/github.com/hashicorp/go-cleanhttp/README.md
generated
vendored
Normal file
30
vendor/github.com/hashicorp/go-cleanhttp/README.md
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# cleanhttp
|
||||||
|
|
||||||
|
Functions for accessing "clean" Go http.Client values
|
||||||
|
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The Go standard library contains a default `http.Client` called
|
||||||
|
`http.DefaultClient`. It is a common idiom in Go code to start with
|
||||||
|
`http.DefaultClient` and tweak it as necessary, and in fact, this is
|
||||||
|
encouraged; from the `http` package documentation:
|
||||||
|
|
||||||
|
> The Client's Transport typically has internal state (cached TCP connections),
|
||||||
|
so Clients should be reused instead of created as needed. Clients are safe for
|
||||||
|
concurrent use by multiple goroutines.
|
||||||
|
|
||||||
|
Unfortunately, this is a shared value, and it is not uncommon for libraries to
|
||||||
|
assume that they are free to modify it at will. With enough dependencies, it
|
||||||
|
can be very easy to encounter strange problems and race conditions due to
|
||||||
|
manipulation of this shared value across libraries and goroutines (clients are
|
||||||
|
safe for concurrent use, but writing values to the client struct itself is not
|
||||||
|
protected).
|
||||||
|
|
||||||
|
Making things worse is the fact that a bare `http.Client` will use a default
|
||||||
|
`http.Transport` called `http.DefaultTransport`, which is another global value
|
||||||
|
that behaves the same way. So it is not simply enough to replace
|
||||||
|
`http.DefaultClient` with `&http.Client{}`.
|
||||||
|
|
||||||
|
This repository provides some simple functions to get a "clean" `http.Client`
|
||||||
|
-- one that uses the same default values as the Go standard library, but
|
||||||
|
returns a client that does not share any state with other clients.
|
||||||
57
vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go
generated
vendored
Normal file
57
vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package cleanhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultTransport returns a new http.Transport with similar default values to
|
||||||
|
// http.DefaultTransport, but with idle connections and keepalives disabled.
|
||||||
|
func DefaultTransport() *http.Transport {
|
||||||
|
transport := DefaultPooledTransport()
|
||||||
|
transport.DisableKeepAlives = true
|
||||||
|
transport.MaxIdleConnsPerHost = -1
|
||||||
|
return transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultPooledTransport returns a new http.Transport with similar default
|
||||||
|
// values to http.DefaultTransport. Do not use this for transient transports as
|
||||||
|
// it can leak file descriptors over time. Only use this for transports that
|
||||||
|
// will be re-used for the same host(s).
|
||||||
|
func DefaultPooledTransport() *http.Transport {
|
||||||
|
transport := &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
DualStack: true,
|
||||||
|
}).DialContext,
|
||||||
|
MaxIdleConns: 100,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
|
||||||
|
}
|
||||||
|
return transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultClient returns a new http.Client with similar default values to
|
||||||
|
// http.Client, but with a non-shared Transport, idle connections disabled, and
|
||||||
|
// keepalives disabled.
|
||||||
|
func DefaultClient() *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: DefaultTransport(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultPooledClient returns a new http.Client with similar default values to
|
||||||
|
// http.Client, but with a shared Transport. Do not use this function for
|
||||||
|
// transient clients as it can leak file descriptors over time. Only use this
|
||||||
|
// for clients that will be re-used for the same host(s).
|
||||||
|
func DefaultPooledClient() *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: DefaultPooledTransport(),
|
||||||
|
}
|
||||||
|
}
|
||||||
20
vendor/github.com/hashicorp/go-cleanhttp/doc.go
generated
vendored
Normal file
20
vendor/github.com/hashicorp/go-cleanhttp/doc.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Package cleanhttp offers convenience utilities for acquiring "clean"
|
||||||
|
// http.Transport and http.Client structs.
|
||||||
|
//
|
||||||
|
// Values set on http.DefaultClient and http.DefaultTransport affect all
|
||||||
|
// callers. This can have detrimental effects, esepcially in TLS contexts,
|
||||||
|
// where client or root certificates set to talk to multiple endpoints can end
|
||||||
|
// up displacing each other, leading to hard-to-debug issues. This package
|
||||||
|
// provides non-shared http.Client and http.Transport structs to ensure that
|
||||||
|
// the configuration will not be overwritten by other parts of the application
|
||||||
|
// or dependencies.
|
||||||
|
//
|
||||||
|
// The DefaultClient and DefaultTransport functions disable idle connections
|
||||||
|
// and keepalives. Without ensuring that idle connections are closed before
|
||||||
|
// garbage collection, short-term clients/transports can leak file descriptors,
|
||||||
|
// eventually leading to "too many open files" errors. If you will be
|
||||||
|
// connecting to the same hosts repeatedly from the same client, you can use
|
||||||
|
// DefaultPooledClient to receive a client that has connection pooling
|
||||||
|
// semantics similar to http.DefaultClient.
|
||||||
|
//
|
||||||
|
package cleanhttp
|
||||||
43
vendor/github.com/hashicorp/go-cleanhttp/handlers.go
generated
vendored
Normal file
43
vendor/github.com/hashicorp/go-cleanhttp/handlers.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package cleanhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandlerInput provides input options to cleanhttp's handlers
|
||||||
|
type HandlerInput struct {
|
||||||
|
ErrStatus int
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintablePathCheckHandler is a middleware that ensures the request path
|
||||||
|
// contains only printable runes.
|
||||||
|
func PrintablePathCheckHandler(next http.Handler, input *HandlerInput) http.Handler {
|
||||||
|
// Nil-check on input to make it optional
|
||||||
|
if input == nil {
|
||||||
|
input = &HandlerInput{
|
||||||
|
ErrStatus: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to http.StatusBadRequest on error
|
||||||
|
if input.ErrStatus == 0 {
|
||||||
|
input.ErrStatus = http.StatusBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Check URL path for non-printable characters
|
||||||
|
idx := strings.IndexFunc(r.URL.Path, func(c rune) bool {
|
||||||
|
return !unicode.IsPrint(c)
|
||||||
|
})
|
||||||
|
|
||||||
|
if idx != -1 {
|
||||||
|
w.WriteHeader(input.ErrStatus)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
72
vendor/github.com/hashicorp/go-cleanhttp/handlers_test.go
generated
vendored
Normal file
72
vendor/github.com/hashicorp/go-cleanhttp/handlers_test.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package cleanhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrintablePathCheckHandler(t *testing.T) {
|
||||||
|
getTestHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintln(w, "Hello, client")
|
||||||
|
})
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
path string
|
||||||
|
expectCode int
|
||||||
|
input *HandlerInput
|
||||||
|
}{
|
||||||
|
"valid nil input": {
|
||||||
|
path: "/valid",
|
||||||
|
expectCode: http.StatusOK,
|
||||||
|
input: nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
"valid empty error status": {
|
||||||
|
path: "/valid",
|
||||||
|
expectCode: http.StatusOK,
|
||||||
|
input: &HandlerInput{},
|
||||||
|
},
|
||||||
|
|
||||||
|
"invalid newline": {
|
||||||
|
path: "/invalid\n",
|
||||||
|
expectCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
|
||||||
|
"invalid carriage return": {
|
||||||
|
path: "/invalid\r",
|
||||||
|
expectCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
|
||||||
|
"invalid null": {
|
||||||
|
path: "/invalid\x00",
|
||||||
|
expectCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
|
||||||
|
"invalid alternate status": {
|
||||||
|
path: "/invalid\n",
|
||||||
|
expectCode: http.StatusInternalServerError,
|
||||||
|
input: &HandlerInput{
|
||||||
|
ErrStatus: http.StatusInternalServerError,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
// Create test HTTP server
|
||||||
|
ts := httptest.NewServer(PrintablePathCheckHandler(getTestHandler, tc.input))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
res, err := http.Get(ts.URL + tc.path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectCode != res.StatusCode {
|
||||||
|
t.Fatalf("expected %d, got :%d", tc.expectCode, res.StatusCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
23
vendor/github.com/hashicorp/go-getter/.travis.yml
generated
vendored
Normal file
23
vendor/github.com/hashicorp/go-getter/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
sudo: false
|
||||||
|
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- sourceline: 'ppa:git-core/ppa'
|
||||||
|
packages:
|
||||||
|
- git
|
||||||
|
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- master
|
||||||
|
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: master
|
||||||
354
vendor/github.com/hashicorp/go-getter/LICENSE
generated
vendored
Normal file
354
vendor/github.com/hashicorp/go-getter/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
Mozilla Public License, version 2.0
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
|
||||||
|
1.1. “Contributor”
|
||||||
|
|
||||||
|
means each individual or legal entity that creates, contributes to the
|
||||||
|
creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. “Contributor Version”
|
||||||
|
|
||||||
|
means the combination of the Contributions of others (if any) used by a
|
||||||
|
Contributor and that particular Contributor’s Contribution.
|
||||||
|
|
||||||
|
1.3. “Contribution”
|
||||||
|
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. “Covered Software”
|
||||||
|
|
||||||
|
means Source Code Form to which the initial Contributor has attached the
|
||||||
|
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||||
|
Modifications of such Source Code Form, in each case including portions
|
||||||
|
thereof.
|
||||||
|
|
||||||
|
1.5. “Incompatible With Secondary Licenses”
|
||||||
|
means
|
||||||
|
|
||||||
|
a. that the initial Contributor has attached the notice described in
|
||||||
|
Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
b. that the Covered Software was made available under the terms of version
|
||||||
|
1.1 or earlier of the License, but not also under the terms of a
|
||||||
|
Secondary License.
|
||||||
|
|
||||||
|
1.6. “Executable Form”
|
||||||
|
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. “Larger Work”
|
||||||
|
|
||||||
|
means a work that combines Covered Software with other material, in a separate
|
||||||
|
file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. “License”
|
||||||
|
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. “Licensable”
|
||||||
|
|
||||||
|
means having the right to grant, to the maximum extent possible, whether at the
|
||||||
|
time of the initial grant or subsequently, any and all of the rights conveyed by
|
||||||
|
this License.
|
||||||
|
|
||||||
|
1.10. “Modifications”
|
||||||
|
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
a. any file in Source Code Form that results from an addition to, deletion
|
||||||
|
from, or modification of the contents of Covered Software; or
|
||||||
|
|
||||||
|
b. any new file in Source Code Form that contains any Covered Software.
|
||||||
|
|
||||||
|
1.11. “Patent Claims” of a Contributor
|
||||||
|
|
||||||
|
means any patent claim(s), including without limitation, method, process,
|
||||||
|
and apparatus claims, in any patent Licensable by such Contributor that
|
||||||
|
would be infringed, but for the grant of the License, by the making,
|
||||||
|
using, selling, offering for sale, having made, import, or transfer of
|
||||||
|
either its Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
1.12. “Secondary License”
|
||||||
|
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||||
|
General Public License, Version 2.1, the GNU Affero General Public
|
||||||
|
License, Version 3.0, or any later versions of those licenses.
|
||||||
|
|
||||||
|
1.13. “Source Code Form”
|
||||||
|
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. “You” (or “Your”)
|
||||||
|
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, “You” includes any entity that controls, is
|
||||||
|
controlled by, or is under common control with You. For purposes of this
|
||||||
|
definition, “control” means (a) the power, direct or indirect, to cause
|
||||||
|
the direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||||
|
outstanding shares or beneficial ownership of such entity.
|
||||||
|
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
a. under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or as
|
||||||
|
part of a Larger Work; and
|
||||||
|
|
||||||
|
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||||
|
sale, have made, import, and otherwise transfer either its Contributions
|
||||||
|
or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution become
|
||||||
|
effective for each Contribution on the date the Contributor first distributes
|
||||||
|
such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under this
|
||||||
|
License. No additional rights or licenses will be implied from the distribution
|
||||||
|
or licensing of Covered Software under this License. Notwithstanding Section
|
||||||
|
2.1(b) above, no patent license is granted by a Contributor:
|
||||||
|
|
||||||
|
a. for any code that a Contributor has removed from Covered Software; or
|
||||||
|
|
||||||
|
b. for infringements caused by: (i) Your and any other third party’s
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
c. under Patent Claims infringed by Covered Software in the absence of its
|
||||||
|
Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks, or
|
||||||
|
logos of any Contributor (except as may be necessary to comply with the
|
||||||
|
notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this License
|
||||||
|
(see Section 10.2) or under the terms of a Secondary License (if permitted
|
||||||
|
under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its Contributions
|
||||||
|
are its original creation(s) or it has sufficient rights to grant the
|
||||||
|
rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under applicable
|
||||||
|
copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||||
|
Section 2.1.
|
||||||
|
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under the
|
||||||
|
terms of this License. You must inform recipients that the Source Code Form
|
||||||
|
of the Covered Software is governed by the terms of this License, and how
|
||||||
|
they can obtain a copy of this License. You may not attempt to alter or
|
||||||
|
restrict the recipients’ rights in the Source Code Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
a. such Covered Software must also be made available in Source Code Form,
|
||||||
|
as described in Section 3.1, and You must inform recipients of the
|
||||||
|
Executable Form how they can obtain a copy of such Source Code Form by
|
||||||
|
reasonable means in a timely manner, at a charge no more than the cost
|
||||||
|
of distribution to the recipient; and
|
||||||
|
|
||||||
|
b. You may distribute such Executable Form under the terms of this License,
|
||||||
|
or sublicense it under different terms, provided that the license for
|
||||||
|
the Executable Form does not attempt to limit or alter the recipients’
|
||||||
|
rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for the
|
||||||
|
Covered Software. If the Larger Work is a combination of Covered Software
|
||||||
|
with a work governed by one or more Secondary Licenses, and the Covered
|
||||||
|
Software is not Incompatible With Secondary Licenses, this License permits
|
||||||
|
You to additionally distribute such Covered Software under the terms of
|
||||||
|
such Secondary License(s), so that the recipient of the Larger Work may, at
|
||||||
|
their option, further distribute the Covered Software under the terms of
|
||||||
|
either this License or such Secondary License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices (including
|
||||||
|
copyright notices, patent notices, disclaimers of warranty, or limitations
|
||||||
|
of liability) contained within the Source Code Form of the Covered
|
||||||
|
Software, except that You may alter any license notices to the extent
|
||||||
|
required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on behalf
|
||||||
|
of any Contributor. You must make it absolutely clear that any such
|
||||||
|
warranty, support, indemnity, or liability obligation is offered by You
|
||||||
|
alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this License
|
||||||
|
with respect to some or all of the Covered Software due to statute, judicial
|
||||||
|
order, or regulation then You must: (a) comply with the terms of this License
|
||||||
|
to the maximum extent possible; and (b) describe the limitations and the code
|
||||||
|
they affect. Such description must be placed in a text file included with all
|
||||||
|
distributions of the Covered Software under this License. Except to the
|
||||||
|
extent prohibited by statute or regulation, such description must be
|
||||||
|
sufficiently detailed for a recipient of ordinary skill to be able to
|
||||||
|
understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically if You
|
||||||
|
fail to comply with any of its terms. However, if You become compliant,
|
||||||
|
then the rights granted under this License from a particular Contributor
|
||||||
|
are reinstated (a) provisionally, unless and until such Contributor
|
||||||
|
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
|
||||||
|
if such Contributor fails to notify You of the non-compliance by some
|
||||||
|
reasonable means prior to 60 days after You have come back into compliance.
|
||||||
|
Moreover, Your grants from a particular Contributor are reinstated on an
|
||||||
|
ongoing basis if such Contributor notifies You of the non-compliance by
|
||||||
|
some reasonable means, this is the first time You have received notice of
|
||||||
|
non-compliance with this License from such Contributor, and You become
|
||||||
|
compliant prior to 30 days after Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions, counter-claims,
|
||||||
|
and cross-claims) alleging that a Contributor Version directly or
|
||||||
|
indirectly infringes any patent, then the rights granted to You by any and
|
||||||
|
all Contributors for the Covered Software under Section 2.1 of this License
|
||||||
|
shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||||
|
license agreements (excluding distributors and resellers) which have been
|
||||||
|
validly granted by You or Your distributors under this License prior to
|
||||||
|
termination shall survive termination.
|
||||||
|
|
||||||
|
6. Disclaimer of Warranty
|
||||||
|
|
||||||
|
Covered Software is provided under this License on an “as is” basis, without
|
||||||
|
warranty of any kind, either expressed, implied, or statutory, including,
|
||||||
|
without limitation, warranties that the Covered Software is free of defects,
|
||||||
|
merchantable, fit for a particular purpose or non-infringing. The entire
|
||||||
|
risk as to the quality and performance of the Covered Software is with You.
|
||||||
|
Should any Covered Software prove defective in any respect, You (not any
|
||||||
|
Contributor) assume the cost of any necessary servicing, repair, or
|
||||||
|
correction. This disclaimer of warranty constitutes an essential part of this
|
||||||
|
License. No use of any Covered Software is authorized under this License
|
||||||
|
except under this disclaimer.
|
||||||
|
|
||||||
|
7. Limitation of Liability
|
||||||
|
|
||||||
|
Under no circumstances and under no legal theory, whether tort (including
|
||||||
|
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||||
|
distributes Covered Software as permitted above, be liable to You for any
|
||||||
|
direct, indirect, special, incidental, or consequential damages of any
|
||||||
|
character including, without limitation, damages for lost profits, loss of
|
||||||
|
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses, even if such party shall have been
|
||||||
|
informed of the possibility of such damages. This limitation of liability
|
||||||
|
shall not apply to liability for death or personal injury resulting from such
|
||||||
|
party’s negligence to the extent applicable law prohibits such limitation.
|
||||||
|
Some jurisdictions do not allow the exclusion or limitation of incidental or
|
||||||
|
consequential damages, so this exclusion and limitation may not apply to You.
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the courts of
|
||||||
|
a jurisdiction where the defendant maintains its principal place of business
|
||||||
|
and such litigation shall be governed by laws of that jurisdiction, without
|
||||||
|
reference to its conflict-of-law provisions. Nothing in this Section shall
|
||||||
|
prevent a party’s ability to bring cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject matter
|
||||||
|
hereof. If any provision of this License is held to be unenforceable, such
|
||||||
|
provision shall be reformed only to the extent necessary to make it
|
||||||
|
enforceable. Any law or regulation which provides that the language of a
|
||||||
|
contract shall be construed against the drafter shall not be used to construe
|
||||||
|
this License against a Contributor.
|
||||||
|
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version of
|
||||||
|
the License under which You originally received the Covered Software, or
|
||||||
|
under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a modified
|
||||||
|
version of this License if you rename the license and remove any
|
||||||
|
references to the name of the license steward (except to note that such
|
||||||
|
modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
|
||||||
|
This Source Code Form is subject to the
|
||||||
|
terms of the Mozilla Public License, v.
|
||||||
|
2.0. If a copy of the MPL was not
|
||||||
|
distributed with this file, You can
|
||||||
|
obtain one at
|
||||||
|
http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular file, then
|
||||||
|
You may include the notice in a location (such as a LICENSE file in a relevant
|
||||||
|
directory) where a recipient would be likely to look for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||||
|
|
||||||
|
This Source Code Form is “Incompatible
|
||||||
|
With Secondary Licenses”, as defined by
|
||||||
|
the Mozilla Public License, v. 2.0.
|
||||||
|
|
||||||
301
vendor/github.com/hashicorp/go-getter/README.md
generated
vendored
Normal file
301
vendor/github.com/hashicorp/go-getter/README.md
generated
vendored
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
# go-getter
|
||||||
|
|
||||||
|
[][travis]
|
||||||
|
[][appveyor]
|
||||||
|
[][godocs]
|
||||||
|
|
||||||
|
[travis]: http://travis-ci.org/hashicorp/go-getter
|
||||||
|
[godocs]: http://godoc.org/github.com/hashicorp/go-getter
|
||||||
|
[appveyor]: https://ci.appveyor.com/project/hashicorp/go-getter/branch/master
|
||||||
|
|
||||||
|
go-getter is a library for Go (golang) for downloading files or directories
|
||||||
|
from various sources using a URL as the primary form of input.
|
||||||
|
|
||||||
|
The power of this library is being flexible in being able to download
|
||||||
|
from a number of different sources (file paths, Git, HTTP, Mercurial, etc.)
|
||||||
|
using a single string as input. This removes the burden of knowing how to
|
||||||
|
download from a variety of sources from the implementer.
|
||||||
|
|
||||||
|
The concept of a _detector_ automatically turns invalid URLs into proper
|
||||||
|
URLs. For example: "github.com/hashicorp/go-getter" would turn into a
|
||||||
|
Git URL. Or "./foo" would turn into a file URL. These are extensible.
|
||||||
|
|
||||||
|
This library is used by [Terraform](https://terraform.io) for
|
||||||
|
downloading modules and [Nomad](https://nomadproject.io) for downloading
|
||||||
|
binaries.
|
||||||
|
|
||||||
|
## Installation and Usage
|
||||||
|
|
||||||
|
Package documentation can be found on
|
||||||
|
[GoDoc](http://godoc.org/github.com/hashicorp/go-getter).
|
||||||
|
|
||||||
|
Installation can be done with a normal `go get`:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go get github.com/hashicorp/go-getter
|
||||||
|
```
|
||||||
|
|
||||||
|
go-getter also has a command you can use to test URL strings:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go install github.com/hashicorp/go-getter/cmd/go-getter
|
||||||
|
...
|
||||||
|
|
||||||
|
$ go-getter github.com/foo/bar ./foo
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
The command is useful for verifying URL structures.
|
||||||
|
|
||||||
|
## URL Format
|
||||||
|
|
||||||
|
go-getter uses a single string URL as input to download from a variety of
|
||||||
|
protocols. go-getter has various "tricks" with this URL to do certain things.
|
||||||
|
This section documents the URL format.
|
||||||
|
|
||||||
|
### Supported Protocols and Detectors
|
||||||
|
|
||||||
|
**Protocols** are used to download files/directories using a specific
|
||||||
|
mechanism. Example protocols are Git and HTTP.
|
||||||
|
|
||||||
|
**Detectors** are used to transform a valid or invalid URL into another
|
||||||
|
URL if it matches a certain pattern. Example: "github.com/user/repo" is
|
||||||
|
automatically transformed into a fully valid Git URL. This allows go-getter
|
||||||
|
to be very user friendly.
|
||||||
|
|
||||||
|
go-getter out of the box supports the following protocols. Additional protocols
|
||||||
|
can be augmented at runtime by implementing the `Getter` interface.
|
||||||
|
|
||||||
|
* Local files
|
||||||
|
* Git
|
||||||
|
* Mercurial
|
||||||
|
* HTTP
|
||||||
|
* Amazon S3
|
||||||
|
|
||||||
|
In addition to the above protocols, go-getter has what are called "detectors."
|
||||||
|
These take a URL and attempt to automatically choose the best protocol for
|
||||||
|
it, which might involve even changing the protocol. The following detection
|
||||||
|
is built-in by default:
|
||||||
|
|
||||||
|
* File paths such as "./foo" are automatically changed to absolute
|
||||||
|
file URLs.
|
||||||
|
* GitHub URLs, such as "github.com/mitchellh/vagrant" are automatically
|
||||||
|
changed to Git protocol over HTTP.
|
||||||
|
* BitBucket URLs, such as "bitbucket.org/mitchellh/vagrant" are automatically
|
||||||
|
changed to a Git or mercurial protocol using the BitBucket API.
|
||||||
|
|
||||||
|
### Forced Protocol
|
||||||
|
|
||||||
|
In some cases, the protocol to use is ambiguous depending on the source
|
||||||
|
URL. For example, "http://github.com/mitchellh/vagrant.git" could reference
|
||||||
|
an HTTP URL or a Git URL. Forced protocol syntax is used to disambiguate this
|
||||||
|
URL.
|
||||||
|
|
||||||
|
Forced protocol can be done by prefixing the URL with the protocol followed
|
||||||
|
by double colons. For example: `git::http://github.com/mitchellh/vagrant.git`
|
||||||
|
would download the given HTTP URL using the Git protocol.
|
||||||
|
|
||||||
|
Forced protocols will also override any detectors.
|
||||||
|
|
||||||
|
In the absense of a forced protocol, detectors may be run on the URL, transforming
|
||||||
|
the protocol anyways. The above example would've used the Git protocol either
|
||||||
|
way since the Git detector would've detected it was a GitHub URL.
|
||||||
|
|
||||||
|
### Protocol-Specific Options
|
||||||
|
|
||||||
|
Each protocol can support protocol-specific options to configure that
|
||||||
|
protocol. For example, the `git` protocol supports specifying a `ref`
|
||||||
|
query parameter that tells it what ref to checkout for that Git
|
||||||
|
repository.
|
||||||
|
|
||||||
|
The options are specified as query parameters on the URL (or URL-like string)
|
||||||
|
given to go-getter. Using the Git example above, the URL below is a valid
|
||||||
|
input to go-getter:
|
||||||
|
|
||||||
|
github.com/hashicorp/go-getter?ref=abcd1234
|
||||||
|
|
||||||
|
The protocol-specific options are documented below the URL format
|
||||||
|
section. But because they are part of the URL, we point it out here so
|
||||||
|
you know they exist.
|
||||||
|
|
||||||
|
### Subdirectories
|
||||||
|
|
||||||
|
If you want to download only a specific subdirectory from a downloaded
|
||||||
|
directory, you can specify a subdirectory after a double-slash `//`.
|
||||||
|
go-getter will first download the URL specified _before_ the double-slash
|
||||||
|
(as if you didn't specify a double-slash), but will then copy the
|
||||||
|
path after the double slash into the target directory.
|
||||||
|
|
||||||
|
For example, if you're downloading this GitHub repository, but you only
|
||||||
|
want to download the `test-fixtures` directory, you can do the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://github.com/hashicorp/go-getter.git//test-fixtures
|
||||||
|
```
|
||||||
|
|
||||||
|
If you downloaded this to the `/tmp` directory, then the file
|
||||||
|
`/tmp/archive.gz` would exist. Notice that this file is in the `test-fixtures`
|
||||||
|
directory in this repository, but because we specified a subdirectory,
|
||||||
|
go-getter automatically copied only that directory contents.
|
||||||
|
|
||||||
|
Subdirectory paths may contain may also use filesystem glob patterns.
|
||||||
|
The path must match _exactly one_ entry or go-getter will return an error.
|
||||||
|
This is useful if you're not sure the exact directory name but it follows
|
||||||
|
a predictable naming structure.
|
||||||
|
|
||||||
|
For example, the following URL would also work:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://github.com/hashicorp/go-getter.git//test-*
|
||||||
|
```
|
||||||
|
|
||||||
|
### Checksumming
|
||||||
|
|
||||||
|
For file downloads of any protocol, go-getter can automatically verify
|
||||||
|
a checksum for you. Note that checksumming only works for downloading files,
|
||||||
|
not directories, but checksumming will work for any protocol.
|
||||||
|
|
||||||
|
To checksum a file, append a `checksum` query parameter to the URL.
|
||||||
|
The paramter value should be in the format of `type:value`, where
|
||||||
|
type is "md5", "sha1", "sha256", or "sha512". The "value" should be
|
||||||
|
the actual checksum value. go-getter will parse out this query parameter
|
||||||
|
automatically and use it to verify the checksum. An example URL
|
||||||
|
is shown below:
|
||||||
|
|
||||||
|
```
|
||||||
|
./foo.txt?checksum=md5:b7d96c89d09d9e204f5fedc4d5d55b21
|
||||||
|
```
|
||||||
|
|
||||||
|
The checksum query parameter is never sent to the backend protocol
|
||||||
|
implementation. It is used at a higher level by go-getter itself.
|
||||||
|
|
||||||
|
### Unarchiving
|
||||||
|
|
||||||
|
go-getter will automatically unarchive files into a file or directory
|
||||||
|
based on the extension of the file being requested (over any protocol).
|
||||||
|
This works for both file and directory downloads.
|
||||||
|
|
||||||
|
go-getter looks for an `archive` query parameter to specify the format of
|
||||||
|
the archive. If this isn't specified, go-getter will use the extension of
|
||||||
|
the path to see if it appears archived. Unarchiving can be explicitly
|
||||||
|
disabled by setting the `archive` query parameter to `false`.
|
||||||
|
|
||||||
|
The following archive formats are supported:
|
||||||
|
|
||||||
|
* `tar.gz` and `tgz`
|
||||||
|
* `tar.bz2` and `tbz2`
|
||||||
|
* `tar.xz` and `txz`
|
||||||
|
* `zip`
|
||||||
|
* `gz`
|
||||||
|
* `bz2`
|
||||||
|
* `xz`
|
||||||
|
|
||||||
|
For example, an example URL is shown below:
|
||||||
|
|
||||||
|
```
|
||||||
|
./foo.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
This will automatically be inferred to be a ZIP file and will be extracted.
|
||||||
|
You can also be explicit about the archive type:
|
||||||
|
|
||||||
|
```
|
||||||
|
./some/other/path?archive=zip
|
||||||
|
```
|
||||||
|
|
||||||
|
And finally, you can disable archiving completely:
|
||||||
|
|
||||||
|
```
|
||||||
|
./some/path?archive=false
|
||||||
|
```
|
||||||
|
|
||||||
|
You can combine unarchiving with the other features of go-getter such
|
||||||
|
as checksumming. The special `archive` query parameter will be removed
|
||||||
|
from the URL before going to the final protocol downloader.
|
||||||
|
|
||||||
|
## Protocol-Specific Options
|
||||||
|
|
||||||
|
This section documents the protocol-specific options that can be specified
|
||||||
|
for go-getter. These options should be appended to the input as normal query
|
||||||
|
parameters. Depending on the usage of go-getter, applications may provide
|
||||||
|
alternate ways of inputting options. For example, [Nomad](https://www.nomadproject.io)
|
||||||
|
provides a nice options block for specifying options rather than in the URL.
|
||||||
|
|
||||||
|
## General (All Protocols)
|
||||||
|
|
||||||
|
The options below are available to all protocols:
|
||||||
|
|
||||||
|
* `archive` - The archive format to use to unarchive this file, or "" (empty
|
||||||
|
string) to disable unarchiving. For more details, see the complete section
|
||||||
|
on archive support above.
|
||||||
|
|
||||||
|
* `checksum` - Checksum to verify the downloaded file or archive. See
|
||||||
|
the entire section on checksumming above for format and more details.
|
||||||
|
|
||||||
|
* `filename` - When in file download mode, allows specifying the name of the
|
||||||
|
downloaded file on disk. Has no effect in directory mode.
|
||||||
|
|
||||||
|
### Local Files (`file`)
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
|
### Git (`git`)
|
||||||
|
|
||||||
|
* `ref` - The Git ref to checkout. This is a ref, so it can point to
|
||||||
|
a commit SHA, a branch name, etc. If it is a named ref such as a branch
|
||||||
|
name, go-getter will update it to the latest on each get.
|
||||||
|
|
||||||
|
* `sshkey` - An SSH private key to use during clones. The provided key must
|
||||||
|
be a base64-encoded string. For example, to generate a suitable `sshkey`
|
||||||
|
from a private key file on disk, you would run `base64 -w0 <file>`.
|
||||||
|
|
||||||
|
**Note**: Git 2.3+ is required to use this feature.
|
||||||
|
|
||||||
|
### Mercurial (`hg`)
|
||||||
|
|
||||||
|
* `rev` - The Mercurial revision to checkout.
|
||||||
|
|
||||||
|
### HTTP (`http`)
|
||||||
|
|
||||||
|
#### Basic Authentication
|
||||||
|
|
||||||
|
To use HTTP basic authentication with go-getter, simply prepend `username:password@` to the
|
||||||
|
hostname in the URL such as `https://Aladdin:OpenSesame@www.example.com/index.html`. All special
|
||||||
|
characters, including the username and password, must be URL encoded.
|
||||||
|
|
||||||
|
### S3 (`s3`)
|
||||||
|
|
||||||
|
S3 takes various access configurations in the URL. Note that it will also
|
||||||
|
read these from standard AWS environment variables if they're set. S3 compliant servers like Minio
|
||||||
|
are also supported. If the query parameters are present, these take priority.
|
||||||
|
|
||||||
|
* `aws_access_key_id` - AWS access key.
|
||||||
|
* `aws_access_key_secret` - AWS access key secret.
|
||||||
|
* `aws_access_token` - AWS access token if this is being used.
|
||||||
|
|
||||||
|
#### Using IAM Instance Profiles with S3
|
||||||
|
|
||||||
|
If you use go-getter and want to use an EC2 IAM Instance Profile to avoid
|
||||||
|
using credentials, then just omit these and the profile, if available will
|
||||||
|
be used automatically.
|
||||||
|
|
||||||
|
### Using S3 with Minio
|
||||||
|
If you use go-gitter for Minio support, you must consider the following:
|
||||||
|
|
||||||
|
* `aws_access_key_id` (required) - Minio access key.
|
||||||
|
* `aws_access_key_secret` (required) - Minio access key secret.
|
||||||
|
* `region` (optional - defaults to us-east-1) - Region identifier to use.
|
||||||
|
* `version` (optional - defaults to Minio default) - Configuration file format.
|
||||||
|
|
||||||
|
#### S3 Bucket Examples
|
||||||
|
|
||||||
|
S3 has several addressing schemes used to reference your bucket. These are
|
||||||
|
listed here: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html#access-bucket-intro
|
||||||
|
|
||||||
|
Some examples for these addressing schemes:
|
||||||
|
- s3::https://s3.amazonaws.com/bucket/foo
|
||||||
|
- s3::https://s3-eu-west-1.amazonaws.com/bucket/foo
|
||||||
|
- bucket.s3.amazonaws.com/foo
|
||||||
|
- bucket.s3-eu-west-1.amazonaws.com/foo/bar
|
||||||
|
- "s3::http://127.0.0.1:9000/test-bucket/hello.txt?aws_access_key_id=KEYID&aws_access_key_secret=SECRETKEY®ion=us-east-2"
|
||||||
|
|
||||||
16
vendor/github.com/hashicorp/go-getter/appveyor.yml
generated
vendored
Normal file
16
vendor/github.com/hashicorp/go-getter/appveyor.yml
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
version: "build-{branch}-{build}"
|
||||||
|
image: Visual Studio 2017
|
||||||
|
clone_folder: c:\gopath\github.com\hashicorp\go-getter
|
||||||
|
environment:
|
||||||
|
GOPATH: c:\gopath
|
||||||
|
install:
|
||||||
|
- cmd: >-
|
||||||
|
echo %Path%
|
||||||
|
|
||||||
|
go version
|
||||||
|
|
||||||
|
go env
|
||||||
|
|
||||||
|
go get -d -v -t ./...
|
||||||
|
build_script:
|
||||||
|
- cmd: go test -v ./...
|
||||||
350
vendor/github.com/hashicorp/go-getter/client.go
generated
vendored
Normal file
350
vendor/github.com/hashicorp/go-getter/client.go
generated
vendored
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
urlhelper "github.com/hashicorp/go-getter/helper/url"
|
||||||
|
"github.com/hashicorp/go-safetemp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client is a client for downloading things.
|
||||||
|
//
|
||||||
|
// Top-level functions such as Get are shortcuts for interacting with a client.
|
||||||
|
// Using a client directly allows more fine-grained control over how downloading
|
||||||
|
// is done, as well as customizing the protocols supported.
|
||||||
|
type Client struct {
|
||||||
|
// Src is the source URL to get.
|
||||||
|
//
|
||||||
|
// Dst is the path to save the downloaded thing as. If Dir is set to
|
||||||
|
// true, then this should be a directory. If the directory doesn't exist,
|
||||||
|
// it will be created for you.
|
||||||
|
//
|
||||||
|
// Pwd is the working directory for detection. If this isn't set, some
|
||||||
|
// detection may fail. Client will not default pwd to the current
|
||||||
|
// working directory for security reasons.
|
||||||
|
Src string
|
||||||
|
Dst string
|
||||||
|
Pwd string
|
||||||
|
|
||||||
|
// Mode is the method of download the client will use. See ClientMode
|
||||||
|
// for documentation.
|
||||||
|
Mode ClientMode
|
||||||
|
|
||||||
|
// Detectors is the list of detectors that are tried on the source.
|
||||||
|
// If this is nil, then the default Detectors will be used.
|
||||||
|
Detectors []Detector
|
||||||
|
|
||||||
|
// Decompressors is the map of decompressors supported by this client.
|
||||||
|
// If this is nil, then the default value is the Decompressors global.
|
||||||
|
Decompressors map[string]Decompressor
|
||||||
|
|
||||||
|
// Getters is the map of protocols supported by this client. If this
|
||||||
|
// is nil, then the default Getters variable will be used.
|
||||||
|
Getters map[string]Getter
|
||||||
|
|
||||||
|
// Dir, if true, tells the Client it is downloading a directory (versus
|
||||||
|
// a single file). This distinction is necessary since filenames and
|
||||||
|
// directory names follow the same format so disambiguating is impossible
|
||||||
|
// without knowing ahead of time.
|
||||||
|
//
|
||||||
|
// WARNING: deprecated. If Mode is set, that will take precedence.
|
||||||
|
Dir bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get downloads the configured source to the destination.
|
||||||
|
func (c *Client) Get() error {
|
||||||
|
// Store this locally since there are cases we swap this
|
||||||
|
mode := c.Mode
|
||||||
|
if mode == ClientModeInvalid {
|
||||||
|
if c.Dir {
|
||||||
|
mode = ClientModeDir
|
||||||
|
} else {
|
||||||
|
mode = ClientModeFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default decompressor value
|
||||||
|
decompressors := c.Decompressors
|
||||||
|
if decompressors == nil {
|
||||||
|
decompressors = Decompressors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect the URL. This is safe if it is already detected.
|
||||||
|
detectors := c.Detectors
|
||||||
|
if detectors == nil {
|
||||||
|
detectors = Detectors
|
||||||
|
}
|
||||||
|
src, err := Detect(c.Src, c.Pwd, detectors)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if we have a forced protocol, i.e. "git::http://..."
|
||||||
|
force, src := getForcedGetter(src)
|
||||||
|
|
||||||
|
// If there is a subdir component, then we download the root separately
|
||||||
|
// and then copy over the proper subdir.
|
||||||
|
var realDst string
|
||||||
|
dst := c.Dst
|
||||||
|
src, subDir := SourceDirSubdir(src)
|
||||||
|
if subDir != "" {
|
||||||
|
td, tdcloser, err := safetemp.Dir("", "getter")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tdcloser.Close()
|
||||||
|
|
||||||
|
realDst = dst
|
||||||
|
dst = td
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := urlhelper.Parse(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if force == "" {
|
||||||
|
force = u.Scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
getters := c.Getters
|
||||||
|
if getters == nil {
|
||||||
|
getters = Getters
|
||||||
|
}
|
||||||
|
|
||||||
|
g, ok := getters[force]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"download not supported for scheme '%s'", force)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have magic query parameters that we use to signal different features
|
||||||
|
q := u.Query()
|
||||||
|
|
||||||
|
// Determine if we have an archive type
|
||||||
|
archiveV := q.Get("archive")
|
||||||
|
if archiveV != "" {
|
||||||
|
// Delete the paramter since it is a magic parameter we don't
|
||||||
|
// want to pass on to the Getter
|
||||||
|
q.Del("archive")
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
// If we can parse the value as a bool and it is false, then
|
||||||
|
// set the archive to "-" which should never map to a decompressor
|
||||||
|
if b, err := strconv.ParseBool(archiveV); err == nil && !b {
|
||||||
|
archiveV = "-"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if archiveV == "" {
|
||||||
|
// We don't appear to... but is it part of the filename?
|
||||||
|
matchingLen := 0
|
||||||
|
for k, _ := range decompressors {
|
||||||
|
if strings.HasSuffix(u.Path, "."+k) && len(k) > matchingLen {
|
||||||
|
archiveV = k
|
||||||
|
matchingLen = len(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a decompressor, then we need to change the destination
|
||||||
|
// to download to a temporary path. We unarchive this into the final,
|
||||||
|
// real path.
|
||||||
|
var decompressDst string
|
||||||
|
var decompressDir bool
|
||||||
|
decompressor := decompressors[archiveV]
|
||||||
|
if decompressor != nil {
|
||||||
|
// Create a temporary directory to store our archive. We delete
|
||||||
|
// this at the end of everything.
|
||||||
|
td, err := ioutil.TempDir("", "getter")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error creating temporary directory for archive: %s", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
|
||||||
|
// Swap the download directory to be our temporary path and
|
||||||
|
// store the old values.
|
||||||
|
decompressDst = dst
|
||||||
|
decompressDir = mode != ClientModeFile
|
||||||
|
dst = filepath.Join(td, "archive")
|
||||||
|
mode = ClientModeFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if we have a checksum
|
||||||
|
var checksumHash hash.Hash
|
||||||
|
var checksumValue []byte
|
||||||
|
if v := q.Get("checksum"); v != "" {
|
||||||
|
// Delete the query parameter if we have it.
|
||||||
|
q.Del("checksum")
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
// Determine the checksum hash type
|
||||||
|
checksumType := ""
|
||||||
|
idx := strings.Index(v, ":")
|
||||||
|
if idx > -1 {
|
||||||
|
checksumType = v[:idx]
|
||||||
|
}
|
||||||
|
switch checksumType {
|
||||||
|
case "md5":
|
||||||
|
checksumHash = md5.New()
|
||||||
|
case "sha1":
|
||||||
|
checksumHash = sha1.New()
|
||||||
|
case "sha256":
|
||||||
|
checksumHash = sha256.New()
|
||||||
|
case "sha512":
|
||||||
|
checksumHash = sha512.New()
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
"unsupported checksum type: %s", checksumType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the remainder of the value and parse it into bytes
|
||||||
|
b, err := hex.DecodeString(v[idx+1:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid checksum: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set our value
|
||||||
|
checksumValue = b
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode == ClientModeAny {
|
||||||
|
// Ask the getter which client mode to use
|
||||||
|
mode, err = g.ClientMode(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destination is the base name of the URL path in "any" mode when
|
||||||
|
// a file source is detected.
|
||||||
|
if mode == ClientModeFile {
|
||||||
|
filename := filepath.Base(u.Path)
|
||||||
|
|
||||||
|
// Determine if we have a custom file name
|
||||||
|
if v := q.Get("filename"); v != "" {
|
||||||
|
// Delete the query parameter if we have it.
|
||||||
|
q.Del("filename")
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
filename = v
|
||||||
|
}
|
||||||
|
|
||||||
|
dst = filepath.Join(dst, filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're not downloading a directory, then just download the file
|
||||||
|
// and return.
|
||||||
|
if mode == ClientModeFile {
|
||||||
|
err := g.GetFile(dst, u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if checksumHash != nil {
|
||||||
|
if err := checksum(dst, checksumHash, checksumValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if decompressor != nil {
|
||||||
|
// We have a decompressor, so decompress the current destination
|
||||||
|
// into the final destination with the proper mode.
|
||||||
|
err := decompressor.Decompress(decompressDst, dst, decompressDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap the information back
|
||||||
|
dst = decompressDst
|
||||||
|
if decompressDir {
|
||||||
|
mode = ClientModeAny
|
||||||
|
} else {
|
||||||
|
mode = ClientModeFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We check the dir value again because it can be switched back
|
||||||
|
// if we were unarchiving. If we're still only Get-ing a file, then
|
||||||
|
// we're done.
|
||||||
|
if mode == ClientModeFile {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're at this point we're either downloading a directory or we've
|
||||||
|
// downloaded and unarchived a directory and we're just checking subdir.
|
||||||
|
// In the case we have a decompressor we don't Get because it was Get
|
||||||
|
// above.
|
||||||
|
if decompressor == nil {
|
||||||
|
// If we're getting a directory, then this is an error. You cannot
|
||||||
|
// checksum a directory. TODO: test
|
||||||
|
if checksumHash != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"checksum cannot be specified for directory download")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're downloading a directory, which might require a bit more work
|
||||||
|
// if we're specifying a subdir.
|
||||||
|
err := g.Get(dst, u)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("error downloading '%s': %s", src, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a subdir, copy that over
|
||||||
|
if subDir != "" {
|
||||||
|
if err := os.RemoveAll(realDst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(realDst, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process any globs
|
||||||
|
subDir, err := SubdirGlob(dst, subDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyDir(realDst, subDir, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checksum is a simple method to compute the checksum of a source file
|
||||||
|
// and compare it to the given expected value.
|
||||||
|
func checksum(source string, h hash.Hash, v []byte) error {
|
||||||
|
f, err := os.Open(source)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to open file for checksum: %s", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(h, f); err != nil {
|
||||||
|
return fmt.Errorf("Failed to hash: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual := h.Sum(nil); !bytes.Equal(actual, v) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Checksums did not match.\nExpected: %s\nGot: %s",
|
||||||
|
hex.EncodeToString(v),
|
||||||
|
hex.EncodeToString(actual))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
24
vendor/github.com/hashicorp/go-getter/client_mode.go
generated
vendored
Normal file
24
vendor/github.com/hashicorp/go-getter/client_mode.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
// ClientMode is the mode that the client operates in.
|
||||||
|
type ClientMode uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
ClientModeInvalid ClientMode = iota
|
||||||
|
|
||||||
|
// ClientModeAny downloads anything it can. In this mode, dst must
|
||||||
|
// be a directory. If src is a file, it is saved into the directory
|
||||||
|
// with the basename of the URL. If src is a directory or archive,
|
||||||
|
// it is unpacked directly into dst.
|
||||||
|
ClientModeAny
|
||||||
|
|
||||||
|
// ClientModeFile downloads a single file. In this mode, dst must
|
||||||
|
// be a file path (doesn't have to exist). src must point to a single
|
||||||
|
// file. It is saved as dst.
|
||||||
|
ClientModeFile
|
||||||
|
|
||||||
|
// ClientModeDir downloads a directory. In this mode, dst must be
|
||||||
|
// a directory path (doesn't have to exist). src must point to an
|
||||||
|
// archive or directory (such as in s3).
|
||||||
|
ClientModeDir
|
||||||
|
)
|
||||||
78
vendor/github.com/hashicorp/go-getter/copy_dir.go
generated
vendored
Normal file
78
vendor/github.com/hashicorp/go-getter/copy_dir.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// copyDir copies the src directory contents into dst. Both directories
|
||||||
|
// should already exist.
|
||||||
|
//
|
||||||
|
// If ignoreDot is set to true, then dot-prefixed files/folders are ignored.
|
||||||
|
func copyDir(dst string, src string, ignoreDot bool) error {
|
||||||
|
src, err := filepath.EvalSymlinks(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
walkFn := func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if path == src {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ignoreDot && strings.HasPrefix(filepath.Base(path), ".") {
|
||||||
|
// Skip any dot files
|
||||||
|
if info.IsDir() {
|
||||||
|
return filepath.SkipDir
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "path" has the src prefixed to it. We need to join our
|
||||||
|
// destination with the path without the src on it.
|
||||||
|
dstPath := filepath.Join(dst, path[len(src):])
|
||||||
|
|
||||||
|
// If we have a directory, make that subdirectory, then continue
|
||||||
|
// the walk.
|
||||||
|
if info.IsDir() {
|
||||||
|
if path == filepath.Join(src, dst) {
|
||||||
|
// dst is in src; don't walk it.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(dstPath, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a file, copy the contents.
|
||||||
|
srcF, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcF.Close()
|
||||||
|
|
||||||
|
dstF, err := os.Create(dstPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dstF.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(dstF, srcF); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chmod it
|
||||||
|
return os.Chmod(dstPath, info.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Walk(src, walkFn)
|
||||||
|
}
|
||||||
58
vendor/github.com/hashicorp/go-getter/decompress.go
generated
vendored
Normal file
58
vendor/github.com/hashicorp/go-getter/decompress.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decompressor defines the interface that must be implemented to add
|
||||||
|
// support for decompressing a type.
|
||||||
|
//
|
||||||
|
// Important: if you're implementing a decompressor, please use the
|
||||||
|
// containsDotDot helper in this file to ensure that files can't be
|
||||||
|
// decompressed outside of the specified directory.
|
||||||
|
type Decompressor interface {
|
||||||
|
// Decompress should decompress src to dst. dir specifies whether dst
|
||||||
|
// is a directory or single file. src is guaranteed to be a single file
|
||||||
|
// that exists. dst is not guaranteed to exist already.
|
||||||
|
Decompress(dst, src string, dir bool) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decompressors is the mapping of extension to the Decompressor implementation
|
||||||
|
// that will decompress that extension/type.
|
||||||
|
var Decompressors map[string]Decompressor
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tbzDecompressor := new(TarBzip2Decompressor)
|
||||||
|
tgzDecompressor := new(TarGzipDecompressor)
|
||||||
|
txzDecompressor := new(TarXzDecompressor)
|
||||||
|
|
||||||
|
Decompressors = map[string]Decompressor{
|
||||||
|
"bz2": new(Bzip2Decompressor),
|
||||||
|
"gz": new(GzipDecompressor),
|
||||||
|
"xz": new(XzDecompressor),
|
||||||
|
"tar.bz2": tbzDecompressor,
|
||||||
|
"tar.gz": tgzDecompressor,
|
||||||
|
"tar.xz": txzDecompressor,
|
||||||
|
"tbz2": tbzDecompressor,
|
||||||
|
"tgz": tgzDecompressor,
|
||||||
|
"txz": txzDecompressor,
|
||||||
|
"zip": new(ZipDecompressor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// containsDotDot checks if the filepath value v contains a ".." entry.
|
||||||
|
// This will check filepath components by splitting along / or \. This
|
||||||
|
// function is copied directly from the Go net/http implementation.
|
||||||
|
func containsDotDot(v string) bool {
|
||||||
|
if !strings.Contains(v, "..") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, ent := range strings.FieldsFunc(v, isSlashRune) {
|
||||||
|
if ent == ".." {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSlashRune(r rune) bool { return r == '/' || r == '\\' }
|
||||||
45
vendor/github.com/hashicorp/go-getter/decompress_bzip2.go
generated
vendored
Normal file
45
vendor/github.com/hashicorp/go-getter/decompress_bzip2.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/bzip2"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bzip2Decompressor is an implementation of Decompressor that can
|
||||||
|
// decompress bz2 files.
|
||||||
|
type Bzip2Decompressor struct{}
|
||||||
|
|
||||||
|
func (d *Bzip2Decompressor) Decompress(dst, src string, dir bool) error {
|
||||||
|
// Directory isn't supported at all
|
||||||
|
if dir {
|
||||||
|
return fmt.Errorf("bzip2-compressed files can only unarchive to a single file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're going into a directory we should make that first
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// File first
|
||||||
|
f, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Bzip2 compression is second
|
||||||
|
bzipR := bzip2.NewReader(f)
|
||||||
|
|
||||||
|
// Copy it out
|
||||||
|
dstF, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dstF.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(dstF, bzipR)
|
||||||
|
return err
|
||||||
|
}
|
||||||
34
vendor/github.com/hashicorp/go-getter/decompress_bzip2_test.go
generated
vendored
Normal file
34
vendor/github.com/hashicorp/go-getter/decompress_bzip2_test.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBzip2Decompressor(t *testing.T) {
|
||||||
|
cases := []TestDecompressCase{
|
||||||
|
{
|
||||||
|
"single.bz2",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
"d3b07384d113edec49eaa6238ad5ff00",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"single.bz2",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
cases[i].Input = filepath.Join("./test-fixtures", "decompress-bz2", tc.Input)
|
||||||
|
}
|
||||||
|
|
||||||
|
TestDecompressor(t, new(Bzip2Decompressor), cases)
|
||||||
|
}
|
||||||
49
vendor/github.com/hashicorp/go-getter/decompress_gzip.go
generated
vendored
Normal file
49
vendor/github.com/hashicorp/go-getter/decompress_gzip.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GzipDecompressor is an implementation of Decompressor that can
|
||||||
|
// decompress gzip files.
|
||||||
|
type GzipDecompressor struct{}
|
||||||
|
|
||||||
|
func (d *GzipDecompressor) Decompress(dst, src string, dir bool) error {
|
||||||
|
// Directory isn't supported at all
|
||||||
|
if dir {
|
||||||
|
return fmt.Errorf("gzip-compressed files can only unarchive to a single file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're going into a directory we should make that first
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// File first
|
||||||
|
f, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// gzip compression is second
|
||||||
|
gzipR, err := gzip.NewReader(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer gzipR.Close()
|
||||||
|
|
||||||
|
// Copy it out
|
||||||
|
dstF, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dstF.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(dstF, gzipR)
|
||||||
|
return err
|
||||||
|
}
|
||||||
34
vendor/github.com/hashicorp/go-getter/decompress_gzip_test.go
generated
vendored
Normal file
34
vendor/github.com/hashicorp/go-getter/decompress_gzip_test.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGzipDecompressor(t *testing.T) {
|
||||||
|
cases := []TestDecompressCase{
|
||||||
|
{
|
||||||
|
"single.gz",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
"d3b07384d113edec49eaa6238ad5ff00",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"single.gz",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
cases[i].Input = filepath.Join("./test-fixtures", "decompress-gz", tc.Input)
|
||||||
|
}
|
||||||
|
|
||||||
|
TestDecompressor(t, new(GzipDecompressor), cases)
|
||||||
|
}
|
||||||
160
vendor/github.com/hashicorp/go-getter/decompress_tar.go
generated
vendored
Normal file
160
vendor/github.com/hashicorp/go-getter/decompress_tar.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// untar is a shared helper for untarring an archive. The reader should provide
|
||||||
|
// an uncompressed view of the tar archive.
|
||||||
|
func untar(input io.Reader, dst, src string, dir bool) error {
|
||||||
|
tarR := tar.NewReader(input)
|
||||||
|
done := false
|
||||||
|
dirHdrs := []*tar.Header{}
|
||||||
|
now := time.Now()
|
||||||
|
for {
|
||||||
|
hdr, err := tarR.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
if !done {
|
||||||
|
// Empty archive
|
||||||
|
return fmt.Errorf("empty archive: %s", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if hdr.Typeflag == tar.TypeXGlobalHeader || hdr.Typeflag == tar.TypeXHeader {
|
||||||
|
// don't unpack extended headers as files
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
path := dst
|
||||||
|
if dir {
|
||||||
|
// Disallow parent traversal
|
||||||
|
if containsDotDot(hdr.Name) {
|
||||||
|
return fmt.Errorf("entry contains '..': %s", hdr.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
path = filepath.Join(path, hdr.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hdr.FileInfo().IsDir() {
|
||||||
|
if !dir {
|
||||||
|
return fmt.Errorf("expected a single file: %s", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A directory, just make the directory and continue unarchiving...
|
||||||
|
if err := os.MkdirAll(path, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record the directory information so that we may set its attributes
|
||||||
|
// after all files have been extracted
|
||||||
|
dirHdrs = append(dirHdrs, hdr)
|
||||||
|
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
// There is no ordering guarantee that a file in a directory is
|
||||||
|
// listed before the directory
|
||||||
|
dstPath := filepath.Dir(path)
|
||||||
|
|
||||||
|
// Check that the directory exists, otherwise create it
|
||||||
|
if _, err := os.Stat(dstPath); os.IsNotExist(err) {
|
||||||
|
if err := os.MkdirAll(dstPath, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a file. If we already decoded, then it is an error
|
||||||
|
if !dir && done {
|
||||||
|
return fmt.Errorf("expected a single file, got multiple: %s", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark that we're done so future in single file mode errors
|
||||||
|
done = true
|
||||||
|
|
||||||
|
// Open the file for writing
|
||||||
|
dstF, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(dstF, tarR)
|
||||||
|
dstF.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chmod the file
|
||||||
|
if err := os.Chmod(path, hdr.FileInfo().Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the access and modification time if valid, otherwise default to current time
|
||||||
|
aTime := now
|
||||||
|
mTime := now
|
||||||
|
if hdr.AccessTime.Unix() > 0 {
|
||||||
|
aTime = hdr.AccessTime
|
||||||
|
}
|
||||||
|
if hdr.ModTime.Unix() > 0 {
|
||||||
|
mTime = hdr.ModTime
|
||||||
|
}
|
||||||
|
if err := os.Chtimes(path, aTime, mTime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform a final pass over extracted directories to update metadata
|
||||||
|
for _, dirHdr := range dirHdrs {
|
||||||
|
path := filepath.Join(dst, dirHdr.Name)
|
||||||
|
// Chmod the directory since they might be created before we know the mode flags
|
||||||
|
if err := os.Chmod(path, dirHdr.FileInfo().Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Set the mtime/atime attributes since they would have been changed during extraction
|
||||||
|
aTime := now
|
||||||
|
mTime := now
|
||||||
|
if dirHdr.AccessTime.Unix() > 0 {
|
||||||
|
aTime = dirHdr.AccessTime
|
||||||
|
}
|
||||||
|
if dirHdr.ModTime.Unix() > 0 {
|
||||||
|
mTime = dirHdr.ModTime
|
||||||
|
}
|
||||||
|
if err := os.Chtimes(path, aTime, mTime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tarDecompressor is an implementation of Decompressor that can
|
||||||
|
// unpack tar files.
|
||||||
|
type tarDecompressor struct{}
|
||||||
|
|
||||||
|
func (d *tarDecompressor) Decompress(dst, src string, dir bool) error {
|
||||||
|
// If we're going into a directory we should make that first
|
||||||
|
mkdir := dst
|
||||||
|
if !dir {
|
||||||
|
mkdir = filepath.Dir(dst)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(mkdir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// File first
|
||||||
|
f, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
return untar(f, dst, src, dir)
|
||||||
|
}
|
||||||
43
vendor/github.com/hashicorp/go-getter/decompress_tar_test.go
generated
vendored
Normal file
43
vendor/github.com/hashicorp/go-getter/decompress_tar_test.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTar(t *testing.T) {
|
||||||
|
mtime := time.Unix(0, 0)
|
||||||
|
cases := []TestDecompressCase{
|
||||||
|
{
|
||||||
|
"extended_header.tar",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
[]string{"directory/", "directory/a", "directory/b"},
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"implied_dir.tar",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
[]string{"directory/", "directory/sub/", "directory/sub/a", "directory/sub/b"},
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unix_time_0.tar",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
[]string{"directory/", "directory/sub/", "directory/sub/a", "directory/sub/b"},
|
||||||
|
"",
|
||||||
|
&mtime,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
cases[i].Input = filepath.Join("./test-fixtures", "decompress-tar", tc.Input)
|
||||||
|
}
|
||||||
|
|
||||||
|
TestDecompressor(t, new(tarDecompressor), cases)
|
||||||
|
}
|
||||||
33
vendor/github.com/hashicorp/go-getter/decompress_tbz2.go
generated
vendored
Normal file
33
vendor/github.com/hashicorp/go-getter/decompress_tbz2.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/bzip2"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TarBzip2Decompressor is an implementation of Decompressor that can
|
||||||
|
// decompress tar.bz2 files.
|
||||||
|
type TarBzip2Decompressor struct{}
|
||||||
|
|
||||||
|
func (d *TarBzip2Decompressor) Decompress(dst, src string, dir bool) error {
|
||||||
|
// If we're going into a directory we should make that first
|
||||||
|
mkdir := dst
|
||||||
|
if !dir {
|
||||||
|
mkdir = filepath.Dir(dst)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(mkdir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// File first
|
||||||
|
f, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Bzip2 compression is second
|
||||||
|
bzipR := bzip2.NewReader(f)
|
||||||
|
return untar(bzipR, dst, src, dir)
|
||||||
|
}
|
||||||
73
vendor/github.com/hashicorp/go-getter/decompress_tbz2_test.go
generated
vendored
Normal file
73
vendor/github.com/hashicorp/go-getter/decompress_tbz2_test.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTarBzip2Decompressor(t *testing.T) {
|
||||||
|
orderingPaths := []string{"workers/", "workers/mq/", "workers/mq/__init__.py"}
|
||||||
|
|
||||||
|
cases := []TestDecompressCase{
|
||||||
|
{
|
||||||
|
"empty.tar.bz2",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"single.tar.bz2",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
"d3b07384d113edec49eaa6238ad5ff00",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"single.tar.bz2",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
[]string{"file"},
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"multiple.tar.bz2",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
[]string{"file1", "file2"},
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"multiple.tar.bz2",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tests when the file is listed before the parent folder
|
||||||
|
{
|
||||||
|
"ordering.tar.bz2",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
orderingPaths,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
cases[i].Input = filepath.Join("./test-fixtures", "decompress-tbz2", tc.Input)
|
||||||
|
}
|
||||||
|
|
||||||
|
TestDecompressor(t, new(TarBzip2Decompressor), cases)
|
||||||
|
}
|
||||||
169
vendor/github.com/hashicorp/go-getter/decompress_testing.go
generated
vendored
Normal file
169
vendor/github.com/hashicorp/go-getter/decompress_testing.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/go-testing-interface"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestDecompressCase is a single test case for testing decompressors
|
||||||
|
type TestDecompressCase struct {
|
||||||
|
Input string // Input is the complete path to the input file
|
||||||
|
Dir bool // Dir is whether or not we're testing directory mode
|
||||||
|
Err bool // Err is whether we expect an error or not
|
||||||
|
DirList []string // DirList is the list of files for Dir mode
|
||||||
|
FileMD5 string // FileMD5 is the expected MD5 for a single file
|
||||||
|
Mtime *time.Time // Mtime is the optionally expected mtime for a single file (or all files if in Dir mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDecompressor is a helper function for testing generic decompressors.
|
||||||
|
func TestDecompressor(t testing.T, d Decompressor, cases []TestDecompressCase) {
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Logf("Testing: %s", tc.Input)
|
||||||
|
|
||||||
|
// Temporary dir to store stuff
|
||||||
|
td, err := ioutil.TempDir("", "getter")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destination is always joining result so that we have a new path
|
||||||
|
dst := filepath.Join(td, "subdir", "result")
|
||||||
|
|
||||||
|
// We use a function so defers work
|
||||||
|
func() {
|
||||||
|
defer os.RemoveAll(td)
|
||||||
|
|
||||||
|
// Decompress
|
||||||
|
err := d.Decompress(dst, tc.Input, tc.Dir)
|
||||||
|
if (err != nil) != tc.Err {
|
||||||
|
t.Fatalf("err %s: %s", tc.Input, err)
|
||||||
|
}
|
||||||
|
if tc.Err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it isn't a directory, then check for a single file
|
||||||
|
if !tc.Dir {
|
||||||
|
fi, err := os.Stat(dst)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err %s: %s", tc.Input, err)
|
||||||
|
}
|
||||||
|
if fi.IsDir() {
|
||||||
|
t.Fatalf("err %s: expected file, got directory", tc.Input)
|
||||||
|
}
|
||||||
|
if tc.FileMD5 != "" {
|
||||||
|
actual := testMD5(t, dst)
|
||||||
|
expected := tc.FileMD5
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("err %s: expected MD5 %s, got %s", tc.Input, expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.Mtime != nil {
|
||||||
|
actual := fi.ModTime()
|
||||||
|
if tc.Mtime.Unix() > 0 {
|
||||||
|
expected := *tc.Mtime
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("err %s: expected mtime '%s' for %s, got '%s'", tc.Input, expected.String(), dst, actual.String())
|
||||||
|
}
|
||||||
|
} else if actual.Unix() <= 0 {
|
||||||
|
t.Fatalf("err %s: expected mtime to be > 0, got '%s'", actual.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert expected for windows
|
||||||
|
expected := tc.DirList
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
for i, v := range expected {
|
||||||
|
expected[i] = strings.Replace(v, "/", "\\", -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directory, check for the correct contents
|
||||||
|
actual := testListDir(t, dst)
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("bad %s\n\n%#v\n\n%#v", tc.Input, actual, expected)
|
||||||
|
}
|
||||||
|
// Check for correct atime/mtime
|
||||||
|
for _, dir := range actual {
|
||||||
|
path := filepath.Join(dst, dir)
|
||||||
|
if tc.Mtime != nil {
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
actual := fi.ModTime()
|
||||||
|
if tc.Mtime.Unix() > 0 {
|
||||||
|
expected := *tc.Mtime
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("err %s: expected mtime '%s' for %s, got '%s'", tc.Input, expected.String(), path, actual.String())
|
||||||
|
}
|
||||||
|
} else if actual.Unix() < 0 {
|
||||||
|
t.Fatalf("err %s: expected mtime to be > 0, got '%s'", actual.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testListDir(t testing.T, path string) []string {
|
||||||
|
var result []string
|
||||||
|
err := filepath.Walk(path, func(sub string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sub = strings.TrimPrefix(sub, path)
|
||||||
|
if sub == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
sub = sub[1:] // Trim the leading path sep.
|
||||||
|
|
||||||
|
// If it is a dir, add trailing sep
|
||||||
|
if info.IsDir() {
|
||||||
|
sub += string(os.PathSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, sub)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMD5(t testing.T, path string) string {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
h := md5.New()
|
||||||
|
_, err = io.Copy(h, f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := h.Sum(nil)
|
||||||
|
return hex.EncodeToString(result)
|
||||||
|
}
|
||||||
39
vendor/github.com/hashicorp/go-getter/decompress_tgz.go
generated
vendored
Normal file
39
vendor/github.com/hashicorp/go-getter/decompress_tgz.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TarGzipDecompressor is an implementation of Decompressor that can
|
||||||
|
// decompress tar.gzip files.
|
||||||
|
type TarGzipDecompressor struct{}
|
||||||
|
|
||||||
|
func (d *TarGzipDecompressor) Decompress(dst, src string, dir bool) error {
|
||||||
|
// If we're going into a directory we should make that first
|
||||||
|
mkdir := dst
|
||||||
|
if !dir {
|
||||||
|
mkdir = filepath.Dir(dst)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(mkdir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// File first
|
||||||
|
f, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Gzip compression is second
|
||||||
|
gzipR, err := gzip.NewReader(f)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error opening a gzip reader for %s: %s", src, err)
|
||||||
|
}
|
||||||
|
defer gzipR.Close()
|
||||||
|
|
||||||
|
return untar(gzipR, dst, src, dir)
|
||||||
|
}
|
||||||
95
vendor/github.com/hashicorp/go-getter/decompress_tgz_test.go
generated
vendored
Normal file
95
vendor/github.com/hashicorp/go-getter/decompress_tgz_test.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTarGzipDecompressor(t *testing.T) {
|
||||||
|
|
||||||
|
multiplePaths := []string{"dir/", "dir/test2", "test1"}
|
||||||
|
orderingPaths := []string{"workers/", "workers/mq/", "workers/mq/__init__.py"}
|
||||||
|
|
||||||
|
cases := []TestDecompressCase{
|
||||||
|
{
|
||||||
|
"empty.tar.gz",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"single.tar.gz",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
"d3b07384d113edec49eaa6238ad5ff00",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"single.tar.gz",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
[]string{"file"},
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"multiple.tar.gz",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
[]string{"file1", "file2"},
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"multiple.tar.gz",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"multiple_dir.tar.gz",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
multiplePaths,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tests when the file is listed before the parent folder
|
||||||
|
{
|
||||||
|
"ordering.tar.gz",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
orderingPaths,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tests that a tar.gz can't contain references with "..".
|
||||||
|
// GNU `tar` also disallows this.
|
||||||
|
{
|
||||||
|
"outside_parent.tar.gz",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
cases[i].Input = filepath.Join("./test-fixtures", "decompress-tgz", tc.Input)
|
||||||
|
}
|
||||||
|
|
||||||
|
TestDecompressor(t, new(TarGzipDecompressor), cases)
|
||||||
|
}
|
||||||
39
vendor/github.com/hashicorp/go-getter/decompress_txz.go
generated
vendored
Normal file
39
vendor/github.com/hashicorp/go-getter/decompress_txz.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/ulikunitz/xz"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TarXzDecompressor is an implementation of Decompressor that can
|
||||||
|
// decompress tar.xz files.
|
||||||
|
type TarXzDecompressor struct{}
|
||||||
|
|
||||||
|
func (d *TarXzDecompressor) Decompress(dst, src string, dir bool) error {
|
||||||
|
// If we're going into a directory we should make that first
|
||||||
|
mkdir := dst
|
||||||
|
if !dir {
|
||||||
|
mkdir = filepath.Dir(dst)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(mkdir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// File first
|
||||||
|
f, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// xz compression is second
|
||||||
|
txzR, err := xz.NewReader(f)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error opening an xz reader for %s: %s", src, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return untar(txzR, dst, src, dir)
|
||||||
|
}
|
||||||
84
vendor/github.com/hashicorp/go-getter/decompress_txz_test.go
generated
vendored
Normal file
84
vendor/github.com/hashicorp/go-getter/decompress_txz_test.go
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTarXzDecompressor(t *testing.T) {
|
||||||
|
|
||||||
|
multiplePaths := []string{"dir/", "dir/test2", "test1"}
|
||||||
|
orderingPaths := []string{"workers/", "workers/mq/", "workers/mq/__init__.py"}
|
||||||
|
|
||||||
|
cases := []TestDecompressCase{
|
||||||
|
{
|
||||||
|
"empty.tar.xz",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"single.tar.xz",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
"d3b07384d113edec49eaa6238ad5ff00",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"single.tar.xz",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
[]string{"file"},
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"multiple.tar.xz",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
[]string{"file1", "file2"},
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"multiple.tar.xz",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"multiple_dir.tar.xz",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
multiplePaths,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tests when the file is listed before the parent folder
|
||||||
|
{
|
||||||
|
"ordering.tar.xz",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
orderingPaths,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
cases[i].Input = filepath.Join("./test-fixtures", "decompress-txz", tc.Input)
|
||||||
|
}
|
||||||
|
|
||||||
|
TestDecompressor(t, new(TarXzDecompressor), cases)
|
||||||
|
}
|
||||||
49
vendor/github.com/hashicorp/go-getter/decompress_xz.go
generated
vendored
Normal file
49
vendor/github.com/hashicorp/go-getter/decompress_xz.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/ulikunitz/xz"
|
||||||
|
)
|
||||||
|
|
||||||
|
// XzDecompressor is an implementation of Decompressor that can
|
||||||
|
// decompress xz files.
|
||||||
|
type XzDecompressor struct{}
|
||||||
|
|
||||||
|
func (d *XzDecompressor) Decompress(dst, src string, dir bool) error {
|
||||||
|
// Directory isn't supported at all
|
||||||
|
if dir {
|
||||||
|
return fmt.Errorf("xz-compressed files can only unarchive to a single file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're going into a directory we should make that first
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// File first
|
||||||
|
f, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// xz compression is second
|
||||||
|
xzR, err := xz.NewReader(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy it out
|
||||||
|
dstF, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dstF.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(dstF, xzR)
|
||||||
|
return err
|
||||||
|
}
|
||||||
34
vendor/github.com/hashicorp/go-getter/decompress_xz_test.go
generated
vendored
Normal file
34
vendor/github.com/hashicorp/go-getter/decompress_xz_test.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestXzDecompressor(t *testing.T) {
|
||||||
|
cases := []TestDecompressCase{
|
||||||
|
{
|
||||||
|
"single.xz",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
"d3b07384d113edec49eaa6238ad5ff00",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"single.xz",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
cases[i].Input = filepath.Join("./test-fixtures", "decompress-xz", tc.Input)
|
||||||
|
}
|
||||||
|
|
||||||
|
TestDecompressor(t, new(XzDecompressor), cases)
|
||||||
|
}
|
||||||
101
vendor/github.com/hashicorp/go-getter/decompress_zip.go
generated
vendored
Normal file
101
vendor/github.com/hashicorp/go-getter/decompress_zip.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ZipDecompressor is an implementation of Decompressor that can
|
||||||
|
// decompress tar.gzip files.
|
||||||
|
type ZipDecompressor struct{}
|
||||||
|
|
||||||
|
func (d *ZipDecompressor) Decompress(dst, src string, dir bool) error {
|
||||||
|
// If we're going into a directory we should make that first
|
||||||
|
mkdir := dst
|
||||||
|
if !dir {
|
||||||
|
mkdir = filepath.Dir(dst)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(mkdir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the zip
|
||||||
|
zipR, err := zip.OpenReader(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer zipR.Close()
|
||||||
|
|
||||||
|
// Check the zip integrity
|
||||||
|
if len(zipR.File) == 0 {
|
||||||
|
// Empty archive
|
||||||
|
return fmt.Errorf("empty archive: %s", src)
|
||||||
|
}
|
||||||
|
if !dir && len(zipR.File) > 1 {
|
||||||
|
return fmt.Errorf("expected a single file: %s", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through and unarchive
|
||||||
|
for _, f := range zipR.File {
|
||||||
|
path := dst
|
||||||
|
if dir {
|
||||||
|
// Disallow parent traversal
|
||||||
|
if containsDotDot(f.Name) {
|
||||||
|
return fmt.Errorf("entry contains '..': %s", f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
path = filepath.Join(path, f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.FileInfo().IsDir() {
|
||||||
|
if !dir {
|
||||||
|
return fmt.Errorf("expected a single file: %s", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A directory, just make the directory and continue unarchiving...
|
||||||
|
if err := os.MkdirAll(path, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the enclosing directories if we must. ZIP files aren't
|
||||||
|
// required to contain entries for just the directories so this
|
||||||
|
// can happen.
|
||||||
|
if dir {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the file for reading
|
||||||
|
srcF, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the file for writing
|
||||||
|
dstF, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
srcF.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(dstF, srcF)
|
||||||
|
srcF.Close()
|
||||||
|
dstF.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chmod the file
|
||||||
|
if err := os.Chmod(path, f.Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
98
vendor/github.com/hashicorp/go-getter/decompress_zip_test.go
generated
vendored
Normal file
98
vendor/github.com/hashicorp/go-getter/decompress_zip_test.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestZipDecompressor(t *testing.T) {
|
||||||
|
cases := []TestDecompressCase{
|
||||||
|
{
|
||||||
|
"empty.zip",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"single.zip",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
"d3b07384d113edec49eaa6238ad5ff00",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"single.zip",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
[]string{"file"},
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"multiple.zip",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
[]string{"file1", "file2"},
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"multiple.zip",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"subdir.zip",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
[]string{"file1", "subdir/", "subdir/child"},
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"subdir_empty.zip",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
[]string{"file1", "subdir/"},
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"subdir_missing_dir.zip",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
[]string{"file1", "subdir/", "subdir/child"},
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tests that a zip can't contain references with "..".
|
||||||
|
{
|
||||||
|
"outside_parent.zip",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
cases[i].Input = filepath.Join("./test-fixtures", "decompress-zip", tc.Input)
|
||||||
|
}
|
||||||
|
|
||||||
|
TestDecompressor(t, new(ZipDecompressor), cases)
|
||||||
|
}
|
||||||
103
vendor/github.com/hashicorp/go-getter/detect.go
generated
vendored
Normal file
103
vendor/github.com/hashicorp/go-getter/detect.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-getter/helper/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Detector defines the interface that an invalid URL or a URL with a blank
|
||||||
|
// scheme is passed through in order to determine if its shorthand for
|
||||||
|
// something else well-known.
|
||||||
|
type Detector interface {
|
||||||
|
// Detect will detect whether the string matches a known pattern to
|
||||||
|
// turn it into a proper URL.
|
||||||
|
Detect(string, string) (string, bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detectors is the list of detectors that are tried on an invalid URL.
|
||||||
|
// This is also the order they're tried (index 0 is first).
|
||||||
|
var Detectors []Detector
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Detectors = []Detector{
|
||||||
|
new(GitHubDetector),
|
||||||
|
new(BitBucketDetector),
|
||||||
|
new(S3Detector),
|
||||||
|
new(FileDetector),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect turns a source string into another source string if it is
|
||||||
|
// detected to be of a known pattern.
|
||||||
|
//
|
||||||
|
// The third parameter should be the list of detectors to use in the
|
||||||
|
// order to try them. If you don't want to configure this, just use
|
||||||
|
// the global Detectors variable.
|
||||||
|
//
|
||||||
|
// This is safe to be called with an already valid source string: Detect
|
||||||
|
// will just return it.
|
||||||
|
func Detect(src string, pwd string, ds []Detector) (string, error) {
|
||||||
|
getForce, getSrc := getForcedGetter(src)
|
||||||
|
|
||||||
|
// Separate out the subdir if there is one, we don't pass that to detect
|
||||||
|
getSrc, subDir := SourceDirSubdir(getSrc)
|
||||||
|
|
||||||
|
u, err := url.Parse(getSrc)
|
||||||
|
if err == nil && u.Scheme != "" {
|
||||||
|
// Valid URL
|
||||||
|
return src, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range ds {
|
||||||
|
result, ok, err := d.Detect(getSrc, pwd)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var detectForce string
|
||||||
|
detectForce, result = getForcedGetter(result)
|
||||||
|
result, detectSubdir := SourceDirSubdir(result)
|
||||||
|
|
||||||
|
// If we have a subdir from the detection, then prepend it to our
|
||||||
|
// requested subdir.
|
||||||
|
if detectSubdir != "" {
|
||||||
|
if subDir != "" {
|
||||||
|
subDir = filepath.Join(detectSubdir, subDir)
|
||||||
|
} else {
|
||||||
|
subDir = detectSubdir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if subDir != "" {
|
||||||
|
u, err := url.Parse(result)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Error parsing URL: %s", err)
|
||||||
|
}
|
||||||
|
u.Path += "//" + subDir
|
||||||
|
|
||||||
|
// a subdir may contain wildcards, but in order to support them we
|
||||||
|
// have to ensure the path isn't escaped.
|
||||||
|
u.RawPath = u.Path
|
||||||
|
|
||||||
|
result = u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve the forced getter if it exists. We try to use the
|
||||||
|
// original set force first, followed by any force set by the
|
||||||
|
// detector.
|
||||||
|
if getForce != "" {
|
||||||
|
result = fmt.Sprintf("%s::%s", getForce, result)
|
||||||
|
} else if detectForce != "" {
|
||||||
|
result = fmt.Sprintf("%s::%s", detectForce, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("invalid source string: %s", src)
|
||||||
|
}
|
||||||
66
vendor/github.com/hashicorp/go-getter/detect_bitbucket.go
generated
vendored
Normal file
66
vendor/github.com/hashicorp/go-getter/detect_bitbucket.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BitBucketDetector implements Detector to detect BitBucket URLs and turn
|
||||||
|
// them into URLs that the Git or Hg Getter can understand.
|
||||||
|
type BitBucketDetector struct{}
|
||||||
|
|
||||||
|
func (d *BitBucketDetector) Detect(src, _ string) (string, bool, error) {
|
||||||
|
if len(src) == 0 {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(src, "bitbucket.org/") {
|
||||||
|
return d.detectHTTP(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BitBucketDetector) detectHTTP(src string) (string, bool, error) {
|
||||||
|
u, err := url.Parse("https://" + src)
|
||||||
|
if err != nil {
|
||||||
|
return "", true, fmt.Errorf("error parsing BitBucket URL: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to get info on this BitBucket repository to determine whether
|
||||||
|
// it is Git or Hg.
|
||||||
|
var info struct {
|
||||||
|
SCM string `json:"scm"`
|
||||||
|
}
|
||||||
|
infoUrl := "https://api.bitbucket.org/1.0/repositories" + u.Path
|
||||||
|
resp, err := http.Get(infoUrl)
|
||||||
|
if err != nil {
|
||||||
|
return "", true, fmt.Errorf("error looking up BitBucket URL: %s", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode == 403 {
|
||||||
|
// A private repo
|
||||||
|
return "", true, fmt.Errorf(
|
||||||
|
"shorthand BitBucket URL can't be used for private repos, " +
|
||||||
|
"please use a full URL")
|
||||||
|
}
|
||||||
|
dec := json.NewDecoder(resp.Body)
|
||||||
|
if err := dec.Decode(&info); err != nil {
|
||||||
|
return "", true, fmt.Errorf("error looking up BitBucket URL: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch info.SCM {
|
||||||
|
case "git":
|
||||||
|
if !strings.HasSuffix(u.Path, ".git") {
|
||||||
|
u.Path += ".git"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "git::" + u.String(), true, nil
|
||||||
|
case "hg":
|
||||||
|
return "hg::" + u.String(), true, nil
|
||||||
|
default:
|
||||||
|
return "", true, fmt.Errorf("unknown BitBucket SCM type: %s", info.SCM)
|
||||||
|
}
|
||||||
|
}
|
||||||
67
vendor/github.com/hashicorp/go-getter/detect_bitbucket_test.go
generated
vendored
Normal file
67
vendor/github.com/hashicorp/go-getter/detect_bitbucket_test.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const testBBUrl = "https://bitbucket.org/hashicorp/tf-test-git"
|
||||||
|
|
||||||
|
func TestBitBucketDetector(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if _, err := http.Get(testBBUrl); err != nil {
|
||||||
|
t.Log("internet may not be working, skipping BB tests")
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
Input string
|
||||||
|
Output string
|
||||||
|
}{
|
||||||
|
// HTTP
|
||||||
|
{
|
||||||
|
"bitbucket.org/hashicorp/tf-test-git",
|
||||||
|
"git::https://bitbucket.org/hashicorp/tf-test-git.git",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitbucket.org/hashicorp/tf-test-git.git",
|
||||||
|
"git::https://bitbucket.org/hashicorp/tf-test-git.git",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitbucket.org/hashicorp/tf-test-hg",
|
||||||
|
"hg::https://bitbucket.org/hashicorp/tf-test-hg",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pwd := "/pwd"
|
||||||
|
f := new(BitBucketDetector)
|
||||||
|
for i, tc := range cases {
|
||||||
|
var err error
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
var output string
|
||||||
|
var ok bool
|
||||||
|
output, ok, err = f.Detect(tc.Input, pwd)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "invalid character") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("not ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
if output != tc.Output {
|
||||||
|
t.Fatalf("%d: bad: %#v", i, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i >= 3 {
|
||||||
|
t.Fatalf("failure from bitbucket: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
67
vendor/github.com/hashicorp/go-getter/detect_file.go
generated
vendored
Normal file
67
vendor/github.com/hashicorp/go-getter/detect_file.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileDetector implements Detector to detect file paths.
|
||||||
|
type FileDetector struct{}
|
||||||
|
|
||||||
|
func (d *FileDetector) Detect(src, pwd string) (string, bool, error) {
|
||||||
|
if len(src) == 0 {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filepath.IsAbs(src) {
|
||||||
|
if pwd == "" {
|
||||||
|
return "", true, fmt.Errorf(
|
||||||
|
"relative paths require a module with a pwd")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat the pwd to determine if its a symbolic link. If it is,
|
||||||
|
// then the pwd becomes the original directory. Otherwise,
|
||||||
|
// `filepath.Join` below does some weird stuff.
|
||||||
|
//
|
||||||
|
// We just ignore if the pwd doesn't exist. That error will be
|
||||||
|
// caught later when we try to use the URL.
|
||||||
|
if fi, err := os.Lstat(pwd); !os.IsNotExist(err) {
|
||||||
|
if err != nil {
|
||||||
|
return "", true, err
|
||||||
|
}
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
pwd, err = filepath.EvalSymlinks(pwd)
|
||||||
|
if err != nil {
|
||||||
|
return "", true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The symlink itself might be a relative path, so we have to
|
||||||
|
// resolve this to have a correctly rooted URL.
|
||||||
|
pwd, err = filepath.Abs(pwd)
|
||||||
|
if err != nil {
|
||||||
|
return "", true, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
src = filepath.Join(pwd, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmtFileURL(src), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fmtFileURL(path string) string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
// Make sure we're using "/" on Windows. URLs are "/"-based.
|
||||||
|
path = filepath.ToSlash(path)
|
||||||
|
return fmt.Sprintf("file://%s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that we don't start with "/" since we add that below.
|
||||||
|
if path[0] == '/' {
|
||||||
|
path = path[1:]
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("file:///%s", path)
|
||||||
|
}
|
||||||
118
vendor/github.com/hashicorp/go-getter/detect_file_test.go
generated
vendored
Normal file
118
vendor/github.com/hashicorp/go-getter/detect_file_test.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fileTest struct {
|
||||||
|
in, pwd, out string
|
||||||
|
err bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileTests = []fileTest{
|
||||||
|
{"./foo", "/pwd", "file:///pwd/foo", false},
|
||||||
|
{"./foo?foo=bar", "/pwd", "file:///pwd/foo?foo=bar", false},
|
||||||
|
{"foo", "/pwd", "file:///pwd/foo", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
var unixFileTests = []fileTest{
|
||||||
|
{"./foo", "test-fixtures/detect-file-symlink-pwd/syml/pwd",
|
||||||
|
"test-fixtures/detect-file-symlink-pwd/real/foo", false},
|
||||||
|
|
||||||
|
{"/foo", "/pwd", "file:///foo", false},
|
||||||
|
{"/foo?bar=baz", "/pwd", "file:///foo?bar=baz", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
var winFileTests = []fileTest{
|
||||||
|
{"/foo", "/pwd", "file:///pwd/foo", false},
|
||||||
|
{`C:\`, `/pwd`, `file://C:/`, false},
|
||||||
|
{`C:\?bar=baz`, `/pwd`, `file://C:/?bar=baz`, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDetector(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
fileTests = append(fileTests, winFileTests...)
|
||||||
|
} else {
|
||||||
|
fileTests = append(fileTests, unixFileTests...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the pwd
|
||||||
|
pwdRoot, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
pwdRoot, err = filepath.Abs(pwdRoot)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f := new(FileDetector)
|
||||||
|
for i, tc := range fileTests {
|
||||||
|
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||||
|
pwd := tc.pwd
|
||||||
|
if !filepath.IsAbs(pwd) {
|
||||||
|
pwd = filepath.Join(pwdRoot, pwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, ok, err := f.Detect(tc.in, pwd)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("not ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := tc.out
|
||||||
|
if !strings.HasPrefix(expected, "file://") {
|
||||||
|
expected = "file://" + filepath.Join(pwdRoot, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
if out != expected {
|
||||||
|
t.Fatalf("input: %q\npwd: %q\nexpected: %q\nbad output: %#v",
|
||||||
|
tc.in, pwd, expected, out)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var noPwdFileTests = []fileTest{
|
||||||
|
{in: "./foo", pwd: "", out: "", err: true},
|
||||||
|
{in: "foo", pwd: "", out: "", err: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
var noPwdUnixFileTests = []fileTest{
|
||||||
|
{in: "/foo", pwd: "", out: "file:///foo", err: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
var noPwdWinFileTests = []fileTest{
|
||||||
|
{in: "/foo", pwd: "", out: "", err: true},
|
||||||
|
{in: `C:\`, pwd: ``, out: `file://C:/`, err: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileDetector_noPwd(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
noPwdFileTests = append(noPwdFileTests, noPwdWinFileTests...)
|
||||||
|
} else {
|
||||||
|
noPwdFileTests = append(noPwdFileTests, noPwdUnixFileTests...)
|
||||||
|
}
|
||||||
|
|
||||||
|
f := new(FileDetector)
|
||||||
|
for i, tc := range noPwdFileTests {
|
||||||
|
out, ok, err := f.Detect(tc.in, tc.pwd)
|
||||||
|
if err != nil != tc.err {
|
||||||
|
t.Fatalf("%d: err: %s", i, err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("not ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
if out != tc.out {
|
||||||
|
t.Fatalf("%d: bad: %#v", i, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
70
vendor/github.com/hashicorp/go-getter/detect_file_unix_test.go
generated
vendored
Normal file
70
vendor/github.com/hashicorp/go-getter/detect_file_unix_test.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// +build test unix
|
||||||
|
|
||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// If a relative symlink is passed in as the pwd to Detect, the resulting URL
|
||||||
|
// can have an invalid path.
|
||||||
|
func TestFileDetector_relativeSymlink(t *testing.T) {
|
||||||
|
tmpDir, err := ioutil.TempDir("", "go-getter")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
// We may have a symlinked tmp dir,
|
||||||
|
// e.g. OSX uses /var -> /private/var
|
||||||
|
tmpDir, err = filepath.EvalSymlinks(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Mkdir(filepath.Join(tmpDir, "realPWD"), 0755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
subdir := filepath.Join(tmpDir, "subdir")
|
||||||
|
err = os.Mkdir(subdir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
prevDir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Chdir(prevDir)
|
||||||
|
|
||||||
|
err = os.Chdir(subdir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Symlink("../realPWD", "linkedPWD")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if detech doesn't fully resolve the pwd symlink, the output will be the
|
||||||
|
// invalid path: "file:///../modules/foo"
|
||||||
|
f := new(FileDetector)
|
||||||
|
out, ok, err := f.Detect("../modules/foo", "./linkedPWD")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("not ok")
|
||||||
|
}
|
||||||
|
if out != "file://"+filepath.Join(tmpDir, "modules/foo") {
|
||||||
|
t.Logf("expected: %v", "file://"+filepath.Join(tmpDir, "modules/foo"))
|
||||||
|
t.Fatalf("bad: %v", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
73
vendor/github.com/hashicorp/go-getter/detect_github.go
generated
vendored
Normal file
73
vendor/github.com/hashicorp/go-getter/detect_github.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GitHubDetector implements Detector to detect GitHub URLs and turn
|
||||||
|
// them into URLs that the Git Getter can understand.
|
||||||
|
type GitHubDetector struct{}
|
||||||
|
|
||||||
|
func (d *GitHubDetector) Detect(src, _ string) (string, bool, error) {
|
||||||
|
if len(src) == 0 {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(src, "github.com/") {
|
||||||
|
return d.detectHTTP(src)
|
||||||
|
} else if strings.HasPrefix(src, "git@github.com:") {
|
||||||
|
return d.detectSSH(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *GitHubDetector) detectHTTP(src string) (string, bool, error) {
|
||||||
|
parts := strings.Split(src, "/")
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return "", false, fmt.Errorf(
|
||||||
|
"GitHub URLs should be github.com/username/repo")
|
||||||
|
}
|
||||||
|
|
||||||
|
urlStr := fmt.Sprintf("https://%s", strings.Join(parts[:3], "/"))
|
||||||
|
url, err := url.Parse(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", true, fmt.Errorf("error parsing GitHub URL: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(url.Path, ".git") {
|
||||||
|
url.Path += ".git"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) > 3 {
|
||||||
|
url.Path += "//" + strings.Join(parts[3:], "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
return "git::" + url.String(), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *GitHubDetector) detectSSH(src string) (string, bool, error) {
|
||||||
|
idx := strings.Index(src, ":")
|
||||||
|
qidx := strings.Index(src, "?")
|
||||||
|
if qidx == -1 {
|
||||||
|
qidx = len(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
var u url.URL
|
||||||
|
u.Scheme = "ssh"
|
||||||
|
u.User = url.User("git")
|
||||||
|
u.Host = "github.com"
|
||||||
|
u.Path = src[idx+1 : qidx]
|
||||||
|
if qidx < len(src) {
|
||||||
|
q, err := url.ParseQuery(src[qidx+1:])
|
||||||
|
if err != nil {
|
||||||
|
return "", true, fmt.Errorf("error parsing GitHub SSH URL: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
return "git::" + u.String(), true, nil
|
||||||
|
}
|
||||||
55
vendor/github.com/hashicorp/go-getter/detect_github_test.go
generated
vendored
Normal file
55
vendor/github.com/hashicorp/go-getter/detect_github_test.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGitHubDetector(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Input string
|
||||||
|
Output string
|
||||||
|
}{
|
||||||
|
// HTTP
|
||||||
|
{"github.com/hashicorp/foo", "git::https://github.com/hashicorp/foo.git"},
|
||||||
|
{"github.com/hashicorp/foo.git", "git::https://github.com/hashicorp/foo.git"},
|
||||||
|
{
|
||||||
|
"github.com/hashicorp/foo/bar",
|
||||||
|
"git::https://github.com/hashicorp/foo.git//bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"github.com/hashicorp/foo?foo=bar",
|
||||||
|
"git::https://github.com/hashicorp/foo.git?foo=bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"github.com/hashicorp/foo.git?foo=bar",
|
||||||
|
"git::https://github.com/hashicorp/foo.git?foo=bar",
|
||||||
|
},
|
||||||
|
|
||||||
|
// SSH
|
||||||
|
{"git@github.com:hashicorp/foo.git", "git::ssh://git@github.com/hashicorp/foo.git"},
|
||||||
|
{
|
||||||
|
"git@github.com:hashicorp/foo.git//bar",
|
||||||
|
"git::ssh://git@github.com/hashicorp/foo.git//bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"git@github.com:hashicorp/foo.git?foo=bar",
|
||||||
|
"git::ssh://git@github.com/hashicorp/foo.git?foo=bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pwd := "/pwd"
|
||||||
|
f := new(GitHubDetector)
|
||||||
|
for i, tc := range cases {
|
||||||
|
output, ok, err := f.Detect(tc.Input, pwd)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("not ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
if output != tc.Output {
|
||||||
|
t.Fatalf("%d: bad: %#v", i, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
61
vendor/github.com/hashicorp/go-getter/detect_s3.go
generated
vendored
Normal file
61
vendor/github.com/hashicorp/go-getter/detect_s3.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// S3Detector implements Detector to detect S3 URLs and turn
|
||||||
|
// them into URLs that the S3 getter can understand.
|
||||||
|
type S3Detector struct{}
|
||||||
|
|
||||||
|
func (d *S3Detector) Detect(src, _ string) (string, bool, error) {
|
||||||
|
if len(src) == 0 {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(src, ".amazonaws.com/") {
|
||||||
|
return d.detectHTTP(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *S3Detector) detectHTTP(src string) (string, bool, error) {
|
||||||
|
parts := strings.Split(src, "/")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return "", false, fmt.Errorf(
|
||||||
|
"URL is not a valid S3 URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
hostParts := strings.Split(parts[0], ".")
|
||||||
|
if len(hostParts) == 3 {
|
||||||
|
return d.detectPathStyle(hostParts[0], parts[1:])
|
||||||
|
} else if len(hostParts) == 4 {
|
||||||
|
return d.detectVhostStyle(hostParts[1], hostParts[0], parts[1:])
|
||||||
|
} else {
|
||||||
|
return "", false, fmt.Errorf(
|
||||||
|
"URL is not a valid S3 URL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *S3Detector) detectPathStyle(region string, parts []string) (string, bool, error) {
|
||||||
|
urlStr := fmt.Sprintf("https://%s.amazonaws.com/%s", region, strings.Join(parts, "/"))
|
||||||
|
url, err := url.Parse(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, fmt.Errorf("error parsing S3 URL: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "s3::" + url.String(), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *S3Detector) detectVhostStyle(region, bucket string, parts []string) (string, bool, error) {
|
||||||
|
urlStr := fmt.Sprintf("https://%s.amazonaws.com/%s/%s", region, bucket, strings.Join(parts, "/"))
|
||||||
|
url, err := url.Parse(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, fmt.Errorf("error parsing S3 URL: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "s3::" + url.String(), true, nil
|
||||||
|
}
|
||||||
84
vendor/github.com/hashicorp/go-getter/detect_s3_test.go
generated
vendored
Normal file
84
vendor/github.com/hashicorp/go-getter/detect_s3_test.go
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestS3Detector(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Input string
|
||||||
|
Output string
|
||||||
|
}{
|
||||||
|
// Virtual hosted style
|
||||||
|
{
|
||||||
|
"bucket.s3.amazonaws.com/foo",
|
||||||
|
"s3::https://s3.amazonaws.com/bucket/foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bucket.s3.amazonaws.com/foo/bar",
|
||||||
|
"s3::https://s3.amazonaws.com/bucket/foo/bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bucket.s3.amazonaws.com/foo/bar.baz",
|
||||||
|
"s3::https://s3.amazonaws.com/bucket/foo/bar.baz",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bucket.s3-eu-west-1.amazonaws.com/foo",
|
||||||
|
"s3::https://s3-eu-west-1.amazonaws.com/bucket/foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bucket.s3-eu-west-1.amazonaws.com/foo/bar",
|
||||||
|
"s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bucket.s3-eu-west-1.amazonaws.com/foo/bar.baz",
|
||||||
|
"s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz",
|
||||||
|
},
|
||||||
|
// Path style
|
||||||
|
{
|
||||||
|
"s3.amazonaws.com/bucket/foo",
|
||||||
|
"s3::https://s3.amazonaws.com/bucket/foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"s3.amazonaws.com/bucket/foo/bar",
|
||||||
|
"s3::https://s3.amazonaws.com/bucket/foo/bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"s3.amazonaws.com/bucket/foo/bar.baz",
|
||||||
|
"s3::https://s3.amazonaws.com/bucket/foo/bar.baz",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"s3-eu-west-1.amazonaws.com/bucket/foo",
|
||||||
|
"s3::https://s3-eu-west-1.amazonaws.com/bucket/foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"s3-eu-west-1.amazonaws.com/bucket/foo/bar",
|
||||||
|
"s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz",
|
||||||
|
"s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz",
|
||||||
|
},
|
||||||
|
// Misc tests
|
||||||
|
{
|
||||||
|
"s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234",
|
||||||
|
"s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pwd := "/pwd"
|
||||||
|
f := new(S3Detector)
|
||||||
|
for i, tc := range cases {
|
||||||
|
output, ok, err := f.Detect(tc.Input, pwd)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("not ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
if output != tc.Output {
|
||||||
|
t.Fatalf("%d: bad: %#v", i, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
vendor/github.com/hashicorp/go-getter/detect_test.go
generated
vendored
Normal file
57
vendor/github.com/hashicorp/go-getter/detect_test.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDetect(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Input string
|
||||||
|
Pwd string
|
||||||
|
Output string
|
||||||
|
Err bool
|
||||||
|
}{
|
||||||
|
{"./foo", "/foo", "file:///foo/foo", false},
|
||||||
|
{"git::./foo", "/foo", "git::file:///foo/foo", false},
|
||||||
|
{
|
||||||
|
"git::github.com/hashicorp/foo",
|
||||||
|
"",
|
||||||
|
"git::https://github.com/hashicorp/foo.git",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"./foo//bar",
|
||||||
|
"/foo",
|
||||||
|
"file:///foo/foo//bar",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"git::github.com/hashicorp/foo//bar",
|
||||||
|
"",
|
||||||
|
"git::https://github.com/hashicorp/foo.git//bar",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"git::https://github.com/hashicorp/consul.git",
|
||||||
|
"",
|
||||||
|
"git::https://github.com/hashicorp/consul.git",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"./foo/archive//*",
|
||||||
|
"/bar",
|
||||||
|
"file:///bar/foo/archive//*",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
output, err := Detect(tc.Input, tc.Pwd, Detectors)
|
||||||
|
if err != nil != tc.Err {
|
||||||
|
t.Fatalf("%d: bad err: %s", i, err)
|
||||||
|
}
|
||||||
|
if output != tc.Output {
|
||||||
|
t.Fatalf("%d: bad output: %s\nexpected: %s", i, output, tc.Output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
65
vendor/github.com/hashicorp/go-getter/folder_storage.go
generated
vendored
Normal file
65
vendor/github.com/hashicorp/go-getter/folder_storage.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FolderStorage is an implementation of the Storage interface that manages
|
||||||
|
// modules on the disk.
|
||||||
|
type FolderStorage struct {
|
||||||
|
// StorageDir is the directory where the modules will be stored.
|
||||||
|
StorageDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dir implements Storage.Dir
|
||||||
|
func (s *FolderStorage) Dir(key string) (d string, e bool, err error) {
|
||||||
|
d = s.dir(key)
|
||||||
|
_, err = os.Stat(d)
|
||||||
|
if err == nil {
|
||||||
|
// Directory exists
|
||||||
|
e = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// Directory doesn't exist
|
||||||
|
d = ""
|
||||||
|
e = false
|
||||||
|
err = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// An error
|
||||||
|
d = ""
|
||||||
|
e = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements Storage.Get
|
||||||
|
func (s *FolderStorage) Get(key string, source string, update bool) error {
|
||||||
|
dir := s.dir(key)
|
||||||
|
if !update {
|
||||||
|
if _, err := os.Stat(dir); err == nil {
|
||||||
|
// If the directory already exists, then we're done since
|
||||||
|
// we're not updating.
|
||||||
|
return nil
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
// If the error we got wasn't a file-not-exist error, then
|
||||||
|
// something went wrong and we should report it.
|
||||||
|
return fmt.Errorf("Error reading module directory: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the source. This always forces an update.
|
||||||
|
return Get(dir, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dir returns the directory name internally that we'll use to map to
|
||||||
|
// internally.
|
||||||
|
func (s *FolderStorage) dir(key string) string {
|
||||||
|
sum := md5.Sum([]byte(key))
|
||||||
|
return filepath.Join(s.StorageDir, hex.EncodeToString(sum[:]))
|
||||||
|
}
|
||||||
48
vendor/github.com/hashicorp/go-getter/folder_storage_test.go
generated
vendored
Normal file
48
vendor/github.com/hashicorp/go-getter/folder_storage_test.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFolderStorage_impl(t *testing.T) {
|
||||||
|
var _ Storage = new(FolderStorage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFolderStorage(t *testing.T) {
|
||||||
|
s := &FolderStorage{StorageDir: tempDir(t)}
|
||||||
|
|
||||||
|
module := testModule("basic")
|
||||||
|
|
||||||
|
// A module shouldn't exist at first...
|
||||||
|
_, ok, err := s.Dir(module)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
t.Fatal("should not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
key := "foo"
|
||||||
|
|
||||||
|
// We can get it
|
||||||
|
err = s.Get(key, module, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now the module exists
|
||||||
|
dir, ok, err := s.Dir(key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPath := filepath.Join(dir, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
146
vendor/github.com/hashicorp/go-getter/get.go
generated
vendored
Normal file
146
vendor/github.com/hashicorp/go-getter/get.go
generated
vendored
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
// getter is a package for downloading files or directories from a variety of
|
||||||
|
// protocols.
|
||||||
|
//
|
||||||
|
// getter is unique in its ability to download both directories and files.
|
||||||
|
// It also detects certain source strings to be protocol-specific URLs. For
|
||||||
|
// example, "github.com/hashicorp/go-getter" would turn into a Git URL and
|
||||||
|
// use the Git protocol.
|
||||||
|
//
|
||||||
|
// Protocols and detectors are extensible.
|
||||||
|
//
|
||||||
|
// To get started, see Client.
|
||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Getter defines the interface that schemes must implement to download
|
||||||
|
// things.
|
||||||
|
type Getter interface {
|
||||||
|
// Get downloads the given URL into the given directory. This always
|
||||||
|
// assumes that we're updating and gets the latest version that it can.
|
||||||
|
//
|
||||||
|
// The directory may already exist (if we're updating). If it is in a
|
||||||
|
// format that isn't understood, an error should be returned. Get shouldn't
|
||||||
|
// simply nuke the directory.
|
||||||
|
Get(string, *url.URL) error
|
||||||
|
|
||||||
|
// GetFile downloads the give URL into the given path. The URL must
|
||||||
|
// reference a single file. If possible, the Getter should check if
|
||||||
|
// the remote end contains the same file and no-op this operation.
|
||||||
|
GetFile(string, *url.URL) error
|
||||||
|
|
||||||
|
// ClientMode returns the mode based on the given URL. This is used to
|
||||||
|
// allow clients to let the getters decide which mode to use.
|
||||||
|
ClientMode(*url.URL) (ClientMode, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters is the mapping of scheme to the Getter implementation that will
|
||||||
|
// be used to get a dependency.
|
||||||
|
var Getters map[string]Getter
|
||||||
|
|
||||||
|
// forcedRegexp is the regular expression that finds forced getters. This
|
||||||
|
// syntax is schema::url, example: git::https://foo.com
|
||||||
|
var forcedRegexp = regexp.MustCompile(`^([A-Za-z0-9]+)::(.+)$`)
|
||||||
|
|
||||||
|
// httpClient is the default client to be used by HttpGetters.
|
||||||
|
var httpClient = cleanhttp.DefaultClient()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
httpGetter := &HttpGetter{
|
||||||
|
Netrc: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
Getters = map[string]Getter{
|
||||||
|
"file": new(FileGetter),
|
||||||
|
"git": new(GitGetter),
|
||||||
|
"hg": new(HgGetter),
|
||||||
|
"s3": new(S3Getter),
|
||||||
|
"http": httpGetter,
|
||||||
|
"https": httpGetter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get downloads the directory specified by src into the folder specified by
|
||||||
|
// dst. If dst already exists, Get will attempt to update it.
|
||||||
|
//
|
||||||
|
// src is a URL, whereas dst is always just a file path to a folder. This
|
||||||
|
// folder doesn't need to exist. It will be created if it doesn't exist.
|
||||||
|
func Get(dst, src string) error {
|
||||||
|
return (&Client{
|
||||||
|
Src: src,
|
||||||
|
Dst: dst,
|
||||||
|
Dir: true,
|
||||||
|
Getters: Getters,
|
||||||
|
}).Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAny downloads a URL into the given destination. Unlike Get or
|
||||||
|
// GetFile, both directories and files are supported.
|
||||||
|
//
|
||||||
|
// dst must be a directory. If src is a file, it will be downloaded
|
||||||
|
// into dst with the basename of the URL. If src is a directory or
|
||||||
|
// archive, it will be unpacked directly into dst.
|
||||||
|
func GetAny(dst, src string) error {
|
||||||
|
return (&Client{
|
||||||
|
Src: src,
|
||||||
|
Dst: dst,
|
||||||
|
Mode: ClientModeAny,
|
||||||
|
Getters: Getters,
|
||||||
|
}).Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFile downloads the file specified by src into the path specified by
|
||||||
|
// dst.
|
||||||
|
func GetFile(dst, src string) error {
|
||||||
|
return (&Client{
|
||||||
|
Src: src,
|
||||||
|
Dst: dst,
|
||||||
|
Dir: false,
|
||||||
|
Getters: Getters,
|
||||||
|
}).Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRunCommand is a helper that will run a command and capture the output
|
||||||
|
// in the case an error happens.
|
||||||
|
func getRunCommand(cmd *exec.Cmd) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
cmd.Stdout = &buf
|
||||||
|
cmd.Stderr = &buf
|
||||||
|
err := cmd.Run()
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
|
// The program has exited with an exit code != 0
|
||||||
|
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"%s exited with %d: %s",
|
||||||
|
cmd.Path,
|
||||||
|
status.ExitStatus(),
|
||||||
|
buf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("error running %s: %s", cmd.Path, buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// getForcedGetter takes a source and returns the tuple of the forced
|
||||||
|
// getter and the raw URL (without the force syntax).
|
||||||
|
func getForcedGetter(src string) (string, string) {
|
||||||
|
var forced string
|
||||||
|
if ms := forcedRegexp.FindStringSubmatch(src); ms != nil {
|
||||||
|
forced = ms[1]
|
||||||
|
src = ms[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
return forced, src
|
||||||
|
}
|
||||||
32
vendor/github.com/hashicorp/go-getter/get_file.go
generated
vendored
Normal file
32
vendor/github.com/hashicorp/go-getter/get_file.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileGetter is a Getter implementation that will download a module from
|
||||||
|
// a file scheme.
|
||||||
|
type FileGetter struct {
|
||||||
|
// Copy, if set to true, will copy data instead of using a symlink
|
||||||
|
Copy bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *FileGetter) ClientMode(u *url.URL) (ClientMode, error) {
|
||||||
|
path := u.Path
|
||||||
|
if u.RawPath != "" {
|
||||||
|
path = u.RawPath
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the source is a directory.
|
||||||
|
if fi.IsDir() {
|
||||||
|
return ClientModeDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ClientModeFile, nil
|
||||||
|
}
|
||||||
202
vendor/github.com/hashicorp/go-getter/get_file_test.go
generated
vendored
Normal file
202
vendor/github.com/hashicorp/go-getter/get_file_test.go
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFileGetter_impl(t *testing.T) {
|
||||||
|
var _ Getter = new(FileGetter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileGetter(t *testing.T) {
|
||||||
|
g := new(FileGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
|
||||||
|
// With a dir that doesn't exist
|
||||||
|
if err := g.Get(dst, testModuleURL("basic")); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the destination folder is a symlink
|
||||||
|
fi, err := os.Lstat(dst)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if fi.Mode()&os.ModeSymlink == 0 {
|
||||||
|
t.Fatal("destination is not a symlink")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileGetter_sourceFile(t *testing.T) {
|
||||||
|
g := new(FileGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
|
||||||
|
// With a source URL that is a path to a file
|
||||||
|
u := testModuleURL("basic")
|
||||||
|
u.Path += "/main.tf"
|
||||||
|
if err := g.Get(dst, u); err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileGetter_sourceNoExist(t *testing.T) {
|
||||||
|
g := new(FileGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
|
||||||
|
// With a source URL that doesn't exist
|
||||||
|
u := testModuleURL("basic")
|
||||||
|
u.Path += "/main"
|
||||||
|
if err := g.Get(dst, u); err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileGetter_dir(t *testing.T) {
|
||||||
|
g := new(FileGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
|
||||||
|
if err := os.MkdirAll(dst, 0755); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With a dir that exists that isn't a symlink
|
||||||
|
if err := g.Get(dst, testModuleURL("basic")); err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileGetter_dirSymlink(t *testing.T) {
|
||||||
|
g := new(FileGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
dst2 := tempDir(t)
|
||||||
|
|
||||||
|
// Make parents
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(dst2, 0755); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a symlink
|
||||||
|
if err := os.Symlink(dst2, dst); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With a dir that exists that isn't a symlink
|
||||||
|
if err := g.Get(dst, testModuleURL("basic")); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileGetter_GetFile(t *testing.T) {
|
||||||
|
g := new(FileGetter)
|
||||||
|
dst := tempFile(t)
|
||||||
|
|
||||||
|
// With a dir that doesn't exist
|
||||||
|
if err := g.GetFile(dst, testModuleURL("basic-file/foo.txt")); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the destination folder is a symlink
|
||||||
|
fi, err := os.Lstat(dst)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if fi.Mode()&os.ModeSymlink == 0 {
|
||||||
|
t.Fatal("destination is not a symlink")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
assertContents(t, dst, "Hello\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileGetter_GetFile_Copy(t *testing.T) {
|
||||||
|
g := new(FileGetter)
|
||||||
|
g.Copy = true
|
||||||
|
|
||||||
|
dst := tempFile(t)
|
||||||
|
|
||||||
|
// With a dir that doesn't exist
|
||||||
|
if err := g.GetFile(dst, testModuleURL("basic-file/foo.txt")); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the destination folder is a symlink
|
||||||
|
fi, err := os.Lstat(dst)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
t.Fatal("destination is a symlink")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
assertContents(t, dst, "Hello\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/hashicorp/terraform/issues/8418
|
||||||
|
func TestFileGetter_percent2F(t *testing.T) {
|
||||||
|
g := new(FileGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
|
||||||
|
// With a dir that doesn't exist
|
||||||
|
if err := g.Get(dst, testModuleURL("basic%2Ftest")); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileGetter_ClientMode_notexist(t *testing.T) {
|
||||||
|
g := new(FileGetter)
|
||||||
|
|
||||||
|
u := testURL("nonexistent")
|
||||||
|
if _, err := g.ClientMode(u); err == nil {
|
||||||
|
t.Fatal("expect source file error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileGetter_ClientMode_file(t *testing.T) {
|
||||||
|
g := new(FileGetter)
|
||||||
|
|
||||||
|
// Check the client mode when pointed at a file.
|
||||||
|
mode, err := g.ClientMode(testModuleURL("basic-file/foo.txt"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if mode != ClientModeFile {
|
||||||
|
t.Fatal("expect ClientModeFile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileGetter_ClientMode_dir(t *testing.T) {
|
||||||
|
g := new(FileGetter)
|
||||||
|
|
||||||
|
// Check the client mode when pointed at a directory.
|
||||||
|
mode, err := g.ClientMode(testModuleURL("basic"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if mode != ClientModeDir {
|
||||||
|
t.Fatal("expect ClientModeDir")
|
||||||
|
}
|
||||||
|
}
|
||||||
103
vendor/github.com/hashicorp/go-getter/get_file_unix.go
generated
vendored
Normal file
103
vendor/github.com/hashicorp/go-getter/get_file_unix.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (g *FileGetter) Get(dst string, u *url.URL) error {
|
||||||
|
path := u.Path
|
||||||
|
if u.RawPath != "" {
|
||||||
|
path = u.RawPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// The source path must exist and be a directory to be usable.
|
||||||
|
if fi, err := os.Stat(path); err != nil {
|
||||||
|
return fmt.Errorf("source path error: %s", err)
|
||||||
|
} else if !fi.IsDir() {
|
||||||
|
return fmt.Errorf("source path must be a directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Lstat(dst)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the destination already exists, it must be a symlink
|
||||||
|
if err == nil {
|
||||||
|
mode := fi.Mode()
|
||||||
|
if mode&os.ModeSymlink == 0 {
|
||||||
|
return fmt.Errorf("destination exists and is not a symlink")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the destination
|
||||||
|
if err := os.Remove(dst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create all the parent directories
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Symlink(path, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *FileGetter) GetFile(dst string, u *url.URL) error {
|
||||||
|
path := u.Path
|
||||||
|
if u.RawPath != "" {
|
||||||
|
path = u.RawPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// The source path must exist and be a file to be usable.
|
||||||
|
if fi, err := os.Stat(path); err != nil {
|
||||||
|
return fmt.Errorf("source path error: %s", err)
|
||||||
|
} else if fi.IsDir() {
|
||||||
|
return fmt.Errorf("source path must be a file")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := os.Lstat(dst)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the destination already exists, it must be a symlink
|
||||||
|
if err == nil {
|
||||||
|
// Remove the destination
|
||||||
|
if err := os.Remove(dst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create all the parent directories
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're not copying, just symlink and we're done
|
||||||
|
if !g.Copy {
|
||||||
|
return os.Symlink(path, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy
|
||||||
|
srcF, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcF.Close()
|
||||||
|
|
||||||
|
dstF, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dstF.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(dstF, srcF)
|
||||||
|
return err
|
||||||
|
}
|
||||||
120
vendor/github.com/hashicorp/go-getter/get_file_windows.go
generated
vendored
Normal file
120
vendor/github.com/hashicorp/go-getter/get_file_windows.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (g *FileGetter) Get(dst string, u *url.URL) error {
|
||||||
|
path := u.Path
|
||||||
|
if u.RawPath != "" {
|
||||||
|
path = u.RawPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// The source path must exist and be a directory to be usable.
|
||||||
|
if fi, err := os.Stat(path); err != nil {
|
||||||
|
return fmt.Errorf("source path error: %s", err)
|
||||||
|
} else if !fi.IsDir() {
|
||||||
|
return fmt.Errorf("source path must be a directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Lstat(dst)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the destination already exists, it must be a symlink
|
||||||
|
if err == nil {
|
||||||
|
mode := fi.Mode()
|
||||||
|
if mode&os.ModeSymlink == 0 {
|
||||||
|
return fmt.Errorf("destination exists and is not a symlink")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the destination
|
||||||
|
if err := os.Remove(dst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create all the parent directories
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourcePath := toBackslash(path)
|
||||||
|
|
||||||
|
// Use mklink to create a junction point
|
||||||
|
output, err := exec.Command("cmd", "/c", "mklink", "/J", dst, sourcePath).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to run mklink %v %v: %v %q", dst, sourcePath, err, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *FileGetter) GetFile(dst string, u *url.URL) error {
|
||||||
|
path := u.Path
|
||||||
|
if u.RawPath != "" {
|
||||||
|
path = u.RawPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// The source path must exist and be a directory to be usable.
|
||||||
|
if fi, err := os.Stat(path); err != nil {
|
||||||
|
return fmt.Errorf("source path error: %s", err)
|
||||||
|
} else if fi.IsDir() {
|
||||||
|
return fmt.Errorf("source path must be a file")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := os.Lstat(dst)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the destination already exists, it must be a symlink
|
||||||
|
if err == nil {
|
||||||
|
// Remove the destination
|
||||||
|
if err := os.Remove(dst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create all the parent directories
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're not copying, just symlink and we're done
|
||||||
|
if !g.Copy {
|
||||||
|
return os.Symlink(path, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy
|
||||||
|
srcF, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcF.Close()
|
||||||
|
|
||||||
|
dstF, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dstF.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(dstF, srcF)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// toBackslash returns the result of replacing each slash character
|
||||||
|
// in path with a backslash ('\') character. Multiple separators are
|
||||||
|
// replaced by multiple backslashes.
|
||||||
|
func toBackslash(path string) string {
|
||||||
|
return strings.Replace(path, "/", "\\", -1)
|
||||||
|
}
|
||||||
241
vendor/github.com/hashicorp/go-getter/get_git.go
generated
vendored
Normal file
241
vendor/github.com/hashicorp/go-getter/get_git.go
generated
vendored
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
urlhelper "github.com/hashicorp/go-getter/helper/url"
|
||||||
|
"github.com/hashicorp/go-safetemp"
|
||||||
|
"github.com/hashicorp/go-version"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GitGetter is a Getter implementation that will download a module from
|
||||||
|
// a git repository.
|
||||||
|
type GitGetter struct{}
|
||||||
|
|
||||||
|
func (g *GitGetter) ClientMode(_ *url.URL) (ClientMode, error) {
|
||||||
|
return ClientModeDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitGetter) Get(dst string, u *url.URL) error {
|
||||||
|
if _, err := exec.LookPath("git"); err != nil {
|
||||||
|
return fmt.Errorf("git must be available and on the PATH")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract some query parameters we use
|
||||||
|
var ref, sshKey string
|
||||||
|
q := u.Query()
|
||||||
|
if len(q) > 0 {
|
||||||
|
ref = q.Get("ref")
|
||||||
|
q.Del("ref")
|
||||||
|
|
||||||
|
sshKey = q.Get("sshkey")
|
||||||
|
q.Del("sshkey")
|
||||||
|
|
||||||
|
// Copy the URL
|
||||||
|
var newU url.URL = *u
|
||||||
|
u = &newU
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
var sshKeyFile string
|
||||||
|
if sshKey != "" {
|
||||||
|
// Check that the git version is sufficiently new.
|
||||||
|
if err := checkGitVersion("2.3"); err != nil {
|
||||||
|
return fmt.Errorf("Error using ssh key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have an SSH key - decode it.
|
||||||
|
raw, err := base64.StdEncoding.DecodeString(sshKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a temp file for the key and ensure it is removed.
|
||||||
|
fh, err := ioutil.TempFile("", "go-getter")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sshKeyFile = fh.Name()
|
||||||
|
defer os.Remove(sshKeyFile)
|
||||||
|
|
||||||
|
// Set the permissions prior to writing the key material.
|
||||||
|
if err := os.Chmod(sshKeyFile, 0600); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the raw key into the temp file.
|
||||||
|
_, err = fh.Write(raw)
|
||||||
|
fh.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone or update the repository
|
||||||
|
_, err := os.Stat(dst)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = g.update(dst, sshKeyFile, ref)
|
||||||
|
} else {
|
||||||
|
err = g.clone(dst, sshKeyFile, u)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next: check out the proper tag/branch if it is specified, and checkout
|
||||||
|
if ref != "" {
|
||||||
|
if err := g.checkout(dst, ref); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lastly, download any/all submodules.
|
||||||
|
return g.fetchSubmodules(dst, sshKeyFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFile for Git doesn't support updating at this time. It will download
|
||||||
|
// the file every time.
|
||||||
|
func (g *GitGetter) GetFile(dst string, u *url.URL) error {
|
||||||
|
td, tdcloser, err := safetemp.Dir("", "getter")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tdcloser.Close()
|
||||||
|
|
||||||
|
// Get the filename, and strip the filename from the URL so we can
|
||||||
|
// just get the repository directly.
|
||||||
|
filename := filepath.Base(u.Path)
|
||||||
|
u.Path = filepath.Dir(u.Path)
|
||||||
|
|
||||||
|
// Get the full repository
|
||||||
|
if err := g.Get(td, u); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the single file
|
||||||
|
u, err = urlhelper.Parse(fmtFileURL(filepath.Join(td, filename)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fg := &FileGetter{Copy: true}
|
||||||
|
return fg.GetFile(dst, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitGetter) checkout(dst string, ref string) error {
|
||||||
|
cmd := exec.Command("git", "checkout", ref)
|
||||||
|
cmd.Dir = dst
|
||||||
|
return getRunCommand(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitGetter) clone(dst, sshKeyFile string, u *url.URL) error {
|
||||||
|
cmd := exec.Command("git", "clone", u.String(), dst)
|
||||||
|
setupGitEnv(cmd, sshKeyFile)
|
||||||
|
return getRunCommand(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitGetter) update(dst, sshKeyFile, ref string) error {
|
||||||
|
// Determine if we're a branch. If we're NOT a branch, then we just
|
||||||
|
// switch to master prior to checking out
|
||||||
|
cmd := exec.Command("git", "show-ref", "-q", "--verify", "refs/heads/"+ref)
|
||||||
|
cmd.Dir = dst
|
||||||
|
|
||||||
|
if getRunCommand(cmd) != nil {
|
||||||
|
// Not a branch, switch to master. This will also catch non-existent
|
||||||
|
// branches, in which case we want to switch to master and then
|
||||||
|
// checkout the proper branch later.
|
||||||
|
ref = "master"
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to be on a branch to pull
|
||||||
|
if err := g.checkout(dst, ref); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = exec.Command("git", "pull", "--ff-only")
|
||||||
|
cmd.Dir = dst
|
||||||
|
setupGitEnv(cmd, sshKeyFile)
|
||||||
|
return getRunCommand(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchSubmodules downloads any configured submodules recursively.
|
||||||
|
func (g *GitGetter) fetchSubmodules(dst, sshKeyFile string) error {
|
||||||
|
cmd := exec.Command("git", "submodule", "update", "--init", "--recursive")
|
||||||
|
cmd.Dir = dst
|
||||||
|
setupGitEnv(cmd, sshKeyFile)
|
||||||
|
return getRunCommand(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupGitEnv sets up the environment for the given command. This is used to
|
||||||
|
// pass configuration data to git and ssh and enables advanced cloning methods.
|
||||||
|
func setupGitEnv(cmd *exec.Cmd, sshKeyFile string) {
|
||||||
|
const gitSSHCommand = "GIT_SSH_COMMAND="
|
||||||
|
var sshCmd []string
|
||||||
|
|
||||||
|
// If we have an existing GIT_SSH_COMMAND, we need to append our options.
|
||||||
|
// We will also remove our old entry to make sure the behavior is the same
|
||||||
|
// with versions of Go < 1.9.
|
||||||
|
env := os.Environ()
|
||||||
|
for i, v := range env {
|
||||||
|
if strings.HasPrefix(v, gitSSHCommand) {
|
||||||
|
sshCmd = []string{v}
|
||||||
|
|
||||||
|
env[i], env[len(env)-1] = env[len(env)-1], env[i]
|
||||||
|
env = env[:len(env)-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sshCmd) == 0 {
|
||||||
|
sshCmd = []string{gitSSHCommand + "ssh"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sshKeyFile != "" {
|
||||||
|
// We have an SSH key temp file configured, tell ssh about this.
|
||||||
|
sshCmd = append(sshCmd, "-i", sshKeyFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
env = append(env, strings.Join(sshCmd, " "))
|
||||||
|
cmd.Env = env
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkGitVersion is used to check the version of git installed on the system
|
||||||
|
// against a known minimum version. Returns an error if the installed version
|
||||||
|
// is older than the given minimum.
|
||||||
|
func checkGitVersion(min string) error {
|
||||||
|
want, err := version.NewVersion(min)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := exec.Command("git", "version").Output()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.Fields(string(out))
|
||||||
|
if len(fields) < 3 {
|
||||||
|
return fmt.Errorf("Unexpected 'git version' output: %q", string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
have, err := version.NewVersion(fields[2])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if have.LessThan(want) {
|
||||||
|
return fmt.Errorf("Required git version = %s, have %s", want, have)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
425
vendor/github.com/hashicorp/go-getter/get_git_test.go
generated
vendored
Normal file
425
vendor/github.com/hashicorp/go-getter/get_git_test.go
generated
vendored
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testHasGit bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if _, err := exec.LookPath("git"); err == nil {
|
||||||
|
testHasGit = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitGetter_impl(t *testing.T) {
|
||||||
|
var _ Getter = new(GitGetter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitGetter(t *testing.T) {
|
||||||
|
if !testHasGit {
|
||||||
|
t.Log("git not found, skipping")
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
g := new(GitGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
|
||||||
|
repo := testGitRepo(t, "basic")
|
||||||
|
repo.commitFile("foo.txt", "hello")
|
||||||
|
|
||||||
|
// With a dir that doesn't exist
|
||||||
|
if err := g.Get(dst, repo.url); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "foo.txt")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitGetter_branch(t *testing.T) {
|
||||||
|
if !testHasGit {
|
||||||
|
t.Log("git not found, skipping")
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
g := new(GitGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
|
||||||
|
repo := testGitRepo(t, "branch")
|
||||||
|
repo.git("checkout", "-b", "test-branch")
|
||||||
|
repo.commitFile("branch.txt", "branch")
|
||||||
|
|
||||||
|
q := repo.url.Query()
|
||||||
|
q.Add("ref", "test-branch")
|
||||||
|
repo.url.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
if err := g.Get(dst, repo.url); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "branch.txt")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get again should work
|
||||||
|
if err := g.Get(dst, repo.url); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath = filepath.Join(dst, "branch.txt")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitGetter_branchUpdate(t *testing.T) {
|
||||||
|
if !testHasGit {
|
||||||
|
t.Log("git not found, skipping")
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
g := new(GitGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
|
||||||
|
// First setup the state with a fresh branch
|
||||||
|
repo := testGitRepo(t, "branch-update")
|
||||||
|
repo.git("checkout", "-b", "test-branch")
|
||||||
|
repo.commitFile("branch.txt", "branch")
|
||||||
|
|
||||||
|
// Get the "test-branch" branch
|
||||||
|
q := repo.url.Query()
|
||||||
|
q.Add("ref", "test-branch")
|
||||||
|
repo.url.RawQuery = q.Encode()
|
||||||
|
if err := g.Get(dst, repo.url); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "branch.txt")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit an update to the branch
|
||||||
|
repo.commitFile("branch-update.txt", "branch-update")
|
||||||
|
|
||||||
|
// Get again should work
|
||||||
|
if err := g.Get(dst, repo.url); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath = filepath.Join(dst, "branch-update.txt")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitGetter_tag(t *testing.T) {
|
||||||
|
if !testHasGit {
|
||||||
|
t.Log("git not found, skipping")
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
g := new(GitGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
|
||||||
|
repo := testGitRepo(t, "tag")
|
||||||
|
repo.commitFile("tag.txt", "tag")
|
||||||
|
repo.git("tag", "v1.0")
|
||||||
|
|
||||||
|
q := repo.url.Query()
|
||||||
|
q.Add("ref", "v1.0")
|
||||||
|
repo.url.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
if err := g.Get(dst, repo.url); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "tag.txt")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get again should work
|
||||||
|
if err := g.Get(dst, repo.url); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath = filepath.Join(dst, "tag.txt")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitGetter_GetFile(t *testing.T) {
|
||||||
|
if !testHasGit {
|
||||||
|
t.Log("git not found, skipping")
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
g := new(GitGetter)
|
||||||
|
dst := tempFile(t)
|
||||||
|
|
||||||
|
repo := testGitRepo(t, "file")
|
||||||
|
repo.commitFile("file.txt", "hello")
|
||||||
|
|
||||||
|
// Download the file
|
||||||
|
repo.url.Path = filepath.Join(repo.url.Path, "file.txt")
|
||||||
|
if err := g.GetFile(dst, repo.url); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
if _, err := os.Stat(dst); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
assertContents(t, dst, "hello")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitGetter_gitVersion(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir("", "go-getter")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
script := filepath.Join(dir, "git")
|
||||||
|
err = ioutil.WriteFile(
|
||||||
|
script,
|
||||||
|
[]byte("#!/bin/sh\necho \"git version 2.0 (Some Metadata Here)\n\""),
|
||||||
|
0700)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func(v string) {
|
||||||
|
os.Setenv("PATH", v)
|
||||||
|
}(os.Getenv("PATH"))
|
||||||
|
|
||||||
|
os.Setenv("PATH", dir)
|
||||||
|
|
||||||
|
// Asking for a higher version throws an error
|
||||||
|
if err := checkGitVersion("2.3"); err == nil {
|
||||||
|
t.Fatal("expect git version error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Passes when version is satisfied
|
||||||
|
if err := checkGitVersion("1.9"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitGetter_sshKey(t *testing.T) {
|
||||||
|
if !testHasGit {
|
||||||
|
t.Log("git not found, skipping")
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
g := new(GitGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
|
||||||
|
encodedKey := base64.StdEncoding.EncodeToString([]byte(testGitToken))
|
||||||
|
|
||||||
|
u, err := url.Parse("ssh://git@github.com/hashicorp/test-private-repo" +
|
||||||
|
"?sshkey=" + encodedKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.Get(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
readmePath := filepath.Join(dst, "README.md")
|
||||||
|
if _, err := os.Stat(readmePath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitGetter_submodule(t *testing.T) {
|
||||||
|
if !testHasGit {
|
||||||
|
t.Log("git not found, skipping")
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
g := new(GitGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
|
||||||
|
// Set up the grandchild
|
||||||
|
gc := testGitRepo(t, "grandchild")
|
||||||
|
gc.commitFile("grandchild.txt", "grandchild")
|
||||||
|
|
||||||
|
// Set up the child
|
||||||
|
c := testGitRepo(t, "child")
|
||||||
|
c.commitFile("child.txt", "child")
|
||||||
|
c.git("submodule", "add", gc.dir)
|
||||||
|
c.git("commit", "-m", "Add grandchild submodule")
|
||||||
|
|
||||||
|
// Set up the parent
|
||||||
|
p := testGitRepo(t, "parent")
|
||||||
|
p.commitFile("parent.txt", "parent")
|
||||||
|
p.git("submodule", "add", c.dir)
|
||||||
|
p.git("commit", "-m", "Add child submodule")
|
||||||
|
|
||||||
|
// Clone the root repository
|
||||||
|
if err := g.Get(dst, p.url); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the files exist
|
||||||
|
for _, path := range []string{
|
||||||
|
filepath.Join(dst, "parent.txt"),
|
||||||
|
filepath.Join(dst, "child", "child.txt"),
|
||||||
|
filepath.Join(dst, "child", "grandchild", "grandchild.txt"),
|
||||||
|
} {
|
||||||
|
if _, err := os.Stat(path); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitGetter_setupGitEnv_sshKey(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skipf("skipping on windows since the test requires sh")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("/bin/sh", "-c", "echo $GIT_SSH_COMMAND")
|
||||||
|
setupGitEnv(cmd, "/tmp/foo.pem")
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(string(out))
|
||||||
|
if actual != "ssh -i /tmp/foo.pem" {
|
||||||
|
t.Fatalf("unexpected GIT_SSH_COMMAND: %q", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGitGetter_setupGitEnvWithExisting_sshKey(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skipf("skipping on windows since the test requires sh")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// start with an existing ssh command configuration
|
||||||
|
os.Setenv("GIT_SSH_COMMAND", "ssh -o StrictHostKeyChecking=no")
|
||||||
|
defer os.Setenv("GIT_SSH_COMMAND", "")
|
||||||
|
|
||||||
|
cmd := exec.Command("/bin/sh", "-c", "echo $GIT_SSH_COMMAND")
|
||||||
|
setupGitEnv(cmd, "/tmp/foo.pem")
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(string(out))
|
||||||
|
if actual != "ssh -o StrictHostKeyChecking=no -i /tmp/foo.pem" {
|
||||||
|
t.Fatalf("unexpected GIT_SSH_COMMAND: %q", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// gitRepo is a helper struct which controls a single temp git repo.
|
||||||
|
type gitRepo struct {
|
||||||
|
t *testing.T
|
||||||
|
url *url.URL
|
||||||
|
dir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// testGitRepo creates a new test git repository.
|
||||||
|
func testGitRepo(t *testing.T, name string) *gitRepo {
|
||||||
|
dir, err := ioutil.TempDir("", "go-getter")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
dir = filepath.Join(dir, name)
|
||||||
|
if err := os.Mkdir(dir, 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &gitRepo{
|
||||||
|
t: t,
|
||||||
|
dir: dir,
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := url.Parse("file://" + r.dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
r.url = url
|
||||||
|
|
||||||
|
r.git("init")
|
||||||
|
r.git("config", "user.name", "go-getter")
|
||||||
|
r.git("config", "user.email", "go-getter@hashicorp.com")
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// git runs a git command against the repo.
|
||||||
|
func (r *gitRepo) git(args ...string) {
|
||||||
|
cmd := exec.Command("git", args...)
|
||||||
|
cmd.Dir = r.dir
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
r.t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// commitFile writes and commits a text file to the repo.
|
||||||
|
func (r *gitRepo) commitFile(file, content string) {
|
||||||
|
path := filepath.Join(r.dir, file)
|
||||||
|
if err := ioutil.WriteFile(path, []byte(content), 0600); err != nil {
|
||||||
|
r.t.Fatal(err)
|
||||||
|
}
|
||||||
|
r.git("add", file)
|
||||||
|
r.git("commit", "-m", "Adding "+file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a read-only deploy key for an empty test repository.
|
||||||
|
// Note: This is split over multiple lines to avoid being disabled by key
|
||||||
|
// scanners automatically.
|
||||||
|
var testGitToken = `-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEA9cHsxCl3Jjgu9DHpwvmfFOl1XEdY+ShHDR/cMnzJ5ddk5/oV
|
||||||
|
Wy6EWatvyHZfRSZMwzv4PtKeUPm6iXjqWp4xdWU9khlPzozyj+U9Fq70TRVUW9E5
|
||||||
|
T1XdQVwJE421yffr4VMMwu60wBqjI1epapH2i2inYvw9Zl9X2MXq0+jTvFvDerbT
|
||||||
|
mDtfStDPljenELAIZtWVETSvbI46gALwbxbM2292ZUIL4D6jRz0aZMmyy/twYv8r
|
||||||
|
9WGJLwmYzU518Ie7zqKW/mCTdTrV0WRiDj0MeRaPgrGY9amuHE4r9iG/cJkwpKAO
|
||||||
|
Ccz0Hs6i89u9vZnTqZU9V7weJqRAQcMjXXR6yQIDAQABAoIBAQDBzICKnGxiTlHw
|
||||||
|
rd+6qqChnAy5jWYDbZjCJ8q8YZ3RS08+g/8NXZxvHftTqM0uOaq1FviHig3gq15H
|
||||||
|
hHvCpBc6jXDFYoKFzq6FfO/0kFkE5HoWweIgxwRow0xBCDJAJ+ryUEyy+Ay/pQHb
|
||||||
|
IAjwilRS0V+WdnVw4mTjBAhPvb4jPOo97Yfy3PYUyx2F3newkqXOZy+zx3G/ANoa
|
||||||
|
ncypfMGyy76sfCWKqw4J1gVkVQLwbB6gQkXUFGYwY9sRrxbG93kQw76Flc/E/s52
|
||||||
|
62j4v1IM0fq0t/St+Y/+s6Lkw` + `aqt3ft1nsqWcRaVDdqvMfkzgJGXlw0bGzJG5MEQ
|
||||||
|
AIBq3dHRAoGBAP8OeG/DKG2Z1VmSfzuz1pas1fbZ+F7venOBrjez3sKlb3Pyl2aH
|
||||||
|
mt2wjaTUi5v10VrHgYtOEdqyhQeUSYydWXIBKNMag0NLLrfFUKZK+57wrHWFdFjn
|
||||||
|
VgpsdkLSNTOZpC8gA5OaJ+36IcOPfGqyyP9wuuRoaYnVT1KEzqLa9FEFAoGBAPaq
|
||||||
|
pglwhil2rxjJE4zq0afQLNpAfi7Xqcrepij+xvJIcIj7nawxXuPxqRFxONE/h3yX
|
||||||
|
zkybO8wLdbHX9Iw/wc1j50Uf1Z5gHdLf7/hQJoWKpz1RnkWRy6CYON8v1tpVp0tb
|
||||||
|
OAajR/kZnzebq2mfa7pyy5zDCX++2kp/dcFwHf31AoGAE8oupBVTZLWj7TBFuP8q
|
||||||
|
LkS40U92Sv9v09iDCQVmylmFvUxcXPM2m+7f/qMTNgWrucxzC7kB/6MMWVszHbrz
|
||||||
|
vrnCTibnemgx9sZTjKOSxHFOIEw7i85fSa3Cu0qOIDPSnmlwfZpfcMKQrhjLAYhf
|
||||||
|
uhooFiLX1X78iZ2OXup4PHUCgYEAsmBrm83sp1V1gAYBBlnVbXakyNv0pCk/Vz61
|
||||||
|
iFXeRt1NzDGxLxGw3kQnED8BaIh5kQcyn8Fud7sdzJMv/LAqlT4Ww60mzNYTGyjo
|
||||||
|
H3jOsqm3ESfRvduWFreeAQBWbiOczGjV1i8D4EbAFfWT+tjXjchwKBf+6Yt5zn/o
|
||||||
|
Bw/uEHUCgYAFs+JPOR25oRyBs7ujrMo/OY1z/eXTVVgZxY+tYGe1FJqDeFyR7ytK
|
||||||
|
+JBB1MuDwQKGm2wSIXdCzTNoIx2B9zTseiPTwT8G7vqNFhXoIaTBp4P2xIQb45mJ
|
||||||
|
7GkTsMBHwpSMOXgX9Weq3v5xOJ2WxVtjENmd6qzxcYCO5lP15O17hA==
|
||||||
|
-----END RSA PRIVATE KEY-----`
|
||||||
131
vendor/github.com/hashicorp/go-getter/get_hg.go
generated
vendored
Normal file
131
vendor/github.com/hashicorp/go-getter/get_hg.go
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
urlhelper "github.com/hashicorp/go-getter/helper/url"
|
||||||
|
"github.com/hashicorp/go-safetemp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HgGetter is a Getter implementation that will download a module from
|
||||||
|
// a Mercurial repository.
|
||||||
|
type HgGetter struct{}
|
||||||
|
|
||||||
|
func (g *HgGetter) ClientMode(_ *url.URL) (ClientMode, error) {
|
||||||
|
return ClientModeDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HgGetter) Get(dst string, u *url.URL) error {
|
||||||
|
if _, err := exec.LookPath("hg"); err != nil {
|
||||||
|
return fmt.Errorf("hg must be available and on the PATH")
|
||||||
|
}
|
||||||
|
|
||||||
|
newURL, err := urlhelper.Parse(u.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fixWindowsDrivePath(newURL) {
|
||||||
|
// See valid file path form on http://www.selenic.com/hg/help/urls
|
||||||
|
newURL.Path = fmt.Sprintf("/%s", newURL.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract some query parameters we use
|
||||||
|
var rev string
|
||||||
|
q := newURL.Query()
|
||||||
|
if len(q) > 0 {
|
||||||
|
rev = q.Get("rev")
|
||||||
|
q.Del("rev")
|
||||||
|
|
||||||
|
newURL.RawQuery = q.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = os.Stat(dst)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if err := g.clone(dst, newURL); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.pull(dst, newURL); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.update(dst, newURL, rev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFile for Hg doesn't support updating at this time. It will download
|
||||||
|
// the file every time.
|
||||||
|
func (g *HgGetter) GetFile(dst string, u *url.URL) error {
|
||||||
|
// Create a temporary directory to store the full source. This has to be
|
||||||
|
// a non-existent directory.
|
||||||
|
td, tdcloser, err := safetemp.Dir("", "getter")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tdcloser.Close()
|
||||||
|
|
||||||
|
// Get the filename, and strip the filename from the URL so we can
|
||||||
|
// just get the repository directly.
|
||||||
|
filename := filepath.Base(u.Path)
|
||||||
|
u.Path = filepath.ToSlash(filepath.Dir(u.Path))
|
||||||
|
|
||||||
|
// If we're on Windows, we need to set the host to "localhost" for hg
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
u.Host = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the full repository
|
||||||
|
if err := g.Get(td, u); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the single file
|
||||||
|
u, err = urlhelper.Parse(fmtFileURL(filepath.Join(td, filename)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fg := &FileGetter{Copy: true}
|
||||||
|
return fg.GetFile(dst, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HgGetter) clone(dst string, u *url.URL) error {
|
||||||
|
cmd := exec.Command("hg", "clone", "-U", u.String(), dst)
|
||||||
|
return getRunCommand(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HgGetter) pull(dst string, u *url.URL) error {
|
||||||
|
cmd := exec.Command("hg", "pull")
|
||||||
|
cmd.Dir = dst
|
||||||
|
return getRunCommand(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HgGetter) update(dst string, u *url.URL, rev string) error {
|
||||||
|
args := []string{"update"}
|
||||||
|
if rev != "" {
|
||||||
|
args = append(args, rev)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("hg", args...)
|
||||||
|
cmd.Dir = dst
|
||||||
|
return getRunCommand(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixWindowsDrivePath(u *url.URL) bool {
|
||||||
|
// hg assumes a file:/// prefix for Windows drive letter file paths.
|
||||||
|
// (e.g. file:///c:/foo/bar)
|
||||||
|
// If the URL Path does not begin with a '/' character, the resulting URL
|
||||||
|
// path will have a file:// prefix. (e.g. file://c:/foo/bar)
|
||||||
|
// See http://www.selenic.com/hg/help/urls and the examples listed in
|
||||||
|
// http://selenic.com/repo/hg-stable/file/1265a3a71d75/mercurial/util.py#l1936
|
||||||
|
return runtime.GOOS == "windows" && u.Scheme == "file" &&
|
||||||
|
len(u.Path) > 1 && u.Path[0] != '/' && u.Path[1] == ':'
|
||||||
|
}
|
||||||
98
vendor/github.com/hashicorp/go-getter/get_hg_test.go
generated
vendored
Normal file
98
vendor/github.com/hashicorp/go-getter/get_hg_test.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testHasHg bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if _, err := exec.LookPath("hg"); err == nil {
|
||||||
|
testHasHg = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHgGetter_impl(t *testing.T) {
|
||||||
|
var _ Getter = new(HgGetter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHgGetter(t *testing.T) {
|
||||||
|
if !testHasHg {
|
||||||
|
t.Log("hg not found, skipping")
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
g := new(HgGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
|
||||||
|
// With a dir that doesn't exist
|
||||||
|
if err := g.Get(dst, testModuleURL("basic-hg")); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHgGetter_branch(t *testing.T) {
|
||||||
|
if !testHasHg {
|
||||||
|
t.Log("hg not found, skipping")
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
g := new(HgGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
|
||||||
|
url := testModuleURL("basic-hg")
|
||||||
|
q := url.Query()
|
||||||
|
q.Add("rev", "test-branch")
|
||||||
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
if err := g.Get(dst, url); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "main_branch.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get again should work
|
||||||
|
if err := g.Get(dst, url); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath = filepath.Join(dst, "main_branch.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHgGetter_GetFile(t *testing.T) {
|
||||||
|
if !testHasHg {
|
||||||
|
t.Log("hg not found, skipping")
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
g := new(HgGetter)
|
||||||
|
dst := tempFile(t)
|
||||||
|
|
||||||
|
// Download
|
||||||
|
if err := g.GetFile(dst, testModuleURL("basic-hg/foo.txt")); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
if _, err := os.Stat(dst); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
assertContents(t, dst, "Hello\n")
|
||||||
|
}
|
||||||
250
vendor/github.com/hashicorp/go-getter/get_http.go
generated
vendored
Normal file
250
vendor/github.com/hashicorp/go-getter/get_http.go
generated
vendored
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-safetemp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HttpGetter is a Getter implementation that will download from an HTTP
|
||||||
|
// endpoint.
|
||||||
|
//
|
||||||
|
// For file downloads, HTTP is used directly.
|
||||||
|
//
|
||||||
|
// The protocol for downloading a directory from an HTTP endpoing is as follows:
|
||||||
|
//
|
||||||
|
// An HTTP GET request is made to the URL with the additional GET parameter
|
||||||
|
// "terraform-get=1". This lets you handle that scenario specially if you
|
||||||
|
// wish. The response must be a 2xx.
|
||||||
|
//
|
||||||
|
// First, a header is looked for "X-Terraform-Get" which should contain
|
||||||
|
// a source URL to download.
|
||||||
|
//
|
||||||
|
// If the header is not present, then a meta tag is searched for named
|
||||||
|
// "terraform-get" and the content should be a source URL.
|
||||||
|
//
|
||||||
|
// The source URL, whether from the header or meta tag, must be a fully
|
||||||
|
// formed URL. The shorthand syntax of "github.com/foo/bar" or relative
|
||||||
|
// paths are not allowed.
|
||||||
|
type HttpGetter struct {
|
||||||
|
// Netrc, if true, will lookup and use auth information found
|
||||||
|
// in the user's netrc file if available.
|
||||||
|
Netrc bool
|
||||||
|
|
||||||
|
// Client is the http.Client to use for Get requests.
|
||||||
|
// This defaults to a cleanhttp.DefaultClient if left unset.
|
||||||
|
Client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HttpGetter) ClientMode(u *url.URL) (ClientMode, error) {
|
||||||
|
if strings.HasSuffix(u.Path, "/") {
|
||||||
|
return ClientModeDir, nil
|
||||||
|
}
|
||||||
|
return ClientModeFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HttpGetter) Get(dst string, u *url.URL) error {
|
||||||
|
// Copy the URL so we can modify it
|
||||||
|
var newU url.URL = *u
|
||||||
|
u = &newU
|
||||||
|
|
||||||
|
if g.Netrc {
|
||||||
|
// Add auth from netrc if we can
|
||||||
|
if err := addAuthFromNetrc(u); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.Client == nil {
|
||||||
|
g.Client = httpClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add terraform-get to the parameter.
|
||||||
|
q := u.Query()
|
||||||
|
q.Add("terraform-get", "1")
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
// Get the URL
|
||||||
|
resp, err := g.Client.Get(u.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return fmt.Errorf("bad response code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the source URL
|
||||||
|
var source string
|
||||||
|
if v := resp.Header.Get("X-Terraform-Get"); v != "" {
|
||||||
|
source = v
|
||||||
|
} else {
|
||||||
|
source, err = g.parseMeta(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if source == "" {
|
||||||
|
return fmt.Errorf("no source URL was returned")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a subdir component, then we download the root separately
|
||||||
|
// into a temporary directory, then copy over the proper subdir.
|
||||||
|
source, subDir := SourceDirSubdir(source)
|
||||||
|
if subDir == "" {
|
||||||
|
return Get(dst, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a subdir, time to jump some hoops
|
||||||
|
return g.getSubdir(dst, source, subDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *HttpGetter) GetFile(dst string, u *url.URL) error {
|
||||||
|
if g.Netrc {
|
||||||
|
// Add auth from netrc if we can
|
||||||
|
if err := addAuthFromNetrc(u); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.Client == nil {
|
||||||
|
g.Client = httpClient
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := g.Client.Get(u.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return fmt.Errorf("bad response code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create all the parent directories
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := io.Copy(f, resp.Body)
|
||||||
|
if err == nil && n < resp.ContentLength {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
}
|
||||||
|
if err1 := f.Close(); err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSubdir downloads the source into the destination, but with
|
||||||
|
// the proper subdir.
|
||||||
|
func (g *HttpGetter) getSubdir(dst, source, subDir string) error {
|
||||||
|
// Create a temporary directory to store the full source. This has to be
|
||||||
|
// a non-existent directory.
|
||||||
|
td, tdcloser, err := safetemp.Dir("", "getter")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tdcloser.Close()
|
||||||
|
|
||||||
|
// Download that into the given directory
|
||||||
|
if err := Get(td, source); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process any globbing
|
||||||
|
sourcePath, err := SubdirGlob(td, subDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the subdir path actually exists
|
||||||
|
if _, err := os.Stat(sourcePath); err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error downloading %s: %s", source, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the subdirectory into our actual destination.
|
||||||
|
if err := os.RemoveAll(dst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the final destination
|
||||||
|
if err := os.MkdirAll(dst, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyDir(dst, sourcePath, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseMeta looks for the first meta tag in the given reader that
|
||||||
|
// will give us the source URL.
|
||||||
|
func (g *HttpGetter) parseMeta(r io.Reader) (string, error) {
|
||||||
|
d := xml.NewDecoder(r)
|
||||||
|
d.CharsetReader = charsetReader
|
||||||
|
d.Strict = false
|
||||||
|
var err error
|
||||||
|
var t xml.Token
|
||||||
|
for {
|
||||||
|
t, err = d.Token()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
e, ok := t.(xml.StartElement)
|
||||||
|
if !ok || !strings.EqualFold(e.Name.Local, "meta") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if attrValue(e.Attr, "name") != "terraform-get" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if f := attrValue(e.Attr, "content"); f != "" {
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// attrValue returns the attribute value for the case-insensitive key
|
||||||
|
// `name', or the empty string if nothing is found.
|
||||||
|
func attrValue(attrs []xml.Attr, name string) string {
|
||||||
|
for _, a := range attrs {
|
||||||
|
if strings.EqualFold(a.Name.Local, name) {
|
||||||
|
return a.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// charsetReader returns a reader for the given charset. Currently
|
||||||
|
// it only supports UTF-8 and ASCII. Otherwise, it returns a meaningful
|
||||||
|
// error which is printed by go get, so the user can find why the package
|
||||||
|
// wasn't downloaded if the encoding is not supported. Note that, in
|
||||||
|
// order to reduce potential errors, ASCII is treated as UTF-8 (i.e. characters
|
||||||
|
// greater than 0x7f are not rejected).
|
||||||
|
func charsetReader(charset string, input io.Reader) (io.Reader, error) {
|
||||||
|
switch strings.ToLower(charset) {
|
||||||
|
case "ascii":
|
||||||
|
return input, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
|
||||||
|
}
|
||||||
|
}
|
||||||
331
vendor/github.com/hashicorp/go-getter/get_http_test.go
generated
vendored
Normal file
331
vendor/github.com/hashicorp/go-getter/get_http_test.go
generated
vendored
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHttpGetter_impl(t *testing.T) {
|
||||||
|
var _ Getter = new(HttpGetter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHttpGetter_header(t *testing.T) {
|
||||||
|
ln := testHttpServer(t)
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
g := new(HttpGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
defer os.RemoveAll(dst)
|
||||||
|
|
||||||
|
var u url.URL
|
||||||
|
u.Scheme = "http"
|
||||||
|
u.Host = ln.Addr().String()
|
||||||
|
u.Path = "/header"
|
||||||
|
|
||||||
|
// Get it!
|
||||||
|
if err := g.Get(dst, &u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHttpGetter_meta(t *testing.T) {
|
||||||
|
ln := testHttpServer(t)
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
g := new(HttpGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
defer os.RemoveAll(dst)
|
||||||
|
|
||||||
|
var u url.URL
|
||||||
|
u.Scheme = "http"
|
||||||
|
u.Host = ln.Addr().String()
|
||||||
|
u.Path = "/meta"
|
||||||
|
|
||||||
|
// Get it!
|
||||||
|
if err := g.Get(dst, &u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHttpGetter_metaSubdir(t *testing.T) {
|
||||||
|
ln := testHttpServer(t)
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
g := new(HttpGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
defer os.RemoveAll(dst)
|
||||||
|
|
||||||
|
var u url.URL
|
||||||
|
u.Scheme = "http"
|
||||||
|
u.Host = ln.Addr().String()
|
||||||
|
u.Path = "/meta-subdir"
|
||||||
|
|
||||||
|
// Get it!
|
||||||
|
if err := g.Get(dst, &u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "sub.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHttpGetter_metaSubdirGlob(t *testing.T) {
|
||||||
|
ln := testHttpServer(t)
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
g := new(HttpGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
defer os.RemoveAll(dst)
|
||||||
|
|
||||||
|
var u url.URL
|
||||||
|
u.Scheme = "http"
|
||||||
|
u.Host = ln.Addr().String()
|
||||||
|
u.Path = "/meta-subdir-glob"
|
||||||
|
|
||||||
|
// Get it!
|
||||||
|
if err := g.Get(dst, &u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "sub.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHttpGetter_none(t *testing.T) {
|
||||||
|
ln := testHttpServer(t)
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
g := new(HttpGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
defer os.RemoveAll(dst)
|
||||||
|
|
||||||
|
var u url.URL
|
||||||
|
u.Scheme = "http"
|
||||||
|
u.Host = ln.Addr().String()
|
||||||
|
u.Path = "/none"
|
||||||
|
|
||||||
|
// Get it!
|
||||||
|
if err := g.Get(dst, &u); err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHttpGetter_file(t *testing.T) {
|
||||||
|
ln := testHttpServer(t)
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
g := new(HttpGetter)
|
||||||
|
dst := tempFile(t)
|
||||||
|
defer os.RemoveAll(dst)
|
||||||
|
|
||||||
|
var u url.URL
|
||||||
|
u.Scheme = "http"
|
||||||
|
u.Host = ln.Addr().String()
|
||||||
|
u.Path = "/file"
|
||||||
|
|
||||||
|
// Get it!
|
||||||
|
if err := g.GetFile(dst, &u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
if _, err := os.Stat(dst); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
assertContents(t, dst, "Hello\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHttpGetter_auth(t *testing.T) {
|
||||||
|
ln := testHttpServer(t)
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
g := new(HttpGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
defer os.RemoveAll(dst)
|
||||||
|
|
||||||
|
var u url.URL
|
||||||
|
u.Scheme = "http"
|
||||||
|
u.Host = ln.Addr().String()
|
||||||
|
u.Path = "/meta-auth"
|
||||||
|
u.User = url.UserPassword("foo", "bar")
|
||||||
|
|
||||||
|
// Get it!
|
||||||
|
if err := g.Get(dst, &u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHttpGetter_authNetrc(t *testing.T) {
|
||||||
|
ln := testHttpServer(t)
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
g := new(HttpGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
defer os.RemoveAll(dst)
|
||||||
|
|
||||||
|
var u url.URL
|
||||||
|
u.Scheme = "http"
|
||||||
|
u.Host = ln.Addr().String()
|
||||||
|
u.Path = "/meta"
|
||||||
|
|
||||||
|
// Write the netrc file
|
||||||
|
path, closer := tempFileContents(t, fmt.Sprintf(testHttpNetrc, ln.Addr().String()))
|
||||||
|
defer closer()
|
||||||
|
defer tempEnv(t, "NETRC", path)()
|
||||||
|
|
||||||
|
// Get it!
|
||||||
|
if err := g.Get(dst, &u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// test round tripper that only returns an error
|
||||||
|
type errRoundTripper struct{}
|
||||||
|
|
||||||
|
func (errRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
|
return nil, errors.New("test round tripper")
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify that the default httpClient no longer comes from http.DefaultClient
|
||||||
|
func TestHttpGetter_cleanhttp(t *testing.T) {
|
||||||
|
ln := testHttpServer(t)
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
// break the default http client
|
||||||
|
http.DefaultClient.Transport = errRoundTripper{}
|
||||||
|
defer func() {
|
||||||
|
http.DefaultClient.Transport = http.DefaultTransport
|
||||||
|
}()
|
||||||
|
|
||||||
|
g := new(HttpGetter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
defer os.RemoveAll(dst)
|
||||||
|
|
||||||
|
var u url.URL
|
||||||
|
u.Scheme = "http"
|
||||||
|
u.Host = ln.Addr().String()
|
||||||
|
u.Path = "/header"
|
||||||
|
|
||||||
|
// Get it!
|
||||||
|
if err := g.Get(dst, &u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHttpServer(t *testing.T) net.Listener {
|
||||||
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/file", testHttpHandlerFile)
|
||||||
|
mux.HandleFunc("/header", testHttpHandlerHeader)
|
||||||
|
mux.HandleFunc("/meta", testHttpHandlerMeta)
|
||||||
|
mux.HandleFunc("/meta-auth", testHttpHandlerMetaAuth)
|
||||||
|
mux.HandleFunc("/meta-subdir", testHttpHandlerMetaSubdir)
|
||||||
|
mux.HandleFunc("/meta-subdir-glob", testHttpHandlerMetaSubdirGlob)
|
||||||
|
|
||||||
|
var server http.Server
|
||||||
|
server.Handler = mux
|
||||||
|
go server.Serve(ln)
|
||||||
|
|
||||||
|
return ln
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHttpHandlerFile(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("Hello\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHttpHandlerHeader(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Add("X-Terraform-Get", testModuleURL("basic").String())
|
||||||
|
w.WriteHeader(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHttpHandlerMeta(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(fmt.Sprintf(testHttpMetaStr, testModuleURL("basic").String())))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHttpHandlerMetaAuth(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user, pass, ok := r.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user != "foo" || pass != "bar" {
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(fmt.Sprintf(testHttpMetaStr, testModuleURL("basic").String())))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHttpHandlerMetaSubdir(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(fmt.Sprintf(testHttpMetaStr, testModuleURL("basic//subdir").String())))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHttpHandlerMetaSubdirGlob(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(fmt.Sprintf(testHttpMetaStr, testModuleURL("basic//sub*").String())))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHttpHandlerNone(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(testHttpNoneStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
const testHttpMetaStr = `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="terraform-get" content="%s">
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
const testHttpNoneStr = `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
const testHttpNetrc = `
|
||||||
|
machine %s
|
||||||
|
login foo
|
||||||
|
password bar
|
||||||
|
`
|
||||||
52
vendor/github.com/hashicorp/go-getter/get_mock.go
generated
vendored
Normal file
52
vendor/github.com/hashicorp/go-getter/get_mock.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockGetter is an implementation of Getter that can be used for tests.
|
||||||
|
type MockGetter struct {
|
||||||
|
// Proxy, if set, will be called after recording the calls below.
|
||||||
|
// If it isn't set, then the *Err values will be returned.
|
||||||
|
Proxy Getter
|
||||||
|
|
||||||
|
GetCalled bool
|
||||||
|
GetDst string
|
||||||
|
GetURL *url.URL
|
||||||
|
GetErr error
|
||||||
|
|
||||||
|
GetFileCalled bool
|
||||||
|
GetFileDst string
|
||||||
|
GetFileURL *url.URL
|
||||||
|
GetFileErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *MockGetter) Get(dst string, u *url.URL) error {
|
||||||
|
g.GetCalled = true
|
||||||
|
g.GetDst = dst
|
||||||
|
g.GetURL = u
|
||||||
|
|
||||||
|
if g.Proxy != nil {
|
||||||
|
return g.Proxy.Get(dst, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.GetErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *MockGetter) GetFile(dst string, u *url.URL) error {
|
||||||
|
g.GetFileCalled = true
|
||||||
|
g.GetFileDst = dst
|
||||||
|
g.GetFileURL = u
|
||||||
|
|
||||||
|
if g.Proxy != nil {
|
||||||
|
return g.Proxy.GetFile(dst, u)
|
||||||
|
}
|
||||||
|
return g.GetFileErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *MockGetter) ClientMode(u *url.URL) (ClientMode, error) {
|
||||||
|
if l := len(u.Path); l > 0 && u.Path[l-1:] == "/" {
|
||||||
|
return ClientModeDir, nil
|
||||||
|
}
|
||||||
|
return ClientModeFile, nil
|
||||||
|
}
|
||||||
270
vendor/github.com/hashicorp/go-getter/get_s3.go
generated
vendored
Normal file
270
vendor/github.com/hashicorp/go-getter/get_s3.go
generated
vendored
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// S3Getter is a Getter implementation that will download a module from
|
||||||
|
// a S3 bucket.
|
||||||
|
type S3Getter struct{}
|
||||||
|
|
||||||
|
func (g *S3Getter) ClientMode(u *url.URL) (ClientMode, error) {
|
||||||
|
// Parse URL
|
||||||
|
region, bucket, path, _, creds, err := g.parseUrl(u)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create client config
|
||||||
|
config := g.getAWSConfig(region, u, creds)
|
||||||
|
sess := session.New(config)
|
||||||
|
client := s3.New(sess)
|
||||||
|
|
||||||
|
// List the object(s) at the given prefix
|
||||||
|
req := &s3.ListObjectsInput{
|
||||||
|
Bucket: aws.String(bucket),
|
||||||
|
Prefix: aws.String(path),
|
||||||
|
}
|
||||||
|
resp, err := client.ListObjects(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range resp.Contents {
|
||||||
|
// Use file mode on exact match.
|
||||||
|
if *o.Key == path {
|
||||||
|
return ClientModeFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use dir mode if child keys are found.
|
||||||
|
if strings.HasPrefix(*o.Key, path+"/") {
|
||||||
|
return ClientModeDir, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There was no match, so just return file mode. The download is going
|
||||||
|
// to fail but we will let S3 return the proper error later.
|
||||||
|
return ClientModeFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *S3Getter) Get(dst string, u *url.URL) error {
|
||||||
|
// Parse URL
|
||||||
|
region, bucket, path, _, creds, err := g.parseUrl(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove destination if it already exists
|
||||||
|
_, err = os.Stat(dst)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// Remove the destination
|
||||||
|
if err := os.RemoveAll(dst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create all the parent directories
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := g.getAWSConfig(region, u, creds)
|
||||||
|
sess := session.New(config)
|
||||||
|
client := s3.New(sess)
|
||||||
|
|
||||||
|
// List files in path, keep listing until no more objects are found
|
||||||
|
lastMarker := ""
|
||||||
|
hasMore := true
|
||||||
|
for hasMore {
|
||||||
|
req := &s3.ListObjectsInput{
|
||||||
|
Bucket: aws.String(bucket),
|
||||||
|
Prefix: aws.String(path),
|
||||||
|
}
|
||||||
|
if lastMarker != "" {
|
||||||
|
req.Marker = aws.String(lastMarker)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.ListObjects(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
hasMore = aws.BoolValue(resp.IsTruncated)
|
||||||
|
|
||||||
|
// Get each object storing each file relative to the destination path
|
||||||
|
for _, object := range resp.Contents {
|
||||||
|
lastMarker = aws.StringValue(object.Key)
|
||||||
|
objPath := aws.StringValue(object.Key)
|
||||||
|
|
||||||
|
// If the key ends with a backslash assume it is a directory and ignore
|
||||||
|
if strings.HasSuffix(objPath, "/") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the object destination path
|
||||||
|
objDst, err := filepath.Rel(path, objPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
objDst = filepath.Join(dst, objDst)
|
||||||
|
|
||||||
|
if err := g.getObject(client, objDst, bucket, objPath, ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *S3Getter) GetFile(dst string, u *url.URL) error {
|
||||||
|
region, bucket, path, version, creds, err := g.parseUrl(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := g.getAWSConfig(region, u, creds)
|
||||||
|
sess := session.New(config)
|
||||||
|
client := s3.New(sess)
|
||||||
|
return g.getObject(client, dst, bucket, path, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *S3Getter) getObject(client *s3.S3, dst, bucket, key, version string) error {
|
||||||
|
req := &s3.GetObjectInput{
|
||||||
|
Bucket: aws.String(bucket),
|
||||||
|
Key: aws.String(key),
|
||||||
|
}
|
||||||
|
if version != "" {
|
||||||
|
req.VersionId = aws.String(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.GetObject(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create all the parent directories
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(f, resp.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *S3Getter) getAWSConfig(region string, url *url.URL, creds *credentials.Credentials) *aws.Config {
|
||||||
|
conf := &aws.Config{}
|
||||||
|
if creds == nil {
|
||||||
|
// Grab the metadata URL
|
||||||
|
metadataURL := os.Getenv("AWS_METADATA_URL")
|
||||||
|
if metadataURL == "" {
|
||||||
|
metadataURL = "http://169.254.169.254:80/latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
creds = credentials.NewChainCredentials(
|
||||||
|
[]credentials.Provider{
|
||||||
|
&credentials.EnvProvider{},
|
||||||
|
&credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
|
||||||
|
&ec2rolecreds.EC2RoleProvider{
|
||||||
|
Client: ec2metadata.New(session.New(&aws.Config{
|
||||||
|
Endpoint: aws.String(metadataURL),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if creds != nil {
|
||||||
|
conf.Endpoint = &url.Host
|
||||||
|
conf.S3ForcePathStyle = aws.Bool(true)
|
||||||
|
if url.Scheme == "http" {
|
||||||
|
conf.DisableSSL = aws.Bool(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.Credentials = creds
|
||||||
|
if region != "" {
|
||||||
|
conf.Region = aws.String(region)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *S3Getter) parseUrl(u *url.URL) (region, bucket, path, version string, creds *credentials.Credentials, err error) {
|
||||||
|
// This just check whether we are dealing with S3 or
|
||||||
|
// any other S3 compliant service. S3 has a predictable
|
||||||
|
// url as others do not
|
||||||
|
if strings.Contains(u.Host, "amazonaws.com") {
|
||||||
|
// Expected host style: s3.amazonaws.com. They always have 3 parts,
|
||||||
|
// although the first may differ if we're accessing a specific region.
|
||||||
|
hostParts := strings.Split(u.Host, ".")
|
||||||
|
if len(hostParts) != 3 {
|
||||||
|
err = fmt.Errorf("URL is not a valid S3 URL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the region out of the first part of the host
|
||||||
|
region = strings.TrimPrefix(strings.TrimPrefix(hostParts[0], "s3-"), "s3")
|
||||||
|
if region == "" {
|
||||||
|
region = "us-east-1"
|
||||||
|
}
|
||||||
|
|
||||||
|
pathParts := strings.SplitN(u.Path, "/", 3)
|
||||||
|
if len(pathParts) != 3 {
|
||||||
|
err = fmt.Errorf("URL is not a valid S3 URL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket = pathParts[1]
|
||||||
|
path = pathParts[2]
|
||||||
|
version = u.Query().Get("version")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
pathParts := strings.SplitN(u.Path, "/", 3)
|
||||||
|
if len(pathParts) != 3 {
|
||||||
|
err = fmt.Errorf("URL is not a valid S3 complaint URL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bucket = pathParts[1]
|
||||||
|
path = pathParts[2]
|
||||||
|
version = u.Query().Get("version")
|
||||||
|
region = u.Query().Get("region")
|
||||||
|
if region == "" {
|
||||||
|
region = "us-east-1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, hasAwsId := u.Query()["aws_access_key_id"]
|
||||||
|
_, hasAwsSecret := u.Query()["aws_access_key_secret"]
|
||||||
|
_, hasAwsToken := u.Query()["aws_access_token"]
|
||||||
|
if hasAwsId || hasAwsSecret || hasAwsToken {
|
||||||
|
creds = credentials.NewStaticCredentials(
|
||||||
|
u.Query().Get("aws_access_key_id"),
|
||||||
|
u.Query().Get("aws_access_key_secret"),
|
||||||
|
u.Query().Get("aws_access_token"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
250
vendor/github.com/hashicorp/go-getter/get_s3_test.go
generated
vendored
Normal file
250
vendor/github.com/hashicorp/go-getter/get_s3_test.go
generated
vendored
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// These are well known restricted IAM keys to a HashiCorp-managed bucket
|
||||||
|
// in a private AWS account that only has access to the open source test
|
||||||
|
// resources.
|
||||||
|
//
|
||||||
|
// We do the string concat below to avoid AWS autodetection of a key. This
|
||||||
|
// key is locked down an IAM policy that is read-only so we're purposely
|
||||||
|
// exposing it.
|
||||||
|
os.Setenv("AWS_ACCESS_KEY", "AKIAITTDR"+"WY2STXOZE2A")
|
||||||
|
os.Setenv("AWS_SECRET_KEY", "oMwSyqdass2kPF"+"/7ORZA9dlb/iegz+89B0Cy01Ea")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestS3Getter_impl(t *testing.T) {
|
||||||
|
var _ Getter = new(S3Getter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestS3Getter(t *testing.T) {
|
||||||
|
g := new(S3Getter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
|
||||||
|
// With a dir that doesn't exist
|
||||||
|
err := g.Get(
|
||||||
|
dst, testURL("https://s3.amazonaws.com/hc-oss-test/go-getter/folder"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestS3Getter_subdir(t *testing.T) {
|
||||||
|
g := new(S3Getter)
|
||||||
|
dst := tempDir(t)
|
||||||
|
|
||||||
|
// With a dir that doesn't exist
|
||||||
|
err := g.Get(
|
||||||
|
dst, testURL("https://s3.amazonaws.com/hc-oss-test/go-getter/folder/subfolder"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
subPath := filepath.Join(dst, "sub.tf")
|
||||||
|
if _, err := os.Stat(subPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestS3Getter_GetFile(t *testing.T) {
|
||||||
|
g := new(S3Getter)
|
||||||
|
dst := tempFile(t)
|
||||||
|
|
||||||
|
// Download
|
||||||
|
err := g.GetFile(
|
||||||
|
dst, testURL("https://s3.amazonaws.com/hc-oss-test/go-getter/folder/main.tf"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
if _, err := os.Stat(dst); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
assertContents(t, dst, "# Main\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestS3Getter_GetFile_badParams(t *testing.T) {
|
||||||
|
g := new(S3Getter)
|
||||||
|
dst := tempFile(t)
|
||||||
|
|
||||||
|
// Download
|
||||||
|
err := g.GetFile(
|
||||||
|
dst,
|
||||||
|
testURL("https://s3.amazonaws.com/hc-oss-test/go-getter/folder/main.tf?aws_access_key_id=foo&aws_access_key_secret=bar&aws_access_token=baz"))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error, got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqerr, ok := err.(awserr.RequestFailure); !ok || reqerr.StatusCode() != 403 {
|
||||||
|
t.Fatalf("expected InvalidAccessKeyId error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestS3Getter_GetFile_notfound(t *testing.T) {
|
||||||
|
g := new(S3Getter)
|
||||||
|
dst := tempFile(t)
|
||||||
|
|
||||||
|
// Download
|
||||||
|
err := g.GetFile(
|
||||||
|
dst, testURL("https://s3.amazonaws.com/hc-oss-test/go-getter/folder/404.tf"))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error, got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestS3Getter_ClientMode_dir(t *testing.T) {
|
||||||
|
g := new(S3Getter)
|
||||||
|
|
||||||
|
// Check client mode on a key prefix with only a single key.
|
||||||
|
mode, err := g.ClientMode(
|
||||||
|
testURL("https://s3.amazonaws.com/hc-oss-test/go-getter/folder"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if mode != ClientModeDir {
|
||||||
|
t.Fatal("expect ClientModeDir")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestS3Getter_ClientMode_file(t *testing.T) {
|
||||||
|
g := new(S3Getter)
|
||||||
|
|
||||||
|
// Check client mode on a key prefix which contains sub-keys.
|
||||||
|
mode, err := g.ClientMode(
|
||||||
|
testURL("https://s3.amazonaws.com/hc-oss-test/go-getter/folder/main.tf"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if mode != ClientModeFile {
|
||||||
|
t.Fatal("expect ClientModeFile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestS3Getter_ClientMode_notfound(t *testing.T) {
|
||||||
|
g := new(S3Getter)
|
||||||
|
|
||||||
|
// Check the client mode when a non-existent key is looked up. This does not
|
||||||
|
// return an error, but rather should just return the file mode so that S3
|
||||||
|
// can return an appropriate error later on. This also checks that the
|
||||||
|
// prefix is handled properly (e.g., "/fold" and "/folder" don't put the
|
||||||
|
// client mode into "dir".
|
||||||
|
mode, err := g.ClientMode(
|
||||||
|
testURL("https://s3.amazonaws.com/hc-oss-test/go-getter/fold"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if mode != ClientModeFile {
|
||||||
|
t.Fatal("expect ClientModeFile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestS3Getter_ClientMode_collision(t *testing.T) {
|
||||||
|
g := new(S3Getter)
|
||||||
|
|
||||||
|
// Check that the client mode is "file" if there is both an object and a
|
||||||
|
// folder with a common prefix (i.e., a "collision" in the namespace).
|
||||||
|
mode, err := g.ClientMode(
|
||||||
|
testURL("https://s3.amazonaws.com/hc-oss-test/go-getter/collision/foo"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if mode != ClientModeFile {
|
||||||
|
t.Fatal("expect ClientModeFile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestS3Getter_Url(t *testing.T) {
|
||||||
|
var s3tests = []struct {
|
||||||
|
name string
|
||||||
|
url string
|
||||||
|
region string
|
||||||
|
bucket string
|
||||||
|
path string
|
||||||
|
version string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "AWSv1234",
|
||||||
|
url: "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234",
|
||||||
|
region: "eu-west-1",
|
||||||
|
bucket: "bucket",
|
||||||
|
path: "foo/bar.baz",
|
||||||
|
version: "1234",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "localhost-1",
|
||||||
|
url: "s3::http://127.0.0.1:9000/test-bucket/hello.txt?aws_access_key_id=TESTID&aws_access_key_secret=TestSecret®ion=us-east-2&version=1",
|
||||||
|
region: "us-east-2",
|
||||||
|
bucket: "test-bucket",
|
||||||
|
path: "hello.txt",
|
||||||
|
version: "1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "localhost-2",
|
||||||
|
url: "s3::http://127.0.0.1:9000/test-bucket/hello.txt?aws_access_key_id=TESTID&aws_access_key_secret=TestSecret&version=1",
|
||||||
|
region: "us-east-1",
|
||||||
|
bucket: "test-bucket",
|
||||||
|
path: "hello.txt",
|
||||||
|
version: "1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "localhost-3",
|
||||||
|
url: "s3::http://127.0.0.1:9000/test-bucket/hello.txt?aws_access_key_id=TESTID&aws_access_key_secret=TestSecret",
|
||||||
|
region: "us-east-1",
|
||||||
|
bucket: "test-bucket",
|
||||||
|
path: "hello.txt",
|
||||||
|
version: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, pt := range s3tests {
|
||||||
|
t.Run(pt.name, func(t *testing.T) {
|
||||||
|
g := new(S3Getter)
|
||||||
|
forced, src := getForcedGetter(pt.url)
|
||||||
|
u, err := url.Parse(src)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test %d: unexpected error: %s", i, err)
|
||||||
|
}
|
||||||
|
if forced != "s3" {
|
||||||
|
t.Fatalf("expected forced protocol to be s3")
|
||||||
|
}
|
||||||
|
|
||||||
|
region, bucket, path, version, creds, err := g.parseUrl(u)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if region != pt.region {
|
||||||
|
t.Fatalf("expected %s, got %s", pt.region, region)
|
||||||
|
}
|
||||||
|
if bucket != pt.bucket {
|
||||||
|
t.Fatalf("expected %s, got %s", pt.bucket, bucket)
|
||||||
|
}
|
||||||
|
if path != pt.path {
|
||||||
|
t.Fatalf("expected %s, got %s", pt.path, path)
|
||||||
|
}
|
||||||
|
if version != pt.version {
|
||||||
|
t.Fatalf("expected %s, got %s", pt.version, version)
|
||||||
|
}
|
||||||
|
if &creds == nil {
|
||||||
|
t.Fatalf("expected to not be nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
380
vendor/github.com/hashicorp/go-getter/get_test.go
generated
vendored
Normal file
380
vendor/github.com/hashicorp/go-getter/get_test.go
generated
vendored
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGet_badSchema(t *testing.T) {
|
||||||
|
dst := tempDir(t)
|
||||||
|
u := testModule("basic")
|
||||||
|
u = strings.Replace(u, "file", "nope", -1)
|
||||||
|
|
||||||
|
if err := Get(dst, u); err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet_file(t *testing.T) {
|
||||||
|
dst := tempDir(t)
|
||||||
|
u := testModule("basic")
|
||||||
|
|
||||||
|
if err := Get(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/hashicorp/terraform/issues/11438
|
||||||
|
func TestGet_fileDecompressorExt(t *testing.T) {
|
||||||
|
dst := tempDir(t)
|
||||||
|
u := testModule("basic-tgz")
|
||||||
|
|
||||||
|
if err := Get(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/hashicorp/terraform/issues/8418
|
||||||
|
func TestGet_filePercent2F(t *testing.T) {
|
||||||
|
dst := tempDir(t)
|
||||||
|
u := testModule("basic%2Ftest")
|
||||||
|
|
||||||
|
if err := Get(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet_fileDetect(t *testing.T) {
|
||||||
|
dst := tempDir(t)
|
||||||
|
u := filepath.Join("./test-fixtures", "basic")
|
||||||
|
pwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &Client{
|
||||||
|
Src: u,
|
||||||
|
Dst: dst,
|
||||||
|
Pwd: pwd,
|
||||||
|
Dir: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.Get(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet_fileForced(t *testing.T) {
|
||||||
|
dst := tempDir(t)
|
||||||
|
u := testModule("basic")
|
||||||
|
u = "file::" + u
|
||||||
|
|
||||||
|
if err := Get(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet_fileSubdir(t *testing.T) {
|
||||||
|
dst := tempDir(t)
|
||||||
|
u := testModule("basic//subdir")
|
||||||
|
|
||||||
|
if err := Get(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPath := filepath.Join(dst, "sub.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet_archive(t *testing.T) {
|
||||||
|
dst := tempDir(t)
|
||||||
|
u := filepath.Join("./test-fixtures", "archive.tar.gz")
|
||||||
|
u, _ = filepath.Abs(u)
|
||||||
|
|
||||||
|
if err := Get(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAny_archive(t *testing.T) {
|
||||||
|
dst := tempDir(t)
|
||||||
|
u := filepath.Join("./test-fixtures", "archive.tar.gz")
|
||||||
|
u, _ = filepath.Abs(u)
|
||||||
|
|
||||||
|
if err := GetAny(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPath := filepath.Join(dst, "main.tf")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet_archiveRooted(t *testing.T) {
|
||||||
|
dst := tempDir(t)
|
||||||
|
u := testModule("archive-rooted/archive.tar.gz")
|
||||||
|
if err := Get(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPath := filepath.Join(dst, "root", "hello.txt")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet_archiveSubdirWild(t *testing.T) {
|
||||||
|
dst := tempDir(t)
|
||||||
|
u := testModule("archive-rooted/archive.tar.gz")
|
||||||
|
u += "//*"
|
||||||
|
if err := Get(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPath := filepath.Join(dst, "hello.txt")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet_archiveSubdirWildMultiMatch(t *testing.T) {
|
||||||
|
dst := tempDir(t)
|
||||||
|
u := testModule("archive-rooted-multi/archive.tar.gz")
|
||||||
|
u += "//*"
|
||||||
|
if err := Get(dst, u); err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
} else if !strings.Contains(err.Error(), "multiple") {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAny_file(t *testing.T) {
|
||||||
|
dst := tempDir(t)
|
||||||
|
u := testModule("basic-file/foo.txt")
|
||||||
|
|
||||||
|
if err := GetAny(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPath := filepath.Join(dst, "foo.txt")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAny_dir(t *testing.T) {
|
||||||
|
dst := tempDir(t)
|
||||||
|
u := filepath.Join("./test-fixtures", "basic")
|
||||||
|
u, _ = filepath.Abs(u)
|
||||||
|
|
||||||
|
if err := GetAny(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
check := []string{
|
||||||
|
"main.tf",
|
||||||
|
"foo/main.tf",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range check {
|
||||||
|
mainPath := filepath.Join(dst, name)
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFile(t *testing.T) {
|
||||||
|
dst := tempFile(t)
|
||||||
|
u := testModule("basic-file/foo.txt")
|
||||||
|
|
||||||
|
if err := GetFile(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
assertContents(t, dst, "Hello\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFile_archive(t *testing.T) {
|
||||||
|
dst := tempFile(t)
|
||||||
|
u := testModule("basic-file-archive/archive.tar.gz")
|
||||||
|
|
||||||
|
if err := GetFile(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
assertContents(t, dst, "Hello\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFile_archiveChecksum(t *testing.T) {
|
||||||
|
dst := tempFile(t)
|
||||||
|
u := testModule(
|
||||||
|
"basic-file-archive/archive.tar.gz?checksum=md5:fbd90037dacc4b1ab40811d610dde2f0")
|
||||||
|
|
||||||
|
if err := GetFile(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
assertContents(t, dst, "Hello\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFile_archiveNoUnarchive(t *testing.T) {
|
||||||
|
dst := tempFile(t)
|
||||||
|
u := testModule("basic-file-archive/archive.tar.gz")
|
||||||
|
u += "?archive=false"
|
||||||
|
|
||||||
|
if err := GetFile(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
actual := testMD5(t, dst)
|
||||||
|
expected := "fbd90037dacc4b1ab40811d610dde2f0"
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad: %s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFile_checksum(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Append string
|
||||||
|
Err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// MD5
|
||||||
|
{
|
||||||
|
"?checksum=md5:09f7e02f1290be211da707a266f153b3",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"?checksum=md5:09f7e02f1290be211da707a266f153b4",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// SHA1
|
||||||
|
{
|
||||||
|
"?checksum=sha1:1d229271928d3f9e2bb0375bd6ce5db6c6d348d9",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"?checksum=sha1:1d229271928d3f9e2bb0375bd6ce5db6c6d348d0",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// SHA256
|
||||||
|
{
|
||||||
|
"?checksum=sha256:66a045b452102c59d840ec097d59d9467e13a3f34f6494e539ffd32c1bb35f18",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"?checksum=sha256:66a045b452102c59d840ec097d59d9467e13a3f34f6494e539ffd32c1bb35f19",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// SHA512
|
||||||
|
{
|
||||||
|
"?checksum=sha512:c2bad2223811194582af4d1508ac02cd69eeeeedeeb98d54fcae4dcefb13cc882e7640328206603d3fb9cd5f949a9be0db054dd34fbfa190c498a5fe09750cef",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"?checksum=sha512:c2bad2223811194582af4d1508ac02cd69eeeeedeeb98d54fcae4dcefb13cc882e7640328206603d3fb9cd5f949a9be0db054dd34fbfa190c498a5fe09750ced",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
u := testModule("basic-file/foo.txt") + tc.Append
|
||||||
|
|
||||||
|
func() {
|
||||||
|
dst := tempFile(t)
|
||||||
|
defer os.Remove(dst)
|
||||||
|
if err := GetFile(dst, u); (err != nil) != tc.Err {
|
||||||
|
t.Fatalf("append: %s\n\nerr: %s", tc.Append, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the main file exists
|
||||||
|
assertContents(t, dst, "Hello\n")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFile_checksumURL(t *testing.T) {
|
||||||
|
dst := tempFile(t)
|
||||||
|
u := testModule("basic-file/foo.txt") + "?checksum=md5:09f7e02f1290be211da707a266f153b3"
|
||||||
|
|
||||||
|
getter := &MockGetter{Proxy: new(FileGetter)}
|
||||||
|
client := &Client{
|
||||||
|
Src: u,
|
||||||
|
Dst: dst,
|
||||||
|
Dir: false,
|
||||||
|
Getters: map[string]Getter{
|
||||||
|
"file": getter,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.Get(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := getter.GetFileURL.Query().Get("checksum"); v != "" {
|
||||||
|
t.Fatalf("bad: %s", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFile_filename(t *testing.T) {
|
||||||
|
dst := tempDir(t)
|
||||||
|
u := testModule("basic-file/foo.txt")
|
||||||
|
|
||||||
|
u += "?filename=bar.txt"
|
||||||
|
|
||||||
|
if err := GetAny(dst, u); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainPath := filepath.Join(dst, "bar.txt")
|
||||||
|
if _, err := os.Stat(mainPath); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
14
vendor/github.com/hashicorp/go-getter/helper/url/url.go
generated
vendored
Normal file
14
vendor/github.com/hashicorp/go-getter/helper/url/url.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package url
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse parses rawURL into a URL structure.
|
||||||
|
// The rawURL may be relative or absolute.
|
||||||
|
//
|
||||||
|
// Parse is a wrapper for the Go stdlib net/url Parse function, but returns
|
||||||
|
// Windows "safe" URLs on Windows platforms.
|
||||||
|
func Parse(rawURL string) (*url.URL, error) {
|
||||||
|
return parse(rawURL)
|
||||||
|
}
|
||||||
88
vendor/github.com/hashicorp/go-getter/helper/url/url_test.go
generated
vendored
Normal file
88
vendor/github.com/hashicorp/go-getter/helper/url/url_test.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package url
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type parseTest struct {
|
||||||
|
rawURL string
|
||||||
|
scheme string
|
||||||
|
host string
|
||||||
|
path string
|
||||||
|
str string
|
||||||
|
err bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var parseTests = []parseTest{
|
||||||
|
{
|
||||||
|
rawURL: "/foo/bar",
|
||||||
|
scheme: "",
|
||||||
|
host: "",
|
||||||
|
path: "/foo/bar",
|
||||||
|
str: "/foo/bar",
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rawURL: "file:///dir/",
|
||||||
|
scheme: "file",
|
||||||
|
host: "",
|
||||||
|
path: "/dir/",
|
||||||
|
str: "file:///dir/",
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var winParseTests = []parseTest{
|
||||||
|
{
|
||||||
|
rawURL: `C:\`,
|
||||||
|
scheme: ``,
|
||||||
|
host: ``,
|
||||||
|
path: `C:/`,
|
||||||
|
str: `C:/`,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rawURL: `file://C:\`,
|
||||||
|
scheme: `file`,
|
||||||
|
host: ``,
|
||||||
|
path: `C:/`,
|
||||||
|
str: `file://C:/`,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rawURL: `file:///C:\`,
|
||||||
|
scheme: `file`,
|
||||||
|
host: ``,
|
||||||
|
path: `C:/`,
|
||||||
|
str: `file://C:/`,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
parseTests = append(parseTests, winParseTests...)
|
||||||
|
}
|
||||||
|
for i, pt := range parseTests {
|
||||||
|
url, err := Parse(pt.rawURL)
|
||||||
|
if err != nil && !pt.err {
|
||||||
|
t.Errorf("test %d: unexpected error: %s", i, err)
|
||||||
|
}
|
||||||
|
if err == nil && pt.err {
|
||||||
|
t.Errorf("test %d: expected an error", i)
|
||||||
|
}
|
||||||
|
if url.Scheme != pt.scheme {
|
||||||
|
t.Errorf("test %d: expected Scheme = %q, got %q", i, pt.scheme, url.Scheme)
|
||||||
|
}
|
||||||
|
if url.Host != pt.host {
|
||||||
|
t.Errorf("test %d: expected Host = %q, got %q", i, pt.host, url.Host)
|
||||||
|
}
|
||||||
|
if url.Path != pt.path {
|
||||||
|
t.Errorf("test %d: expected Path = %q, got %q", i, pt.path, url.Path)
|
||||||
|
}
|
||||||
|
if url.String() != pt.str {
|
||||||
|
t.Errorf("test %d: expected url.String() = %q, got %q", i, pt.str, url.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
vendor/github.com/hashicorp/go-getter/helper/url/url_unix.go
generated
vendored
Normal file
11
vendor/github.com/hashicorp/go-getter/helper/url/url_unix.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package url
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parse(rawURL string) (*url.URL, error) {
|
||||||
|
return url.Parse(rawURL)
|
||||||
|
}
|
||||||
40
vendor/github.com/hashicorp/go-getter/helper/url/url_windows.go
generated
vendored
Normal file
40
vendor/github.com/hashicorp/go-getter/helper/url/url_windows.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package url
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parse(rawURL string) (*url.URL, error) {
|
||||||
|
// Make sure we're using "/" since URLs are "/"-based.
|
||||||
|
rawURL = filepath.ToSlash(rawURL)
|
||||||
|
|
||||||
|
u, err := url.Parse(rawURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rawURL) > 1 && rawURL[1] == ':' {
|
||||||
|
// Assume we're dealing with a drive letter file path where the drive
|
||||||
|
// letter has been parsed into the URL Scheme, and the rest of the path
|
||||||
|
// has been parsed into the URL Path without the leading ':' character.
|
||||||
|
u.Path = fmt.Sprintf("%s:%s", string(rawURL[0]), u.Path)
|
||||||
|
u.Scheme = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(u.Host) > 1 && u.Host[1] == ':' && strings.HasPrefix(rawURL, "file://") {
|
||||||
|
// Assume we're dealing with a drive letter file path where the drive
|
||||||
|
// letter has been parsed into the URL Host.
|
||||||
|
u.Path = fmt.Sprintf("%s%s", u.Host, u.Path)
|
||||||
|
u.Host = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove leading slash for absolute file paths.
|
||||||
|
if len(u.Path) > 2 && u.Path[0] == '/' && u.Path[2] == ':' {
|
||||||
|
u.Path = u.Path[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return u, err
|
||||||
|
}
|
||||||
78
vendor/github.com/hashicorp/go-getter/module_test.go
generated
vendored
Normal file
78
vendor/github.com/hashicorp/go-getter/module_test.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package getter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
urlhelper "github.com/hashicorp/go-getter/helper/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
const fixtureDir = "./test-fixtures"
|
||||||
|
|
||||||
|
func tempDir(t *testing.T) string {
|
||||||
|
dir, err := ioutil.TempDir("", "tf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if err := os.RemoveAll(dir); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
func tempFile(t *testing.T) string {
|
||||||
|
dir := tempDir(t)
|
||||||
|
return filepath.Join(dir, "foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testModule(n string) string {
|
||||||
|
p := filepath.Join(fixtureDir, n)
|
||||||
|
p, err := filepath.Abs(p)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return fmtFileURL(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testModuleURL(n string) *url.URL {
|
||||||
|
n, subDir := SourceDirSubdir(n)
|
||||||
|
u, err := urlhelper.Parse(testModule(n))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if subDir != "" {
|
||||||
|
u.Path += "//" + subDir
|
||||||
|
u.RawPath = u.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func testURL(s string) *url.URL {
|
||||||
|
u, err := urlhelper.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func testStorage(t *testing.T) Storage {
|
||||||
|
return &FolderStorage{StorageDir: tempDir(t)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertContents(t *testing.T, path string, contents string) {
|
||||||
|
data, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(data, []byte(contents)) {
|
||||||
|
t.Fatalf("bad. expected:\n\n%s\n\nGot:\n\n%s", contents, string(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user