mirror of https://github.com/k3s-io/k3s
commit
595d4b4abd
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"ImportPath": "github.com/GoogleCloudPlatform/kubernetes",
|
||||
"GoVersion": "go1.3",
|
||||
"GoVersion": "go1.3.1",
|
||||
"Packages": [
|
||||
"./..."
|
||||
],
|
||||
|
@ -81,6 +81,10 @@
|
|||
"ImportPath": "github.com/google/gofuzz",
|
||||
"Rev": "aef70dacbc78771e35beb261bb3a72986adf7906"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/kr/text",
|
||||
"Rev": "6807e777504f54ad073ecef66747de158294b639"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/goamz/aws",
|
||||
"Rev": "9cad7da945e699385c1a3e115aa255211921c9bb"
|
||||
|
@ -89,6 +93,20 @@
|
|||
"ImportPath": "github.com/mitchellh/goamz/ec2",
|
||||
"Rev": "9cad7da945e699385c1a3e115aa255211921c9bb"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/mapstructure",
|
||||
"Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/racker/perigee",
|
||||
"Comment": "v0.0.0-18-g0c00cb0",
|
||||
"Rev": "0c00cb0a026b71034ebc8205263c77dad3577db5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud",
|
||||
"Comment": "v0.1.0-31-ge13cda2",
|
||||
"Rev": "e13cda260ce48d63ce816f4fa72b6c6cd096596d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/skratchdot/open-golang/open",
|
||||
"Rev": "ba570a111973b539baf23c918213059543b5bb6e"
|
||||
|
@ -105,6 +123,11 @@
|
|||
"ImportPath": "github.com/stretchr/testify/mock",
|
||||
"Rev": "37614ac27794505bf7867ca93aac883cadb6a5f7"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/tonnerre/golang-pretty",
|
||||
"Comment": "debian/0.0_git20130613-1-1-ge7fccc0",
|
||||
"Rev": "e7fccc03e91bad289b96c21aa3312a220689bdd7"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vaughan0/go-ini",
|
||||
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
Copyright 2012 Keith Rarick
|
||||
|
||||
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.
|
|
@ -0,0 +1,3 @@
|
|||
This is a Go package for manipulating paragraphs of text.
|
||||
|
||||
See http://go.pkgdoc.org/github.com/kr/text for full documentation.
|
|
@ -0,0 +1,5 @@
|
|||
Package colwriter provides a write filter that formats
|
||||
input lines in multiple columns.
|
||||
|
||||
The package is a straightforward translation from
|
||||
/src/cmd/draw/mc.c in Plan 9 from User Space.
|
|
@ -0,0 +1,147 @@
|
|||
// Package colwriter provides a write filter that formats
|
||||
// input lines in multiple columns.
|
||||
//
|
||||
// The package is a straightforward translation from
|
||||
// /src/cmd/draw/mc.c in Plan 9 from User Space.
|
||||
package colwriter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
tab = 4
|
||||
)
|
||||
|
||||
const (
|
||||
// Print each input line ending in a colon ':' separately.
|
||||
BreakOnColon uint = 1 << iota
|
||||
)
|
||||
|
||||
// A Writer is a filter that arranges input lines in as many columns as will
|
||||
// fit in its width. Tab '\t' chars in the input are translated to sequences
|
||||
// of spaces ending at multiples of 4 positions.
|
||||
//
|
||||
// If BreakOnColon is set, each input line ending in a colon ':' is written
|
||||
// separately.
|
||||
//
|
||||
// The Writer assumes that all Unicode code points have the same width; this
|
||||
// may not be true in some fonts.
|
||||
type Writer struct {
|
||||
w io.Writer
|
||||
buf []byte
|
||||
width int
|
||||
flag uint
|
||||
}
|
||||
|
||||
// NewWriter allocates and initializes a new Writer writing to w.
|
||||
// Parameter width controls the total number of characters on each line
|
||||
// across all columns.
|
||||
func NewWriter(w io.Writer, width int, flag uint) *Writer {
|
||||
return &Writer{
|
||||
w: w,
|
||||
width: width,
|
||||
flag: flag,
|
||||
}
|
||||
}
|
||||
|
||||
// Write writes p to the writer w. The only errors returned are ones
|
||||
// encountered while writing to the underlying output stream.
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
var linelen int
|
||||
var lastWasColon bool
|
||||
for i, c := range p {
|
||||
w.buf = append(w.buf, c)
|
||||
linelen++
|
||||
if c == '\t' {
|
||||
w.buf[len(w.buf)-1] = ' '
|
||||
for linelen%tab != 0 {
|
||||
w.buf = append(w.buf, ' ')
|
||||
linelen++
|
||||
}
|
||||
}
|
||||
if w.flag&BreakOnColon != 0 && c == ':' {
|
||||
lastWasColon = true
|
||||
} else if lastWasColon {
|
||||
if c == '\n' {
|
||||
pos := bytes.LastIndex(w.buf[:len(w.buf)-1], []byte{'\n'})
|
||||
if pos < 0 {
|
||||
pos = 0
|
||||
}
|
||||
line := w.buf[pos:]
|
||||
w.buf = w.buf[:pos]
|
||||
if err = w.columnate(); err != nil {
|
||||
if len(line) < i {
|
||||
return i - len(line), err
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
if n, err := w.w.Write(line); err != nil {
|
||||
if r := len(line) - n; r < i {
|
||||
return i - r, err
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
lastWasColon = false
|
||||
}
|
||||
if c == '\n' {
|
||||
linelen = 0
|
||||
}
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Flush should be called after the last call to Write to ensure that any data
|
||||
// buffered in the Writer is written to output.
|
||||
func (w *Writer) Flush() error {
|
||||
return w.columnate()
|
||||
}
|
||||
|
||||
func (w *Writer) columnate() error {
|
||||
words := bytes.Split(w.buf, []byte{'\n'})
|
||||
w.buf = nil
|
||||
if len(words[len(words)-1]) == 0 {
|
||||
words = words[:len(words)-1]
|
||||
}
|
||||
maxwidth := 0
|
||||
for _, wd := range words {
|
||||
if n := utf8.RuneCount(wd); n > maxwidth {
|
||||
maxwidth = n
|
||||
}
|
||||
}
|
||||
maxwidth++ // space char
|
||||
wordsPerLine := w.width / maxwidth
|
||||
if wordsPerLine <= 0 {
|
||||
wordsPerLine = 1
|
||||
}
|
||||
nlines := (len(words) + wordsPerLine - 1) / wordsPerLine
|
||||
for i := 0; i < nlines; i++ {
|
||||
col := 0
|
||||
endcol := 0
|
||||
for j := i; j < len(words); j += nlines {
|
||||
endcol += maxwidth
|
||||
_, err := w.w.Write(words[j])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
col += utf8.RuneCount(words[j])
|
||||
if j+nlines < len(words) {
|
||||
for col < endcol {
|
||||
_, err := w.w.Write([]byte{' '})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
col++
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err := w.w.Write([]byte{'\n'})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package colwriter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var src = `
|
||||
.git
|
||||
.gitignore
|
||||
.godir
|
||||
Procfile:
|
||||
README.md
|
||||
api.go
|
||||
apps.go
|
||||
auth.go
|
||||
darwin.go
|
||||
data.go
|
||||
dyno.go:
|
||||
env.go
|
||||
git.go
|
||||
help.go
|
||||
hkdist
|
||||
linux.go
|
||||
ls.go
|
||||
main.go
|
||||
plugin.go
|
||||
run.go
|
||||
scale.go
|
||||
ssh.go
|
||||
tail.go
|
||||
term
|
||||
unix.go
|
||||
update.go
|
||||
version.go
|
||||
windows.go
|
||||
`[1:]
|
||||
|
||||
var tests = []struct{
|
||||
wid int
|
||||
flag uint
|
||||
src string
|
||||
want string
|
||||
}{
|
||||
{80, 0, "", ""},
|
||||
{80, 0, src, `
|
||||
.git README.md darwin.go git.go ls.go scale.go unix.go
|
||||
.gitignore api.go data.go help.go main.go ssh.go update.go
|
||||
.godir apps.go dyno.go: hkdist plugin.go tail.go version.go
|
||||
Procfile: auth.go env.go linux.go run.go term windows.go
|
||||
`[1:]},
|
||||
{80, BreakOnColon, src, `
|
||||
.git .gitignore .godir
|
||||
|
||||
Procfile:
|
||||
README.md api.go apps.go auth.go darwin.go data.go
|
||||
|
||||
dyno.go:
|
||||
env.go hkdist main.go scale.go term version.go
|
||||
git.go linux.go plugin.go ssh.go unix.go windows.go
|
||||
help.go ls.go run.go tail.go update.go
|
||||
`[1:]},
|
||||
{20, 0, `
|
||||
Hello
|
||||
Γειά σου
|
||||
안녕
|
||||
今日は
|
||||
`[1:], `
|
||||
Hello 안녕
|
||||
Γειά σου 今日は
|
||||
`[1:]},
|
||||
}
|
||||
|
||||
func TestWriter(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
b := new(bytes.Buffer)
|
||||
w := NewWriter(b, test.wid, test.flag)
|
||||
if _, err := w.Write([]byte(test.src)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := w.Flush(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if g := b.String(); test.want != g {
|
||||
t.Log("\n" + test.want)
|
||||
t.Log("\n" + g)
|
||||
t.Errorf("%q != %q", test.want, g)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
// Package text provides rudimentary functions for manipulating text in
|
||||
// paragraphs.
|
||||
package text
|
|
@ -0,0 +1,74 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Indent inserts prefix at the beginning of each non-empty line of s. The
|
||||
// end-of-line marker is NL.
|
||||
func Indent(s, prefix string) string {
|
||||
return string(IndentBytes([]byte(s), []byte(prefix)))
|
||||
}
|
||||
|
||||
// IndentBytes inserts prefix at the beginning of each non-empty line of b.
|
||||
// The end-of-line marker is NL.
|
||||
func IndentBytes(b, prefix []byte) []byte {
|
||||
var res []byte
|
||||
bol := true
|
||||
for _, c := range b {
|
||||
if bol && c != '\n' {
|
||||
res = append(res, prefix...)
|
||||
}
|
||||
res = append(res, c)
|
||||
bol = c == '\n'
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Writer indents each line of its input.
|
||||
type indentWriter struct {
|
||||
w io.Writer
|
||||
bol bool
|
||||
pre [][]byte
|
||||
sel int
|
||||
off int
|
||||
}
|
||||
|
||||
// NewIndentWriter makes a new write filter that indents the input
|
||||
// lines. Each line is prefixed in order with the corresponding
|
||||
// element of pre. If there are more lines than elements, the last
|
||||
// element of pre is repeated for each subsequent line.
|
||||
func NewIndentWriter(w io.Writer, pre ...[]byte) io.Writer {
|
||||
return &indentWriter{
|
||||
w: w,
|
||||
pre: pre,
|
||||
bol: true,
|
||||
}
|
||||
}
|
||||
|
||||
// The only errors returned are from the underlying indentWriter.
|
||||
func (w *indentWriter) Write(p []byte) (n int, err error) {
|
||||
for _, c := range p {
|
||||
if w.bol {
|
||||
var i int
|
||||
i, err = w.w.Write(w.pre[w.sel][w.off:])
|
||||
w.off += i
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
_, err = w.w.Write([]byte{c})
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
w.bol = c == '\n'
|
||||
if w.bol {
|
||||
w.off = 0
|
||||
if w.sel < len(w.pre)-1 {
|
||||
w.sel++
|
||||
}
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type T struct {
|
||||
inp, exp, pre string
|
||||
}
|
||||
|
||||
var tests = []T{
|
||||
{
|
||||
"The quick brown fox\njumps over the lazy\ndog.\nBut not quickly.\n",
|
||||
"xxxThe quick brown fox\nxxxjumps over the lazy\nxxxdog.\nxxxBut not quickly.\n",
|
||||
"xxx",
|
||||
},
|
||||
{
|
||||
"The quick brown fox\njumps over the lazy\ndog.\n\nBut not quickly.",
|
||||
"xxxThe quick brown fox\nxxxjumps over the lazy\nxxxdog.\n\nxxxBut not quickly.",
|
||||
"xxx",
|
||||
},
|
||||
}
|
||||
|
||||
func TestIndent(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
got := Indent(test.inp, test.pre)
|
||||
if got != test.exp {
|
||||
t.Errorf("mismatch %q != %q", got, test.exp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type IndentWriterTest struct {
|
||||
inp, exp string
|
||||
pre []string
|
||||
}
|
||||
|
||||
var ts = []IndentWriterTest{
|
||||
{
|
||||
`
|
||||
The quick brown fox
|
||||
jumps over the lazy
|
||||
dog.
|
||||
But not quickly.
|
||||
`[1:],
|
||||
`
|
||||
xxxThe quick brown fox
|
||||
xxxjumps over the lazy
|
||||
xxxdog.
|
||||
xxxBut not quickly.
|
||||
`[1:],
|
||||
[]string{"xxx"},
|
||||
},
|
||||
{
|
||||
`
|
||||
The quick brown fox
|
||||
jumps over the lazy
|
||||
dog.
|
||||
But not quickly.
|
||||
`[1:],
|
||||
`
|
||||
xxaThe quick brown fox
|
||||
xxxjumps over the lazy
|
||||
xxxdog.
|
||||
xxxBut not quickly.
|
||||
`[1:],
|
||||
[]string{"xxa", "xxx"},
|
||||
},
|
||||
{
|
||||
`
|
||||
The quick brown fox
|
||||
jumps over the lazy
|
||||
dog.
|
||||
But not quickly.
|
||||
`[1:],
|
||||
`
|
||||
xxaThe quick brown fox
|
||||
xxbjumps over the lazy
|
||||
xxcdog.
|
||||
xxxBut not quickly.
|
||||
`[1:],
|
||||
[]string{"xxa", "xxb", "xxc", "xxx"},
|
||||
},
|
||||
{
|
||||
`
|
||||
The quick brown fox
|
||||
jumps over the lazy
|
||||
dog.
|
||||
|
||||
But not quickly.`[1:],
|
||||
`
|
||||
xxaThe quick brown fox
|
||||
xxxjumps over the lazy
|
||||
xxxdog.
|
||||
xxx
|
||||
xxxBut not quickly.`[1:],
|
||||
[]string{"xxa", "xxx"},
|
||||
},
|
||||
}
|
||||
|
||||
func TestIndentWriter(t *testing.T) {
|
||||
for _, test := range ts {
|
||||
b := new(bytes.Buffer)
|
||||
pre := make([][]byte, len(test.pre))
|
||||
for i := range test.pre {
|
||||
pre[i] = []byte(test.pre[i])
|
||||
}
|
||||
w := NewIndentWriter(b, pre...)
|
||||
if _, err := w.Write([]byte(test.inp)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if got := b.String(); got != test.exp {
|
||||
t.Errorf("mismatch %q != %q", got, test.exp)
|
||||
t.Log(got)
|
||||
t.Log(test.exp)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
Command mc prints in multiple columns.
|
||||
|
||||
Usage: mc [-] [-N] [file...]
|
||||
|
||||
Mc splits the input into as many columns as will fit in N
|
||||
print positions. If the output is a tty, the default N is
|
||||
the number of characters in a terminal line; otherwise the
|
||||
default N is 80. Under option - each input line ending in
|
||||
a colon ':' is printed separately.
|
|
@ -0,0 +1,62 @@
|
|||
// Command mc prints in multiple columns.
|
||||
//
|
||||
// Usage: mc [-] [-N] [file...]
|
||||
//
|
||||
// Mc splits the input into as many columns as will fit in N
|
||||
// print positions. If the output is a tty, the default N is
|
||||
// the number of characters in a terminal line; otherwise the
|
||||
// default N is 80. Under option - each input line ending in
|
||||
// a colon ':' is printed separately.
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kr/pty"
|
||||
"github.com/kr/text/colwriter"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var width int
|
||||
var flag uint
|
||||
args := os.Args[1:]
|
||||
for len(args) > 0 && len(args[0]) > 0 && args[0][0] == '-' {
|
||||
if len(args[0]) > 1 {
|
||||
width, _ = strconv.Atoi(args[0][1:])
|
||||
} else {
|
||||
flag |= colwriter.BreakOnColon
|
||||
}
|
||||
args = args[1:]
|
||||
}
|
||||
if width < 1 {
|
||||
_, width, _ = pty.Getsize(os.Stdout)
|
||||
}
|
||||
if width < 1 {
|
||||
width = 80
|
||||
}
|
||||
|
||||
w := colwriter.NewWriter(os.Stdout, width, flag)
|
||||
if len(args) > 0 {
|
||||
for _, s := range args {
|
||||
if f, err := os.Open(s); err == nil {
|
||||
copyin(w, f)
|
||||
f.Close()
|
||||
} else {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
copyin(w, os.Stdin)
|
||||
}
|
||||
}
|
||||
|
||||
func copyin(w *colwriter.Writer, r io.Reader) {
|
||||
if _, err := io.Copy(w, r); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if err := w.Flush(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
)
|
||||
|
||||
var (
|
||||
nl = []byte{'\n'}
|
||||
sp = []byte{' '}
|
||||
)
|
||||
|
||||
const defaultPenalty = 1e5
|
||||
|
||||
// Wrap wraps s into a paragraph of lines of length lim, with minimal
|
||||
// raggedness.
|
||||
func Wrap(s string, lim int) string {
|
||||
return string(WrapBytes([]byte(s), lim))
|
||||
}
|
||||
|
||||
// WrapBytes wraps b into a paragraph of lines of length lim, with minimal
|
||||
// raggedness.
|
||||
func WrapBytes(b []byte, lim int) []byte {
|
||||
words := bytes.Split(bytes.Replace(bytes.TrimSpace(b), nl, sp, -1), sp)
|
||||
var lines [][]byte
|
||||
for _, line := range WrapWords(words, 1, lim, defaultPenalty) {
|
||||
lines = append(lines, bytes.Join(line, sp))
|
||||
}
|
||||
return bytes.Join(lines, nl)
|
||||
}
|
||||
|
||||
// WrapWords is the low-level line-breaking algorithm, useful if you need more
|
||||
// control over the details of the text wrapping process. For most uses, either
|
||||
// Wrap or WrapBytes will be sufficient and more convenient.
|
||||
//
|
||||
// WrapWords splits a list of words into lines with minimal "raggedness",
|
||||
// treating each byte as one unit, accounting for spc units between adjacent
|
||||
// words on each line, and attempting to limit lines to lim units. Raggedness
|
||||
// is the total error over all lines, where error is the square of the
|
||||
// difference of the length of the line and lim. Too-long lines (which only
|
||||
// happen when a single word is longer than lim units) have pen penalty units
|
||||
// added to the error.
|
||||
func WrapWords(words [][]byte, spc, lim, pen int) [][][]byte {
|
||||
n := len(words)
|
||||
|
||||
length := make([][]int, n)
|
||||
for i := 0; i < n; i++ {
|
||||
length[i] = make([]int, n)
|
||||
length[i][i] = len(words[i])
|
||||
for j := i + 1; j < n; j++ {
|
||||
length[i][j] = length[i][j-1] + spc + len(words[j])
|
||||
}
|
||||
}
|
||||
|
||||
nbrk := make([]int, n)
|
||||
cost := make([]int, n)
|
||||
for i := range cost {
|
||||
cost[i] = math.MaxInt32
|
||||
}
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
if length[i][n-1] <= lim {
|
||||
cost[i] = 0
|
||||
nbrk[i] = n
|
||||
} else {
|
||||
for j := i + 1; j < n; j++ {
|
||||
d := lim - length[i][j-1]
|
||||
c := d*d + cost[j]
|
||||
if length[i][j-1] > lim {
|
||||
c += pen // too-long lines get a worse penalty
|
||||
}
|
||||
if c < cost[i] {
|
||||
cost[i] = c
|
||||
nbrk[i] = j
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var lines [][][]byte
|
||||
i := 0
|
||||
for i < n {
|
||||
lines = append(lines, words[i:nbrk[i]])
|
||||
i = nbrk[i]
|
||||
}
|
||||
return lines
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package text
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var text = "The quick brown fox jumps over the lazy dog."
|
||||
|
||||
func TestWrap(t *testing.T) {
|
||||
exp := [][]string{
|
||||
{"The", "quick", "brown", "fox"},
|
||||
{"jumps", "over", "the", "lazy", "dog."},
|
||||
}
|
||||
words := bytes.Split([]byte(text), sp)
|
||||
got := WrapWords(words, 1, 24, defaultPenalty)
|
||||
if len(exp) != len(got) {
|
||||
t.Fail()
|
||||
}
|
||||
for i := range exp {
|
||||
if len(exp[i]) != len(got[i]) {
|
||||
t.Fail()
|
||||
}
|
||||
for j := range exp[i] {
|
||||
if exp[i][j] != string(got[i][j]) {
|
||||
t.Fatal(i, exp[i][j], got[i][j])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapNarrow(t *testing.T) {
|
||||
exp := "The\nquick\nbrown\nfox\njumps\nover\nthe\nlazy\ndog."
|
||||
if Wrap(text, 5) != exp {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapOneLine(t *testing.T) {
|
||||
exp := "The quick brown fox jumps over the lazy dog."
|
||||
if Wrap(text, 500) != exp {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Mitchell Hashimoto
|
||||
|
||||
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.
|
|
@ -0,0 +1,46 @@
|
|||
# mapstructure
|
||||
|
||||
mapstructure is a Go library for decoding generic map values to structures
|
||||
and vice versa, while providing helpful error handling.
|
||||
|
||||
This library is most useful when decoding values from some data stream (JSON,
|
||||
Gob, etc.) where you don't _quite_ know the structure of the underlying data
|
||||
until you read a part of it. You can therefore read a `map[string]interface{}`
|
||||
and use this library to decode it into the proper underlying native Go
|
||||
structure.
|
||||
|
||||
## Installation
|
||||
|
||||
Standard `go get`:
|
||||
|
||||
```
|
||||
$ go get github.com/mitchellh/mapstructure
|
||||
```
|
||||
|
||||
## Usage & Example
|
||||
|
||||
For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/mapstructure).
|
||||
|
||||
The `Decode` function has examples associated with it there.
|
||||
|
||||
## But Why?!
|
||||
|
||||
Go offers fantastic standard libraries for decoding formats such as JSON.
|
||||
The standard method is to have a struct pre-created, and populate that struct
|
||||
from the bytes of the encoded format. This is great, but the problem is if
|
||||
you have configuration or an encoding that changes slightly depending on
|
||||
specific fields. For example, consider this JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "person",
|
||||
"name": "Mitchell"
|
||||
}
|
||||
```
|
||||
|
||||
Perhaps we can't populate a specific structure without first reading
|
||||
the "type" field from the JSON. We could always do two passes over the
|
||||
decoding of the JSON (reading the "type" first, and the rest later).
|
||||
However, it is much simpler to just decode this into a `map[string]interface{}`
|
||||
structure, read the "type" key, then use something like this library
|
||||
to decode it into the proper structure.
|
84
Godeps/_workspace/src/github.com/mitchellh/mapstructure/decode_hooks.go
generated
vendored
Normal file
84
Godeps/_workspace/src/github.com/mitchellh/mapstructure/decode_hooks.go
generated
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
package mapstructure
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ComposeDecodeHookFunc creates a single DecodeHookFunc that
|
||||
// automatically composes multiple DecodeHookFuncs.
|
||||
//
|
||||
// The composed funcs are called in order, with the result of the
|
||||
// previous transformation.
|
||||
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
var err error
|
||||
for _, f1 := range fs {
|
||||
data, err = f1(f, t, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Modify the from kind to be correct with the new data
|
||||
f = getKind(reflect.ValueOf(data))
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
// StringToSliceHookFunc returns a DecodeHookFunc that converts
|
||||
// string to []string by splitting on the given sep.
|
||||
func StringToSliceHookFunc(sep string) DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
if f != reflect.String || t != reflect.Slice {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
raw := data.(string)
|
||||
if raw == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
return strings.Split(raw, sep), nil
|
||||
}
|
||||
}
|
||||
|
||||
func WeaklyTypedHook(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
switch t {
|
||||
case reflect.String:
|
||||
switch f {
|
||||
case reflect.Bool:
|
||||
if dataVal.Bool() {
|
||||
return "1", nil
|
||||
} else {
|
||||
return "0", nil
|
||||
}
|
||||
case reflect.Float32:
|
||||
return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
|
||||
case reflect.Int:
|
||||
return strconv.FormatInt(dataVal.Int(), 10), nil
|
||||
case reflect.Slice:
|
||||
dataType := dataVal.Type()
|
||||
elemKind := dataType.Elem().Kind()
|
||||
if elemKind == reflect.Uint8 {
|
||||
return string(dataVal.Interface().([]uint8)), nil
|
||||
}
|
||||
case reflect.Uint:
|
||||
return strconv.FormatUint(dataVal.Uint(), 10), nil
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
191
Godeps/_workspace/src/github.com/mitchellh/mapstructure/decode_hooks_test.go
generated
vendored
Normal file
191
Godeps/_workspace/src/github.com/mitchellh/mapstructure/decode_hooks_test.go
generated
vendored
Normal file
|
@ -0,0 +1,191 @@
|
|||
package mapstructure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestComposeDecodeHookFunc(t *testing.T) {
|
||||
f1 := func(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
return data.(string) + "foo", nil
|
||||
}
|
||||
|
||||
f2 := func(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
return data.(string) + "bar", nil
|
||||
}
|
||||
|
||||
f := ComposeDecodeHookFunc(f1, f2)
|
||||
|
||||
result, err := f(reflect.String, reflect.Slice, "")
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
if result.(string) != "foobar" {
|
||||
t.Fatalf("bad: %#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposeDecodeHookFunc_err(t *testing.T) {
|
||||
f1 := func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) {
|
||||
return nil, errors.New("foo")
|
||||
}
|
||||
|
||||
f2 := func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) {
|
||||
panic("NOPE")
|
||||
}
|
||||
|
||||
f := ComposeDecodeHookFunc(f1, f2)
|
||||
|
||||
_, err := f(reflect.String, reflect.Slice, 42)
|
||||
if err.Error() != "foo" {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposeDecodeHookFunc_kinds(t *testing.T) {
|
||||
var f2From reflect.Kind
|
||||
|
||||
f1 := func(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
return int(42), nil
|
||||
}
|
||||
|
||||
f2 := func(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
f2From = f
|
||||
return data, nil
|
||||
}
|
||||
|
||||
f := ComposeDecodeHookFunc(f1, f2)
|
||||
|
||||
_, err := f(reflect.String, reflect.Slice, "")
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
if f2From != reflect.Int {
|
||||
t.Fatalf("bad: %#v", f2From)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringToSliceHookFunc(t *testing.T) {
|
||||
f := StringToSliceHookFunc(",")
|
||||
|
||||
cases := []struct {
|
||||
f, t reflect.Kind
|
||||
data interface{}
|
||||
result interface{}
|
||||
err bool
|
||||
}{
|
||||
{reflect.Slice, reflect.Slice, 42, 42, false},
|
||||
{reflect.String, reflect.String, 42, 42, false},
|
||||
{
|
||||
reflect.String,
|
||||
reflect.Slice,
|
||||
"foo,bar,baz",
|
||||
[]string{"foo", "bar", "baz"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
reflect.String,
|
||||
reflect.Slice,
|
||||
"",
|
||||
[]string{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
actual, err := f(tc.f, tc.t, tc.data)
|
||||
if tc.err != (err != nil) {
|
||||
t.Fatalf("case %d: expected err %#v", i, tc.err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, tc.result) {
|
||||
t.Fatalf(
|
||||
"case %d: expected %#v, got %#v",
|
||||
i, tc.result, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeaklyTypedHook(t *testing.T) {
|
||||
var f DecodeHookFunc = WeaklyTypedHook
|
||||
|
||||
cases := []struct {
|
||||
f, t reflect.Kind
|
||||
data interface{}
|
||||
result interface{}
|
||||
err bool
|
||||
}{
|
||||
// TO STRING
|
||||
{
|
||||
reflect.Bool,
|
||||
reflect.String,
|
||||
false,
|
||||
"0",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
reflect.Bool,
|
||||
reflect.String,
|
||||
true,
|
||||
"1",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
reflect.Float32,
|
||||
reflect.String,
|
||||
float32(7),
|
||||
"7",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
reflect.Int,
|
||||
reflect.String,
|
||||
int(7),
|
||||
"7",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
reflect.Slice,
|
||||
reflect.String,
|
||||
[]uint8("foo"),
|
||||
"foo",
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
reflect.Uint,
|
||||
reflect.String,
|
||||
uint(7),
|
||||
"7",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
actual, err := f(tc.f, tc.t, tc.data)
|
||||
if tc.err != (err != nil) {
|
||||
t.Fatalf("case %d: expected err %#v", i, tc.err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, tc.result) {
|
||||
t.Fatalf(
|
||||
"case %d: expected %#v, got %#v",
|
||||
i, tc.result, actual)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package mapstructure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Error implements the error interface and can represents multiple
|
||||
// errors that occur in the course of a single decode.
|
||||
type Error struct {
|
||||
Errors []string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
points := make([]string, len(e.Errors))
|
||||
for i, err := range e.Errors {
|
||||
points[i] = fmt.Sprintf("* %s", err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
"%d error(s) decoding:\n\n%s",
|
||||
len(e.Errors), strings.Join(points, "\n"))
|
||||
}
|
||||
|
||||
func appendErrors(errors []string, err error) []string {
|
||||
switch e := err.(type) {
|
||||
case *Error:
|
||||
return append(errors, e.Errors...)
|
||||
default:
|
||||
return append(errors, e.Error())
|
||||
}
|
||||
}
|
704
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure.go
generated
vendored
Normal file
704
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure.go
generated
vendored
Normal file
|
@ -0,0 +1,704 @@
|
|||
// The mapstructure package exposes functionality to convert an
|
||||
// abitrary map[string]interface{} into a native Go structure.
|
||||
//
|
||||
// The Go structure can be arbitrarily complex, containing slices,
|
||||
// other structs, etc. and the decoder will properly decode nested
|
||||
// maps and so on into the proper structures in the native Go struct.
|
||||
// See the examples to see what the decoder is capable of.
|
||||
package mapstructure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DecodeHookFunc is the callback function that can be used for
|
||||
// data transformations. See "DecodeHook" in the DecoderConfig
|
||||
// struct.
|
||||
type DecodeHookFunc func(
|
||||
from reflect.Kind,
|
||||
to reflect.Kind,
|
||||
data interface{}) (interface{}, error)
|
||||
|
||||
// DecoderConfig is the configuration that is used to create a new decoder
|
||||
// and allows customization of various aspects of decoding.
|
||||
type DecoderConfig struct {
|
||||
// DecodeHook, if set, will be called before any decoding and any
|
||||
// type conversion (if WeaklyTypedInput is on). This lets you modify
|
||||
// the values before they're set down onto the resulting struct.
|
||||
//
|
||||
// If an error is returned, the entire decode will fail with that
|
||||
// error.
|
||||
DecodeHook DecodeHookFunc
|
||||
|
||||
// If ErrorUnused is true, then it is an error for there to exist
|
||||
// keys in the original map that were unused in the decoding process
|
||||
// (extra keys).
|
||||
ErrorUnused bool
|
||||
|
||||
// If WeaklyTypedInput is true, the decoder will make the following
|
||||
// "weak" conversions:
|
||||
//
|
||||
// - bools to string (true = "1", false = "0")
|
||||
// - numbers to string (base 10)
|
||||
// - bools to int/uint (true = 1, false = 0)
|
||||
// - strings to int/uint (base implied by prefix)
|
||||
// - int to bool (true if value != 0)
|
||||
// - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F,
|
||||
// FALSE, false, False. Anything else is an error)
|
||||
// - empty array = empty map and vice versa
|
||||
//
|
||||
WeaklyTypedInput bool
|
||||
|
||||
// Metadata is the struct that will contain extra metadata about
|
||||
// the decoding. If this is nil, then no metadata will be tracked.
|
||||
Metadata *Metadata
|
||||
|
||||
// Result is a pointer to the struct that will contain the decoded
|
||||
// value.
|
||||
Result interface{}
|
||||
|
||||
// The tag name that mapstructure reads for field names. This
|
||||
// defaults to "mapstructure"
|
||||
TagName string
|
||||
}
|
||||
|
||||
// A Decoder takes a raw interface value and turns it into structured
|
||||
// data, keeping track of rich error information along the way in case
|
||||
// anything goes wrong. Unlike the basic top-level Decode method, you can
|
||||
// more finely control how the Decoder behaves using the DecoderConfig
|
||||
// structure. The top-level Decode method is just a convenience that sets
|
||||
// up the most basic Decoder.
|
||||
type Decoder struct {
|
||||
config *DecoderConfig
|
||||
}
|
||||
|
||||
// Metadata contains information about decoding a structure that
|
||||
// is tedious or difficult to get otherwise.
|
||||
type Metadata struct {
|
||||
// Keys are the keys of the structure which were successfully decoded
|
||||
Keys []string
|
||||
|
||||
// Unused is a slice of keys that were found in the raw value but
|
||||
// weren't decoded since there was no matching field in the result interface
|
||||
Unused []string
|
||||
}
|
||||
|
||||
// Decode takes a map and uses reflection to convert it into the
|
||||
// given Go native structure. val must be a pointer to a struct.
|
||||
func Decode(m interface{}, rawVal interface{}) error {
|
||||
config := &DecoderConfig{
|
||||
Metadata: nil,
|
||||
Result: rawVal,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return decoder.Decode(m)
|
||||
}
|
||||
|
||||
// WeakDecode is the same as Decode but is shorthand to enable
|
||||
// WeaklyTypedInput. See DecoderConfig for more info.
|
||||
func WeakDecode(input, output interface{}) error {
|
||||
config := &DecoderConfig{
|
||||
Metadata: nil,
|
||||
Result: output,
|
||||
WeaklyTypedInput: true,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return decoder.Decode(input)
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder for the given configuration. Once
|
||||
// a decoder has been returned, the same configuration must not be used
|
||||
// again.
|
||||
func NewDecoder(config *DecoderConfig) (*Decoder, error) {
|
||||
val := reflect.ValueOf(config.Result)
|
||||
if val.Kind() != reflect.Ptr {
|
||||
return nil, errors.New("result must be a pointer")
|
||||
}
|
||||
|
||||
val = val.Elem()
|
||||
if !val.CanAddr() {
|
||||
return nil, errors.New("result must be addressable (a pointer)")
|
||||
}
|
||||
|
||||
if config.Metadata != nil {
|
||||
if config.Metadata.Keys == nil {
|
||||
config.Metadata.Keys = make([]string, 0)
|
||||
}
|
||||
|
||||
if config.Metadata.Unused == nil {
|
||||
config.Metadata.Unused = make([]string, 0)
|
||||
}
|
||||
}
|
||||
|
||||
if config.TagName == "" {
|
||||
config.TagName = "mapstructure"
|
||||
}
|
||||
|
||||
result := &Decoder{
|
||||
config: config,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Decode decodes the given raw interface to the target pointer specified
|
||||
// by the configuration.
|
||||
func (d *Decoder) Decode(raw interface{}) error {
|
||||
return d.decode("", raw, reflect.ValueOf(d.config.Result).Elem())
|
||||
}
|
||||
|
||||
// Decodes an unknown data type into a specific reflection value.
|
||||
func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error {
|
||||
if data == nil {
|
||||
// If the data is nil, then we don't set anything.
|
||||
return nil
|
||||
}
|
||||
|
||||
dataVal := reflect.ValueOf(data)
|
||||
if !dataVal.IsValid() {
|
||||
// If the data value is invalid, then we just set the value
|
||||
// to be the zero value.
|
||||
val.Set(reflect.Zero(val.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
if d.config.DecodeHook != nil {
|
||||
// We have a DecodeHook, so let's pre-process the data.
|
||||
var err error
|
||||
data, err = d.config.DecodeHook(getKind(dataVal), getKind(val), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
dataKind := getKind(val)
|
||||
switch dataKind {
|
||||
case reflect.Bool:
|
||||
err = d.decodeBool(name, data, val)
|
||||
case reflect.Interface:
|
||||
err = d.decodeBasic(name, data, val)
|
||||
case reflect.String:
|
||||
err = d.decodeString(name, data, val)
|
||||
case reflect.Int:
|
||||
err = d.decodeInt(name, data, val)
|
||||
case reflect.Uint:
|
||||
err = d.decodeUint(name, data, val)
|
||||
case reflect.Float32:
|
||||
err = d.decodeFloat(name, data, val)
|
||||
case reflect.Struct:
|
||||
err = d.decodeStruct(name, data, val)
|
||||
case reflect.Map:
|
||||
err = d.decodeMap(name, data, val)
|
||||
case reflect.Ptr:
|
||||
err = d.decodePtr(name, data, val)
|
||||
case reflect.Slice:
|
||||
err = d.decodeSlice(name, data, val)
|
||||
default:
|
||||
// If we reached this point then we weren't able to decode it
|
||||
return fmt.Errorf("%s: unsupported type: %s", name, dataKind)
|
||||
}
|
||||
|
||||
// If we reached here, then we successfully decoded SOMETHING, so
|
||||
// mark the key as used if we're tracking metadata.
|
||||
if d.config.Metadata != nil && name != "" {
|
||||
d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// This decodes a basic type (bool, int, string, etc.) and sets the
|
||||
// value to "data" of that type.
|
||||
func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
dataValType := dataVal.Type()
|
||||
if !dataValType.AssignableTo(val.Type()) {
|
||||
return fmt.Errorf(
|
||||
"'%s' expected type '%s', got '%s'",
|
||||
name, val.Type(), dataValType)
|
||||
}
|
||||
|
||||
val.Set(dataVal)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
dataKind := getKind(dataVal)
|
||||
|
||||
converted := true
|
||||
switch {
|
||||
case dataKind == reflect.String:
|
||||
val.SetString(dataVal.String())
|
||||
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
|
||||
if dataVal.Bool() {
|
||||
val.SetString("1")
|
||||
} else {
|
||||
val.SetString("0")
|
||||
}
|
||||
case dataKind == reflect.Int && d.config.WeaklyTypedInput:
|
||||
val.SetString(strconv.FormatInt(dataVal.Int(), 10))
|
||||
case dataKind == reflect.Uint && d.config.WeaklyTypedInput:
|
||||
val.SetString(strconv.FormatUint(dataVal.Uint(), 10))
|
||||
case dataKind == reflect.Float32 && d.config.WeaklyTypedInput:
|
||||
val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64))
|
||||
case dataKind == reflect.Slice && d.config.WeaklyTypedInput:
|
||||
dataType := dataVal.Type()
|
||||
elemKind := dataType.Elem().Kind()
|
||||
switch {
|
||||
case elemKind == reflect.Uint8:
|
||||
val.SetString(string(dataVal.Interface().([]uint8)))
|
||||
default:
|
||||
converted = false
|
||||
}
|
||||
default:
|
||||
converted = false
|
||||
}
|
||||
|
||||
if !converted {
|
||||
return fmt.Errorf(
|
||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||
name, val.Type(), dataVal.Type())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
dataKind := getKind(dataVal)
|
||||
|
||||
switch {
|
||||
case dataKind == reflect.Int:
|
||||
val.SetInt(dataVal.Int())
|
||||
case dataKind == reflect.Uint:
|
||||
val.SetInt(int64(dataVal.Uint()))
|
||||
case dataKind == reflect.Float32:
|
||||
val.SetInt(int64(dataVal.Float()))
|
||||
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
|
||||
if dataVal.Bool() {
|
||||
val.SetInt(1)
|
||||
} else {
|
||||
val.SetInt(0)
|
||||
}
|
||||
case dataKind == reflect.String && d.config.WeaklyTypedInput:
|
||||
i, err := strconv.ParseInt(dataVal.String(), 0, val.Type().Bits())
|
||||
if err == nil {
|
||||
val.SetInt(i)
|
||||
} else {
|
||||
return fmt.Errorf("cannot parse '%s' as int: %s", name, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||
name, val.Type(), dataVal.Type())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
dataKind := getKind(dataVal)
|
||||
|
||||
switch {
|
||||
case dataKind == reflect.Int:
|
||||
val.SetUint(uint64(dataVal.Int()))
|
||||
case dataKind == reflect.Uint:
|
||||
val.SetUint(dataVal.Uint())
|
||||
case dataKind == reflect.Float32:
|
||||
val.SetUint(uint64(dataVal.Float()))
|
||||
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
|
||||
if dataVal.Bool() {
|
||||
val.SetUint(1)
|
||||
} else {
|
||||
val.SetUint(0)
|
||||
}
|
||||
case dataKind == reflect.String && d.config.WeaklyTypedInput:
|
||||
i, err := strconv.ParseUint(dataVal.String(), 0, val.Type().Bits())
|
||||
if err == nil {
|
||||
val.SetUint(i)
|
||||
} else {
|
||||
return fmt.Errorf("cannot parse '%s' as uint: %s", name, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||
name, val.Type(), dataVal.Type())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
dataKind := getKind(dataVal)
|
||||
|
||||
switch {
|
||||
case dataKind == reflect.Bool:
|
||||
val.SetBool(dataVal.Bool())
|
||||
case dataKind == reflect.Int && d.config.WeaklyTypedInput:
|
||||
val.SetBool(dataVal.Int() != 0)
|
||||
case dataKind == reflect.Uint && d.config.WeaklyTypedInput:
|
||||
val.SetBool(dataVal.Uint() != 0)
|
||||
case dataKind == reflect.Float32 && d.config.WeaklyTypedInput:
|
||||
val.SetBool(dataVal.Float() != 0)
|
||||
case dataKind == reflect.String && d.config.WeaklyTypedInput:
|
||||
b, err := strconv.ParseBool(dataVal.String())
|
||||
if err == nil {
|
||||
val.SetBool(b)
|
||||
} else if dataVal.String() == "" {
|
||||
val.SetBool(false)
|
||||
} else {
|
||||
return fmt.Errorf("cannot parse '%s' as bool: %s", name, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||
name, val.Type(), dataVal.Type())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
dataKind := getKind(dataVal)
|
||||
|
||||
switch {
|
||||
case dataKind == reflect.Int:
|
||||
val.SetFloat(float64(dataVal.Int()))
|
||||
case dataKind == reflect.Uint:
|
||||
val.SetFloat(float64(dataVal.Uint()))
|
||||
case dataKind == reflect.Float32:
|
||||
val.SetFloat(float64(dataVal.Float()))
|
||||
case dataKind == reflect.Bool && d.config.WeaklyTypedInput:
|
||||
if dataVal.Bool() {
|
||||
val.SetFloat(1)
|
||||
} else {
|
||||
val.SetFloat(0)
|
||||
}
|
||||
case dataKind == reflect.String && d.config.WeaklyTypedInput:
|
||||
f, err := strconv.ParseFloat(dataVal.String(), val.Type().Bits())
|
||||
if err == nil {
|
||||
val.SetFloat(f)
|
||||
} else {
|
||||
return fmt.Errorf("cannot parse '%s' as float: %s", name, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"'%s' expected type '%s', got unconvertible type '%s'",
|
||||
name, val.Type(), dataVal.Type())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error {
|
||||
valType := val.Type()
|
||||
valKeyType := valType.Key()
|
||||
valElemType := valType.Elem()
|
||||
|
||||
// Make a new map to hold our result
|
||||
mapType := reflect.MapOf(valKeyType, valElemType)
|
||||
valMap := reflect.MakeMap(mapType)
|
||||
|
||||
// Check input type
|
||||
dataVal := reflect.Indirect(reflect.ValueOf(data))
|
||||
if dataVal.Kind() != reflect.Map {
|
||||
// Accept empty array/slice instead of an empty map in weakly typed mode
|
||||
if d.config.WeaklyTypedInput &&
|
||||
(dataVal.Kind() == reflect.Slice || dataVal.Kind() == reflect.Array) &&
|
||||
dataVal.Len() == 0 {
|
||||
val.Set(valMap)
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
|
||||
}
|
||||
}
|
||||
|
||||
// Accumulate errors
|
||||
errors := make([]string, 0)
|
||||
|
||||
for _, k := range dataVal.MapKeys() {
|
||||
fieldName := fmt.Sprintf("%s[%s]", name, k)
|
||||
|
||||
// First decode the key into the proper type
|
||||
currentKey := reflect.Indirect(reflect.New(valKeyType))
|
||||
if err := d.decode(fieldName, k.Interface(), currentKey); err != nil {
|
||||
errors = appendErrors(errors, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Next decode the data into the proper type
|
||||
v := dataVal.MapIndex(k).Interface()
|
||||
currentVal := reflect.Indirect(reflect.New(valElemType))
|
||||
if err := d.decode(fieldName, v, currentVal); err != nil {
|
||||
errors = appendErrors(errors, err)
|
||||
continue
|
||||
}
|
||||
|
||||
valMap.SetMapIndex(currentKey, currentVal)
|
||||
}
|
||||
|
||||
// Set the built up map to the value
|
||||
val.Set(valMap)
|
||||
|
||||
// If we had errors, return those
|
||||
if len(errors) > 0 {
|
||||
return &Error{errors}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error {
|
||||
// Create an element of the concrete (non pointer) type and decode
|
||||
// into that. Then set the value of the pointer to this type.
|
||||
valType := val.Type()
|
||||
valElemType := valType.Elem()
|
||||
realVal := reflect.New(valElemType)
|
||||
if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val.Set(realVal)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.Indirect(reflect.ValueOf(data))
|
||||
dataValKind := dataVal.Kind()
|
||||
valType := val.Type()
|
||||
valElemType := valType.Elem()
|
||||
sliceType := reflect.SliceOf(valElemType)
|
||||
|
||||
// Check input type
|
||||
if dataValKind != reflect.Array && dataValKind != reflect.Slice {
|
||||
// Accept empty map instead of array/slice in weakly typed mode
|
||||
if d.config.WeaklyTypedInput && dataVal.Kind() == reflect.Map && dataVal.Len() == 0 {
|
||||
val.Set(reflect.MakeSlice(sliceType, 0, 0))
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf(
|
||||
"'%s': source data must be an array or slice, got %s", name, dataValKind)
|
||||
}
|
||||
}
|
||||
|
||||
// Make a new slice to hold our result, same size as the original data.
|
||||
valSlice := reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len())
|
||||
|
||||
// Accumulate any errors
|
||||
errors := make([]string, 0)
|
||||
|
||||
for i := 0; i < dataVal.Len(); i++ {
|
||||
currentData := dataVal.Index(i).Interface()
|
||||
currentField := valSlice.Index(i)
|
||||
|
||||
fieldName := fmt.Sprintf("%s[%d]", name, i)
|
||||
if err := d.decode(fieldName, currentData, currentField); err != nil {
|
||||
errors = appendErrors(errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, set the value to the slice we built up
|
||||
val.Set(valSlice)
|
||||
|
||||
// If there were errors, we return those
|
||||
if len(errors) > 0 {
|
||||
return &Error{errors}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error {
|
||||
dataVal := reflect.Indirect(reflect.ValueOf(data))
|
||||
dataValKind := dataVal.Kind()
|
||||
if dataValKind != reflect.Map {
|
||||
return fmt.Errorf("'%s' expected a map, got '%s'", name, dataValKind)
|
||||
}
|
||||
|
||||
dataValType := dataVal.Type()
|
||||
if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface {
|
||||
return fmt.Errorf(
|
||||
"'%s' needs a map with string keys, has '%s' keys",
|
||||
name, dataValType.Key().Kind())
|
||||
}
|
||||
|
||||
dataValKeys := make(map[reflect.Value]struct{})
|
||||
dataValKeysUnused := make(map[interface{}]struct{})
|
||||
for _, dataValKey := range dataVal.MapKeys() {
|
||||
dataValKeys[dataValKey] = struct{}{}
|
||||
dataValKeysUnused[dataValKey.Interface()] = struct{}{}
|
||||
}
|
||||
|
||||
errors := make([]string, 0)
|
||||
|
||||
// This slice will keep track of all the structs we'll be decoding.
|
||||
// There can be more than one struct if there are embedded structs
|
||||
// that are squashed.
|
||||
structs := make([]reflect.Value, 1, 5)
|
||||
structs[0] = val
|
||||
|
||||
// Compile the list of all the fields that we're going to be decoding
|
||||
// from all the structs.
|
||||
fields := make(map[*reflect.StructField]reflect.Value)
|
||||
for len(structs) > 0 {
|
||||
structVal := structs[0]
|
||||
structs = structs[1:]
|
||||
|
||||
structType := structVal.Type()
|
||||
for i := 0; i < structType.NumField(); i++ {
|
||||
fieldType := structType.Field(i)
|
||||
|
||||
if fieldType.Anonymous {
|
||||
fieldKind := fieldType.Type.Kind()
|
||||
if fieldKind != reflect.Struct {
|
||||
errors = appendErrors(errors,
|
||||
fmt.Errorf("%s: unsupported type: %s", fieldType.Name, fieldKind))
|
||||
continue
|
||||
}
|
||||
|
||||
// We have an embedded field. We "squash" the fields down
|
||||
// if specified in the tag.
|
||||
squash := false
|
||||
tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",")
|
||||
for _, tag := range tagParts[1:] {
|
||||
if tag == "squash" {
|
||||
squash = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if squash {
|
||||
structs = append(structs, val.FieldByName(fieldType.Name))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Normal struct field, store it away
|
||||
fields[&fieldType] = structVal.Field(i)
|
||||
}
|
||||
}
|
||||
|
||||
for fieldType, field := range fields {
|
||||
fieldName := fieldType.Name
|
||||
|
||||
tagValue := fieldType.Tag.Get(d.config.TagName)
|
||||
tagValue = strings.SplitN(tagValue, ",", 2)[0]
|
||||
if tagValue != "" {
|
||||
fieldName = tagValue
|
||||
}
|
||||
|
||||
rawMapKey := reflect.ValueOf(fieldName)
|
||||
rawMapVal := dataVal.MapIndex(rawMapKey)
|
||||
if !rawMapVal.IsValid() {
|
||||
// Do a slower search by iterating over each key and
|
||||
// doing case-insensitive search.
|
||||
for dataValKey, _ := range dataValKeys {
|
||||
mK, ok := dataValKey.Interface().(string)
|
||||
if !ok {
|
||||
// Not a string key
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.EqualFold(mK, fieldName) {
|
||||
rawMapKey = dataValKey
|
||||
rawMapVal = dataVal.MapIndex(dataValKey)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !rawMapVal.IsValid() {
|
||||
// There was no matching key in the map for the value in
|
||||
// the struct. Just ignore.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the key we're using from the unused map so we stop tracking
|
||||
delete(dataValKeysUnused, rawMapKey.Interface())
|
||||
|
||||
if !field.IsValid() {
|
||||
// This should never happen
|
||||
panic("field is not valid")
|
||||
}
|
||||
|
||||
// If we can't set the field, then it is unexported or something,
|
||||
// and we just continue onwards.
|
||||
if !field.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the name is empty string, then we're at the root, and we
|
||||
// don't dot-join the fields.
|
||||
if name != "" {
|
||||
fieldName = fmt.Sprintf("%s.%s", name, fieldName)
|
||||
}
|
||||
|
||||
if err := d.decode(fieldName, rawMapVal.Interface(), field); err != nil {
|
||||
errors = appendErrors(errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
if d.config.ErrorUnused && len(dataValKeysUnused) > 0 {
|
||||
keys := make([]string, 0, len(dataValKeysUnused))
|
||||
for rawKey, _ := range dataValKeysUnused {
|
||||
keys = append(keys, rawKey.(string))
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
err := fmt.Errorf("'%s' has invalid keys: %s", name, strings.Join(keys, ", "))
|
||||
errors = appendErrors(errors, err)
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return &Error{errors}
|
||||
}
|
||||
|
||||
// Add the unused keys to the list of unused keys if we're tracking metadata
|
||||
if d.config.Metadata != nil {
|
||||
for rawKey, _ := range dataValKeysUnused {
|
||||
key := rawKey.(string)
|
||||
if name != "" {
|
||||
key = fmt.Sprintf("%s.%s", name, key)
|
||||
}
|
||||
|
||||
d.config.Metadata.Unused = append(d.config.Metadata.Unused, key)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getKind(val reflect.Value) reflect.Kind {
|
||||
kind := val.Kind()
|
||||
|
||||
switch {
|
||||
case kind >= reflect.Int && kind <= reflect.Int64:
|
||||
return reflect.Int
|
||||
case kind >= reflect.Uint && kind <= reflect.Uint64:
|
||||
return reflect.Uint
|
||||
case kind >= reflect.Float32 && kind <= reflect.Float64:
|
||||
return reflect.Float32
|
||||
default:
|
||||
return kind
|
||||
}
|
||||
}
|
243
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_benchmark_test.go
generated
vendored
Normal file
243
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_benchmark_test.go
generated
vendored
Normal file
|
@ -0,0 +1,243 @@
|
|||
package mapstructure
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Benchmark_Decode(b *testing.B) {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string
|
||||
Extra map[string]string
|
||||
}
|
||||
|
||||
input := map[string]interface{}{
|
||||
"name": "Mitchell",
|
||||
"age": 91,
|
||||
"emails": []string{"one", "two", "three"},
|
||||
"extra": map[string]string{
|
||||
"twitter": "mitchellh",
|
||||
},
|
||||
}
|
||||
|
||||
var result Person
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeBasic(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"vstring": "foo",
|
||||
"vint": 42,
|
||||
"Vuint": 42,
|
||||
"vbool": true,
|
||||
"Vfloat": 42.42,
|
||||
"vsilent": true,
|
||||
"vdata": 42,
|
||||
}
|
||||
|
||||
var result Basic
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeEmbedded(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"vstring": "foo",
|
||||
"Basic": map[string]interface{}{
|
||||
"vstring": "innerfoo",
|
||||
},
|
||||
"vunique": "bar",
|
||||
}
|
||||
|
||||
var result Embedded
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeTypeConversion(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"IntToFloat": 42,
|
||||
"IntToUint": 42,
|
||||
"IntToBool": 1,
|
||||
"IntToString": 42,
|
||||
"UintToInt": 42,
|
||||
"UintToFloat": 42,
|
||||
"UintToBool": 42,
|
||||
"UintToString": 42,
|
||||
"BoolToInt": true,
|
||||
"BoolToUint": true,
|
||||
"BoolToFloat": true,
|
||||
"BoolToString": true,
|
||||
"FloatToInt": 42.42,
|
||||
"FloatToUint": 42.42,
|
||||
"FloatToBool": 42.42,
|
||||
"FloatToString": 42.42,
|
||||
"StringToInt": "42",
|
||||
"StringToUint": "42",
|
||||
"StringToBool": "1",
|
||||
"StringToFloat": "42.42",
|
||||
"SliceToMap": []interface{}{},
|
||||
"MapToSlice": map[string]interface{}{},
|
||||
}
|
||||
|
||||
var resultStrict TypeConversionResult
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &resultStrict)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeMap(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"vfoo": "foo",
|
||||
"vother": map[interface{}]interface{}{
|
||||
"foo": "foo",
|
||||
"bar": "bar",
|
||||
},
|
||||
}
|
||||
|
||||
var result Map
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeMapOfStruct(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"value": map[string]interface{}{
|
||||
"foo": map[string]string{"vstring": "one"},
|
||||
"bar": map[string]string{"vstring": "two"},
|
||||
},
|
||||
}
|
||||
|
||||
var result MapOfStruct
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeSlice(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"vfoo": "foo",
|
||||
"vbar": []string{"foo", "bar", "baz"},
|
||||
}
|
||||
|
||||
var result Slice
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeSliceOfStruct(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"value": []map[string]interface{}{
|
||||
{"vstring": "one"},
|
||||
{"vstring": "two"},
|
||||
},
|
||||
}
|
||||
|
||||
var result SliceOfStruct
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeWeaklyTypedInput(b *testing.B) {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string
|
||||
}
|
||||
|
||||
// This input can come from anywhere, but typically comes from
|
||||
// something like decoding JSON, generated by a weakly typed language
|
||||
// such as PHP.
|
||||
input := map[string]interface{}{
|
||||
"name": 123, // number => string
|
||||
"age": "42", // string => number
|
||||
"emails": map[string]interface{}{}, // empty map => empty array
|
||||
}
|
||||
|
||||
var result Person
|
||||
config := &DecoderConfig{
|
||||
WeaklyTypedInput: true,
|
||||
Result: &result,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
decoder.Decode(input)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeMetadata(b *testing.B) {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
input := map[string]interface{}{
|
||||
"name": "Mitchell",
|
||||
"age": 91,
|
||||
"email": "foo@bar.com",
|
||||
}
|
||||
|
||||
var md Metadata
|
||||
var result Person
|
||||
config := &DecoderConfig{
|
||||
Metadata: &md,
|
||||
Result: &result,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
decoder.Decode(input)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeMetadataEmbedded(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"vstring": "foo",
|
||||
"vunique": "bar",
|
||||
}
|
||||
|
||||
var md Metadata
|
||||
var result EmbeddedSquash
|
||||
config := &DecoderConfig{
|
||||
Metadata: &md,
|
||||
Result: &result,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
b.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
decoder.Decode(input)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DecodeTagged(b *testing.B) {
|
||||
input := map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"bar": "value",
|
||||
}
|
||||
|
||||
var result Tagged
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(input, &result)
|
||||
}
|
||||
}
|
47
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_bugs_test.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_bugs_test.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
package mapstructure
|
||||
|
||||
import "testing"
|
||||
|
||||
// GH-1
|
||||
func TestDecode_NilValue(t *testing.T) {
|
||||
input := map[string]interface{}{
|
||||
"vfoo": nil,
|
||||
"vother": nil,
|
||||
}
|
||||
|
||||
var result Map
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("should not error: %s", err)
|
||||
}
|
||||
|
||||
if result.Vfoo != "" {
|
||||
t.Fatalf("value should be default: %s", result.Vfoo)
|
||||
}
|
||||
|
||||
if result.Vother != nil {
|
||||
t.Fatalf("Vother should be nil: %s", result.Vother)
|
||||
}
|
||||
}
|
||||
|
||||
// GH-10
|
||||
func TestDecode_mapInterfaceInterface(t *testing.T) {
|
||||
input := map[interface{}]interface{}{
|
||||
"vfoo": nil,
|
||||
"vother": nil,
|
||||
}
|
||||
|
||||
var result Map
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("should not error: %s", err)
|
||||
}
|
||||
|
||||
if result.Vfoo != "" {
|
||||
t.Fatalf("value should be default: %s", result.Vfoo)
|
||||
}
|
||||
|
||||
if result.Vother != nil {
|
||||
t.Fatalf("Vother should be nil: %s", result.Vother)
|
||||
}
|
||||
}
|
169
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_examples_test.go
generated
vendored
Normal file
169
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_examples_test.go
generated
vendored
Normal file
|
@ -0,0 +1,169 @@
|
|||
package mapstructure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func ExampleDecode() {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string
|
||||
Extra map[string]string
|
||||
}
|
||||
|
||||
// This input can come from anywhere, but typically comes from
|
||||
// something like decoding JSON where we're not quite sure of the
|
||||
// struct initially.
|
||||
input := map[string]interface{}{
|
||||
"name": "Mitchell",
|
||||
"age": 91,
|
||||
"emails": []string{"one", "two", "three"},
|
||||
"extra": map[string]string{
|
||||
"twitter": "mitchellh",
|
||||
},
|
||||
}
|
||||
|
||||
var result Person
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%#v", result)
|
||||
// Output:
|
||||
// mapstructure.Person{Name:"Mitchell", Age:91, Emails:[]string{"one", "two", "three"}, Extra:map[string]string{"twitter":"mitchellh"}}
|
||||
}
|
||||
|
||||
func ExampleDecode_errors() {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string
|
||||
Extra map[string]string
|
||||
}
|
||||
|
||||
// This input can come from anywhere, but typically comes from
|
||||
// something like decoding JSON where we're not quite sure of the
|
||||
// struct initially.
|
||||
input := map[string]interface{}{
|
||||
"name": 123,
|
||||
"age": "bad value",
|
||||
"emails": []int{1, 2, 3},
|
||||
}
|
||||
|
||||
var result Person
|
||||
err := Decode(input, &result)
|
||||
if err == nil {
|
||||
panic("should have an error")
|
||||
}
|
||||
|
||||
fmt.Println(err.Error())
|
||||
// Output:
|
||||
// 5 error(s) decoding:
|
||||
//
|
||||
// * 'Name' expected type 'string', got unconvertible type 'int'
|
||||
// * 'Age' expected type 'int', got unconvertible type 'string'
|
||||
// * 'Emails[0]' expected type 'string', got unconvertible type 'int'
|
||||
// * 'Emails[1]' expected type 'string', got unconvertible type 'int'
|
||||
// * 'Emails[2]' expected type 'string', got unconvertible type 'int'
|
||||
}
|
||||
|
||||
func ExampleDecode_metadata() {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
// This input can come from anywhere, but typically comes from
|
||||
// something like decoding JSON where we're not quite sure of the
|
||||
// struct initially.
|
||||
input := map[string]interface{}{
|
||||
"name": "Mitchell",
|
||||
"age": 91,
|
||||
"email": "foo@bar.com",
|
||||
}
|
||||
|
||||
// For metadata, we make a more advanced DecoderConfig so we can
|
||||
// more finely configure the decoder that is used. In this case, we
|
||||
// just tell the decoder we want to track metadata.
|
||||
var md Metadata
|
||||
var result Person
|
||||
config := &DecoderConfig{
|
||||
Metadata: &md,
|
||||
Result: &result,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := decoder.Decode(input); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Unused keys: %#v", md.Unused)
|
||||
// Output:
|
||||
// Unused keys: []string{"email"}
|
||||
}
|
||||
|
||||
func ExampleDecode_weaklyTypedInput() {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
Emails []string
|
||||
}
|
||||
|
||||
// This input can come from anywhere, but typically comes from
|
||||
// something like decoding JSON, generated by a weakly typed language
|
||||
// such as PHP.
|
||||
input := map[string]interface{}{
|
||||
"name": 123, // number => string
|
||||
"age": "42", // string => number
|
||||
"emails": map[string]interface{}{}, // empty map => empty array
|
||||
}
|
||||
|
||||
var result Person
|
||||
config := &DecoderConfig{
|
||||
WeaklyTypedInput: true,
|
||||
Result: &result,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = decoder.Decode(input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%#v", result)
|
||||
// Output: mapstructure.Person{Name:"123", Age:42, Emails:[]string{}}
|
||||
}
|
||||
|
||||
func ExampleDecode_tags() {
|
||||
// Note that the mapstructure tags defined in the struct type
|
||||
// can indicate which fields the values are mapped to.
|
||||
type Person struct {
|
||||
Name string `mapstructure:"person_name"`
|
||||
Age int `mapstructure:"person_age"`
|
||||
}
|
||||
|
||||
input := map[string]interface{}{
|
||||
"person_name": "Mitchell",
|
||||
"person_age": 91,
|
||||
}
|
||||
|
||||
var result Person
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%#v", result)
|
||||
// Output:
|
||||
// mapstructure.Person{Name:"Mitchell", Age:91}
|
||||
}
|
828
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_test.go
generated
vendored
Normal file
828
Godeps/_workspace/src/github.com/mitchellh/mapstructure/mapstructure_test.go
generated
vendored
Normal file
|
@ -0,0 +1,828 @@
|
|||
package mapstructure
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Basic struct {
|
||||
Vstring string
|
||||
Vint int
|
||||
Vuint uint
|
||||
Vbool bool
|
||||
Vfloat float64
|
||||
Vextra string
|
||||
vsilent bool
|
||||
Vdata interface{}
|
||||
}
|
||||
|
||||
type Embedded struct {
|
||||
Basic
|
||||
Vunique string
|
||||
}
|
||||
|
||||
type EmbeddedPointer struct {
|
||||
*Basic
|
||||
Vunique string
|
||||
}
|
||||
|
||||
type EmbeddedSquash struct {
|
||||
Basic `mapstructure:",squash"`
|
||||
Vunique string
|
||||
}
|
||||
|
||||
type Map struct {
|
||||
Vfoo string
|
||||
Vother map[string]string
|
||||
}
|
||||
|
||||
type MapOfStruct struct {
|
||||
Value map[string]Basic
|
||||
}
|
||||
|
||||
type Nested struct {
|
||||
Vfoo string
|
||||
Vbar Basic
|
||||
}
|
||||
|
||||
type NestedPointer struct {
|
||||
Vfoo string
|
||||
Vbar *Basic
|
||||
}
|
||||
|
||||
type Slice struct {
|
||||
Vfoo string
|
||||
Vbar []string
|
||||
}
|
||||
|
||||
type SliceOfStruct struct {
|
||||
Value []Basic
|
||||
}
|
||||
|
||||
type Tagged struct {
|
||||
Extra string `mapstructure:"bar,what,what"`
|
||||
Value string `mapstructure:"foo"`
|
||||
}
|
||||
|
||||
type TypeConversionResult struct {
|
||||
IntToFloat float32
|
||||
IntToUint uint
|
||||
IntToBool bool
|
||||
IntToString string
|
||||
UintToInt int
|
||||
UintToFloat float32
|
||||
UintToBool bool
|
||||
UintToString string
|
||||
BoolToInt int
|
||||
BoolToUint uint
|
||||
BoolToFloat float32
|
||||
BoolToString string
|
||||
FloatToInt int
|
||||
FloatToUint uint
|
||||
FloatToBool bool
|
||||
FloatToString string
|
||||
SliceUint8ToString string
|
||||
StringToInt int
|
||||
StringToUint uint
|
||||
StringToBool bool
|
||||
StringToFloat float32
|
||||
SliceToMap map[string]interface{}
|
||||
MapToSlice []interface{}
|
||||
}
|
||||
|
||||
func TestBasicTypes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"vstring": "foo",
|
||||
"vint": 42,
|
||||
"Vuint": 42,
|
||||
"vbool": true,
|
||||
"Vfloat": 42.42,
|
||||
"vsilent": true,
|
||||
"vdata": 42,
|
||||
}
|
||||
|
||||
var result Basic
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Errorf("got an err: %s", err.Error())
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if result.Vstring != "foo" {
|
||||
t.Errorf("vstring value should be 'foo': %#v", result.Vstring)
|
||||
}
|
||||
|
||||
if result.Vint != 42 {
|
||||
t.Errorf("vint value should be 42: %#v", result.Vint)
|
||||
}
|
||||
|
||||
if result.Vuint != 42 {
|
||||
t.Errorf("vuint value should be 42: %#v", result.Vuint)
|
||||
}
|
||||
|
||||
if result.Vbool != true {
|
||||
t.Errorf("vbool value should be true: %#v", result.Vbool)
|
||||
}
|
||||
|
||||
if result.Vfloat != 42.42 {
|
||||
t.Errorf("vfloat value should be 42.42: %#v", result.Vfloat)
|
||||
}
|
||||
|
||||
if result.Vextra != "" {
|
||||
t.Errorf("vextra value should be empty: %#v", result.Vextra)
|
||||
}
|
||||
|
||||
if result.vsilent != false {
|
||||
t.Error("vsilent should not be set, it is unexported")
|
||||
}
|
||||
|
||||
if result.Vdata != 42 {
|
||||
t.Error("vdata should be valid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasic_IntWithFloat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"vint": float64(42),
|
||||
}
|
||||
|
||||
var result Basic
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("got an err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode_Embedded(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"vstring": "foo",
|
||||
"Basic": map[string]interface{}{
|
||||
"vstring": "innerfoo",
|
||||
},
|
||||
"vunique": "bar",
|
||||
}
|
||||
|
||||
var result Embedded
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("got an err: %s", err.Error())
|
||||
}
|
||||
|
||||
if result.Vstring != "innerfoo" {
|
||||
t.Errorf("vstring value should be 'innerfoo': %#v", result.Vstring)
|
||||
}
|
||||
|
||||
if result.Vunique != "bar" {
|
||||
t.Errorf("vunique value should be 'bar': %#v", result.Vunique)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode_EmbeddedPointer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"vstring": "foo",
|
||||
"Basic": map[string]interface{}{
|
||||
"vstring": "innerfoo",
|
||||
},
|
||||
"vunique": "bar",
|
||||
}
|
||||
|
||||
var result EmbeddedPointer
|
||||
err := Decode(input, &result)
|
||||
if err == nil {
|
||||
t.Fatal("should get error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode_EmbeddedSquash(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"vstring": "foo",
|
||||
"vunique": "bar",
|
||||
}
|
||||
|
||||
var result EmbeddedSquash
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("got an err: %s", err.Error())
|
||||
}
|
||||
|
||||
if result.Vstring != "foo" {
|
||||
t.Errorf("vstring value should be 'foo': %#v", result.Vstring)
|
||||
}
|
||||
|
||||
if result.Vunique != "bar" {
|
||||
t.Errorf("vunique value should be 'bar': %#v", result.Vunique)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode_DecodeHook(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"vint": "WHAT",
|
||||
}
|
||||
|
||||
decodeHook := func(from reflect.Kind, to reflect.Kind, v interface{}) (interface{}, error) {
|
||||
if from == reflect.String && to != reflect.String {
|
||||
return 5, nil
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
var result Basic
|
||||
config := &DecoderConfig{
|
||||
DecodeHook: decodeHook,
|
||||
Result: &result,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
err = decoder.Decode(input)
|
||||
if err != nil {
|
||||
t.Fatalf("got an err: %s", err)
|
||||
}
|
||||
|
||||
if result.Vint != 5 {
|
||||
t.Errorf("vint should be 5: %#v", result.Vint)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode_Nil(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var input interface{} = nil
|
||||
result := Basic{
|
||||
Vstring: "foo",
|
||||
}
|
||||
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if result.Vstring != "foo" {
|
||||
t.Fatalf("bad: %#v", result.Vstring)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode_NonStruct(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"bar": "baz",
|
||||
}
|
||||
|
||||
var result map[string]string
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if result["foo"] != "bar" {
|
||||
t.Fatal("foo is not bar")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode_TypeConversion(t *testing.T) {
|
||||
input := map[string]interface{}{
|
||||
"IntToFloat": 42,
|
||||
"IntToUint": 42,
|
||||
"IntToBool": 1,
|
||||
"IntToString": 42,
|
||||
"UintToInt": 42,
|
||||
"UintToFloat": 42,
|
||||
"UintToBool": 42,
|
||||
"UintToString": 42,
|
||||
"BoolToInt": true,
|
||||
"BoolToUint": true,
|
||||
"BoolToFloat": true,
|
||||
"BoolToString": true,
|
||||
"FloatToInt": 42.42,
|
||||
"FloatToUint": 42.42,
|
||||
"FloatToBool": 42.42,
|
||||
"FloatToString": 42.42,
|
||||
"SliceUint8ToString": []uint8("foo"),
|
||||
"StringToInt": "42",
|
||||
"StringToUint": "42",
|
||||
"StringToBool": "1",
|
||||
"StringToFloat": "42.42",
|
||||
"SliceToMap": []interface{}{},
|
||||
"MapToSlice": map[string]interface{}{},
|
||||
}
|
||||
|
||||
expectedResultStrict := TypeConversionResult{
|
||||
IntToFloat: 42.0,
|
||||
IntToUint: 42,
|
||||
UintToInt: 42,
|
||||
UintToFloat: 42,
|
||||
BoolToInt: 0,
|
||||
BoolToUint: 0,
|
||||
BoolToFloat: 0,
|
||||
FloatToInt: 42,
|
||||
FloatToUint: 42,
|
||||
}
|
||||
|
||||
expectedResultWeak := TypeConversionResult{
|
||||
IntToFloat: 42.0,
|
||||
IntToUint: 42,
|
||||
IntToBool: true,
|
||||
IntToString: "42",
|
||||
UintToInt: 42,
|
||||
UintToFloat: 42,
|
||||
UintToBool: true,
|
||||
UintToString: "42",
|
||||
BoolToInt: 1,
|
||||
BoolToUint: 1,
|
||||
BoolToFloat: 1,
|
||||
BoolToString: "1",
|
||||
FloatToInt: 42,
|
||||
FloatToUint: 42,
|
||||
FloatToBool: true,
|
||||
FloatToString: "42.42",
|
||||
SliceUint8ToString: "foo",
|
||||
StringToInt: 42,
|
||||
StringToUint: 42,
|
||||
StringToBool: true,
|
||||
StringToFloat: 42.42,
|
||||
SliceToMap: map[string]interface{}{},
|
||||
MapToSlice: []interface{}{},
|
||||
}
|
||||
|
||||
// Test strict type conversion
|
||||
var resultStrict TypeConversionResult
|
||||
err := Decode(input, &resultStrict)
|
||||
if err == nil {
|
||||
t.Errorf("should return an error")
|
||||
}
|
||||
if !reflect.DeepEqual(resultStrict, expectedResultStrict) {
|
||||
t.Errorf("expected %v, got: %v", expectedResultStrict, resultStrict)
|
||||
}
|
||||
|
||||
// Test weak type conversion
|
||||
var decoder *Decoder
|
||||
var resultWeak TypeConversionResult
|
||||
|
||||
config := &DecoderConfig{
|
||||
WeaklyTypedInput: true,
|
||||
Result: &resultWeak,
|
||||
}
|
||||
|
||||
decoder, err = NewDecoder(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
err = decoder.Decode(input)
|
||||
if err != nil {
|
||||
t.Fatalf("got an err: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(resultWeak, expectedResultWeak) {
|
||||
t.Errorf("expected \n%#v, got: \n%#v", expectedResultWeak, resultWeak)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecoder_ErrorUnused(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"vstring": "hello",
|
||||
"foo": "bar",
|
||||
}
|
||||
|
||||
var result Basic
|
||||
config := &DecoderConfig{
|
||||
ErrorUnused: true,
|
||||
Result: &result,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
err = decoder.Decode(input)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"vfoo": "foo",
|
||||
"vother": map[interface{}]interface{}{
|
||||
"foo": "foo",
|
||||
"bar": "bar",
|
||||
},
|
||||
}
|
||||
|
||||
var result Map
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("got an error: %s", err)
|
||||
}
|
||||
|
||||
if result.Vfoo != "foo" {
|
||||
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
|
||||
}
|
||||
|
||||
if result.Vother == nil {
|
||||
t.Fatal("vother should not be nil")
|
||||
}
|
||||
|
||||
if len(result.Vother) != 2 {
|
||||
t.Error("vother should have two items")
|
||||
}
|
||||
|
||||
if result.Vother["foo"] != "foo" {
|
||||
t.Errorf("'foo' key should be foo, got: %#v", result.Vother["foo"])
|
||||
}
|
||||
|
||||
if result.Vother["bar"] != "bar" {
|
||||
t.Errorf("'bar' key should be bar, got: %#v", result.Vother["bar"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapOfStruct(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"value": map[string]interface{}{
|
||||
"foo": map[string]string{"vstring": "one"},
|
||||
"bar": map[string]string{"vstring": "two"},
|
||||
},
|
||||
}
|
||||
|
||||
var result MapOfStruct
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("got an err: %s", err)
|
||||
}
|
||||
|
||||
if result.Value == nil {
|
||||
t.Fatal("value should not be nil")
|
||||
}
|
||||
|
||||
if len(result.Value) != 2 {
|
||||
t.Error("value should have two items")
|
||||
}
|
||||
|
||||
if result.Value["foo"].Vstring != "one" {
|
||||
t.Errorf("foo value should be 'one', got: %s", result.Value["foo"].Vstring)
|
||||
}
|
||||
|
||||
if result.Value["bar"].Vstring != "two" {
|
||||
t.Errorf("bar value should be 'two', got: %s", result.Value["bar"].Vstring)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNestedType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"vfoo": "foo",
|
||||
"vbar": map[string]interface{}{
|
||||
"vstring": "foo",
|
||||
"vint": 42,
|
||||
"vbool": true,
|
||||
},
|
||||
}
|
||||
|
||||
var result Nested
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("got an err: %s", err.Error())
|
||||
}
|
||||
|
||||
if result.Vfoo != "foo" {
|
||||
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
|
||||
}
|
||||
|
||||
if result.Vbar.Vstring != "foo" {
|
||||
t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring)
|
||||
}
|
||||
|
||||
if result.Vbar.Vint != 42 {
|
||||
t.Errorf("vint value should be 42: %#v", result.Vbar.Vint)
|
||||
}
|
||||
|
||||
if result.Vbar.Vbool != true {
|
||||
t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool)
|
||||
}
|
||||
|
||||
if result.Vbar.Vextra != "" {
|
||||
t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNestedTypePointer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"vfoo": "foo",
|
||||
"vbar": &map[string]interface{}{
|
||||
"vstring": "foo",
|
||||
"vint": 42,
|
||||
"vbool": true,
|
||||
},
|
||||
}
|
||||
|
||||
var result NestedPointer
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("got an err: %s", err.Error())
|
||||
}
|
||||
|
||||
if result.Vfoo != "foo" {
|
||||
t.Errorf("vfoo value should be 'foo': %#v", result.Vfoo)
|
||||
}
|
||||
|
||||
if result.Vbar.Vstring != "foo" {
|
||||
t.Errorf("vstring value should be 'foo': %#v", result.Vbar.Vstring)
|
||||
}
|
||||
|
||||
if result.Vbar.Vint != 42 {
|
||||
t.Errorf("vint value should be 42: %#v", result.Vbar.Vint)
|
||||
}
|
||||
|
||||
if result.Vbar.Vbool != true {
|
||||
t.Errorf("vbool value should be true: %#v", result.Vbar.Vbool)
|
||||
}
|
||||
|
||||
if result.Vbar.Vextra != "" {
|
||||
t.Errorf("vextra value should be empty: %#v", result.Vbar.Vextra)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
inputStringSlice := map[string]interface{}{
|
||||
"vfoo": "foo",
|
||||
"vbar": []string{"foo", "bar", "baz"},
|
||||
}
|
||||
|
||||
inputStringSlicePointer := map[string]interface{}{
|
||||
"vfoo": "foo",
|
||||
"vbar": &[]string{"foo", "bar", "baz"},
|
||||
}
|
||||
|
||||
outputStringSlice := &Slice{
|
||||
"foo",
|
||||
[]string{"foo", "bar", "baz"},
|
||||
}
|
||||
|
||||
testSliceInput(t, inputStringSlice, outputStringSlice)
|
||||
testSliceInput(t, inputStringSlicePointer, outputStringSlice)
|
||||
}
|
||||
|
||||
func TestInvalidSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"vfoo": "foo",
|
||||
"vbar": 42,
|
||||
}
|
||||
|
||||
result := Slice{}
|
||||
err := Decode(input, &result)
|
||||
if err == nil {
|
||||
t.Errorf("expected failure")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliceOfStruct(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"value": []map[string]interface{}{
|
||||
{"vstring": "one"},
|
||||
{"vstring": "two"},
|
||||
},
|
||||
}
|
||||
|
||||
var result SliceOfStruct
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("got unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if len(result.Value) != 2 {
|
||||
t.Fatalf("expected two values, got %d", len(result.Value))
|
||||
}
|
||||
|
||||
if result.Value[0].Vstring != "one" {
|
||||
t.Errorf("first value should be 'one', got: %s", result.Value[0].Vstring)
|
||||
}
|
||||
|
||||
if result.Value[1].Vstring != "two" {
|
||||
t.Errorf("second value should be 'two', got: %s", result.Value[1].Vstring)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"vstring": 42,
|
||||
}
|
||||
|
||||
var result Basic
|
||||
err := Decode(input, &result)
|
||||
if err == nil {
|
||||
t.Fatal("error should exist")
|
||||
}
|
||||
|
||||
derr, ok := err.(*Error)
|
||||
if !ok {
|
||||
t.Fatalf("error should be kind of Error, instead: %#v", err)
|
||||
}
|
||||
|
||||
if derr.Errors[0] != "'Vstring' expected type 'string', got unconvertible type 'int'" {
|
||||
t.Errorf("got unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadata(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"vfoo": "foo",
|
||||
"vbar": map[string]interface{}{
|
||||
"vstring": "foo",
|
||||
"Vuint": 42,
|
||||
"foo": "bar",
|
||||
},
|
||||
"bar": "nil",
|
||||
}
|
||||
|
||||
var md Metadata
|
||||
var result Nested
|
||||
config := &DecoderConfig{
|
||||
Metadata: &md,
|
||||
Result: &result,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
err = decoder.Decode(input)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err.Error())
|
||||
}
|
||||
|
||||
expectedKeys := []string{"Vfoo", "Vbar.Vstring", "Vbar.Vuint", "Vbar"}
|
||||
if !reflect.DeepEqual(md.Keys, expectedKeys) {
|
||||
t.Fatalf("bad keys: %#v", md.Keys)
|
||||
}
|
||||
|
||||
expectedUnused := []string{"Vbar.foo", "bar"}
|
||||
if !reflect.DeepEqual(md.Unused, expectedUnused) {
|
||||
t.Fatalf("bad unused: %#v", md.Unused)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadata_Embedded(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"vstring": "foo",
|
||||
"vunique": "bar",
|
||||
}
|
||||
|
||||
var md Metadata
|
||||
var result EmbeddedSquash
|
||||
config := &DecoderConfig{
|
||||
Metadata: &md,
|
||||
Result: &result,
|
||||
}
|
||||
|
||||
decoder, err := NewDecoder(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
err = decoder.Decode(input)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err.Error())
|
||||
}
|
||||
|
||||
expectedKeys := []string{"Vstring", "Vunique"}
|
||||
|
||||
sort.Strings(md.Keys)
|
||||
if !reflect.DeepEqual(md.Keys, expectedKeys) {
|
||||
t.Fatalf("bad keys: %#v", md.Keys)
|
||||
}
|
||||
|
||||
expectedUnused := []string{}
|
||||
if !reflect.DeepEqual(md.Unused, expectedUnused) {
|
||||
t.Fatalf("bad unused: %#v", md.Unused)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonPtrValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := Decode(map[string]interface{}{}, Basic{})
|
||||
if err == nil {
|
||||
t.Fatal("error should exist")
|
||||
}
|
||||
|
||||
if err.Error() != "result must be a pointer" {
|
||||
t.Errorf("got unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagged(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"bar": "value",
|
||||
}
|
||||
|
||||
var result Tagged
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if result.Value != "bar" {
|
||||
t.Errorf("value should be 'bar', got: %#v", result.Value)
|
||||
}
|
||||
|
||||
if result.Extra != "value" {
|
||||
t.Errorf("extra should be 'value', got: %#v", result.Extra)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeakDecode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"foo": "4",
|
||||
"bar": "value",
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Foo int
|
||||
Bar string
|
||||
}
|
||||
|
||||
if err := WeakDecode(input, &result); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if result.Foo != 4 {
|
||||
t.Fatalf("bad: %#v", result)
|
||||
}
|
||||
if result.Bar != "value" {
|
||||
t.Fatalf("bad: %#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func testSliceInput(t *testing.T, input map[string]interface{}, expected *Slice) {
|
||||
var result Slice
|
||||
err := Decode(input, &result)
|
||||
if err != nil {
|
||||
t.Fatalf("got error: %s", err)
|
||||
}
|
||||
|
||||
if result.Vfoo != expected.Vfoo {
|
||||
t.Errorf("Vfoo expected '%s', got '%s'", expected.Vfoo, result.Vfoo)
|
||||
}
|
||||
|
||||
if result.Vbar == nil {
|
||||
t.Fatalf("Vbar a slice, got '%#v'", result.Vbar)
|
||||
}
|
||||
|
||||
if len(result.Vbar) != len(expected.Vbar) {
|
||||
t.Errorf("Vbar length should be %d, got %d", len(expected.Vbar), len(result.Vbar))
|
||||
}
|
||||
|
||||
for i, v := range result.Vbar {
|
||||
if v != expected.Vbar[i] {
|
||||
t.Errorf(
|
||||
"Vbar[%d] should be '%#v', got '%#v'",
|
||||
i, expected.Vbar[i], v)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
bin/*
|
||||
pkg/*
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
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:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) 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
|
||||
|
||||
(d) 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 [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
|
@ -0,0 +1,120 @@
|
|||
# perigee
|
||||
|
||||
Perigee provides a REST client that, while it should be generic enough to use with most any RESTful API, is nonetheless optimized to the needs of the OpenStack APIs.
|
||||
Perigee grew out of the need to refactor out common API access code from the [gorax](http://github.com/racker/gorax) project.
|
||||
|
||||
Several things influenced the name of the project.
|
||||
Numerous elements of the OpenStack ecosystem are named after astronomical artifacts.
|
||||
Additionally, perigee occurs when two orbiting bodies are closest to each other.
|
||||
Perigee seemed appropriate for something aiming to bring OpenStack and other RESTful services closer to the end-user.
|
||||
|
||||
**This library is still in the very early stages of development. Unless you want to contribute, it probably isn't what you want**
|
||||
|
||||
## Installation and Testing
|
||||
|
||||
To install:
|
||||
|
||||
```bash
|
||||
go get github.com/racker/perigee
|
||||
```
|
||||
|
||||
To run unit tests:
|
||||
|
||||
```bash
|
||||
go test github.com/racker/perigee
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
The following guidelines are preliminary, as this project is just starting out.
|
||||
However, this should serve as a working first-draft.
|
||||
|
||||
### Branching
|
||||
|
||||
The master branch must always be a valid build.
|
||||
The `go get` command will not work otherwise.
|
||||
Therefore, development must occur on a different branch.
|
||||
|
||||
When creating a feature branch, do so off the master branch:
|
||||
|
||||
```bash
|
||||
git checkout master
|
||||
git pull
|
||||
git checkout -b featureBranch
|
||||
git checkout -b featureBranch-wip # optional
|
||||
```
|
||||
|
||||
Perform all your editing and testing in the WIP-branch.
|
||||
Feel free to make as many commits as you see fit.
|
||||
You may even open "WIP" pull requests from your feature branch to seek feedback.
|
||||
WIP pull requests will **never** be merged, however.
|
||||
|
||||
To get code merged, you'll need to "squash" your changes into one or more clean commits in the feature branch.
|
||||
These steps should be followed:
|
||||
|
||||
```bash
|
||||
git checkout featureBranch
|
||||
git merge --squash featureBranch-wip
|
||||
git commit -a
|
||||
git push origin featureBranch
|
||||
```
|
||||
|
||||
You may now open a nice, clean, self-contained pull request from featureBranch to master.
|
||||
|
||||
The `git commit -a` command above will open a text editor so that
|
||||
you may provide a comprehensive description of the changes.
|
||||
|
||||
In general, when submitting a pull request against master,
|
||||
be sure to answer the following questions:
|
||||
|
||||
- What is the problem?
|
||||
- Why is it a problem?
|
||||
- What is your solution?
|
||||
- How does your solution work? (Recommended for non-trivial changes.)
|
||||
- Why should we use your solution over someone elses? (Recommended especially if multiple solutions being discussed.)
|
||||
|
||||
Remember that monster-sized pull requests are a bear to code-review,
|
||||
so having helpful commit logs are an absolute must to review changes as quickly as possible.
|
||||
|
||||
Finally, (s)he who breaks master is ultimately responsible for fixing master.
|
||||
|
||||
### Source Representation
|
||||
|
||||
The Go community firmly believes in a consistent representation for all Go source code.
|
||||
We do too.
|
||||
Make sure all source code is passed through "go fmt" *before* you create your pull request.
|
||||
|
||||
Please note, however, that we fully acknowledge and recognize that we no longer rely upon punch-cards for representing source files.
|
||||
Therefore, no 80-column limit exists.
|
||||
However, if a line exceeds 132 columns, you may want to consider splitting the line.
|
||||
|
||||
### Unit and Integration Tests
|
||||
|
||||
Pull requests that include non-trivial code changes without accompanying unit tests will be flatly rejected.
|
||||
While we have no way of enforcing this practice,
|
||||
you can ensure your code is thoroughly tested by always [writing tests first by intention.](http://en.wikipedia.org/wiki/Test-driven_development)
|
||||
|
||||
When creating a pull request, if even one test fails, the PR will be rejected.
|
||||
Make sure all unit tests pass.
|
||||
Make sure all integration tests pass.
|
||||
|
||||
### Documentation
|
||||
|
||||
Private functions and methods which are obvious to anyone unfamiliar with gorax needn't be accompanied by documentation.
|
||||
However, this is a code-smell; if submitting a PR, expect to justify your decision.
|
||||
|
||||
Public functions, regardless of how obvious, **must** have accompanying godoc-style documentation.
|
||||
This is not to suggest you should provide a tome for each function, however.
|
||||
Sometimes a link to more information is more appropriate, provided the link is stable, reliable, and pertinent.
|
||||
|
||||
Changing documentation often results in bizarre diffs in pull requests, due to text often spanning multiple lines.
|
||||
To work around this, put [one logical thought or sentence on a single line.](http://rhodesmill.org/brandon/2012/one-sentence-per-line/)
|
||||
While this looks weird in a plain-text editor,
|
||||
remember that both godoc and HTML viewers will reflow text.
|
||||
The source code and its comments should be easy to edit with minimal diff pollution.
|
||||
Let software dedicated to presenting the documentation to human readers deal with its presentation.
|
||||
|
||||
## Examples
|
||||
|
||||
t.b.d.
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
// vim: ts=8 sw=8 noet ai
|
||||
|
||||
package perigee
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The UnexpectedResponseCodeError structure represents a mismatch in understanding between server and client in terms of response codes.
|
||||
// Most often, this is due to an actual error condition (e.g., getting a 404 for a resource when you expect a 200).
|
||||
// However, it needn't always be the case (e.g., getting a 204 (No Content) response back when a 200 is expected).
|
||||
type UnexpectedResponseCodeError struct {
|
||||
Url string
|
||||
Expected []int
|
||||
Actual int
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func (err *UnexpectedResponseCodeError) Error() string {
|
||||
return fmt.Sprintf("Expected HTTP response code %d when accessing URL(%s); got %d instead with the following body:\n%s", err.Expected, err.Url, err.Actual, string(err.Body))
|
||||
}
|
||||
|
||||
// Request issues an HTTP request, marshaling parameters, and unmarshaling results, as configured in the provided Options parameter.
|
||||
// The Response structure returned, if any, will include accumulated results recovered from the HTTP server.
|
||||
// See the Response structure for more details.
|
||||
func Request(method string, url string, opts Options) (*Response, error) {
|
||||
var body io.Reader
|
||||
var response Response
|
||||
|
||||
client := opts.CustomClient
|
||||
if client == nil {
|
||||
client = new(http.Client)
|
||||
}
|
||||
|
||||
contentType := opts.ContentType
|
||||
|
||||
body = nil
|
||||
if opts.ReqBody != nil {
|
||||
if contentType == "" {
|
||||
contentType = "application/json"
|
||||
}
|
||||
|
||||
if contentType == "application/json" {
|
||||
bodyText, err := json.Marshal(opts.ReqBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body = strings.NewReader(string(bodyText))
|
||||
if opts.DumpReqJson {
|
||||
log.Printf("Making request:\n%#v\n", string(bodyText))
|
||||
}
|
||||
} else {
|
||||
// assume opts.ReqBody implements the correct interface
|
||||
body = opts.ReqBody.(io.Reader)
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if contentType != "" {
|
||||
req.Header.Add("Content-Type", contentType)
|
||||
}
|
||||
|
||||
if opts.ContentLength > 0 {
|
||||
req.ContentLength = opts.ContentLength
|
||||
req.Header.Add("Content-Length", string(opts.ContentLength))
|
||||
}
|
||||
|
||||
if opts.MoreHeaders != nil {
|
||||
for k, v := range opts.MoreHeaders {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if accept := req.Header.Get("Accept"); accept == "" {
|
||||
accept = opts.Accept
|
||||
if accept == "" {
|
||||
accept = "application/json"
|
||||
}
|
||||
req.Header.Add("Accept", accept)
|
||||
}
|
||||
|
||||
if opts.SetHeaders != nil {
|
||||
err = opts.SetHeaders(req)
|
||||
if err != nil {
|
||||
return &response, err
|
||||
}
|
||||
}
|
||||
|
||||
httpResponse, err := client.Do(req)
|
||||
if httpResponse != nil {
|
||||
response.HttpResponse = *httpResponse
|
||||
response.StatusCode = httpResponse.StatusCode
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return &response, err
|
||||
}
|
||||
// This if-statement is legacy code, preserved for backward compatibility.
|
||||
if opts.StatusCode != nil {
|
||||
*opts.StatusCode = httpResponse.StatusCode
|
||||
}
|
||||
|
||||
acceptableResponseCodes := opts.OkCodes
|
||||
if len(acceptableResponseCodes) != 0 {
|
||||
if not_in(httpResponse.StatusCode, acceptableResponseCodes) {
|
||||
b, _ := ioutil.ReadAll(httpResponse.Body)
|
||||
httpResponse.Body.Close()
|
||||
return &response, &UnexpectedResponseCodeError{
|
||||
Url: url,
|
||||
Expected: acceptableResponseCodes,
|
||||
Actual: httpResponse.StatusCode,
|
||||
Body: b,
|
||||
}
|
||||
}
|
||||
}
|
||||
if opts.Results != nil {
|
||||
defer httpResponse.Body.Close()
|
||||
jsonResult, err := ioutil.ReadAll(httpResponse.Body)
|
||||
response.JsonResult = jsonResult
|
||||
if err != nil {
|
||||
return &response, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(jsonResult, opts.Results)
|
||||
// This if-statement is legacy code, preserved for backward compatibility.
|
||||
if opts.ResponseJson != nil {
|
||||
*opts.ResponseJson = jsonResult
|
||||
}
|
||||
}
|
||||
return &response, err
|
||||
}
|
||||
|
||||
// not_in returns false if, and only if, the provided needle is _not_
|
||||
// in the given set of integers.
|
||||
func not_in(needle int, haystack []int) bool {
|
||||
for _, straw := range haystack {
|
||||
if needle == straw {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Post makes a POST request against a server using the provided HTTP client.
|
||||
// The url must be a fully-formed URL string.
|
||||
// DEPRECATED. Use Request() instead.
|
||||
func Post(url string, opts Options) error {
|
||||
r, err := Request("POST", url, opts)
|
||||
if opts.Response != nil {
|
||||
*opts.Response = r
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Get makes a GET request against a server using the provided HTTP client.
|
||||
// The url must be a fully-formed URL string.
|
||||
// DEPRECATED. Use Request() instead.
|
||||
func Get(url string, opts Options) error {
|
||||
r, err := Request("GET", url, opts)
|
||||
if opts.Response != nil {
|
||||
*opts.Response = r
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete makes a DELETE request against a server using the provided HTTP client.
|
||||
// The url must be a fully-formed URL string.
|
||||
// DEPRECATED. Use Request() instead.
|
||||
func Delete(url string, opts Options) error {
|
||||
r, err := Request("DELETE", url, opts)
|
||||
if opts.Response != nil {
|
||||
*opts.Response = r
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Put makes a PUT request against a server using the provided HTTP client.
|
||||
// The url must be a fully-formed URL string.
|
||||
// DEPRECATED. Use Request() instead.
|
||||
func Put(url string, opts Options) error {
|
||||
r, err := Request("PUT", url, opts)
|
||||
if opts.Response != nil {
|
||||
*opts.Response = r
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Options describes a set of optional parameters to the various request calls.
|
||||
//
|
||||
// The custom client can be used for a variety of purposes beyond selecting encrypted versus unencrypted channels.
|
||||
// Transports can be defined to provide augmented logging, header manipulation, et. al.
|
||||
//
|
||||
// If the ReqBody field is provided, it will be embedded as a JSON object.
|
||||
// Otherwise, provide nil.
|
||||
//
|
||||
// If JSON output is to be expected from the response,
|
||||
// provide either a pointer to the container structure in Results,
|
||||
// or a pointer to a nil-initialized pointer variable.
|
||||
// The latter method will cause the unmarshaller to allocate the container type for you.
|
||||
// If no response is expected, provide a nil Results value.
|
||||
//
|
||||
// The MoreHeaders map, if non-nil or empty, provides a set of headers to add to those
|
||||
// already present in the request. At present, only Accepted and Content-Type are set
|
||||
// by default.
|
||||
//
|
||||
// OkCodes provides a set of acceptable, positive responses.
|
||||
//
|
||||
// If provided, StatusCode specifies a pointer to an integer, which will receive the
|
||||
// returned HTTP status code, successful or not. DEPRECATED; use the Response.StatusCode field instead for new software.
|
||||
//
|
||||
// ResponseJson, if specified, provides a means for returning the raw JSON. This is
|
||||
// most useful for diagnostics. DEPRECATED; use the Response.JsonResult field instead for new software.
|
||||
//
|
||||
// DumpReqJson, if set to true, will cause the request to appear to stdout for debugging purposes.
|
||||
// This attribute may be removed at any time in the future; DO NOT use this attribute in production software.
|
||||
//
|
||||
// Response, if set, provides a way to communicate the complete set of HTTP response, raw JSON, status code, and
|
||||
// other useful attributes back to the caller. Note that the Request() method returns a Response structure as part
|
||||
// of its public interface; you don't need to set the Response field here to use this structure. The Response field
|
||||
// exists primarily for legacy or deprecated functions.
|
||||
//
|
||||
// SetHeaders allows the caller to provide code to set any custom headers programmatically. Typically, this
|
||||
// facility can invoke, e.g., SetBasicAuth() on the request to easily set up authentication.
|
||||
// Any error generated will terminate the request and will propegate back to the caller.
|
||||
type Options struct {
|
||||
CustomClient *http.Client
|
||||
ReqBody interface{}
|
||||
Results interface{}
|
||||
MoreHeaders map[string]string
|
||||
OkCodes []int
|
||||
StatusCode *int `DEPRECATED`
|
||||
DumpReqJson bool `UNSUPPORTED`
|
||||
ResponseJson *[]byte `DEPRECATED`
|
||||
Response **Response
|
||||
ContentType string `json:"Content-Type,omitempty"`
|
||||
ContentLength int64 `json:"Content-Length,omitempty"`
|
||||
Accept string `json:"Accept,omitempty"`
|
||||
SetHeaders func(r *http.Request) error
|
||||
}
|
||||
|
||||
// Response contains return values from the various request calls.
|
||||
//
|
||||
// HttpResponse will return the http response from the request call.
|
||||
// Note: HttpResponse.Body is always closed and will not be available from this return value.
|
||||
//
|
||||
// StatusCode specifies the returned HTTP status code, successful or not.
|
||||
//
|
||||
// If Results is specified in the Options:
|
||||
// - JsonResult will contain the raw return from the request call
|
||||
// This is most useful for diagnostics.
|
||||
// - Result will contain the unmarshalled json either in the Result passed in
|
||||
// or the unmarshaller will allocate the container type for you.
|
||||
|
||||
type Response struct {
|
||||
HttpResponse http.Response
|
||||
JsonResult []byte
|
||||
Results interface{}
|
||||
StatusCode int
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
package perigee
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNormal(t *testing.T) {
|
||||
handler := http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("testing"))
|
||||
})
|
||||
ts := httptest.NewServer(handler)
|
||||
defer ts.Close()
|
||||
|
||||
response, err := Request("GET", ts.URL, Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
if response.StatusCode != 200 {
|
||||
t.Fatalf("response code %d is not 200", response.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOKCodes(t *testing.T) {
|
||||
expectCode := 201
|
||||
handler := http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(expectCode)
|
||||
w.Write([]byte("testing"))
|
||||
})
|
||||
ts := httptest.NewServer(handler)
|
||||
defer ts.Close()
|
||||
|
||||
options := Options{
|
||||
OkCodes: []int{expectCode},
|
||||
}
|
||||
results, err := Request("GET", ts.URL, options)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
if results.StatusCode != expectCode {
|
||||
t.Fatalf("response code %d is not %d", results.StatusCode, expectCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocation(t *testing.T) {
|
||||
newLocation := "http://www.example.com"
|
||||
handler := http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Location", newLocation)
|
||||
w.Write([]byte("testing"))
|
||||
})
|
||||
ts := httptest.NewServer(handler)
|
||||
defer ts.Close()
|
||||
|
||||
response, err := Request("GET", ts.URL, Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
location, err := response.HttpResponse.Location()
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if location.String() != newLocation {
|
||||
t.Fatalf("location returned \"%s\" is not \"%s\"", location.String(), newLocation)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeaders(t *testing.T) {
|
||||
newLocation := "http://www.example.com"
|
||||
handler := http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Location", newLocation)
|
||||
w.Write([]byte("testing"))
|
||||
})
|
||||
ts := httptest.NewServer(handler)
|
||||
defer ts.Close()
|
||||
|
||||
response, err := Request("GET", ts.URL, Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
location := response.HttpResponse.Header.Get("Location")
|
||||
if location == "" {
|
||||
t.Fatalf("Location should not empty")
|
||||
}
|
||||
|
||||
if location != newLocation {
|
||||
t.Fatalf("location returned \"%s\" is not \"%s\"", location, newLocation)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomHeaders(t *testing.T) {
|
||||
var contentType, accept, contentLength string
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
m := map[string][]string(r.Header)
|
||||
contentType = m["Content-Type"][0]
|
||||
accept = m["Accept"][0]
|
||||
contentLength = m["Content-Length"][0]
|
||||
})
|
||||
ts := httptest.NewServer(handler)
|
||||
defer ts.Close()
|
||||
|
||||
_, err := Request("GET", ts.URL, Options{
|
||||
ContentLength: 5,
|
||||
ContentType: "x-application/vb",
|
||||
Accept: "x-application/c",
|
||||
ReqBody: strings.NewReader("Hello"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
if contentType != "x-application/vb" {
|
||||
t.Fatalf("I expected x-application/vb; got ", contentType)
|
||||
}
|
||||
|
||||
if contentLength != "5" {
|
||||
t.Fatalf("I expected 5 byte content length; got ", contentLength)
|
||||
}
|
||||
|
||||
if accept != "x-application/c" {
|
||||
t.Fatalf("I expected x-application/c; got ", accept)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJson(t *testing.T) {
|
||||
newLocation := "http://www.example.com"
|
||||
jsonBytes := []byte(`{"foo": {"bar": "baz"}}`)
|
||||
handler := http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Location", newLocation)
|
||||
w.Write(jsonBytes)
|
||||
})
|
||||
ts := httptest.NewServer(handler)
|
||||
defer ts.Close()
|
||||
|
||||
type Data struct {
|
||||
Foo struct {
|
||||
Bar string `json:"bar"`
|
||||
} `json:"foo"`
|
||||
}
|
||||
var data Data
|
||||
|
||||
response, err := Request("GET", ts.URL, Options{Results: &data})
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
|
||||
if bytes.Compare(jsonBytes, response.JsonResult) != 0 {
|
||||
t.Fatalf("json returned \"%s\" is not \"%s\"", response.JsonResult, jsonBytes)
|
||||
}
|
||||
|
||||
if data.Foo.Bar != "baz" {
|
||||
t.Fatalf("Results returned %v", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetHeaders(t *testing.T) {
|
||||
var wasCalled bool
|
||||
handler := http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("Hi"))
|
||||
})
|
||||
ts := httptest.NewServer(handler)
|
||||
defer ts.Close()
|
||||
|
||||
_, err := Request("GET", ts.URL, Options{
|
||||
SetHeaders: func(r *http.Request) error {
|
||||
wasCalled = true
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !wasCalled {
|
||||
t.Fatal("I expected header setter callback to be called, but it wasn't")
|
||||
}
|
||||
|
||||
myError := fmt.Errorf("boo")
|
||||
|
||||
_, err = Request("GET", ts.URL, Options{
|
||||
SetHeaders: func(r *http.Request) error {
|
||||
return myError
|
||||
},
|
||||
})
|
||||
|
||||
if err != myError {
|
||||
t.Fatal("I expected errors to propegate back to the caller.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBodilessMethodsAreSentWithoutContentHeaders(t *testing.T) {
|
||||
var h map[string][]string
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h = r.Header
|
||||
})
|
||||
ts := httptest.NewServer(handler)
|
||||
defer ts.Close()
|
||||
|
||||
_, err := Request("GET", ts.URL, Options{})
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
if len(h["Content-Type"]) != 0 {
|
||||
t.Fatalf("I expected nothing for Content-Type but got ", h["Content-Type"])
|
||||
}
|
||||
|
||||
if len(h["Content-Length"]) != 0 {
|
||||
t.Fatalf("I expected nothing for Content-Length but got ", h["Content-Length"])
|
||||
}
|
||||
}
|
16
Godeps/_workspace/src/github.com/rackspace/gophercloud/.editorconfig
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/rackspace/gophercloud/.editorconfig
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# All files
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Golang
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
14
Godeps/_workspace/src/github.com/rackspace/gophercloud/.travis.yml
generated
vendored
Normal file
14
Godeps/_workspace/src/github.com/rackspace/gophercloud/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
language: go
|
||||
install:
|
||||
- go get -v .
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- tip
|
||||
after_success:
|
||||
- go get code.google.com/p/go.tools/cmd/cover
|
||||
- go get github.com/axw/gocov/gocov
|
||||
- go get github.com/mattn/goveralls
|
||||
- export PATH=$PATH:$HOME/gopath/bin/
|
||||
- goveralls 2k7PTU3xa474Hymwgdj6XjqenNfGTNkO8
|
||||
|
6
Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTORS.md
generated
vendored
Normal file
6
Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTORS.md
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
Contributors
|
||||
============
|
||||
|
||||
Samuel A. Falvo II <sam.falvo@rackspace.com>
|
||||
Glen Campbell <glen.campbell@rackspace.com>
|
||||
Jesse Noller <jesse.noller@rackspace.com>
|
|
@ -0,0 +1,191 @@
|
|||
Copyright 2012-2013 Rackspace, Inc.
|
||||
|
||||
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.
|
||||
|
||||
------
|
||||
|
||||
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:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) 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
|
||||
|
||||
(d) 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
|
44
Godeps/_workspace/src/github.com/rackspace/gophercloud/README.asciidoc
generated
vendored
Normal file
44
Godeps/_workspace/src/github.com/rackspace/gophercloud/README.asciidoc
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
== Gophercloud -- V0.1.0 image:https://secure.travis-ci.org/rackspace/gophercloud.png?branch=master["build status",link="https://travis-ci.org/rackspace/gophercloud"]
|
||||
|
||||
Gophercloud currently lets you authenticate with OpenStack providers to create and manage servers.
|
||||
We are working on extending the API to further include cloud files, block storage, DNS, databases, security groups, and other features.
|
||||
|
||||
WARNING: This library is still in the very early stages of development. Unless you want to contribute, it probably isn't what you want. Yet.
|
||||
|
||||
=== Outstanding Features
|
||||
|
||||
1. Apache 2.0 License, making Gophercloud friendly to commercial and open-source enterprises alike.
|
||||
2. Gophercloud is one of the most actively maintained Go SDKs for OpenStack.
|
||||
3. Gophercloud supports Identity V2 and Nova V2 APIs. More coming soon!
|
||||
4. The up-coming Gophercloud 0.2.0 release supports API extensions, and makes writing support for new extensions easy.
|
||||
5. Gophercloud supports automatic reauthentication upon auth token timeout, if enabled by your software.
|
||||
6. Gophercloud is the only SDK implementation with actual acceptance-level integration tests.
|
||||
|
||||
=== What Does it Look Like?
|
||||
|
||||
The Gophercloud 0.1.0 and earlier APIs are now deprecated and obsolete.
|
||||
No new feature development will occur for 0.1.0 or 0.0.0.
|
||||
However, we will accept and provide bug fixes for these APIs.
|
||||
Please refer to the acceptance tests in the master brach for code examples using the v0.1.0 API.
|
||||
The most up to date documentation for version 0.1.x can be found at link:http://godoc.org/github.com/rackspace/gophercloud[our Godoc.org documentation].
|
||||
|
||||
We are working on a new API that provides much better support for extensions, pagination, and other features that proved difficult to implement before.
|
||||
This new API will be substantially more Go-idiomatic as well; one of the complaints received about 0.1.x and earlier is that it didn't "feel" right.
|
||||
To see what this new API is going to look like, you can look at the code examples up on the link:http://gophercloud.io/docs.html[Gophercloud website].
|
||||
If you're interested in tracking progress, note that features for version 0.2.0 will appear in the `v0.2.0` branch until merged to master.
|
||||
|
||||
=== How can I Contribute?
|
||||
|
||||
After using Gophercloud for a while, you might find that it lacks some useful feature, or that existing behavior seems buggy. We welcome contributions
|
||||
from our users for both missing functionality as well as for bug fixes. We encourage contributors to collaborate with the
|
||||
link:http://gophercloud.io/community.html[Gophercloud community.]
|
||||
|
||||
Finally, Gophercloud maintains its own link:http://gophercloud.io[announcements and updates blog.]
|
||||
Feel free to check back now and again to see what's new.
|
||||
|
||||
== License
|
||||
|
||||
Copyright (C) 2013, 2014 Rackspace, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0
|
||||
|
30
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/00-authentication.go
generated
vendored
Normal file
30
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/00-authentication.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
provider, username, _, apiKey := getCredentials()
|
||||
|
||||
if !strings.Contains(provider, "rackspace") {
|
||||
fmt.Fprintf(os.Stdout, "Skipping test because provider doesn't support API_KEYs\n")
|
||||
return
|
||||
}
|
||||
|
||||
_, err := gophercloud.Authenticate(
|
||||
provider,
|
||||
gophercloud.AuthOptions{
|
||||
Username: username,
|
||||
ApiKey: apiKey,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
22
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/01-authentication.go
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/01-authentication.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
func main() {
|
||||
provider, username, password, _ := getCredentials()
|
||||
|
||||
_, err := gophercloud.Authenticate(
|
||||
provider,
|
||||
gophercloud.AuthOptions{
|
||||
Username: username,
|
||||
Password: password,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
62
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/02-list-servers.go
generated
vendored
Normal file
62
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/02-list-servers.go
generated
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(api gophercloud.CloudServersProvider) {
|
||||
tryFullDetails(api)
|
||||
tryLinksOnly(api)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func tryLinksOnly(api gophercloud.CloudServersProvider) {
|
||||
servers, err := api.ListServersLinksOnly()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("Id,Name")
|
||||
for _, s := range servers {
|
||||
if s.AccessIPv4 != "" {
|
||||
panic("IPv4 not expected")
|
||||
}
|
||||
|
||||
if s.Status != "" {
|
||||
panic("Status not expected")
|
||||
}
|
||||
|
||||
if s.Progress != 0 {
|
||||
panic("Progress not expected")
|
||||
}
|
||||
|
||||
fmt.Printf("%s,\"%s\"\n", s.Id, s.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tryFullDetails(api gophercloud.CloudServersProvider) {
|
||||
servers, err := api.ListServers()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("Id,Name,AccessIPv4,Status,Progress")
|
||||
for _, s := range servers {
|
||||
fmt.Printf("%s,\"%s\",%s,%s,%d\n", s.Id, s.Name, s.AccessIPv4, s.Status, s.Progress)
|
||||
}
|
||||
}
|
||||
}
|
134
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/03-get-server-details.go
generated
vendored
Normal file
134
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/03-get-server-details.go
generated
vendored
Normal file
|
@ -0,0 +1,134 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"os"
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
var id = flag.String("i", "", "Server ID to get info on. Defaults to first server in your account if unspecified.")
|
||||
var rgn = flag.String("r", "", "Datacenter region. Leave blank for default region.")
|
||||
var quiet = flag.Bool("quiet", false, "Run quietly, for acceptance testing. $? non-zero if issue.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
resultCode := 0
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
var (
|
||||
err error
|
||||
serverId string
|
||||
deleteAfterwards bool
|
||||
)
|
||||
|
||||
// Figure out which server to provide server details for.
|
||||
if *id == "" {
|
||||
deleteAfterwards, serverId, err = locateAServer(servers)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if deleteAfterwards {
|
||||
defer servers.DeleteServerById(serverId)
|
||||
}
|
||||
} else {
|
||||
serverId = *id
|
||||
}
|
||||
|
||||
// Grab server details by ID, and provide a report.
|
||||
s, err := servers.ServerById(serverId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configs := []string{
|
||||
"Access IPv4: %s\n",
|
||||
"Access IPv6: %s\n",
|
||||
" Created: %s\n",
|
||||
" Flavor: %s\n",
|
||||
" Host ID: %s\n",
|
||||
" ID: %s\n",
|
||||
" Image: %s\n",
|
||||
" Name: %s\n",
|
||||
" Progress: %s\n",
|
||||
" Status: %s\n",
|
||||
" Tenant ID: %s\n",
|
||||
" Updated: %s\n",
|
||||
" User ID: %s\n",
|
||||
}
|
||||
|
||||
values := []string{
|
||||
s.AccessIPv4,
|
||||
s.AccessIPv6,
|
||||
s.Created,
|
||||
s.Flavor.Id,
|
||||
s.HostId,
|
||||
s.Id,
|
||||
s.Image.Id,
|
||||
s.Name,
|
||||
fmt.Sprintf("%d", s.Progress),
|
||||
s.Status,
|
||||
s.TenantId,
|
||||
s.Updated,
|
||||
s.UserId,
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("Server info:")
|
||||
for i, _ := range configs {
|
||||
fmt.Printf(configs[i], values[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Negative test -- We should absolutely never panic for a server that doesn't exist.
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
_, err := servers.ServerById(randomString("garbage", 32))
|
||||
if err == nil {
|
||||
fmt.Printf("Expected a 404 response when looking for a server known not to exist\n")
|
||||
resultCode = 1
|
||||
}
|
||||
perigeeError, ok := err.(*perigee.UnexpectedResponseCodeError)
|
||||
if !ok {
|
||||
fmt.Printf("Unexpected error type\n")
|
||||
resultCode = 1
|
||||
} else {
|
||||
if perigeeError.Actual != 404 {
|
||||
fmt.Printf("Expected a 404 error code\n")
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
os.Exit(resultCode)
|
||||
}
|
||||
|
||||
// locateAServer queries the set of servers owned by the user. If at least one
|
||||
// exists, the first found is picked, and its ID is returned. Otherwise, a new
|
||||
// server will be created, and its ID returned.
|
||||
//
|
||||
// deleteAfter will be true if the caller should schedule a call to DeleteServerById()
|
||||
// to clean up.
|
||||
func locateAServer(servers gophercloud.CloudServersProvider) (deleteAfter bool, id string, err error) {
|
||||
ss, err := servers.ListServers()
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
if len(ss) > 0 {
|
||||
// We could just cheat and dump the server details from ss[0].
|
||||
// But, that tests ListServers(), and not ServerById(). So, we
|
||||
// elect not to cheat.
|
||||
return false, ss[0].Id, nil
|
||||
}
|
||||
|
||||
serverId, err := createServer(servers, "", "", "", "")
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
err = waitForServerState(servers, serverId, "ACTIVE")
|
||||
return true, serverId, err
|
||||
}
|
47
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/04-create-server.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/04-create-server.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var region, serverName, imageRef, flavorRef *string
|
||||
var adminPass = flag.String("a", "", "Administrator password (auto-assigned if none)")
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance tests. $? non-zero if error.")
|
||||
|
||||
func configure() {
|
||||
region = flag.String("r", "", "Region in which to create the server. Leave blank for provider-default region.")
|
||||
serverName = flag.String("n", randomString("ACPTTEST--", 16), "Server name (what you see in the control panel)")
|
||||
imageRef = flag.String("i", "", "ID of image to deploy onto the server")
|
||||
flavorRef = flag.String("f", "", "Flavor of server to deploy image upon")
|
||||
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
configure()
|
||||
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
_, err := createServer(servers, *imageRef, *flavorRef, *serverName, *adminPass)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
allServers, err := servers.ListServers()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Printf("ID,Name,Status,Progress\n")
|
||||
for _, i := range allServers {
|
||||
fmt.Printf("%s,\"%s\",%s,%d\n", i.Id, i.Name, i.Status, i.Progress)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
32
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/05-list-images.go
generated
vendored
Normal file
32
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/05-list-images.go
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance testing. $? non-zero on error though.")
|
||||
var rgn = flag.String("r", "", "Datacenter region to interrogate. Leave blank for provider-default region.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
images, err := servers.ListImages()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("ID,Name,MinRam,MinDisk")
|
||||
for _, image := range images {
|
||||
fmt.Printf("%s,\"%s\",%d,%d\n", image.Id, image.Name, image.MinRam, image.MinDisk)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
32
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/06-list-flavors.go
generated
vendored
Normal file
32
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/06-list-flavors.go
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance testing. $? non-zero on error though.")
|
||||
var rgn = flag.String("r", "", "Datacenter region to interrogate. Leave blank for provider-default region.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
flavors, err := servers.ListFlavors()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("ID,Name,MinRam,MinDisk")
|
||||
for _, f := range flavors {
|
||||
fmt.Printf("%s,\"%s\",%d,%d\n", f.Id, f.Name, f.Ram, f.Disk)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
49
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/07-change-admin-password.go
generated
vendored
Normal file
49
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/07-change-admin-password.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
var serverId = flag.String("i", "", "ID of server whose admin password is to be changed.")
|
||||
var newPass = flag.String("p", "", "New password for the server.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(api gophercloud.CloudServersProvider) {
|
||||
// If user doesn't explicitly provide a server ID, create one dynamically.
|
||||
if *serverId == "" {
|
||||
var err error
|
||||
*serverId, err = createServer(api, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(api, *serverId, "ACTIVE")
|
||||
}
|
||||
|
||||
// If no password is provided, create one dynamically.
|
||||
if *newPass == "" {
|
||||
*newPass = randomString("", 16)
|
||||
}
|
||||
|
||||
// Submit the request for changing the admin password.
|
||||
// Note that we don't verify this actually completes;
|
||||
// doing so is beyond the scope of the SDK, and should be
|
||||
// the responsibility of your specific OpenStack provider.
|
||||
err := api.SetAdminPassword(*serverId, *newPass)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("Password change request submitted.")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
50
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/08-reauthentication.go
generated
vendored
Normal file
50
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/08-reauthentication.go
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance testing. $? non-zero on error though.")
|
||||
var rgn = flag.String("r", "", "Datacenter region to interrogate. Leave blank for provider-default region.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Invoke withIdentity such that re-auth is enabled.
|
||||
withIdentity(true, func(auth gophercloud.AccessProvider) {
|
||||
token1 := auth.AuthToken()
|
||||
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
// Just to confirm everything works, we should be able to list images without error.
|
||||
_, err := servers.ListImages()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Revoke our current authentication token.
|
||||
auth.Revoke(auth.AuthToken())
|
||||
|
||||
// Attempt to list images again. This should _succeed_, because we enabled re-authentication.
|
||||
_, err = servers.ListImages()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// However, our new authentication token should differ.
|
||||
token2 := auth.AuthToken()
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("Old authentication token: ", token1)
|
||||
fmt.Println("New authentication token: ", token2)
|
||||
}
|
||||
|
||||
if token1 == token2 {
|
||||
panic("Tokens should differ")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
102
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/09-resize-server.go
generated
vendored
Normal file
102
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/09-resize-server.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"time"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(api gophercloud.CloudServersProvider) {
|
||||
// These tests are going to take some time to complete.
|
||||
// So, we'll do two tests at the same time to help amortize test time.
|
||||
done := make(chan bool)
|
||||
go resizeRejectTest(api, done)
|
||||
go resizeAcceptTest(api, done)
|
||||
_ = <-done
|
||||
_ = <-done
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("Done.")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Perform the resize test, but reject the resize request.
|
||||
func resizeRejectTest(api gophercloud.CloudServersProvider, done chan bool) {
|
||||
withServer(api, func(id string) {
|
||||
newFlavorId := findAlternativeFlavor()
|
||||
err := api.ResizeServer(id, randomString("ACPTTEST", 24), newFlavorId, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
waitForServerState(api, id, "VERIFY_RESIZE")
|
||||
|
||||
err = api.RevertResize(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
done <- true
|
||||
}
|
||||
|
||||
// Perform the resize test, but accept the resize request.
|
||||
func resizeAcceptTest(api gophercloud.CloudServersProvider, done chan bool) {
|
||||
withServer(api, func(id string) {
|
||||
newFlavorId := findAlternativeFlavor()
|
||||
err := api.ResizeServer(id, randomString("ACPTTEST", 24), newFlavorId, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
waitForServerState(api, id, "VERIFY_RESIZE")
|
||||
|
||||
err = api.ConfirmResize(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
done <- true
|
||||
}
|
||||
|
||||
func withServer(api gophercloud.CloudServersProvider, f func(string)) {
|
||||
id, err := createServer(api, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for {
|
||||
s, err := api.ServerById(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if s.Status == "ACTIVE" {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
|
||||
f(id)
|
||||
|
||||
// I've learned that resizing an instance can fail if a delete request
|
||||
// comes in prior to its completion. This ends up leaving the server
|
||||
// in an error state, and neither the resize NOR the delete complete.
|
||||
// This is a bug in OpenStack, as far as I'm concerned, but thankfully,
|
||||
// there's an easy work-around -- just wait for your server to return to
|
||||
// active state first!
|
||||
waitForServerState(api, id, "ACTIVE")
|
||||
err = api.DeleteServerById(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
45
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/10-reboot-server.go
generated
vendored
Normal file
45
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/10-reboot-server.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(servers gophercloud.CloudServersProvider) {
|
||||
log("Creating server")
|
||||
serverId, err := createServer(servers, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(servers, serverId, "ACTIVE")
|
||||
|
||||
log("Soft-rebooting server")
|
||||
servers.RebootServer(serverId, false)
|
||||
waitForServerState(servers, serverId, "REBOOT")
|
||||
waitForServerState(servers, serverId, "ACTIVE")
|
||||
|
||||
log("Hard-rebooting server")
|
||||
servers.RebootServer(serverId, true)
|
||||
waitForServerState(servers, serverId, "HARD_REBOOT")
|
||||
waitForServerState(servers, serverId, "ACTIVE")
|
||||
|
||||
log("Done")
|
||||
servers.DeleteServerById(serverId)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func log(s string) {
|
||||
if !*quiet {
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
52
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/11-rescue-unrescue-server.go
generated
vendored
Normal file
52
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/11-rescue-unrescue-server.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(servers gophercloud.CloudServersProvider) {
|
||||
log("Creating server")
|
||||
id, err := createServer(servers, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(servers, id, "ACTIVE")
|
||||
defer servers.DeleteServerById(id)
|
||||
|
||||
log("Rescuing server")
|
||||
adminPass, err := servers.RescueServer(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log(" Admin password = " + adminPass)
|
||||
if len(adminPass) < 1 {
|
||||
panic("Empty admin password")
|
||||
}
|
||||
waitForServerState(servers, id, "RESCUE")
|
||||
|
||||
log("Unrescuing server")
|
||||
err = servers.UnrescueServer(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(servers, id, "ACTIVE")
|
||||
|
||||
log("Done")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func log(s string) {
|
||||
if !*quiet {
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
46
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/12-update-server.go
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/12-update-server.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(servers gophercloud.CloudServersProvider) {
|
||||
log("Creating server")
|
||||
id, err := createServer(servers, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(servers, id, "ACTIVE")
|
||||
defer servers.DeleteServerById(id)
|
||||
|
||||
log("Updating name of server")
|
||||
newName := randomString("ACPTTEST", 32)
|
||||
newDetails, err := servers.UpdateServer(id, gophercloud.NewServerSettings{
|
||||
Name: newName,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if newDetails.Name != newName {
|
||||
panic("Name change didn't appear to take")
|
||||
}
|
||||
|
||||
log("Done")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func log(s string) {
|
||||
if !*quiet {
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
46
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/13-rebuild-server.go
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/13-rebuild-server.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(servers gophercloud.CloudServersProvider) {
|
||||
log("Creating server")
|
||||
id, err := createServer(servers, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(servers, id, "ACTIVE")
|
||||
defer servers.DeleteServerById(id)
|
||||
|
||||
log("Rebuilding server")
|
||||
newDetails, err := servers.RebuildServer(id, gophercloud.NewServer{
|
||||
Name: randomString("ACPTTEST", 32),
|
||||
ImageRef: findAlternativeImage(),
|
||||
FlavorRef: findAlternativeFlavor(),
|
||||
AdminPass: randomString("", 16),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(servers, newDetails.Id, "ACTIVE")
|
||||
|
||||
log("Done")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func log(s string) {
|
||||
if !*quiet {
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
66
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/14-list-addresses.go
generated
vendored
Normal file
66
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/14-list-addresses.go
generated
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(api gophercloud.CloudServersProvider) {
|
||||
log("Creating server")
|
||||
id, err := createServer(api, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(api, id, "ACTIVE")
|
||||
defer api.DeleteServerById(id)
|
||||
|
||||
tryAllAddresses(id, api)
|
||||
tryAddressesByNetwork("private", id, api)
|
||||
|
||||
log("Done")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func tryAllAddresses(id string, api gophercloud.CloudServersProvider) {
|
||||
log("Getting list of all addresses...")
|
||||
addresses, err := api.ListAddresses(id)
|
||||
if (err != nil) && (err != gophercloud.WarnUnauthoritative) {
|
||||
panic(err)
|
||||
}
|
||||
if err == gophercloud.WarnUnauthoritative {
|
||||
log("Uh oh -- got a response back, but it's not authoritative for some reason.")
|
||||
}
|
||||
if !*quiet {
|
||||
fmt.Println("Addresses:")
|
||||
fmt.Printf("%+v\n", addresses)
|
||||
}
|
||||
}
|
||||
|
||||
func tryAddressesByNetwork(networkLabel string, id string, api gophercloud.CloudServersProvider) {
|
||||
log("Getting list of addresses on", networkLabel, "network...")
|
||||
network, err := api.ListAddressesByNetwork(id, networkLabel)
|
||||
if (err != nil) && (err != gophercloud.WarnUnauthoritative) {
|
||||
panic(err)
|
||||
}
|
||||
if err == gophercloud.WarnUnauthoritative {
|
||||
log("Uh oh -- got a response back, but it's not authoritative for some reason.")
|
||||
}
|
||||
for _, addr := range network[networkLabel] {
|
||||
log("Address:", addr.Addr, " IPv", addr.Version)
|
||||
}
|
||||
}
|
||||
|
||||
func log(s ...interface{}) {
|
||||
if !*quiet {
|
||||
fmt.Println(s...)
|
||||
}
|
||||
}
|
32
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/15-list-keypairs.go
generated
vendored
Normal file
32
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/15-list-keypairs.go
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance testing. $? non-zero on error though.")
|
||||
var rgn = flag.String("r", "", "Datacenter region to interrogate. Leave blank for provider-default region.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
keypairs, err := servers.ListKeyPairs()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Println("name,fingerprint,publickey")
|
||||
for _, key := range keypairs {
|
||||
fmt.Printf("%s,%s,%s\n", key.Name, key.FingerPrint, key.PublicKey)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
45
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/16-create-delete-keypair.go
generated
vendored
Normal file
45
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/16-create-delete-keypair.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance testing. $? non-zero on error though.")
|
||||
var rgn = flag.String("r", "", "Datacenter region to interrogate. Leave blank for provider-default region.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
name := randomString("ACPTTEST", 16)
|
||||
kp := gophercloud.NewKeyPair{
|
||||
Name: name,
|
||||
}
|
||||
keypair, err := servers.CreateKeyPair(kp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !*quiet {
|
||||
fmt.Printf("%s,%s,%s\n", keypair.Name, keypair.FingerPrint, keypair.PublicKey)
|
||||
}
|
||||
|
||||
keypair, err = servers.ShowKeyPair(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !*quiet {
|
||||
fmt.Printf("%s,%s,%s\n", keypair.Name, keypair.FingerPrint, keypair.PublicKey)
|
||||
}
|
||||
|
||||
err = servers.DeleteKeyPair(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
52
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/17-create-delete-image.go
generated
vendored
Normal file
52
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/17-create-delete-image.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance testing. $? non-zero on error though.")
|
||||
var rgn = flag.String("r", "", "Datacenter region to interrogate. Leave blank for provider-default region.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
log("Creating server")
|
||||
serverId, err := createServer(servers, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(servers, serverId, "ACTIVE")
|
||||
|
||||
log("Creating image")
|
||||
name := randomString("ACPTTEST", 16)
|
||||
createImage := gophercloud.CreateImage{
|
||||
Name: name,
|
||||
}
|
||||
imageId, err := servers.CreateImage(serverId, createImage)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForImageState(servers, imageId, "ACTIVE")
|
||||
|
||||
log("Deleting server")
|
||||
servers.DeleteServerById(serverId)
|
||||
|
||||
log("Deleting image")
|
||||
servers.DeleteImageById(imageId)
|
||||
|
||||
log("Done")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func log(s string) {
|
||||
if !*quiet {
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
19
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/18-osutil-authentication.go
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/18-osutil-authentication.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/osutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
provider, authOptions, err := osutil.AuthOptions()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = gophercloud.Authenticate(provider, authOptions)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
58
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/19-list-addresses-0.1.go
generated
vendored
Normal file
58
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/19-list-addresses-0.1.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing. $? still indicates errors though.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
withIdentity(false, func(acc gophercloud.AccessProvider) {
|
||||
withServerApi(acc, func(api gophercloud.CloudServersProvider) {
|
||||
log("Creating server")
|
||||
id, err := createServer(api, "", "", "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
waitForServerState(api, id, "ACTIVE")
|
||||
defer api.DeleteServerById(id)
|
||||
|
||||
tryAllAddresses(id, api)
|
||||
|
||||
log("Done")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func tryAllAddresses(id string, api gophercloud.CloudServersProvider) {
|
||||
log("Getting the server instance")
|
||||
s, err := api.ServerById(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log("Getting the complete set of pools")
|
||||
ps, err := s.AllAddressPools()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log("Listing IPs for each pool")
|
||||
for k, v := range ps {
|
||||
log(fmt.Sprintf(" Pool %s", k))
|
||||
for _, a := range v {
|
||||
log(fmt.Sprintf(" IP: %s, Version: %d", a.Addr, a.Version))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func log(s ...interface{}) {
|
||||
if !*quiet {
|
||||
fmt.Println(s...)
|
||||
}
|
||||
}
|
48
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/99-delete-server.go
generated
vendored
Normal file
48
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/99-delete-server.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
var quiet = flag.Bool("quiet", false, "Quiet operation for acceptance tests. $? non-zero if problem.")
|
||||
var region = flag.String("r", "", "Datacenter region. Leave blank for provider-default region.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
withIdentity(false, func(auth gophercloud.AccessProvider) {
|
||||
withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
|
||||
// Grab a listing of all servers.
|
||||
ss, err := servers.ListServers()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// And for each one that starts with the ACPTTEST prefix, delete it.
|
||||
// These are likely left-overs from previously running acceptance tests.
|
||||
// Note that 04-create-servers.go is intended to leak servers by intention,
|
||||
// so as to test this code. :)
|
||||
n := 0
|
||||
for _, s := range ss {
|
||||
if len(s.Name) < 8 {
|
||||
continue
|
||||
}
|
||||
if s.Name[0:8] == "ACPTTEST" {
|
||||
err := servers.DeleteServerById(s.Id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Printf("%d servers removed.\n", n)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
239
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/libargs.go
generated
vendored
Normal file
239
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/libargs.go
generated
vendored
Normal file
|
@ -0,0 +1,239 @@
|
|||
// +build acceptance,old
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// getCredentials will verify existence of needed credential information
|
||||
// provided through environment variables. This function will not return
|
||||
// if at least one piece of required information is missing.
|
||||
func getCredentials() (provider, username, password, apiKey string) {
|
||||
provider = os.Getenv("SDK_PROVIDER")
|
||||
username = os.Getenv("SDK_USERNAME")
|
||||
password = os.Getenv("SDK_PASSWORD")
|
||||
apiKey = os.Getenv("SDK_API_KEY")
|
||||
var authURL = os.Getenv("OS_AUTH_URL")
|
||||
|
||||
if (provider == "") || (username == "") || (password == "") {
|
||||
fmt.Fprintf(os.Stderr, "One or more of the following environment variables aren't set:\n")
|
||||
fmt.Fprintf(os.Stderr, " SDK_PROVIDER=\"%s\"\n", provider)
|
||||
fmt.Fprintf(os.Stderr, " SDK_USERNAME=\"%s\"\n", username)
|
||||
fmt.Fprintf(os.Stderr, " SDK_PASSWORD=\"%s\"\n", password)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if strings.Contains(provider, "rackspace") && (authURL != "") {
|
||||
provider = authURL + "/v2.0/tokens"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// randomString generates a string of given length, but random content.
|
||||
// All content will be within the ASCII graphic character set.
|
||||
// (Implementation from Even Shaw's contribution on
|
||||
// http://stackoverflow.com/questions/12771930/what-is-the-fastest-way-to-generate-a-long-random-string-in-go).
|
||||
func randomString(prefix string, n int) string {
|
||||
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
var bytes = make([]byte, n)
|
||||
rand.Read(bytes)
|
||||
for i, b := range bytes {
|
||||
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||
}
|
||||
return prefix + string(bytes)
|
||||
}
|
||||
|
||||
// aSuitableImage finds a minimal image for use in dynamically creating servers.
|
||||
// If none can be found, this function will panic.
|
||||
func aSuitableImage(api gophercloud.CloudServersProvider) string {
|
||||
images, err := api.ListImages()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// TODO(sfalvo):
|
||||
// Works for Rackspace, might not work for your provider!
|
||||
// Need to figure out why ListImages() provides 0 values for
|
||||
// Ram and Disk fields.
|
||||
//
|
||||
// Until then, just return Ubuntu 12.04 LTS.
|
||||
for i := 0; i < len(images); i++ {
|
||||
if strings.Contains(images[i].Name, "Ubuntu 12.04 LTS") {
|
||||
return images[i].Id
|
||||
}
|
||||
}
|
||||
panic("Image for Ubuntu 12.04 LTS not found.")
|
||||
}
|
||||
|
||||
// aSuitableFlavor finds the minimum flavor capable of running the test image
|
||||
// chosen by aSuitableImage. If none can be found, this function will panic.
|
||||
func aSuitableFlavor(api gophercloud.CloudServersProvider) string {
|
||||
flavors, err := api.ListFlavors()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// TODO(sfalvo):
|
||||
// Works for Rackspace, might not work for your provider!
|
||||
// Need to figure out why ListFlavors() provides 0 values for
|
||||
// Ram and Disk fields.
|
||||
//
|
||||
// Until then, just return Ubuntu 12.04 LTS.
|
||||
for i := 0; i < len(flavors); i++ {
|
||||
if flavors[i].Id == "2" {
|
||||
return flavors[i].Id
|
||||
}
|
||||
}
|
||||
panic("Flavor 2 (512MB 1-core 20GB machine) not found.")
|
||||
}
|
||||
|
||||
// createServer creates a new server in a manner compatible with acceptance testing.
|
||||
// In particular, it ensures that the name of the server always starts with "ACPTTEST--",
|
||||
// which the delete servers acceptance test relies on to identify servers to delete.
|
||||
// Passing in empty image and flavor references will force the use of reasonable defaults.
|
||||
// An empty name string will result in a dynamically created name prefixed with "ACPTTEST--".
|
||||
// A blank admin password will cause a password to be automatically generated; however,
|
||||
// at present no means of recovering this password exists, as no acceptance tests yet require
|
||||
// this data.
|
||||
func createServer(servers gophercloud.CloudServersProvider, imageRef, flavorRef, name, adminPass string) (string, error) {
|
||||
if imageRef == "" {
|
||||
imageRef = aSuitableImage(servers)
|
||||
}
|
||||
|
||||
if flavorRef == "" {
|
||||
flavorRef = aSuitableFlavor(servers)
|
||||
}
|
||||
|
||||
if len(name) < 1 {
|
||||
name = randomString("ACPTTEST", 16)
|
||||
}
|
||||
|
||||
if (len(name) < 8) || (name[0:8] != "ACPTTEST") {
|
||||
name = fmt.Sprintf("ACPTTEST--%s", name)
|
||||
}
|
||||
|
||||
newServer, err := servers.CreateServer(gophercloud.NewServer{
|
||||
Name: name,
|
||||
ImageRef: imageRef,
|
||||
FlavorRef: flavorRef,
|
||||
AdminPass: adminPass,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return newServer.Id, nil
|
||||
}
|
||||
|
||||
// findAlternativeFlavor locates a flavor to resize a server to. It is guaranteed to be different
|
||||
// than what aSuitableFlavor() returns. If none could be found, this function will panic.
|
||||
func findAlternativeFlavor() string {
|
||||
return "3" // 1GB image, up from 512MB image
|
||||
}
|
||||
|
||||
// findAlternativeImage locates an image to resize or rebuild a server with. It is guaranteed to be
|
||||
// different than what aSuitableImage() returns. If none could be found, this function will panic.
|
||||
func findAlternativeImage() string {
|
||||
return "c6f9c411-e708-4952-91e5-62ded5ea4d3e"
|
||||
}
|
||||
|
||||
// withIdentity authenticates the user against the provider's identity service, and provides an
|
||||
// accessor for additional services.
|
||||
func withIdentity(ar bool, f func(gophercloud.AccessProvider)) {
|
||||
_, _, _, apiKey := getCredentials()
|
||||
if len(apiKey) == 0 {
|
||||
withPasswordIdentity(ar, f)
|
||||
} else {
|
||||
withAPIKeyIdentity(ar, f)
|
||||
}
|
||||
}
|
||||
|
||||
func withPasswordIdentity(ar bool, f func(gophercloud.AccessProvider)) {
|
||||
provider, username, password, _ := getCredentials()
|
||||
acc, err := gophercloud.Authenticate(
|
||||
provider,
|
||||
gophercloud.AuthOptions{
|
||||
Username: username,
|
||||
Password: password,
|
||||
AllowReauth: ar,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
f(acc)
|
||||
}
|
||||
|
||||
func withAPIKeyIdentity(ar bool, f func(gophercloud.AccessProvider)) {
|
||||
provider, username, _, apiKey := getCredentials()
|
||||
acc, err := gophercloud.Authenticate(
|
||||
provider,
|
||||
gophercloud.AuthOptions{
|
||||
Username: username,
|
||||
ApiKey: apiKey,
|
||||
AllowReauth: ar,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
f(acc)
|
||||
}
|
||||
|
||||
// withServerApi acquires the cloud servers API.
|
||||
func withServerApi(acc gophercloud.AccessProvider, f func(gophercloud.CloudServersProvider)) {
|
||||
api, err := gophercloud.ServersApi(acc, gophercloud.ApiCriteria{
|
||||
Name: "cloudServersOpenStack",
|
||||
VersionId: "2",
|
||||
UrlChoice: gophercloud.PublicURL,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
f(api)
|
||||
}
|
||||
|
||||
// waitForServerState polls, every 10 seconds, for a given server to appear in the indicated state.
|
||||
// This call will block forever if it never appears in the desired state, so if a timeout is required,
|
||||
// make sure to call this function in a goroutine.
|
||||
func waitForServerState(api gophercloud.CloudServersProvider, id, state string) error {
|
||||
for {
|
||||
s, err := api.ServerById(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.Status == state {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
panic("Impossible")
|
||||
}
|
||||
|
||||
// waitForImageState polls, every 10 seconds, for a given image to appear in the indicated state.
|
||||
// This call will block forever if it never appears in the desired state, so if a timeout is required,
|
||||
// make sure to call this function in a goroutine.
|
||||
func waitForImageState(api gophercloud.CloudServersProvider, id, state string) error {
|
||||
for {
|
||||
s, err := api.ImageById(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.Status == state {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
panic("Impossible")
|
||||
}
|
49
Godeps/_workspace/src/github.com/rackspace/gophercloud/api_fetch.go
generated
vendored
Normal file
49
Godeps/_workspace/src/github.com/rackspace/gophercloud/api_fetch.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
package gophercloud
|
||||
|
||||
import(
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
//The default generic openstack api
|
||||
var OpenstackApi = map[string]interface{}{
|
||||
"Type": "compute",
|
||||
"UrlChoice": PublicURL,
|
||||
}
|
||||
|
||||
// Api for use with rackspace
|
||||
var RackspaceApi = map[string]interface{}{
|
||||
"Name": "cloudServersOpenStack",
|
||||
"VersionId": "2",
|
||||
"UrlChoice": PublicURL,
|
||||
}
|
||||
|
||||
|
||||
//Populates an ApiCriteria struct with the api values
|
||||
//from one of the api maps
|
||||
func PopulateApi(variant string) (ApiCriteria, error){
|
||||
var Api ApiCriteria
|
||||
var variantMap map[string]interface{}
|
||||
|
||||
switch variant {
|
||||
case "":
|
||||
variantMap = OpenstackApi
|
||||
|
||||
case "openstack":
|
||||
variantMap = OpenstackApi
|
||||
|
||||
case "rackspace":
|
||||
variantMap = RackspaceApi
|
||||
|
||||
default:
|
||||
var err = fmt.Errorf(
|
||||
"PopulateApi: Unknown variant %# v; legal values: \"openstack\", \"rackspace\"", variant)
|
||||
return Api, err
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(variantMap,&Api)
|
||||
if err != nil{
|
||||
return Api,err
|
||||
}
|
||||
return Api, err
|
||||
}
|
257
Godeps/_workspace/src/github.com/rackspace/gophercloud/authenticate.go
generated
vendored
Normal file
257
Godeps/_workspace/src/github.com/rackspace/gophercloud/authenticate.go
generated
vendored
Normal file
|
@ -0,0 +1,257 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
// AuthOptions lets anyone calling Authenticate() supply the required access credentials.
|
||||
// At present, only Identity V2 API support exists; therefore, only Username, Password,
|
||||
// and optionally, TenantId are provided. If future Identity API versions become available,
|
||||
// alternative fields unique to those versions may appear here.
|
||||
type AuthOptions struct {
|
||||
// Username and Password are required if using Identity V2 API.
|
||||
// Consult with your provider's control panel to discover your
|
||||
// account's username and password.
|
||||
Username, Password string
|
||||
|
||||
// ApiKey used for providers that support Api Key authentication
|
||||
ApiKey string
|
||||
|
||||
// The TenantId field is optional for the Identity V2 API.
|
||||
TenantId string
|
||||
|
||||
// The TenantName can be specified instead of the TenantId
|
||||
TenantName string
|
||||
|
||||
// AllowReauth should be set to true if you grant permission for Gophercloud to cache
|
||||
// your credentials in memory, and to allow Gophercloud to attempt to re-authenticate
|
||||
// automatically if/when your token expires. If you set it to false, it will not cache
|
||||
// these settings, but re-authentication will not be possible. This setting defaults
|
||||
// to false.
|
||||
AllowReauth bool
|
||||
}
|
||||
|
||||
// AuthContainer provides a JSON encoding wrapper for passing credentials to the Identity
|
||||
// service. You will not work with this structure directly.
|
||||
type AuthContainer struct {
|
||||
Auth Auth `json:"auth"`
|
||||
}
|
||||
|
||||
// Auth provides a JSON encoding wrapper for passing credentials to the Identity
|
||||
// service. You will not work with this structure directly.
|
||||
type Auth struct {
|
||||
PasswordCredentials *PasswordCredentials `json:"passwordCredentials,omitempty"`
|
||||
ApiKeyCredentials *ApiKeyCredentials `json:"RAX-KSKEY:apiKeyCredentials,omitempty"`
|
||||
TenantId string `json:"tenantId,omitempty"`
|
||||
TenantName string `json:"tenantName,omitempty"`
|
||||
}
|
||||
|
||||
// PasswordCredentials provides a JSON encoding wrapper for passing credentials to the Identity
|
||||
// service. You will not work with this structure directly.
|
||||
type PasswordCredentials struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type ApiKeyCredentials struct {
|
||||
Username string `json:"username"`
|
||||
ApiKey string `json:"apiKey"`
|
||||
}
|
||||
|
||||
// Access encapsulates the API token and its relevant fields, as well as the
|
||||
// services catalog that Identity API returns once authenticated.
|
||||
type Access struct {
|
||||
Token Token
|
||||
ServiceCatalog []CatalogEntry
|
||||
User User
|
||||
provider Provider `json:"-"`
|
||||
options AuthOptions `json:"-"`
|
||||
context *Context `json:"-"`
|
||||
}
|
||||
|
||||
// Token encapsulates an authentication token and when it expires. It also includes
|
||||
// tenant information if available.
|
||||
type Token struct {
|
||||
Id, Expires string
|
||||
Tenant Tenant
|
||||
}
|
||||
|
||||
// Tenant encapsulates tenant authentication information. If, after authentication,
|
||||
// no tenant information is supplied, both Id and Name will be "".
|
||||
type Tenant struct {
|
||||
Id, Name string
|
||||
}
|
||||
|
||||
// User encapsulates the user credentials, and provides visibility in what
|
||||
// the user can do through its role assignments.
|
||||
type User struct {
|
||||
Id, Name string
|
||||
XRaxDefaultRegion string `json:"RAX-AUTH:defaultRegion"`
|
||||
Roles []Role
|
||||
}
|
||||
|
||||
// Role encapsulates a permission that a user can rely on.
|
||||
type Role struct {
|
||||
Description, Id, Name string
|
||||
}
|
||||
|
||||
// CatalogEntry encapsulates a service catalog record.
|
||||
type CatalogEntry struct {
|
||||
Name, Type string
|
||||
Endpoints []EntryEndpoint
|
||||
}
|
||||
|
||||
// EntryEndpoint encapsulates how to get to the API of some service.
|
||||
type EntryEndpoint struct {
|
||||
Region, TenantId string
|
||||
PublicURL, InternalURL string
|
||||
VersionId, VersionInfo, VersionList string
|
||||
}
|
||||
|
||||
type AuthError struct {
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
func (ae *AuthError) Error() string {
|
||||
switch ae.StatusCode {
|
||||
case 401:
|
||||
return "Auth failed. Bad credentials."
|
||||
|
||||
default:
|
||||
return fmt.Sprintf("Auth failed. Status code is: %s.", ae.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
func getAuthCredentials(options AuthOptions) Auth {
|
||||
if options.ApiKey == "" {
|
||||
return Auth{
|
||||
PasswordCredentials: &PasswordCredentials{
|
||||
Username: options.Username,
|
||||
Password: options.Password,
|
||||
},
|
||||
TenantId: options.TenantId,
|
||||
TenantName: options.TenantName,
|
||||
}
|
||||
} else {
|
||||
return Auth{
|
||||
ApiKeyCredentials: &ApiKeyCredentials{
|
||||
Username: options.Username,
|
||||
ApiKey: options.ApiKey,
|
||||
},
|
||||
TenantId: options.TenantId,
|
||||
TenantName: options.TenantName,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// papersPlease contains the common logic between authentication and re-authentication.
|
||||
// The name, obviously a joke on the process of authentication, was chosen because
|
||||
// of how many other entities exist in the program containing the word Auth or Authorization.
|
||||
// I didn't need another one.
|
||||
func (c *Context) papersPlease(p Provider, options AuthOptions) (*Access, error) {
|
||||
var access *Access
|
||||
access = new(Access)
|
||||
|
||||
if (options.Username == "") || (options.Password == "" && options.ApiKey == "") {
|
||||
return nil, ErrCredentials
|
||||
}
|
||||
|
||||
resp, err := perigee.Request("POST", p.AuthEndpoint, perigee.Options{
|
||||
CustomClient: c.httpClient,
|
||||
ReqBody: &AuthContainer{
|
||||
Auth: getAuthCredentials(options),
|
||||
},
|
||||
Results: &struct {
|
||||
Access **Access `json:"access"`
|
||||
}{
|
||||
&access,
|
||||
},
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
switch resp.StatusCode {
|
||||
case 200:
|
||||
access.options = options
|
||||
access.provider = p
|
||||
access.context = c
|
||||
|
||||
default:
|
||||
err = &AuthError {
|
||||
StatusCode: resp.StatusCode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return access, err
|
||||
}
|
||||
|
||||
// Authenticate() grants access to the OpenStack-compatible provider API.
|
||||
//
|
||||
// Providers are identified through a unique key string.
|
||||
// See the RegisterProvider() method for more details.
|
||||
//
|
||||
// The supplied AuthOptions instance allows the client to specify only those credentials
|
||||
// relevant for the authentication request. At present, support exists for OpenStack
|
||||
// Identity V2 API only; support for V3 will become available as soon as documentation for it
|
||||
// becomes readily available.
|
||||
//
|
||||
// For Identity V2 API requirements, you must provide at least the Username and Password
|
||||
// options. The TenantId field is optional, and defaults to "".
|
||||
func (c *Context) Authenticate(provider string, options AuthOptions) (*Access, error) {
|
||||
p, err := c.ProviderByName(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.papersPlease(p, options)
|
||||
}
|
||||
|
||||
// Reauthenticate attempts to reauthenticate using the configured access credentials, if
|
||||
// allowed. This method takes no action unless your AuthOptions has the AllowReauth flag
|
||||
// set to true.
|
||||
func (a *Access) Reauthenticate() error {
|
||||
var other *Access
|
||||
var err error
|
||||
|
||||
if a.options.AllowReauth {
|
||||
other, err = a.context.papersPlease(a.provider, a.options)
|
||||
if err == nil {
|
||||
*a = *other
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// See AccessProvider interface definition for details.
|
||||
func (a *Access) FirstEndpointUrlByCriteria(ac ApiCriteria) string {
|
||||
ep := FindFirstEndpointByCriteria(a.ServiceCatalog, ac)
|
||||
urls := []string{ep.PublicURL, ep.InternalURL}
|
||||
return urls[ac.UrlChoice]
|
||||
}
|
||||
|
||||
// See AccessProvider interface definition for details.
|
||||
func (a *Access) AuthToken() string {
|
||||
return a.Token.Id
|
||||
}
|
||||
|
||||
// See AccessProvider interface definition for details.
|
||||
func (a *Access) Revoke(tok string) error {
|
||||
url := a.provider.AuthEndpoint + "/" + tok
|
||||
err := perigee.Delete(url, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": a.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{204},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// See ServiceCatalogerForIdentityV2 interface definition for details.
|
||||
// Note that the raw slice is returend; be careful not to alter the fields of any members,
|
||||
// for other components of Gophercloud may depend upon them.
|
||||
// If this becomes a problem in the future,
|
||||
// a future revision may return a deep-copy of the service catalog instead.
|
||||
func (a *Access) V2ServiceCatalog() []CatalogEntry {
|
||||
return a.ServiceCatalog
|
||||
}
|
264
Godeps/_workspace/src/github.com/rackspace/gophercloud/authenticate_test.go
generated
vendored
Normal file
264
Godeps/_workspace/src/github.com/rackspace/gophercloud/authenticate_test.go
generated
vendored
Normal file
|
@ -0,0 +1,264 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const SUCCESSFUL_RESPONSE = `{
|
||||
"access": {
|
||||
"serviceCatalog": [{
|
||||
"endpoints": [{
|
||||
"publicURL": "https://ord.servers.api.rackspacecloud.com/v2/12345",
|
||||
"region": "ORD",
|
||||
"tenantId": "12345",
|
||||
"versionId": "2",
|
||||
"versionInfo": "https://ord.servers.api.rackspacecloud.com/v2",
|
||||
"versionList": "https://ord.servers.api.rackspacecloud.com/"
|
||||
},{
|
||||
"publicURL": "https://dfw.servers.api.rackspacecloud.com/v2/12345",
|
||||
"region": "DFW",
|
||||
"tenantId": "12345",
|
||||
"versionId": "2",
|
||||
"versionInfo": "https://dfw.servers.api.rackspacecloud.com/v2",
|
||||
"versionList": "https://dfw.servers.api.rackspacecloud.com/"
|
||||
}],
|
||||
"name": "cloudServersOpenStack",
|
||||
"type": "compute"
|
||||
},{
|
||||
"endpoints": [{
|
||||
"publicURL": "https://ord.databases.api.rackspacecloud.com/v1.0/12345",
|
||||
"region": "ORD",
|
||||
"tenantId": "12345"
|
||||
}],
|
||||
"name": "cloudDatabases",
|
||||
"type": "rax:database"
|
||||
}],
|
||||
"token": {
|
||||
"expires": "2012-04-13T13:15:00.000-05:00",
|
||||
"id": "aaaaa-bbbbb-ccccc-dddd"
|
||||
},
|
||||
"user": {
|
||||
"RAX-AUTH:defaultRegion": "DFW",
|
||||
"id": "161418",
|
||||
"name": "demoauthor",
|
||||
"roles": [{
|
||||
"description": "User Admin Role.",
|
||||
"id": "3",
|
||||
"name": "identity:user-admin"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func TestAuthProvider(t *testing.T) {
|
||||
tt := newTransport().WithResponse(SUCCESSFUL_RESPONSE)
|
||||
c := TestContext().UseCustomClient(&http.Client{
|
||||
Transport: tt,
|
||||
})
|
||||
|
||||
_, err := c.Authenticate("", AuthOptions{})
|
||||
if err == nil {
|
||||
t.Error("Expected error for empty provider string")
|
||||
return
|
||||
}
|
||||
_, err = c.Authenticate("unknown-provider", AuthOptions{Username: "u", Password: "p"})
|
||||
if err == nil {
|
||||
t.Error("Expected error for unknown service provider")
|
||||
return
|
||||
}
|
||||
|
||||
err = c.RegisterProvider("provider", Provider{AuthEndpoint: "/"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
_, err = c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if tt.called != 1 {
|
||||
t.Error("Expected transport to be called once.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestTenantIdEncoding(t *testing.T) {
|
||||
tt := newTransport().WithResponse(SUCCESSFUL_RESPONSE)
|
||||
c := TestContext().
|
||||
UseCustomClient(&http.Client{
|
||||
Transport: tt,
|
||||
}).
|
||||
WithProvider("provider", Provider{AuthEndpoint: "/"})
|
||||
|
||||
tt.IgnoreTenantId()
|
||||
_, err := c.Authenticate("provider", AuthOptions{
|
||||
Username: "u",
|
||||
Password: "p",
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if tt.tenantIdFound {
|
||||
t.Error("Tenant ID should not have been encoded")
|
||||
return
|
||||
}
|
||||
|
||||
tt.ExpectTenantId()
|
||||
_, err = c.Authenticate("provider", AuthOptions{
|
||||
Username: "u",
|
||||
Password: "p",
|
||||
TenantId: "t",
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if !tt.tenantIdFound {
|
||||
t.Error("Tenant ID should have been encoded")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserNameAndPassword(t *testing.T) {
|
||||
c := TestContext().
|
||||
WithProvider("provider", Provider{AuthEndpoint: "http://localhost/"}).
|
||||
UseCustomClient(&http.Client{Transport: newTransport().WithResponse(SUCCESSFUL_RESPONSE)})
|
||||
|
||||
credentials := []AuthOptions{
|
||||
{},
|
||||
{Username: "u"},
|
||||
{Password: "p"},
|
||||
}
|
||||
for i, auth := range credentials {
|
||||
_, err := c.Authenticate("provider", auth)
|
||||
if err == nil {
|
||||
t.Error("Expected error from missing credentials (%d)", i)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserNameAndApiKey(t *testing.T) {
|
||||
c := TestContext().
|
||||
WithProvider("provider", Provider{AuthEndpoint: "http://localhost/"}).
|
||||
UseCustomClient(&http.Client{Transport: newTransport().WithResponse(SUCCESSFUL_RESPONSE)})
|
||||
|
||||
credentials := []AuthOptions{
|
||||
{},
|
||||
{Username: "u"},
|
||||
{ApiKey: "a"},
|
||||
}
|
||||
for i, auth := range credentials {
|
||||
_, err := c.Authenticate("provider", auth)
|
||||
if err == nil {
|
||||
t.Error("Expected error from missing credentials (%d)", i)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, err := c.Authenticate("provider", AuthOptions{Username: "u", ApiKey: "a"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenAcquisition(t *testing.T) {
|
||||
c := TestContext().
|
||||
UseCustomClient(&http.Client{Transport: newTransport().WithResponse(SUCCESSFUL_RESPONSE)}).
|
||||
WithProvider("provider", Provider{AuthEndpoint: "http://localhost/"})
|
||||
|
||||
acc, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
tok := acc.Token
|
||||
if (tok.Id == "") || (tok.Expires == "") {
|
||||
t.Error("Expected a valid token for successful login; got %s, %s", tok.Id, tok.Expires)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceCatalogAcquisition(t *testing.T) {
|
||||
c := TestContext().
|
||||
UseCustomClient(&http.Client{Transport: newTransport().WithResponse(SUCCESSFUL_RESPONSE)}).
|
||||
WithProvider("provider", Provider{AuthEndpoint: "http://localhost/"})
|
||||
|
||||
acc, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
svcs := acc.ServiceCatalog
|
||||
if len(svcs) < 2 {
|
||||
t.Error("Expected 2 service catalog entries; got %d", len(svcs))
|
||||
return
|
||||
}
|
||||
|
||||
types := map[string]bool{
|
||||
"compute": true,
|
||||
"rax:database": true,
|
||||
}
|
||||
for _, entry := range svcs {
|
||||
if !types[entry.Type] {
|
||||
t.Error("Expected to find type %s.", entry.Type)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserAcquisition(t *testing.T) {
|
||||
c := TestContext().
|
||||
UseCustomClient(&http.Client{Transport: newTransport().WithResponse(SUCCESSFUL_RESPONSE)}).
|
||||
WithProvider("provider", Provider{AuthEndpoint: "http://localhost/"})
|
||||
|
||||
acc, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
u := acc.User
|
||||
if u.Id != "161418" {
|
||||
t.Error("Expected user ID of 16148; got", u.Id)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticationNeverReauths(t *testing.T) {
|
||||
tt := newTransport().WithError(401)
|
||||
c := TestContext().
|
||||
UseCustomClient(&http.Client{Transport: tt}).
|
||||
WithProvider("provider", Provider{AuthEndpoint: "http://localhost"})
|
||||
|
||||
_, err := c.Authenticate("provider", AuthOptions{Username: "u", Password: "p"})
|
||||
if err == nil {
|
||||
t.Error("Expected an error from a 401 Unauthorized response")
|
||||
return
|
||||
}
|
||||
|
||||
rc, _ := ActualResponseCode(err)
|
||||
if rc != 401 {
|
||||
t.Error("Expected a 401 error code")
|
||||
return
|
||||
}
|
||||
|
||||
err = tt.VerifyCalls(t, 1)
|
||||
if err != nil {
|
||||
// Test object already flagged.
|
||||
return
|
||||
}
|
||||
}
|
24
Godeps/_workspace/src/github.com/rackspace/gophercloud/common_types.go
generated
vendored
Normal file
24
Godeps/_workspace/src/github.com/rackspace/gophercloud/common_types.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
package gophercloud
|
||||
|
||||
// Link is used for JSON (un)marshalling.
|
||||
// It provides RESTful links to a resource.
|
||||
type Link struct {
|
||||
Href string `json:"href"`
|
||||
Rel string `json:"rel"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// FileConfig structures represent a blob of data which must appear at a
|
||||
// a specific location in a server's filesystem. The file contents are
|
||||
// base-64 encoded.
|
||||
type FileConfig struct {
|
||||
Path string `json:"path"`
|
||||
Contents string `json:"contents"`
|
||||
}
|
||||
|
||||
// NetworkConfig structures represent an affinity between a server and a
|
||||
// specific, uniquely identified network. Networks are identified through
|
||||
// universally unique IDs.
|
||||
type NetworkConfig struct {
|
||||
Uuid string `json:"uuid"`
|
||||
}
|
150
Godeps/_workspace/src/github.com/rackspace/gophercloud/context.go
generated
vendored
Normal file
150
Godeps/_workspace/src/github.com/rackspace/gophercloud/context.go
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"fmt"
|
||||
"github.com/tonnerre/golang-pretty"
|
||||
)
|
||||
|
||||
// Provider structures exist for each tangible provider of OpenStack service.
|
||||
// For example, Rackspace, Hewlett-Packard, and NASA might have their own instance of this structure.
|
||||
//
|
||||
// At a minimum, a provider must expose an authentication endpoint.
|
||||
type Provider struct {
|
||||
AuthEndpoint string
|
||||
}
|
||||
|
||||
// ReauthHandlerFunc functions are responsible for somehow performing the task of
|
||||
// reauthentication.
|
||||
type ReauthHandlerFunc func(AccessProvider) error
|
||||
|
||||
// Context structures encapsulate Gophercloud-global state in a manner which
|
||||
// facilitates easier unit testing. As a user of this SDK, you'll never
|
||||
// have to use this structure, except when contributing new code to the SDK.
|
||||
type Context struct {
|
||||
// providerMap serves as a directory of supported providers.
|
||||
providerMap map[string]Provider
|
||||
|
||||
// httpClient refers to the current HTTP client interface to use.
|
||||
httpClient *http.Client
|
||||
|
||||
// reauthHandler provides the functionality needed to re-authenticate
|
||||
// if that feature is enabled. Note: in order to allow for automatic
|
||||
// re-authentication, the Context object will need to remember your
|
||||
// username, password, and tenant ID as provided in the initial call
|
||||
// to Authenticate(). If you do not desire this, you'll need to handle
|
||||
// reauthentication yourself through other means. Two methods exist:
|
||||
// the first approach is to just handle errors yourself at the application
|
||||
// layer, and the other is through a custom reauthentication handler
|
||||
// set through the WithReauthHandler() method.
|
||||
reauthHandler ReauthHandlerFunc
|
||||
}
|
||||
|
||||
// TestContext yields a new Context instance, pre-initialized with a barren
|
||||
// state suitable for per-unit-test customization. This configuration consists
|
||||
// of:
|
||||
//
|
||||
// * An empty provider map.
|
||||
//
|
||||
// * An HTTP client built by the net/http package (see http://godoc.org/net/http#Client).
|
||||
func TestContext() *Context {
|
||||
return &Context{
|
||||
providerMap: make(map[string]Provider),
|
||||
httpClient: &http.Client{},
|
||||
reauthHandler: func(acc AccessProvider) error {
|
||||
return acc.Reauthenticate()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// UseCustomClient configures the context to use a customized HTTP client
|
||||
// instance. By default, TestContext() will return a Context which uses
|
||||
// the net/http package's default client instance.
|
||||
func (c *Context) UseCustomClient(hc *http.Client) *Context {
|
||||
c.httpClient = hc
|
||||
return c
|
||||
}
|
||||
|
||||
// RegisterProvider allows a unit test to register a mythical provider convenient for testing.
|
||||
// If the provider structure lacks adequate configuration, or the configuration given has some
|
||||
// detectable error, an ErrConfiguration error will result.
|
||||
func (c *Context) RegisterProvider(name string, p Provider) error {
|
||||
if p.AuthEndpoint == "" {
|
||||
return ErrConfiguration
|
||||
}
|
||||
|
||||
c.providerMap[name] = p
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithProvider offers convenience for unit tests.
|
||||
func (c *Context) WithProvider(name string, p Provider) *Context {
|
||||
err := c.RegisterProvider(name, p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// ProviderByName will locate a provider amongst those previously registered, if it exists.
|
||||
// If the named provider has not been registered, an ErrProvider error will result.
|
||||
//
|
||||
// You may also specify a custom Identity API URL.
|
||||
// Any provider name that contains the characters "://", in that order, will be treated as a custom Identity API URL.
|
||||
// Custom URLs, important for private cloud deployments, overrides all provider configurations.
|
||||
func (c *Context) ProviderByName(name string) (p Provider, err error) {
|
||||
for provider, descriptor := range c.providerMap {
|
||||
if name == provider {
|
||||
return descriptor, nil
|
||||
}
|
||||
}
|
||||
if strings.Contains(name, "://") {
|
||||
p = Provider{
|
||||
AuthEndpoint: name,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
return Provider{}, ErrProvider
|
||||
}
|
||||
|
||||
func getServiceCatalogFromAccessProvider(provider AccessProvider) ([]CatalogEntry) {
|
||||
access, found := provider.(*Access)
|
||||
if found {
|
||||
return access.ServiceCatalog
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiates a Cloud Servers API for the provider given.
|
||||
func (c *Context) ServersApi(provider AccessProvider, criteria ApiCriteria) (CloudServersProvider, error) {
|
||||
url := provider.FirstEndpointUrlByCriteria(criteria)
|
||||
if url == "" {
|
||||
var err = fmt.Errorf(
|
||||
"Missing endpoint, or insufficient privileges to access endpoint; criteria = %# v; serviceCatalog = %# v",
|
||||
pretty.Formatter(criteria),
|
||||
pretty.Formatter(getServiceCatalogFromAccessProvider(provider)))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcp := &genericServersProvider{
|
||||
endpoint: url,
|
||||
context: c,
|
||||
access: provider,
|
||||
}
|
||||
|
||||
return gcp, nil
|
||||
}
|
||||
|
||||
// WithReauthHandler configures the context to handle reauthentication attempts using the supplied
|
||||
// funtion. By default, reauthentication happens by invoking Authenticate(), which is unlikely to be
|
||||
// useful in a unit test.
|
||||
//
|
||||
// Do not confuse this function with WithReauth()! Although they work together to support reauthentication,
|
||||
// WithReauth() actually contains the decision-making logic to determine when to perform a reauth,
|
||||
// while WithReauthHandler() is used to configure what a reauth actually entails.
|
||||
func (c *Context) WithReauthHandler(f ReauthHandlerFunc) *Context {
|
||||
c.reauthHandler = f
|
||||
return c
|
||||
}
|
28
Godeps/_workspace/src/github.com/rackspace/gophercloud/context_test.go
generated
vendored
Normal file
28
Godeps/_workspace/src/github.com/rackspace/gophercloud/context_test.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProviderRegistry(t *testing.T) {
|
||||
c := TestContext()
|
||||
|
||||
_, err := c.ProviderByName("aProvider")
|
||||
if err == nil {
|
||||
t.Error("Expected error when looking for a provider by non-existant name")
|
||||
return
|
||||
}
|
||||
|
||||
err = c.RegisterProvider("aProvider", Provider{})
|
||||
if err != ErrConfiguration {
|
||||
t.Error("Unexpected error/nil when registering a provider w/out an auth endpoint\n %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
_ = c.RegisterProvider("aProvider", Provider{AuthEndpoint: "http://localhost/auth"})
|
||||
_, err = c.ProviderByName("aProvider")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrNotImplemented should be used only while developing new SDK features.
|
||||
// No established function or method will ever produce this error.
|
||||
var ErrNotImplemented = fmt.Errorf("Not implemented")
|
||||
|
||||
// ErrProvider errors occur when attempting to reference an unsupported
|
||||
// provider. More often than not, this error happens due to a typo in
|
||||
// the name.
|
||||
var ErrProvider = fmt.Errorf("Missing or incorrect provider")
|
||||
|
||||
// ErrCredentials errors happen when attempting to authenticate using a
|
||||
// set of credentials not recognized by the Authenticate() method.
|
||||
// For example, not providing a username or password when attempting to
|
||||
// authenticate against an Identity V2 API.
|
||||
var ErrCredentials = fmt.Errorf("Missing or incomplete credentials")
|
||||
|
||||
// ErrConfiguration errors happen when attempting to add a new provider, and
|
||||
// the provider added lacks a correct or consistent configuration.
|
||||
// For example, all providers must expose at least an Identity V2 API
|
||||
// for authentication; if this endpoint isn't specified, you may receive
|
||||
// this error when attempting to register it against a context.
|
||||
var ErrConfiguration = fmt.Errorf("Missing or incomplete configuration")
|
||||
|
||||
// ErrError errors happen when you attempt to discover the response code
|
||||
// responsible for a previous request bombing with an error, but pass in an
|
||||
// error interface which doesn't belong to the web client.
|
||||
var ErrError = fmt.Errorf("Attempt to solicit actual HTTP response code from error entity which doesn't know")
|
||||
|
||||
// WarnUnauthoritative warnings happen when a service believes its response
|
||||
// to be correct, but is not in a position of knowing for sure at the moment.
|
||||
// For example, the service could be responding with cached data that has
|
||||
// exceeded its time-to-live setting, but which has not yet received an official
|
||||
// update from an authoritative source.
|
||||
var WarnUnauthoritative = fmt.Errorf("Unauthoritative data")
|
|
@ -0,0 +1,55 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
// See CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListFlavors() ([]Flavor, error) {
|
||||
var fs []Flavor
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/flavors/detail"
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
Results: &struct{ Flavors *[]Flavor }{&fs},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return fs, err
|
||||
}
|
||||
|
||||
// FlavorLink provides a reference to a flavor by either ID or by direct URL.
|
||||
// Some services use just the ID, others use just the URL.
|
||||
// This structure provides a common means of expressing both in a single field.
|
||||
type FlavorLink struct {
|
||||
Id string `json:"id"`
|
||||
Links []Link `json:"links"`
|
||||
}
|
||||
|
||||
// Flavor records represent (virtual) hardware configurations for server resources in a region.
|
||||
//
|
||||
// The Id field contains the flavor's unique identifier.
|
||||
// For example, this identifier will be useful when specifying which hardware configuration to use for a new server instance.
|
||||
//
|
||||
// The Disk and Ram fields provide a measure of storage space offered by the flavor, in GB and MB, respectively.
|
||||
//
|
||||
// The Name field provides a human-readable moniker for the flavor.
|
||||
//
|
||||
// Swap indicates how much space is reserved for swap.
|
||||
// If not provided, this field will be set to 0.
|
||||
//
|
||||
// VCpus indicates how many (virtual) CPUs are available for this flavor.
|
||||
type Flavor struct {
|
||||
OsFlvDisabled bool `json:"OS-FLV-DISABLED:disabled"`
|
||||
Disk int `json:"disk"`
|
||||
Id string `json:"id"`
|
||||
Links []Link `json:"links"`
|
||||
Name string `json:"name"`
|
||||
Ram int `json:"ram"`
|
||||
RxTxFactor float64 `json:"rxtx_factor"`
|
||||
Swap int `json:"swap"`
|
||||
VCpus int `json:"vcpus"`
|
||||
}
|
88
Godeps/_workspace/src/github.com/rackspace/gophercloud/floating_ips.go
generated
vendored
Normal file
88
Godeps/_workspace/src/github.com/rackspace/gophercloud/floating_ips.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
func (gsp *genericServersProvider) ListFloatingIps() ([]FloatingIp, error) {
|
||||
var fips []FloatingIp
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/os-floating-ips"
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
Results: &struct {
|
||||
FloatingIps *[]FloatingIp `json:"floating_ips"`
|
||||
}{&fips},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return fips, err
|
||||
}
|
||||
|
||||
func (gsp *genericServersProvider) CreateFloatingIp(pool string) (FloatingIp, error) {
|
||||
fip := new(FloatingIp)
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/os-floating-ips"
|
||||
return perigee.Post(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
ReqBody: map[string]string{
|
||||
"pool": pool,
|
||||
},
|
||||
Results: &struct {
|
||||
FloatingIp **FloatingIp `json:"floating_ip"`
|
||||
}{&fip},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
if fip.Ip == "" {
|
||||
return *fip, errors.New("Error creating floating IP")
|
||||
}
|
||||
|
||||
return *fip, err
|
||||
}
|
||||
|
||||
func (gsp *genericServersProvider) AssociateFloatingIp(serverId string, ip FloatingIp) error {
|
||||
return gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, serverId)
|
||||
return perigee.Post(ep, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
ReqBody: map[string](map[string]string){
|
||||
"addFloatingIp": map[string]string{"address": ip.Ip},
|
||||
},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (gsp *genericServersProvider) DeleteFloatingIp(ip FloatingIp) error {
|
||||
return gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-floating-ips/%d", gsp.endpoint, ip.Id)
|
||||
return perigee.Delete(ep, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type FloatingIp struct {
|
||||
Id int `json:"id"`
|
||||
Pool string `json:"pool"`
|
||||
Ip string `json:"ip"`
|
||||
FixedIp string `json:"fixed_ip"`
|
||||
InstanceId string `json:"instance_id"`
|
||||
}
|
67
Godeps/_workspace/src/github.com/rackspace/gophercloud/global_context.go
generated
vendored
Normal file
67
Godeps/_workspace/src/github.com/rackspace/gophercloud/global_context.go
generated
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
// globalContext is the, well, "global context."
|
||||
// Most of this SDK is written in a manner to facilitate easier testing,
|
||||
// which doesn't require all the configuration a real-world application would require.
|
||||
// However, for real-world deployments, applications should be able to rely on a consistent configuration of providers, etc.
|
||||
var globalContext *Context
|
||||
|
||||
// providers is the set of supported providers.
|
||||
var providers = map[string]Provider{
|
||||
"rackspace-us": {
|
||||
AuthEndpoint: "https://identity.api.rackspacecloud.com/v2.0/tokens",
|
||||
},
|
||||
"rackspace-uk": {
|
||||
AuthEndpoint: "https://lon.identity.api.rackspacecloud.com/v2.0/tokens",
|
||||
},
|
||||
}
|
||||
|
||||
// Initialize the global context to sane configuration.
|
||||
// The Go runtime ensures this function is called before main(),
|
||||
// thus guaranteeing proper configuration before your application ever runs.
|
||||
func init() {
|
||||
globalContext = TestContext()
|
||||
for name, descriptor := range providers {
|
||||
globalContext.RegisterProvider(name, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
// Authenticate() grants access to the OpenStack-compatible provider API.
|
||||
//
|
||||
// Providers are identified through a unique key string.
|
||||
// Specifying an unsupported provider will result in an ErrProvider error.
|
||||
// However, you may also specify a custom Identity API URL.
|
||||
// Any provider name that contains the characters "://", in that order, will be treated as a custom Identity API URL.
|
||||
// Custom URLs, important for private cloud deployments, overrides all provider configurations.
|
||||
//
|
||||
// The supplied AuthOptions instance allows the client to specify only those credentials
|
||||
// relevant for the authentication request. At present, support exists for OpenStack
|
||||
// Identity V2 API only; support for V3 will become available as soon as documentation for it
|
||||
// becomes readily available.
|
||||
//
|
||||
// For Identity V2 API requirements, you must provide at least the Username and Password
|
||||
// options. The TenantId field is optional, and defaults to "".
|
||||
func Authenticate(provider string, options AuthOptions) (*Access, error) {
|
||||
return globalContext.Authenticate(provider, options)
|
||||
}
|
||||
|
||||
// Instantiates a Cloud Servers object for the provider given.
|
||||
func ServersApi(acc AccessProvider, criteria ApiCriteria) (CloudServersProvider, error) {
|
||||
return globalContext.ServersApi(acc, criteria)
|
||||
}
|
||||
|
||||
// ActualResponseCode inspects a returned error, and discovers the actual response actual
|
||||
// response code that caused the error to be raised.
|
||||
func ActualResponseCode(e error) (int, error) {
|
||||
if err, typeOk := e.(*perigee.UnexpectedResponseCodeError); typeOk {
|
||||
return err.Actual, nil
|
||||
} else if err, typeOk := e.(*AuthError); typeOk{
|
||||
return err.StatusCode, nil
|
||||
}
|
||||
|
||||
return 0, ErrError
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
// See the CloudImagesProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListImages() ([]Image, error) {
|
||||
var is []Image
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/images/detail"
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
Results: &struct{ Images *[]Image }{&is},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return is, err
|
||||
}
|
||||
|
||||
func (gsp *genericServersProvider) ImageById(id string) (*Image, error) {
|
||||
var is *Image
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/images/" + id
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
Results: &struct{ Image **Image }{&is},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return is, err
|
||||
}
|
||||
|
||||
func (gsp *genericServersProvider) DeleteImageById(id string) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/images/" + id
|
||||
_, err := perigee.Request("DELETE", url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
return err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// ImageLink provides a reference to a image by either ID or by direct URL.
|
||||
// Some services use just the ID, others use just the URL.
|
||||
// This structure provides a common means of expressing both in a single field.
|
||||
type ImageLink struct {
|
||||
Id string `json:"id"`
|
||||
Links []Link `json:"links"`
|
||||
}
|
||||
|
||||
// Image is used for JSON (un)marshalling.
|
||||
// It provides a description of an OS image.
|
||||
//
|
||||
// The Id field contains the image's unique identifier.
|
||||
// For example, this identifier will be useful for specifying which operating system to install on a new server instance.
|
||||
//
|
||||
// The MinDisk and MinRam fields specify the minimum resources a server must provide to be able to install the image.
|
||||
//
|
||||
// The Name field provides a human-readable moniker for the OS image.
|
||||
//
|
||||
// The Progress and Status fields indicate image-creation status.
|
||||
// Any usable image will have 100% progress.
|
||||
//
|
||||
// The Updated field indicates the last time this image was changed.
|
||||
//
|
||||
// OsDcfDiskConfig indicates the server's boot volume configuration.
|
||||
// Valid values are:
|
||||
// AUTO
|
||||
// ----
|
||||
// The server is built with a single partition the size of the target flavor disk.
|
||||
// The file system is automatically adjusted to fit the entire partition.
|
||||
// This keeps things simple and automated.
|
||||
// AUTO is valid only for images and servers with a single partition that use the EXT3 file system.
|
||||
// This is the default setting for applicable Rackspace base images.
|
||||
//
|
||||
// MANUAL
|
||||
// ------
|
||||
// The server is built using whatever partition scheme and file system is in the source image.
|
||||
// If the target flavor disk is larger,
|
||||
// the remaining disk space is left unpartitioned.
|
||||
// This enables images to have non-EXT3 file systems, multiple partitions, and so on,
|
||||
// and enables you to manage the disk configuration.
|
||||
//
|
||||
type Image struct {
|
||||
Created string `json:"created"`
|
||||
Id string `json:"id"`
|
||||
Links []Link `json:"links"`
|
||||
MinDisk int `json:"minDisk"`
|
||||
MinRam int `json:"minRam"`
|
||||
Name string `json:"name"`
|
||||
Progress int `json:"progress"`
|
||||
Status string `json:"status"`
|
||||
Updated string `json:"updated"`
|
||||
OsDcfDiskConfig string `json:"OS-DCF:diskConfig"`
|
||||
}
|
247
Godeps/_workspace/src/github.com/rackspace/gophercloud/interfaces.go
generated
vendored
Normal file
247
Godeps/_workspace/src/github.com/rackspace/gophercloud/interfaces.go
generated
vendored
Normal file
|
@ -0,0 +1,247 @@
|
|||
package gophercloud
|
||||
|
||||
import "net/url"
|
||||
|
||||
// AccessProvider instances encapsulate a Keystone authentication interface.
|
||||
type AccessProvider interface {
|
||||
// FirstEndpointUrlByCriteria searches through the service catalog for the first
|
||||
// matching entry endpoint fulfilling the provided criteria. If nothing found,
|
||||
// return "". Otherwise, return either the public or internal URL for the
|
||||
// endpoint, depending on both its existence and the setting of the ApiCriteria.UrlChoice
|
||||
// field.
|
||||
FirstEndpointUrlByCriteria(ApiCriteria) string
|
||||
|
||||
// AuthToken provides a copy of the current authentication token for the user's credentials.
|
||||
// Note that AuthToken() will not automatically refresh an expired token.
|
||||
AuthToken() string
|
||||
|
||||
// Revoke allows you to terminate any program's access to the OpenStack API by token ID.
|
||||
Revoke(string) error
|
||||
|
||||
// Reauthenticate attempts to acquire a new authentication token, if the feature is enabled by
|
||||
// AuthOptions.AllowReauth.
|
||||
Reauthenticate() error
|
||||
}
|
||||
|
||||
// ServiceCatalogerIdentityV2 interface provides direct access to the service catalog as offered by the Identity V2 API.
|
||||
// We regret we need to fracture the namespace of what should otherwise be a simple concept; however,
|
||||
// the OpenStack community saw fit to render V3's service catalog completely incompatible with V2.
|
||||
type ServiceCatalogerForIdentityV2 interface {
|
||||
V2ServiceCatalog() []CatalogEntry
|
||||
}
|
||||
|
||||
// CloudServersProvider instances encapsulate a Cloud Servers API, should one exist in the service catalog
|
||||
// for your provider.
|
||||
type CloudServersProvider interface {
|
||||
// Servers
|
||||
|
||||
// ListServers provides a complete list of servers hosted by the user
|
||||
// in a given region. This function differs from ListServersLinksOnly()
|
||||
// in that it returns all available details for each server returned.
|
||||
ListServers() ([]Server, error)
|
||||
|
||||
// ListServersByFilters provides a list of servers hosted by the user in a
|
||||
// given region. This function let you requests servers by certain URI
|
||||
// paramaters defined by the API endpoint. This is sometimes more suitable
|
||||
// if you have many servers and you only want to pick servers on certain
|
||||
// criterias. An example usage could be :
|
||||
//
|
||||
// filter := url.Values{}
|
||||
// filter.Set("name", "MyServer")
|
||||
// filter.Set("status", "ACTIVE")
|
||||
//
|
||||
// filteredServers, err := c.ListServersByFilters(filter)
|
||||
//
|
||||
// Here, filteredServers only contains servers whose name started with
|
||||
// "MyServer" and are in "ACTIVE" status.
|
||||
ListServersByFilter(filter url.Values) ([]Server, error)
|
||||
|
||||
// ListServers provides a complete list of servers hosted by the user
|
||||
// in a given region. This function differs from ListServers() in that
|
||||
// it returns only IDs and links to each server returned.
|
||||
//
|
||||
// This function should be used only under certain circumstances.
|
||||
// It's most useful for checking to see if a server with a given ID exists,
|
||||
// or that you have permission to work with that server. It's also useful
|
||||
// when the cost of retrieving the server link list plus the overhead of manually
|
||||
// invoking ServerById() for each of the servers you're interested in is less than
|
||||
// just calling ListServers() to begin with. This may be a consideration, for
|
||||
// example, with mobile applications.
|
||||
//
|
||||
// In other cases, you probably should just call ListServers() and cache the
|
||||
// results to conserve overall bandwidth and reduce your access rate on the API.
|
||||
ListServersLinksOnly() ([]Server, error)
|
||||
|
||||
// ServerById will retrieve a detailed server description given the unique ID
|
||||
// of a server. The ID can be returned by either ListServers() or by ListServersLinksOnly().
|
||||
ServerById(id string) (*Server, error)
|
||||
|
||||
// CreateServer requests a new server to be created by the cloud server provider.
|
||||
// The user must pass in a pointer to an initialized NewServerContainer structure.
|
||||
// Please refer to the NewServerContainer documentation for more details.
|
||||
//
|
||||
// If the NewServer structure's AdminPass is empty (""), a password will be
|
||||
// automatically generated by your OpenStack provider, and returned through the
|
||||
// AdminPass field of the result. Take care, however; this will be the only time
|
||||
// this happens. No other means exists in the public API to acquire a password
|
||||
// for a pre-existing server. If you lose it, you'll need to call SetAdminPassword()
|
||||
// to set a new one.
|
||||
CreateServer(ns NewServer) (*NewServer, error)
|
||||
|
||||
// DeleteServerById requests that the server with the assigned ID be removed
|
||||
// from your account. The delete happens asynchronously.
|
||||
DeleteServerById(id string) error
|
||||
|
||||
// SetAdminPassword requests that the server with the specified ID have its
|
||||
// administrative password changed. For Linux, BSD, or other POSIX-like
|
||||
// system, this password corresponds to the root user. For Windows machines,
|
||||
// the Administrator password will be affected instead.
|
||||
SetAdminPassword(id string, pw string) error
|
||||
|
||||
// ResizeServer can be a short-hand for RebuildServer where only the size of the server
|
||||
// changes. Note that after the resize operation is requested, you will need to confirm
|
||||
// the resize has completed for changes to take effect permanently. Changes will assume
|
||||
// to be confirmed even without an explicit confirmation after 24 hours from the initial
|
||||
// request.
|
||||
ResizeServer(id, newName, newFlavor, newDiskConfig string) error
|
||||
|
||||
// RevertResize will reject a server's resized configuration, thus
|
||||
// rolling back to the original server.
|
||||
RevertResize(id string) error
|
||||
|
||||
// ConfirmResizeServer will acknowledge a server's resized configuration.
|
||||
ConfirmResize(id string) error
|
||||
|
||||
// RebootServer requests that the server with the specified ID be rebooted.
|
||||
// Two reboot mechanisms exist.
|
||||
//
|
||||
// - Hard. This will physically power-cycle the unit.
|
||||
// - Soft. This will attempt to use the server's software-based mechanisms to restart
|
||||
// the machine. E.g., "shutdown -r now" on Linux.
|
||||
RebootServer(id string, hard bool) error
|
||||
|
||||
// RescueServer requests that the server with the specified ID be placed into
|
||||
// a state of maintenance. The server instance is replaced with a new instance,
|
||||
// of the same flavor and image. This new image will have the boot volume of the
|
||||
// original machine mounted as a secondary device, so that repair and administration
|
||||
// may occur. Use UnrescueServer() to restore the server to its previous state.
|
||||
// Note also that many providers will impose a time limit for how long a server may
|
||||
// exist in rescue mode! Consult the API documentation for your provider for
|
||||
// details.
|
||||
RescueServer(id string) (string, error)
|
||||
|
||||
// UnrescueServer requests that a server in rescue state be placed into its nominal
|
||||
// operating state.
|
||||
UnrescueServer(id string) error
|
||||
|
||||
// UpdateServer alters one or more fields of the identified server's Server record.
|
||||
// However, not all fields may be altered. Presently, only Name, AccessIPv4, and
|
||||
// AccessIPv6 fields may be altered. If unspecified, or set to an empty or zero
|
||||
// value, the corresponding field remains unaltered.
|
||||
//
|
||||
// This function returns the new set of server details if successful.
|
||||
UpdateServer(id string, newValues NewServerSettings) (*Server, error)
|
||||
|
||||
// RebuildServer reprovisions a server to the specifications given by the
|
||||
// NewServer structure. The following fields are guaranteed to be recognized:
|
||||
//
|
||||
// Name (required) AccessIPv4
|
||||
// imageRef (required) AccessIPv6
|
||||
// AdminPass (required) Metadata
|
||||
// Personality
|
||||
//
|
||||
// Other providers may reserve the right to act on additional fields.
|
||||
RebuildServer(id string, ns NewServer) (*Server, error)
|
||||
|
||||
// CreateImage will create a new image from the specified server id returning the id of the new image.
|
||||
CreateImage(id string, ci CreateImage) (string, error)
|
||||
|
||||
// Addresses
|
||||
|
||||
// ListAddresses yields the list of available addresses for the server.
|
||||
// This information is also returned by ServerById() in the Server.Addresses
|
||||
// field. However, if you have a lot of servers and all you need are addresses,
|
||||
// this function might be more efficient.
|
||||
ListAddresses(id string) (AddressSet, error)
|
||||
|
||||
// ListAddressesByNetwork yields the list of available addresses for a given server id and networkLabel.
|
||||
// Example: ListAddressesByNetwork("234-4353-4jfrj-43j2s", "private")
|
||||
ListAddressesByNetwork(id, networkLabel string) (NetworkAddress, error)
|
||||
|
||||
// ListFloatingIps yields the list of all floating IP addresses allocated to the current project.
|
||||
ListFloatingIps() ([]FloatingIp, error)
|
||||
|
||||
// CreateFloatingIp allocates a new IP from the named pool to the current project.
|
||||
CreateFloatingIp(pool string) (FloatingIp, error)
|
||||
|
||||
// DeleteFloatingIp returns the specified IP from the current project to the pool.
|
||||
DeleteFloatingIp(ip FloatingIp) error
|
||||
|
||||
// AssociateFloatingIp associates the given floating IP to the given server id.
|
||||
AssociateFloatingIp(serverId string, ip FloatingIp) error
|
||||
|
||||
// Images
|
||||
|
||||
// ListImages yields the list of available operating system images. This function
|
||||
// returns full details for each image, if available.
|
||||
ListImages() ([]Image, error)
|
||||
|
||||
// ImageById yields details about a specific image.
|
||||
ImageById(id string) (*Image, error)
|
||||
|
||||
// DeleteImageById will delete the specific image.
|
||||
DeleteImageById(id string) error
|
||||
|
||||
// Flavors
|
||||
|
||||
// ListFlavors yields the list of available system flavors. This function
|
||||
// returns full details for each flavor, if available.
|
||||
ListFlavors() ([]Flavor, error)
|
||||
|
||||
// KeyPairs
|
||||
|
||||
// ListKeyPairs yields the list of available keypairs.
|
||||
ListKeyPairs() ([]KeyPair, error)
|
||||
|
||||
// CreateKeyPairs will create or generate a new keypair.
|
||||
CreateKeyPair(nkp NewKeyPair) (KeyPair, error)
|
||||
|
||||
// DeleteKeyPair wil delete a keypair.
|
||||
DeleteKeyPair(name string) error
|
||||
|
||||
// ShowKeyPair will yield the named keypair.
|
||||
ShowKeyPair(name string) (KeyPair, error)
|
||||
|
||||
// ListSecurityGroups provides a listing of security groups for the tenant.
|
||||
// This method works only if the provider supports the os-security-groups extension.
|
||||
ListSecurityGroups() ([]SecurityGroup, error)
|
||||
|
||||
// CreateSecurityGroup lets a tenant create a new security group.
|
||||
// Only the SecurityGroup fields which are specified will be marshalled to the API.
|
||||
// This method works only if the provider supports the os-security-groups extension.
|
||||
CreateSecurityGroup(desired SecurityGroup) (*SecurityGroup, error)
|
||||
|
||||
// ListSecurityGroupsByServerId provides a list of security groups which apply to the indicated server.
|
||||
// This method works only if the provider supports the os-security-groups extension.
|
||||
ListSecurityGroupsByServerId(id string) ([]SecurityGroup, error)
|
||||
|
||||
// SecurityGroupById returns a security group corresponding to the provided ID number.
|
||||
// This method works only if the provider supports the os-security-groups extension.
|
||||
SecurityGroupById(id int) (*SecurityGroup, error)
|
||||
|
||||
// DeleteSecurityGroupById disposes of a security group corresponding to the provided ID number.
|
||||
// This method works only if the provider supports the os-security-groups extension.
|
||||
DeleteSecurityGroupById(id int) error
|
||||
|
||||
// ListDefaultSGRules lists default security group rules.
|
||||
// This method only works if the provider supports the os-security-groups-default-rules extension.
|
||||
ListDefaultSGRules() ([]SGRule, error)
|
||||
|
||||
// CreateDefaultSGRule creates a default security group rule.
|
||||
// This method only works if the provider supports the os-security-groups-default-rules extension.
|
||||
CreateDefaultSGRule(SGRule) (*SGRule, error)
|
||||
|
||||
// GetSGRule obtains information for a specified security group rule.
|
||||
// This method only works if the provider supports the os-security-groups-default-rules extension.
|
||||
GetSGRule(string) (*SGRule, error)
|
||||
}
|
98
Godeps/_workspace/src/github.com/rackspace/gophercloud/keypairs.go
generated
vendored
Normal file
98
Godeps/_workspace/src/github.com/rackspace/gophercloud/keypairs.go
generated
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
// See the CloudImagesProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListKeyPairs() ([]KeyPair, error) {
|
||||
type KeyPairs struct {
|
||||
KeyPairs []struct {
|
||||
KeyPair KeyPair `json:"keypair"`
|
||||
} `json:"keypairs"`
|
||||
}
|
||||
|
||||
var kp KeyPairs
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/os-keypairs"
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
Results: &kp,
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// Flatten out the list of keypairs
|
||||
var keypairs []KeyPair
|
||||
for _, k := range kp.KeyPairs {
|
||||
keypairs = append(keypairs, k.KeyPair)
|
||||
}
|
||||
return keypairs, err
|
||||
}
|
||||
|
||||
func (gsp *genericServersProvider) CreateKeyPair(nkp NewKeyPair) (KeyPair, error) {
|
||||
var kp KeyPair
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/os-keypairs"
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
KeyPair *NewKeyPair `json:"keypair"`
|
||||
}{&nkp},
|
||||
CustomClient: gsp.context.httpClient,
|
||||
Results: &struct{ KeyPair *KeyPair }{&kp},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
})
|
||||
return kp, err
|
||||
}
|
||||
|
||||
// See the CloudImagesProvider interface for details.
|
||||
func (gsp *genericServersProvider) DeleteKeyPair(name string) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/os-keypairs/" + name
|
||||
return perigee.Delete(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (gsp *genericServersProvider) ShowKeyPair(name string) (KeyPair, error) {
|
||||
var kp KeyPair
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/os-keypairs/" + name
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gsp.context.httpClient,
|
||||
Results: &struct{ KeyPair *KeyPair }{&kp},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return kp, err
|
||||
}
|
||||
|
||||
type KeyPair struct {
|
||||
FingerPrint string `json:"fingerprint"`
|
||||
Name string `json:"name"`
|
||||
PrivateKey string `json:"private_key,omitempty"`
|
||||
PublicKey string `json:"public_key"`
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
}
|
||||
|
||||
type NewKeyPair struct {
|
||||
Name string `json:"name"`
|
||||
PublicKey string `json:"public_key,omitempty"`
|
||||
}
|
64
Godeps/_workspace/src/github.com/rackspace/gophercloud/osutil/auth.go
generated
vendored
Normal file
64
Godeps/_workspace/src/github.com/rackspace/gophercloud/osutil/auth.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
package osutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
nilOptions = gophercloud.AuthOptions{}
|
||||
|
||||
// ErrNoAuthUrl errors occur when the value of the OS_AUTH_URL environment variable cannot be determined.
|
||||
ErrNoAuthUrl = fmt.Errorf("Environment variable OS_AUTH_URL needs to be set.")
|
||||
|
||||
// ErrNoUsername errors occur when the value of the OS_USERNAME environment variable cannot be determined.
|
||||
ErrNoUsername = fmt.Errorf("Environment variable OS_USERNAME needs to be set.")
|
||||
|
||||
// ErrNoPassword errors occur when the value of the OS_PASSWORD environment variable cannot be determined.
|
||||
ErrNoPassword = fmt.Errorf("Environment variable OS_PASSWORD or OS_API_KEY needs to be set.")
|
||||
)
|
||||
|
||||
// AuthOptions fills out a gophercloud.AuthOptions structure with the settings found on the various OpenStack
|
||||
// OS_* environment variables. The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME,
|
||||
// OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME. Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must
|
||||
// have settings, or an error will result. OS_TENANT_ID and OS_TENANT_NAME are optional.
|
||||
//
|
||||
// The value of OS_AUTH_URL will be returned directly to the caller, for subsequent use in
|
||||
// gophercloud.Authenticate()'s Provider parameter. This function will not interpret the value of OS_AUTH_URL,
|
||||
// so as a convenient extention, you may set OS_AUTH_URL to, e.g., "rackspace-uk", or any other Gophercloud-recognized
|
||||
// provider shortcuts. For broad compatibility, especially with local installations, you should probably
|
||||
// avoid the temptation to do this.
|
||||
func AuthOptions() (string, gophercloud.AuthOptions, error) {
|
||||
provider := os.Getenv("OS_AUTH_URL")
|
||||
username := os.Getenv("OS_USERNAME")
|
||||
password := os.Getenv("OS_PASSWORD")
|
||||
tenantId := os.Getenv("OS_TENANT_ID")
|
||||
tenantName := os.Getenv("OS_TENANT_NAME")
|
||||
|
||||
if provider == "" {
|
||||
return "", nilOptions, ErrNoAuthUrl
|
||||
}
|
||||
|
||||
if username == "" {
|
||||
return "", nilOptions, ErrNoUsername
|
||||
}
|
||||
|
||||
if password == "" {
|
||||
return "", nilOptions, ErrNoPassword
|
||||
}
|
||||
|
||||
ao := gophercloud.AuthOptions{
|
||||
Username: username,
|
||||
Password: password,
|
||||
TenantId: tenantId,
|
||||
TenantName: tenantName,
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(provider, "/tokens") {
|
||||
provider += "/tokens"
|
||||
}
|
||||
|
||||
return provider, ao, nil
|
||||
}
|
9
Godeps/_workspace/src/github.com/rackspace/gophercloud/osutil/region.go
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/rackspace/gophercloud/osutil/region.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
package osutil
|
||||
|
||||
import "os"
|
||||
|
||||
// Region provides a means of querying the OS_REGION_NAME environment variable.
|
||||
// At present, you may also use os.Getenv("OS_REGION_NAME") as well.
|
||||
func Region() string {
|
||||
return os.Getenv("OS_REGION_NAME")
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
// Gophercloud provides a multi-vendor interface to OpenStack-compatible clouds which attempts to follow
|
||||
// established Go community coding standards and social norms.
|
||||
//
|
||||
// Unless you intend on contributing code to the SDK, you will almost certainly never have to use any
|
||||
// Context structures or any of its methods. Contextual methods exist for easier unit testing only.
|
||||
// Stick with the global functions unless you know exactly what you're doing, and why.
|
||||
package gophercloud
|
|
@ -0,0 +1,36 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
// WithReauth wraps a Perigee request fragment with logic to perform re-authentication
|
||||
// if it's deemed necessary.
|
||||
//
|
||||
// Do not confuse this function with WithReauth()! Although they work together to support reauthentication,
|
||||
// WithReauth() actually contains the decision-making logic to determine when to perform a reauth,
|
||||
// while WithReauthHandler() is used to configure what a reauth actually entails.
|
||||
func (c *Context) WithReauth(ap AccessProvider, f func() error) error {
|
||||
err := f()
|
||||
cause, ok := err.(*perigee.UnexpectedResponseCodeError)
|
||||
if ok && cause.Actual == 401 {
|
||||
err = c.reauthHandler(ap)
|
||||
if err == nil {
|
||||
err = f()
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// This is like WithReauth above but returns a perigee Response object
|
||||
func (c *Context) ResponseWithReauth(ap AccessProvider, f func() (*perigee.Response, error)) (*perigee.Response, error) {
|
||||
response, err := f()
|
||||
cause, ok := err.(*perigee.UnexpectedResponseCodeError)
|
||||
if ok && cause.Actual == 401 {
|
||||
err = c.reauthHandler(ap)
|
||||
if err == nil {
|
||||
response, err = f()
|
||||
}
|
||||
}
|
||||
return response, err
|
||||
}
|
133
Godeps/_workspace/src/github.com/rackspace/gophercloud/reauth_test.go
generated
vendored
Normal file
133
Godeps/_workspace/src/github.com/rackspace/gophercloud/reauth_test.go
generated
vendored
Normal file
|
@ -0,0 +1,133 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"github.com/racker/perigee"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This reauth-handler does nothing, and returns no error.
|
||||
func doNothing(_ AccessProvider) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestOtherErrorsPropegate(t *testing.T) {
|
||||
calls := 0
|
||||
c := TestContext().WithReauthHandler(doNothing)
|
||||
|
||||
err := c.WithReauth(nil, func() error {
|
||||
calls++
|
||||
return &perigee.UnexpectedResponseCodeError{
|
||||
Expected: []int{204},
|
||||
Actual: 404,
|
||||
}
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected MyError to be returned; got nil instead.")
|
||||
return
|
||||
}
|
||||
if _, ok := err.(*perigee.UnexpectedResponseCodeError); !ok {
|
||||
t.Error("Expected UnexpectedResponseCodeError; got %#v", err)
|
||||
return
|
||||
}
|
||||
if calls != 1 {
|
||||
t.Errorf("Expected the body to be invoked once; found %d calls instead", calls)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func Test401ErrorCausesBodyInvokation2ndTime(t *testing.T) {
|
||||
calls := 0
|
||||
c := TestContext().WithReauthHandler(doNothing)
|
||||
|
||||
err := c.WithReauth(nil, func() error {
|
||||
calls++
|
||||
return &perigee.UnexpectedResponseCodeError{
|
||||
Expected: []int{204},
|
||||
Actual: 401,
|
||||
}
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Error("Expected MyError to be returned; got nil instead.")
|
||||
return
|
||||
}
|
||||
if calls != 2 {
|
||||
t.Errorf("Expected the body to be invoked once; found %d calls instead", calls)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestReauthAttemptShouldHappen(t *testing.T) {
|
||||
calls := 0
|
||||
c := TestContext().WithReauthHandler(func(_ AccessProvider) error {
|
||||
calls++
|
||||
return nil
|
||||
})
|
||||
c.WithReauth(nil, func() error {
|
||||
return &perigee.UnexpectedResponseCodeError{
|
||||
Expected: []int{204},
|
||||
Actual: 401,
|
||||
}
|
||||
})
|
||||
|
||||
if calls != 1 {
|
||||
t.Errorf("Expected Reauthenticator to be called once; found %d instead", calls)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type MyError struct{}
|
||||
|
||||
func (*MyError) Error() string {
|
||||
return "MyError instance"
|
||||
}
|
||||
|
||||
func TestReauthErrorShouldPropegate(t *testing.T) {
|
||||
c := TestContext().WithReauthHandler(func(_ AccessProvider) error {
|
||||
return &MyError{}
|
||||
})
|
||||
|
||||
err := c.WithReauth(nil, func() error {
|
||||
return &perigee.UnexpectedResponseCodeError{
|
||||
Expected: []int{204},
|
||||
Actual: 401,
|
||||
}
|
||||
})
|
||||
|
||||
if _, ok := err.(*MyError); !ok {
|
||||
t.Errorf("Expected a MyError; got %#v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type MyAccess struct{}
|
||||
|
||||
func (my *MyAccess) FirstEndpointUrlByCriteria(ApiCriteria) string {
|
||||
return ""
|
||||
}
|
||||
func (my *MyAccess) AuthToken() string {
|
||||
return ""
|
||||
}
|
||||
func (my *MyAccess) Revoke(string) error {
|
||||
return nil
|
||||
}
|
||||
func (my *MyAccess) Reauthenticate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestReauthHandlerUsesSameAccessProvider(t *testing.T) {
|
||||
fakeAccess := &MyAccess{}
|
||||
c := TestContext().WithReauthHandler(func(acc AccessProvider) error {
|
||||
if acc != fakeAccess {
|
||||
t.Errorf("Expected acc = fakeAccess")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.WithReauth(fakeAccess, func() error {
|
||||
return &perigee.UnexpectedResponseCodeError{
|
||||
Expected: []int{204},
|
||||
Actual: 401,
|
||||
}
|
||||
})
|
||||
}
|
26
Godeps/_workspace/src/github.com/rackspace/gophercloud/scripts/create-environment.sh
generated
vendored
Normal file
26
Godeps/_workspace/src/github.com/rackspace/gophercloud/scripts/create-environment.sh
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# This script helps new contributors set up their local workstation for
|
||||
# gophercloud development and contributions.
|
||||
|
||||
# Create the environment
|
||||
export GOPATH=$HOME/go/gophercloud
|
||||
mkdir -p $GOPATH
|
||||
|
||||
# Download gophercloud into that environment
|
||||
go get github.com/rackspace/gophercloud
|
||||
cd $GOPATH/src/github.com/rackspace/gophercloud
|
||||
git checkout master
|
||||
|
||||
# Write out the env.sh convenience file.
|
||||
cd $GOPATH
|
||||
cat <<EOF >env.sh
|
||||
#!/bin/bash
|
||||
export GOPATH=$(pwd)
|
||||
export GOPHERCLOUD=$GOPATH/src/github.com/rackspace/gophercloud
|
||||
EOF
|
||||
chmod a+x env.sh
|
||||
|
||||
# Make changes immediately available as a convenience.
|
||||
. ./env.sh
|
||||
|
37
Godeps/_workspace/src/github.com/rackspace/gophercloud/scripts/test-all.sh
generated
vendored
Normal file
37
Godeps/_workspace/src/github.com/rackspace/gophercloud/scripts/test-all.sh
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# This script is responsible for executing all the acceptance tests found in
|
||||
# the acceptance/ directory.
|
||||
|
||||
# Find where _this_ script is running from.
|
||||
SCRIPTS=$(dirname $0)
|
||||
SCRIPTS=$(cd $SCRIPTS; pwd)
|
||||
|
||||
# Locate the acceptance test / examples directory.
|
||||
ACCEPTANCE=$(cd $SCRIPTS/../acceptance; pwd)
|
||||
|
||||
# Go workspace path
|
||||
WS=$(cd $SCRIPTS/..; pwd)
|
||||
|
||||
# In order to run Go code interactively, we need the GOPATH environment
|
||||
# to be set.
|
||||
if [ "x$GOPATH" == "x" ]; then
|
||||
export GOPATH=$WS
|
||||
echo "WARNING: You didn't have your GOPATH environment variable set."
|
||||
echo " I'm assuming $GOPATH as its value."
|
||||
fi
|
||||
|
||||
# Run all acceptance tests sequentially.
|
||||
# If any test fails, we fail fast.
|
||||
LIBS=$(ls $ACCEPTANCE/lib*.go)
|
||||
for T in $(ls -1 $ACCEPTANCE/[0-9][0-9]*.go); do
|
||||
if ! [ -x $T ]; then
|
||||
CMD="go run $T $LIBS -quiet"
|
||||
echo "$CMD ..."
|
||||
if ! $CMD ; then
|
||||
echo "- FAILED. Try re-running w/out the -quiet option to see output."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
807
Godeps/_workspace/src/github.com/rackspace/gophercloud/servers.go
generated
vendored
Normal file
807
Godeps/_workspace/src/github.com/rackspace/gophercloud/servers.go
generated
vendored
Normal file
|
@ -0,0 +1,807 @@
|
|||
// TODO(sfalvo): Remove Rackspace-specific Server structure fields and refactor them into a provider-specific access method.
|
||||
// Be sure to update godocs accordingly.
|
||||
|
||||
package gophercloud
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/racker/perigee"
|
||||
)
|
||||
|
||||
// genericServersProvider structures provide the implementation for generic OpenStack-compatible
|
||||
// CloudServersProvider interfaces.
|
||||
type genericServersProvider struct {
|
||||
// endpoint refers to the provider's API endpoint base URL. This will be used to construct
|
||||
// and issue queries.
|
||||
endpoint string
|
||||
|
||||
// Test context (if any) in which to issue requests.
|
||||
context *Context
|
||||
|
||||
// access associates this API provider with a set of credentials,
|
||||
// which may be automatically renewed if they near expiration.
|
||||
access AccessProvider
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gcp *genericServersProvider) ListServersByFilter(filter url.Values) ([]Server, error) {
|
||||
var ss []Server
|
||||
|
||||
err := gcp.context.WithReauth(gcp.access, func() error {
|
||||
url := gcp.endpoint + "/servers/detail?" + filter.Encode()
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gcp.context.httpClient,
|
||||
Results: &struct{ Servers *[]Server }{&ss},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gcp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gcp *genericServersProvider) ListServersLinksOnly() ([]Server, error) {
|
||||
var ss []Server
|
||||
|
||||
err := gcp.context.WithReauth(gcp.access, func() error {
|
||||
url := gcp.endpoint + "/servers"
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gcp.context.httpClient,
|
||||
Results: &struct{ Servers *[]Server }{&ss},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gcp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gcp *genericServersProvider) ListServers() ([]Server, error) {
|
||||
var ss []Server
|
||||
|
||||
err := gcp.context.WithReauth(gcp.access, func() error {
|
||||
url := gcp.endpoint + "/servers/detail"
|
||||
return perigee.Get(url, perigee.Options{
|
||||
CustomClient: gcp.context.httpClient,
|
||||
Results: &struct{ Servers *[]Server }{&ss},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gcp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// Compatibility with v0.0.x -- we "map" our public and private
|
||||
// addresses into a legacy structure field for the benefit of
|
||||
// earlier software.
|
||||
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
for _, s := range ss {
|
||||
err = mapstructure.Decode(s.RawAddresses, &s.Addresses)
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
}
|
||||
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ServerById(id string) (*Server, error) {
|
||||
var s *Server
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/servers/" + id
|
||||
return perigee.Get(url, perigee.Options{
|
||||
Results: &struct{ Server **Server }{&s},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
})
|
||||
|
||||
// Compatibility with v0.0.x -- we "map" our public and private
|
||||
// addresses into a legacy structure field for the benefit of
|
||||
// earlier software.
|
||||
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
err = mapstructure.Decode(s.RawAddresses, &s.Addresses)
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) CreateServer(ns NewServer) (*NewServer, error) {
|
||||
var s *NewServer
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := gsp.endpoint + "/servers"
|
||||
return perigee.Post(ep, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
Server *NewServer `json:"server"`
|
||||
}{&ns},
|
||||
Results: &struct{ Server **NewServer }{&s},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
})
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) DeleteServerById(id string) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := gsp.endpoint + "/servers/" + id
|
||||
return perigee.Delete(url, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{204},
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) SetAdminPassword(id, pw string) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
ChangePassword struct {
|
||||
AdminPass string `json:"adminPass"`
|
||||
} `json:"changePassword"`
|
||||
}{
|
||||
struct {
|
||||
AdminPass string `json:"adminPass"`
|
||||
}{pw},
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ResizeServer(id, newName, newFlavor, newDiskConfig string) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
rr := ResizeRequest{
|
||||
Name: newName,
|
||||
FlavorRef: newFlavor,
|
||||
DiskConfig: newDiskConfig,
|
||||
}
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
Resize ResizeRequest `json:"resize"`
|
||||
}{rr},
|
||||
OkCodes: []int{202},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) RevertResize(id string) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
RevertResize *int `json:"revertResize"`
|
||||
}{nil},
|
||||
OkCodes: []int{202},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ConfirmResize(id string) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
ConfirmResize *int `json:"confirmResize"`
|
||||
}{nil},
|
||||
OkCodes: []int{204},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details
|
||||
func (gsp *genericServersProvider) RebootServer(id string, hard bool) error {
|
||||
return gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
types := map[bool]string{false: "SOFT", true: "HARD"}
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
Reboot struct {
|
||||
Type string `json:"type"`
|
||||
} `json:"reboot"`
|
||||
}{
|
||||
struct {
|
||||
Type string `json:"type"`
|
||||
}{types[hard]},
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details
|
||||
func (gsp *genericServersProvider) RescueServer(id string) (string, error) {
|
||||
var pw *string
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
Rescue string `json:"rescue"`
|
||||
}{"none"},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct {
|
||||
AdminPass **string `json:"adminPass"`
|
||||
}{&pw},
|
||||
})
|
||||
})
|
||||
return *pw, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details
|
||||
func (gsp *genericServersProvider) UnrescueServer(id string) error {
|
||||
return gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
return perigee.Post(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
Unrescue *int `json:"unrescue"`
|
||||
}{nil},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details
|
||||
func (gsp *genericServersProvider) UpdateServer(id string, changes NewServerSettings) (*Server, error) {
|
||||
var svr *Server
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
url := fmt.Sprintf("%s/servers/%s", gsp.endpoint, id)
|
||||
return perigee.Put(url, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
Server NewServerSettings `json:"server"`
|
||||
}{changes},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct {
|
||||
Server **Server `json:"server"`
|
||||
}{&svr},
|
||||
})
|
||||
})
|
||||
return svr, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) RebuildServer(id string, ns NewServer) (*Server, error) {
|
||||
var s *Server
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
return perigee.Post(ep, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
Rebuild *NewServer `json:"rebuild"`
|
||||
}{&ns},
|
||||
Results: &struct{ Server **Server }{&s},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
})
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListAddresses(id string) (AddressSet, error) {
|
||||
var pas *AddressSet
|
||||
var statusCode int
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/servers/%s/ips", gsp.endpoint, id)
|
||||
return perigee.Get(ep, perigee.Options{
|
||||
Results: &struct{ Addresses **AddressSet }{&pas},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{200, 203},
|
||||
StatusCode: &statusCode,
|
||||
})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if statusCode == 203 {
|
||||
err = WarnUnauthoritative
|
||||
}
|
||||
}
|
||||
|
||||
return *pas, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListAddressesByNetwork(id, networkLabel string) (NetworkAddress, error) {
|
||||
pas := make(NetworkAddress)
|
||||
var statusCode int
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/servers/%s/ips/%s", gsp.endpoint, id, networkLabel)
|
||||
return perigee.Get(ep, perigee.Options{
|
||||
Results: &pas,
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{200, 203},
|
||||
StatusCode: &statusCode,
|
||||
})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if statusCode == 203 {
|
||||
err = WarnUnauthoritative
|
||||
}
|
||||
}
|
||||
|
||||
return pas, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) CreateImage(id string, ci CreateImage) (string, error) {
|
||||
response, err := gsp.context.ResponseWithReauth(gsp.access, func() (*perigee.Response, error) {
|
||||
ep := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
|
||||
return perigee.Request("POST", ep, perigee.Options{
|
||||
ReqBody: &struct {
|
||||
CreateImage *CreateImage `json:"createImage"`
|
||||
}{&ci},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{200, 202},
|
||||
})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
location, err := response.HttpResponse.Location()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Return the last element of the location which is the image id
|
||||
locationArr := strings.Split(location.Path, "/")
|
||||
return locationArr[len(locationArr)-1], err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListSecurityGroups() ([]SecurityGroup, error) {
|
||||
var sgs []SecurityGroup
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-security-groups", gsp.endpoint)
|
||||
return perigee.Get(ep, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct {
|
||||
SecurityGroups *[]SecurityGroup `json:"security_groups"`
|
||||
}{&sgs},
|
||||
})
|
||||
})
|
||||
return sgs, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) CreateSecurityGroup(desired SecurityGroup) (*SecurityGroup, error) {
|
||||
var actual *SecurityGroup
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-security-groups", gsp.endpoint)
|
||||
return perigee.Post(ep, perigee.Options{
|
||||
ReqBody: struct {
|
||||
AddSecurityGroup SecurityGroup `json:"security_group"`
|
||||
}{desired},
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct {
|
||||
SecurityGroup **SecurityGroup `json:"security_group"`
|
||||
}{&actual},
|
||||
})
|
||||
})
|
||||
return actual, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListSecurityGroupsByServerId(id string) ([]SecurityGroup, error) {
|
||||
var sgs []SecurityGroup
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/servers/%s/os-security-groups", gsp.endpoint, id)
|
||||
return perigee.Get(ep, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct {
|
||||
SecurityGroups *[]SecurityGroup `json:"security_groups"`
|
||||
}{&sgs},
|
||||
})
|
||||
})
|
||||
return sgs, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) SecurityGroupById(id int) (*SecurityGroup, error) {
|
||||
var actual *SecurityGroup
|
||||
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-security-groups/%d", gsp.endpoint, id)
|
||||
return perigee.Get(ep, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct {
|
||||
SecurityGroup **SecurityGroup `json:"security_group"`
|
||||
}{&actual},
|
||||
})
|
||||
})
|
||||
return actual, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) DeleteSecurityGroupById(id int) error {
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-security-groups/%d", gsp.endpoint, id)
|
||||
return perigee.Delete(ep, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) ListDefaultSGRules() ([]SGRule, error) {
|
||||
var sgrs []SGRule
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-security-group-default-rules", gsp.endpoint)
|
||||
return perigee.Get(ep, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct{ Security_group_default_rules *[]SGRule }{&sgrs},
|
||||
})
|
||||
})
|
||||
return sgrs, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) CreateDefaultSGRule(r SGRule) (*SGRule, error) {
|
||||
var sgr *SGRule
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-security-group-default-rules", gsp.endpoint)
|
||||
return perigee.Post(ep, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct{ Security_group_default_rule **SGRule }{&sgr},
|
||||
ReqBody: struct {
|
||||
Security_group_default_rule SGRule `json:"security_group_default_rule"`
|
||||
}{r},
|
||||
})
|
||||
})
|
||||
return sgr, err
|
||||
}
|
||||
|
||||
// See the CloudServersProvider interface for details.
|
||||
func (gsp *genericServersProvider) GetSGRule(id string) (*SGRule, error) {
|
||||
var sgr *SGRule
|
||||
err := gsp.context.WithReauth(gsp.access, func() error {
|
||||
ep := fmt.Sprintf("%s/os-security-group-default-rules/%s", gsp.endpoint, id)
|
||||
return perigee.Get(ep, perigee.Options{
|
||||
MoreHeaders: map[string]string{
|
||||
"X-Auth-Token": gsp.access.AuthToken(),
|
||||
},
|
||||
Results: &struct{ Security_group_default_rule **SGRule }{&sgr},
|
||||
})
|
||||
})
|
||||
return sgr, err
|
||||
}
|
||||
|
||||
// SecurityGroup provides a description of a security group, including all its rules.
|
||||
type SecurityGroup struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Id int `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Rules []SGRule `json:"rules,omitempty"`
|
||||
TenantId string `json:"tenant_id,omitempty"`
|
||||
}
|
||||
|
||||
// SGRule encapsulates a single rule which applies to a security group.
|
||||
// This definition is just a guess, based on the documentation found in another extension here: http://docs.openstack.org/api/openstack-compute/2/content/GET_os-security-group-default-rules-v2_listSecGroupDefaultRules_v2__tenant_id__os-security-group-rules_ext-os-security-group-default-rules.html
|
||||
type SGRule struct {
|
||||
FromPort int `json:"from_port,omitempty"`
|
||||
Id int `json:"id,omitempty"`
|
||||
IpProtocol string `json:"ip_protocol,omitempty"`
|
||||
IpRange map[string]interface{} `json:"ip_range,omitempty"`
|
||||
ToPort int `json:"to_port,omitempty"`
|
||||
}
|
||||
|
||||
// RaxBandwidth provides measurement of server bandwidth consumed over a given audit interval.
|
||||
type RaxBandwidth struct {
|
||||
AuditPeriodEnd string `json:"audit_period_end"`
|
||||
AuditPeriodStart string `json:"audit_period_start"`
|
||||
BandwidthInbound int64 `json:"bandwidth_inbound"`
|
||||
BandwidthOutbound int64 `json:"bandwidth_outbound"`
|
||||
Interface string `json:"interface"`
|
||||
}
|
||||
|
||||
// A VersionedAddress denotes either an IPv4 or IPv6 (depending on version indicated)
|
||||
// address.
|
||||
type VersionedAddress struct {
|
||||
Addr string `json:"addr"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
// An AddressSet provides a set of public and private IP addresses for a resource.
|
||||
// Each address has a version to identify if IPv4 or IPv6.
|
||||
type AddressSet struct {
|
||||
Public []VersionedAddress `json:"public"`
|
||||
Private []VersionedAddress `json:"private"`
|
||||
}
|
||||
|
||||
type NetworkAddress map[string][]VersionedAddress
|
||||
|
||||
// Server records represent (virtual) hardware instances (not configurations) accessible by the user.
|
||||
//
|
||||
// The AccessIPv4 / AccessIPv6 fields provides IP addresses for the server in the IPv4 or IPv6 format, respectively.
|
||||
//
|
||||
// Addresses provides addresses for any attached isolated networks.
|
||||
// The version field indicates whether the IP address is version 4 or 6.
|
||||
// Note: only public and private pools appear here.
|
||||
// To get the complete set, use the AllAddressPools() method instead.
|
||||
//
|
||||
// Created tells when the server entity was created.
|
||||
//
|
||||
// The Flavor field includes the flavor ID and flavor links.
|
||||
//
|
||||
// The compute provisioning algorithm has an anti-affinity property that
|
||||
// attempts to spread customer VMs across hosts.
|
||||
// Under certain situations,
|
||||
// VMs from the same customer might be placed on the same host.
|
||||
// The HostId field represents the host your server runs on and
|
||||
// can be used to determine this scenario if it is relevant to your application.
|
||||
// Note that HostId is unique only per account; it is not globally unique.
|
||||
//
|
||||
// Id provides the server's unique identifier.
|
||||
// This field must be treated opaquely.
|
||||
//
|
||||
// Image indicates which image is installed on the server.
|
||||
//
|
||||
// Links provides one or more means of accessing the server.
|
||||
//
|
||||
// Metadata provides a small key-value store for application-specific information.
|
||||
//
|
||||
// Name provides a human-readable name for the server.
|
||||
//
|
||||
// Progress indicates how far along it is towards being provisioned.
|
||||
// 100 represents complete, while 0 represents just beginning.
|
||||
//
|
||||
// Status provides an indication of what the server's doing at the moment.
|
||||
// A server will be in ACTIVE state if it's ready for use.
|
||||
//
|
||||
// OsDcfDiskConfig indicates the server's boot volume configuration.
|
||||
// Valid values are:
|
||||
// AUTO
|
||||
// ----
|
||||
// The server is built with a single partition the size of the target flavor disk.
|
||||
// The file system is automatically adjusted to fit the entire partition.
|
||||
// This keeps things simple and automated.
|
||||
// AUTO is valid only for images and servers with a single partition that use the EXT3 file system.
|
||||
// This is the default setting for applicable Rackspace base images.
|
||||
//
|
||||
// MANUAL
|
||||
// ------
|
||||
// The server is built using whatever partition scheme and file system is in the source image.
|
||||
// If the target flavor disk is larger,
|
||||
// the remaining disk space is left unpartitioned.
|
||||
// This enables images to have non-EXT3 file systems, multiple partitions, and so on,
|
||||
// and enables you to manage the disk configuration.
|
||||
//
|
||||
// RaxBandwidth provides measures of the server's inbound and outbound bandwidth per interface.
|
||||
//
|
||||
// OsExtStsPowerState provides an indication of the server's power.
|
||||
// This field appears to be a set of flag bits:
|
||||
//
|
||||
// ... 4 3 2 1 0
|
||||
// +--//--+---+---+---+---+
|
||||
// | .... | 0 | S | 0 | I |
|
||||
// +--//--+---+---+---+---+
|
||||
// | |
|
||||
// | +--- 0=Instance is down.
|
||||
// | 1=Instance is up.
|
||||
// |
|
||||
// +----------- 0=Server is switched ON.
|
||||
// 1=Server is switched OFF.
|
||||
// (note reverse logic.)
|
||||
//
|
||||
// Unused bits should be ignored when read, and written as 0 for future compatibility.
|
||||
//
|
||||
// OsExtStsTaskState and OsExtStsVmState work together
|
||||
// to provide visibility in the provisioning process for the instance.
|
||||
// Consult Rackspace documentation at
|
||||
// http://docs.rackspace.com/servers/api/v2/cs-devguide/content/ch_extensions.html#ext_status
|
||||
// for more details. It's too lengthy to include here.
|
||||
type Server struct {
|
||||
AccessIPv4 string `json:"accessIPv4"`
|
||||
AccessIPv6 string `json:"accessIPv6"`
|
||||
Addresses AddressSet
|
||||
Created string `json:"created"`
|
||||
Flavor FlavorLink `json:"flavor"`
|
||||
HostId string `json:"hostId"`
|
||||
Id string `json:"id"`
|
||||
Image ImageLink `json:"image"`
|
||||
Links []Link `json:"links"`
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
Name string `json:"name"`
|
||||
Progress int `json:"progress"`
|
||||
Status string `json:"status"`
|
||||
TenantId string `json:"tenant_id"`
|
||||
Updated string `json:"updated"`
|
||||
UserId string `json:"user_id"`
|
||||
OsDcfDiskConfig string `json:"OS-DCF:diskConfig"`
|
||||
RaxBandwidth []RaxBandwidth `json:"rax-bandwidth:bandwidth"`
|
||||
OsExtStsPowerState int `json:"OS-EXT-STS:power_state"`
|
||||
OsExtStsTaskState string `json:"OS-EXT-STS:task_state"`
|
||||
OsExtStsVmState string `json:"OS-EXT-STS:vm_state"`
|
||||
|
||||
RawAddresses map[string]interface{} `json:"addresses"`
|
||||
}
|
||||
|
||||
// AllAddressPools returns a complete set of address pools available on the server.
|
||||
// The name of each pool supported keys the map.
|
||||
// The value of the map contains the addresses provided in the corresponding pool.
|
||||
func (s *Server) AllAddressPools() (map[string][]VersionedAddress, error) {
|
||||
pools := make(map[string][]VersionedAddress, 0)
|
||||
for pool, subtree := range s.RawAddresses {
|
||||
addresses := make([]VersionedAddress, 0)
|
||||
err := mapstructure.Decode(subtree, &addresses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pools[pool] = addresses
|
||||
}
|
||||
return pools, nil
|
||||
}
|
||||
|
||||
// NewServerSettings structures record those fields of the Server structure to change
|
||||
// when updating a server (see UpdateServer method).
|
||||
type NewServerSettings struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
AccessIPv4 string `json:"accessIPv4,omitempty"`
|
||||
AccessIPv6 string `json:"accessIPv6,omitempty"`
|
||||
}
|
||||
|
||||
// NewServer structures are used for both requests and responses.
|
||||
// The fields discussed below are relevent for server-creation purposes.
|
||||
//
|
||||
// The Name field contains the desired name of the server.
|
||||
// Note that (at present) Rackspace permits more than one server with the same name;
|
||||
// however, software should not depend on this.
|
||||
// Not only will Rackspace support thank you, so will your own devops engineers.
|
||||
// A name is required.
|
||||
//
|
||||
// The ImageRef field contains the ID of the desired software image to place on the server.
|
||||
// This ID must be found in the image slice returned by the Images() function.
|
||||
// This field is required.
|
||||
//
|
||||
// The FlavorRef field contains the ID of the server configuration desired for deployment.
|
||||
// This ID must be found in the flavor slice returned by the Flavors() function.
|
||||
// This field is required.
|
||||
//
|
||||
// For OsDcfDiskConfig, refer to the Image or Server structure documentation.
|
||||
// This field defaults to "AUTO" if not explicitly provided.
|
||||
//
|
||||
// Metadata contains a small key/value association of arbitrary data.
|
||||
// Neither Rackspace nor OpenStack places significance on this field in any way.
|
||||
// This field defaults to an empty map if not provided.
|
||||
//
|
||||
// Personality specifies the contents of certain files in the server's filesystem.
|
||||
// The files and their contents are mapped through a slice of FileConfig structures.
|
||||
// If not provided, all filesystem entities retain their image-specific configuration.
|
||||
//
|
||||
// Networks specifies an affinity for the server's various networks and interfaces.
|
||||
// Networks are identified through UUIDs; see NetworkConfig structure documentation for more details.
|
||||
// If not provided, network affinity is determined automatically.
|
||||
//
|
||||
// The AdminPass field may be used to provide a root- or administrator-password
|
||||
// during the server provisioning process.
|
||||
// If not provided, a random password will be automatically generated and returned in this field.
|
||||
//
|
||||
// The following fields are intended to be used to communicate certain results about the server being provisioned.
|
||||
// When attempting to create a new server, these fields MUST not be provided.
|
||||
// They'll be filled in by the response received from the Rackspace APIs.
|
||||
//
|
||||
// The Id field contains the server's unique identifier.
|
||||
// The identifier's scope is best assumed to be bound by the user's account, unless other arrangements have been made with Rackspace.
|
||||
//
|
||||
// The SecurityGroup field allows the user to specify a security group at launch.
|
||||
//
|
||||
// Any Links provided are used to refer to the server specifically by URL.
|
||||
// These links are useful for making additional REST calls not explicitly supported by Gorax.
|
||||
type NewServer struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
ImageRef string `json:"imageRef,omitempty"`
|
||||
FlavorRef string `json:"flavorRef,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
Personality []FileConfig `json:"personality,omitempty"`
|
||||
Networks []NetworkConfig `json:"networks,omitempty"`
|
||||
AdminPass string `json:"adminPass,omitempty"`
|
||||
KeyPairName string `json:"key_name,omitempty"`
|
||||
Id string `json:"id,omitempty"`
|
||||
Links []Link `json:"links,omitempty"`
|
||||
OsDcfDiskConfig string `json:"OS-DCF:diskConfig,omitempty"`
|
||||
SecurityGroup []map[string]interface{} `json:"security_groups,omitempty"`
|
||||
ConfigDrive bool `json:"config_drive"`
|
||||
UserData string `json:"user_data"`
|
||||
}
|
||||
|
||||
// ResizeRequest structures are used internally to encode to JSON the parameters required to resize a server instance.
|
||||
// Client applications will not use this structure (no API accepts an instance of this structure).
|
||||
// See the Region method ResizeServer() for more details on how to resize a server.
|
||||
type ResizeRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
FlavorRef string `json:"flavorRef"`
|
||||
DiskConfig string `json:"OS-DCF:diskConfig,omitempty"`
|
||||
}
|
||||
|
||||
type CreateImage struct {
|
||||
Name string `json:"name"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
54
Godeps/_workspace/src/github.com/rackspace/gophercloud/servers_test.go
generated
vendored
Normal file
54
Godeps/_workspace/src/github.com/rackspace/gophercloud/servers_test.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testAccess struct {
|
||||
public, internal string
|
||||
calledFirstEndpointByCriteria int
|
||||
}
|
||||
|
||||
func (ta *testAccess) FirstEndpointUrlByCriteria(ac ApiCriteria) string {
|
||||
ta.calledFirstEndpointByCriteria++
|
||||
urls := []string{ta.public, ta.internal}
|
||||
return urls[ac.UrlChoice]
|
||||
}
|
||||
|
||||
func (ta *testAccess) AuthToken() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (ta *testAccess) Revoke(string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ta *testAccess) Reauthenticate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestGetServersApi(t *testing.T) {
|
||||
c := TestContext().UseCustomClient(&http.Client{Transport: newTransport().WithResponse("Hello")})
|
||||
|
||||
acc := &testAccess{
|
||||
public: "http://localhost:8080",
|
||||
internal: "http://localhost:8086",
|
||||
}
|
||||
|
||||
_, err := c.ServersApi(acc, ApiCriteria{
|
||||
Name: "cloudComputeOpenStack",
|
||||
Region: "dfw",
|
||||
VersionId: "2",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if acc.calledFirstEndpointByCriteria != 1 {
|
||||
t.Error("Expected FirstEndpointByCriteria to be called")
|
||||
return
|
||||
}
|
||||
}
|
75
Godeps/_workspace/src/github.com/rackspace/gophercloud/service_catalog.go
generated
vendored
Normal file
75
Godeps/_workspace/src/github.com/rackspace/gophercloud/service_catalog.go
generated
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ApiCriteria provides one or more criteria for the SDK to look for appropriate endpoints.
|
||||
// Fields left unspecified or otherwise set to their zero-values are assumed to not be
|
||||
// relevant, and do not participate in the endpoint search.
|
||||
//
|
||||
// Name specifies the desired service catalog entry name.
|
||||
// Type specifies the desired service catalog entry type.
|
||||
// Region specifies the desired endpoint region.
|
||||
// If unset, Gophercloud will try to use the region set in the
|
||||
// OS_REGION_NAME environment variable. If that's not set,
|
||||
// region comparison will not occur. If OS_REGION_NAME is set
|
||||
// and IgnoreEnvVars is also set, OS_REGION_NAME will be ignored.
|
||||
// VersionId specifies the desired version of the endpoint.
|
||||
// Note that this field is matched exactly, and is (at present)
|
||||
// opaque to Gophercloud. Thus, requesting a version 2
|
||||
// endpoint will _not_ match a version 3 endpoint.
|
||||
// The UrlChoice field inidicates whether or not gophercloud
|
||||
// should use the public or internal endpoint URL if a
|
||||
// candidate endpoint is found.
|
||||
// IgnoreEnvVars instructs Gophercloud to ignore helpful environment variables.
|
||||
type ApiCriteria struct {
|
||||
Name string
|
||||
Type string
|
||||
Region string
|
||||
VersionId string
|
||||
UrlChoice int
|
||||
IgnoreEnvVars bool
|
||||
}
|
||||
|
||||
// The choices available for UrlChoice. See the ApiCriteria structure for details.
|
||||
const (
|
||||
PublicURL = iota
|
||||
InternalURL
|
||||
)
|
||||
|
||||
// Given a set of criteria to match on, locate the first candidate endpoint
|
||||
// in the provided service catalog.
|
||||
//
|
||||
// If nothing found, the result will be a zero-valued EntryEndpoint (all URLs
|
||||
// set to "").
|
||||
func FindFirstEndpointByCriteria(entries []CatalogEntry, ac ApiCriteria) EntryEndpoint {
|
||||
rgn := strings.ToUpper(ac.Region)
|
||||
if (rgn == "") && !ac.IgnoreEnvVars {
|
||||
rgn = os.Getenv("OS_REGION_NAME")
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if (ac.Name != "") && (ac.Name != entry.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (ac.Type != "") && (ac.Type != entry.Type) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, endpoint := range entry.Endpoints {
|
||||
if (rgn != "") && (rgn != strings.ToUpper(endpoint.Region)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (ac.VersionId != "") && (ac.VersionId != endpoint.VersionId) {
|
||||
continue
|
||||
}
|
||||
|
||||
return endpoint
|
||||
}
|
||||
}
|
||||
return EntryEndpoint{}
|
||||
}
|
190
Godeps/_workspace/src/github.com/rackspace/gophercloud/service_catalog_test.go
generated
vendored
Normal file
190
Godeps/_workspace/src/github.com/rackspace/gophercloud/service_catalog_test.go
generated
vendored
Normal file
|
@ -0,0 +1,190 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestFFEBCViaEnvVariable exercises only those calls where a region
|
||||
// parameter is required, but is provided by an environment variable.
|
||||
func TestFFEBCViaEnvVariable(t *testing.T) {
|
||||
changeRegion("RGN")
|
||||
|
||||
endpoint := FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "", ""),
|
||||
ApiCriteria{Name: "test"},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("If provided, the Region qualifier must exclude endpoints with missing or mismatching regions.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", ""),
|
||||
ApiCriteria{Name: "test"},
|
||||
)
|
||||
if endpoint.PublicURL != "http://localhost" {
|
||||
t.Error("Regions are case insensitive.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", ""),
|
||||
ApiCriteria{Name: "test", VersionId: "2"},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("Missing version ID means no match.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", "3"),
|
||||
ApiCriteria{Name: "test", VersionId: "2"},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("Mismatched version ID means no match.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", "2"),
|
||||
ApiCriteria{Name: "test", VersionId: "2"},
|
||||
)
|
||||
if endpoint.PublicURL != "http://localhost" {
|
||||
t.Error("All search criteria met; endpoint expected.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TestFFEBCViaRegionOption exercises only those calls where a region
|
||||
// parameter is specified explicitly. The region option overrides
|
||||
// any defined OS_REGION_NAME environment setting.
|
||||
func TestFFEBCViaRegionOption(t *testing.T) {
|
||||
changeRegion("Starfleet Command")
|
||||
|
||||
endpoint := FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "", ""),
|
||||
ApiCriteria{Name: "test", Region: "RGN"},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("If provided, the Region qualifier must exclude endpoints with missing or mismatching regions.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", ""),
|
||||
ApiCriteria{Name: "test", Region: "RGN"},
|
||||
)
|
||||
if endpoint.PublicURL != "http://localhost" {
|
||||
t.Error("Regions are case insensitive.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", ""),
|
||||
ApiCriteria{Name: "test", Region: "RGN", VersionId: "2"},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("Missing version ID means no match.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", "3"),
|
||||
ApiCriteria{Name: "test", Region: "RGN", VersionId: "2"},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("Mismatched version ID means no match.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "rgn", "2"),
|
||||
ApiCriteria{Name: "test", Region: "RGN", VersionId: "2"},
|
||||
)
|
||||
if endpoint.PublicURL != "http://localhost" {
|
||||
t.Error("All search criteria met; endpoint expected.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TestFFEBCWithoutRegion exercises only those calls where a region
|
||||
// is irrelevant. Just to make sure, though, we enforce Gophercloud
|
||||
// from paying any attention to OS_REGION_NAME if it happens to be set.
|
||||
func TestFindFirstEndpointByCriteria(t *testing.T) {
|
||||
endpoint := FindFirstEndpointByCriteria([]CatalogEntry{}, ApiCriteria{Name: "test", IgnoreEnvVars: true})
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("Not expecting to find anything in an empty service catalog.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
[]CatalogEntry{
|
||||
{Name: "test"},
|
||||
},
|
||||
ApiCriteria{Name: "test", IgnoreEnvVars: true},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("Even though we have a matching entry, no endpoints exist")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "", ""),
|
||||
ApiCriteria{Name: "test", IgnoreEnvVars: true},
|
||||
)
|
||||
if endpoint.PublicURL != "http://localhost" {
|
||||
t.Error("Looking for an endpoint by name but without region or version ID should match first entry endpoint.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "", ""),
|
||||
ApiCriteria{Type: "compute", IgnoreEnvVars: true},
|
||||
)
|
||||
if endpoint.PublicURL != "http://localhost" {
|
||||
t.Error("Looking for an endpoint by type but without region or version ID should match first entry endpoint.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "", ""),
|
||||
ApiCriteria{Type: "identity", IgnoreEnvVars: true},
|
||||
)
|
||||
if endpoint.PublicURL != "" {
|
||||
t.Error("Returned mismatched type.")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint = FindFirstEndpointByCriteria(
|
||||
catalog("test", "compute", "http://localhost", "ord", "2"),
|
||||
ApiCriteria{Name: "test", VersionId: "2", IgnoreEnvVars: true},
|
||||
)
|
||||
if endpoint.PublicURL != "http://localhost" {
|
||||
t.Error("Sometimes, you might not care what region your stuff is in.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func catalog(name, entry_type, url, region, version string) []CatalogEntry {
|
||||
return []CatalogEntry{
|
||||
{
|
||||
Name: name,
|
||||
Type: entry_type,
|
||||
Endpoints: []EntryEndpoint{
|
||||
{
|
||||
PublicURL: url,
|
||||
Region: region,
|
||||
VersionId: version,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func changeRegion(r string) {
|
||||
err := os.Setenv("OS_REGION_NAME", r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
103
Godeps/_workspace/src/github.com/rackspace/gophercloud/transport_double_test.go
generated
vendored
Normal file
103
Godeps/_workspace/src/github.com/rackspace/gophercloud/transport_double_test.go
generated
vendored
Normal file
|
@ -0,0 +1,103 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type transport struct {
|
||||
called int
|
||||
response string
|
||||
expectTenantId bool
|
||||
tenantIdFound bool
|
||||
status int
|
||||
}
|
||||
|
||||
func (t *transport) RoundTrip(req *http.Request) (rsp *http.Response, err error) {
|
||||
var authContainer *AuthContainer
|
||||
|
||||
t.called++
|
||||
|
||||
headers := make(http.Header)
|
||||
headers.Add("Content-Type", "application/xml; charset=UTF-8")
|
||||
|
||||
body := ioutil.NopCloser(strings.NewReader(t.response))
|
||||
|
||||
if t.status == 0 {
|
||||
t.status = 200
|
||||
}
|
||||
statusMsg := "OK"
|
||||
if (t.status < 200) || (299 < t.status) {
|
||||
statusMsg = "Error"
|
||||
}
|
||||
|
||||
rsp = &http.Response{
|
||||
Status: fmt.Sprintf("%d %s", t.status, statusMsg),
|
||||
StatusCode: t.status,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: headers,
|
||||
Body: body,
|
||||
ContentLength: -1,
|
||||
TransferEncoding: nil,
|
||||
Close: true,
|
||||
Trailer: nil,
|
||||
Request: req,
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(bytes, &authContainer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.tenantIdFound = (authContainer.Auth.TenantId != "")
|
||||
|
||||
if t.tenantIdFound != t.expectTenantId {
|
||||
rsp.Status = "500 Internal Server Error"
|
||||
rsp.StatusCode = 500
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func newTransport() *transport {
|
||||
return &transport{}
|
||||
}
|
||||
|
||||
func (t *transport) IgnoreTenantId() *transport {
|
||||
t.expectTenantId = false
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *transport) ExpectTenantId() *transport {
|
||||
t.expectTenantId = true
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *transport) WithResponse(r string) *transport {
|
||||
t.response = r
|
||||
t.status = 200
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *transport) WithError(code int) *transport {
|
||||
t.response = fmt.Sprintf("Error %d", code)
|
||||
t.status = code
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *transport) VerifyCalls(test *testing.T, n int) error {
|
||||
if t.called != n {
|
||||
err := fmt.Errorf("Expected Transport to be called %d times; found %d instead", n, t.called)
|
||||
test.Error(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
[568].out
|
||||
_go*
|
||||
_test*
|
||||
_obj
|
|
@ -0,0 +1,19 @@
|
|||
Copyright 2012 Keith Rarick
|
||||
|
||||
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.
|
|
@ -0,0 +1,9 @@
|
|||
package pretty
|
||||
|
||||
import "github.com/kr/pretty"
|
||||
|
||||
Package pretty provides pretty-printing for Go values.
|
||||
|
||||
Documentation
|
||||
|
||||
http://godoc.org/github.com/kr/pretty
|
5
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/changelog
generated
vendored
Normal file
5
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/changelog
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
golang-pretty (0.0~git20130613-1) unstable; urgency=low
|
||||
|
||||
* Initial release. Closes: #722983
|
||||
|
||||
-- Tonnerre Lombard <tonnerre@ancient-solutions.com> Wed, 11 Sep 2013 02:36:12 +0200
|
1
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/compat
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/compat
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
9
|
22
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/control
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/control
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
Source: golang-pretty
|
||||
Section: devel
|
||||
Priority: extra
|
||||
Maintainer: Tonnerre Lombard <tonnerre@ancient-solutions.com>
|
||||
Build-Depends: debhelper (>= 9), golang-go, dh-golang,
|
||||
golang-text-dev
|
||||
Standards-Version: 3.9.4
|
||||
Homepage: https://github.com/kr/pretty/
|
||||
Vcs-Git: git://anonscm.debian.org/pkg-go/packages/golang-pretty.git
|
||||
Vcs-Browser: http://anonscm.debian.org/gitweb/?p=pkg-go/packages/golang-pretty.git;a=summary
|
||||
|
||||
Package: golang-pretty-dev
|
||||
Architecture: all
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}, golang-text-dev
|
||||
Description: Pretty printing for go values
|
||||
Package pretty provides pretty-printing for Go values. This is useful
|
||||
during debugging, to avoid wrapping long output lines in the
|
||||
terminal.
|
||||
.
|
||||
It provides a function, Formatter, that can be used with any function
|
||||
that accepts a format string. It also provides convenience wrappers
|
||||
for functions in packages fmt and log.
|
30
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/copyright
generated
vendored
Normal file
30
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/copyright
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: golang-pretty
|
||||
Source: https://github.com/kr/pretty/
|
||||
|
||||
Files: *
|
||||
Copyright: 2011, 2012, 2013 Keith Rarick <kr@xph.us>
|
||||
License: Expat
|
||||
|
||||
Files: debian/*
|
||||
Copyright: 2013 Tonnerre Lombard <tonnerre@ancient-solutions.com>
|
||||
License: Expat
|
||||
|
||||
License: Expat
|
||||
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.
|
1
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/docs
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/docs
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Readme
|
11
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/rules
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/rules
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/make -f
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
export DH_VERBOSE=1
|
||||
|
||||
# DH_GOPKG is the upstream path which you would normally “go get”.
|
||||
# Using it allows us to build applications without patching locations.
|
||||
export DH_GOPKG := github.com/kr/pretty
|
||||
|
||||
%:
|
||||
dh $@ --buildsystem=golang --with=golang
|
1
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/source/format
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/debian/source/format
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
3.0 (quilt)
|
|
@ -0,0 +1,148 @@
|
|||
package pretty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type sbuf []string
|
||||
|
||||
func (s *sbuf) Write(b []byte) (int, error) {
|
||||
*s = append(*s, string(b))
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// Diff returns a slice where each element describes
|
||||
// a difference between a and b.
|
||||
func Diff(a, b interface{}) (desc []string) {
|
||||
Fdiff((*sbuf)(&desc), a, b)
|
||||
return desc
|
||||
}
|
||||
|
||||
// Fdiff writes to w a description of the differences between a and b.
|
||||
func Fdiff(w io.Writer, a, b interface{}) {
|
||||
diffWriter{w: w}.diff(reflect.ValueOf(a), reflect.ValueOf(b))
|
||||
}
|
||||
|
||||
type diffWriter struct {
|
||||
w io.Writer
|
||||
l string // label
|
||||
}
|
||||
|
||||
func (w diffWriter) printf(f string, a ...interface{}) {
|
||||
var l string
|
||||
if w.l != "" {
|
||||
l = w.l + ": "
|
||||
}
|
||||
fmt.Fprintf(w.w, l+f, a...)
|
||||
}
|
||||
|
||||
func (w diffWriter) diff(av, bv reflect.Value) {
|
||||
if !av.IsValid() && bv.IsValid() {
|
||||
w.printf("nil != %#v", bv.Interface())
|
||||
return
|
||||
}
|
||||
if av.IsValid() && !bv.IsValid() {
|
||||
w.printf("%#v != nil", av.Interface())
|
||||
return
|
||||
}
|
||||
if !av.IsValid() && !bv.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
at := av.Type()
|
||||
bt := bv.Type()
|
||||
if at != bt {
|
||||
w.printf("%v != %v", at, bt)
|
||||
return
|
||||
}
|
||||
|
||||
// numeric types, including bool
|
||||
if at.Kind() < reflect.Array {
|
||||
a, b := av.Interface(), bv.Interface()
|
||||
if a != b {
|
||||
w.printf("%#v != %#v", a, b)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch at.Kind() {
|
||||
case reflect.String:
|
||||
a, b := av.Interface(), bv.Interface()
|
||||
if a != b {
|
||||
w.printf("%q != %q", a, b)
|
||||
}
|
||||
case reflect.Ptr:
|
||||
switch {
|
||||
case av.IsNil() && !bv.IsNil():
|
||||
w.printf("nil != %v", bv.Interface())
|
||||
case !av.IsNil() && bv.IsNil():
|
||||
w.printf("%v != nil", av.Interface())
|
||||
case !av.IsNil() && !bv.IsNil():
|
||||
w.diff(av.Elem(), bv.Elem())
|
||||
}
|
||||
case reflect.Struct:
|
||||
for i := 0; i < av.NumField(); i++ {
|
||||
w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i))
|
||||
}
|
||||
case reflect.Map:
|
||||
ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys())
|
||||
for _, k := range ak {
|
||||
w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
|
||||
w.printf("%q != (missing)", av.MapIndex(k))
|
||||
}
|
||||
for _, k := range both {
|
||||
w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
|
||||
w.diff(av.MapIndex(k), bv.MapIndex(k))
|
||||
}
|
||||
for _, k := range bk {
|
||||
w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
|
||||
w.printf("(missing) != %q", bv.MapIndex(k))
|
||||
}
|
||||
case reflect.Interface:
|
||||
w.diff(reflect.ValueOf(av.Interface()), reflect.ValueOf(bv.Interface()))
|
||||
default:
|
||||
if !reflect.DeepEqual(av.Interface(), bv.Interface()) {
|
||||
w.printf("%# v != %# v", Formatter(av.Interface()), Formatter(bv.Interface()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d diffWriter) relabel(name string) (d1 diffWriter) {
|
||||
d1 = d
|
||||
if d.l != "" && name[0] != '[' {
|
||||
d1.l += "."
|
||||
}
|
||||
d1.l += name
|
||||
return d1
|
||||
}
|
||||
|
||||
func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) {
|
||||
for _, av := range a {
|
||||
inBoth := false
|
||||
for _, bv := range b {
|
||||
if reflect.DeepEqual(av.Interface(), bv.Interface()) {
|
||||
inBoth = true
|
||||
both = append(both, av)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !inBoth {
|
||||
ak = append(ak, av)
|
||||
}
|
||||
}
|
||||
for _, bv := range b {
|
||||
inBoth := false
|
||||
for _, av := range a {
|
||||
if reflect.DeepEqual(av.Interface(), bv.Interface()) {
|
||||
inBoth = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !inBoth {
|
||||
bk = append(bk, bv)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
73
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/diff_test.go
generated
vendored
Normal file
73
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/diff_test.go
generated
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
package pretty
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type difftest struct {
|
||||
a interface{}
|
||||
b interface{}
|
||||
exp []string
|
||||
}
|
||||
|
||||
type S struct {
|
||||
A int
|
||||
S *S
|
||||
I interface{}
|
||||
C []int
|
||||
}
|
||||
|
||||
var diffs = []difftest{
|
||||
{a: nil, b: nil},
|
||||
{a: S{A: 1}, b: S{A: 1}},
|
||||
|
||||
{0, "", []string{`int != string`}},
|
||||
{0, 1, []string{`0 != 1`}},
|
||||
{S{}, new(S), []string{`pretty.S != *pretty.S`}},
|
||||
{"a", "b", []string{`"a" != "b"`}},
|
||||
{S{}, S{A: 1}, []string{`A: 0 != 1`}},
|
||||
{new(S), &S{A: 1}, []string{`A: 0 != 1`}},
|
||||
{S{S: new(S)}, S{S: &S{A: 1}}, []string{`S.A: 0 != 1`}},
|
||||
{S{}, S{I: 0}, []string{`I: nil != 0`}},
|
||||
{S{I: 1}, S{I: "x"}, []string{`I: int != string`}},
|
||||
{S{}, S{C: []int{1}}, []string{`C: []int(nil) != []int{1}`}},
|
||||
{S{C: []int{}}, S{C: []int{1}}, []string{`C: []int{} != []int{1}`}},
|
||||
{S{}, S{A: 1, S: new(S)}, []string{`A: 0 != 1`, `S: nil != &{0 <nil> <nil> []}`}},
|
||||
}
|
||||
|
||||
func TestDiff(t *testing.T) {
|
||||
for _, tt := range diffs {
|
||||
got := Diff(tt.a, tt.b)
|
||||
eq := len(got) == len(tt.exp)
|
||||
if eq {
|
||||
for i := range got {
|
||||
eq = eq && got[i] == tt.exp[i]
|
||||
}
|
||||
}
|
||||
if !eq {
|
||||
t.Errorf("diffing % #v", tt.a)
|
||||
t.Errorf("with % #v", tt.b)
|
||||
diffdiff(t, got, tt.exp)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func diffdiff(t *testing.T, got, exp []string) {
|
||||
minus(t, "unexpected:", got, exp)
|
||||
minus(t, "missing:", exp, got)
|
||||
}
|
||||
|
||||
func minus(t *testing.T, s string, a, b []string) {
|
||||
var i, j int
|
||||
for i = 0; i < len(a); i++ {
|
||||
for j = 0; j < len(b); j++ {
|
||||
if a[i] == b[j] {
|
||||
break
|
||||
}
|
||||
}
|
||||
if j == len(b) {
|
||||
t.Error(s, a[i])
|
||||
}
|
||||
}
|
||||
}
|
20
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/example_test.go
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
package pretty_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kr/pretty"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
type myType struct {
|
||||
a, b int
|
||||
}
|
||||
var x = []myType{{1, 2}, {3, 4}, {5, 6}}
|
||||
fmt.Printf("%# v", pretty.Formatter(x))
|
||||
// output:
|
||||
// []pretty_test.myType{
|
||||
// {a:1, b:2},
|
||||
// {a:3, b:4},
|
||||
// {a:5, b:6},
|
||||
// }
|
||||
}
|
300
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/formatter.go
generated
vendored
Normal file
300
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/formatter.go
generated
vendored
Normal file
|
@ -0,0 +1,300 @@
|
|||
package pretty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kr/text"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
const (
|
||||
limit = 50
|
||||
)
|
||||
|
||||
type formatter struct {
|
||||
x interface{}
|
||||
force bool
|
||||
quote bool
|
||||
}
|
||||
|
||||
// Formatter makes a wrapper, f, that will format x as go source with line
|
||||
// breaks and tabs. Object f responds to the "%v" formatting verb when both the
|
||||
// "#" and " " (space) flags are set, for example:
|
||||
//
|
||||
// fmt.Sprintf("%# v", Formatter(x))
|
||||
//
|
||||
// If one of these two flags is not set, or any other verb is used, f will
|
||||
// format x according to the usual rules of package fmt.
|
||||
// In particular, if x satisfies fmt.Formatter, then x.Format will be called.
|
||||
func Formatter(x interface{}) (f fmt.Formatter) {
|
||||
return formatter{x: x, quote: true}
|
||||
}
|
||||
|
||||
func (fo formatter) String() string {
|
||||
return fmt.Sprint(fo.x) // unwrap it
|
||||
}
|
||||
|
||||
func (fo formatter) passThrough(f fmt.State, c rune) {
|
||||
s := "%"
|
||||
for i := 0; i < 128; i++ {
|
||||
if f.Flag(i) {
|
||||
s += string(i)
|
||||
}
|
||||
}
|
||||
if w, ok := f.Width(); ok {
|
||||
s += fmt.Sprintf("%d", w)
|
||||
}
|
||||
if p, ok := f.Precision(); ok {
|
||||
s += fmt.Sprintf(".%d", p)
|
||||
}
|
||||
s += string(c)
|
||||
fmt.Fprintf(f, s, fo.x)
|
||||
}
|
||||
|
||||
func (fo formatter) Format(f fmt.State, c rune) {
|
||||
if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') {
|
||||
w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0)
|
||||
p := &printer{tw: w, Writer: w}
|
||||
p.printValue(reflect.ValueOf(fo.x), true, fo.quote)
|
||||
w.Flush()
|
||||
return
|
||||
}
|
||||
fo.passThrough(f, c)
|
||||
}
|
||||
|
||||
type printer struct {
|
||||
io.Writer
|
||||
tw *tabwriter.Writer
|
||||
}
|
||||
|
||||
func (p *printer) indent() *printer {
|
||||
q := *p
|
||||
q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0)
|
||||
q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'})
|
||||
return &q
|
||||
}
|
||||
|
||||
func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) {
|
||||
if showType {
|
||||
io.WriteString(p, v.Type().String())
|
||||
fmt.Fprintf(p, "(%#v)", x)
|
||||
} else {
|
||||
fmt.Fprintf(p, "%#v", x)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *printer) printValue(v reflect.Value, showType, quote bool) {
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
p.printInline(v, v.Bool(), showType)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
p.printInline(v, v.Int(), showType)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
p.printInline(v, v.Uint(), showType)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
p.printInline(v, v.Float(), showType)
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
fmt.Fprintf(p, "%#v", v.Complex())
|
||||
case reflect.String:
|
||||
p.fmtString(v.String(), quote)
|
||||
case reflect.Map:
|
||||
t := v.Type()
|
||||
if showType {
|
||||
io.WriteString(p, t.String())
|
||||
}
|
||||
writeByte(p, '{')
|
||||
if nonzero(v) {
|
||||
expand := !canInline(v.Type())
|
||||
pp := p
|
||||
if expand {
|
||||
writeByte(p, '\n')
|
||||
pp = p.indent()
|
||||
}
|
||||
keys := v.MapKeys()
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
showTypeInStruct := true
|
||||
k := keys[i]
|
||||
mv := v.MapIndex(k)
|
||||
pp.printValue(k, false, true)
|
||||
writeByte(pp, ':')
|
||||
if expand {
|
||||
writeByte(pp, '\t')
|
||||
}
|
||||
showTypeInStruct = t.Elem().Kind() == reflect.Interface
|
||||
pp.printValue(mv, showTypeInStruct, true)
|
||||
if expand {
|
||||
io.WriteString(pp, ",\n")
|
||||
} else if i < v.Len()-1 {
|
||||
io.WriteString(pp, ", ")
|
||||
}
|
||||
}
|
||||
if expand {
|
||||
pp.tw.Flush()
|
||||
}
|
||||
}
|
||||
writeByte(p, '}')
|
||||
case reflect.Struct:
|
||||
t := v.Type()
|
||||
if showType {
|
||||
io.WriteString(p, t.String())
|
||||
}
|
||||
writeByte(p, '{')
|
||||
if nonzero(v) {
|
||||
expand := !canInline(v.Type())
|
||||
pp := p
|
||||
if expand {
|
||||
writeByte(p, '\n')
|
||||
pp = p.indent()
|
||||
}
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
showTypeInStruct := true
|
||||
if f := t.Field(i); f.Name != "" {
|
||||
io.WriteString(pp, f.Name)
|
||||
writeByte(pp, ':')
|
||||
if expand {
|
||||
writeByte(pp, '\t')
|
||||
}
|
||||
showTypeInStruct = f.Type.Kind() == reflect.Interface
|
||||
}
|
||||
pp.printValue(getField(v, i), showTypeInStruct, true)
|
||||
if expand {
|
||||
io.WriteString(pp, ",\n")
|
||||
} else if i < v.NumField()-1 {
|
||||
io.WriteString(pp, ", ")
|
||||
}
|
||||
}
|
||||
if expand {
|
||||
pp.tw.Flush()
|
||||
}
|
||||
}
|
||||
writeByte(p, '}')
|
||||
case reflect.Interface:
|
||||
switch e := v.Elem(); {
|
||||
case e.Kind() == reflect.Invalid:
|
||||
io.WriteString(p, "nil")
|
||||
case e.IsValid():
|
||||
p.printValue(e, showType, true)
|
||||
default:
|
||||
io.WriteString(p, v.Type().String())
|
||||
io.WriteString(p, "(nil)")
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
t := v.Type()
|
||||
if showType {
|
||||
io.WriteString(p, t.String())
|
||||
}
|
||||
if v.Kind() == reflect.Slice && v.IsNil() && showType {
|
||||
io.WriteString(p, "(nil)")
|
||||
break
|
||||
}
|
||||
if v.Kind() == reflect.Slice && v.IsNil() {
|
||||
io.WriteString(p, "nil")
|
||||
break
|
||||
}
|
||||
writeByte(p, '{')
|
||||
expand := !canInline(v.Type())
|
||||
pp := p
|
||||
if expand {
|
||||
writeByte(p, '\n')
|
||||
pp = p.indent()
|
||||
}
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
showTypeInSlice := t.Elem().Kind() == reflect.Interface
|
||||
pp.printValue(v.Index(i), showTypeInSlice, true)
|
||||
if expand {
|
||||
io.WriteString(pp, ",\n")
|
||||
} else if i < v.Len()-1 {
|
||||
io.WriteString(pp, ", ")
|
||||
}
|
||||
}
|
||||
if expand {
|
||||
pp.tw.Flush()
|
||||
}
|
||||
writeByte(p, '}')
|
||||
case reflect.Ptr:
|
||||
e := v.Elem()
|
||||
if !e.IsValid() {
|
||||
writeByte(p, '(')
|
||||
io.WriteString(p, v.Type().String())
|
||||
io.WriteString(p, ")(nil)")
|
||||
} else {
|
||||
writeByte(p, '&')
|
||||
p.printValue(e, true, true)
|
||||
}
|
||||
case reflect.Chan:
|
||||
x := v.Pointer()
|
||||
if showType {
|
||||
writeByte(p, '(')
|
||||
io.WriteString(p, v.Type().String())
|
||||
fmt.Fprintf(p, ")(%#v)", x)
|
||||
} else {
|
||||
fmt.Fprintf(p, "%#v", x)
|
||||
}
|
||||
case reflect.Func:
|
||||
io.WriteString(p, v.Type().String())
|
||||
io.WriteString(p, " {...}")
|
||||
case reflect.UnsafePointer:
|
||||
p.printInline(v, v.Pointer(), showType)
|
||||
case reflect.Invalid:
|
||||
io.WriteString(p, "nil")
|
||||
}
|
||||
}
|
||||
|
||||
func canInline(t reflect.Type) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Map:
|
||||
return !canExpand(t.Elem())
|
||||
case reflect.Struct:
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
if canExpand(t.Field(i).Type) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Interface:
|
||||
return false
|
||||
case reflect.Array, reflect.Slice:
|
||||
return !canExpand(t.Elem())
|
||||
case reflect.Ptr:
|
||||
return false
|
||||
case reflect.Chan, reflect.Func, reflect.UnsafePointer:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func canExpand(t reflect.Type) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Map, reflect.Struct,
|
||||
reflect.Interface, reflect.Array, reflect.Slice,
|
||||
reflect.Ptr:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *printer) fmtString(s string, quote bool) {
|
||||
if quote {
|
||||
s = strconv.Quote(s)
|
||||
}
|
||||
io.WriteString(p, s)
|
||||
}
|
||||
|
||||
func tryDeepEqual(a, b interface{}) bool {
|
||||
defer func() { recover() }()
|
||||
return reflect.DeepEqual(a, b)
|
||||
}
|
||||
|
||||
func writeByte(w io.Writer, b byte) {
|
||||
w.Write([]byte{b})
|
||||
}
|
||||
|
||||
func getField(v reflect.Value, i int) reflect.Value {
|
||||
val := v.Field(i)
|
||||
if val.Kind() == reflect.Interface && !val.IsNil() {
|
||||
val = val.Elem()
|
||||
}
|
||||
return val
|
||||
}
|
146
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/formatter_test.go
generated
vendored
Normal file
146
Godeps/_workspace/src/github.com/tonnerre/golang-pretty/formatter_test.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
|||
package pretty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type test struct {
|
||||
v interface{}
|
||||
s string
|
||||
}
|
||||
|
||||
type LongStructTypeName struct {
|
||||
longFieldName interface{}
|
||||
otherLongFieldName interface{}
|
||||
}
|
||||
|
||||
type SA struct {
|
||||
t *T
|
||||
}
|
||||
|
||||
type T struct {
|
||||
x, y int
|
||||
}
|
||||
|
||||
type F int
|
||||
|
||||
func (f F) Format(s fmt.State, c rune) {
|
||||
fmt.Fprintf(s, "F(%d)", int(f))
|
||||
}
|
||||
|
||||
var long = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
var gosyntax = []test{
|
||||
{nil, `nil`},
|
||||
{"", `""`},
|
||||
{"a", `"a"`},
|
||||
{1, "int(1)"},
|
||||
{1.0, "float64(1)"},
|
||||
{[]int(nil), "[]int(nil)"},
|
||||
{[0]int{}, "[0]int{}"},
|
||||
{complex(1, 0), "(1+0i)"},
|
||||
//{make(chan int), "(chan int)(0x1234)"},
|
||||
{unsafe.Pointer(uintptr(1)), "unsafe.Pointer(0x1)"},
|
||||
{func(int) {}, "func(int) {...}"},
|
||||
{map[int]int{1: 1}, "map[int]int{1:1}"},
|
||||
{int32(1), "int32(1)"},
|
||||
{io.EOF, `&errors.errorString{s:"EOF"}`},
|
||||
{[]string{"a"}, `[]string{"a"}`},
|
||||
{
|
||||
[]string{long},
|
||||
`[]string{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"}`,
|
||||
},
|
||||
{F(5), "pretty.F(5)"},
|
||||
{
|
||||
SA{&T{1, 2}},
|
||||
`pretty.SA{
|
||||
t: &pretty.T{x:1, y:2},
|
||||
}`,
|
||||
},
|
||||
{
|
||||
map[int][]byte{1: []byte{}},
|
||||
`map[int][]uint8{
|
||||
1: {},
|
||||
}`,
|
||||
},
|
||||
{
|
||||
map[int]T{1: T{}},
|
||||
`map[int]pretty.T{
|
||||
1: {},
|
||||
}`,
|
||||
},
|
||||
{
|
||||
long,
|
||||
`"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"`,
|
||||
},
|
||||
{
|
||||
LongStructTypeName{
|
||||
longFieldName: LongStructTypeName{},
|
||||
otherLongFieldName: long,
|
||||
},
|
||||
`pretty.LongStructTypeName{
|
||||
longFieldName: pretty.LongStructTypeName{},
|
||||
otherLongFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
|
||||
}`,
|
||||
},
|
||||
{
|
||||
&LongStructTypeName{
|
||||
longFieldName: &LongStructTypeName{},
|
||||
otherLongFieldName: (*LongStructTypeName)(nil),
|
||||
},
|
||||
`&pretty.LongStructTypeName{
|
||||
longFieldName: &pretty.LongStructTypeName{},
|
||||
otherLongFieldName: (*pretty.LongStructTypeName)(nil),
|
||||
}`,
|
||||
},
|
||||
{
|
||||
[]LongStructTypeName{
|
||||
{nil, nil},
|
||||
{3, 3},
|
||||
{long, nil},
|
||||
},
|
||||
`[]pretty.LongStructTypeName{
|
||||
{},
|
||||
{
|
||||
longFieldName: int(3),
|
||||
otherLongFieldName: int(3),
|
||||
},
|
||||
{
|
||||
longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
|
||||
otherLongFieldName: nil,
|
||||
},
|
||||
}`,
|
||||
},
|
||||
{
|
||||
[]interface{}{
|
||||
LongStructTypeName{nil, nil},
|
||||
[]byte{1, 2, 3},
|
||||
T{3, 4},
|
||||
LongStructTypeName{long, nil},
|
||||
},
|
||||
`[]interface {}{
|
||||
pretty.LongStructTypeName{},
|
||||
[]uint8{0x1, 0x2, 0x3},
|
||||
pretty.T{x:3, y:4},
|
||||
pretty.LongStructTypeName{
|
||||
longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
|
||||
otherLongFieldName: nil,
|
||||
},
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestGoSyntax(t *testing.T) {
|
||||
for _, tt := range gosyntax {
|
||||
s := fmt.Sprintf("%# v", Formatter(tt.v))
|
||||
if tt.s != s {
|
||||
t.Errorf("expected %q", tt.s)
|
||||
t.Errorf("got %q", s)
|
||||
t.Errorf("expraw\n%s", tt.s)
|
||||
t.Errorf("gotraw\n%s", s)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
// Package pretty provides pretty-printing for Go values. This is
|
||||
// useful during debugging, to avoid wrapping long output lines in
|
||||
// the terminal.
|
||||
//
|
||||
// It provides a function, Formatter, that can be used with any
|
||||
// function that accepts a format string. It also provides
|
||||
// convenience wrappers for functions in packages fmt and log.
|
||||
package pretty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Errorf is a convenience wrapper for fmt.Errorf.
|
||||
//
|
||||
// Calling Errorf(f, x, y) is equivalent to
|
||||
// fmt.Errorf(f, Formatter(x), Formatter(y)).
|
||||
func Errorf(format string, a ...interface{}) error {
|
||||
return fmt.Errorf(format, wrap(a, false)...)
|
||||
}
|
||||
|
||||
// Fprintf is a convenience wrapper for fmt.Fprintf.
|
||||
//
|
||||
// Calling Fprintf(w, f, x, y) is equivalent to
|
||||
// fmt.Fprintf(w, f, Formatter(x), Formatter(y)).
|
||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, error error) {
|
||||
return fmt.Fprintf(w, format, wrap(a, false)...)
|
||||
}
|
||||
|
||||
// Log is a convenience wrapper for log.Printf.
|
||||
//
|
||||
// Calling Log(x, y) is equivalent to
|
||||
// log.Print(Formatter(x), Formatter(y)), but each operand is
|
||||
// formatted with "%# v".
|
||||
func Log(a ...interface{}) {
|
||||
log.Print(wrap(a, true)...)
|
||||
}
|
||||
|
||||
// Logf is a convenience wrapper for log.Printf.
|
||||
//
|
||||
// Calling Logf(f, x, y) is equivalent to
|
||||
// log.Printf(f, Formatter(x), Formatter(y)).
|
||||
func Logf(format string, a ...interface{}) {
|
||||
log.Printf(format, wrap(a, false)...)
|
||||
}
|
||||
|
||||
// Logln is a convenience wrapper for log.Printf.
|
||||
//
|
||||
// Calling Logln(x, y) is equivalent to
|
||||
// log.Println(Formatter(x), Formatter(y)), but each operand is
|
||||
// formatted with "%# v".
|
||||
func Logln(a ...interface{}) {
|
||||
log.Println(wrap(a, true)...)
|
||||
}
|
||||
|
||||
// Print pretty-prints its operands and writes to standard output.
|
||||
//
|
||||
// Calling Print(x, y) is equivalent to
|
||||
// fmt.Print(Formatter(x), Formatter(y)), but each operand is
|
||||
// formatted with "%# v".
|
||||
func Print(a ...interface{}) (n int, errno error) {
|
||||
return fmt.Print(wrap(a, true)...)
|
||||
}
|
||||
|
||||
// Printf is a convenience wrapper for fmt.Printf.
|
||||
//
|
||||
// Calling Printf(f, x, y) is equivalent to
|
||||
// fmt.Printf(f, Formatter(x), Formatter(y)).
|
||||
func Printf(format string, a ...interface{}) (n int, errno error) {
|
||||
return fmt.Printf(format, wrap(a, false)...)
|
||||
}
|
||||
|
||||
// Println pretty-prints its operands and writes to standard output.
|
||||
//
|
||||
// Calling Print(x, y) is equivalent to
|
||||
// fmt.Println(Formatter(x), Formatter(y)), but each operand is
|
||||
// formatted with "%# v".
|
||||
func Println(a ...interface{}) (n int, errno error) {
|
||||
return fmt.Println(wrap(a, true)...)
|
||||
}
|
||||
|
||||
// Sprintf is a convenience wrapper for fmt.Sprintf.
|
||||
//
|
||||
// Calling Sprintf(f, x, y) is equivalent to
|
||||
// fmt.Sprintf(f, Formatter(x), Formatter(y)).
|
||||
func Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, wrap(a, false)...)
|
||||
}
|
||||
|
||||
func wrap(a []interface{}, force bool) []interface{} {
|
||||
w := make([]interface{}, len(a))
|
||||
for i, x := range a {
|
||||
w[i] = formatter{x: x, force: force}
|
||||
}
|
||||
return w
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package pretty
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func nonzero(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
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.Complex64, reflect.Complex128:
|
||||
return v.Complex() != complex(0, 0)
|
||||
case reflect.String:
|
||||
return v.String() != ""
|
||||
case reflect.Struct:
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if nonzero(getField(v, i)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case reflect.Array:
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if nonzero(v.Index(i)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case reflect.Map, reflect.Interface, reflect.Slice, reflect.Ptr, reflect.Chan, reflect.Func:
|
||||
return !v.IsNil()
|
||||
case reflect.UnsafePointer:
|
||||
return v.Pointer() != 0
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -22,6 +22,7 @@ package main
|
|||
import (
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/aws"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/gce"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/openstack"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/ovirt"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/vagrant"
|
||||
)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# Maintainers
|
||||
|
||||
* [Angus Lees](https://github.com/anguslees)
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
||||
"code.google.com/p/gcfg"
|
||||
"github.com/rackspace/gophercloud"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
)
|
||||
|
||||
var ErrServerNotFound = errors.New("Server not found")
|
||||
var ErrMultipleServersFound = errors.New("Multiple servers matched query")
|
||||
var ErrFlavorNotFound = errors.New("Flavor not found")
|
||||
|
||||
// OpenStack is an implementation of cloud provider Interface for OpenStack.
|
||||
type OpenStack struct {
|
||||
provider string
|
||||
authOpt gophercloud.AuthOptions
|
||||
region string
|
||||
access *gophercloud.Access
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Global struct {
|
||||
AuthUrl string
|
||||
Username, Password string
|
||||
ApiKey string
|
||||
TenantId, TenantName string
|
||||
Region string
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloudprovider.RegisterCloudProvider("openstack", func(config io.Reader) (cloudprovider.Interface, error) {
|
||||
cfg, err := readConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newOpenStack(cfg)
|
||||
})
|
||||
}
|
||||
|
||||
func (cfg Config) toAuthOptions() gophercloud.AuthOptions {
|
||||
return gophercloud.AuthOptions{
|
||||
Username: cfg.Global.Username,
|
||||
Password: cfg.Global.Password,
|
||||
ApiKey: cfg.Global.ApiKey,
|
||||
TenantId: cfg.Global.TenantId,
|
||||
TenantName: cfg.Global.TenantName,
|
||||
|
||||
// Persistent service, so we need to be able to renew tokens
|
||||
AllowReauth: true,
|
||||
}
|
||||
}
|
||||
|
||||
func readConfig(config io.Reader) (Config, error) {
|
||||
if config == nil {
|
||||
err := fmt.Errorf("No OpenStack cloud provider config file given")
|
||||
return Config{}, err
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
err := gcfg.ReadInto(&cfg, config)
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func newOpenStack(cfg Config) (*OpenStack, error) {
|
||||
os := OpenStack{
|
||||
provider: cfg.Global.AuthUrl,
|
||||
authOpt: cfg.toAuthOptions(),
|
||||
region: cfg.Global.Region,
|
||||
}
|
||||
|
||||
access, err := gophercloud.Authenticate(os.provider, os.authOpt)
|
||||
os.access = access
|
||||
|
||||
return &os, err
|
||||
}
|
||||
|
||||
type Instances struct {
|
||||
servers gophercloud.CloudServersProvider
|
||||
flavor_to_resource map[string]*api.NodeResources // keyed by flavor id
|
||||
}
|
||||
|
||||
// Instances returns an implementation of Instances for OpenStack.
|
||||
func (os *OpenStack) Instances() (cloudprovider.Instances, bool) {
|
||||
servers, err := gophercloud.ServersApi(os.access, gophercloud.ApiCriteria{
|
||||
Type: "compute",
|
||||
UrlChoice: gophercloud.PublicURL,
|
||||
Region: os.region,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
flavors, err := servers.ListFlavors()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
flavor_to_resource := make(map[string]*api.NodeResources, len(flavors))
|
||||
for _, flavor := range flavors {
|
||||
rsrc := api.NodeResources{
|
||||
Capacity: api.ResourceList{
|
||||
"cpu": util.NewIntOrStringFromInt(flavor.VCpus),
|
||||
"memory": util.NewIntOrStringFromString(fmt.Sprintf("%dMiB", flavor.Ram)),
|
||||
"openstack.org/disk": util.NewIntOrStringFromString(fmt.Sprintf("%dGB", flavor.Disk)),
|
||||
"openstack.org/rxTxFactor": util.NewIntOrStringFromInt(int(flavor.RxTxFactor * 1000)),
|
||||
"openstack.org/swap": util.NewIntOrStringFromString(fmt.Sprintf("%dMiB", flavor.Swap)),
|
||||
},
|
||||
}
|
||||
flavor_to_resource[flavor.Id] = &rsrc
|
||||
}
|
||||
|
||||
return &Instances{servers, flavor_to_resource}, true
|
||||
}
|
||||
|
||||
func (i *Instances) List(name_filter string) ([]string, error) {
|
||||
filter := url.Values{}
|
||||
filter.Set("name", name_filter)
|
||||
filter.Set("status", "ACTIVE")
|
||||
|
||||
servers, err := i.servers.ListServersByFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := make([]string, len(servers))
|
||||
for idx, srv := range servers {
|
||||
ret[idx] = srv.Name
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func getServerByName(api gophercloud.CloudServersProvider, name string) (*gophercloud.Server, error) {
|
||||
filter := url.Values{}
|
||||
filter.Set("name", fmt.Sprintf("^%s$", regexp.QuoteMeta(name)))
|
||||
filter.Set("status", "ACTIVE")
|
||||
|
||||
servers, err := api.ListServersByFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(servers) == 0 {
|
||||
return nil, ErrServerNotFound
|
||||
} else if len(servers) > 1 {
|
||||
return nil, ErrMultipleServersFound
|
||||
}
|
||||
|
||||
return &servers[0], nil
|
||||
}
|
||||
|
||||
func (i *Instances) IPAddress(name string) (net.IP, error) {
|
||||
srv, err := getServerByName(i.servers, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var s string
|
||||
if len(srv.Addresses.Private) > 0 {
|
||||
s = srv.Addresses.Private[0].Addr
|
||||
} else if len(srv.Addresses.Public) > 0 {
|
||||
s = srv.Addresses.Public[0].Addr
|
||||
} else if srv.AccessIPv4 != "" {
|
||||
s = srv.AccessIPv4
|
||||
} else {
|
||||
s = srv.AccessIPv6
|
||||
}
|
||||
return net.ParseIP(s), nil
|
||||
}
|
||||
|
||||
func (i *Instances) GetNodeResources(name string) (*api.NodeResources, error) {
|
||||
srv, err := getServerByName(i.servers, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rsrc, ok := i.flavor_to_resource[srv.Flavor.Id]
|
||||
if !ok {
|
||||
return nil, ErrFlavorNotFound
|
||||
}
|
||||
|
||||
return rsrc, nil
|
||||
}
|
||||
|
||||
func (os *OpenStack) TCPLoadBalancer() (cloudprovider.TCPLoadBalancer, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (os *OpenStack) Zones() (cloudprovider.Zones, bool) {
|
||||
return nil, false
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue