From 87cf7e222e101caed7992c7001e95dc3b5054c19 Mon Sep 17 00:00:00 2001 From: Sergio Botero Date: Fri, 10 Jul 2015 21:07:40 -0600 Subject: [PATCH 01/36] Add status command closes #1471 by adding the status command that prints the state of a machine. Signed-off-by: Sergio Botero --- commands/commands.go | 6 ++++++ commands/status.go | 18 ++++++++++++++++++ commands/status_test.go | 1 + docs/reference/index.md | 3 ++- docs/reference/status.md | 18 ++++++++++++++++++ 5 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 commands/status.go create mode 100644 commands/status_test.go create mode 100644 docs/reference/status.md diff --git a/commands/commands.go b/commands/commands.go index 9f85f051d5..e0d0079e12 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -379,6 +379,12 @@ var Commands = []cli.Command{ Description: "Argument(s) are one or more machine names.", Action: cmdStart, }, + { + Name: "status", + Usage: "Get the status of a machine", + Description: "Argument is a machine name.", + Action: cmdStatus, + }, { Name: "stop", Usage: "Stop a machine", diff --git a/commands/status.go b/commands/status.go new file mode 100644 index 0000000000..d775bba85d --- /dev/null +++ b/commands/status.go @@ -0,0 +1,18 @@ +package commands + +import ( + "fmt" + + "github.com/docker/machine/log" + + "github.com/codegangsta/cli" +) + +func cmdStatus(c *cli.Context) { + host := getHost(c) + currentState, err := host.Driver.GetState() + if err != nil { + log.Errorf("error getting state for host %s: %s", host.Name, err) + } + fmt.Println(currentState) +} diff --git a/commands/status_test.go b/commands/status_test.go new file mode 100644 index 0000000000..cdff10da75 --- /dev/null +++ b/commands/status_test.go @@ -0,0 +1 @@ +package commands diff --git a/docs/reference/index.md b/docs/reference/index.md index fa4515862b..3976e2a84c 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -27,6 +27,7 @@ parent="smn_machine_ref" * [scp](scp.md) * [ssh](ssh.md) * [start](start.md) +* [status](status.md) * [stop](stop.md) * [upgrade](upgrade.md) -* [url](url.md) \ No newline at end of file +* [url](url.md) diff --git a/docs/reference/status.md b/docs/reference/status.md new file mode 100644 index 0000000000..815800c934 --- /dev/null +++ b/docs/reference/status.md @@ -0,0 +1,18 @@ + + +# status + +Get the status of a machine. + +``` +$ docker-machine status dev +Running +``` From 918ed6a135446dcf14519fd584f4dd165826859e Mon Sep 17 00:00:00 2001 From: Sergio Botero Date: Tue, 14 Jul 2015 00:18:08 -0700 Subject: [PATCH 02/36] Add integration test for status command Use `log.Info` instead of `fmt.Println` Signed-off-by: Sergio Botero --- commands/status.go | 4 +--- test/integration/core/core-commands.bats | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/commands/status.go b/commands/status.go index d775bba85d..1247d31235 100644 --- a/commands/status.go +++ b/commands/status.go @@ -1,8 +1,6 @@ package commands import ( - "fmt" - "github.com/docker/machine/log" "github.com/codegangsta/cli" @@ -14,5 +12,5 @@ func cmdStatus(c *cli.Context) { if err != nil { log.Errorf("error getting state for host %s: %s", host.Name, err) } - fmt.Println(currentState) + log.Info(currentState) } diff --git a/test/integration/core/core-commands.bats b/test/integration/core/core-commands.bats index 70c220ce5e..b79481685b 100644 --- a/test/integration/core/core-commands.bats +++ b/test/integration/core/core-commands.bats @@ -104,3 +104,9 @@ load ${BASE_TEST_DIR}/helpers.bash [ "$status" -eq 0 ] [[ ${lines[1]} == *"Running"* ]] } + +@test "$DRIVER: status" { + run machine status $NAME + [ "$status" -eq 0 ] + [[ ${output} == *"Running"* ]] +} From d553a2c7574d88dc65c48f516dddb3a8e40930b9 Mon Sep 17 00:00:00 2001 From: Ryan Grothouse Date: Thu, 9 Jul 2015 16:40:18 -0400 Subject: [PATCH 03/36] added support for setting environment variables in docker engines Signed-off-by: Ryan Grothouse --- commands/commands.go | 5 +++++ commands/create.go | 1 + docs/index.md | 4 ++-- docs/reference/create.md | 12 ++++++++++++ libmachine/engine/engine.go | 1 + libmachine/provision/boot2docker.go | 3 +++ libmachine/provision/debian.go | 1 + libmachine/provision/generic.go | 2 ++ libmachine/provision/redhat.go | 1 + libmachine/provision/utils.go | 2 +- .../core/arbitrary-engine-envs.bats | 19 +++++++++++++++++++ 11 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 test/integration/core/arbitrary-engine-envs.bats diff --git a/commands/commands.go b/commands/commands.go index 9f85f051d5..a9c2a10f6d 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -196,6 +196,11 @@ var sharedCreateFlags = []cli.Flag{ Name: "engine-storage-driver", Usage: "Specify a storage driver to use with the engine", }, + cli.StringSliceFlag{ + Name: "engine-env", + Usage: "Specify environment variables to set in the engine", + Value: &cli.StringSlice{}, + }, cli.BoolFlag{ Name: "swarm", Usage: "Configure Machine with Swarm", diff --git a/commands/create.go b/commands/create.go index 0a6890bf0f..e69ac3c539 100644 --- a/commands/create.go +++ b/commands/create.go @@ -70,6 +70,7 @@ func cmdCreate(c *cli.Context) { }, EngineOptions: &engine.EngineOptions{ ArbitraryFlags: c.StringSlice("engine-opt"), + Env: c.StringSlice("engine-env"), InsecureRegistry: c.StringSlice("engine-insecure-registry"), Labels: c.StringSlice("engine-label"), RegistryMirror: c.StringSlice("engine-registry-mirror"), diff --git a/docs/index.md b/docs/index.md index cd61b34b42..f4a9405790 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,7 +20,7 @@ them, then configures the `docker` client to talk to them. A "machine" is the combination of a Docker host and a configured client. Once you create one or more Docker hosts, Docker Machine supplies a number of commands for -managing them. Using these commands you can +managing them. Using these commands you can - start, inspect, stop, and restart a host - upgrade the Docker client and daemon @@ -28,7 +28,7 @@ managing them. Using these commands you can ## Understand Docker Machine basic concepts -Docker Machine allows you to provision Docker on virtual machines that reside either on your local system or on a cloud provider. Docker Machine creates a host on a VM and you use the Docker Engine client as needed to build images and create containers on the host. +Docker Machine allows you to provision Docker on virtual machines that reside either on your local system or on a cloud provider. Docker Machine creates a host on a VM and you use the Docker Engine client as needed to build images and create containers on the host. To create a virtual machine, you supply Docker Machine with the name of the driver you want use. The driver represents the virtual environment. For example, on a local Linux, Mac, or Windows system the driver is typically Oracle Virtual Box. For cloud providers, Docker Machine supports drivers such as AWS, Microsoft Azure, Digital Ocean and many more. The Docker Machine reference includes a complete [list of the supported drivers](/drivers). diff --git a/docs/reference/create.md b/docs/reference/create.md index 95e1824b5b..b6dd61c314 100644 --- a/docs/reference/create.md +++ b/docs/reference/create.md @@ -60,6 +60,7 @@ Options: --engine-registry-mirror [--engine-registry-mirror option --engine-registry-mirror option] Specify registry mirrors to use --engine-label [--engine-label option --engine-label option] Specify labels for the created engine --engine-storage-driver "aufs" Specify a storage driver to use with the engine + --engine-env Specify environment variables to set in the engine --swarm Configure Machine with Swarm --swarm-master Configure Machine to be a Swarm master --swarm-discovery Discovery service to use with Swarm @@ -146,6 +147,17 @@ $ docker-machine create -d virtualbox \ gdns ``` +Additionally, Docker Machine supports a flag, `--engine-env`, which can be used to +specify arbitrary environment variables to be set within the engine with the syntax `--engine-env name=value`. For example, to specify that the engine should use `example.com` as the proxy server, you could run the following create command: + +``` +$ docker-machine create -d virtualbox \ + --engine-env HTTP_PROXY=http://example.com:8080 \ + --engine-env HTTPS_PROXY=https://example.com:8080 \ + --engine-env NO_PROXY=example2.com \ + proxbox +``` + ## Specifying Docker Swarm options for the created machine In addition to being able to configure Docker Engine options as listed above, diff --git a/libmachine/engine/engine.go b/libmachine/engine/engine.go index d829e14022..39049106dc 100644 --- a/libmachine/engine/engine.go +++ b/libmachine/engine/engine.go @@ -4,6 +4,7 @@ type EngineOptions struct { ArbitraryFlags []string Dns []string GraphDir string + Env []string Ipv6 bool InsecureRegistry []string Labels []string diff --git a/libmachine/provision/boot2docker.go b/libmachine/provision/boot2docker.go index f7cdf9602f..1486181b7b 100644 --- a/libmachine/provision/boot2docker.go +++ b/libmachine/provision/boot2docker.go @@ -140,6 +140,9 @@ DOCKER_STORAGE={{.EngineOptions.StorageDriver}} DOCKER_TLS=auto SERVERKEY={{.AuthOptions.ServerKeyRemotePath}} SERVERCERT={{.AuthOptions.ServerCertRemotePath}} + +{{range .EngineOptions.Env}}export \"{{ printf "%q" . }}\" +{{end}} ` t, err := template.New("engineConfig").Parse(engineConfigTmpl) if err != nil { diff --git a/libmachine/provision/debian.go b/libmachine/provision/debian.go index b6070ef027..aa0691b600 100644 --- a/libmachine/provision/debian.go +++ b/libmachine/provision/debian.go @@ -172,6 +172,7 @@ MountFlags=slave LimitNOFILE=1048576 LimitNPROC=1048576 LimitCORE=infinity +Environment={{range .EngineOptions.Env}}{{ printf "%q" . }} {{end}} [Install] WantedBy=multi-user.target diff --git a/libmachine/provision/generic.go b/libmachine/provision/generic.go index a2991e4b80..04ab390029 100644 --- a/libmachine/provision/generic.go +++ b/libmachine/provision/generic.go @@ -91,6 +91,8 @@ DOCKER_OPTS=' {{ end }}{{ range .EngineOptions.ArbitraryFlags }}--{{.}} {{ end }} ' +{{range .EngineOptions.Env}}export \"{{ printf "%q" . }}\" +{{end}} ` t, err := template.New("engineConfig").Parse(engineConfigTmpl) if err != nil { diff --git a/libmachine/provision/redhat.go b/libmachine/provision/redhat.go index c583635af1..271e5d5ee1 100644 --- a/libmachine/provision/redhat.go +++ b/libmachine/provision/redhat.go @@ -246,6 +246,7 @@ MountFlags=slave LimitNOFILE=1048576 LimitNPROC=1048576 LimitCORE=infinity +Environment={{range .EngineOptions.Env}}{{ printf "%q" . }} {{end}} ` t, err := template.New("engineConfig").Parse(engineConfigTmpl) if err != nil { diff --git a/libmachine/provision/utils.go b/libmachine/provision/utils.go index 8727a6884f..1a51d665b6 100644 --- a/libmachine/provision/utils.go +++ b/libmachine/provision/utils.go @@ -164,7 +164,7 @@ func ConfigureAuth(p Provisioner) error { return err } - if _, err = p.SSHCommand(fmt.Sprintf("printf \"%s\" | sudo tee %s", dkrcfg.EngineOptions, dkrcfg.EngineOptionsPath)); err != nil { + if _, err = p.SSHCommand(fmt.Sprintf("printf %%s \"%s\" | sudo tee %s", dkrcfg.EngineOptions, dkrcfg.EngineOptionsPath)); err != nil { return err } diff --git a/test/integration/core/arbitrary-engine-envs.bats b/test/integration/core/arbitrary-engine-envs.bats new file mode 100644 index 0000000000..4e8fe89a7e --- /dev/null +++ b/test/integration/core/arbitrary-engine-envs.bats @@ -0,0 +1,19 @@ +#!/usr/bin/env bats + +load ${BASE_TEST_DIR}/helpers.bash + + +@test "$DRIVER: create with arbitrary engine envs" { + +run machine create -d $DRIVER \ + --engine-env=TEST=VALUE \ + $NAME + [ $status -eq 0 ] +} + +@test "$DRIVER: test docker process envs" { + + # get pid of docker process, check process envs for set Environment Variable from above test + run machine ssh $NAME 'pgrep -f "docker -d" | xargs -I % sudo cat /proc/%/environ | grep -q "TEST=VALUE"' + [ $status -eq 0 ] +} From e5a70592890986f7fd4dd9078955af6510185339 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Mon, 6 Jul 2015 10:19:54 +0200 Subject: [PATCH 04/36] Support static IP on GCE Signed-off-by: David Gageot --- drivers/google/compute_util.go | 7 +++++++ drivers/google/google.go | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/google/compute_util.go b/drivers/google/compute_util.go index 793506a090..aa5365c59c 100644 --- a/drivers/google/compute_util.go +++ b/drivers/google/compute_util.go @@ -19,6 +19,7 @@ type ComputeUtil struct { userName string project string diskTypeURL string + address string service *raw.Service zoneURL string authTokenPath string @@ -51,6 +52,7 @@ func newComputeUtil(driver *Driver) (*ComputeUtil, error) { userName: driver.SSHUser, project: driver.Project, diskTypeURL: driver.DiskType, + address: driver.Address, service: service, zoneURL: apiURL + driver.Project + "/zones/" + driver.Zone, globalURL: apiURL + driver.Project + "/global", @@ -179,6 +181,11 @@ func (c *ComputeUtil) createInstance(d *Driver) error { }, }, } + + if c.address != "" { + instance.NetworkInterfaces[0].AccessConfigs[0].NatIP = c.address + } + disk, err := c.disk() if disk == nil || err != nil { instance.Disks[0].InitializeParams = &raw.AttachedDiskInitializeParams{ diff --git a/drivers/google/google.go b/drivers/google/google.go index 2a535265f6..e4d1973dbc 100644 --- a/drivers/google/google.go +++ b/drivers/google/google.go @@ -16,6 +16,7 @@ type Driver struct { Zone string MachineType string DiskType string + Address string Scopes string DiskSize int AuthTokenPath string @@ -79,6 +80,11 @@ func GetCreateFlags() []cli.Flag { Value: "pd-standard", EnvVar: "GOOGLE_DISK_TYPE", }, + cli.StringFlag{ + Name: "google-address", + Usage: "GCE Instance External IP", + EnvVar: "GOOGLE_ADDRESS", + }, } } @@ -111,6 +117,7 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { d.MachineType = flags.String("google-machine-type") d.DiskSize = flags.Int("google-disk-size") d.DiskType = flags.String("google-disk-type") + d.Address = flags.String("google-address") d.AuthTokenPath = flags.String("google-auth-token") d.Project = flags.String("google-project") d.Scopes = flags.String("google-scopes") From 8ce161e8303cfbec7422406f12eb62603e7b706d Mon Sep 17 00:00:00 2001 From: David Gageot Date: Mon, 6 Jul 2015 11:04:33 +0200 Subject: [PATCH 05/36] Support Preemptiple instances on GCE Signed-off-by: David Gageot --- Godeps/Godeps.json | 8 +- .../src/golang.org/x/net/context/context.go | 447 ++ .../golang.org/x/net/context/context_test.go | 575 ++ .../x/net/context/withtimeout_test.go | 26 + .../api/compute/v1/compute-api.json | 2544 ++++++--- .../api/compute/v1/compute-gen.go | 4959 +++++++++++++---- .../api/googleapi/googleapi.go | 269 +- .../api/googleapi/googleapi_test.go | 237 + drivers/google/compute_util.go | 5 + drivers/google/google.go | 7 + 10 files changed, 7252 insertions(+), 1825 deletions(-) create mode 100644 Godeps/_workspace/src/golang.org/x/net/context/context.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/context/context_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index a551002a04..18dd12df0b 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -147,13 +147,17 @@ "ImportPath": "golang.org/x/crypto/ssh", "Rev": "1fbbd62cfec66bd39d91e97749579579d4d3037e" }, + { + "ImportPath": "golang.org/x/net/context", + "Rev": "d9558e5c97f85372afee28cf2b6059d7d3818919" + }, { "ImportPath": "google.golang.org/api/compute/v1", - "Rev": "aa91ac681e18e52b1a0dfe29b9d8354e88c0dcf5" + "Rev": "a09229c13c2f13bbdedf7b31b506cad4c83ef3bf" }, { "ImportPath": "google.golang.org/api/googleapi", - "Rev": "aa91ac681e18e52b1a0dfe29b9d8354e88c0dcf5" + "Rev": "a09229c13c2f13bbdedf7b31b506cad4c83ef3bf" } ] } diff --git a/Godeps/_workspace/src/golang.org/x/net/context/context.go b/Godeps/_workspace/src/golang.org/x/net/context/context.go new file mode 100644 index 0000000000..ef2f3e86fe --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/context/context.go @@ -0,0 +1,447 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package context defines the Context type, which carries deadlines, +// cancelation signals, and other request-scoped values across API boundaries +// and between processes. +// +// Incoming requests to a server should create a Context, and outgoing calls to +// servers should accept a Context. The chain of function calls between must +// propagate the Context, optionally replacing it with a modified copy created +// using WithDeadline, WithTimeout, WithCancel, or WithValue. +// +// Programs that use Contexts should follow these rules to keep interfaces +// consistent across packages and enable static analysis tools to check context +// propagation: +// +// Do not store Contexts inside a struct type; instead, pass a Context +// explicitly to each function that needs it. The Context should be the first +// parameter, typically named ctx: +// +// func DoSomething(ctx context.Context, arg Arg) error { +// // ... use ctx ... +// } +// +// Do not pass a nil Context, even if a function permits it. Pass context.TODO +// if you are unsure about which Context to use. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +// +// The same Context may be passed to functions running in different goroutines; +// Contexts are safe for simultaneous use by multiple goroutines. +// +// See http://blog.golang.org/context for example code for a server that uses +// Contexts. +package context + +import ( + "errors" + "fmt" + "sync" + "time" +) + +// A Context carries a deadline, a cancelation signal, and other values across +// API boundaries. +// +// Context's methods may be called by multiple goroutines simultaneously. +type Context interface { + // Deadline returns the time when work done on behalf of this context + // should be canceled. Deadline returns ok==false when no deadline is + // set. Successive calls to Deadline return the same results. + Deadline() (deadline time.Time, ok bool) + + // Done returns a channel that's closed when work done on behalf of this + // context should be canceled. Done may return nil if this context can + // never be canceled. Successive calls to Done return the same value. + // + // WithCancel arranges for Done to be closed when cancel is called; + // WithDeadline arranges for Done to be closed when the deadline + // expires; WithTimeout arranges for Done to be closed when the timeout + // elapses. + // + // Done is provided for use in select statements: + // + // // Stream generates values with DoSomething and sends them to out + // // until DoSomething returns an error or ctx.Done is closed. + // func Stream(ctx context.Context, out <-chan Value) error { + // for { + // v, err := DoSomething(ctx) + // if err != nil { + // return err + // } + // select { + // case <-ctx.Done(): + // return ctx.Err() + // case out <- v: + // } + // } + // } + // + // See http://blog.golang.org/pipelines for more examples of how to use + // a Done channel for cancelation. + Done() <-chan struct{} + + // Err returns a non-nil error value after Done is closed. Err returns + // Canceled if the context was canceled or DeadlineExceeded if the + // context's deadline passed. No other values for Err are defined. + // After Done is closed, successive calls to Err return the same value. + Err() error + + // Value returns the value associated with this context for key, or nil + // if no value is associated with key. Successive calls to Value with + // the same key returns the same result. + // + // Use context values only for request-scoped data that transits + // processes and API boundaries, not for passing optional parameters to + // functions. + // + // A key identifies a specific value in a Context. Functions that wish + // to store values in Context typically allocate a key in a global + // variable then use that key as the argument to context.WithValue and + // Context.Value. A key can be any type that supports equality; + // packages should define keys as an unexported type to avoid + // collisions. + // + // Packages that define a Context key should provide type-safe accessors + // for the values stores using that key: + // + // // Package user defines a User type that's stored in Contexts. + // package user + // + // import "golang.org/x/net/context" + // + // // User is the type of value stored in the Contexts. + // type User struct {...} + // + // // key is an unexported type for keys defined in this package. + // // This prevents collisions with keys defined in other packages. + // type key int + // + // // userKey is the key for user.User values in Contexts. It is + // // unexported; clients use user.NewContext and user.FromContext + // // instead of using this key directly. + // var userKey key = 0 + // + // // NewContext returns a new Context that carries value u. + // func NewContext(ctx context.Context, u *User) context.Context { + // return context.WithValue(ctx, userKey, u) + // } + // + // // FromContext returns the User value stored in ctx, if any. + // func FromContext(ctx context.Context) (*User, bool) { + // u, ok := ctx.Value(userKey).(*User) + // return u, ok + // } + Value(key interface{}) interface{} +} + +// Canceled is the error returned by Context.Err when the context is canceled. +var Canceled = errors.New("context canceled") + +// DeadlineExceeded is the error returned by Context.Err when the context's +// deadline passes. +var DeadlineExceeded = errors.New("context deadline exceeded") + +// An emptyCtx is never canceled, has no values, and has no deadline. It is not +// struct{}, since vars of this type must have distinct addresses. +type emptyCtx int + +func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { + return +} + +func (*emptyCtx) Done() <-chan struct{} { + return nil +} + +func (*emptyCtx) Err() error { + return nil +} + +func (*emptyCtx) Value(key interface{}) interface{} { + return nil +} + +func (e *emptyCtx) String() string { + switch e { + case background: + return "context.Background" + case todo: + return "context.TODO" + } + return "unknown empty Context" +} + +var ( + background = new(emptyCtx) + todo = new(emptyCtx) +) + +// Background returns a non-nil, empty Context. It is never canceled, has no +// values, and has no deadline. It is typically used by the main function, +// initialization, and tests, and as the top-level Context for incoming +// requests. +func Background() Context { + return background +} + +// TODO returns a non-nil, empty Context. Code should use context.TODO when +// it's unclear which Context to use or it's is not yet available (because the +// surrounding function has not yet been extended to accept a Context +// parameter). TODO is recognized by static analysis tools that determine +// whether Contexts are propagated correctly in a program. +func TODO() Context { + return todo +} + +// A CancelFunc tells an operation to abandon its work. +// A CancelFunc does not wait for the work to stop. +// After the first call, subsequent calls to a CancelFunc do nothing. +type CancelFunc func() + +// WithCancel returns a copy of parent with a new Done channel. The returned +// context's Done channel is closed when the returned cancel function is called +// or when the parent context's Done channel is closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { + c := newCancelCtx(parent) + propagateCancel(parent, &c) + return &c, func() { c.cancel(true, Canceled) } +} + +// newCancelCtx returns an initialized cancelCtx. +func newCancelCtx(parent Context) cancelCtx { + return cancelCtx{ + Context: parent, + done: make(chan struct{}), + } +} + +// propagateCancel arranges for child to be canceled when parent is. +func propagateCancel(parent Context, child canceler) { + if parent.Done() == nil { + return // parent is never canceled + } + if p, ok := parentCancelCtx(parent); ok { + p.mu.Lock() + if p.err != nil { + // parent has already been canceled + child.cancel(false, p.err) + } else { + if p.children == nil { + p.children = make(map[canceler]bool) + } + p.children[child] = true + } + p.mu.Unlock() + } else { + go func() { + select { + case <-parent.Done(): + child.cancel(false, parent.Err()) + case <-child.Done(): + } + }() + } +} + +// parentCancelCtx follows a chain of parent references until it finds a +// *cancelCtx. This function understands how each of the concrete types in this +// package represents its parent. +func parentCancelCtx(parent Context) (*cancelCtx, bool) { + for { + switch c := parent.(type) { + case *cancelCtx: + return c, true + case *timerCtx: + return &c.cancelCtx, true + case *valueCtx: + parent = c.Context + default: + return nil, false + } + } +} + +// removeChild removes a context from its parent. +func removeChild(parent Context, child canceler) { + p, ok := parentCancelCtx(parent) + if !ok { + return + } + p.mu.Lock() + if p.children != nil { + delete(p.children, child) + } + p.mu.Unlock() +} + +// A canceler is a context type that can be canceled directly. The +// implementations are *cancelCtx and *timerCtx. +type canceler interface { + cancel(removeFromParent bool, err error) + Done() <-chan struct{} +} + +// A cancelCtx can be canceled. When canceled, it also cancels any children +// that implement canceler. +type cancelCtx struct { + Context + + done chan struct{} // closed by the first cancel call. + + mu sync.Mutex + children map[canceler]bool // set to nil by the first cancel call + err error // set to non-nil by the first cancel call +} + +func (c *cancelCtx) Done() <-chan struct{} { + return c.done +} + +func (c *cancelCtx) Err() error { + c.mu.Lock() + defer c.mu.Unlock() + return c.err +} + +func (c *cancelCtx) String() string { + return fmt.Sprintf("%v.WithCancel", c.Context) +} + +// cancel closes c.done, cancels each of c's children, and, if +// removeFromParent is true, removes c from its parent's children. +func (c *cancelCtx) cancel(removeFromParent bool, err error) { + if err == nil { + panic("context: internal error: missing cancel error") + } + c.mu.Lock() + if c.err != nil { + c.mu.Unlock() + return // already canceled + } + c.err = err + close(c.done) + for child := range c.children { + // NOTE: acquiring the child's lock while holding parent's lock. + child.cancel(false, err) + } + c.children = nil + c.mu.Unlock() + + if removeFromParent { + removeChild(c.Context, c) + } +} + +// WithDeadline returns a copy of the parent context with the deadline adjusted +// to be no later than d. If the parent's deadline is already earlier than d, +// WithDeadline(parent, d) is semantically equivalent to parent. The returned +// context's Done channel is closed when the deadline expires, when the returned +// cancel function is called, or when the parent context's Done channel is +// closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { + if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { + // The current deadline is already sooner than the new one. + return WithCancel(parent) + } + c := &timerCtx{ + cancelCtx: newCancelCtx(parent), + deadline: deadline, + } + propagateCancel(parent, c) + d := deadline.Sub(time.Now()) + if d <= 0 { + c.cancel(true, DeadlineExceeded) // deadline has already passed + return c, func() { c.cancel(true, Canceled) } + } + c.mu.Lock() + defer c.mu.Unlock() + if c.err == nil { + c.timer = time.AfterFunc(d, func() { + c.cancel(true, DeadlineExceeded) + }) + } + return c, func() { c.cancel(true, Canceled) } +} + +// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to +// implement Done and Err. It implements cancel by stopping its timer then +// delegating to cancelCtx.cancel. +type timerCtx struct { + cancelCtx + timer *time.Timer // Under cancelCtx.mu. + + deadline time.Time +} + +func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { + return c.deadline, true +} + +func (c *timerCtx) String() string { + return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now())) +} + +func (c *timerCtx) cancel(removeFromParent bool, err error) { + c.cancelCtx.cancel(false, err) + if removeFromParent { + // Remove this timerCtx from its parent cancelCtx's children. + removeChild(c.cancelCtx.Context, c) + } + c.mu.Lock() + if c.timer != nil { + c.timer.Stop() + c.timer = nil + } + c.mu.Unlock() +} + +// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete: +// +// func slowOperationWithTimeout(ctx context.Context) (Result, error) { +// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) +// defer cancel() // releases resources if slowOperation completes before timeout elapses +// return slowOperation(ctx) +// } +func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { + return WithDeadline(parent, time.Now().Add(timeout)) +} + +// WithValue returns a copy of parent in which the value associated with key is +// val. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +func WithValue(parent Context, key interface{}, val interface{}) Context { + return &valueCtx{parent, key, val} +} + +// A valueCtx carries a key-value pair. It implements Value for that key and +// delegates all other calls to the embedded Context. +type valueCtx struct { + Context + key, val interface{} +} + +func (c *valueCtx) String() string { + return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) +} + +func (c *valueCtx) Value(key interface{}) interface{} { + if c.key == key { + return c.val + } + return c.Context.Value(key) +} diff --git a/Godeps/_workspace/src/golang.org/x/net/context/context_test.go b/Godeps/_workspace/src/golang.org/x/net/context/context_test.go new file mode 100644 index 0000000000..faf67722a0 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/context/context_test.go @@ -0,0 +1,575 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package context + +import ( + "fmt" + "math/rand" + "runtime" + "strings" + "sync" + "testing" + "time" +) + +// otherContext is a Context that's not one of the types defined in context.go. +// This lets us test code paths that differ based on the underlying type of the +// Context. +type otherContext struct { + Context +} + +func TestBackground(t *testing.T) { + c := Background() + if c == nil { + t.Fatalf("Background returned nil") + } + select { + case x := <-c.Done(): + t.Errorf("<-c.Done() == %v want nothing (it should block)", x) + default: + } + if got, want := fmt.Sprint(c), "context.Background"; got != want { + t.Errorf("Background().String() = %q want %q", got, want) + } +} + +func TestTODO(t *testing.T) { + c := TODO() + if c == nil { + t.Fatalf("TODO returned nil") + } + select { + case x := <-c.Done(): + t.Errorf("<-c.Done() == %v want nothing (it should block)", x) + default: + } + if got, want := fmt.Sprint(c), "context.TODO"; got != want { + t.Errorf("TODO().String() = %q want %q", got, want) + } +} + +func TestWithCancel(t *testing.T) { + c1, cancel := WithCancel(Background()) + + if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want { + t.Errorf("c1.String() = %q want %q", got, want) + } + + o := otherContext{c1} + c2, _ := WithCancel(o) + contexts := []Context{c1, o, c2} + + for i, c := range contexts { + if d := c.Done(); d == nil { + t.Errorf("c[%d].Done() == %v want non-nil", i, d) + } + if e := c.Err(); e != nil { + t.Errorf("c[%d].Err() == %v want nil", i, e) + } + + select { + case x := <-c.Done(): + t.Errorf("<-c.Done() == %v want nothing (it should block)", x) + default: + } + } + + cancel() + time.Sleep(100 * time.Millisecond) // let cancelation propagate + + for i, c := range contexts { + select { + case <-c.Done(): + default: + t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i) + } + if e := c.Err(); e != Canceled { + t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled) + } + } +} + +func TestParentFinishesChild(t *testing.T) { + // Context tree: + // parent -> cancelChild + // parent -> valueChild -> timerChild + parent, cancel := WithCancel(Background()) + cancelChild, stop := WithCancel(parent) + defer stop() + valueChild := WithValue(parent, "key", "value") + timerChild, stop := WithTimeout(valueChild, 10000*time.Hour) + defer stop() + + select { + case x := <-parent.Done(): + t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) + case x := <-cancelChild.Done(): + t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x) + case x := <-timerChild.Done(): + t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x) + case x := <-valueChild.Done(): + t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x) + default: + } + + // The parent's children should contain the two cancelable children. + pc := parent.(*cancelCtx) + cc := cancelChild.(*cancelCtx) + tc := timerChild.(*timerCtx) + pc.mu.Lock() + if len(pc.children) != 2 || !pc.children[cc] || !pc.children[tc] { + t.Errorf("bad linkage: pc.children = %v, want %v and %v", + pc.children, cc, tc) + } + pc.mu.Unlock() + + if p, ok := parentCancelCtx(cc.Context); !ok || p != pc { + t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc) + } + if p, ok := parentCancelCtx(tc.Context); !ok || p != pc { + t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc) + } + + cancel() + + pc.mu.Lock() + if len(pc.children) != 0 { + t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children) + } + pc.mu.Unlock() + + // parent and children should all be finished. + check := func(ctx Context, name string) { + select { + case <-ctx.Done(): + default: + t.Errorf("<-%s.Done() blocked, but shouldn't have", name) + } + if e := ctx.Err(); e != Canceled { + t.Errorf("%s.Err() == %v want %v", name, e, Canceled) + } + } + check(parent, "parent") + check(cancelChild, "cancelChild") + check(valueChild, "valueChild") + check(timerChild, "timerChild") + + // WithCancel should return a canceled context on a canceled parent. + precanceledChild := WithValue(parent, "key", "value") + select { + case <-precanceledChild.Done(): + default: + t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have") + } + if e := precanceledChild.Err(); e != Canceled { + t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled) + } +} + +func TestChildFinishesFirst(t *testing.T) { + cancelable, stop := WithCancel(Background()) + defer stop() + for _, parent := range []Context{Background(), cancelable} { + child, cancel := WithCancel(parent) + + select { + case x := <-parent.Done(): + t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) + case x := <-child.Done(): + t.Errorf("<-child.Done() == %v want nothing (it should block)", x) + default: + } + + cc := child.(*cancelCtx) + pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background() + if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) { + t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok) + } + + if pcok { + pc.mu.Lock() + if len(pc.children) != 1 || !pc.children[cc] { + t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc) + } + pc.mu.Unlock() + } + + cancel() + + if pcok { + pc.mu.Lock() + if len(pc.children) != 0 { + t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children) + } + pc.mu.Unlock() + } + + // child should be finished. + select { + case <-child.Done(): + default: + t.Errorf("<-child.Done() blocked, but shouldn't have") + } + if e := child.Err(); e != Canceled { + t.Errorf("child.Err() == %v want %v", e, Canceled) + } + + // parent should not be finished. + select { + case x := <-parent.Done(): + t.Errorf("<-parent.Done() == %v want nothing (it should block)", x) + default: + } + if e := parent.Err(); e != nil { + t.Errorf("parent.Err() == %v want nil", e) + } + } +} + +func testDeadline(c Context, wait time.Duration, t *testing.T) { + select { + case <-time.After(wait): + t.Fatalf("context should have timed out") + case <-c.Done(): + } + if e := c.Err(); e != DeadlineExceeded { + t.Errorf("c.Err() == %v want %v", e, DeadlineExceeded) + } +} + +func TestDeadline(t *testing.T) { + c, _ := WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { + t.Errorf("c.String() = %q want prefix %q", got, prefix) + } + testDeadline(c, 200*time.Millisecond, t) + + c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + o := otherContext{c} + testDeadline(o, 200*time.Millisecond, t) + + c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond)) + o = otherContext{c} + c, _ = WithDeadline(o, time.Now().Add(300*time.Millisecond)) + testDeadline(c, 200*time.Millisecond, t) +} + +func TestTimeout(t *testing.T) { + c, _ := WithTimeout(Background(), 100*time.Millisecond) + if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { + t.Errorf("c.String() = %q want prefix %q", got, prefix) + } + testDeadline(c, 200*time.Millisecond, t) + + c, _ = WithTimeout(Background(), 100*time.Millisecond) + o := otherContext{c} + testDeadline(o, 200*time.Millisecond, t) + + c, _ = WithTimeout(Background(), 100*time.Millisecond) + o = otherContext{c} + c, _ = WithTimeout(o, 300*time.Millisecond) + testDeadline(c, 200*time.Millisecond, t) +} + +func TestCanceledTimeout(t *testing.T) { + c, _ := WithTimeout(Background(), 200*time.Millisecond) + o := otherContext{c} + c, cancel := WithTimeout(o, 400*time.Millisecond) + cancel() + time.Sleep(100 * time.Millisecond) // let cancelation propagate + select { + case <-c.Done(): + default: + t.Errorf("<-c.Done() blocked, but shouldn't have") + } + if e := c.Err(); e != Canceled { + t.Errorf("c.Err() == %v want %v", e, Canceled) + } +} + +type key1 int +type key2 int + +var k1 = key1(1) +var k2 = key2(1) // same int as k1, different type +var k3 = key2(3) // same type as k2, different int + +func TestValues(t *testing.T) { + check := func(c Context, nm, v1, v2, v3 string) { + if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 { + t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0) + } + if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 { + t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0) + } + if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 { + t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0) + } + } + + c0 := Background() + check(c0, "c0", "", "", "") + + c1 := WithValue(Background(), k1, "c1k1") + check(c1, "c1", "c1k1", "", "") + + if got, want := fmt.Sprint(c1), `context.Background.WithValue(1, "c1k1")`; got != want { + t.Errorf("c.String() = %q want %q", got, want) + } + + c2 := WithValue(c1, k2, "c2k2") + check(c2, "c2", "c1k1", "c2k2", "") + + c3 := WithValue(c2, k3, "c3k3") + check(c3, "c2", "c1k1", "c2k2", "c3k3") + + c4 := WithValue(c3, k1, nil) + check(c4, "c4", "", "c2k2", "c3k3") + + o0 := otherContext{Background()} + check(o0, "o0", "", "", "") + + o1 := otherContext{WithValue(Background(), k1, "c1k1")} + check(o1, "o1", "c1k1", "", "") + + o2 := WithValue(o1, k2, "o2k2") + check(o2, "o2", "c1k1", "o2k2", "") + + o3 := otherContext{c4} + check(o3, "o3", "", "c2k2", "c3k3") + + o4 := WithValue(o3, k3, nil) + check(o4, "o4", "", "c2k2", "") +} + +func TestAllocs(t *testing.T) { + bg := Background() + for _, test := range []struct { + desc string + f func() + limit float64 + gccgoLimit float64 + }{ + { + desc: "Background()", + f: func() { Background() }, + limit: 0, + gccgoLimit: 0, + }, + { + desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1), + f: func() { + c := WithValue(bg, k1, nil) + c.Value(k1) + }, + limit: 3, + gccgoLimit: 3, + }, + { + desc: "WithTimeout(bg, 15*time.Millisecond)", + f: func() { + c, _ := WithTimeout(bg, 15*time.Millisecond) + <-c.Done() + }, + limit: 8, + gccgoLimit: 13, + }, + { + desc: "WithCancel(bg)", + f: func() { + c, cancel := WithCancel(bg) + cancel() + <-c.Done() + }, + limit: 5, + gccgoLimit: 8, + }, + { + desc: "WithTimeout(bg, 100*time.Millisecond)", + f: func() { + c, cancel := WithTimeout(bg, 100*time.Millisecond) + cancel() + <-c.Done() + }, + limit: 8, + gccgoLimit: 25, + }, + } { + limit := test.limit + if runtime.Compiler == "gccgo" { + // gccgo does not yet do escape analysis. + // TOOD(iant): Remove this when gccgo does do escape analysis. + limit = test.gccgoLimit + } + if n := testing.AllocsPerRun(100, test.f); n > limit { + t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit)) + } + } +} + +func TestSimultaneousCancels(t *testing.T) { + root, cancel := WithCancel(Background()) + m := map[Context]CancelFunc{root: cancel} + q := []Context{root} + // Create a tree of contexts. + for len(q) != 0 && len(m) < 100 { + parent := q[0] + q = q[1:] + for i := 0; i < 4; i++ { + ctx, cancel := WithCancel(parent) + m[ctx] = cancel + q = append(q, ctx) + } + } + // Start all the cancels in a random order. + var wg sync.WaitGroup + wg.Add(len(m)) + for _, cancel := range m { + go func(cancel CancelFunc) { + cancel() + wg.Done() + }(cancel) + } + // Wait on all the contexts in a random order. + for ctx := range m { + select { + case <-ctx.Done(): + case <-time.After(1 * time.Second): + buf := make([]byte, 10<<10) + n := runtime.Stack(buf, true) + t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n]) + } + } + // Wait for all the cancel functions to return. + done := make(chan struct{}) + go func() { + wg.Wait() + close(done) + }() + select { + case <-done: + case <-time.After(1 * time.Second): + buf := make([]byte, 10<<10) + n := runtime.Stack(buf, true) + t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n]) + } +} + +func TestInterlockedCancels(t *testing.T) { + parent, cancelParent := WithCancel(Background()) + child, cancelChild := WithCancel(parent) + go func() { + parent.Done() + cancelChild() + }() + cancelParent() + select { + case <-child.Done(): + case <-time.After(1 * time.Second): + buf := make([]byte, 10<<10) + n := runtime.Stack(buf, true) + t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n]) + } +} + +func TestLayersCancel(t *testing.T) { + testLayers(t, time.Now().UnixNano(), false) +} + +func TestLayersTimeout(t *testing.T) { + testLayers(t, time.Now().UnixNano(), true) +} + +func testLayers(t *testing.T, seed int64, testTimeout bool) { + rand.Seed(seed) + errorf := func(format string, a ...interface{}) { + t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...) + } + const ( + timeout = 200 * time.Millisecond + minLayers = 30 + ) + type value int + var ( + vals []*value + cancels []CancelFunc + numTimers int + ctx = Background() + ) + for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ { + switch rand.Intn(3) { + case 0: + v := new(value) + ctx = WithValue(ctx, v, v) + vals = append(vals, v) + case 1: + var cancel CancelFunc + ctx, cancel = WithCancel(ctx) + cancels = append(cancels, cancel) + case 2: + var cancel CancelFunc + ctx, cancel = WithTimeout(ctx, timeout) + cancels = append(cancels, cancel) + numTimers++ + } + } + checkValues := func(when string) { + for _, key := range vals { + if val := ctx.Value(key).(*value); key != val { + errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key) + } + } + } + select { + case <-ctx.Done(): + errorf("ctx should not be canceled yet") + default: + } + if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) { + t.Errorf("ctx.String() = %q want prefix %q", s, prefix) + } + t.Log(ctx) + checkValues("before cancel") + if testTimeout { + select { + case <-ctx.Done(): + case <-time.After(timeout + timeout/10): + errorf("ctx should have timed out") + } + checkValues("after timeout") + } else { + cancel := cancels[rand.Intn(len(cancels))] + cancel() + select { + case <-ctx.Done(): + default: + errorf("ctx should be canceled") + } + checkValues("after cancel") + } +} + +func TestCancelRemoves(t *testing.T) { + checkChildren := func(when string, ctx Context, want int) { + if got := len(ctx.(*cancelCtx).children); got != want { + t.Errorf("%s: context has %d children, want %d", when, got, want) + } + } + + ctx, _ := WithCancel(Background()) + checkChildren("after creation", ctx, 0) + _, cancel := WithCancel(ctx) + checkChildren("with WithCancel child ", ctx, 1) + cancel() + checkChildren("after cancelling WithCancel child", ctx, 0) + + ctx, _ = WithCancel(Background()) + checkChildren("after creation", ctx, 0) + _, cancel = WithTimeout(ctx, 60*time.Minute) + checkChildren("with WithTimeout child ", ctx, 1) + cancel() + checkChildren("after cancelling WithTimeout child", ctx, 0) +} diff --git a/Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go b/Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go new file mode 100644 index 0000000000..a6754dc368 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go @@ -0,0 +1,26 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package context_test + +import ( + "fmt" + "time" + + "golang.org/x/net/context" +) + +func ExampleWithTimeout() { + // Pass a context with a timeout to tell a blocking function that it + // should abandon its work after the timeout elapses. + ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond) + select { + case <-time.After(200 * time.Millisecond): + fmt.Println("overslept") + case <-ctx.Done(): + fmt.Println(ctx.Err()) // prints "context deadline exceeded" + } + // Output: + // context deadline exceeded +} diff --git a/Godeps/_workspace/src/google.golang.org/api/compute/v1/compute-api.json b/Godeps/_workspace/src/google.golang.org/api/compute/v1/compute-api.json index 726a0ac363..921d5fd90e 100644 --- a/Godeps/_workspace/src/google.golang.org/api/compute/v1/compute-api.json +++ b/Godeps/_workspace/src/google.golang.org/api/compute/v1/compute-api.json @@ -1,11 +1,11 @@ { "kind": "discovery#restDescription", - "etag": "\"l66ggWbucbkBw9Lpos72oziyefE/qp3DHGvWPpREzEdWk7WwxnpgC9w\"", + "etag": "\"ye6orv2F-1npMW3u9suM3a7C5Bo/zfEgIM85dgKQ1dAjVHrRpCmO0gk\"", "discoveryVersion": "v1", "id": "compute:v1", "name": "compute", "version": "v1", - "revision": "20141014", + "revision": "20150617", "title": "Compute Engine API", "description": "API for the Google Compute Engine service.", "ownerDomain": "google.com", @@ -69,6 +69,9 @@ "auth": { "oauth2": { "scopes": { + "https://www.googleapis.com/auth/cloud-platform": { + "description": "View and manage your data across Google Cloud Platform services" + }, "https://www.googleapis.com/auth/compute": { "description": "View and manage your Google Compute Engine resources" }, @@ -95,7 +98,7 @@ "properties": { "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Only] Type of the resource. Always compute#accessConfig for access configs.", "default": "compute#accessConfig" }, "name": { @@ -104,11 +107,11 @@ }, "natIP": { "type": "string", - "description": "An external IP address associated with this instance. Specify an unused static IP address available to the project. If not specified, the external IP will be drawn from a shared ephemeral pool." + "description": "An external IP address associated with this instance. Specify an unused static external IP address available to the project or leave this field undefined to use an IP from a shared ephemeral IP address pool. If you specify a static external IP address, it must live in the same region as the zone of the instance." }, "type": { "type": "string", - "description": "Type of configuration. Must be set to \"ONE_TO_ONE_NAT\". This configures port-for-port NAT to the internet.", + "description": "The type of configuration. The default and only option is ONE_TO_ONE_NAT.", "default": "ONE_TO_ONE_NAT", "enum": [ "ONE_TO_ONE_NAT" @@ -126,11 +129,11 @@ "properties": { "address": { "type": "string", - "description": "The IP address represented by this resource." + "description": "The static external IP address represented by this resource." }, "creationTimestamp": { "type": "string", - "description": "Creation timestamp in RFC3339 text format (output only)." + "description": "[Output Only] Creation timestamp in RFC3339 text format." }, "description": { "type": "string", @@ -138,17 +141,17 @@ }, "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only).", + "description": "[Output Only] Unique identifier for the resource; defined by the server.", "format": "uint64" }, "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Only] Type of the resource. Always compute#address for addresses.", "default": "compute#address" }, "name": { "type": "string", - "description": "Name of the resource; provided by the client when the resource is created. The name must be 1-63 characters long, and comply with RFC1035.", + "description": "Name of the resource; provided by the client when the resource is created. The name must be 1-63 characters long, and comply with RFC1035. Specifically, the name must be 1-63 characters long and match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash.", "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "annotations": { "required": [ @@ -158,15 +161,15 @@ }, "region": { "type": "string", - "description": "URL of the region where the regional address resides (output only). This field is not applicable to global addresses." + "description": "[Output Only] URL of the region where the regional address resides. This field is not applicable to global addresses." }, "selfLink": { "type": "string", - "description": "Server defined URL for the resource (output only)." + "description": "[Output Only] Server defined URL for the resource." }, "status": { "type": "string", - "description": "The status of the address (output only).", + "description": "[Output Only] The status of the address, which can be either IN_USE or RESERVED. An address that is RESERVED is currently reserved and available to use. An IN_USE address is currently being used by another resource and is not available.", "enum": [ "IN_USE", "RESERVED" @@ -178,7 +181,7 @@ }, "users": { "type": "array", - "description": "The resources that are using this address resource.", + "description": "[Output Only] The URLs of the resources that are using this address.", "items": { "type": "string" } @@ -191,28 +194,28 @@ "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "object", - "description": "A map of scoped address lists.", + "description": "[Output Only] A map of scoped address lists.", "additionalProperties": { "$ref": "AddressesScopedList", - "description": "Name of the scope containing this set of addresses." + "description": "[Output Only] Name of the scope containing this set of addresses." } }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#addressAggregatedList for aggregated lists of addresses.", "default": "compute#addressAggregatedList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncated list request." }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] Server defined URL for this resource." } } }, @@ -223,27 +226,27 @@ "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "array", - "description": "The address resources.", + "description": "[Output Only] A list of Address resources.", "items": { "$ref": "Address" } }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#addressList for lists of addresses.", "default": "compute#addressList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncated list request." }, "selfLink": { "type": "string", - "description": "Server defined URL for the resource (output only)." + "description": "[Output Only] Server defined URL for the resource." } } }, @@ -253,18 +256,18 @@ "properties": { "addresses": { "type": "array", - "description": "List of addresses contained in this scope.", + "description": "[Output Only] List of addresses contained in this scope.", "items": { "$ref": "Address" } }, "warning": { "type": "object", - "description": "Informational warning which replaces the list of addresses when the list is empty.", + "description": "[Output Only] Informational warning which replaces the list of addresses when the list is empty.", "properties": { "code": { "type": "string", - "description": "The warning type identifier for this warning.", + "description": "[Output Only] The warning type identifier for this warning.", "enum": [ "DEPRECATED_RESOURCE_USED", "DISK_SIZE_LARGER_THAN_IMAGE_SIZE", @@ -274,9 +277,11 @@ "NEXT_HOP_INSTANCE_NOT_FOUND", "NEXT_HOP_INSTANCE_NOT_ON_NETWORK", "NEXT_HOP_NOT_RUNNING", + "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", "RESOURCE_NOT_DELETED", + "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" ], "enumDescriptions": [ @@ -291,29 +296,31 @@ "", "", "", + "", + "", "" ] }, "data": { "type": "array", - "description": "Metadata for this warning in 'key: value' format.", + "description": "[Output Only] Metadata for this warning in key: value format.", "items": { "type": "object", "properties": { "key": { "type": "string", - "description": "A key for the warning data." + "description": "[Output Only] A key for the warning data." }, "value": { "type": "string", - "description": "A warning data value corresponding to the key." + "description": "[Output Only] A warning data value corresponding to the key." } } } }, "message": { "type": "string", - "description": "Optional human-readable details for this warning." + "description": "[Output Only] Optional human-readable details for this warning." } } } @@ -326,24 +333,24 @@ "properties": { "autoDelete": { "type": "boolean", - "description": "Whether the disk will be auto-deleted when the instance is deleted (but not when the disk is detached from the instance)." + "description": "Specifies whether the disk will be auto-deleted when the instance is deleted (but not when the disk is detached from the instance)." }, "boot": { "type": "boolean", - "description": "Indicates that this is a boot disk. VM will use the first partition of the disk for its root filesystem." + "description": "Indicates that this is a boot disk. The virtual machine will use the first partition of the disk for its root filesystem." }, "deviceName": { "type": "string", - "description": "Persistent disk only; must be unique within the instance when specified. This represents a unique device name that is reflected into the /dev/ tree of a Linux operating system running within the instance. If not specified, a default will be chosen by the system." + "description": "Specifies a unique device name of your choice that is reflected into the /dev/ tree of a Linux operating system running within the instance. This name can be used to reference the device for mounting, resizing, and so on, from within the instance.\n\nIf not specified, the server chooses a default device name to apply to this disk, in the form persistent-disks-x, where x is a number assigned by Google Compute Engine. This field is only applicable for persistent disks." }, "index": { "type": "integer", - "description": "A zero-based index to assign to this disk, where 0 is reserved for the boot disk. If not specified, the server will choose an appropriate value (output only).", + "description": "Assigns a zero-based index to this disk, where 0 is reserved for the boot disk. For example, if you have many disks attached to an instance, each disk would have a unique index number. If not specified, the server will choose an appropriate value.", "format": "int32" }, "initializeParams": { "$ref": "AttachedDiskInitializeParams", - "description": "Initialization parameters." + "description": "[Input Only] Specifies the parameters for a new disk that will be created alongside the new instance. Use initialization parameters to create boot disks or local SSDs attached to the new instance.\n\nThis property is mutually exclusive with the source property; you can only define one or the other, but not both." }, "interface": { "type": "string", @@ -358,19 +365,19 @@ }, "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Only] Type of the resource. Always compute#attachedDisk for attached disks.", "default": "compute#attachedDisk" }, "licenses": { "type": "array", - "description": "Public visible licenses.", + "description": "[Output Only] Any valid publicly visible licenses.", "items": { "type": "string" } }, "mode": { "type": "string", - "description": "The mode in which to attach this disk, either \"READ_WRITE\" or \"READ_ONLY\".", + "description": "The mode in which to attach this disk, either READ_WRITE or READ_ONLY. If not specified, the default is to attach the disk in READ_WRITE mode.", "enum": [ "READ_ONLY", "READ_WRITE" @@ -382,11 +389,11 @@ }, "source": { "type": "string", - "description": "Persistent disk only; the URL of the persistent disk resource." + "description": "Specifies a valid partial or full URL to an existing Persistent Disk resource. This field is only applicable for persistent disks." }, "type": { "type": "string", - "description": "Type of the disk, either \"SCRATCH\" or \"PERSISTENT\". Note that persistent disks must be created before you can specify them here.", + "description": "Specifies the type of the disk, either SCRATCH or PERSISTENT. If not specified, the default is PERSISTENT.", "enum": [ "PERSISTENT", "SCRATCH" @@ -406,24 +413,24 @@ "AttachedDiskInitializeParams": { "id": "AttachedDiskInitializeParams", "type": "object", - "description": "Initialization parameters for the new disk (input-only). Can only be specified on the boot disk or local SSDs. Mutually exclusive with 'source'.", + "description": "[Input Only] Specifies the parameters for a new disk that will be created alongside the new instance. Use initialization parameters to create boot disks or local SSDs attached to the new instance.\n\nThis property is mutually exclusive with the source property; you can only define one or the other, but not both.", "properties": { "diskName": { "type": "string", - "description": "Name of the disk (when not provided defaults to the name of the instance)." + "description": "Specifies the disk name. If not specified, the default is to use the name of the instance." }, "diskSizeGb": { "type": "string", - "description": "Size of the disk in base-2 GB.", + "description": "Specifies the size of the disk in base-2 GB.", "format": "int64" }, "diskType": { "type": "string", - "description": "URL of the disk type resource describing which disk type to use to create the disk; provided by the client when the disk is created." + "description": "Specifies the disk type to use to create the instance. If not specified, the default is pd-standard, specified using the full URL. For example:\n\nhttps://www.googleapis.com/compute/v1/projects/project/zones/zone/diskTypes/pd-standard \n\nOther values include pd-ssd and local-ssd. If you define this field, you can provide either the full or partial URL. For example, the following are valid values: \n- https://www.googleapis.com/compute/v1/projects/project/zones/zone/diskTypes/diskType \n- projects/project/zones/zone/diskTypes/diskType \n- zones/zone/diskTypes/diskType" }, "sourceImage": { "type": "string", - "description": "The source image used to create this disk." + "description": "A source image used to create the disk. You can provide a private (custom) image, and Compute Engine will use the corresponding image from your project. For example:\n\nglobal/images/my-private-image \n\nOr you can provide an image from a publicly-available project. For example, to use a Debian image from the debian-cloud project, make sure to include the project in the URL:\n\nprojects/debian-cloud/global/images/debian-7-wheezy-vYYYYMMDD \n\nwhere vYYYYMMDD is the image version. The fully-qualified URL will also work in both cases." } } }, @@ -446,7 +453,7 @@ }, "capacityScaler": { "type": "number", - "description": "The multiplier (a value between 0 and 1e6) of the max capacity (CPU or RPS, depending on 'balancingMode') the group should serve up to. 0 means the group is totally drained. Default value is 1. Valid range is [0, 1e6].", + "description": "The multiplier (a value between 0.0 and 1.0) of the max capacity (CPU or RPS, depending on 'balancingMode') the group should serve up to. 0 means the group is totally drained. Default value is 1. Valid range is [0.0, 1.0].", "format": "float" }, "description": { @@ -477,7 +484,7 @@ "BackendService": { "id": "BackendService", "type": "object", - "description": "A BackendService resource. This resource defines a group of backend VMs together with their serving capacity.", + "description": "A BackendService resource. This resource defines a group of backend VMs together with their serving capacity.\n\nIf you add field foo, you probably need to also add: com.google.cloud.cluster.manager.api.BackendServiceResource: foo com.google.cloud.cluster.manager.networking.entities: BackendService, BackendServiceEntity: getFoo, setFoo:\n\nConverters/mappers will need to be updated: com.google.cloud.cluster.manager.networking.services.backendservice.BackendServiceResourceConverter: toResource, updateEntity: copy foo com.google.cloud.cluster.mixer.protomappers.BackendServiceMappers.ResourceMapper: ResourceMapper: add a new map call\n\nTests to update: com.google.cloud.cluster.manager.networking.services.backendservice.BackendServiceResourceConverterTest com.google.cloud.cluster.mixer.protomappers.BackendServiceMappersTest.testResourceMapping", "properties": { "backends": { "type": "array", @@ -578,7 +585,7 @@ }, "items": { "type": "array", - "description": "The BackendService resources.", + "description": "A list of BackendService resources.", "items": { "$ref": "BackendService" } @@ -617,11 +624,11 @@ }, "replacement": { "type": "string", - "description": "A URL of the suggested replacement for the deprecated resource. The deprecated resource and its replacement must be resources of the same kind." + "description": "The URL of the suggested replacement for a deprecated resource. The suggested replacement resource must be the same kind of resource as the deprecated resource." }, "state": { "type": "string", - "description": "The deprecation state. Can be \"DEPRECATED\", \"OBSOLETE\", or \"DELETED\". Operations which create a new resource using a \"DEPRECATED\" resource will return successfully, but with a warning indicating the deprecated resource and recommending its replacement. New uses of \"OBSOLETE\" or \"DELETED\" resources will result in an error.", + "description": "The deprecation state of this resource. This can be DEPRECATED, OBSOLETE, or DELETED. Operations which create a new resource using a DEPRECATED resource will return successfully, but with a warning indicating the deprecated resource and recommending its replacement. Operations which use OBSOLETE or DELETED resources will be rejected and result in an error.", "enum": [ "DELETED", "DEPRECATED", @@ -638,11 +645,11 @@ "Disk": { "id": "Disk", "type": "object", - "description": "A persistent disk resource.", + "description": "A Disk resource.", "properties": { "creationTimestamp": { "type": "string", - "description": "Creation timestamp in RFC3339 text format (output only)." + "description": "[Output Only] Creation timestamp in RFC3339 text format." }, "description": { "type": "string", @@ -650,24 +657,32 @@ }, "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only).", + "description": "[Output Only] Unique identifier for the resource; defined by the server.", "format": "uint64" }, "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Only] Type of the resource. Always compute#disk for disks.", "default": "compute#disk" }, + "lastAttachTimestamp": { + "type": "string", + "description": "[Output Only] Last attach timestamp in RFC3339 text format." + }, + "lastDetachTimestamp": { + "type": "string", + "description": "[Output Only] Last detach timestamp in RFC3339 text format." + }, "licenses": { "type": "array", - "description": "Public visible licenses.", + "description": "Any applicable publicly visible licenses.", "items": { "type": "string" } }, "name": { "type": "string", - "description": "Name of the resource; provided by the client when the resource is created. The name must be 1-63 characters long, and comply with RFC1035.", + "description": "Name of the resource; provided by the client when the resource is created. The name must be 1-63 characters long, and comply with RFC1035. Specifically, the name must be 1-63 characters long and match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash.", "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "annotations": { "required": [ @@ -681,32 +696,32 @@ }, "selfLink": { "type": "string", - "description": "Server defined URL for the resource (output only)." + "description": "[Output Only] Server-defined fully-qualified URL for this resource." }, "sizeGb": { "type": "string", - "description": "Size of the persistent disk, specified in GB. This parameter is optional when creating a disk from a disk image or a snapshot, otherwise it is required.", + "description": "Size of the persistent disk, specified in GB. You can specify this field when creating a persistent disk using the sourceImage or sourceSnapshot parameter, or specify it alone to create an empty persistent disk.\n\nIf you specify this field along with sourceImage or sourceSnapshot, the value of sizeGb must not be less than the size of the sourceImage or the size of the snapshot.", "format": "int64" }, "sourceImage": { "type": "string", - "description": "The source image used to create this disk." + "description": "The source image used to create this disk. If the source image is deleted from the system, this field will not be set, even if an image with the same name has been re-created.\n\nWhen creating a disk, you can provide a private (custom) image using the following input, and Compute Engine will use the corresponding image from your project. For example:\n\nglobal/images/my-private-image \n\nOr you can provide an image from a publicly-available project. For example, to use a Debian image from the debian-cloud project, make sure to include the project in the URL:\n\nprojects/debian-cloud/global/images/debian-7-wheezy-vYYYYMMDD \n\nwhere vYYYYMMDD is the image version. The fully-qualified URL will also work in both cases." }, "sourceImageId": { "type": "string", - "description": "The 'id' value of the image used to create this disk. This value may be used to determine whether the disk was created from the current or a previous instance of a given image." + "description": "The ID value of the image used to create this disk. This value identifies the exact image that was used to create this persistent disk. For example, if you created the persistent disk from an image that was later deleted and recreated under the same name, the source image ID would identify the exact version of the image that was used." }, "sourceSnapshot": { "type": "string", - "description": "The source snapshot used to create this disk." + "description": "The source snapshot used to create this disk. You can provide this as a partial or full URL to the resource. For example, the following are valid values: \n- https://www.googleapis.com/compute/v1/projects/project/global/snapshots/snapshot \n- projects/project/global/snapshots/snapshot \n- global/snapshots/snapshot" }, "sourceSnapshotId": { "type": "string", - "description": "The 'id' value of the snapshot used to create this disk. This value may be used to determine whether the disk was created from the current or a previous instance of a given disk snapshot." + "description": "[Output Only] The unique ID of the snapshot used to create this disk. This value identifies the exact snapshot that was used to create this persistent disk. For example, if you created the persistent disk from a snapshot that was later deleted and recreated under the same name, the source snapshot ID would identify the exact version of the snapshot that was used." }, "status": { "type": "string", - "description": "The status of disk creation (output only).", + "description": "[Output Only] The status of disk creation. Applicable statuses includes: CREATING, FAILED, READY, RESTORING.", "enum": [ "CREATING", "FAILED", @@ -724,9 +739,16 @@ "type": "string", "description": "URL of the disk type resource describing which disk type to use to create the disk; provided by the client when the disk is created." }, + "users": { + "type": "array", + "description": "Links to the users of the disk (attached instances) in form: project/zones/zone/instances/instance", + "items": { + "type": "string" + } + }, "zone": { "type": "string", - "description": "URL of the zone where the disk resides (output only)." + "description": "[Output Only] URL of the zone where the disk resides." } } }, @@ -736,59 +758,73 @@ "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "object", - "description": "A map of scoped disk lists.", + "description": "[Output Only] A map of scoped disk lists.", "additionalProperties": { "$ref": "DisksScopedList", - "description": "Name of the scope containing this set of disks." + "description": "[Output Only] Name of the scope containing this set of disks." } }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#diskAggregatedList for aggregated lists of persistent disks.", "default": "compute#diskAggregatedList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncated list request." }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] Server defined URL for this resource." } } }, "DiskList": { "id": "DiskList", "type": "object", - "description": "Contains a list of persistent disk resources.", + "description": "A list of Disk resources.", "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "array", - "description": "The persistent disk resources.", + "description": "[Output Only] A list of persistent disks.", "items": { "$ref": "Disk" } }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#diskList for lists of disks.", "default": "compute#diskList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncated list request." }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] Server defined URL for this resource." + } + } + }, + "DiskMoveRequest": { + "id": "DiskMoveRequest", + "type": "object", + "properties": { + "destinationZone": { + "type": "string", + "description": "The URL of the destination zone to move the disk to. This can be a full or partial URL. For example, the following are all valid URLs to a zone: \n- https://www.googleapis.com/compute/v1/projects/project/zones/zone \n- projects/project/zones/zone \n- zones/zone" + }, + "targetDisk": { + "type": "string", + "description": "The URL of the target disk to move. This can be a full or partial URL. For example, the following are all valid URLs to a disk: \n- https://www.googleapis.com/compute/v1/projects/project/zones/zone/disks/disk \n- projects/project/zones/zone/disks/disk \n- zones/zone/disks/disk" } } }, @@ -799,47 +835,47 @@ "properties": { "creationTimestamp": { "type": "string", - "description": "Creation timestamp in RFC3339 text format (output only)." + "description": "[Output Only] Creation timestamp in RFC3339 text format." }, "defaultDiskSizeGb": { "type": "string", - "description": "Server defined default disk size in gb (output only).", + "description": "[Output Only] Server defined default disk size in GB.", "format": "int64" }, "deprecated": { "$ref": "DeprecationStatus", - "description": "The deprecation status associated with this disk type." + "description": "[Output Only] The deprecation status associated with this disk type." }, "description": { "type": "string", - "description": "An optional textual description of the resource." + "description": "[Output Only] An optional textual description of the resource." }, "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only).", + "description": "[Output Only] Unique identifier for the resource; defined by the server.", "format": "uint64" }, "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Only] Type of the resource. Always compute#diskType for disk types.", "default": "compute#diskType" }, "name": { "type": "string", - "description": "Name of the resource.", + "description": "[Output Only] Name of the resource.", "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?" }, "selfLink": { "type": "string", - "description": "Server defined URL for the resource (output only)." + "description": "[Output Only] Server defined URL for the resource." }, "validDiskSize": { "type": "string", - "description": "An optional textual descroption of the valid disk size, e.g., \"10GB-10TB\"." + "description": "[Output Only] An optional textual description of the valid disk size, such as \"10GB-10TB\"." }, "zone": { "type": "string", - "description": "Url of the zone where the disk type resides (output only)." + "description": "[Output Only] URL of the zone where the disk type resides." } } }, @@ -849,28 +885,28 @@ "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "object", - "description": "A map of scoped disk type lists.", + "description": "[Output Only] A map of scoped disk type lists.", "additionalProperties": { "$ref": "DiskTypesScopedList", - "description": "Name of the scope containing this set of disk types." + "description": "[Output Only] Name of the scope containing this set of disk types." } }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#diskTypeAggregatedList.", "default": "compute#diskTypeAggregatedList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncated list request." }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] Server defined URL for this resource." } } }, @@ -881,27 +917,27 @@ "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "array", - "description": "The disk type resources.", + "description": "[Output Only] A list of Disk Type resources.", "items": { "$ref": "DiskType" } }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#diskTypeList for disk types.", "default": "compute#diskTypeList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncated list request." }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] Server defined URL for this resource." } } }, @@ -911,18 +947,18 @@ "properties": { "diskTypes": { "type": "array", - "description": "List of disk types contained in this scope.", + "description": "[Output Only] List of disk types contained in this scope.", "items": { "$ref": "DiskType" } }, "warning": { "type": "object", - "description": "Informational warning which replaces the list of disk types when the list is empty.", + "description": "[Output Only] Informational warning which replaces the list of disk types when the list is empty.", "properties": { "code": { "type": "string", - "description": "The warning type identifier for this warning.", + "description": "[Output Only] The warning type identifier for this warning.", "enum": [ "DEPRECATED_RESOURCE_USED", "DISK_SIZE_LARGER_THAN_IMAGE_SIZE", @@ -932,9 +968,11 @@ "NEXT_HOP_INSTANCE_NOT_FOUND", "NEXT_HOP_INSTANCE_NOT_ON_NETWORK", "NEXT_HOP_NOT_RUNNING", + "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", "RESOURCE_NOT_DELETED", + "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" ], "enumDescriptions": [ @@ -949,29 +987,31 @@ "", "", "", + "", + "", "" ] }, "data": { "type": "array", - "description": "Metadata for this warning in 'key: value' format.", + "description": "[Output Only] Metadata for this warning in key: value format.", "items": { "type": "object", "properties": { "key": { "type": "string", - "description": "A key for the warning data." + "description": "[Output Only] A key for the warning data." }, "value": { "type": "string", - "description": "A warning data value corresponding to the key." + "description": "[Output Only] A warning data value corresponding to the key." } } } }, "message": { "type": "string", - "description": "Optional human-readable details for this warning." + "description": "[Output Only] Optional human-readable details for this warning." } } } @@ -983,18 +1023,18 @@ "properties": { "disks": { "type": "array", - "description": "List of disks contained in this scope.", + "description": "[Output Only] List of disks contained in this scope.", "items": { "$ref": "Disk" } }, "warning": { "type": "object", - "description": "Informational warning which replaces the list of disks when the list is empty.", + "description": "[Output Only] Informational warning which replaces the list of disks when the list is empty.", "properties": { "code": { "type": "string", - "description": "The warning type identifier for this warning.", + "description": "[Output Only] The warning type identifier for this warning.", "enum": [ "DEPRECATED_RESOURCE_USED", "DISK_SIZE_LARGER_THAN_IMAGE_SIZE", @@ -1004,9 +1044,11 @@ "NEXT_HOP_INSTANCE_NOT_FOUND", "NEXT_HOP_INSTANCE_NOT_ON_NETWORK", "NEXT_HOP_NOT_RUNNING", + "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", "RESOURCE_NOT_DELETED", + "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" ], "enumDescriptions": [ @@ -1021,29 +1063,31 @@ "", "", "", + "", + "", "" ] }, "data": { "type": "array", - "description": "Metadata for this warning in 'key: value' format.", + "description": "[Output Only] Metadata for this warning in key: value format.", "items": { "type": "object", "properties": { "key": { "type": "string", - "description": "A key for the warning data." + "description": "[Output Only] A key for the warning data." }, "value": { "type": "string", - "description": "A warning data value corresponding to the key." + "description": "[Output Only] A warning data value corresponding to the key." } } } }, "message": { "type": "string", - "description": "Optional human-readable details for this warning." + "description": "[Output Only] Optional human-readable details for this warning." } } } @@ -1052,7 +1096,7 @@ "Firewall": { "id": "Firewall", "type": "object", - "description": "A firewall resource.", + "description": "A Firewall resource.", "properties": { "allowed": { "type": "array", @@ -1062,11 +1106,11 @@ "properties": { "IPProtocol": { "type": "string", - "description": "Required; this is the IP protocol that is allowed for this rule. This can either be one of the following well known protocol strings [\"tcp\", \"udp\", \"icmp\", \"esp\", \"ah\", \"sctp\"], or the IP protocol number." + "description": "The IP protocol that is allowed for this rule. The protocol type is required when creating a firewall. This value can either be one of the following well known protocol strings (tcp, udp, icmp, esp, ah, sctp), or the IP protocol number." }, "ports": { "type": "array", - "description": "An optional list of ports which are allowed. It is an error to specify this for any protocol that isn't UDP or TCP. Each entry must be either an integer or a range. If not specified, connections through any port are allowed.\n\nExample inputs include: [\"22\"], [\"80\",\"443\"] and [\"12345-12349\"].", + "description": "An optional list of ports which are allowed. This field is only applicable for UDP or TCP protocol. Each entry must be either an integer or a range. If not specified, connections through any port are allowed\n\nExample inputs include: [\"22\"], [\"80\",\"443\"], and [\"12345-12349\"].", "items": { "type": "string" } @@ -1076,7 +1120,7 @@ }, "creationTimestamp": { "type": "string", - "description": "Creation timestamp in RFC3339 text format (output only)." + "description": "[Output Only] Creation timestamp in RFC3339text format." }, "description": { "type": "string", @@ -1084,17 +1128,17 @@ }, "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only).", + "description": "[Output Only] Unique identifier for the resource; defined by the server.", "format": "uint64" }, "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Ony] Type of the resource. Always compute#firewall for firewall rules.", "default": "compute#firewall" }, "name": { "type": "string", - "description": "Name of the resource; provided by the client when the resource is created. The name must be 1-63 characters long, and comply with RFC1035.", + "description": "Name of the resource; provided by the client when the resource is created. The name must be 1-63 characters long, and comply with RFC1035. Specifically, the name must be 1-63 characters long and match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash.", "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "annotations": { "required": [ @@ -1105,29 +1149,29 @@ }, "network": { "type": "string", - "description": "URL of the network to which this firewall is applied; provided by the client when the firewall is created." + "description": "URL of the network resource for this firewall rule. This field is required for creating an instance but optional when creating a firewall rule. If not specified when creating a firewall rule, the default network is used:\nglobal/networks/default\nIf you choose to specify this property, you can specify the network as a full or partial URL. For example, the following are all valid URLs: \n- https://www.googleapis.com/compute/v1/projects/myproject/global/networks/my-network \n- projects/myproject/global/networks/my-network \n- global/networks/default" }, "selfLink": { "type": "string", - "description": "Server defined URL for the resource (output only)." + "description": "[Output Only] Server defined URL for the resource." }, "sourceRanges": { "type": "array", - "description": "A list of IP address blocks expressed in CIDR format which this rule applies to. One or both of sourceRanges and sourceTags may be set; an inbound connection is allowed if either the range or the tag of the source matches.", + "description": "The IP address blocks that this rule applies to, expressed in CIDR format. One or both of sourceRanges and sourceTags may be set.\n\nIf both properties are set, an inbound connection is allowed if the range or the tag of the source matches the sourceRanges OR matches the sourceTags property; the connection does not need to match both properties.", "items": { "type": "string" } }, "sourceTags": { "type": "array", - "description": "A list of instance tags which this rule applies to. One or both of sourceRanges and sourceTags may be set; an inbound connection is allowed if either the range or the tag of the source matches.", + "description": "A list of instance tags which this rule applies to. One or both of sourceRanges and sourceTags may be set.\n\nIf both properties are set, an inbound connection is allowed if the range or the tag of the source matches the sourceRanges OR matches the sourceTags property; the connection does not need to match both properties.", "items": { "type": "string" } }, "targetTags": { "type": "array", - "description": "A list of instance tags indicating sets of instances located on network which may make network connections as specified in allowed. If no targetTags are specified, the firewall rule applies to all instances on the specified network.", + "description": "A list of instance tags indicating sets of instances located on network which may make network connections as specified in allowed[]. If no targetTags are specified, the firewall rule applies to all instances on the specified network.", "items": { "type": "string" } @@ -1137,31 +1181,31 @@ "FirewallList": { "id": "FirewallList", "type": "object", - "description": "Contains a list of firewall resources.", + "description": "Contains a list of Firewall resources.", "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "array", - "description": "The firewall resources.", + "description": "[Output Only] A list of Firewall resources.", "items": { "$ref": "Firewall" } }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#firewallList for lists of firewalls.", "default": "compute#firewallList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncated list request." }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] Server defined URL for this resource." } } }, @@ -1275,7 +1319,7 @@ }, "items": { "type": "array", - "description": "The ForwardingRule resources.", + "description": "A list of ForwardingRule resources.", "items": { "$ref": "ForwardingRule" } @@ -1312,7 +1356,7 @@ "properties": { "code": { "type": "string", - "description": "The warning type identifier for this warning.", + "description": "[Output Only] The warning type identifier for this warning.", "enum": [ "DEPRECATED_RESOURCE_USED", "DISK_SIZE_LARGER_THAN_IMAGE_SIZE", @@ -1322,9 +1366,11 @@ "NEXT_HOP_INSTANCE_NOT_FOUND", "NEXT_HOP_INSTANCE_NOT_ON_NETWORK", "NEXT_HOP_NOT_RUNNING", + "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", "RESOURCE_NOT_DELETED", + "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" ], "enumDescriptions": [ @@ -1339,29 +1385,31 @@ "", "", "", + "", + "", "" ] }, "data": { "type": "array", - "description": "Metadata for this warning in 'key: value' format.", + "description": "[Output Only] Metadata for this warning in key: value format.", "items": { "type": "object", "properties": { "key": { "type": "string", - "description": "A key for the warning data." + "description": "[Output Only] A key for the warning data." }, "value": { "type": "string", - "description": "A warning data value corresponding to the key." + "description": "[Output Only] A warning data value corresponding to the key." } } } }, "message": { "type": "string", - "description": "Optional human-readable details for this warning." + "description": "[Output Only] Optional human-readable details for this warning." } } } @@ -1485,7 +1533,7 @@ }, "timeoutSec": { "type": "integer", - "description": "How long (in seconds) to wait before claiming failure. The default value is 5 seconds.", + "description": "How long (in seconds) to wait before claiming failure. The default value is 5 seconds. It is invalid for timeoutSec to have greater value than checkIntervalSec.", "format": "int32" }, "unhealthyThreshold": { @@ -1506,7 +1554,7 @@ }, "items": { "type": "array", - "description": "The HttpHealthCheck resources.", + "description": "A list of HttpHealthCheck resources.", "items": { "$ref": "HttpHealthCheck" } @@ -1529,7 +1577,7 @@ "Image": { "id": "Image", "type": "object", - "description": "A disk image resource.", + "description": "An Image resource.", "properties": { "archiveSizeBytes": { "type": "string", @@ -1538,7 +1586,7 @@ }, "creationTimestamp": { "type": "string", - "description": "Creation timestamp in RFC3339 text format (output only)." + "description": "[Output Only] Creation timestamp in RFC3339 text format." }, "deprecated": { "$ref": "DeprecationStatus", @@ -1550,29 +1598,29 @@ }, "diskSizeGb": { "type": "string", - "description": "Size of the image when restored onto a disk (in GiB).", + "description": "Size of the image when restored onto a persistent disk (in GB).", "format": "int64" }, "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only).", + "description": "[Output Only] Unique identifier for the resource; defined by the server.", "format": "uint64" }, "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Only] Type of the resource. Always compute#image for images.", "default": "compute#image" }, "licenses": { "type": "array", - "description": "Public visible licenses.", + "description": "Any applicable publicly visible licenses.", "items": { "type": "string" } }, "name": { "type": "string", - "description": "Name of the resource; provided by the client when the resource is created. The name must be 1-63 characters long, and comply with RFC1035.", + "description": "Name of the resource; provided by the client when the resource is created. The name must be 1-63 characters long, and comply with RFC1035. Specifically, the name must be 1-63 characters long and match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash.", "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "annotations": { "required": [ @@ -1582,11 +1630,11 @@ }, "rawDisk": { "type": "object", - "description": "The raw disk image parameters.", + "description": "The parameters of the raw disk image.", "properties": { "containerType": { "type": "string", - "description": "The format used to encode and transmit the block device. Should be TAR. This is just a container and transmission format and not a runtime format. Provided by the client when the disk image is created.", + "description": "The format used to encode and transmit the block device, which should be TAR. This is just a container and transmission format and not a runtime format. Provided by the client when the disk image is created.", "enum": [ "TAR" ], @@ -1601,7 +1649,7 @@ }, "source": { "type": "string", - "description": "The full Google Cloud Storage URL where the disk image is stored; provided by the client when the disk image is created.", + "description": "The full Google Cloud Storage URL where the disk image is stored. You must provide either this property or the sourceDisk property but not both.", "annotations": { "required": [ "compute.images.insert" @@ -1612,19 +1660,19 @@ }, "selfLink": { "type": "string", - "description": "Server defined URL for the resource (output only)." + "description": "[Output Only] Server defined URL for the resource." }, "sourceDisk": { "type": "string", - "description": "The source disk used to create this image." + "description": "URL of the The source disk used to create this image. This can be a full or valid partial URL. You must provide either this property or the rawDisk.source property but not both to create an image. For example, the following are valid values: \n- https://www.googleapis.com/compute/v1/projects/project/zones/zone/disk/disk \n- projects/project/zones/zone/disk/disk \n- zones/zone/disks/disk" }, "sourceDiskId": { "type": "string", - "description": "The 'id' value of the disk used to create this image. This value may be used to determine whether the image was taken from the current or a previous instance of a given disk name." + "description": "The ID value of the disk used to create this image. This value may be used to determine whether the image was taken from the current or a previous instance of a given disk name." }, "sourceType": { "type": "string", - "description": "Must be \"RAW\"; provided by the client when the disk image is created.", + "description": "The type of the image used to create this disk. The default and only value is RAW", "default": "RAW", "enum": [ "RAW" @@ -1635,7 +1683,7 @@ }, "status": { "type": "string", - "description": "Status of the image (output only). It will be one of the following READY - after image has been successfully created and is ready for use FAILED - if creating the image fails for some reason PENDING - the image creation is in progress An image can be used to create other resources suck as instances only after the image has been successfully created and the status is set to READY.", + "description": "[Output Only] The status of the image. An image can be used to create other resources, such as instances, only after the image has been successfully created and the status is set to READY. Possible values are FAILED, PENDING, or READY.", "enum": [ "FAILED", "PENDING", @@ -1652,7 +1700,7 @@ "ImageList": { "id": "ImageList", "type": "object", - "description": "Contains a list of disk image resources.", + "description": "Contains a list of Image resources.", "properties": { "id": { "type": "string", @@ -1660,7 +1708,7 @@ }, "items": { "type": "array", - "description": "The disk image resources.", + "description": "A list of Image resources.", "items": { "$ref": "Image" } @@ -1683,15 +1731,19 @@ "Instance": { "id": "Instance", "type": "object", - "description": "An instance resource.", + "description": "An Instance resource.", "properties": { "canIpForward": { "type": "boolean", - "description": "Allows this instance to send packets with source IP addresses other than its own and receive packets with destination IP addresses other than its own. If this instance will be used as an IP gateway or it will be set as the next-hop in a Route resource, say true. If unsure, leave this set to false." + "description": "Allows this instance to send and receive packets with non-matching destination or source IPs. This is required if you plan to use this instance to forward routes. For more information, see Enabling IP Forwarding." + }, + "cpuPlatform": { + "type": "string", + "description": "[Output Only] The CPU platform used by this instance." }, "creationTimestamp": { "type": "string", - "description": "Creation timestamp in RFC3339 text format (output only)." + "description": "[Output Only] Creation timestamp in RFC3339 text format." }, "description": { "type": "string", @@ -1706,17 +1758,17 @@ }, "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only).", + "description": "[Output Only] Unique identifier for the resource; defined by the server.", "format": "uint64" }, "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Only] Type of the resource. Always compute#instance for instances.", "default": "compute#instance" }, "machineType": { "type": "string", - "description": "URL of the machine type resource describing which machine type to use to host the instance; provided by the client when the instance is created.", + "description": "Full or partial URL of the machine type resource to use for this instance. This is provided by the client when the instance is created. For example, the following is a valid partial url:\n\nzones/zone/machineTypes/machine-type", "annotations": { "required": [ "compute.instances.insert" @@ -1725,21 +1777,15 @@ }, "metadata": { "$ref": "Metadata", - "description": "Metadata key/value pairs assigned to this instance. Consists of custom metadata or predefined keys; see Instance documentation for more information." + "description": "The metadata key/value pairs assigned to this instance. This includes custom metadata and predefined keys." }, "name": { "type": "string", - "description": "Name of the resource; provided by the client when the resource is created. The name must be 1-63 characters long, and comply with RFC1035.", - "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", - "annotations": { - "required": [ - "compute.instances.insert" - ] - } + "description": "Name of the resource; provided by the client when the resource is created. The name must be 1-63 characters long, and comply with RFC1035. Specifically, the name must be 1-63 characters long and match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash." }, "networkInterfaces": { "type": "array", - "description": "Array of configurations for this interface. This specifies how this interface is configured to interact with other network services, such as connecting to the internet. Currently, ONE_TO_ONE_NAT is the only access config supported. If there are no accessConfigs specified, then this instance will have no external internet access.", + "description": "An array of configurations for this interface. This specifies how this interface is configured to interact with other network services, such as connecting to the internet.", "items": { "$ref": "NetworkInterface" } @@ -1750,24 +1796,26 @@ }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] Server defined URL for this resource." }, "serviceAccounts": { "type": "array", - "description": "A list of service accounts each with specified scopes, for which access tokens are to be made available to the instance through metadata queries.", + "description": "A list of service accounts, with their specified scopes, authorized for this instance. Service accounts generate access tokens that can be accessed through the metadata server and used to authenticate applications on the instance. See Authenticating from Google Compute Engine for more information.", "items": { "$ref": "ServiceAccount" } }, "status": { "type": "string", - "description": "Instance status. One of the following values: \"PROVISIONING\", \"STAGING\", \"RUNNING\", \"STOPPING\", \"STOPPED\", \"TERMINATED\" (output only).", + "description": "[Output Only] The status of the instance. One of the following values: PROVISIONING, STAGING, RUNNING, STOPPING, and TERMINATED.", "enum": [ "PROVISIONING", "RUNNING", "STAGING", "STOPPED", "STOPPING", + "SUSPENDED", + "SUSPENDING", "TERMINATED" ], "enumDescriptions": [ @@ -1776,20 +1824,22 @@ "", "", "", + "", + "", "" ] }, "statusMessage": { "type": "string", - "description": "An optional, human-readable explanation of the status (output only)." + "description": "[Output Only] An optional, human-readable explanation of the status." }, "tags": { "$ref": "Tags", - "description": "A list of tags to be applied to this instance. Used to identify valid sources or targets for network firewalls. Provided by the client on instance creation. The tags can be later modified by the setTags method. Each tag within the list must comply with RFC1035." + "description": "A list of tags to appy to this instance. Tags are used to identify valid sources or targets for network firewalls and are specified by the client during instance creation. The tags can be later modified by the setTags method. Each tag within the list must comply with RFC1035." }, "zone": { "type": "string", - "description": "URL of the zone where the instance resides (output only)." + "description": "[Output Only] URL of the zone where the instance resides." } } }, @@ -1799,11 +1849,11 @@ "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "object", - "description": "A map of scoped instance lists.", + "description": "[Output Only] A map of scoped instance lists.", "additionalProperties": { "$ref": "InstancesScopedList", "description": "Name of the scope containing this set of instances." @@ -1811,16 +1861,16 @@ }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#instanceAggregatedList for aggregated lists of Instance resources.", "default": "compute#instanceAggregatedList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncated list request." }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] Server defined URL for this resource." } } }, @@ -1831,27 +1881,41 @@ "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "array", - "description": "A list of instance resources.", + "description": "[Output Only] A list of Instance resources.", "items": { "$ref": "Instance" } }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#instanceList for lists of Instance resources.", "default": "compute#instanceList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncated list request." }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] Server defined URL for this resource." + } + } + }, + "InstanceMoveRequest": { + "id": "InstanceMoveRequest", + "type": "object", + "properties": { + "destinationZone": { + "type": "string", + "description": "The URL of the destination zone to move the instance to. This can be a full or partial URL. For example, the following are all valid URLs to a zone: \n- https://www.googleapis.com/compute/v1/projects/project/zones/zone \n- projects/project/zones/zone \n- zones/zone" + }, + "targetInstance": { + "type": "string", + "description": "The URL of the target instance to move. This can be a full or partial URL. For example, the following are all valid URLs to an instance: \n- https://www.googleapis.com/compute/v1/projects/project/zones/zone/instances/instance \n- projects/project/zones/zone/instances/instance \n- zones/zone/instances/instance" } } }, @@ -1862,22 +1926,22 @@ "properties": { "canIpForward": { "type": "boolean", - "description": "Allows instances created based on this template to send packets with source IP addresses other than their own and receive packets with destination IP addresses other than their own. If these instances will be used as an IP gateway or it will be set as the next-hop in a Route resource, say true. If unsure, leave this set to false." + "description": "A boolean that specifies if instances created from this template can send packets with source IP addresses other than their own or receive packets with destination IP addresses other than their own. If you use these instances as an IP gateway or as the next-hop in a Route resource, specify true. Otherwise, specify false." }, "description": { "type": "string", - "description": "An optional textual description for the instances created based on the instance template resource; provided by the client when the template is created." + "description": "An optional text description for the instances that are created from this instance template." }, "disks": { "type": "array", - "description": "Array of disks associated with instance created based on this template.", + "description": "An array of disks that are associated with the instances that are created from this template.", "items": { "$ref": "AttachedDisk" } }, "machineType": { "type": "string", - "description": "Name of the machine type resource describing which machine type to use to host the instances created based on this template; provided by the client when the instance template is created.", + "description": "The machine type to use for instances that are created from this template.", "annotations": { "required": [ "compute.instanceTemplates.insert" @@ -1886,29 +1950,29 @@ }, "metadata": { "$ref": "Metadata", - "description": "Metadata key/value pairs assigned to instances created based on this template. Consists of custom metadata or predefined keys; see Instance documentation for more information." + "description": "The metadata key/value pairs to assign to instances that are created from this template. These pairs can consist of custom metadata or predefined keys. See Project and instance metadata for more information." }, "networkInterfaces": { "type": "array", - "description": "Array of configurations for this interface. This specifies how this interface is configured to interact with other network services, such as connecting to the internet. Currently, ONE_TO_ONE_NAT is the only access config supported. If there are no accessConfigs specified, then this instances created based based on this template will have no external internet access.", + "description": "An array of network access configurations for this interface. This specifies how this interface is configured to interact with other network services, such as connecting to the internet. Currently, ONE_TO_ONE_NAT is the only supported access configuration. If you do not specify any access configurations, the instances that are created from this template will have no external internet access.", "items": { "$ref": "NetworkInterface" } }, "scheduling": { "$ref": "Scheduling", - "description": "Scheduling options for the instances created based on this template." + "description": "A list of scheduling options for the instances that are created from this template." }, "serviceAccounts": { "type": "array", - "description": "A list of service accounts each with specified scopes, for which access tokens are to be made available to the instances created based on this template, through metadata queries.", + "description": "A list of service accounts with specified scopes. Access tokens for these service accounts are available to the instances that are created from this template. Use metadata queries to obtain the access tokens for these instances.", "items": { "$ref": "ServiceAccount" } }, "tags": { "$ref": "Tags", - "description": "A list of tags to be applied to the instances created based on this template used to identify valid sources or targets for network firewalls. Provided by the client on instance creation. The tags can be later modified by the setTags method. Each tag within the list must comply with RFC1035." + "description": "A list of tags to apply to the instances that are created from this template. The tags identify valid sources or targets for network firewalls. The setTags method can modify this list of tags. Each tag within the list must comply with RFC1035." } } }, @@ -1928,25 +1992,25 @@ "properties": { "creationTimestamp": { "type": "string", - "description": "Creation timestamp in RFC3339 text format (output only)." + "description": "[Output Only] The creation timestamp for this instance template in RFC3339 text format." }, "description": { "type": "string", - "description": "An optional textual description of the instance template resource; provided by the client when the resource is created." + "description": "An optional text description for the instance template." }, "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only).", + "description": "[Output Only] A unique identifier for this instance template. The server defines this identifier.", "format": "uint64" }, "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Only] The resource type, which is always compute#instanceTemplate for instance templates.", "default": "compute#instanceTemplate" }, "name": { "type": "string", - "description": "Name of the instance template resource; provided by the client when the resource is created. The name must be 1-63 characters long, and comply with RFC1035", + "description": "The name of the instance template. The name must be 1-63 characters long, and comply with RFC1035.", "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "annotations": { "required": [ @@ -1956,42 +2020,42 @@ }, "properties": { "$ref": "InstanceProperties", - "description": "The instance properties portion of this instance template resource." + "description": "The instance properties for the instance template resource." }, "selfLink": { "type": "string", - "description": "Server defined URL for the resource (output only)." + "description": "[Output Only] The URL for this instance template. The server defines this URL." } } }, "InstanceTemplateList": { "id": "InstanceTemplateList", "type": "object", - "description": "Contains a list of instance template resources.", + "description": "A list of instance templates.", "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] A unique identifier for this instance template. The server defines this identifier." }, "items": { "type": "array", - "description": "A list of instance template resources.", + "description": "A list of InstanceTemplate resources.", "items": { "$ref": "InstanceTemplate" } }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] The resource type, which is always compute#instanceTemplatesListResponse for instance template lists.", "default": "compute#instanceTemplateList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token that is used to continue a truncated list request." }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] The URL for this instance template list. The server defines this URL." } } }, @@ -2001,18 +2065,18 @@ "properties": { "instances": { "type": "array", - "description": "List of instances contained in this scope.", + "description": "[Output Only] List of instances contained in this scope.", "items": { "$ref": "Instance" } }, "warning": { "type": "object", - "description": "Informational warning which replaces the list of instances when the list is empty.", + "description": "[Output Only] Informational warning which replaces the list of instances when the list is empty.", "properties": { "code": { "type": "string", - "description": "The warning type identifier for this warning.", + "description": "[Output Only] The warning type identifier for this warning.", "enum": [ "DEPRECATED_RESOURCE_USED", "DISK_SIZE_LARGER_THAN_IMAGE_SIZE", @@ -2022,9 +2086,11 @@ "NEXT_HOP_INSTANCE_NOT_FOUND", "NEXT_HOP_INSTANCE_NOT_ON_NETWORK", "NEXT_HOP_NOT_RUNNING", + "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", "RESOURCE_NOT_DELETED", + "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" ], "enumDescriptions": [ @@ -2039,29 +2105,31 @@ "", "", "", + "", + "", "" ] }, "data": { "type": "array", - "description": "Metadata for this warning in 'key: value' format.", + "description": "[Output Only] Metadata for this warning in key: value format.", "items": { "type": "object", "properties": { "key": { "type": "string", - "description": "A key for the warning data." + "description": "[Output Only] A key for the warning data." }, "value": { "type": "string", - "description": "A warning data value corresponding to the key." + "description": "[Output Only] A warning data value corresponding to the key." } } } }, "message": { "type": "string", - "description": "Optional human-readable details for this warning." + "description": "[Output Only] Optional human-readable details for this warning." } } } @@ -2078,12 +2146,12 @@ }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#license for licenses.", "default": "compute#license" }, "name": { "type": "string", - "description": "Name of the resource; provided by the client when the resource is created. The name must be 1-63 characters long, and comply with RFC1035.", + "description": "Name of the resource. The name must be 1-63 characters long, and comply with RCF1035.", "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "annotations": { "required": [ @@ -2093,40 +2161,40 @@ }, "selfLink": { "type": "string", - "description": "Server defined URL for the resource (output only)." + "description": "[Output Only] Server defined URL for the resource." } } }, "MachineType": { "id": "MachineType", "type": "object", - "description": "A machine type resource.", + "description": "A Machine Type resource.", "properties": { "creationTimestamp": { "type": "string", - "description": "Creation timestamp in RFC3339 text format (output only)." + "description": "[Output Only] Creation timestamp in RFC3339 text format." }, "deprecated": { "$ref": "DeprecationStatus", - "description": "The deprecation status associated with this machine type." + "description": "[Output Only] The deprecation status associated with this machine type." }, "description": { "type": "string", - "description": "An optional textual description of the resource." + "description": "[Output Only] An optional textual description of the resource." }, "guestCpus": { "type": "integer", - "description": "Count of CPUs exposed to the instance.", + "description": "[Output Only] The tumber of CPUs exposed to the instance.", "format": "int32" }, "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only).", + "description": "[Output Only] Unique identifier for the resource; defined by the server.", "format": "uint64" }, "imageSpaceGb": { "type": "integer", - "description": "Space allotted for the image, defined in GB.", + "description": "[Deprecated] This property is deprecated and will never be populated with any relevant values.", "format": "int32" }, "kind": { @@ -2136,27 +2204,27 @@ }, "maximumPersistentDisks": { "type": "integer", - "description": "Maximum persistent disks allowed.", + "description": "[Output Only] Maximum persistent disks allowed.", "format": "int32" }, "maximumPersistentDisksSizeGb": { "type": "string", - "description": "Maximum total persistent disks size (GB) allowed.", + "description": "[Output Only] Maximum total persistent disks size (GB) allowed.", "format": "int64" }, "memoryMb": { "type": "integer", - "description": "Physical memory assigned to the instance, defined in MB.", + "description": "[Output Only] The amount of physical memory available to the instance, defined in MB.", "format": "int32" }, "name": { "type": "string", - "description": "Name of the resource.", + "description": "[Output Only] Name of the resource.", "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?" }, "scratchDisks": { "type": "array", - "description": "List of extended scratch disks assigned to the instance.", + "description": "[Output Only] List of extended scratch disks assigned to the instance.", "items": { "type": "object", "properties": { @@ -2170,11 +2238,11 @@ }, "selfLink": { "type": "string", - "description": "Server defined URL for the resource (output only)." + "description": "[Output Only] Server defined URL for the resource." }, "zone": { "type": "string", - "description": "Url of the zone where the machine type resides (output only)." + "description": "[Output Only] The name of the zone where the machine type resides, such as us-central1-a." } } }, @@ -2184,59 +2252,59 @@ "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "object", - "description": "A map of scoped machine type lists.", + "description": "[Output Only] A map of scoped machine type lists.", "additionalProperties": { "$ref": "MachineTypesScopedList", - "description": "Name of the scope containing this set of machine types." + "description": "[Output Only] Name of the scope containing this set of machine types." } }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#machineTypeAggregatedList for aggregated lists of machine types.", "default": "compute#machineTypeAggregatedList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncated list request." }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] Server defined URL for this resource." } } }, "MachineTypeList": { "id": "MachineTypeList", "type": "object", - "description": "Contains a list of machine type resources.", + "description": "Contains a list of Machine Type resources.", "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "array", - "description": "The machine type resources.", + "description": "[Output Only] A list of Machine Type resources.", "items": { "$ref": "MachineType" } }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#machineTypeList for lists of machine types.", "default": "compute#machineTypeList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncated list request." }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] Server defined URL for this resource." } } }, @@ -2246,18 +2314,18 @@ "properties": { "machineTypes": { "type": "array", - "description": "List of machine types contained in this scope.", + "description": "[Output Only] List of machine types contained in this scope.", "items": { "$ref": "MachineType" } }, "warning": { "type": "object", - "description": "Informational warning which replaces the list of machine types when the list is empty.", + "description": "[Output Only] An informational warning that appears when the machine types list is empty.", "properties": { "code": { "type": "string", - "description": "The warning type identifier for this warning.", + "description": "[Output Only] The warning type identifier for this warning.", "enum": [ "DEPRECATED_RESOURCE_USED", "DISK_SIZE_LARGER_THAN_IMAGE_SIZE", @@ -2267,9 +2335,11 @@ "NEXT_HOP_INSTANCE_NOT_FOUND", "NEXT_HOP_INSTANCE_NOT_ON_NETWORK", "NEXT_HOP_NOT_RUNNING", + "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", "RESOURCE_NOT_DELETED", + "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" ], "enumDescriptions": [ @@ -2284,29 +2354,31 @@ "", "", "", + "", + "", "" ] }, "data": { "type": "array", - "description": "Metadata for this warning in 'key: value' format.", + "description": "[Output Only] Metadata for this warning in key: value format.", "items": { "type": "object", "properties": { "key": { "type": "string", - "description": "A key for the warning data." + "description": "[Output Only] A key for the warning data." }, "value": { "type": "string", - "description": "A warning data value corresponding to the key." + "description": "[Output Only] A warning data value corresponding to the key." } } } }, "message": { "type": "string", - "description": "Optional human-readable details for this warning." + "description": "[Output Only] Optional human-readable details for this warning." } } } @@ -2319,7 +2391,7 @@ "properties": { "fingerprint": { "type": "string", - "description": "Fingerprint of this resource. A hash of the metadata's contents. This field is used for optimistic locking. An up-to-date metadata fingerprint must be provided in order to modify metadata.", + "description": "Specifies a fingerprint for this request, which is essentially a hash of the metadata's contents and used for optimistic locking. The fingerprint is initially generated by Compute Engine and changes after every request to modify or update metadata. You must always provide an up-to-date fingerprint hash in order to update or change metadata.", "format": "byte" }, "items": { @@ -2354,7 +2426,7 @@ }, "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Only] Type of the resource. Always compute#metadata for metadata.", "default": "compute#metadata" } } @@ -2366,7 +2438,7 @@ "properties": { "IPv4Range": { "type": "string", - "description": "Required; The range of internal addresses that are legal on this network. This range is a CIDR specification, for example: 192.168.0.0/16. Provided by the client when the network is created.", + "description": "The range of internal addresses that are legal on this network. This range is a CIDR specification, for example: 192.168.0.0/16. Provided by the client when the network is created.", "pattern": "[0-9]{1,3}(?:\\.[0-9]{1,3}){3}/[0-9]{1,2}", "annotations": { "required": [ @@ -2376,7 +2448,7 @@ }, "creationTimestamp": { "type": "string", - "description": "Creation timestamp in RFC3339 text format (output only)." + "description": "[Output Only] Creation timestamp in RFC3339 text format." }, "description": { "type": "string", @@ -2384,22 +2456,22 @@ }, "gatewayIPv4": { "type": "string", - "description": "An optional address that is used for default routing to other networks. This must be within the range specified by IPv4Range, and is typically the first usable address in that range. If not specified, the default value is the first usable address in IPv4Range.", + "description": "A gateway address for default routing to other networks. This value is read only and is selected by the Google Compute Engine, typically as the first usable address in the IPv4Range.", "pattern": "[0-9]{1,3}(?:\\.[0-9]{1,3}){3}" }, "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only).", + "description": "[Output Only] Unique identifier for the resource; defined by the server.", "format": "uint64" }, "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Only] Type of the resource. Always compute#network for networks.", "default": "compute#network" }, "name": { "type": "string", - "description": "Name of the resource; provided by the client when the resource is created. The name must be 1-63 characters long, and comply with RFC1035.", + "description": "Name of the resource; provided by the client when the resource is created. The name must be 1-63 characters long, and comply with RFC1035. Specifically, the name must be 1-63 characters long and match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash.", "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "annotations": { "required": [ @@ -2409,7 +2481,7 @@ }, "selfLink": { "type": "string", - "description": "Server defined URL for the resource (output only)." + "description": "[Output Only] Server defined URL for the resource." } } }, @@ -2420,18 +2492,18 @@ "properties": { "accessConfigs": { "type": "array", - "description": "Array of configurations for this interface. This specifies how this interface is configured to interact with other network services, such as connecting to the internet. Currently, ONE_TO_ONE_NAT is the only access config supported. If there are no accessConfigs specified, then this instance will have no external internet access.", + "description": "An array of configurations for this interface. Currently, \u003ccodeONE_TO_ONE_NAT is the only access config supported. If there are no accessConfigs specified, then this instance will have no external internet access.", "items": { "$ref": "AccessConfig" } }, "name": { "type": "string", - "description": "Name of the network interface, determined by the server; for network devices, these are e.g. eth0, eth1, etc. (output only)." + "description": "[Output Only] The name of the network interface, generated by the server. For network devices, these are eth0, eth1, etc." }, "network": { "type": "string", - "description": "URL of the network resource attached to this interface.", + "description": "URL of the network resource for this instance. This is required for creating an instance but optional when creating a firewall rule. If not specified when creating a firewall rule, the default network is used:\n\nglobal/networks/default \n\nIf you specify this property, you can specify the network as a full or partial URL. For example, the following are all valid URLs: \n- https://www.googleapis.com/compute/v1/projects/project/global/networks/network \n- projects/project/global/networks/network \n- global/networks/default", "annotations": { "required": [ "compute.instances.insert" @@ -2440,38 +2512,38 @@ }, "networkIP": { "type": "string", - "description": "An optional IPV4 internal network address assigned to the instance for this network interface (output only)." + "description": "[Output Only] An optional IPV4 internal network address assigned to the instance for this network interface." } } }, "NetworkList": { "id": "NetworkList", "type": "object", - "description": "Contains a list of network resources.", + "description": "Contains a list of Network resources.", "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "array", - "description": "The network resources.", + "description": "[Output Only] A list of Network resources.", "items": { "$ref": "Network" } }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#networkList for lists of networks.", "default": "compute#networkList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncated list request." }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] Server defined URL for this resource ." } } }, @@ -2482,37 +2554,37 @@ "properties": { "clientOperationId": { "type": "string", - "description": "An optional identifier specified by the client when the mutation was initiated. Must be unique for all operation resources in the project (output only)." + "description": "[Output Only] An optional identifier specified by the client when the mutation was initiated. Must be unique for all operation resources in the project." }, "creationTimestamp": { "type": "string", - "description": "Creation timestamp in RFC3339 text format (output only)." + "description": "[Output Only] Creation timestamp in RFC3339 text format." }, "endTime": { "type": "string", - "description": "The time that this operation was completed. This is in RFC 3339 format (output only)." + "description": "[Output Only] The time that this operation was completed. This is in RFC3339 text format." }, "error": { "type": "object", - "description": "If errors occurred during processing of this operation, this field will be populated (output only).", + "description": "[Output Only] If errors are generated during processing of the operation, this field will be populated.", "properties": { "errors": { "type": "array", - "description": "The array of errors encountered while processing this operation.", + "description": "[Output Only] The array of errors encountered while processing this operation.", "items": { "type": "object", "properties": { "code": { "type": "string", - "description": "The error type identifier for this error." + "description": "[Output Only] The error type identifier for this error." }, "location": { "type": "string", - "description": "Indicates the field in the request which caused the error. This property is optional." + "description": "[Output Only] Indicates the field in the request which caused the error. This property is optional." }, "message": { "type": "string", - "description": "An optional, human-readable error message." + "description": "[Output Only] An optional, human-readable error message." } } } @@ -2521,55 +2593,55 @@ }, "httpErrorMessage": { "type": "string", - "description": "If operation fails, the HTTP error message returned, e.g. NOT FOUND. (output only)." + "description": "[Output Only] If the operation fails, this field contains the HTTP error message that was returned, such as NOT FOUND." }, "httpErrorStatusCode": { "type": "integer", - "description": "If operation fails, the HTTP error status code returned, e.g. 404. (output only).", + "description": "[Output Only] If the operation fails, this field contains the HTTP error message that was returned, such as 404.", "format": "int32" }, "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only).", + "description": "[Output Only] Unique identifier for the resource; defined by the server.", "format": "uint64" }, "insertTime": { "type": "string", - "description": "The time that this operation was requested. This is in RFC 3339 format (output only)." + "description": "[Output Only] The time that this operation was requested. This is in RFC3339 text format." }, "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Only] Type of the resource. Always compute#Operation for Operation resources.", "default": "compute#operation" }, "name": { "type": "string", - "description": "Name of the resource (output only)." + "description": "[Output Only] Name of the resource." }, "operationType": { "type": "string", - "description": "Type of the operation. Examples include \"insert\", \"update\", and \"delete\" (output only)." + "description": "[Output Only] Type of the operation, such as insert, update, and delete." }, "progress": { "type": "integer", - "description": "An optional progress indicator that ranges from 0 to 100. There is no requirement that this be linear or support any granularity of operations. This should not be used to guess at when the operation will be complete. This number should be monotonically increasing as the operation progresses (output only).", + "description": "[Output Only] An optional progress indicator that ranges from 0 to 100. There is no requirement that this be linear or support any granularity of operations. This should not be used to guess at when the operation will be complete. This number should monotonically increase as the operation progresses.", "format": "int32" }, "region": { "type": "string", - "description": "URL of the region where the operation resides (output only)." + "description": "[Output Only] URL of the region where the operation resides. Only applicable for regional resources." }, "selfLink": { "type": "string", - "description": "Server defined URL for the resource (output only)." + "description": "[Output Only] Server defined URL for the resource." }, "startTime": { "type": "string", - "description": "The time that this operation was started by the server. This is in RFC 3339 format (output only)." + "description": "[Output Only] The time that this operation was started by the server. This is in RFC3339 text format." }, "status": { "type": "string", - "description": "Status of the operation. Can be one of the following: \"PENDING\", \"RUNNING\", or \"DONE\" (output only).", + "description": "[Output Only] Status of the operation. Can be one of the following: PENDING, RUNNING, or DONE.", "enum": [ "DONE", "PENDING", @@ -2583,30 +2655,30 @@ }, "statusMessage": { "type": "string", - "description": "An optional textual description of the current status of the operation (output only)." + "description": "[Output Only] An optional textual description of the current status of the operation." }, "targetId": { "type": "string", - "description": "Unique target id which identifies a particular incarnation of the target (output only).", + "description": "[Output Only] Unique target ID which identifies a particular incarnation of the target.", "format": "uint64" }, "targetLink": { "type": "string", - "description": "URL of the resource the operation is mutating (output only)." + "description": "[Output Only] URL of the resource the operation is mutating." }, "user": { "type": "string", - "description": "User who requested the operation, for example \"user@example.com\" (output only)." + "description": "[Output Only] User who requested the operation, for example: user@example.com." }, "warnings": { "type": "array", - "description": "If warning messages generated during processing of this operation, this field will be populated (output only).", + "description": "[Output Only] If warning messages are generated during processing of the operation, this field will be populated.", "items": { "type": "object", "properties": { "code": { "type": "string", - "description": "The warning type identifier for this warning.", + "description": "[Output Only] The warning type identifier for this warning.", "enum": [ "DEPRECATED_RESOURCE_USED", "DISK_SIZE_LARGER_THAN_IMAGE_SIZE", @@ -2616,9 +2688,11 @@ "NEXT_HOP_INSTANCE_NOT_FOUND", "NEXT_HOP_INSTANCE_NOT_ON_NETWORK", "NEXT_HOP_NOT_RUNNING", + "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", "RESOURCE_NOT_DELETED", + "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" ], "enumDescriptions": [ @@ -2633,36 +2707,38 @@ "", "", "", + "", + "", "" ] }, "data": { "type": "array", - "description": "Metadata for this warning in 'key: value' format.", + "description": "[Output Only] Metadata for this warning in key: value format.", "items": { "type": "object", "properties": { "key": { "type": "string", - "description": "A key for the warning data." + "description": "[Output Only] A key for the warning data." }, "value": { "type": "string", - "description": "A warning data value corresponding to the key." + "description": "[Output Only] A warning data value corresponding to the key." } } } }, "message": { "type": "string", - "description": "Optional human-readable details for this warning." + "description": "[Output Only] Optional human-readable details for this warning." } } } }, "zone": { "type": "string", - "description": "URL of the zone where the operation resides (output only)." + "description": "[Output Only] URL of the zone where the operation resides." } } }, @@ -2672,28 +2748,28 @@ "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "object", - "description": "A map of scoped operation lists.", + "description": "[Output Only] A map of scoped operation lists.", "additionalProperties": { "$ref": "OperationsScopedList", - "description": "Name of the scope containing this set of operations." + "description": "[Output Only] Name of the scope containing this set of operations." } }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#operationAggregatedList for aggregated lists of operations.", "default": "compute#operationAggregatedList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncated list request." }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] Server defined URL for this resource." } } }, @@ -2704,27 +2780,27 @@ "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "array", - "description": "The operation resources.", + "description": "[Output Only] The operation resources.", "items": { "$ref": "Operation" } }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#operations for Operations resource.", "default": "compute#operationList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncate." }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] Server defined URL for this resource." } } }, @@ -2734,18 +2810,18 @@ "properties": { "operations": { "type": "array", - "description": "List of operations contained in this scope.", + "description": "[Output Only] List of operations contained in this scope.", "items": { "$ref": "Operation" } }, "warning": { "type": "object", - "description": "Informational warning which replaces the list of operations when the list is empty.", + "description": "[Output Only] Informational warning which replaces the list of operations when the list is empty.", "properties": { "code": { "type": "string", - "description": "The warning type identifier for this warning.", + "description": "[Output Only] The warning type identifier for this warning.", "enum": [ "DEPRECATED_RESOURCE_USED", "DISK_SIZE_LARGER_THAN_IMAGE_SIZE", @@ -2755,9 +2831,11 @@ "NEXT_HOP_INSTANCE_NOT_FOUND", "NEXT_HOP_INSTANCE_NOT_ON_NETWORK", "NEXT_HOP_NOT_RUNNING", + "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", "RESOURCE_NOT_DELETED", + "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" ], "enumDescriptions": [ @@ -2772,29 +2850,31 @@ "", "", "", + "", + "", "" ] }, "data": { "type": "array", - "description": "Metadata for this warning in 'key: value' format.", + "description": "[Output Only] Metadata for this warning in key: value format.", "items": { "type": "object", "properties": { "key": { "type": "string", - "description": "A key for the warning data." + "description": "[Output Only] A key for the warning data." }, "value": { "type": "string", - "description": "A warning data value corresponding to the key." + "description": "[Output Only] A warning data value corresponding to the key." } } } }, "message": { "type": "string", - "description": "Optional human-readable details for this warning." + "description": "[Output Only] Optional human-readable details for this warning." } } } @@ -2846,15 +2926,15 @@ "Project": { "id": "Project", "type": "object", - "description": "A project resource. Projects can be created only in the APIs Console. Unless marked otherwise, values can only be modified in the console.", + "description": "A Project resource. Projects can only be created in the Google Developers Console. Unless marked otherwise, values can only be modified in the console.", "properties": { "commonInstanceMetadata": { "$ref": "Metadata", - "description": "Metadata key/value pairs available to all instances contained in this project." + "description": "Metadata key/value pairs available to all instances contained in this project. See Custom metadata for more information." }, "creationTimestamp": { "type": "string", - "description": "Creation timestamp in RFC3339 text format (output only)." + "description": "[Output Only] Creation timestamp in RFC3339 text format." }, "description": { "type": "string", @@ -2862,12 +2942,12 @@ }, "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only).", + "description": "[Output Only] Unique identifier for the resource; defined by the server.", "format": "uint64" }, "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Only] Type of the resource. Always compute#project for projects.", "default": "compute#project" }, "name": { @@ -2876,14 +2956,14 @@ }, "quotas": { "type": "array", - "description": "Quotas assigned to this project.", + "description": "[Output Only] Quotas assigned to this project.", "items": { "$ref": "Quota" } }, "selfLink": { "type": "string", - "description": "Server defined URL for the resource (output only)." + "description": "[Output Only] Server defined URL for the resource." }, "usageExportLocation": { "$ref": "UsageExportLocation", @@ -2898,30 +2978,25 @@ "properties": { "limit": { "type": "number", - "description": "Quota limit for this metric.", + "description": "[Output Only] Quota limit for this metric.", "format": "double" }, "metric": { "type": "string", - "description": "Name of the quota metric.", + "description": "[Output Only] Name of the quota metric.", "enum": [ "BACKEND_SERVICES", "CPUS", - "DISKS", "DISKS_TOTAL_GB", - "EPHEMERAL_ADDRESSES", "FIREWALLS", "FORWARDING_RULES", "HEALTH_CHECKS", "IMAGES", - "IMAGES_TOTAL_GB", "INSTANCES", + "INSTANCE_TEMPLATES", "IN_USE_ADDRESSES", - "KERNELS", - "KERNELS_TOTAL_GB", "LOCAL_SSD_TOTAL_GB", "NETWORKS", - "OPERATIONS", "ROUTES", "SNAPSHOTS", "SSD_TOTAL_GB", @@ -2929,7 +3004,9 @@ "TARGET_HTTP_PROXIES", "TARGET_INSTANCES", "TARGET_POOLS", - "URL_MAPS" + "TARGET_VPN_GATEWAYS", + "URL_MAPS", + "VPN_TUNNELS" ], "enumDescriptions": [ "", @@ -2953,15 +3030,12 @@ "", "", "", - "", - "", - "", "" ] }, "usage": { "type": "number", - "description": "Current usage of this metric.", + "description": "[Output Only] Current usage of this metric.", "format": "double" } } @@ -2973,44 +3047,44 @@ "properties": { "creationTimestamp": { "type": "string", - "description": "Creation timestamp in RFC3339 text format (output only)." + "description": "[Output Only] Creation timestamp in RFC3339 text format." }, "deprecated": { "$ref": "DeprecationStatus", - "description": "The deprecation status associated with this region." + "description": "[Output Only] The deprecation status associated with this region." }, "description": { "type": "string", - "description": "Textual description of the resource." + "description": "[Output Only] Textual description of the resource." }, "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only).", + "description": "[Output Only] Unique identifier for the resource; defined by the server .", "format": "uint64" }, "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Only] Type of the resource. Always compute#region for regions.", "default": "compute#region" }, "name": { "type": "string", - "description": "Name of the resource." + "description": "[Output Only] Name of the resource." }, "quotas": { "type": "array", - "description": "Quotas assigned to this region.", + "description": "[Output Only] Quotas assigned to this region.", "items": { "$ref": "Quota" } }, "selfLink": { "type": "string", - "description": "Server defined URL for the resource (output only)." + "description": "[Output Only] Server defined URL for the resource." }, "status": { "type": "string", - "description": "Status of the region, \"UP\" or \"DOWN\".", + "description": "[Output Only] Status of the region, either UP or DOWN.", "enum": [ "DOWN", "UP" @@ -3022,7 +3096,7 @@ }, "zones": { "type": "array", - "description": "A list of zones homed in this region, in the form of resource URLs.", + "description": "[Output Only] A list of zones available in this region, in the form of resource URLs.", "items": { "type": "string" } @@ -3036,27 +3110,27 @@ "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "array", - "description": "The region resources.", + "description": "[Output Only] A list of Region resources.", "items": { "$ref": "Region" } }, "kind": { "type": "string", - "description": "Type of resource.", + "description": "[Output Only] Type of resource. Always compute#regionList for lists of regions.", "default": "compute#regionList" }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncated list request." }, "selfLink": { "type": "string", - "description": "Server defined URL for this resource (output only)." + "description": "[Output Only] Server defined URL for this resource." } } }, @@ -3137,9 +3211,13 @@ "type": "string", "description": "The URL of the local network if it should handle matching packets." }, + "nextHopVpnTunnel": { + "type": "string", + "description": "The URL to a VpnTunnel that should handle matching packets." + }, "priority": { "type": "integer", - "description": "Breaks ties between Routes of equal specificity. Routes with smaller values win when tied with routes with larger values.", + "description": "Breaks ties between Routes of equal specificity. Routes with smaller values win when tied with routes with larger values. Default value is 1000. A valid range is between 0 and 65535.", "format": "uint32", "annotations": { "required": [ @@ -3171,7 +3249,7 @@ "properties": { "code": { "type": "string", - "description": "The warning type identifier for this warning.", + "description": "[Output Only] The warning type identifier for this warning.", "enum": [ "DEPRECATED_RESOURCE_USED", "DISK_SIZE_LARGER_THAN_IMAGE_SIZE", @@ -3181,9 +3259,11 @@ "NEXT_HOP_INSTANCE_NOT_FOUND", "NEXT_HOP_INSTANCE_NOT_ON_NETWORK", "NEXT_HOP_NOT_RUNNING", + "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", "RESOURCE_NOT_DELETED", + "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" ], "enumDescriptions": [ @@ -3198,29 +3278,31 @@ "", "", "", + "", + "", "" ] }, "data": { "type": "array", - "description": "Metadata for this warning in 'key: value' format.", + "description": "[Output Only] Metadata for this warning in key: value format.", "items": { "type": "object", "properties": { "key": { "type": "string", - "description": "A key for the warning data." + "description": "[Output Only] A key for the warning data." }, "value": { "type": "string", - "description": "A warning data value corresponding to the key." + "description": "[Output Only] A warning data value corresponding to the key." } } } }, "message": { "type": "string", - "description": "Optional human-readable details for this warning." + "description": "[Output Only] Optional human-readable details for this warning." } } } @@ -3238,7 +3320,7 @@ }, "items": { "type": "array", - "description": "The route resources.", + "description": "A list of Route resources.", "items": { "$ref": "Route" } @@ -3261,15 +3343,15 @@ "Scheduling": { "id": "Scheduling", "type": "object", - "description": "Scheduling options for an Instance.", + "description": "Sets the scheduling options for an Instance.", "properties": { "automaticRestart": { "type": "boolean", - "description": "Whether the Instance should be automatically restarted whenever it is terminated by Compute Engine (not terminated by user)." + "description": "Specifies whether the instance should be automatically restarted if it is terminated by Compute Engine (not terminated by a user)." }, "onHostMaintenance": { "type": "string", - "description": "How the instance should behave when the host machine undergoes maintenance that may temporarily impact instance performance.", + "description": "Defines the maintenance behavior for this instance. The default behavior is MIGRATE. For more information, see Setting maintenance behavior.", "enum": [ "MIGRATE", "TERMINATE" @@ -3278,26 +3360,30 @@ "", "" ] + }, + "preemptible": { + "type": "boolean", + "description": "Whether the Instance is preemptible." } } }, "SerialPortOutput": { "id": "SerialPortOutput", "type": "object", - "description": "An instance serial console output.", + "description": "An instance's serial console output.", "properties": { "contents": { "type": "string", - "description": "The contents of the console output." + "description": "[Output Only] The contents of the console output." }, "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Only] Type of the resource. Always compute#serialPortOutput for serial port output.", "default": "compute#serialPortOutput" }, "selfLink": { "type": "string", - "description": "Server defined URL for the resource (output only)." + "description": "[Output Only] Server defined URL for the resource." } } }, @@ -3419,7 +3505,7 @@ }, "items": { "type": "array", - "description": "The persistent snapshot resources.", + "description": "A list of Snapshot resources.", "items": { "$ref": "Snapshot" } @@ -3446,7 +3532,7 @@ "properties": { "fingerprint": { "type": "string", - "description": "Fingerprint of this resource. A hash of the tags stored in this object. This field is used optimistic locking. An up-to-date tags fingerprint must be provided in order to modify tags.", + "description": "Specifies a fingerprint for this request, which is essentially a hash of the metadata's contents and used for optimistic locking. The fingerprint is initially generated by Compute Engine and changes after every request to modify or update metadata. You must always provide an up-to-date fingerprint hash in order to update or change metadata.\n\nTo see the latest fingerprint, make get() request to the instance.", "format": "byte" }, "items": { @@ -3507,7 +3593,7 @@ }, "items": { "type": "array", - "description": "The TargetHttpProxy resources.", + "description": "A list of TargetHttpProxy resources.", "items": { "$ref": "TargetHttpProxy" } @@ -3621,7 +3707,7 @@ }, "items": { "type": "array", - "description": "The TargetInstance resources.", + "description": "A list of TargetInstance resources.", "items": { "$ref": "TargetInstance" } @@ -3658,7 +3744,7 @@ "properties": { "code": { "type": "string", - "description": "The warning type identifier for this warning.", + "description": "[Output Only] The warning type identifier for this warning.", "enum": [ "DEPRECATED_RESOURCE_USED", "DISK_SIZE_LARGER_THAN_IMAGE_SIZE", @@ -3668,9 +3754,11 @@ "NEXT_HOP_INSTANCE_NOT_FOUND", "NEXT_HOP_INSTANCE_NOT_ON_NETWORK", "NEXT_HOP_NOT_RUNNING", + "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", "RESOURCE_NOT_DELETED", + "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" ], "enumDescriptions": [ @@ -3685,29 +3773,31 @@ "", "", "", + "", + "", "" ] }, "data": { "type": "array", - "description": "Metadata for this warning in 'key: value' format.", + "description": "[Output Only] Metadata for this warning in key: value format.", "items": { "type": "object", "properties": { "key": { "type": "string", - "description": "A key for the warning data." + "description": "[Output Only] A key for the warning data." }, "value": { "type": "string", - "description": "A warning data value corresponding to the key." + "description": "[Output Only] A warning data value corresponding to the key." } } } }, "message": { "type": "string", - "description": "Optional human-readable details for this warning." + "description": "[Output Only] Optional human-readable details for this warning." } } } @@ -3847,7 +3937,7 @@ }, "items": { "type": "array", - "description": "The TargetPool resources.", + "description": "A list of TargetPool resources.", "items": { "$ref": "TargetPool" } @@ -3936,7 +4026,7 @@ "properties": { "code": { "type": "string", - "description": "The warning type identifier for this warning.", + "description": "[Output Only] The warning type identifier for this warning.", "enum": [ "DEPRECATED_RESOURCE_USED", "DISK_SIZE_LARGER_THAN_IMAGE_SIZE", @@ -3946,9 +4036,11 @@ "NEXT_HOP_INSTANCE_NOT_FOUND", "NEXT_HOP_INSTANCE_NOT_ON_NETWORK", "NEXT_HOP_NOT_RUNNING", + "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", "RESOURCE_NOT_DELETED", + "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" ], "enumDescriptions": [ @@ -3963,29 +4055,31 @@ "", "", "", + "", + "", "" ] }, "data": { "type": "array", - "description": "Metadata for this warning in 'key: value' format.", + "description": "[Output Only] Metadata for this warning in key: value format.", "items": { "type": "object", "properties": { "key": { "type": "string", - "description": "A key for the warning data." + "description": "[Output Only] A key for the warning data." }, "value": { "type": "string", - "description": "A warning data value corresponding to the key." + "description": "[Output Only] A warning data value corresponding to the key." } } } }, "message": { "type": "string", - "description": "Optional human-readable details for this warning." + "description": "[Output Only] Optional human-readable details for this warning." } } } @@ -4000,6 +4094,225 @@ } } }, + "TargetVpnGateway": { + "id": "TargetVpnGateway", + "type": "object", + "properties": { + "creationTimestamp": { + "type": "string", + "description": "[Output Only] Creation timestamp in RFC3339 text format." + }, + "description": { + "type": "string", + "description": "An optional textual description of the resource. Provided by the client when the resource is created." + }, + "forwardingRules": { + "type": "array", + "description": "[Output Only] A list of URLs to the ForwardingRule resources. ForwardingRules are created using compute.forwardingRules.insert and associated to a VPN gateway.", + "items": { + "type": "string" + } + }, + "id": { + "type": "string", + "description": "[Output Only] Unique identifier for the resource. Defined by the server.", + "format": "uint64" + }, + "kind": { + "type": "string", + "description": "[Output Only] Type of resource. Always compute#targetVpnGateway for target VPN gateways.", + "default": "compute#targetVpnGateway" + }, + "name": { + "type": "string", + "description": "Name of the resource. Provided by the client when the resource is created. The name must be 1-63 characters long and comply with RFC1035.", + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "annotations": { + "required": [ + "compute.targetVpnGateways.insert" + ] + } + }, + "network": { + "type": "string", + "description": "URL of the network to which this VPN gateway is attached. Provided by the client when the VPN gateway is created.", + "annotations": { + "required": [ + "compute.targetVpnGateways.insert" + ] + } + }, + "region": { + "type": "string", + "description": "[Output Only] URL of the region where the target VPN gateway resides." + }, + "selfLink": { + "type": "string", + "description": "[Output Only] Server-defined URL for the resource." + }, + "status": { + "type": "string", + "description": "[Output Only] The status of the VPN gateway.", + "enum": [ + "CREATING", + "DELETING", + "FAILED", + "READY" + ], + "enumDescriptions": [ + "", + "", + "", + "" + ] + }, + "tunnels": { + "type": "array", + "description": "[Output Only] A list of URLs to VpnTunnel resources. VpnTunnels are created using compute.vpntunnels.insert and associated to a VPN gateway.", + "items": { + "type": "string" + } + } + } + }, + "TargetVpnGatewayAggregatedList": { + "id": "TargetVpnGatewayAggregatedList", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "[Output Only] Unique identifier for the resource. Defined by the server." + }, + "items": { + "type": "object", + "description": "A map of scoped target vpn gateway lists.", + "additionalProperties": { + "$ref": "TargetVpnGatewaysScopedList", + "description": "[Output Only] Name of the scope containing this set of target vpn gateways." + } + }, + "kind": { + "type": "string", + "description": "[Output Only] Type of resource. Always compute#targetVpnGateway for target VPN gateways.", + "default": "compute#targetVpnGatewayAggregatedList" + }, + "nextPageToken": { + "type": "string", + "description": "[Output Only] A token used to continue a truncated list request." + }, + "selfLink": { + "type": "string", + "description": "[Output Only] Server-defined URL for the resource." + } + } + }, + "TargetVpnGatewayList": { + "id": "TargetVpnGatewayList", + "type": "object", + "description": "Contains a list of TargetVpnGateway resources.", + "properties": { + "id": { + "type": "string", + "description": "[Output Only] Unique identifier for the resource. Defined by the server." + }, + "items": { + "type": "array", + "description": "[Output Only] A list of TargetVpnGateway resources.", + "items": { + "$ref": "TargetVpnGateway" + } + }, + "kind": { + "type": "string", + "description": "[Output Only] Type of resource. Always compute#targetVpnGateway for target VPN gateways.", + "default": "compute#targetVpnGatewayList" + }, + "nextPageToken": { + "type": "string", + "description": "[Output Only] A token used to continue a truncated list request." + }, + "selfLink": { + "type": "string", + "description": "[Output Only] Server-defined URL for the resource." + } + } + }, + "TargetVpnGatewaysScopedList": { + "id": "TargetVpnGatewaysScopedList", + "type": "object", + "properties": { + "targetVpnGateways": { + "type": "array", + "description": "[Output Only] List of target vpn gateways contained in this scope.", + "items": { + "$ref": "TargetVpnGateway" + } + }, + "warning": { + "type": "object", + "description": "[Output Only] Informational warning which replaces the list of addresses when the list is empty.", + "properties": { + "code": { + "type": "string", + "description": "[Output Only] The warning type identifier for this warning.", + "enum": [ + "DEPRECATED_RESOURCE_USED", + "DISK_SIZE_LARGER_THAN_IMAGE_SIZE", + "INJECTED_KERNELS_DEPRECATED", + "NEXT_HOP_ADDRESS_NOT_ASSIGNED", + "NEXT_HOP_CANNOT_IP_FORWARD", + "NEXT_HOP_INSTANCE_NOT_FOUND", + "NEXT_HOP_INSTANCE_NOT_ON_NETWORK", + "NEXT_HOP_NOT_RUNNING", + "NOT_CRITICAL_ERROR", + "NO_RESULTS_ON_PAGE", + "REQUIRED_TOS_AGREEMENT", + "RESOURCE_NOT_DELETED", + "SINGLE_INSTANCE_PROPERTY_TEMPLATE", + "UNREACHABLE" + ], + "enumDescriptions": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "data": { + "type": "array", + "description": "[Output Only] Metadata for this warning in key: value format.", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "[Output Only] A key for the warning data." + }, + "value": { + "type": "string", + "description": "[Output Only] A warning data value corresponding to the key." + } + } + } + }, + "message": { + "type": "string", + "description": "[Output Only] Optional human-readable details for this warning." + } + } + } + } + }, "TestFailure": { "id": "TestFailure", "type": "object", @@ -4093,7 +4406,7 @@ }, "items": { "type": "array", - "description": "The UrlMap resources.", + "description": "A list of UrlMap resources.", "items": { "$ref": "UrlMap" } @@ -4198,81 +4511,328 @@ "properties": { "bucketName": { "type": "string", - "description": "The name of an existing bucket in Cloud Storage where the usage report object is stored. The Google Service Account is granted write access to this bucket. This is simply the bucket name, with no \"gs://\" or \"https://storage.googleapis.com/\" in front of it." + "description": "The name of an existing bucket in Cloud Storage where the usage report object is stored. The Google Service Account is granted write access to this bucket. This is just the bucket name, with no gs:// or https://storage.googleapis.com/ in front of it." }, "reportNamePrefix": { "type": "string", - "description": "An optional prefix for the name of the usage report object stored in bucket_name. If not supplied, defaults to \"usage_\". The report is stored as a CSV file named _gce_.csv. where is the day of the usage according to Pacific Time. The prefix should conform to Cloud Storage object naming conventions." + "description": "An optional prefix for the name of the usage report object stored in bucketName. If not supplied, defaults to usage. The report is stored as a CSV file named report_name_prefix_gce_YYYYMMDD.csv where YYYYMMDD is the day of the usage according to Pacific Time. If you supply a prefix, it should conform to Cloud Storage object naming conventions." + } + } + }, + "VpnTunnel": { + "id": "VpnTunnel", + "type": "object", + "properties": { + "creationTimestamp": { + "type": "string", + "description": "[Output Only] Creation timestamp in RFC3339 text format." + }, + "description": { + "type": "string", + "description": "An optional textual description of the resource. Provided by the client when the resource is created." + }, + "detailedStatus": { + "type": "string", + "description": "[Output Only] Detailed status message for the VPN tunnel." + }, + "id": { + "type": "string", + "description": "[Output Only] Unique identifier for the resource. Defined by the server.", + "format": "uint64" + }, + "ikeNetworks": { + "type": "array", + "description": "IKE networks to use when establishing the VPN tunnel with peer VPN gateway. The value should be a CIDR formatted string, for example: 192.168.0.0/16. The ranges should be disjoint.", + "items": { + "type": "string" + } + }, + "ikeVersion": { + "type": "integer", + "description": "IKE protocol version to use when establishing the VPN tunnel with peer VPN gateway. Acceptable IKE versions are 1 or 2. Default version is 2.", + "format": "int32" + }, + "kind": { + "type": "string", + "description": "[Output Only] Type of resource. Always compute#vpnTunnel for VPN tunnels.", + "default": "compute#vpnTunnel" + }, + "name": { + "type": "string", + "description": "Name of the resource. Provided by the client when the resource is created. The name must be 1-63 characters long and comply with RFC1035.", + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "annotations": { + "required": [ + "compute.vpnTunnels.insert" + ] + } + }, + "peerIp": { + "type": "string", + "description": "IP address of the peer VPN gateway." + }, + "region": { + "type": "string", + "description": "[Output Only] URL of the region where the VPN tunnel resides." + }, + "selfLink": { + "type": "string", + "description": "[Output Only] Server defined URL for the resource." + }, + "sharedSecret": { + "type": "string", + "description": "Shared secret used to set the secure session between the GCE VPN gateway and the peer VPN gateway." + }, + "sharedSecretHash": { + "type": "string", + "description": "Hash of the shared secret." + }, + "status": { + "type": "string", + "description": "[Output Only] The status of the VPN tunnel.", + "enum": [ + "AUTHORIZATION_ERROR", + "DEPROVISIONING", + "ESTABLISHED", + "FAILED", + "FIRST_HANDSHAKE", + "NEGOTIATION_FAILURE", + "NETWORK_ERROR", + "NO_INCOMING_PACKETS", + "PROVISIONING", + "REJECTED", + "WAITING_FOR_FULL_CONFIG" + ], + "enumDescriptions": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "targetVpnGateway": { + "type": "string", + "description": "URL of the VPN gateway to which this VPN tunnel is associated. Provided by the client when the VPN tunnel is created.", + "annotations": { + "required": [ + "compute.vpnTunnels.insert" + ] + } + } + } + }, + "VpnTunnelAggregatedList": { + "id": "VpnTunnelAggregatedList", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "[Output Only] Unique identifier for the resource; defined by the server." + }, + "items": { + "type": "object", + "description": "[Output Only] A map of scoped vpn tunnel lists.", + "additionalProperties": { + "$ref": "VpnTunnelsScopedList", + "description": "Name of the scope containing this set of vpn tunnels." + } + }, + "kind": { + "type": "string", + "description": "[Output Only] Type of resource. Always compute#vpnTunnel for VPN tunnels.", + "default": "compute#vpnTunnelAggregatedList" + }, + "nextPageToken": { + "type": "string", + "description": "[Output Only] A token used to continue a truncated list request." + }, + "selfLink": { + "type": "string", + "description": "[Output Only] Server defined URL for this resource." + } + } + }, + "VpnTunnelList": { + "id": "VpnTunnelList", + "type": "object", + "description": "Contains a list of VpnTunnel resources.", + "properties": { + "id": { + "type": "string", + "description": "[Output Only] Unique identifier for the resource; defined by the server." + }, + "items": { + "type": "array", + "description": "[Output Only] A list of VpnTunnel resources.", + "items": { + "$ref": "VpnTunnel" + } + }, + "kind": { + "type": "string", + "description": "[Output Only] Type of resource. Always compute#vpnTunnel for VPN tunnels.", + "default": "compute#vpnTunnelList" + }, + "nextPageToken": { + "type": "string", + "description": "[Output Only] A token used to continue a truncated list request." + }, + "selfLink": { + "type": "string", + "description": "[Output Only] Server-defined URL for the resource." + } + } + }, + "VpnTunnelsScopedList": { + "id": "VpnTunnelsScopedList", + "type": "object", + "properties": { + "vpnTunnels": { + "type": "array", + "description": "List of vpn tunnels contained in this scope.", + "items": { + "$ref": "VpnTunnel" + } + }, + "warning": { + "type": "object", + "description": "Informational warning which replaces the list of addresses when the list is empty.", + "properties": { + "code": { + "type": "string", + "description": "[Output Only] The warning type identifier for this warning.", + "enum": [ + "DEPRECATED_RESOURCE_USED", + "DISK_SIZE_LARGER_THAN_IMAGE_SIZE", + "INJECTED_KERNELS_DEPRECATED", + "NEXT_HOP_ADDRESS_NOT_ASSIGNED", + "NEXT_HOP_CANNOT_IP_FORWARD", + "NEXT_HOP_INSTANCE_NOT_FOUND", + "NEXT_HOP_INSTANCE_NOT_ON_NETWORK", + "NEXT_HOP_NOT_RUNNING", + "NOT_CRITICAL_ERROR", + "NO_RESULTS_ON_PAGE", + "REQUIRED_TOS_AGREEMENT", + "RESOURCE_NOT_DELETED", + "SINGLE_INSTANCE_PROPERTY_TEMPLATE", + "UNREACHABLE" + ], + "enumDescriptions": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "data": { + "type": "array", + "description": "[Output Only] Metadata for this warning in key: value format.", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "[Output Only] A key for the warning data." + }, + "value": { + "type": "string", + "description": "[Output Only] A warning data value corresponding to the key." + } + } + } + }, + "message": { + "type": "string", + "description": "[Output Only] Optional human-readable details for this warning." + } + } } } }, "Zone": { "id": "Zone", "type": "object", - "description": "A zone resource.", + "description": "A Zone resource.", "properties": { "creationTimestamp": { "type": "string", - "description": "Creation timestamp in RFC3339 text format (output only)." + "description": "[Output Only] Creation timestamp in RFC3339 text format." }, "deprecated": { "$ref": "DeprecationStatus", - "description": "The deprecation status associated with this zone." + "description": "[Output Only] The deprecation status associated with this zone." }, "description": { "type": "string", - "description": "Textual description of the resource." + "description": "[Output Only] Textual description of the resource." }, "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only).", + "description": "[Output Only] Unique identifier for the resource; defined by the server.", "format": "uint64" }, "kind": { "type": "string", - "description": "Type of the resource.", + "description": "[Output Only] Type of the resource. Always kind#zone for zones.", "default": "compute#zone" }, "maintenanceWindows": { "type": "array", - "description": "Scheduled maintenance windows for the zone. When the zone is in a maintenance window, all resources which reside in the zone will be unavailable.", + "description": "[Output Only] Any scheduled maintenance windows for this zone. When the zone is in a maintenance window, all resources which reside in the zone will be unavailable. For more information, see Maintenance Windows", "items": { "type": "object", "properties": { "beginTime": { "type": "string", - "description": "Begin time of the maintenance window, in RFC 3339 format." + "description": "[Output Only] Starting time of the maintenance window, in RFC3339 format." }, "description": { "type": "string", - "description": "Textual description of the maintenance window." + "description": "[Output Only] Textual description of the maintenance window." }, "endTime": { "type": "string", - "description": "End time of the maintenance window, in RFC 3339 format." + "description": "[Output Only] Ending time of the maintenance window, in RFC3339 format." }, "name": { "type": "string", - "description": "Name of the maintenance window." + "description": "[Output Only] Name of the maintenance window." } } } }, "name": { "type": "string", - "description": "Name of the resource." + "description": "[Output Only] Name of the resource." }, "region": { "type": "string", - "description": "Full URL reference to the region which hosts the zone (output only)." + "description": "[Output Only] Full URL reference to the region which hosts the zone." }, "selfLink": { "type": "string", - "description": "Server defined URL for the resource (output only)." + "description": "[Output Only] Server defined URL for the resource." }, "status": { "type": "string", - "description": "Status of the zone. \"UP\" or \"DOWN\".", + "description": "[Output Only] Status of the zone, either UP or DOWN.", "enum": [ "DOWN", "UP" @@ -4291,11 +4851,11 @@ "properties": { "id": { "type": "string", - "description": "Unique identifier for the resource; defined by the server (output only)." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "array", - "description": "The zone resources.", + "description": "[Output Only] A list of Zone resources.", "items": { "$ref": "Zone" } @@ -4307,7 +4867,7 @@ }, "nextPageToken": { "type": "string", - "description": "A token used to continue a truncated list request (output only)." + "description": "[Output Only] A token used to continue a truncated list request." }, "selfLink": { "type": "string", @@ -4327,12 +4887,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -4341,12 +4901,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -4359,6 +4919,7 @@ "$ref": "AddressAggregatedList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -4378,14 +4939,14 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "region": { "type": "string", - "description": "Name of the region scoping this request.", + "description": "The name of the region for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -4400,6 +4961,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -4418,14 +4980,14 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "region": { "type": "string", - "description": "Name of the region scoping this request.", + "description": "The name of the region for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -4440,6 +5002,7 @@ "$ref": "Address" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -4452,14 +5015,14 @@ "parameters": { "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "region": { "type": "string", - "description": "Name of the region scoping this request.", + "description": "The name of the region for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -4476,6 +5039,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -4487,12 +5051,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -4501,19 +5065,19 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "region": { "type": "string", - "description": "Name of the region scoping this request.", + "description": "The name of the region for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -4527,6 +5091,7 @@ "$ref": "AddressList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -4564,6 +5129,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -4596,6 +5162,7 @@ "$ref": "BackendService" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -4631,6 +5198,7 @@ "$ref": "BackendServiceGroupHealth" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -4659,6 +5227,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -4670,12 +5239,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -4684,7 +5253,7 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { @@ -4702,6 +5271,7 @@ "$ref": "BackendServiceList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -4738,6 +5308,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -4773,6 +5344,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] } @@ -4788,12 +5360,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -4802,12 +5374,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -4820,6 +5392,7 @@ "$ref": "DiskTypeAggregatedList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -4839,14 +5412,14 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -4861,6 +5434,7 @@ "$ref": "DiskType" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -4873,12 +5447,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -4887,19 +5461,19 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -4913,6 +5487,7 @@ "$ref": "DiskTypeList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -4929,12 +5504,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -4943,12 +5518,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -4961,6 +5536,7 @@ "$ref": "DiskAggregatedList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -4969,24 +5545,25 @@ "id": "compute.disks.createSnapshot", "path": "{project}/zones/{zone}/disks/{disk}/createSnapshot", "httpMethod": "POST", + "description": "Creates a snapshot of this disk.", "parameters": { "disk": { "type": "string", - "description": "Name of the persistent disk resource to snapshot.", + "description": "Name of the persistent disk to snapshot.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -5004,6 +5581,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -5011,25 +5589,25 @@ "id": "compute.disks.delete", "path": "{project}/zones/{zone}/disks/{disk}", "httpMethod": "DELETE", - "description": "Deletes the specified persistent disk resource.", + "description": "Deletes the specified persistent disk.", "parameters": { "disk": { "type": "string", - "description": "Name of the persistent disk resource to delete.", + "description": "Name of the persistent disk to delete.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -5044,6 +5622,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -5051,25 +5630,25 @@ "id": "compute.disks.get", "path": "{project}/zones/{zone}/disks/{disk}", "httpMethod": "GET", - "description": "Returns the specified persistent disk resource.", + "description": "Returns a specified persistent disk.", "parameters": { "disk": { "type": "string", - "description": "Name of the persistent disk resource to return.", + "description": "Name of the persistent disk to return.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -5084,6 +5663,7 @@ "$ref": "Disk" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -5092,11 +5672,11 @@ "id": "compute.disks.insert", "path": "{project}/zones/{zone}/disks", "httpMethod": "POST", - "description": "Creates a persistent disk resource in the specified project using the data included in the request.", + "description": "Creates a persistent disk in the specified project using the data included in the request.", "parameters": { "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -5108,7 +5688,7 @@ }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -5125,6 +5705,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -5132,16 +5713,16 @@ "id": "compute.disks.list", "path": "{project}/zones/{zone}/disks", "httpMethod": "GET", - "description": "Retrieves the list of persistent disk resources contained within the specified zone.", + "description": "Retrieves the list of persistent disks contained within the specified zone.", "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -5150,19 +5731,19 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -5176,6 +5757,7 @@ "$ref": "DiskList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -5199,7 +5781,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -5213,6 +5795,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -5231,7 +5814,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -5245,6 +5828,7 @@ "$ref": "Firewall" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -5257,7 +5841,7 @@ "parameters": { "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -5273,6 +5857,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -5284,12 +5869,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -5298,12 +5883,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -5316,6 +5901,7 @@ "$ref": "FirewallList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -5335,7 +5921,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -5352,6 +5938,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -5370,7 +5957,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -5387,6 +5974,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] } @@ -5402,12 +5990,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -5416,7 +6004,7 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { @@ -5434,6 +6022,7 @@ "$ref": "ForwardingRuleAggregatedList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -5475,6 +6064,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -5515,6 +6105,7 @@ "$ref": "ForwardingRule" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -5551,6 +6142,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -5562,12 +6154,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -5576,7 +6168,7 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { @@ -5602,6 +6194,7 @@ "$ref": "ForwardingRuleList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -5646,6 +6239,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] } @@ -5668,7 +6262,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -5682,6 +6276,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -5700,7 +6295,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -5714,6 +6309,7 @@ "$ref": "Address" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -5726,7 +6322,7 @@ "parameters": { "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -5742,6 +6338,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -5753,12 +6350,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -5767,12 +6364,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -5785,6 +6382,7 @@ "$ref": "AddressList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -5822,6 +6420,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -5854,6 +6453,7 @@ "$ref": "ForwardingRule" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -5882,6 +6482,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -5893,12 +6494,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -5907,7 +6508,7 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { @@ -5925,6 +6526,7 @@ "$ref": "ForwardingRuleList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -5961,6 +6563,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] } @@ -5976,12 +6579,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -5990,12 +6593,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -6008,6 +6611,7 @@ "$ref": "OperationAggregatedList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -6027,7 +6631,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -6038,6 +6642,7 @@ "operation" ], "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -6056,7 +6661,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -6070,6 +6675,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -6082,12 +6688,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -6096,12 +6702,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -6114,6 +6720,7 @@ "$ref": "OperationList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -6151,6 +6758,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -6183,6 +6791,7 @@ "$ref": "HttpHealthCheck" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -6211,6 +6820,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -6222,12 +6832,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -6236,7 +6846,7 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { @@ -6254,6 +6864,7 @@ "$ref": "HttpHealthCheckList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -6290,6 +6901,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -6325,6 +6937,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] } @@ -6347,7 +6960,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -6361,6 +6974,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -6368,7 +6982,7 @@ "id": "compute.images.deprecate", "path": "{project}/global/images/{image}/deprecate", "httpMethod": "POST", - "description": "Sets the deprecation status of an image. If no message body is given, clears the deprecation status instead.", + "description": "Sets the deprecation status of an image.\n\nIf an empty request body is given, clears the deprecation status instead.", "parameters": { "image": { "type": "string", @@ -6379,7 +6993,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -6396,6 +7010,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -6414,7 +7029,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -6428,6 +7043,7 @@ "$ref": "Image" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -6440,7 +7056,7 @@ "parameters": { "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -6456,6 +7072,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control", "https://www.googleapis.com/auth/devstorage.read_only", @@ -6470,12 +7087,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -6484,12 +7101,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -6502,6 +7119,7 @@ "$ref": "ImageList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -6514,18 +7132,18 @@ "id": "compute.instanceTemplates.delete", "path": "{project}/global/instanceTemplates/{instanceTemplate}", "httpMethod": "DELETE", - "description": "Deletes the specified instance template resource.", + "description": "Deletes the specified instance template.", "parameters": { "instanceTemplate": { "type": "string", - "description": "Name of the instance template resource to delete.", + "description": "The name of the instance template to delete.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "The project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -6539,6 +7157,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -6550,14 +7169,14 @@ "parameters": { "instanceTemplate": { "type": "string", - "description": "Name of the instance template resource to return.", + "description": "The name of the instance template.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "The project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -6571,6 +7190,7 @@ "$ref": "InstanceTemplate" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -6579,11 +7199,11 @@ "id": "compute.instanceTemplates.insert", "path": "{project}/global/instanceTemplates", "httpMethod": "POST", - "description": "Creates an instance template resource in the specified project using the data included in the request.", + "description": "Creates an instance template in the specified project using the data that is included in the request.", "parameters": { "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "The project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -6599,6 +7219,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -6606,16 +7227,16 @@ "id": "compute.instanceTemplates.list", "path": "{project}/global/instanceTemplates", "httpMethod": "GET", - "description": "Retrieves the list of instance template resources contained within the specified project.", + "description": "Retrieves a list of instance templates that are contained within the specified project and zone.", "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -6624,12 +7245,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "The project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -6642,6 +7263,7 @@ "$ref": "InstanceTemplateList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -6658,27 +7280,27 @@ "parameters": { "instance": { "type": "string", - "description": "Instance name.", + "description": "The instance name for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" }, "networkInterface": { "type": "string", - "description": "Network interface name.", + "description": "The name of the network interface to add to this instance.", "required": true, "location": "query" }, "project": { "type": "string", - "description": "Project name.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -6697,6 +7319,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -6707,12 +7330,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -6721,12 +7344,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -6739,6 +7362,7 @@ "$ref": "InstanceAggregatedList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -6747,7 +7371,7 @@ "id": "compute.instances.attachDisk", "path": "{project}/zones/{zone}/instances/{instance}/attachDisk", "httpMethod": "POST", - "description": "Attaches a disk resource to an instance.", + "description": "Attaches a Disk resource to an instance.", "parameters": { "instance": { "type": "string", @@ -6758,14 +7382,14 @@ }, "project": { "type": "string", - "description": "Project name.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -6783,6 +7407,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -6790,7 +7415,7 @@ "id": "compute.instances.delete", "path": "{project}/zones/{zone}/instances/{instance}", "httpMethod": "DELETE", - "description": "Deletes the specified instance resource.", + "description": "Deletes the specified Instance resource. For more information, see Shutting down an instance.", "parameters": { "instance": { "type": "string", @@ -6801,14 +7426,14 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -6823,6 +7448,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -6834,33 +7460,33 @@ "parameters": { "accessConfig": { "type": "string", - "description": "Access config name.", + "description": "The name of the access config to delete.", "required": true, "location": "query" }, "instance": { "type": "string", - "description": "Instance name.", + "description": "The instance name for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" }, "networkInterface": { "type": "string", - "description": "Network interface name.", + "description": "The name of the network interface.", "required": true, "location": "query" }, "project": { "type": "string", - "description": "Project name.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -6877,6 +7503,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -6902,14 +7529,14 @@ }, "project": { "type": "string", - "description": "Project name.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -6925,6 +7552,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -6943,14 +7571,14 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the The name of the zone for this request..", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -6965,6 +7593,7 @@ "$ref": "Instance" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -6982,16 +7611,25 @@ "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" }, + "port": { + "type": "integer", + "description": "Which COM port to retrieve data from.", + "default": "1", + "format": "int32", + "minimum": "1", + "maximum": "4", + "location": "query" + }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -7006,6 +7644,7 @@ "$ref": "SerialPortOutput" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -7018,14 +7657,14 @@ "parameters": { "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -7042,6 +7681,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -7053,12 +7693,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -7067,19 +7707,19 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -7093,6 +7733,7 @@ "$ref": "InstanceList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -7112,14 +7753,14 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -7134,6 +7775,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -7141,7 +7783,7 @@ "id": "compute.instances.setDiskAutoDelete", "path": "{project}/zones/{zone}/instances/{instance}/setDiskAutoDelete", "httpMethod": "POST", - "description": "Sets the auto-delete flag for a disk attached to an instance", + "description": "Sets the auto-delete flag for a disk attached to an instance.", "parameters": { "autoDelete": { "type": "boolean", @@ -7151,28 +7793,28 @@ }, "deviceName": { "type": "string", - "description": "Disk device name to modify.", + "description": "The device name of the disk to modify.", "required": true, "pattern": "\\w[\\w.-]{0,254}", "location": "query" }, "instance": { "type": "string", - "description": "Instance name.", + "description": "The instance name.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" }, "project": { "type": "string", - "description": "Project name.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -7189,6 +7831,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -7207,14 +7850,14 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -7232,6 +7875,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -7250,14 +7894,14 @@ }, "project": { "type": "string", - "description": "Project name.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -7275,6 +7919,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -7293,14 +7938,14 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -7318,6 +7963,89 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute" + ] + }, + "start": { + "id": "compute.instances.start", + "path": "{project}/zones/{zone}/instances/{instance}/start", + "httpMethod": "POST", + "description": "This method starts an instance that was stopped using the using the instances().stop method. For more information, see Restart an instance.", + "parameters": { + "instance": { + "type": "string", + "description": "Name of the instance resource to start.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + }, + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + }, + "zone": { + "type": "string", + "description": "The name of the zone for this request.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "zone", + "instance" + ], + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute" + ] + }, + "stop": { + "id": "compute.instances.stop", + "path": "{project}/zones/{zone}/instances/{instance}/stop", + "httpMethod": "POST", + "description": "This method stops a running instance, shutting it down cleanly, and allows you to restart the instance at a later time. Stopped instances do not incur per-minute, virtual machine usage charges while they are stopped, but any resources that the virtual machine is using, such as persistent disks and static IP addresses,will continue to be charged until they are deleted. For more information, see Stopping an instance.", + "parameters": { + "instance": { + "type": "string", + "description": "Name of the instance resource to stop.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + }, + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + }, + "zone": { + "type": "string", + "description": "The name of the zone for this request.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "zone", + "instance" + ], + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] } @@ -7340,7 +8068,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -7354,6 +8082,7 @@ "$ref": "License" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -7370,12 +8099,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -7384,12 +8113,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -7402,6 +8131,7 @@ "$ref": "MachineTypeAggregatedList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -7421,14 +8151,14 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -7443,6 +8173,7 @@ "$ref": "MachineType" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -7455,12 +8186,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -7469,19 +8200,19 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" }, "zone": { "type": "string", - "description": "Name of the zone scoping this request.", + "description": "The name of the zone for this request.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -7495,6 +8226,7 @@ "$ref": "MachineTypeList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -7518,7 +8250,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -7532,6 +8264,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -7550,7 +8283,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -7564,6 +8297,7 @@ "$ref": "Network" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -7576,7 +8310,7 @@ "parameters": { "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -7592,6 +8326,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -7603,12 +8338,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -7617,12 +8352,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -7635,6 +8370,7 @@ "$ref": "NetworkList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -7651,7 +8387,7 @@ "parameters": { "project": { "type": "string", - "description": "Name of the project resource to retrieve.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -7664,10 +8400,67 @@ "$ref": "Project" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] }, + "moveDisk": { + "id": "compute.projects.moveDisk", + "path": "{project}/moveDisk", + "httpMethod": "POST", + "description": "Moves a persistent disk from one zone to another.", + "parameters": { + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + } + }, + "parameterOrder": [ + "project" + ], + "request": { + "$ref": "DiskMoveRequest" + }, + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute" + ] + }, + "moveInstance": { + "id": "compute.projects.moveInstance", + "path": "{project}/moveInstance", + "httpMethod": "POST", + "description": "Moves an instance and its attached persistent disks from one zone to another.", + "parameters": { + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + } + }, + "parameterOrder": [ + "project" + ], + "request": { + "$ref": "InstanceMoveRequest" + }, + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute" + ] + }, "setCommonInstanceMetadata": { "id": "compute.projects.setCommonInstanceMetadata", "path": "{project}/setCommonInstanceMetadata", @@ -7676,7 +8469,7 @@ "parameters": { "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -7692,6 +8485,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -7699,11 +8493,11 @@ "id": "compute.projects.setUsageExportBucket", "path": "{project}/setUsageExportBucket", "httpMethod": "POST", - "description": "Sets usage export location", + "description": "Enables the usage export feature and sets the usage export bucket where reports are stored. If you provide an empty request body using this method, the usage export feature will be disabled.", "parameters": { "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -7719,6 +8513,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control", "https://www.googleapis.com/auth/devstorage.read_only", @@ -7744,7 +8539,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -7763,6 +8558,7 @@ "operation" ], "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -7781,7 +8577,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -7803,6 +8599,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -7815,12 +8612,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -7829,12 +8626,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -7855,6 +8652,7 @@ "$ref": "OperationList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -7871,7 +8669,7 @@ "parameters": { "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -7892,6 +8690,7 @@ "$ref": "Region" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -7904,12 +8703,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -7918,12 +8717,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -7936,6 +8735,7 @@ "$ref": "RegionList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -7973,6 +8773,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -8005,6 +8806,7 @@ "$ref": "Route" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -8033,6 +8835,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -8044,12 +8847,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -8058,7 +8861,7 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { @@ -8076,6 +8879,7 @@ "$ref": "RouteList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -8113,6 +8917,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -8145,6 +8950,7 @@ "$ref": "Snapshot" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -8157,12 +8963,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -8171,7 +8977,7 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { @@ -8189,6 +8995,7 @@ "$ref": "SnapshotList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -8226,6 +9033,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -8258,6 +9066,7 @@ "$ref": "TargetHttpProxy" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -8286,6 +9095,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -8297,12 +9107,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -8311,7 +9121,7 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { @@ -8329,6 +9139,7 @@ "$ref": "TargetHttpProxyList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -8365,6 +9176,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] } @@ -8380,12 +9192,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -8394,7 +9206,7 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { @@ -8412,6 +9224,7 @@ "$ref": "TargetInstanceAggregatedList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -8453,6 +9266,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -8493,6 +9307,7 @@ "$ref": "TargetInstance" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -8529,6 +9344,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -8540,12 +9356,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -8554,7 +9370,7 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { @@ -8580,6 +9396,7 @@ "$ref": "TargetInstanceList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -8627,6 +9444,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -8669,6 +9487,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -8680,12 +9499,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -8694,7 +9513,7 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { @@ -8712,6 +9531,7 @@ "$ref": "TargetPoolAggregatedList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -8753,6 +9573,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -8793,6 +9614,7 @@ "$ref": "TargetPool" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -8836,6 +9658,7 @@ "$ref": "TargetPoolInstanceHealth" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -8872,6 +9695,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -8883,12 +9707,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -8897,7 +9721,7 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { @@ -8923,6 +9747,7 @@ "$ref": "TargetPoolList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -8966,6 +9791,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -9008,6 +9834,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -9057,11 +9884,233 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] } } }, + "targetVpnGateways": { + "methods": { + "aggregatedList": { + "id": "compute.targetVpnGateways.aggregatedList", + "path": "{project}/aggregated/targetVpnGateways", + "httpMethod": "GET", + "description": "Retrieves the list of target VPN gateways grouped by scope.", + "parameters": { + "filter": { + "type": "string", + "description": "Filter expression for filtering listed resources.", + "location": "query" + }, + "maxResults": { + "type": "integer", + "description": "Maximum count of results to be returned.", + "default": "500", + "format": "uint32", + "minimum": "0", + "maximum": "500", + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", + "location": "query" + }, + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + } + }, + "parameterOrder": [ + "project" + ], + "response": { + "$ref": "TargetVpnGatewayAggregatedList" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" + ] + }, + "delete": { + "id": "compute.targetVpnGateways.delete", + "path": "{project}/regions/{region}/targetVpnGateways/{targetVpnGateway}", + "httpMethod": "DELETE", + "description": "Deletes the specified TargetVpnGateway resource.", + "parameters": { + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + }, + "region": { + "type": "string", + "description": "The name of the region for this request.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + }, + "targetVpnGateway": { + "type": "string", + "description": "Name of the TargetVpnGateway resource to delete.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "region", + "targetVpnGateway" + ], + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute" + ] + }, + "get": { + "id": "compute.targetVpnGateways.get", + "path": "{project}/regions/{region}/targetVpnGateways/{targetVpnGateway}", + "httpMethod": "GET", + "description": "Returns the specified TargetVpnGateway resource.", + "parameters": { + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + }, + "region": { + "type": "string", + "description": "The name of the region for this request.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + }, + "targetVpnGateway": { + "type": "string", + "description": "Name of the TargetVpnGateway resource to return.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "region", + "targetVpnGateway" + ], + "response": { + "$ref": "TargetVpnGateway" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" + ] + }, + "insert": { + "id": "compute.targetVpnGateways.insert", + "path": "{project}/regions/{region}/targetVpnGateways", + "httpMethod": "POST", + "description": "Creates a TargetVpnGateway resource in the specified project and region using the data included in the request.", + "parameters": { + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + }, + "region": { + "type": "string", + "description": "The name of the region for this request.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "region" + ], + "request": { + "$ref": "TargetVpnGateway" + }, + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute" + ] + }, + "list": { + "id": "compute.targetVpnGateways.list", + "path": "{project}/regions/{region}/targetVpnGateways", + "httpMethod": "GET", + "description": "Retrieves the list of TargetVpnGateway resources available to the specified project and region.", + "parameters": { + "filter": { + "type": "string", + "description": "Filter expression for filtering listed resources.", + "location": "query" + }, + "maxResults": { + "type": "integer", + "description": "Maximum count of results to be returned.", + "default": "500", + "format": "uint32", + "minimum": "0", + "maximum": "500", + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", + "location": "query" + }, + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + }, + "region": { + "type": "string", + "description": "The name of the region for this request.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "region" + ], + "response": { + "$ref": "TargetVpnGatewayList" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" + ] + } + } + }, "urlMaps": { "methods": { "delete": { @@ -9093,6 +10142,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -9125,6 +10175,7 @@ "$ref": "UrlMap" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -9153,6 +10204,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -9164,12 +10216,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -9178,7 +10230,7 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { @@ -9196,6 +10248,7 @@ "$ref": "UrlMapList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -9232,6 +10285,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -9267,6 +10321,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -9302,11 +10357,233 @@ "$ref": "UrlMapsValidateResponse" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] } } }, + "vpnTunnels": { + "methods": { + "aggregatedList": { + "id": "compute.vpnTunnels.aggregatedList", + "path": "{project}/aggregated/vpnTunnels", + "httpMethod": "GET", + "description": "Retrieves the list of VPN tunnels grouped by scope.", + "parameters": { + "filter": { + "type": "string", + "description": "Filter expression for filtering listed resources.", + "location": "query" + }, + "maxResults": { + "type": "integer", + "description": "Maximum count of results to be returned.", + "default": "500", + "format": "uint32", + "minimum": "0", + "maximum": "500", + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", + "location": "query" + }, + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + } + }, + "parameterOrder": [ + "project" + ], + "response": { + "$ref": "VpnTunnelAggregatedList" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" + ] + }, + "delete": { + "id": "compute.vpnTunnels.delete", + "path": "{project}/regions/{region}/vpnTunnels/{vpnTunnel}", + "httpMethod": "DELETE", + "description": "Deletes the specified VpnTunnel resource.", + "parameters": { + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + }, + "region": { + "type": "string", + "description": "The name of the region for this request.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + }, + "vpnTunnel": { + "type": "string", + "description": "Name of the VpnTunnel resource to delete.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "region", + "vpnTunnel" + ], + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute" + ] + }, + "get": { + "id": "compute.vpnTunnels.get", + "path": "{project}/regions/{region}/vpnTunnels/{vpnTunnel}", + "httpMethod": "GET", + "description": "Returns the specified VpnTunnel resource.", + "parameters": { + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + }, + "region": { + "type": "string", + "description": "The name of the region for this request.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + }, + "vpnTunnel": { + "type": "string", + "description": "Name of the VpnTunnel resource to return.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "region", + "vpnTunnel" + ], + "response": { + "$ref": "VpnTunnel" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" + ] + }, + "insert": { + "id": "compute.vpnTunnels.insert", + "path": "{project}/regions/{region}/vpnTunnels", + "httpMethod": "POST", + "description": "Creates a VpnTunnel resource in the specified project and region using the data included in the request.", + "parameters": { + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + }, + "region": { + "type": "string", + "description": "The name of the region for this request.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "region" + ], + "request": { + "$ref": "VpnTunnel" + }, + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute" + ] + }, + "list": { + "id": "compute.vpnTunnels.list", + "path": "{project}/regions/{region}/vpnTunnels", + "httpMethod": "GET", + "description": "Retrieves the list of VpnTunnel resources contained in the specified project and region.", + "parameters": { + "filter": { + "type": "string", + "description": "Filter expression for filtering listed resources.", + "location": "query" + }, + "maxResults": { + "type": "integer", + "description": "Maximum count of results to be returned.", + "default": "500", + "format": "uint32", + "minimum": "0", + "maximum": "500", + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", + "location": "query" + }, + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + }, + "region": { + "type": "string", + "description": "The name of the region for this request.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "region" + ], + "response": { + "$ref": "VpnTunnelList" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" + ] + } + } + }, "zoneOperations": { "methods": { "delete": { @@ -9324,7 +10601,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -9343,6 +10620,7 @@ "operation" ], "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute" ] }, @@ -9361,7 +10639,7 @@ }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -9383,6 +10661,7 @@ "$ref": "Operation" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -9395,12 +10674,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -9409,12 +10688,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -9435,6 +10714,7 @@ "$ref": "OperationList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -9451,7 +10731,7 @@ "parameters": { "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -9472,6 +10752,7 @@ "$ref": "Zone" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] @@ -9484,12 +10765,12 @@ "parameters": { "filter": { "type": "string", - "description": "Optional. Filter expression for filtering listed resources.", + "description": "Filter expression for filtering listed resources.", "location": "query" }, "maxResults": { "type": "integer", - "description": "Optional. Maximum count of results to be returned. Maximum value is 500 and default value is 500.", + "description": "Maximum count of results to be returned.", "default": "500", "format": "uint32", "minimum": "0", @@ -9498,12 +10779,12 @@ }, "pageToken": { "type": "string", - "description": "Optional. Tag returned by a previous list request truncated by maxResults. Used to continue a previous list request.", + "description": "Tag returned by a previous list request when that list was truncated to maxResults. Used to continue a previous list request.", "location": "query" }, "project": { "type": "string", - "description": "Name of the project scoping this request.", + "description": "Project ID for this request.", "required": true, "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?))", "location": "path" @@ -9516,6 +10797,7 @@ "$ref": "ZoneList" }, "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] diff --git a/Godeps/_workspace/src/google.golang.org/api/compute/v1/compute-gen.go b/Godeps/_workspace/src/google.golang.org/api/compute/v1/compute-gen.go index 5e9da0dffd..d5cd944676 100644 --- a/Godeps/_workspace/src/google.golang.org/api/compute/v1/compute-gen.go +++ b/Godeps/_workspace/src/google.golang.org/api/compute/v1/compute-gen.go @@ -14,6 +14,7 @@ import ( "encoding/json" "errors" "fmt" + "golang.org/x/net/context" "google.golang.org/api/googleapi" "io" "net/http" @@ -33,6 +34,7 @@ var _ = url.Parse var _ = googleapi.Version var _ = errors.New var _ = strings.Replace +var _ = context.Background const apiId = "compute:v1" const apiName = "compute" @@ -41,6 +43,9 @@ const basePath = "https://www.googleapis.com/compute/v1/projects/" // OAuth2 scopes used by this API. const ( + // View and manage your data across Google Cloud Platform services + CloudPlatformScope = "https://www.googleapis.com/auth/cloud-platform" + // View and manage your Google Compute Engine resources ComputeScope = "https://www.googleapis.com/auth/compute" @@ -48,13 +53,13 @@ const ( ComputeReadonlyScope = "https://www.googleapis.com/auth/compute.readonly" // Manage your data and permissions in Google Cloud Storage - DevstorageFull_controlScope = "https://www.googleapis.com/auth/devstorage.full_control" + DevstorageFullControlScope = "https://www.googleapis.com/auth/devstorage.full_control" // View your data in Google Cloud Storage - DevstorageRead_onlyScope = "https://www.googleapis.com/auth/devstorage.read_only" + DevstorageReadOnlyScope = "https://www.googleapis.com/auth/devstorage.read_only" // Manage your data in Google Cloud Storage - DevstorageRead_writeScope = "https://www.googleapis.com/auth/devstorage.read_write" + DevstorageReadWriteScope = "https://www.googleapis.com/auth/devstorage.read_write" ) func New(client *http.Client) (*Service, error) { @@ -86,15 +91,18 @@ func New(client *http.Client) (*Service, error) { s.TargetHttpProxies = NewTargetHttpProxiesService(s) s.TargetInstances = NewTargetInstancesService(s) s.TargetPools = NewTargetPoolsService(s) + s.TargetVpnGateways = NewTargetVpnGatewaysService(s) s.UrlMaps = NewUrlMapsService(s) + s.VpnTunnels = NewVpnTunnelsService(s) s.ZoneOperations = NewZoneOperationsService(s) s.Zones = NewZonesService(s) return s, nil } type Service struct { - client *http.Client - BasePath string // API endpoint base URL + client *http.Client + BasePath string // API endpoint base URL + UserAgent string // optional additional User-Agent fragment Addresses *AddressesService @@ -144,13 +152,24 @@ type Service struct { TargetPools *TargetPoolsService + TargetVpnGateways *TargetVpnGatewaysService + UrlMaps *UrlMapsService + VpnTunnels *VpnTunnelsService + ZoneOperations *ZoneOperationsService Zones *ZonesService } +func (s *Service) userAgent() string { + if s.UserAgent == "" { + return googleapi.UserAgent + } + return googleapi.UserAgent + " " + s.UserAgent +} + func NewAddressesService(s *Service) *AddressesService { rs := &AddressesService{s: s} return rs @@ -367,6 +386,15 @@ type TargetPoolsService struct { s *Service } +func NewTargetVpnGatewaysService(s *Service) *TargetVpnGatewaysService { + rs := &TargetVpnGatewaysService{s: s} + return rs +} + +type TargetVpnGatewaysService struct { + s *Service +} + func NewUrlMapsService(s *Service) *UrlMapsService { rs := &UrlMapsService{s: s} return rs @@ -376,6 +404,15 @@ type UrlMapsService struct { s *Service } +func NewVpnTunnelsService(s *Service) *VpnTunnelsService { + rs := &VpnTunnelsService{s: s} + return rs +} + +type VpnTunnelsService struct { + s *Service +} + func NewZoneOperationsService(s *Service) *ZoneOperationsService { rs := &ZoneOperationsService{s: s} return rs @@ -395,198 +432,293 @@ type ZonesService struct { } type AccessConfig struct { - // Kind: Type of the resource. + // Kind: [Output Only] Type of the resource. Always compute#accessConfig + // for access configs. Kind string `json:"kind,omitempty"` // Name: Name of this access configuration. Name string `json:"name,omitempty"` // NatIP: An external IP address associated with this instance. Specify - // an unused static IP address available to the project. If not - // specified, the external IP will be drawn from a shared ephemeral - // pool. + // an unused static external IP address available to the project or + // leave this field undefined to use an IP from a shared ephemeral IP + // address pool. If you specify a static external IP address, it must + // live in the same region as the zone of the instance. NatIP string `json:"natIP,omitempty"` - // Type: Type of configuration. Must be set to "ONE_TO_ONE_NAT". This - // configures port-for-port NAT to the internet. + // Type: The type of configuration. The default and only option is + // ONE_TO_ONE_NAT. + // + // Possible values: + // "ONE_TO_ONE_NAT" (default) Type string `json:"type,omitempty"` } type Address struct { - // Address: The IP address represented by this resource. + // Address: The static external IP address represented by this resource. Address string `json:"address,omitempty"` - // CreationTimestamp: Creation timestamp in RFC3339 text format (output - // only). + // CreationTimestamp: [Output Only] Creation timestamp in RFC3339 text + // format. CreationTimestamp string `json:"creationTimestamp,omitempty"` // Description: An optional textual description of the resource; // provided by the client when the resource is created. Description string `json:"description,omitempty"` - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id uint64 `json:"id,omitempty,string"` - // Kind: Type of the resource. + // Kind: [Output Only] Type of the resource. Always compute#address for + // addresses. Kind string `json:"kind,omitempty"` // Name: Name of the resource; provided by the client when the resource // is created. The name must be 1-63 characters long, and comply with - // RFC1035. + // RFC1035. Specifically, the name must be 1-63 characters long and + // match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means + // the first character must be a lowercase letter, and all following + // characters must be a dash, lowercase letter, or digit, except the + // last character, which cannot be a dash. Name string `json:"name,omitempty"` - // Region: URL of the region where the regional address resides (output - // only). This field is not applicable to global addresses. + // Region: [Output Only] URL of the region where the regional address + // resides. This field is not applicable to global addresses. Region string `json:"region,omitempty"` - // SelfLink: Server defined URL for the resource (output only). + // SelfLink: [Output Only] Server defined URL for the resource. SelfLink string `json:"selfLink,omitempty"` - // Status: The status of the address (output only). + // Status: [Output Only] The status of the address, which can be either + // IN_USE or RESERVED. An address that is RESERVED is currently reserved + // and available to use. An IN_USE address is currently being used by + // another resource and is not available. + // + // Possible values: + // "IN_USE" + // "RESERVED" Status string `json:"status,omitempty"` - // Users: The resources that are using this address resource. + // Users: [Output Only] The URLs of the resources that are using this + // address. Users []string `json:"users,omitempty"` } type AddressAggregatedList struct { - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id string `json:"id,omitempty"` - // Items: A map of scoped address lists. + // Items: [Output Only] A map of scoped address lists. Items map[string]AddressesScopedList `json:"items,omitempty"` - // Kind: Type of resource. + // Kind: [Output Only] Type of resource. Always + // compute#addressAggregatedList for aggregated lists of addresses. Kind string `json:"kind,omitempty"` - // NextPageToken: A token used to continue a truncated list request - // (output only). + // NextPageToken: [Output Only] A token used to continue a truncated + // list request. NextPageToken string `json:"nextPageToken,omitempty"` - // SelfLink: Server defined URL for this resource (output only). + // SelfLink: [Output Only] Server defined URL for this resource. SelfLink string `json:"selfLink,omitempty"` } type AddressList struct { - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id string `json:"id,omitempty"` - // Items: The address resources. + // Items: [Output Only] A list of Address resources. Items []*Address `json:"items,omitempty"` - // Kind: Type of resource. + // Kind: [Output Only] Type of resource. Always compute#addressList for + // lists of addresses. Kind string `json:"kind,omitempty"` - // NextPageToken: A token used to continue a truncated list request - // (output only). + // NextPageToken: [Output Only] A token used to continue a truncated + // list request. NextPageToken string `json:"nextPageToken,omitempty"` - // SelfLink: Server defined URL for the resource (output only). + // SelfLink: [Output Only] Server defined URL for the resource. SelfLink string `json:"selfLink,omitempty"` } type AddressesScopedList struct { - // Addresses: List of addresses contained in this scope. + // Addresses: [Output Only] List of addresses contained in this scope. Addresses []*Address `json:"addresses,omitempty"` - // Warning: Informational warning which replaces the list of addresses - // when the list is empty. + // Warning: [Output Only] Informational warning which replaces the list + // of addresses when the list is empty. Warning *AddressesScopedListWarning `json:"warning,omitempty"` } type AddressesScopedListWarning struct { - // Code: The warning type identifier for this warning. + // Code: [Output Only] The warning type identifier for this warning. + // + // Possible values: + // "DEPRECATED_RESOURCE_USED" + // "DISK_SIZE_LARGER_THAN_IMAGE_SIZE" + // "INJECTED_KERNELS_DEPRECATED" + // "NEXT_HOP_ADDRESS_NOT_ASSIGNED" + // "NEXT_HOP_CANNOT_IP_FORWARD" + // "NEXT_HOP_INSTANCE_NOT_FOUND" + // "NEXT_HOP_INSTANCE_NOT_ON_NETWORK" + // "NEXT_HOP_NOT_RUNNING" + // "NOT_CRITICAL_ERROR" + // "NO_RESULTS_ON_PAGE" + // "REQUIRED_TOS_AGREEMENT" + // "RESOURCE_NOT_DELETED" + // "SINGLE_INSTANCE_PROPERTY_TEMPLATE" + // "UNREACHABLE" Code string `json:"code,omitempty"` - // Data: Metadata for this warning in 'key: value' format. + // Data: [Output Only] Metadata for this warning in key: value format. Data []*AddressesScopedListWarningData `json:"data,omitempty"` - // Message: Optional human-readable details for this warning. + // Message: [Output Only] Optional human-readable details for this + // warning. Message string `json:"message,omitempty"` } type AddressesScopedListWarningData struct { - // Key: A key for the warning data. + // Key: [Output Only] A key for the warning data. Key string `json:"key,omitempty"` - // Value: A warning data value corresponding to the key. + // Value: [Output Only] A warning data value corresponding to the key. Value string `json:"value,omitempty"` } type AttachedDisk struct { - // AutoDelete: Whether the disk will be auto-deleted when the instance - // is deleted (but not when the disk is detached from the instance). + // AutoDelete: Specifies whether the disk will be auto-deleted when the + // instance is deleted (but not when the disk is detached from the + // instance). AutoDelete bool `json:"autoDelete,omitempty"` - // Boot: Indicates that this is a boot disk. VM will use the first - // partition of the disk for its root filesystem. + // Boot: Indicates that this is a boot disk. The virtual machine will + // use the first partition of the disk for its root filesystem. Boot bool `json:"boot,omitempty"` - // DeviceName: Persistent disk only; must be unique within the instance - // when specified. This represents a unique device name that is + // DeviceName: Specifies a unique device name of your choice that is // reflected into the /dev/ tree of a Linux operating system running - // within the instance. If not specified, a default will be chosen by - // the system. + // within the instance. This name can be used to reference the device + // for mounting, resizing, and so on, from within the instance. + // + // If not specified, the server chooses a default device name to apply + // to this disk, in the form persistent-disks-x, where x is a number + // assigned by Google Compute Engine. This field is only applicable for + // persistent disks. DeviceName string `json:"deviceName,omitempty"` - // Index: A zero-based index to assign to this disk, where 0 is reserved - // for the boot disk. If not specified, the server will choose an - // appropriate value (output only). + // Index: Assigns a zero-based index to this disk, where 0 is reserved + // for the boot disk. For example, if you have many disks attached to an + // instance, each disk would have a unique index number. If not + // specified, the server will choose an appropriate value. Index int64 `json:"index,omitempty"` - // InitializeParams: Initialization parameters. + // InitializeParams: [Input Only] Specifies the parameters for a new + // disk that will be created alongside the new instance. Use + // initialization parameters to create boot disks or local SSDs attached + // to the new instance. + // + // This property is mutually exclusive with the source property; you can + // only define one or the other, but not both. InitializeParams *AttachedDiskInitializeParams `json:"initializeParams,omitempty"` + // Possible values: + // "NVME" + // "SCSI" Interface string `json:"interface,omitempty"` - // Kind: Type of the resource. + // Kind: [Output Only] Type of the resource. Always compute#attachedDisk + // for attached disks. Kind string `json:"kind,omitempty"` - // Licenses: Public visible licenses. + // Licenses: [Output Only] Any valid publicly visible licenses. Licenses []string `json:"licenses,omitempty"` - // Mode: The mode in which to attach this disk, either "READ_WRITE" or - // "READ_ONLY". + // Mode: The mode in which to attach this disk, either READ_WRITE or + // READ_ONLY. If not specified, the default is to attach the disk in + // READ_WRITE mode. + // + // Possible values: + // "READ_ONLY" + // "READ_WRITE" Mode string `json:"mode,omitempty"` - // Source: Persistent disk only; the URL of the persistent disk - // resource. + // Source: Specifies a valid partial or full URL to an existing + // Persistent Disk resource. This field is only applicable for + // persistent disks. Source string `json:"source,omitempty"` - // Type: Type of the disk, either "SCRATCH" or "PERSISTENT". Note that - // persistent disks must be created before you can specify them here. + // Type: Specifies the type of the disk, either SCRATCH or PERSISTENT. + // If not specified, the default is PERSISTENT. + // + // Possible values: + // "PERSISTENT" + // "SCRATCH" Type string `json:"type,omitempty"` } type AttachedDiskInitializeParams struct { - // DiskName: Name of the disk (when not provided defaults to the name of - // the instance). + // DiskName: Specifies the disk name. If not specified, the default is + // to use the name of the instance. DiskName string `json:"diskName,omitempty"` - // DiskSizeGb: Size of the disk in base-2 GB. + // DiskSizeGb: Specifies the size of the disk in base-2 GB. DiskSizeGb int64 `json:"diskSizeGb,omitempty,string"` - // DiskType: URL of the disk type resource describing which disk type to - // use to create the disk; provided by the client when the disk is - // created. + // DiskType: Specifies the disk type to use to create the instance. If + // not specified, the default is pd-standard, specified using the full + // URL. For + // example: + // + // https://www.googleapis.com/compute/v1/projects/project/zones + // /zone/diskTypes/pd-standard + // + // Other values include pd-ssd and local-ssd. If you define this field, + // you can provide either the full or partial URL. For example, the + // following are valid values: + // - + // https://www.googleapis.com/compute/v1/projects/project/zones/zone/disk + // Types/diskType + // - projects/project/zones/zone/diskTypes/diskType + // - zones/zone/diskTypes/diskType DiskType string `json:"diskType,omitempty"` - // SourceImage: The source image used to create this disk. + // SourceImage: A source image used to create the disk. You can provide + // a private (custom) image, and Compute Engine will use the + // corresponding image from your project. For + // example: + // + // global/images/my-private-image + // + // Or you can provide an image from a publicly-available project. For + // example, to use a Debian image from the debian-cloud project, make + // sure to include the project in the + // URL: + // + // projects/debian-cloud/global/images/debian-7-wheezy-vYYYYMMDD + // + // where vYYYYMMDD is the image version. The fully-qualified URL will + // also work in both cases. SourceImage string `json:"sourceImage,omitempty"` } type Backend struct { // BalancingMode: The balancing mode of this backend, default is // UTILIZATION. + // + // Possible values: + // "RATE" + // "UTILIZATION" BalancingMode string `json:"balancingMode,omitempty"` - // CapacityScaler: The multiplier (a value between 0 and 1e6) of the max - // capacity (CPU or RPS, depending on 'balancingMode') the group should - // serve up to. 0 means the group is totally drained. Default value is - // 1. Valid range is [0, 1e6]. + // CapacityScaler: The multiplier (a value between 0.0 and 1.0) of the + // max capacity (CPU or RPS, depending on 'balancingMode') the group + // should serve up to. 0 means the group is totally drained. Default + // value is 1. Valid range is [0.0, 1.0]. CapacityScaler float64 `json:"capacityScaler,omitempty"` // Description: An optional textual description of the resource, which @@ -660,6 +792,8 @@ type BackendService struct { // resource views referenced by this service. Required. PortName string `json:"portName,omitempty"` + // Possible values: + // "HTTP" Protocol string `json:"protocol,omitempty"` // SelfLink: Server defined URL for the resource (output only). @@ -682,7 +816,7 @@ type BackendServiceList struct { // only). Id string `json:"id,omitempty"` - // Items: The BackendService resources. + // Items: A list of BackendService resources. Items []*BackendService `json:"items,omitempty"` // Kind: Type of resource. @@ -709,248 +843,375 @@ type DeprecationStatus struct { // deprecation state of this resource will be changed to OBSOLETE. Obsolete string `json:"obsolete,omitempty"` - // Replacement: A URL of the suggested replacement for the deprecated - // resource. The deprecated resource and its replacement must be - // resources of the same kind. + // Replacement: The URL of the suggested replacement for a deprecated + // resource. The suggested replacement resource must be the same kind of + // resource as the deprecated resource. Replacement string `json:"replacement,omitempty"` - // State: The deprecation state. Can be "DEPRECATED", "OBSOLETE", or - // "DELETED". Operations which create a new resource using a - // "DEPRECATED" resource will return successfully, but with a warning - // indicating the deprecated resource and recommending its replacement. - // New uses of "OBSOLETE" or "DELETED" resources will result in an - // error. + // State: The deprecation state of this resource. This can be + // DEPRECATED, OBSOLETE, or DELETED. Operations which create a new + // resource using a DEPRECATED resource will return successfully, but + // with a warning indicating the deprecated resource and recommending + // its replacement. Operations which use OBSOLETE or DELETED resources + // will be rejected and result in an error. + // + // Possible values: + // "DELETED" + // "DEPRECATED" + // "OBSOLETE" State string `json:"state,omitempty"` } type Disk struct { - // CreationTimestamp: Creation timestamp in RFC3339 text format (output - // only). + // CreationTimestamp: [Output Only] Creation timestamp in RFC3339 text + // format. CreationTimestamp string `json:"creationTimestamp,omitempty"` // Description: An optional textual description of the resource; // provided by the client when the resource is created. Description string `json:"description,omitempty"` - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id uint64 `json:"id,omitempty,string"` - // Kind: Type of the resource. + // Kind: [Output Only] Type of the resource. Always compute#disk for + // disks. Kind string `json:"kind,omitempty"` - // Licenses: Public visible licenses. + // LastAttachTimestamp: [Output Only] Last attach timestamp in RFC3339 + // text format. + LastAttachTimestamp string `json:"lastAttachTimestamp,omitempty"` + + // LastDetachTimestamp: [Output Only] Last detach timestamp in RFC3339 + // text format. + LastDetachTimestamp string `json:"lastDetachTimestamp,omitempty"` + + // Licenses: Any applicable publicly visible licenses. Licenses []string `json:"licenses,omitempty"` // Name: Name of the resource; provided by the client when the resource // is created. The name must be 1-63 characters long, and comply with - // RFC1035. + // RFC1035. Specifically, the name must be 1-63 characters long and + // match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means + // the first character must be a lowercase letter, and all following + // characters must be a dash, lowercase letter, or digit, except the + // last character, which cannot be a dash. Name string `json:"name,omitempty"` // Options: Internal use only. Options string `json:"options,omitempty"` - // SelfLink: Server defined URL for the resource (output only). + // SelfLink: [Output Only] Server-defined fully-qualified URL for this + // resource. SelfLink string `json:"selfLink,omitempty"` - // SizeGb: Size of the persistent disk, specified in GB. This parameter - // is optional when creating a disk from a disk image or a snapshot, - // otherwise it is required. + // SizeGb: Size of the persistent disk, specified in GB. You can specify + // this field when creating a persistent disk using the sourceImage or + // sourceSnapshot parameter, or specify it alone to create an empty + // persistent disk. + // + // If you specify this field along with sourceImage or sourceSnapshot, + // the value of sizeGb must not be less than the size of the sourceImage + // or the size of the snapshot. SizeGb int64 `json:"sizeGb,omitempty,string"` - // SourceImage: The source image used to create this disk. + // SourceImage: The source image used to create this disk. If the source + // image is deleted from the system, this field will not be set, even if + // an image with the same name has been re-created. + // + // When creating a disk, you can provide a private (custom) image using + // the following input, and Compute Engine will use the corresponding + // image from your project. For example: + // + // global/images/my-private-image + // + // Or you can provide an image from a publicly-available project. For + // example, to use a Debian image from the debian-cloud project, make + // sure to include the project in the + // URL: + // + // projects/debian-cloud/global/images/debian-7-wheezy-vYYYYMMDD + // + // where vYYYYMMDD is the image version. The fully-qualified URL will + // also work in both cases. SourceImage string `json:"sourceImage,omitempty"` - // SourceImageId: The 'id' value of the image used to create this disk. - // This value may be used to determine whether the disk was created from - // the current or a previous instance of a given image. + // SourceImageId: The ID value of the image used to create this disk. + // This value identifies the exact image that was used to create this + // persistent disk. For example, if you created the persistent disk from + // an image that was later deleted and recreated under the same name, + // the source image ID would identify the exact version of the image + // that was used. SourceImageId string `json:"sourceImageId,omitempty"` - // SourceSnapshot: The source snapshot used to create this disk. + // SourceSnapshot: The source snapshot used to create this disk. You can + // provide this as a partial or full URL to the resource. For example, + // the following are valid values: + // - + // https://www.googleapis.com/compute/v1/projects/project/global/snapshot + // s/snapshot + // - projects/project/global/snapshots/snapshot + // - global/snapshots/snapshot SourceSnapshot string `json:"sourceSnapshot,omitempty"` - // SourceSnapshotId: The 'id' value of the snapshot used to create this - // disk. This value may be used to determine whether the disk was - // created from the current or a previous instance of a given disk - // snapshot. + // SourceSnapshotId: [Output Only] The unique ID of the snapshot used to + // create this disk. This value identifies the exact snapshot that was + // used to create this persistent disk. For example, if you created the + // persistent disk from a snapshot that was later deleted and recreated + // under the same name, the source snapshot ID would identify the exact + // version of the snapshot that was used. SourceSnapshotId string `json:"sourceSnapshotId,omitempty"` - // Status: The status of disk creation (output only). + // Status: [Output Only] The status of disk creation. Applicable + // statuses includes: CREATING, FAILED, READY, RESTORING. + // + // Possible values: + // "CREATING" + // "FAILED" + // "READY" + // "RESTORING" Status string `json:"status,omitempty"` // Type: URL of the disk type resource describing which disk type to use // to create the disk; provided by the client when the disk is created. Type string `json:"type,omitempty"` - // Zone: URL of the zone where the disk resides (output only). + // Users: Links to the users of the disk (attached instances) in form: + // project/zones/zone/instances/instance + Users []string `json:"users,omitempty"` + + // Zone: [Output Only] URL of the zone where the disk resides. Zone string `json:"zone,omitempty"` } type DiskAggregatedList struct { - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id string `json:"id,omitempty"` - // Items: A map of scoped disk lists. + // Items: [Output Only] A map of scoped disk lists. Items map[string]DisksScopedList `json:"items,omitempty"` - // Kind: Type of resource. + // Kind: [Output Only] Type of resource. Always + // compute#diskAggregatedList for aggregated lists of persistent disks. Kind string `json:"kind,omitempty"` - // NextPageToken: A token used to continue a truncated list request - // (output only). + // NextPageToken: [Output Only] A token used to continue a truncated + // list request. NextPageToken string `json:"nextPageToken,omitempty"` - // SelfLink: Server defined URL for this resource (output only). + // SelfLink: [Output Only] Server defined URL for this resource. SelfLink string `json:"selfLink,omitempty"` } type DiskList struct { - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id string `json:"id,omitempty"` - // Items: The persistent disk resources. + // Items: [Output Only] A list of persistent disks. Items []*Disk `json:"items,omitempty"` - // Kind: Type of resource. + // Kind: [Output Only] Type of resource. Always compute#diskList for + // lists of disks. Kind string `json:"kind,omitempty"` - // NextPageToken: A token used to continue a truncated list request - // (output only). + // NextPageToken: [Output Only] A token used to continue a truncated + // list request. NextPageToken string `json:"nextPageToken,omitempty"` - // SelfLink: Server defined URL for this resource (output only). + // SelfLink: [Output Only] Server defined URL for this resource. SelfLink string `json:"selfLink,omitempty"` } +type DiskMoveRequest struct { + // DestinationZone: The URL of the destination zone to move the disk to. + // This can be a full or partial URL. For example, the following are all + // valid URLs to a zone: + // - https://www.googleapis.com/compute/v1/projects/project/zones/zone + // + // - projects/project/zones/zone + // - zones/zone + DestinationZone string `json:"destinationZone,omitempty"` + + // TargetDisk: The URL of the target disk to move. This can be a full or + // partial URL. For example, the following are all valid URLs to a disk: + // + // - + // https://www.googleapis.com/compute/v1/projects/project/zones/zone/disk + // s/disk + // - projects/project/zones/zone/disks/disk + // - zones/zone/disks/disk + TargetDisk string `json:"targetDisk,omitempty"` +} + type DiskType struct { - // CreationTimestamp: Creation timestamp in RFC3339 text format (output - // only). + // CreationTimestamp: [Output Only] Creation timestamp in RFC3339 text + // format. CreationTimestamp string `json:"creationTimestamp,omitempty"` - // DefaultDiskSizeGb: Server defined default disk size in gb (output - // only). + // DefaultDiskSizeGb: [Output Only] Server defined default disk size in + // GB. DefaultDiskSizeGb int64 `json:"defaultDiskSizeGb,omitempty,string"` - // Deprecated: The deprecation status associated with this disk type. + // Deprecated: [Output Only] The deprecation status associated with this + // disk type. Deprecated *DeprecationStatus `json:"deprecated,omitempty"` - // Description: An optional textual description of the resource. + // Description: [Output Only] An optional textual description of the + // resource. Description string `json:"description,omitempty"` - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id uint64 `json:"id,omitempty,string"` - // Kind: Type of the resource. + // Kind: [Output Only] Type of the resource. Always compute#diskType for + // disk types. Kind string `json:"kind,omitempty"` - // Name: Name of the resource. + // Name: [Output Only] Name of the resource. Name string `json:"name,omitempty"` - // SelfLink: Server defined URL for the resource (output only). + // SelfLink: [Output Only] Server defined URL for the resource. SelfLink string `json:"selfLink,omitempty"` - // ValidDiskSize: An optional textual descroption of the valid disk - // size, e.g., "10GB-10TB". + // ValidDiskSize: [Output Only] An optional textual description of the + // valid disk size, such as "10GB-10TB". ValidDiskSize string `json:"validDiskSize,omitempty"` - // Zone: Url of the zone where the disk type resides (output only). + // Zone: [Output Only] URL of the zone where the disk type resides. Zone string `json:"zone,omitempty"` } type DiskTypeAggregatedList struct { - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id string `json:"id,omitempty"` - // Items: A map of scoped disk type lists. + // Items: [Output Only] A map of scoped disk type lists. Items map[string]DiskTypesScopedList `json:"items,omitempty"` - // Kind: Type of resource. + // Kind: [Output Only] Type of resource. Always + // compute#diskTypeAggregatedList. Kind string `json:"kind,omitempty"` - // NextPageToken: A token used to continue a truncated list request - // (output only). + // NextPageToken: [Output Only] A token used to continue a truncated + // list request. NextPageToken string `json:"nextPageToken,omitempty"` - // SelfLink: Server defined URL for this resource (output only). + // SelfLink: [Output Only] Server defined URL for this resource. SelfLink string `json:"selfLink,omitempty"` } type DiskTypeList struct { - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id string `json:"id,omitempty"` - // Items: The disk type resources. + // Items: [Output Only] A list of Disk Type resources. Items []*DiskType `json:"items,omitempty"` - // Kind: Type of resource. + // Kind: [Output Only] Type of resource. Always compute#diskTypeList for + // disk types. Kind string `json:"kind,omitempty"` - // NextPageToken: A token used to continue a truncated list request - // (output only). + // NextPageToken: [Output Only] A token used to continue a truncated + // list request. NextPageToken string `json:"nextPageToken,omitempty"` - // SelfLink: Server defined URL for this resource (output only). + // SelfLink: [Output Only] Server defined URL for this resource. SelfLink string `json:"selfLink,omitempty"` } type DiskTypesScopedList struct { - // DiskTypes: List of disk types contained in this scope. + // DiskTypes: [Output Only] List of disk types contained in this scope. DiskTypes []*DiskType `json:"diskTypes,omitempty"` - // Warning: Informational warning which replaces the list of disk types - // when the list is empty. + // Warning: [Output Only] Informational warning which replaces the list + // of disk types when the list is empty. Warning *DiskTypesScopedListWarning `json:"warning,omitempty"` } type DiskTypesScopedListWarning struct { - // Code: The warning type identifier for this warning. + // Code: [Output Only] The warning type identifier for this warning. + // + // Possible values: + // "DEPRECATED_RESOURCE_USED" + // "DISK_SIZE_LARGER_THAN_IMAGE_SIZE" + // "INJECTED_KERNELS_DEPRECATED" + // "NEXT_HOP_ADDRESS_NOT_ASSIGNED" + // "NEXT_HOP_CANNOT_IP_FORWARD" + // "NEXT_HOP_INSTANCE_NOT_FOUND" + // "NEXT_HOP_INSTANCE_NOT_ON_NETWORK" + // "NEXT_HOP_NOT_RUNNING" + // "NOT_CRITICAL_ERROR" + // "NO_RESULTS_ON_PAGE" + // "REQUIRED_TOS_AGREEMENT" + // "RESOURCE_NOT_DELETED" + // "SINGLE_INSTANCE_PROPERTY_TEMPLATE" + // "UNREACHABLE" Code string `json:"code,omitempty"` - // Data: Metadata for this warning in 'key: value' format. + // Data: [Output Only] Metadata for this warning in key: value format. Data []*DiskTypesScopedListWarningData `json:"data,omitempty"` - // Message: Optional human-readable details for this warning. + // Message: [Output Only] Optional human-readable details for this + // warning. Message string `json:"message,omitempty"` } type DiskTypesScopedListWarningData struct { - // Key: A key for the warning data. + // Key: [Output Only] A key for the warning data. Key string `json:"key,omitempty"` - // Value: A warning data value corresponding to the key. + // Value: [Output Only] A warning data value corresponding to the key. Value string `json:"value,omitempty"` } type DisksScopedList struct { - // Disks: List of disks contained in this scope. + // Disks: [Output Only] List of disks contained in this scope. Disks []*Disk `json:"disks,omitempty"` - // Warning: Informational warning which replaces the list of disks when - // the list is empty. + // Warning: [Output Only] Informational warning which replaces the list + // of disks when the list is empty. Warning *DisksScopedListWarning `json:"warning,omitempty"` } type DisksScopedListWarning struct { - // Code: The warning type identifier for this warning. + // Code: [Output Only] The warning type identifier for this warning. + // + // Possible values: + // "DEPRECATED_RESOURCE_USED" + // "DISK_SIZE_LARGER_THAN_IMAGE_SIZE" + // "INJECTED_KERNELS_DEPRECATED" + // "NEXT_HOP_ADDRESS_NOT_ASSIGNED" + // "NEXT_HOP_CANNOT_IP_FORWARD" + // "NEXT_HOP_INSTANCE_NOT_FOUND" + // "NEXT_HOP_INSTANCE_NOT_ON_NETWORK" + // "NEXT_HOP_NOT_RUNNING" + // "NOT_CRITICAL_ERROR" + // "NO_RESULTS_ON_PAGE" + // "REQUIRED_TOS_AGREEMENT" + // "RESOURCE_NOT_DELETED" + // "SINGLE_INSTANCE_PROPERTY_TEMPLATE" + // "UNREACHABLE" Code string `json:"code,omitempty"` - // Data: Metadata for this warning in 'key: value' format. + // Data: [Output Only] Metadata for this warning in key: value format. Data []*DisksScopedListWarningData `json:"data,omitempty"` - // Message: Optional human-readable details for this warning. + // Message: [Output Only] Optional human-readable details for this + // warning. Message string `json:"message,omitempty"` } type DisksScopedListWarningData struct { - // Key: A key for the warning data. + // Key: [Output Only] A key for the warning data. Key string `json:"key,omitempty"` - // Value: A warning data value corresponding to the key. + // Value: [Output Only] A warning data value corresponding to the key. Value string `json:"value,omitempty"` } @@ -960,85 +1221,108 @@ type Firewall struct { // connection. Allowed []*FirewallAllowed `json:"allowed,omitempty"` - // CreationTimestamp: Creation timestamp in RFC3339 text format (output - // only). + // CreationTimestamp: [Output Only] Creation timestamp in RFC3339text + // format. CreationTimestamp string `json:"creationTimestamp,omitempty"` // Description: An optional textual description of the resource; // provided by the client when the resource is created. Description string `json:"description,omitempty"` - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id uint64 `json:"id,omitempty,string"` - // Kind: Type of the resource. + // Kind: [Output Ony] Type of the resource. Always compute#firewall for + // firewall rules. Kind string `json:"kind,omitempty"` // Name: Name of the resource; provided by the client when the resource // is created. The name must be 1-63 characters long, and comply with - // RFC1035. + // RFC1035. Specifically, the name must be 1-63 characters long and + // match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means + // the first character must be a lowercase letter, and all following + // characters must be a dash, lowercase letter, or digit, except the + // last character, which cannot be a dash. Name string `json:"name,omitempty"` - // Network: URL of the network to which this firewall is applied; - // provided by the client when the firewall is created. + // Network: URL of the network resource for this firewall rule. This + // field is required for creating an instance but optional when creating + // a firewall rule. If not specified when creating a firewall rule, the + // default network is used: + // global/networks/default + // If you choose to specify this property, you can specify the network + // as a full or partial URL. For example, the following are all valid + // URLs: + // - + // https://www.googleapis.com/compute/v1/projects/myproject/global/networ + // ks/my-network + // - projects/myproject/global/networks/my-network + // - global/networks/default Network string `json:"network,omitempty"` - // SelfLink: Server defined URL for the resource (output only). + // SelfLink: [Output Only] Server defined URL for the resource. SelfLink string `json:"selfLink,omitempty"` - // SourceRanges: A list of IP address blocks expressed in CIDR format - // which this rule applies to. One or both of sourceRanges and - // sourceTags may be set; an inbound connection is allowed if either the - // range or the tag of the source matches. + // SourceRanges: The IP address blocks that this rule applies to, + // expressed in CIDR format. One or both of sourceRanges and sourceTags + // may be set. + // + // If both properties are set, an inbound connection is allowed if the + // range or the tag of the source matches the sourceRanges OR matches + // the sourceTags property; the connection does not need to match both + // properties. SourceRanges []string `json:"sourceRanges,omitempty"` // SourceTags: A list of instance tags which this rule applies to. One - // or both of sourceRanges and sourceTags may be set; an inbound - // connection is allowed if either the range or the tag of the source - // matches. + // or both of sourceRanges and sourceTags may be set. + // + // If both properties are set, an inbound connection is allowed if the + // range or the tag of the source matches the sourceRanges OR matches + // the sourceTags property; the connection does not need to match both + // properties. SourceTags []string `json:"sourceTags,omitempty"` // TargetTags: A list of instance tags indicating sets of instances // located on network which may make network connections as specified in - // allowed. If no targetTags are specified, the firewall rule applies to - // all instances on the specified network. + // allowed[]. If no targetTags are specified, the firewall rule applies + // to all instances on the specified network. TargetTags []string `json:"targetTags,omitempty"` } type FirewallAllowed struct { - // IPProtocol: Required; this is the IP protocol that is allowed for - // this rule. This can either be one of the following well known - // protocol strings ["tcp", "udp", "icmp", "esp", "ah", "sctp"], or the - // IP protocol number. + // IPProtocol: The IP protocol that is allowed for this rule. The + // protocol type is required when creating a firewall. This value can + // either be one of the following well known protocol strings (tcp, udp, + // icmp, esp, ah, sctp), or the IP protocol number. IPProtocol string `json:"IPProtocol,omitempty"` - // Ports: An optional list of ports which are allowed. It is an error to - // specify this for any protocol that isn't UDP or TCP. Each entry must - // be either an integer or a range. If not specified, connections - // through any port are allowed. + // Ports: An optional list of ports which are allowed. This field is + // only applicable for UDP or TCP protocol. Each entry must be either an + // integer or a range. If not specified, connections through any port + // are allowed // - // Example inputs include: ["22"], - // ["80","443"] and ["12345-12349"]. + // Example inputs include: ["22"], ["80","443"], and ["12345-12349"]. Ports []string `json:"ports,omitempty"` } type FirewallList struct { - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id string `json:"id,omitempty"` - // Items: The firewall resources. + // Items: [Output Only] A list of Firewall resources. Items []*Firewall `json:"items,omitempty"` - // Kind: Type of resource. + // Kind: [Output Only] Type of resource. Always compute#firewallList for + // lists of firewalls. Kind string `json:"kind,omitempty"` - // NextPageToken: A token used to continue a truncated list request - // (output only). + // NextPageToken: [Output Only] A token used to continue a truncated + // list request. NextPageToken string `json:"nextPageToken,omitempty"` - // SelfLink: Server defined URL for this resource (output only). + // SelfLink: [Output Only] Server defined URL for this resource. SelfLink string `json:"selfLink,omitempty"` } @@ -1053,6 +1337,13 @@ type ForwardingRule struct { // IPProtocol: The IP protocol to which this rule applies, valid options // are 'TCP', 'UDP', 'ESP', 'AH' or 'SCTP'. + // + // Possible values: + // "AH" + // "ESP" + // "SCTP" + // "TCP" + // "UDP" IPProtocol string `json:"IPProtocol,omitempty"` // CreationTimestamp: Creation timestamp in RFC3339 text format (output @@ -1121,7 +1412,7 @@ type ForwardingRuleList struct { // only). Id string `json:"id,omitempty"` - // Items: The ForwardingRule resources. + // Items: A list of ForwardingRule resources. Items []*ForwardingRule `json:"items,omitempty"` // Kind: Type of resource. @@ -1145,21 +1436,38 @@ type ForwardingRulesScopedList struct { } type ForwardingRulesScopedListWarning struct { - // Code: The warning type identifier for this warning. + // Code: [Output Only] The warning type identifier for this warning. + // + // Possible values: + // "DEPRECATED_RESOURCE_USED" + // "DISK_SIZE_LARGER_THAN_IMAGE_SIZE" + // "INJECTED_KERNELS_DEPRECATED" + // "NEXT_HOP_ADDRESS_NOT_ASSIGNED" + // "NEXT_HOP_CANNOT_IP_FORWARD" + // "NEXT_HOP_INSTANCE_NOT_FOUND" + // "NEXT_HOP_INSTANCE_NOT_ON_NETWORK" + // "NEXT_HOP_NOT_RUNNING" + // "NOT_CRITICAL_ERROR" + // "NO_RESULTS_ON_PAGE" + // "REQUIRED_TOS_AGREEMENT" + // "RESOURCE_NOT_DELETED" + // "SINGLE_INSTANCE_PROPERTY_TEMPLATE" + // "UNREACHABLE" Code string `json:"code,omitempty"` - // Data: Metadata for this warning in 'key: value' format. + // Data: [Output Only] Metadata for this warning in key: value format. Data []*ForwardingRulesScopedListWarningData `json:"data,omitempty"` - // Message: Optional human-readable details for this warning. + // Message: [Output Only] Optional human-readable details for this + // warning. Message string `json:"message,omitempty"` } type ForwardingRulesScopedListWarningData struct { - // Key: A key for the warning data. + // Key: [Output Only] A key for the warning data. Key string `json:"key,omitempty"` - // Value: A warning data value corresponding to the key. + // Value: [Output Only] A warning data value corresponding to the key. Value string `json:"value,omitempty"` } @@ -1169,6 +1477,10 @@ type HealthCheckReference struct { type HealthStatus struct { // HealthState: Health state of the instance. + // + // Possible values: + // "HEALTHY" + // "UNHEALTHY" HealthState string `json:"healthState,omitempty"` // Instance: URL of the instance resource. @@ -1241,7 +1553,8 @@ type HttpHealthCheck struct { SelfLink string `json:"selfLink,omitempty"` // TimeoutSec: How long (in seconds) to wait before claiming failure. - // The default value is 5 seconds. + // The default value is 5 seconds. It is invalid for timeoutSec to have + // greater value than checkIntervalSec. TimeoutSec int64 `json:"timeoutSec,omitempty"` // UnhealthyThreshold: A so-far healthy VM will be marked unhealthy @@ -1254,7 +1567,7 @@ type HttpHealthCheckList struct { // only). Id string `json:"id,omitempty"` - // Items: The HttpHealthCheck resources. + // Items: A list of HttpHealthCheck resources. Items []*HttpHealthCheck `json:"items,omitempty"` // Kind: Type of resource. @@ -1273,8 +1586,8 @@ type Image struct { // Cloud Storage (in bytes). ArchiveSizeBytes int64 `json:"archiveSizeBytes,omitempty,string"` - // CreationTimestamp: Creation timestamp in RFC3339 text format (output - // only). + // CreationTimestamp: [Output Only] Creation timestamp in RFC3339 text + // format. CreationTimestamp string `json:"creationTimestamp,omitempty"` // Deprecated: The deprecation status associated with this image. @@ -1284,56 +1597,79 @@ type Image struct { // client when the resource is created. Description string `json:"description,omitempty"` - // DiskSizeGb: Size of the image when restored onto a disk (in GiB). + // DiskSizeGb: Size of the image when restored onto a persistent disk + // (in GB). DiskSizeGb int64 `json:"diskSizeGb,omitempty,string"` - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id uint64 `json:"id,omitempty,string"` - // Kind: Type of the resource. + // Kind: [Output Only] Type of the resource. Always compute#image for + // images. Kind string `json:"kind,omitempty"` - // Licenses: Public visible licenses. + // Licenses: Any applicable publicly visible licenses. Licenses []string `json:"licenses,omitempty"` // Name: Name of the resource; provided by the client when the resource // is created. The name must be 1-63 characters long, and comply with - // RFC1035. + // RFC1035. Specifically, the name must be 1-63 characters long and + // match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means + // the first character must be a lowercase letter, and all following + // characters must be a dash, lowercase letter, or digit, except the + // last character, which cannot be a dash. Name string `json:"name,omitempty"` - // RawDisk: The raw disk image parameters. + // RawDisk: The parameters of the raw disk image. RawDisk *ImageRawDisk `json:"rawDisk,omitempty"` - // SelfLink: Server defined URL for the resource (output only). + // SelfLink: [Output Only] Server defined URL for the resource. SelfLink string `json:"selfLink,omitempty"` - // SourceDisk: The source disk used to create this image. + // SourceDisk: URL of the The source disk used to create this image. + // This can be a full or valid partial URL. You must provide either this + // property or the rawDisk.source property but not both to create an + // image. For example, the following are valid values: + // - + // https://www.googleapis.com/compute/v1/projects/project/zones/zone/disk + // /disk + // - projects/project/zones/zone/disk/disk + // - zones/zone/disks/disk SourceDisk string `json:"sourceDisk,omitempty"` - // SourceDiskId: The 'id' value of the disk used to create this image. + // SourceDiskId: The ID value of the disk used to create this image. // This value may be used to determine whether the image was taken from // the current or a previous instance of a given disk name. SourceDiskId string `json:"sourceDiskId,omitempty"` - // SourceType: Must be "RAW"; provided by the client when the disk image - // is created. + // SourceType: The type of the image used to create this disk. The + // default and only value is RAW + // + // Possible values: + // "RAW" (default) SourceType string `json:"sourceType,omitempty"` - // Status: Status of the image (output only). It will be one of the - // following READY - after image has been successfully created and is - // ready for use FAILED - if creating the image fails for some reason - // PENDING - the image creation is in progress An image can be used to - // create other resources suck as instances only after the image has - // been successfully created and the status is set to READY. + // Status: [Output Only] The status of the image. An image can be used + // to create other resources, such as instances, only after the image + // has been successfully created and the status is set to READY. + // Possible values are FAILED, PENDING, or READY. + // + // Possible values: + // "FAILED" + // "PENDING" + // "READY" Status string `json:"status,omitempty"` } type ImageRawDisk struct { // ContainerType: The format used to encode and transmit the block - // device. Should be TAR. This is just a container and transmission - // format and not a runtime format. Provided by the client when the disk - // image is created. + // device, which should be TAR. This is just a container and + // transmission format and not a runtime format. Provided by the client + // when the disk image is created. + // + // Possible values: + // "TAR" ContainerType string `json:"containerType,omitempty"` // Sha1Checksum: An optional SHA1 checksum of the disk image before @@ -1341,7 +1677,8 @@ type ImageRawDisk struct { Sha1Checksum string `json:"sha1Checksum,omitempty"` // Source: The full Google Cloud Storage URL where the disk image is - // stored; provided by the client when the disk image is created. + // stored. You must provide either this property or the sourceDisk + // property but not both. Source string `json:"source,omitempty"` } @@ -1350,7 +1687,7 @@ type ImageList struct { // only). Id string `json:"id,omitempty"` - // Items: The disk image resources. + // Items: A list of Image resources. Items []*Image `json:"items,omitempty"` // Kind: Type of resource. @@ -1365,15 +1702,17 @@ type ImageList struct { } type Instance struct { - // CanIpForward: Allows this instance to send packets with source IP - // addresses other than its own and receive packets with destination IP - // addresses other than its own. If this instance will be used as an IP - // gateway or it will be set as the next-hop in a Route resource, say - // true. If unsure, leave this set to false. + // CanIpForward: Allows this instance to send and receive packets with + // non-matching destination or source IPs. This is required if you plan + // to use this instance to forward routes. For more information, see + // Enabling IP Forwarding. CanIpForward bool `json:"canIpForward,omitempty"` - // CreationTimestamp: Creation timestamp in RFC3339 text format (output - // only). + // CpuPlatform: [Output Only] The CPU platform used by this instance. + CpuPlatform string `json:"cpuPlatform,omitempty"` + + // CreationTimestamp: [Output Only] Creation timestamp in RFC3339 text + // format. CreationTimestamp string `json:"creationTimestamp,omitempty"` // Description: An optional textual description of the resource; @@ -1384,155 +1723,194 @@ type Instance struct { // must be created before you can assign them. Disks []*AttachedDisk `json:"disks,omitempty"` - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id uint64 `json:"id,omitempty,string"` - // Kind: Type of the resource. + // Kind: [Output Only] Type of the resource. Always compute#instance for + // instances. Kind string `json:"kind,omitempty"` - // MachineType: URL of the machine type resource describing which - // machine type to use to host the instance; provided by the client when - // the instance is created. + // MachineType: Full or partial URL of the machine type resource to use + // for this instance. This is provided by the client when the instance + // is created. For example, the following is a valid partial + // url: + // + // zones/zone/machineTypes/machine-type MachineType string `json:"machineType,omitempty"` - // Metadata: Metadata key/value pairs assigned to this instance. - // Consists of custom metadata or predefined keys; see Instance - // documentation for more information. + // Metadata: The metadata key/value pairs assigned to this instance. + // This includes custom metadata and predefined keys. Metadata *Metadata `json:"metadata,omitempty"` // Name: Name of the resource; provided by the client when the resource // is created. The name must be 1-63 characters long, and comply with - // RFC1035. + // RFC1035. Specifically, the name must be 1-63 characters long and + // match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means + // the first character must be a lowercase letter, and all following + // characters must be a dash, lowercase letter, or digit, except the + // last character, which cannot be a dash. Name string `json:"name,omitempty"` - // NetworkInterfaces: Array of configurations for this interface. This - // specifies how this interface is configured to interact with other - // network services, such as connecting to the internet. Currently, - // ONE_TO_ONE_NAT is the only access config supported. If there are no - // accessConfigs specified, then this instance will have no external - // internet access. + // NetworkInterfaces: An array of configurations for this interface. + // This specifies how this interface is configured to interact with + // other network services, such as connecting to the internet. NetworkInterfaces []*NetworkInterface `json:"networkInterfaces,omitempty"` // Scheduling: Scheduling options for this instance. Scheduling *Scheduling `json:"scheduling,omitempty"` - // SelfLink: Server defined URL for this resource (output only). + // SelfLink: [Output Only] Server defined URL for this resource. SelfLink string `json:"selfLink,omitempty"` - // ServiceAccounts: A list of service accounts each with specified - // scopes, for which access tokens are to be made available to the - // instance through metadata queries. + // ServiceAccounts: A list of service accounts, with their specified + // scopes, authorized for this instance. Service accounts generate + // access tokens that can be accessed through the metadata server and + // used to authenticate applications on the instance. See Authenticating + // from Google Compute Engine for more information. ServiceAccounts []*ServiceAccount `json:"serviceAccounts,omitempty"` - // Status: Instance status. One of the following values: "PROVISIONING", - // "STAGING", "RUNNING", "STOPPING", "STOPPED", "TERMINATED" (output - // only). + // Status: [Output Only] The status of the instance. One of the + // following values: PROVISIONING, STAGING, RUNNING, STOPPING, and + // TERMINATED. + // + // Possible values: + // "PROVISIONING" + // "RUNNING" + // "STAGING" + // "STOPPED" + // "STOPPING" + // "SUSPENDED" + // "SUSPENDING" + // "TERMINATED" Status string `json:"status,omitempty"` - // StatusMessage: An optional, human-readable explanation of the status - // (output only). + // StatusMessage: [Output Only] An optional, human-readable explanation + // of the status. StatusMessage string `json:"statusMessage,omitempty"` - // Tags: A list of tags to be applied to this instance. Used to identify - // valid sources or targets for network firewalls. Provided by the - // client on instance creation. The tags can be later modified by the - // setTags method. Each tag within the list must comply with RFC1035. + // Tags: A list of tags to appy to this instance. Tags are used to + // identify valid sources or targets for network firewalls and are + // specified by the client during instance creation. The tags can be + // later modified by the setTags method. Each tag within the list must + // comply with RFC1035. Tags *Tags `json:"tags,omitempty"` - // Zone: URL of the zone where the instance resides (output only). + // Zone: [Output Only] URL of the zone where the instance resides. Zone string `json:"zone,omitempty"` } type InstanceAggregatedList struct { - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id string `json:"id,omitempty"` - // Items: A map of scoped instance lists. + // Items: [Output Only] A map of scoped instance lists. Items map[string]InstancesScopedList `json:"items,omitempty"` - // Kind: Type of resource. + // Kind: [Output Only] Type of resource. Always + // compute#instanceAggregatedList for aggregated lists of Instance + // resources. Kind string `json:"kind,omitempty"` - // NextPageToken: A token used to continue a truncated list request - // (output only). + // NextPageToken: [Output Only] A token used to continue a truncated + // list request. NextPageToken string `json:"nextPageToken,omitempty"` - // SelfLink: Server defined URL for this resource (output only). + // SelfLink: [Output Only] Server defined URL for this resource. SelfLink string `json:"selfLink,omitempty"` } type InstanceList struct { - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id string `json:"id,omitempty"` - // Items: A list of instance resources. + // Items: [Output Only] A list of Instance resources. Items []*Instance `json:"items,omitempty"` - // Kind: Type of resource. + // Kind: [Output Only] Type of resource. Always compute#instanceList for + // lists of Instance resources. Kind string `json:"kind,omitempty"` - // NextPageToken: A token used to continue a truncated list request - // (output only). + // NextPageToken: [Output Only] A token used to continue a truncated + // list request. NextPageToken string `json:"nextPageToken,omitempty"` - // SelfLink: Server defined URL for this resource (output only). + // SelfLink: [Output Only] Server defined URL for this resource. SelfLink string `json:"selfLink,omitempty"` } +type InstanceMoveRequest struct { + // DestinationZone: The URL of the destination zone to move the instance + // to. This can be a full or partial URL. For example, the following are + // all valid URLs to a zone: + // - https://www.googleapis.com/compute/v1/projects/project/zones/zone + // + // - projects/project/zones/zone + // - zones/zone + DestinationZone string `json:"destinationZone,omitempty"` + + // TargetInstance: The URL of the target instance to move. This can be a + // full or partial URL. For example, the following are all valid URLs to + // an instance: + // - + // https://www.googleapis.com/compute/v1/projects/project/zones/zone/inst + // ances/instance + // - projects/project/zones/zone/instances/instance + // - zones/zone/instances/instance + TargetInstance string `json:"targetInstance,omitempty"` +} + type InstanceProperties struct { - // CanIpForward: Allows instances created based on this template to send - // packets with source IP addresses other than their own and receive - // packets with destination IP addresses other than their own. If these - // instances will be used as an IP gateway or it will be set as the - // next-hop in a Route resource, say true. If unsure, leave this set to - // false. + // CanIpForward: A boolean that specifies if instances created from this + // template can send packets with source IP addresses other than their + // own or receive packets with destination IP addresses other than their + // own. If you use these instances as an IP gateway or as the next-hop + // in a Route resource, specify true. Otherwise, specify false. CanIpForward bool `json:"canIpForward,omitempty"` - // Description: An optional textual description for the instances - // created based on the instance template resource; provided by the - // client when the template is created. + // Description: An optional text description for the instances that are + // created from this instance template. Description string `json:"description,omitempty"` - // Disks: Array of disks associated with instance created based on this - // template. + // Disks: An array of disks that are associated with the instances that + // are created from this template. Disks []*AttachedDisk `json:"disks,omitempty"` - // MachineType: Name of the machine type resource describing which - // machine type to use to host the instances created based on this - // template; provided by the client when the instance template is - // created. + // MachineType: The machine type to use for instances that are created + // from this template. MachineType string `json:"machineType,omitempty"` - // Metadata: Metadata key/value pairs assigned to instances created - // based on this template. Consists of custom metadata or predefined - // keys; see Instance documentation for more information. + // Metadata: The metadata key/value pairs to assign to instances that + // are created from this template. These pairs can consist of custom + // metadata or predefined keys. See Project and instance metadata for + // more information. Metadata *Metadata `json:"metadata,omitempty"` - // NetworkInterfaces: Array of configurations for this interface. This - // specifies how this interface is configured to interact with other - // network services, such as connecting to the internet. Currently, - // ONE_TO_ONE_NAT is the only access config supported. If there are no - // accessConfigs specified, then this instances created based based on - // this template will have no external internet access. + // NetworkInterfaces: An array of network access configurations for this + // interface. This specifies how this interface is configured to + // interact with other network services, such as connecting to the + // internet. Currently, ONE_TO_ONE_NAT is the only supported access + // configuration. If you do not specify any access configurations, the + // instances that are created from this template will have no external + // internet access. NetworkInterfaces []*NetworkInterface `json:"networkInterfaces,omitempty"` - // Scheduling: Scheduling options for the instances created based on - // this template. + // Scheduling: A list of scheduling options for the instances that are + // created from this template. Scheduling *Scheduling `json:"scheduling,omitempty"` - // ServiceAccounts: A list of service accounts each with specified - // scopes, for which access tokens are to be made available to the - // instances created based on this template, through metadata queries. + // ServiceAccounts: A list of service accounts with specified scopes. + // Access tokens for these service accounts are available to the + // instances that are created from this template. Use metadata queries + // to obtain the access tokens for these instances. ServiceAccounts []*ServiceAccount `json:"serviceAccounts,omitempty"` - // Tags: A list of tags to be applied to the instances created based on - // this template used to identify valid sources or targets for network - // firewalls. Provided by the client on instance creation. The tags can - // be later modified by the setTags method. Each tag within the list - // must comply with RFC1035. + // Tags: A list of tags to apply to the instances that are created from + // this template. The tags identify valid sources or targets for network + // firewalls. The setTags method can modify this list of tags. Each tag + // within the list must comply with RFC1035. Tags *Tags `json:"tags,omitempty"` } @@ -1541,78 +1919,97 @@ type InstanceReference struct { } type InstanceTemplate struct { - // CreationTimestamp: Creation timestamp in RFC3339 text format (output - // only). + // CreationTimestamp: [Output Only] The creation timestamp for this + // instance template in RFC3339 text format. CreationTimestamp string `json:"creationTimestamp,omitempty"` - // Description: An optional textual description of the instance template - // resource; provided by the client when the resource is created. + // Description: An optional text description for the instance template. Description string `json:"description,omitempty"` - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] A unique identifier for this instance template. The + // server defines this identifier. Id uint64 `json:"id,omitempty,string"` - // Kind: Type of the resource. + // Kind: [Output Only] The resource type, which is always + // compute#instanceTemplate for instance templates. Kind string `json:"kind,omitempty"` - // Name: Name of the instance template resource; provided by the client - // when the resource is created. The name must be 1-63 characters long, - // and comply with RFC1035 + // Name: The name of the instance template. The name must be 1-63 + // characters long, and comply with RFC1035. Name string `json:"name,omitempty"` - // Properties: The instance properties portion of this instance template + // Properties: The instance properties for the instance template // resource. Properties *InstanceProperties `json:"properties,omitempty"` - // SelfLink: Server defined URL for the resource (output only). + // SelfLink: [Output Only] The URL for this instance template. The + // server defines this URL. SelfLink string `json:"selfLink,omitempty"` } type InstanceTemplateList struct { - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] A unique identifier for this instance template. The + // server defines this identifier. Id string `json:"id,omitempty"` - // Items: A list of instance template resources. + // Items: A list of InstanceTemplate resources. Items []*InstanceTemplate `json:"items,omitempty"` - // Kind: Type of resource. + // Kind: [Output Only] The resource type, which is always + // compute#instanceTemplatesListResponse for instance template lists. Kind string `json:"kind,omitempty"` - // NextPageToken: A token used to continue a truncated list request - // (output only). + // NextPageToken: [Output Only] A token that is used to continue a + // truncated list request. NextPageToken string `json:"nextPageToken,omitempty"` - // SelfLink: Server defined URL for this resource (output only). + // SelfLink: [Output Only] The URL for this instance template list. The + // server defines this URL. SelfLink string `json:"selfLink,omitempty"` } type InstancesScopedList struct { - // Instances: List of instances contained in this scope. + // Instances: [Output Only] List of instances contained in this scope. Instances []*Instance `json:"instances,omitempty"` - // Warning: Informational warning which replaces the list of instances - // when the list is empty. + // Warning: [Output Only] Informational warning which replaces the list + // of instances when the list is empty. Warning *InstancesScopedListWarning `json:"warning,omitempty"` } type InstancesScopedListWarning struct { - // Code: The warning type identifier for this warning. + // Code: [Output Only] The warning type identifier for this warning. + // + // Possible values: + // "DEPRECATED_RESOURCE_USED" + // "DISK_SIZE_LARGER_THAN_IMAGE_SIZE" + // "INJECTED_KERNELS_DEPRECATED" + // "NEXT_HOP_ADDRESS_NOT_ASSIGNED" + // "NEXT_HOP_CANNOT_IP_FORWARD" + // "NEXT_HOP_INSTANCE_NOT_FOUND" + // "NEXT_HOP_INSTANCE_NOT_ON_NETWORK" + // "NEXT_HOP_NOT_RUNNING" + // "NOT_CRITICAL_ERROR" + // "NO_RESULTS_ON_PAGE" + // "REQUIRED_TOS_AGREEMENT" + // "RESOURCE_NOT_DELETED" + // "SINGLE_INSTANCE_PROPERTY_TEMPLATE" + // "UNREACHABLE" Code string `json:"code,omitempty"` - // Data: Metadata for this warning in 'key: value' format. + // Data: [Output Only] Metadata for this warning in key: value format. Data []*InstancesScopedListWarningData `json:"data,omitempty"` - // Message: Optional human-readable details for this warning. + // Message: [Output Only] Optional human-readable details for this + // warning. Message string `json:"message,omitempty"` } type InstancesScopedListWarningData struct { - // Key: A key for the warning data. + // Key: [Output Only] A key for the warning data. Key string `json:"key,omitempty"` - // Value: A warning data value corresponding to the key. + // Value: [Output Only] A warning data value corresponding to the key. Value string `json:"value,omitempty"` } @@ -1621,63 +2018,69 @@ type License struct { // running software that contains this license on an instance. ChargesUseFee bool `json:"chargesUseFee,omitempty"` - // Kind: Type of resource. + // Kind: [Output Only] Type of resource. Always compute#license for + // licenses. Kind string `json:"kind,omitempty"` - // Name: Name of the resource; provided by the client when the resource - // is created. The name must be 1-63 characters long, and comply with - // RFC1035. + // Name: Name of the resource. The name must be 1-63 characters long, + // and comply with RCF1035. Name string `json:"name,omitempty"` - // SelfLink: Server defined URL for the resource (output only). + // SelfLink: [Output Only] Server defined URL for the resource. SelfLink string `json:"selfLink,omitempty"` } type MachineType struct { - // CreationTimestamp: Creation timestamp in RFC3339 text format (output - // only). + // CreationTimestamp: [Output Only] Creation timestamp in RFC3339 text + // format. CreationTimestamp string `json:"creationTimestamp,omitempty"` - // Deprecated: The deprecation status associated with this machine type. + // Deprecated: [Output Only] The deprecation status associated with this + // machine type. Deprecated *DeprecationStatus `json:"deprecated,omitempty"` - // Description: An optional textual description of the resource. + // Description: [Output Only] An optional textual description of the + // resource. Description string `json:"description,omitempty"` - // GuestCpus: Count of CPUs exposed to the instance. + // GuestCpus: [Output Only] The tumber of CPUs exposed to the instance. GuestCpus int64 `json:"guestCpus,omitempty"` - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id uint64 `json:"id,omitempty,string"` - // ImageSpaceGb: Space allotted for the image, defined in GB. + // ImageSpaceGb: [Deprecated] This property is deprecated and will never + // be populated with any relevant values. ImageSpaceGb int64 `json:"imageSpaceGb,omitempty"` // Kind: Type of the resource. Kind string `json:"kind,omitempty"` - // MaximumPersistentDisks: Maximum persistent disks allowed. + // MaximumPersistentDisks: [Output Only] Maximum persistent disks + // allowed. MaximumPersistentDisks int64 `json:"maximumPersistentDisks,omitempty"` - // MaximumPersistentDisksSizeGb: Maximum total persistent disks size - // (GB) allowed. + // MaximumPersistentDisksSizeGb: [Output Only] Maximum total persistent + // disks size (GB) allowed. MaximumPersistentDisksSizeGb int64 `json:"maximumPersistentDisksSizeGb,omitempty,string"` - // MemoryMb: Physical memory assigned to the instance, defined in MB. + // MemoryMb: [Output Only] The amount of physical memory available to + // the instance, defined in MB. MemoryMb int64 `json:"memoryMb,omitempty"` - // Name: Name of the resource. + // Name: [Output Only] Name of the resource. Name string `json:"name,omitempty"` - // ScratchDisks: List of extended scratch disks assigned to the - // instance. + // ScratchDisks: [Output Only] List of extended scratch disks assigned + // to the instance. ScratchDisks []*MachineTypeScratchDisks `json:"scratchDisks,omitempty"` - // SelfLink: Server defined URL for the resource (output only). + // SelfLink: [Output Only] Server defined URL for the resource. SelfLink string `json:"selfLink,omitempty"` - // Zone: Url of the zone where the machine type resides (output only). + // Zone: [Output Only] The name of the zone where the machine type + // resides, such as us-central1-a. Zone string `json:"zone,omitempty"` } @@ -1687,82 +2090,107 @@ type MachineTypeScratchDisks struct { } type MachineTypeAggregatedList struct { - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id string `json:"id,omitempty"` - // Items: A map of scoped machine type lists. + // Items: [Output Only] A map of scoped machine type lists. Items map[string]MachineTypesScopedList `json:"items,omitempty"` - // Kind: Type of resource. + // Kind: [Output Only] Type of resource. Always + // compute#machineTypeAggregatedList for aggregated lists of machine + // types. Kind string `json:"kind,omitempty"` - // NextPageToken: A token used to continue a truncated list request - // (output only). + // NextPageToken: [Output Only] A token used to continue a truncated + // list request. NextPageToken string `json:"nextPageToken,omitempty"` - // SelfLink: Server defined URL for this resource (output only). + // SelfLink: [Output Only] Server defined URL for this resource. SelfLink string `json:"selfLink,omitempty"` } type MachineTypeList struct { - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id string `json:"id,omitempty"` - // Items: The machine type resources. + // Items: [Output Only] A list of Machine Type resources. Items []*MachineType `json:"items,omitempty"` - // Kind: Type of resource. + // Kind: [Output Only] Type of resource. Always compute#machineTypeList + // for lists of machine types. Kind string `json:"kind,omitempty"` - // NextPageToken: A token used to continue a truncated list request - // (output only). + // NextPageToken: [Output Only] A token used to continue a truncated + // list request. NextPageToken string `json:"nextPageToken,omitempty"` - // SelfLink: Server defined URL for this resource (output only). + // SelfLink: [Output Only] Server defined URL for this resource. SelfLink string `json:"selfLink,omitempty"` } type MachineTypesScopedList struct { - // MachineTypes: List of machine types contained in this scope. + // MachineTypes: [Output Only] List of machine types contained in this + // scope. MachineTypes []*MachineType `json:"machineTypes,omitempty"` - // Warning: Informational warning which replaces the list of machine - // types when the list is empty. + // Warning: [Output Only] An informational warning that appears when the + // machine types list is empty. Warning *MachineTypesScopedListWarning `json:"warning,omitempty"` } type MachineTypesScopedListWarning struct { - // Code: The warning type identifier for this warning. + // Code: [Output Only] The warning type identifier for this warning. + // + // Possible values: + // "DEPRECATED_RESOURCE_USED" + // "DISK_SIZE_LARGER_THAN_IMAGE_SIZE" + // "INJECTED_KERNELS_DEPRECATED" + // "NEXT_HOP_ADDRESS_NOT_ASSIGNED" + // "NEXT_HOP_CANNOT_IP_FORWARD" + // "NEXT_HOP_INSTANCE_NOT_FOUND" + // "NEXT_HOP_INSTANCE_NOT_ON_NETWORK" + // "NEXT_HOP_NOT_RUNNING" + // "NOT_CRITICAL_ERROR" + // "NO_RESULTS_ON_PAGE" + // "REQUIRED_TOS_AGREEMENT" + // "RESOURCE_NOT_DELETED" + // "SINGLE_INSTANCE_PROPERTY_TEMPLATE" + // "UNREACHABLE" Code string `json:"code,omitempty"` - // Data: Metadata for this warning in 'key: value' format. + // Data: [Output Only] Metadata for this warning in key: value format. Data []*MachineTypesScopedListWarningData `json:"data,omitempty"` - // Message: Optional human-readable details for this warning. + // Message: [Output Only] Optional human-readable details for this + // warning. Message string `json:"message,omitempty"` } type MachineTypesScopedListWarningData struct { - // Key: A key for the warning data. + // Key: [Output Only] A key for the warning data. Key string `json:"key,omitempty"` - // Value: A warning data value corresponding to the key. + // Value: [Output Only] A warning data value corresponding to the key. Value string `json:"value,omitempty"` } type Metadata struct { - // Fingerprint: Fingerprint of this resource. A hash of the metadata's - // contents. This field is used for optimistic locking. An up-to-date - // metadata fingerprint must be provided in order to modify metadata. + // Fingerprint: Specifies a fingerprint for this request, which is + // essentially a hash of the metadata's contents and used for optimistic + // locking. The fingerprint is initially generated by Compute Engine and + // changes after every request to modify or update metadata. You must + // always provide an up-to-date fingerprint hash in order to update or + // change metadata. Fingerprint string `json:"fingerprint,omitempty"` // Items: Array of key/value pairs. The total size of all keys and // values must be less than 512 KB. Items []*MetadataItems `json:"items,omitempty"` - // Kind: Type of the resource. + // Kind: [Output Only] Type of the resource. Always compute#metadata for + // metadata. Kind string `json:"kind,omitempty"` } @@ -1782,271 +2210,328 @@ type MetadataItems struct { } type Network struct { - // IPv4Range: Required; The range of internal addresses that are legal - // on this network. This range is a CIDR specification, for example: + // IPv4Range: The range of internal addresses that are legal on this + // network. This range is a CIDR specification, for example: // 192.168.0.0/16. Provided by the client when the network is created. IPv4Range string `json:"IPv4Range,omitempty"` - // CreationTimestamp: Creation timestamp in RFC3339 text format (output - // only). + // CreationTimestamp: [Output Only] Creation timestamp in RFC3339 text + // format. CreationTimestamp string `json:"creationTimestamp,omitempty"` // Description: An optional textual description of the resource; // provided by the client when the resource is created. Description string `json:"description,omitempty"` - // GatewayIPv4: An optional address that is used for default routing to - // other networks. This must be within the range specified by IPv4Range, - // and is typically the first usable address in that range. If not - // specified, the default value is the first usable address in - // IPv4Range. + // GatewayIPv4: A gateway address for default routing to other networks. + // This value is read only and is selected by the Google Compute Engine, + // typically as the first usable address in the IPv4Range. GatewayIPv4 string `json:"gatewayIPv4,omitempty"` - // Id: Unique identifier for the resource; defined by the server (output - // only). + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id uint64 `json:"id,omitempty,string"` - // Kind: Type of the resource. + // Kind: [Output Only] Type of the resource. Always compute#network for + // networks. Kind string `json:"kind,omitempty"` // Name: Name of the resource; provided by the client when the resource // is created. The name must be 1-63 characters long, and comply with - // RFC1035. + // RFC1035. Specifically, the name must be 1-63 characters long and + // match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means + // the first character must be a lowercase letter, and all following + // characters must be a dash, lowercase letter, or digit, except the + // last character, which cannot be a dash. Name string `json:"name,omitempty"` - // SelfLink: Server defined URL for the resource (output only). + // SelfLink: [Output Only] Server defined URL for the resource. SelfLink string `json:"selfLink,omitempty"` } type NetworkInterface struct { - // AccessConfigs: Array of configurations for this interface. This - // specifies how this interface is configured to interact with other - // network services, such as connecting to the internet. Currently, - // ONE_TO_ONE_NAT is the only access config supported. If there are no - // accessConfigs specified, then this instance will have no external - // internet access. + // AccessConfigs: An array of configurations for this interface. + // Currently, chunkSize { + reqSize = chunkSize + } + r := io.NewSectionReader(rx.Media, start, reqSize) + req, _ := http.NewRequest("POST", rx.URI, r) + req.ContentLength = reqSize + req.Header.Set("Content-Range", fmt.Sprintf("bytes %v-%v/%v", start, start+reqSize-1, rx.ContentLength)) + req.Header.Set("Content-Type", rx.MediaType) + req.Header.Set("User-Agent", rx.UserAgent) + res, err = rx.Client.Do(req) + start += reqSize + if err == nil && (res.StatusCode == statusResumeIncomplete || res.StatusCode == http.StatusOK) { + rx.mu.Lock() + rx.progress = start // keep track of number of bytes sent so far + rx.mu.Unlock() + if rx.Callback != nil { + rx.Callback(start, rx.ContentLength) + } + } + if err != nil || res.StatusCode != statusResumeIncomplete { + break + } + } + return res, err +} + +var sleep = time.Sleep // override in unit tests + +// Upload starts the process of a resumable upload with a cancellable context. +// It retries indefinitely (with a pause of uploadPause between attempts) until cancelled. +// It is called from the auto-generated API code and is not visible to the user. +// rx is private to the auto-generated API code. +func (rx *ResumableUpload) Upload(ctx context.Context) (*http.Response, error) { + var res *http.Response + var err error + for { + res, err = rx.transferChunks(ctx) + if err != nil || res.StatusCode == http.StatusCreated || res.StatusCode == http.StatusOK { + return res, err + } + select { // Check for cancellation + case <-ctx.Done(): + res.StatusCode = http.StatusRequestTimeout + return res, ctx.Err() + default: + } + sleep(uploadPause) + } + return res, err } func ResolveRelative(basestr, relstr string) string { diff --git a/Godeps/_workspace/src/google.golang.org/api/googleapi/googleapi_test.go b/Godeps/_workspace/src/google.golang.org/api/googleapi/googleapi_test.go index abc5185061..c4989f70a5 100644 --- a/Godeps/_workspace/src/google.golang.org/api/googleapi/googleapi_test.go +++ b/Godeps/_workspace/src/google.golang.org/api/googleapi/googleapi_test.go @@ -11,9 +11,15 @@ import ( "io/ioutil" "net/http" "net/url" + "os" "reflect" + "regexp" + "strconv" "strings" "testing" + "time" + + "golang.org/x/net/context" ) type SetOpaqueTest struct { @@ -359,3 +365,234 @@ func TestConvertVariant(t *testing.T) { } } } + +type unexpectedReader struct{} + +func (unexpectedReader) Read([]byte) (int, error) { + return 0, fmt.Errorf("unexpected read in test.") +} + +var contentRangeRE = regexp.MustCompile(`^bytes (\d+)\-(\d+)/(\d+)$`) + +func (t *testTransport) RoundTrip(req *http.Request) (*http.Response, error) { + t.req = req + if rng := req.Header.Get("Content-Range"); rng != "" && !strings.HasPrefix(rng, "bytes */") { // Read the data + m := contentRangeRE.FindStringSubmatch(rng) + if len(m) != 4 { + return nil, fmt.Errorf("unable to parse content range: %v", rng) + } + start, err := strconv.ParseInt(m[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("unable to parse content range: %v", rng) + } + end, err := strconv.ParseInt(m[2], 10, 64) + if err != nil { + return nil, fmt.Errorf("unable to parse content range: %v", rng) + } + totalSize, err := strconv.ParseInt(m[3], 10, 64) + if err != nil { + return nil, fmt.Errorf("unable to parse content range: %v", rng) + } + partialSize := end - start + 1 + t.buf, err = ioutil.ReadAll(req.Body) + if err != nil || int64(len(t.buf)) != partialSize { + return nil, fmt.Errorf("unable to read %v bytes from request data, n=%v: %v", partialSize, len(t.buf), err) + } + if totalSize == end+1 { + t.statusCode = 200 // signify completion of transfer + } + } + f := ioutil.NopCloser(unexpectedReader{}) + res := &http.Response{ + Body: f, + StatusCode: t.statusCode, + Header: http.Header{}, + } + if t.rangeVal != "" { + res.Header.Set("Range", t.rangeVal) + } + return res, nil +} + +type testTransport struct { + req *http.Request + statusCode int + rangeVal string + want int64 + buf []byte +} + +var statusTests = []*testTransport{ + &testTransport{statusCode: 308, want: 0}, + &testTransport{statusCode: 308, rangeVal: "bytes=0-0", want: 1}, + &testTransport{statusCode: 308, rangeVal: "bytes=0-42", want: 43}, +} + +func TestTransferStatus(t *testing.T) { + for _, tr := range statusTests { + rx := &ResumableUpload{ + Client: &http.Client{Transport: tr}, + } + g, _, err := rx.transferStatus() + if err != nil { + t.Error(err) + } + if g != tr.want { + t.Errorf("transferStatus got %v, want %v", g, tr.want) + } + } +} + +func (t *interruptedTransport) RoundTrip(req *http.Request) (*http.Response, error) { + t.req = req + if rng := req.Header.Get("Content-Range"); rng != "" && !strings.HasPrefix(rng, "bytes */") { + t.interruptCount += 1 + if t.interruptCount%7 == 0 { // Respond with a "service unavailable" error + res := &http.Response{ + StatusCode: http.StatusServiceUnavailable, + Header: http.Header{}, + } + t.rangeVal = fmt.Sprintf("bytes=0-%v", len(t.buf)-1) // Set the response for next time + return res, nil + } + m := contentRangeRE.FindStringSubmatch(rng) + if len(m) != 4 { + return nil, fmt.Errorf("unable to parse content range: %v", rng) + } + start, err := strconv.ParseInt(m[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("unable to parse content range: %v", rng) + } + end, err := strconv.ParseInt(m[2], 10, 64) + if err != nil { + return nil, fmt.Errorf("unable to parse content range: %v", rng) + } + totalSize, err := strconv.ParseInt(m[3], 10, 64) + if err != nil { + return nil, fmt.Errorf("unable to parse content range: %v", rng) + } + partialSize := end - start + 1 + buf, err := ioutil.ReadAll(req.Body) + if err != nil || int64(len(buf)) != partialSize { + return nil, fmt.Errorf("unable to read %v bytes from request data, n=%v: %v", partialSize, len(buf), err) + } + t.buf = append(t.buf, buf...) + if totalSize == end+1 { + t.statusCode = 200 // signify completion of transfer + } + } + f := ioutil.NopCloser(unexpectedReader{}) + res := &http.Response{ + Body: f, + StatusCode: t.statusCode, + Header: http.Header{}, + } + if t.rangeVal != "" { + res.Header.Set("Range", t.rangeVal) + } + return res, nil +} + +type interruptedTransport struct { + req *http.Request + statusCode int + rangeVal string + interruptCount int + buf []byte + progressBuf string +} + +func (tr *interruptedTransport) ProgressUpdate(current, total int64) { + tr.progressBuf += fmt.Sprintf("%v, %v\n", current, total) +} + +func TestInterruptedTransferChunks(t *testing.T) { + f, err := os.Open("googleapi.go") + if err != nil { + t.Fatalf("unable to open googleapi.go: %v", err) + } + defer f.Close() + slurp, err := ioutil.ReadAll(f) + if err != nil { + t.Fatalf("unable to slurp file: %v", err) + } + st, err := f.Stat() + if err != nil { + t.Fatalf("unable to stat googleapi.go: %v", err) + } + tr := &interruptedTransport{ + statusCode: 308, + buf: make([]byte, 0, st.Size()), + } + oldChunkSize := chunkSize + defer func() { chunkSize = oldChunkSize }() + chunkSize = 100 // override to process small chunks for test. + + sleep = func(time.Duration) {} // override time.Sleep + rx := &ResumableUpload{ + Client: &http.Client{Transport: tr}, + Media: f, + MediaType: "text/plain", + ContentLength: st.Size(), + Callback: tr.ProgressUpdate, + } + res, err := rx.Upload(context.Background()) + if err != nil || res == nil || res.StatusCode != http.StatusOK { + if res == nil { + t.Errorf("transferChunks not successful, res=nil: %v", err) + } else { + t.Errorf("transferChunks not successful, statusCode=%v: %v", res.StatusCode, err) + } + } + if len(tr.buf) != len(slurp) || bytes.Compare(tr.buf, slurp) != 0 { + t.Errorf("transfered file corrupted:\ngot %s\nwant %s", tr.buf, slurp) + } + w := "" + for i := chunkSize; i <= st.Size(); i += chunkSize { + w += fmt.Sprintf("%v, %v\n", i, st.Size()) + } + if st.Size()%chunkSize != 0 { + w += fmt.Sprintf("%v, %v\n", st.Size(), st.Size()) + } + if tr.progressBuf != w { + t.Errorf("progress update error, got %v, want %v", tr.progressBuf, w) + } +} + +func TestCancelUpload(t *testing.T) { + f, err := os.Open("googleapi.go") + if err != nil { + t.Fatalf("unable to open googleapi.go: %v", err) + } + defer f.Close() + st, err := f.Stat() + if err != nil { + t.Fatalf("unable to stat googleapi.go: %v", err) + } + tr := &interruptedTransport{ + statusCode: 308, + buf: make([]byte, 0, st.Size()), + } + oldChunkSize := chunkSize + defer func() { chunkSize = oldChunkSize }() + chunkSize = 100 // override to process small chunks for test. + + sleep = func(time.Duration) {} // override time.Sleep + rx := &ResumableUpload{ + Client: &http.Client{Transport: tr}, + Media: f, + MediaType: "text/plain", + ContentLength: st.Size(), + Callback: tr.ProgressUpdate, + } + ctx, cancelFunc := context.WithCancel(context.Background()) + cancelFunc() // stop the upload that hasn't started yet + res, err := rx.Upload(ctx) + if err == nil || res == nil || res.StatusCode != http.StatusRequestTimeout { + if res == nil { + t.Errorf("transferChunks not successful, got res=nil, err=%v, want StatusRequestTimeout", err) + } else { + t.Errorf("transferChunks not successful, got statusCode=%v, err=%v, want StatusRequestTimeout", res.StatusCode, err) + } + } +} diff --git a/drivers/google/compute_util.go b/drivers/google/compute_util.go index aa5365c59c..1e4d7771bc 100644 --- a/drivers/google/compute_util.go +++ b/drivers/google/compute_util.go @@ -20,6 +20,7 @@ type ComputeUtil struct { project string diskTypeURL string address string + preemptible bool service *raw.Service zoneURL string authTokenPath string @@ -53,6 +54,7 @@ func newComputeUtil(driver *Driver) (*ComputeUtil, error) { project: driver.Project, diskTypeURL: driver.DiskType, address: driver.Address, + preemptible: driver.Preemptible, service: service, zoneURL: apiURL + driver.Project + "/zones/" + driver.Zone, globalURL: apiURL + driver.Project + "/global", @@ -180,6 +182,9 @@ func (c *ComputeUtil) createInstance(d *Driver) error { Scopes: strings.Split(d.Scopes, ","), }, }, + Scheduling: &raw.Scheduling{ + Preemptible: c.preemptible, + }, } if c.address != "" { diff --git a/drivers/google/google.go b/drivers/google/google.go index e4d1973dbc..cd936014bc 100644 --- a/drivers/google/google.go +++ b/drivers/google/google.go @@ -17,6 +17,7 @@ type Driver struct { MachineType string DiskType string Address string + Preemptible bool Scopes string DiskSize int AuthTokenPath string @@ -85,6 +86,11 @@ func GetCreateFlags() []cli.Flag { Usage: "GCE Instance External IP", EnvVar: "GOOGLE_ADDRESS", }, + cli.BoolFlag{ + Name: "google-preemptible", + Usage: "GCE Instance Preemptibility", + EnvVar: "GOOGLE_PREEMPTIBLE", + }, } } @@ -118,6 +124,7 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { d.DiskSize = flags.Int("google-disk-size") d.DiskType = flags.String("google-disk-type") d.Address = flags.String("google-address") + d.Preemptible = flags.Bool("google-preemptible") d.AuthTokenPath = flags.String("google-auth-token") d.Project = flags.String("google-project") d.Scopes = flags.String("google-scopes") From 8aa5e0771a59bcffc6ff577600b89c58ce782038 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Sun, 19 Jul 2015 16:30:15 +0200 Subject: [PATCH 06/36] Support static IP by name on GCE Signed-off-by: David Gageot --- drivers/google/compute_util.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/drivers/google/compute_util.go b/drivers/google/compute_util.go index 1e4d7771bc..159788c56b 100644 --- a/drivers/google/compute_util.go +++ b/drivers/google/compute_util.go @@ -4,6 +4,7 @@ import ( "fmt" "io/ioutil" "net/url" + "regexp" "strings" "time" @@ -88,6 +89,31 @@ func (c *ComputeUtil) deleteDisk() error { return c.waitForRegionalOp(op.Name) } +// staticAddress returns the external static IP address. +func (c *ComputeUtil) staticAddress() (string, error) { + // is the address a name? + isName, err := regexp.MatchString("[a-z]([-a-z0-9]*[a-z0-9])?", c.address) + if err != nil { + return "", err + } + + if (!isName) { + return c.address, nil + } + + // resolve the address by name + externalAddress, err := c.service.Addresses.Get(c.project, c.region(), c.address).Do() + if err != nil { + return "", err + } + + return externalAddress.Address, nil +} + +func (c *ComputeUtil) region() (string) { + return c.zone[:len(c.zone)-2] +} + func (c *ComputeUtil) firewallRule() (*raw.Firewall, error) { return c.service.Firewalls.Get(c.project, firewallRule).Do() } @@ -188,7 +214,12 @@ func (c *ComputeUtil) createInstance(d *Driver) error { } if c.address != "" { - instance.NetworkInterfaces[0].AccessConfigs[0].NatIP = c.address + staticAddress, err := c.staticAddress() + if err != nil { + return err + } + + instance.NetworkInterfaces[0].AccessConfigs[0].NatIP = staticAddress } disk, err := c.disk() From 60be68c73cf2506c064dda5ff0fe4b7909a2d01a Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Sun, 19 Jul 2015 15:44:46 -0700 Subject: [PATCH 07/36] Updating to point to latest build Signed-off-by: Mary Anthony --- docs/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index 568883e88f..f993586075 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,4 +1,4 @@ -FROM docs/base:hugo +FROM docs/base:latest MAINTAINER Mary Anthony (@moxiegirl) # to get the git info for this repo From 6db82141f0e70616b870ee6af9d1cf20f58cc27e Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Fri, 10 Jul 2015 11:07:49 -0700 Subject: [PATCH 08/36] Bail on failed SSH command with information about what failed Signed-off-by: Nathan LeClaire --- drivers/utils.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/utils.go b/drivers/utils.go index 5f6b6d620e..4c73076f61 100644 --- a/drivers/utils.go +++ b/drivers/utils.go @@ -2,12 +2,17 @@ package drivers import ( "fmt" + "strings" "github.com/docker/machine/log" "github.com/docker/machine/ssh" "github.com/docker/machine/utils" ) +const ( + ErrExitCode255 = "255" +) + func GetSSHClientFromDriver(d Driver) (ssh.Client, error) { addr, err := d.GetSSHHostname() if err != nil { @@ -28,6 +33,10 @@ func GetSSHClientFromDriver(d Driver) (ssh.Client, error) { } +func isErr255Exit(err error) bool { + return strings.Contains(err.Error(), ErrExitCode255) +} + func RunSSHCommandFromDriver(d Driver, command string) (string, error) { client, err := GetSSHClientFromDriver(d) if err != nil { @@ -38,6 +47,12 @@ func RunSSHCommandFromDriver(d Driver, command string) (string, error) { output, err := client.Output(command) log.Debugf("SSH cmd err, output: %v: %s", err, output) + if err != nil && !isErr255Exit(err) { + log.Error("SSH cmd error!") + log.Errorf("command: %s", command) + log.Errorf("err : %v", err) + log.Fatalf("output : %s", output) + } return output, err } From 9c3ec2e92a4e01d0994d5c8f348d31507b8be6c8 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Wed, 22 Jul 2015 13:35:41 -0400 Subject: [PATCH 09/36] rename changelog Signed-off-by: Evan Hazlett --- CHANGES.md => CHANGELOG.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename CHANGES.md => CHANGELOG.md (100%) diff --git a/CHANGES.md b/CHANGELOG.md similarity index 100% rename from CHANGES.md rename to CHANGELOG.md From 79b9450fef8a2ec886a1479262fb6bf1704e96bd Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Fri, 17 Jul 2015 16:48:11 -0700 Subject: [PATCH 10/36] Revert "Remove flat (0.1.0) => nested (0.2.0) migration code" This reverts commit 2f831887c6c37fd58f847669ba491e7accb9bdc9. Signed-off-by: Nathan LeClaire --- libmachine/filestore.go | 3 +- libmachine/host.go | 15 ++++- libmachine/migrate.go | 122 +++++++++++++++++++++++++++++++++++++ libmachine/migrate_test.go | 119 ++++++++++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 libmachine/migrate.go create mode 100644 libmachine/migrate_test.go diff --git a/libmachine/filestore.go b/libmachine/filestore.go index eba66100ee..7cdebb6394 100644 --- a/libmachine/filestore.go +++ b/libmachine/filestore.go @@ -35,7 +35,8 @@ func (s Filestore) loadHost(name string) (*Host, error) { return nil, err } - return host, nil + h := FillNestedHost(host) + return h, nil } func (s Filestore) GetPath() string { diff --git a/libmachine/host.go b/libmachine/host.go index 59a62845fe..83a1b46a61 100644 --- a/libmachine/host.go +++ b/libmachine/host.go @@ -33,6 +33,17 @@ type Host struct { Driver drivers.Driver StorePath string HostOptions *HostOptions + + // deprecated options; these are left to assist in config migrations + SwarmHost string + SwarmMaster bool + SwarmDiscovery string + CaCertPath string + PrivateKeyPath string + ServerCertPath string + ServerKeyPath string + ClientCertPath string + ClientKeyPath string } type HostOptions struct { @@ -282,7 +293,9 @@ func (h *Host) LoadConfig() error { return err } - authOptions := hostMetadata.HostOptions.AuthOptions + meta := FillNestedHostMetadata(&hostMetadata) + + authOptions := meta.HostOptions.AuthOptions driver, err := drivers.NewDriver(hostMetadata.DriverName, h.Name, h.StorePath, authOptions.CaCertPath, authOptions.PrivateKeyPath) if err != nil { diff --git a/libmachine/migrate.go b/libmachine/migrate.go new file mode 100644 index 0000000000..69f062e016 --- /dev/null +++ b/libmachine/migrate.go @@ -0,0 +1,122 @@ +package libmachine + +import ( + "path/filepath" + + "github.com/docker/machine/libmachine/auth" + "github.com/docker/machine/libmachine/engine" + "github.com/docker/machine/libmachine/swarm" + "github.com/docker/machine/utils" +) + +// In the 0.0.1 => 0.0.2 transition, the JSON representation of +// machines changed from a "flat" to a more "nested" structure +// for various options and configuration settings. To preserve +// compatibility with existing machines, these migration functions +// have been introduced. They preserve backwards compat at the expense +// of some duplicated information. + +// validates host config and modifies if needed +// this is used for configuration updates +func FillNestedHost(host *Host) *Host { + certInfo := getCertInfoFromHost(host) + + if host.HostOptions == nil { + host.HostOptions = &HostOptions{} + } + if host.HostOptions.EngineOptions == nil { + host.HostOptions.EngineOptions = &engine.EngineOptions{} + } + + if host.HostOptions.SwarmOptions == nil { + host.HostOptions.SwarmOptions = &swarm.SwarmOptions{ + Address: "", + Discovery: host.SwarmDiscovery, + Host: host.SwarmHost, + Master: host.SwarmMaster, + } + } + + host.HostOptions.AuthOptions = &auth.AuthOptions{ + StorePath: host.StorePath, + CaCertPath: certInfo.CaCertPath, + CaCertRemotePath: "", + ServerCertPath: certInfo.ServerCertPath, + ServerKeyPath: certInfo.ServerKeyPath, + ClientKeyPath: certInfo.ClientKeyPath, + ServerCertRemotePath: "", + ServerKeyRemotePath: "", + PrivateKeyPath: certInfo.CaKeyPath, + ClientCertPath: certInfo.ClientCertPath, + } + + return host +} + +// fills nested host metadata and modifies if needed +// this is used for configuration updates +func FillNestedHostMetadata(m *HostMetadata) *HostMetadata { + if m.HostOptions.EngineOptions == nil { + m.HostOptions.EngineOptions = &engine.EngineOptions{} + } + + if m.HostOptions.AuthOptions == nil { + m.HostOptions.AuthOptions = &auth.AuthOptions{ + StorePath: m.StorePath, + CaCertPath: m.CaCertPath, + CaCertRemotePath: "", + ServerCertPath: m.ServerCertPath, + ServerKeyPath: m.ServerKeyPath, + ClientKeyPath: "", + ServerCertRemotePath: "", + ServerKeyRemotePath: "", + PrivateKeyPath: m.PrivateKeyPath, + ClientCertPath: m.ClientCertPath, + } + } + + return m +} + +func getCertInfoFromHost(h *Host) CertPathInfo { + // setup cert paths + caCertPath := h.CaCertPath + caKeyPath := h.PrivateKeyPath + clientCertPath := h.ClientCertPath + clientKeyPath := h.ClientKeyPath + serverCertPath := h.ServerCertPath + serverKeyPath := h.ServerKeyPath + + if caCertPath == "" { + caCertPath = filepath.Join(utils.GetMachineCertDir(), "ca.pem") + } + + if caKeyPath == "" { + caKeyPath = filepath.Join(utils.GetMachineCertDir(), "ca-key.pem") + } + + if clientCertPath == "" { + clientCertPath = filepath.Join(utils.GetMachineCertDir(), "cert.pem") + } + + if clientKeyPath == "" { + clientKeyPath = filepath.Join(utils.GetMachineCertDir(), "key.pem") + } + + if serverCertPath == "" { + serverCertPath = filepath.Join(utils.GetMachineCertDir(), "server.pem") + } + + if serverKeyPath == "" { + serverKeyPath = filepath.Join(utils.GetMachineCertDir(), "server-key.pem") + } + + return CertPathInfo{ + CaCertPath: caCertPath, + CaKeyPath: caKeyPath, + ClientCertPath: clientCertPath, + ClientKeyPath: clientKeyPath, + ServerCertPath: serverCertPath, + ServerKeyPath: serverKeyPath, + } +} diff --git a/libmachine/migrate_test.go b/libmachine/migrate_test.go new file mode 100644 index 0000000000..57c726cf1f --- /dev/null +++ b/libmachine/migrate_test.go @@ -0,0 +1,119 @@ +package libmachine + +import ( + "os" + "reflect" + "testing" + + "github.com/docker/machine/libmachine/auth" + "github.com/docker/machine/libmachine/engine" + "github.com/docker/machine/libmachine/swarm" +) + +func TestFillNestedHost(t *testing.T) { + os.Setenv("MACHINE_STORAGE_PATH", "/tmp/migration") + originalHost := &Host{ + HostOptions: nil, + SwarmDiscovery: "token://foobar", + SwarmHost: "1.2.3.4:2376", + SwarmMaster: true, + CaCertPath: "", + PrivateKeyPath: "", + ClientCertPath: "", + ClientKeyPath: "", + ServerCertPath: "", + ServerKeyPath: "", + } + hostOptions := &HostOptions{ + SwarmOptions: &swarm.SwarmOptions{ + Master: true, + Discovery: "token://foobar", + Host: "1.2.3.4:2376", + }, + AuthOptions: &auth.AuthOptions{ + CaCertPath: "/tmp/migration/certs/ca.pem", + PrivateKeyPath: "/tmp/migration/certs/ca-key.pem", + ClientCertPath: "/tmp/migration/certs/cert.pem", + ClientKeyPath: "/tmp/migration/certs/key.pem", + ServerCertPath: "/tmp/migration/certs/server.pem", + ServerKeyPath: "/tmp/migration/certs/server-key.pem", + }, + EngineOptions: &engine.EngineOptions{}, + } + + expectedHost := &Host{ + SwarmHost: "1.2.3.4:2376", + SwarmDiscovery: "token://foobar", + SwarmMaster: true, + HostOptions: hostOptions, + } + + host := FillNestedHost(originalHost) + + if !reflect.DeepEqual(host, expectedHost) { + t.Logf("\n%+v\n%+v", host, expectedHost) + t.Logf("\n%+v\n%+v", host.HostOptions, expectedHost.HostOptions) + t.Fatal("Expected these structs to be equal, they were different") + } +} + +func TestFillNestedHostMetadata(t *testing.T) { + metadata := &HostMetadata{ + HostOptions: HostOptions{ + EngineOptions: nil, + AuthOptions: nil, + }, + StorePath: "/tmp/store", + CaCertPath: "/tmp/store/certs/ca.pem", + ServerCertPath: "/tmp/store/certs/server.pem", + } + expectedAuthOptions := &auth.AuthOptions{ + StorePath: "/tmp/store", + CaCertPath: "/tmp/store/certs/ca.pem", + ServerCertPath: "/tmp/store/certs/server.pem", + } + + expectedMetadata := &HostMetadata{ + HostOptions: HostOptions{ + EngineOptions: &engine.EngineOptions{}, + AuthOptions: expectedAuthOptions, + }, + StorePath: "/tmp/store", + CaCertPath: "/tmp/store/certs/ca.pem", + ServerCertPath: "/tmp/store/certs/server.pem", + } + + m := FillNestedHostMetadata(metadata) + + if !reflect.DeepEqual(m, expectedMetadata) { + t.Logf("\n%+v\n%+v", m, expectedMetadata) + t.Fatal("Expected these structs to be equal, they were different") + } +} + +// Tests a function which "prefills" certificate information for a host +// due to a schema migration from "flat" to a "nested" structure. +func TestGetCertInfoFromHost(t *testing.T) { + os.Setenv("MACHINE_STORAGE_PATH", "/tmp/migration") + host := &Host{ + CaCertPath: "", + PrivateKeyPath: "", + ClientCertPath: "", + ClientKeyPath: "", + ServerCertPath: "", + ServerKeyPath: "", + } + expectedCertInfo := CertPathInfo{ + CaCertPath: "/tmp/migration/certs/ca.pem", + CaKeyPath: "/tmp/migration/certs/ca-key.pem", + ClientCertPath: "/tmp/migration/certs/cert.pem", + ClientKeyPath: "/tmp/migration/certs/key.pem", + ServerCertPath: "/tmp/migration/certs/server.pem", + ServerKeyPath: "/tmp/migration/certs/server-key.pem", + } + certInfo := getCertInfoFromHost(host) + if !reflect.DeepEqual(expectedCertInfo, certInfo) { + t.Log("\n\n\n", expectedCertInfo, "\n\n\n", certInfo) + t.Fatal("Expected these structs to be equal, they were different") + } +} From 01bf2ea2d75249a8e7ae5280963e7b348aa1663c Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Thu, 16 Jul 2015 20:38:22 -0400 Subject: [PATCH 11/36] use yum repos for redhat like distros Signed-off-by: Evan Hazlett --- libmachine/provision/centos.go | 8 ---- libmachine/provision/fedora.go | 8 ---- libmachine/provision/redhat.go | 85 +++++++++++++++++++++++++--------- 3 files changed, 63 insertions(+), 38 deletions(-) diff --git a/libmachine/provision/centos.go b/libmachine/provision/centos.go index 8226cd1ab9..b348e005e0 100644 --- a/libmachine/provision/centos.go +++ b/libmachine/provision/centos.go @@ -4,13 +4,6 @@ import ( "github.com/docker/machine/drivers" ) -const ( - // TODO: eventually the RPM install process will be integrated - // into the get.docker.com install script; for now - // we install via vendored RPMs - dockerCentosRPMPath = "https://get.docker.com/rpm/1.7.0/centos-7/RPMS/x86_64/docker-engine-1.7.0-1.el7.centos.x86_64.rpm" -) - func init() { Register("Centos", &RegisteredProvisioner{ New: NewCentosProvisioner, @@ -28,7 +21,6 @@ func NewCentosProvisioner(d drivers.Driver) Provisioner { p := &CentosProvisioner{ RedHatProvisioner{ GenericProvisioner: g, - DockerRPMPath: dockerCentosRPMPath, }, } return p diff --git a/libmachine/provision/fedora.go b/libmachine/provision/fedora.go index 1b71cb6232..a201fe7a9d 100644 --- a/libmachine/provision/fedora.go +++ b/libmachine/provision/fedora.go @@ -4,13 +4,6 @@ import ( "github.com/docker/machine/drivers" ) -const ( - // TODO: eventually the RPM install process will be integrated - // into the get.docker.com install script; for now - // we install via vendored RPMs - dockerFedoraRPMPath = "https://get.docker.com/rpm/1.7.0/fedora-21/RPMS/x86_64/docker-engine-1.7.0-1.fc21.x86_64.rpm" -) - func init() { Register("Fedora", &RegisteredProvisioner{ New: NewFedoraProvisioner, @@ -28,7 +21,6 @@ func NewFedoraProvisioner(d drivers.Driver) Provisioner { p := &FedoraProvisioner{ RedHatProvisioner{ GenericProvisioner: g, - DockerRPMPath: dockerFedoraRPMPath, }, } return p diff --git a/libmachine/provision/redhat.go b/libmachine/provision/redhat.go index 271e5d5ee1..d6b8afcf95 100644 --- a/libmachine/provision/redhat.go +++ b/libmachine/provision/redhat.go @@ -2,6 +2,7 @@ package provision import ( "bytes" + "errors" "fmt" "text/template" @@ -15,13 +16,31 @@ import ( "github.com/docker/machine/utils" ) -const ( - // TODO: eventually the RPM install process will be integrated - // into the get.docker.com install script; for now - // we install via vendored RPMs - dockerRHELRPMPath = "https://get.docker.com/rpm/1.7.0/centos-7/RPMS/x86_64/docker-engine-1.7.0-1.el7.centos.x86_64.rpm" +var ( + ErrUnknownYumOsRelease = errors.New("unknown OS for Yum repository") + + packageListTemplate = `[docker] +name=Docker Stable Repository +baseurl=https://yum.dockerproject.org/repo/main/{{.OsRelease}}/{{.OsReleaseVersion}} +priority=1 +enabled=1 +gpgkey=https://yum.dockerproject.org/gpg +` + engineConfigTemplate = `[Service] +ExecStart=/usr/bin/docker -d -H tcp://0.0.0.0:{{.DockerPort}} -H unix:///var/run/docker.sock --storage-driver {{.EngineOptions.StorageDriver}} --tlsverify --tlscacert {{.AuthOptions.CaCertRemotePath}} --tlscert {{.AuthOptions.ServerCertRemotePath}} --tlskey {{.AuthOptions.ServerKeyRemotePath}} {{ range .EngineOptions.Labels }}--label {{.}} {{ end }}{{ range .EngineOptions.InsecureRegistry }}--insecure-registry {{.}} {{ end }}{{ range .EngineOptions.RegistryMirror }}--registry-mirror {{.}} {{ end }}{{ range .EngineOptions.ArbitraryFlags }}--{{.}} {{ end }} +MountFlags=slave +LimitNOFILE=1048576 +LimitNPROC=1048576 +LimitCORE=infinity +Environment={{range .EngineOptions.Env}}{{ printf "%q" . }} {{end}} +` ) +type PackageListInfo struct { + OsRelease string + OsReleaseVersion string +} + func init() { Register("RedHat", &RegisteredProvisioner{ New: NewRedHatProvisioner, @@ -39,13 +58,11 @@ func NewRedHatProvisioner(d drivers.Driver) Provisioner { }, Driver: d, }, - DockerRPMPath: dockerRHELRPMPath, } } type RedHatProvisioner struct { GenericProvisioner - DockerRPMPath string } func (provisioner *RedHatProvisioner) SSHCommand(args string) (string, error) { @@ -155,7 +172,11 @@ func installDocker(provisioner *RedHatProvisioner) error { func (provisioner *RedHatProvisioner) installOfficialDocker() error { log.Debug("installing docker") - if _, err := provisioner.SSHCommand(fmt.Sprintf("sudo yum install -y --nogpgcheck %s", provisioner.DockerRPMPath)); err != nil { + if err := provisioner.ConfigurePackageList(); err != nil { + return err + } + + if _, err := provisioner.SSHCommand("sudo yum install -y docker-engine"); err != nil { return err } @@ -230,25 +251,12 @@ func (provisioner *RedHatProvisioner) GenerateDockerOptions(dockerPort int) (*Do configPath = provisioner.DaemonOptionsFile ) - // remove existing - //if _, err := provisioner.SSHCommand(fmt.Sprintf("sudo rm %s", configPath)); err != nil { - // return nil, err - //} - driverNameLabel := fmt.Sprintf("provider=%s", provisioner.Driver.DriverName()) provisioner.EngineOptions.Labels = append(provisioner.EngineOptions.Labels, driverNameLabel) // systemd / redhat will not load options if they are on newlines // instead, it just continues with a different set of options; yeah... - engineConfigTmpl := `[Service] -ExecStart=/usr/bin/docker -d -H tcp://0.0.0.0:{{.DockerPort}} -H unix:///var/run/docker.sock --storage-driver {{.EngineOptions.StorageDriver}} --tlsverify --tlscacert {{.AuthOptions.CaCertRemotePath}} --tlscert {{.AuthOptions.ServerCertRemotePath}} --tlskey {{.AuthOptions.ServerKeyRemotePath}} {{ range .EngineOptions.Labels }}--label {{.}} {{ end }}{{ range .EngineOptions.InsecureRegistry }}--insecure-registry {{.}} {{ end }}{{ range .EngineOptions.RegistryMirror }}--registry-mirror {{.}} {{ end }}{{ range .EngineOptions.ArbitraryFlags }}--{{.}} {{ end }} -MountFlags=slave -LimitNOFILE=1048576 -LimitNPROC=1048576 -LimitCORE=infinity -Environment={{range .EngineOptions.Env}}{{ printf "%q" . }} {{end}} -` - t, err := template.New("engineConfig").Parse(engineConfigTmpl) + t, err := template.New("engineConfig").Parse(engineConfigTemplate) if err != nil { return nil, err } @@ -268,3 +276,36 @@ Environment={{range .EngineOptions.Env}}{{ printf "%q" . }} {{end}} EngineOptionsPath: daemonOptsDir, }, nil } + +func (provisioner *RedHatProvisioner) ConfigurePackageList() error { + packageListInfo := &PackageListInfo{} + switch provisioner.OsReleaseId { + case "rhel", "centos": + // rhel and centos both use the "centos" repo + packageListInfo.OsRelease = "centos" + packageListInfo.OsReleaseVersion = "7" + case "fedora": + packageListInfo.OsRelease = "fedora" + packageListInfo.OsReleaseVersion = "22" + default: + return ErrUnknownYumOsRelease + } + + t, err := template.New("packageList").Parse(packageListTemplate) + if err != nil { + return err + } + + var buf bytes.Buffer + + if err := t.Execute(&buf, packageListInfo); err != nil { + return err + } + + packageCmd := fmt.Sprintf("echo \"%s\" | sudo tee /etc/yum.repos.d/docker.repo", buf.String()) + if _, err := provisioner.SSHCommand(packageCmd); err != nil { + return err + } + + return nil +} From b43a76deeccf1ae387a012bca132e8ddf6095533 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Fri, 17 Jul 2015 12:13:21 -0400 Subject: [PATCH 12/36] add GetOsReleaseInfo to provisioner to assist in testing; add tests for rpm repo Signed-off-by: Evan Hazlett --- libmachine/provision/boot2docker.go | 4 ++++ libmachine/provision/centos_test.go | 28 ++++++++++++++++++++++++++++ libmachine/provision/fedora_test.go | 28 ++++++++++++++++++++++++++++ libmachine/provision/generic.go | 4 ++++ libmachine/provision/provisioner.go | 3 +++ libmachine/provision/redhat.go | 25 +++++++++++++++++++++---- libmachine/provision/redhat_test.go | 28 ++++++++++++++++++++++++++++ 7 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 libmachine/provision/centos_test.go create mode 100644 libmachine/provision/fedora_test.go create mode 100644 libmachine/provision/redhat_test.go diff --git a/libmachine/provision/boot2docker.go b/libmachine/provision/boot2docker.go index 1486181b7b..485cfb852e 100644 --- a/libmachine/provision/boot2docker.go +++ b/libmachine/provision/boot2docker.go @@ -172,6 +172,10 @@ func (provisioner *Boot2DockerProvisioner) SetOsReleaseInfo(info *OsRelease) { provisioner.OsReleaseInfo = info } +func (provisioner *Boot2DockerProvisioner) GetOsReleaseInfo() (*OsRelease, error) { + return provisioner.OsReleaseInfo, nil +} + func (provisioner *Boot2DockerProvisioner) Provision(swarmOptions swarm.SwarmOptions, authOptions auth.AuthOptions, engineOptions engine.EngineOptions) error { provisioner.SwarmOptions = swarmOptions provisioner.AuthOptions = authOptions diff --git a/libmachine/provision/centos_test.go b/libmachine/provision/centos_test.go new file mode 100644 index 0000000000..df6bfc9653 --- /dev/null +++ b/libmachine/provision/centos_test.go @@ -0,0 +1,28 @@ +package provision + +import ( + "regexp" + "testing" +) + +func TestCentosGenerateYumRepoList(t *testing.T) { + info := &OsRelease{ + Id: "centos", + } + p := NewCentosProvisioner(nil) + p.SetOsReleaseInfo(info) + + buf, err := generateYumRepoList(p) + if err != nil { + t.Fatal(err) + } + + m, err := regexp.MatchString(".*centos/7.*", buf.String()) + if err != nil { + t.Fatal(err) + } + + if !m { + t.Fatalf("expected match for centos/7") + } +} diff --git a/libmachine/provision/fedora_test.go b/libmachine/provision/fedora_test.go new file mode 100644 index 0000000000..8bc0180393 --- /dev/null +++ b/libmachine/provision/fedora_test.go @@ -0,0 +1,28 @@ +package provision + +import ( + "regexp" + "testing" +) + +func TestFedoraGenerateYumRepoList(t *testing.T) { + info := &OsRelease{ + Id: "fedora", + } + p := NewCentosProvisioner(nil) + p.SetOsReleaseInfo(info) + + buf, err := generateYumRepoList(p) + if err != nil { + t.Fatal(err) + } + + m, err := regexp.MatchString(".*fedora/22.*", buf.String()) + if err != nil { + t.Fatal(err) + } + + if !m { + t.Fatalf("expected match for fedora/22") + } +} diff --git a/libmachine/provision/generic.go b/libmachine/provision/generic.go index 04ab390029..1b174f24b1 100644 --- a/libmachine/provision/generic.go +++ b/libmachine/provision/generic.go @@ -68,6 +68,10 @@ func (provisioner *GenericProvisioner) SetOsReleaseInfo(info *OsRelease) { provisioner.OsReleaseInfo = info } +func (provisioner *GenericProvisioner) GetOsReleaseInfo() (*OsRelease, error) { + return provisioner.OsReleaseInfo, nil +} + func (provisioner *GenericProvisioner) GenerateDockerOptions(dockerPort int) (*DockerOptions, error) { var ( engineCfg bytes.Buffer diff --git a/libmachine/provision/provisioner.go b/libmachine/provision/provisioner.go index 0ed994b155..3d0184432a 100644 --- a/libmachine/provision/provisioner.go +++ b/libmachine/provision/provisioner.go @@ -56,6 +56,9 @@ type Provisioner interface { // Set the OS Release info depending on how it's represented // internally SetOsReleaseInfo(info *OsRelease) + + // Get the OS Release info for the current provisioner + GetOsReleaseInfo() (*OsRelease, error) } // Detection diff --git a/libmachine/provision/redhat.go b/libmachine/provision/redhat.go index d6b8afcf95..ec199da6fb 100644 --- a/libmachine/provision/redhat.go +++ b/libmachine/provision/redhat.go @@ -277,9 +277,15 @@ func (provisioner *RedHatProvisioner) GenerateDockerOptions(dockerPort int) (*Do }, nil } -func (provisioner *RedHatProvisioner) ConfigurePackageList() error { +func generateYumRepoList(provisioner Provisioner) (*bytes.Buffer, error) { packageListInfo := &PackageListInfo{} - switch provisioner.OsReleaseId { + + releaseInfo, err := provisioner.GetOsReleaseInfo() + if err != nil { + return nil, err + } + + switch releaseInfo.Id { case "rhel", "centos": // rhel and centos both use the "centos" repo packageListInfo.OsRelease = "centos" @@ -288,20 +294,31 @@ func (provisioner *RedHatProvisioner) ConfigurePackageList() error { packageListInfo.OsRelease = "fedora" packageListInfo.OsReleaseVersion = "22" default: - return ErrUnknownYumOsRelease + return nil, ErrUnknownYumOsRelease } t, err := template.New("packageList").Parse(packageListTemplate) if err != nil { - return err + return nil, err } var buf bytes.Buffer if err := t.Execute(&buf, packageListInfo); err != nil { + return nil, err + } + + return &buf, nil +} + +func (provisioner *RedHatProvisioner) ConfigurePackageList() error { + buf, err := generateYumRepoList(provisioner) + if err != nil { return err } + // we cannot use %q here as it combines the newlines in the formatting + // on transport causing yum to not use the repo packageCmd := fmt.Sprintf("echo \"%s\" | sudo tee /etc/yum.repos.d/docker.repo", buf.String()) if _, err := provisioner.SSHCommand(packageCmd); err != nil { return err diff --git a/libmachine/provision/redhat_test.go b/libmachine/provision/redhat_test.go new file mode 100644 index 0000000000..27d0f46706 --- /dev/null +++ b/libmachine/provision/redhat_test.go @@ -0,0 +1,28 @@ +package provision + +import ( + "regexp" + "testing" +) + +func TestRedHatGenerateYumRepoList(t *testing.T) { + info := &OsRelease{ + Id: "rhel", + } + p := NewRedHatProvisioner(nil) + p.SetOsReleaseInfo(info) + + buf, err := generateYumRepoList(p) + if err != nil { + t.Fatal(err) + } + + m, err := regexp.MatchString(".*centos/7.*", buf.String()) + if err != nil { + t.Fatal(err) + } + + if !m { + t.Fatalf("expected match for centos/7") + } +} From 6aca2c9faf48a94bd6edda16c63075cb4b40984e Mon Sep 17 00:00:00 2001 From: Joey Geiger Date: Wed, 22 Jul 2015 11:17:31 -0600 Subject: [PATCH 13/36] Update install-machine.md to reference 0.3.1 Update the docs to include links to newest release. Signed-off-by: jgeiger --- docs/install-machine.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/install-machine.md b/docs/install-machine.md index 6adb52fe06..89e87a1aa9 100644 --- a/docs/install-machine.md +++ b/docs/install-machine.md @@ -14,7 +14,7 @@ weight=3 Docker Machine is supported on Windows, OS X, and Linux and is installable as one standalone binary. The links to the binaries for the various platforms and architectures are available at the [Github -Release](https://github.com/docker/machine/releases/tag/v0.3.0) page. +Release](https://github.com/docker/machine/releases/tag/v0.3.1) page. ### OS X and Linux @@ -24,7 +24,7 @@ To install on OS X or Linux, download the proper binary to somewhere in your most OS X machines these commands should suffice: ``` -$ curl -L https://github.com/docker/machine/releases/download/v0.3.0/docker-machine_darwin-amd64 > /usr/local/bin/docker-machine +$ curl -L https://github.com/docker/machine/releases/download/v0.3.1/docker-machine_darwin-amd64 > /usr/local/bin/docker-machine $ chmod +x /usr/local/bin/docker-machine ``` @@ -34,7 +34,7 @@ Now you should be able to check the version with `docker-machine -v`: ``` $ docker-machine -v -machine version 0.3.0 +machine version 0.3.1 ``` In order to run Docker commands on your machines without having to use SSH, make @@ -66,12 +66,12 @@ $ curl -L https://get.docker.com/builds/Windows/x86_64/docker-latest.exe > /bin/ Next, install the Docker Machine binary: ``` -$ curl -L https://github.com/docker/machine/releases/download/v0.3.0/docker-machine_windows-amd64.exe > /bin/docker-machine +$ curl -L https://github.com/docker/machine/releases/download/v0.3.1/docker-machine_windows-amd64.exe > /bin/docker-machine ``` Now running `docker-machine` should work. ``` $ docker-machine -v -machine version 0.3.0 -``` \ No newline at end of file +machine version 0.3.1 +``` From ab31d52165a7781d3297278922b006970f4ada6e Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Wed, 15 Jul 2015 17:53:45 -0400 Subject: [PATCH 14/36] update vbox driver to work with new b2d Signed-off-by: Evan Hazlett vbox: lock down shared folders to user home dir Signed-off-by: Evan Hazlett use env var for user for cross platform Signed-off-by: Evan Hazlett vbox: remove sudo from setting ssh key for user Signed-off-by: Evan Hazlett wip: use homedir for share Signed-off-by: Evan Hazlett --- Godeps/Godeps.json | 5 + .../docker/docker/pkg/homedir/homedir.go | 39 ++ .../docker/docker/pkg/homedir/homedir_test.go | 24 ++ .../docker/libcontainer/user/MAINTAINERS | 2 + .../docker/libcontainer/user/lookup.go | 108 ++++++ .../docker/libcontainer/user/lookup_unix.go | 30 ++ .../libcontainer/user/lookup_unsupported.go | 21 ++ .../docker/libcontainer/user/user.go | 350 +++++++++++++++++ .../docker/libcontainer/user/user_test.go | 352 ++++++++++++++++++ drivers/virtualbox/virtualbox.go | 158 +++++--- drivers/virtualbox/virtualbox_test.go | 36 ++ libmachine/provision/boot2docker.go | 221 +---------- libmachine/provision/debian.go | 2 +- 13 files changed, 1090 insertions(+), 258 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir.go create mode 100644 Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir_test.go create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup.go create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go create mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index a551002a04..e270f2ecd3 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -46,6 +46,11 @@ "Comment": "v1.5.0", "Rev": "a8a31eff10544860d2188dddabdee4d727545796" }, + { + "ImportPath": "github.com/docker/docker/pkg/homedir", + "Comment": "v1.7.0", + "Rev": "0baf60984522744eed290348f33f396c046b2f3a" + }, { "ImportPath": "github.com/docker/docker/pkg/ioutils", "Comment": "v1.5.0", diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir.go new file mode 100644 index 0000000000..61137a8f5d --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir.go @@ -0,0 +1,39 @@ +package homedir + +import ( + "os" + "runtime" + + "github.com/docker/libcontainer/user" +) + +// Key returns the env var name for the user's home dir based on +// the platform being run on +func Key() string { + if runtime.GOOS == "windows" { + return "USERPROFILE" + } + return "HOME" +} + +// Get returns the home directory of the current user with the help of +// environment variables depending on the target operating system. +// Returned path should be used with "path/filepath" to form new paths. +func Get() string { + home := os.Getenv(Key()) + if home == "" && runtime.GOOS != "windows" { + if u, err := user.CurrentUser(); err == nil { + return u.Home + } + } + return home +} + +// GetShortcutString returns the string that is shortcut to user's home directory +// in the native shell of the platform running on. +func GetShortcutString() string { + if runtime.GOOS == "windows" { + return "%USERPROFILE%" // be careful while using in format functions + } + return "~" +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir_test.go new file mode 100644 index 0000000000..7a95cb2bd7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir_test.go @@ -0,0 +1,24 @@ +package homedir + +import ( + "path/filepath" + "testing" +) + +func TestGet(t *testing.T) { + home := Get() + if home == "" { + t.Fatal("returned home directory is empty") + } + + if !filepath.IsAbs(home) { + t.Fatalf("returned path is not absolute: %s", home) + } +} + +func TestGetShortcutString(t *testing.T) { + shortcut := GetShortcutString() + if shortcut == "" { + t.Fatal("returned shortcut string is empty") + } +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS b/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS new file mode 100644 index 0000000000..edbe200669 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS @@ -0,0 +1,2 @@ +Tianon Gravi (@tianon) +Aleksa Sarai (@cyphar) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup.go new file mode 100644 index 0000000000..6f8a982ff7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup.go @@ -0,0 +1,108 @@ +package user + +import ( + "errors" + "fmt" + "syscall" +) + +var ( + // The current operating system does not provide the required data for user lookups. + ErrUnsupported = errors.New("user lookup: operating system does not provide passwd-formatted data") +) + +func lookupUser(filter func(u User) bool) (User, error) { + // Get operating system-specific passwd reader-closer. + passwd, err := GetPasswd() + if err != nil { + return User{}, err + } + defer passwd.Close() + + // Get the users. + users, err := ParsePasswdFilter(passwd, filter) + if err != nil { + return User{}, err + } + + // No user entries found. + if len(users) == 0 { + return User{}, fmt.Errorf("no matching entries in passwd file") + } + + // Assume the first entry is the "correct" one. + return users[0], nil +} + +// CurrentUser looks up the current user by their user id in /etc/passwd. If the +// user cannot be found (or there is no /etc/passwd file on the filesystem), +// then CurrentUser returns an error. +func CurrentUser() (User, error) { + return LookupUid(syscall.Getuid()) +} + +// LookupUser looks up a user by their username in /etc/passwd. If the user +// cannot be found (or there is no /etc/passwd file on the filesystem), then +// LookupUser returns an error. +func LookupUser(username string) (User, error) { + return lookupUser(func(u User) bool { + return u.Name == username + }) +} + +// LookupUid looks up a user by their user id in /etc/passwd. If the user cannot +// be found (or there is no /etc/passwd file on the filesystem), then LookupId +// returns an error. +func LookupUid(uid int) (User, error) { + return lookupUser(func(u User) bool { + return u.Uid == uid + }) +} + +func lookupGroup(filter func(g Group) bool) (Group, error) { + // Get operating system-specific group reader-closer. + group, err := GetGroup() + if err != nil { + return Group{}, err + } + defer group.Close() + + // Get the users. + groups, err := ParseGroupFilter(group, filter) + if err != nil { + return Group{}, err + } + + // No user entries found. + if len(groups) == 0 { + return Group{}, fmt.Errorf("no matching entries in group file") + } + + // Assume the first entry is the "correct" one. + return groups[0], nil +} + +// CurrentGroup looks up the current user's group by their primary group id's +// entry in /etc/passwd. If the group cannot be found (or there is no +// /etc/group file on the filesystem), then CurrentGroup returns an error. +func CurrentGroup() (Group, error) { + return LookupGid(syscall.Getgid()) +} + +// LookupGroup looks up a group by its name in /etc/group. If the group cannot +// be found (or there is no /etc/group file on the filesystem), then LookupGroup +// returns an error. +func LookupGroup(groupname string) (Group, error) { + return lookupGroup(func(g Group) bool { + return g.Name == groupname + }) +} + +// LookupGid looks up a group by its group id in /etc/group. If the group cannot +// be found (or there is no /etc/group file on the filesystem), then LookupGid +// returns an error. +func LookupGid(gid int) (Group, error) { + return lookupGroup(func(g Group) bool { + return g.Gid == gid + }) +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go new file mode 100644 index 0000000000..758b734c22 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go @@ -0,0 +1,30 @@ +// +build darwin dragonfly freebsd linux netbsd openbsd solaris + +package user + +import ( + "io" + "os" +) + +// Unix-specific path to the passwd and group formatted files. +const ( + unixPasswdPath = "/etc/passwd" + unixGroupPath = "/etc/group" +) + +func GetPasswdPath() (string, error) { + return unixPasswdPath, nil +} + +func GetPasswd() (io.ReadCloser, error) { + return os.Open(unixPasswdPath) +} + +func GetGroupPath() (string, error) { + return unixGroupPath, nil +} + +func GetGroup() (io.ReadCloser, error) { + return os.Open(unixGroupPath) +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go new file mode 100644 index 0000000000..7217948870 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go @@ -0,0 +1,21 @@ +// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris + +package user + +import "io" + +func GetPasswdPath() (string, error) { + return "", ErrUnsupported +} + +func GetPasswd() (io.ReadCloser, error) { + return nil, ErrUnsupported +} + +func GetGroupPath() (string, error) { + return "", ErrUnsupported +} + +func GetGroup() (io.ReadCloser, error) { + return nil, ErrUnsupported +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go new file mode 100644 index 0000000000..d7439f12e3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go @@ -0,0 +1,350 @@ +package user + +import ( + "bufio" + "fmt" + "io" + "os" + "strconv" + "strings" +) + +const ( + minId = 0 + maxId = 1<<31 - 1 //for 32-bit systems compatibility +) + +var ( + ErrRange = fmt.Errorf("Uids and gids must be in range %d-%d", minId, maxId) +) + +type User struct { + Name string + Pass string + Uid int + Gid int + Gecos string + Home string + Shell string +} + +type Group struct { + Name string + Pass string + Gid int + List []string +} + +func parseLine(line string, v ...interface{}) { + if line == "" { + return + } + + parts := strings.Split(line, ":") + for i, p := range parts { + if len(v) <= i { + // if we have more "parts" than we have places to put them, bail for great "tolerance" of naughty configuration files + break + } + + switch e := v[i].(type) { + case *string: + // "root", "adm", "/bin/bash" + *e = p + case *int: + // "0", "4", "1000" + // ignore string to int conversion errors, for great "tolerance" of naughty configuration files + *e, _ = strconv.Atoi(p) + case *[]string: + // "", "root", "root,adm,daemon" + if p != "" { + *e = strings.Split(p, ",") + } else { + *e = []string{} + } + default: + // panic, because this is a programming/logic error, not a runtime one + panic("parseLine expects only pointers! argument " + strconv.Itoa(i) + " is not a pointer!") + } + } +} + +func ParsePasswdFile(path string) ([]User, error) { + passwd, err := os.Open(path) + if err != nil { + return nil, err + } + defer passwd.Close() + return ParsePasswd(passwd) +} + +func ParsePasswd(passwd io.Reader) ([]User, error) { + return ParsePasswdFilter(passwd, nil) +} + +func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) { + passwd, err := os.Open(path) + if err != nil { + return nil, err + } + defer passwd.Close() + return ParsePasswdFilter(passwd, filter) +} + +func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) { + if r == nil { + return nil, fmt.Errorf("nil source for passwd-formatted data") + } + + var ( + s = bufio.NewScanner(r) + out = []User{} + ) + + for s.Scan() { + if err := s.Err(); err != nil { + return nil, err + } + + text := strings.TrimSpace(s.Text()) + if text == "" { + continue + } + + // see: man 5 passwd + // name:password:UID:GID:GECOS:directory:shell + // Name:Pass:Uid:Gid:Gecos:Home:Shell + // root:x:0:0:root:/root:/bin/bash + // adm:x:3:4:adm:/var/adm:/bin/false + p := User{} + parseLine( + text, + &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell, + ) + + if filter == nil || filter(p) { + out = append(out, p) + } + } + + return out, nil +} + +func ParseGroupFile(path string) ([]Group, error) { + group, err := os.Open(path) + if err != nil { + return nil, err + } + defer group.Close() + return ParseGroup(group) +} + +func ParseGroup(group io.Reader) ([]Group, error) { + return ParseGroupFilter(group, nil) +} + +func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) { + group, err := os.Open(path) + if err != nil { + return nil, err + } + defer group.Close() + return ParseGroupFilter(group, filter) +} + +func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) { + if r == nil { + return nil, fmt.Errorf("nil source for group-formatted data") + } + + var ( + s = bufio.NewScanner(r) + out = []Group{} + ) + + for s.Scan() { + if err := s.Err(); err != nil { + return nil, err + } + + text := s.Text() + if text == "" { + continue + } + + // see: man 5 group + // group_name:password:GID:user_list + // Name:Pass:Gid:List + // root:x:0:root + // adm:x:4:root,adm,daemon + p := Group{} + parseLine( + text, + &p.Name, &p.Pass, &p.Gid, &p.List, + ) + + if filter == nil || filter(p) { + out = append(out, p) + } + } + + return out, nil +} + +type ExecUser struct { + Uid, Gid int + Sgids []int + Home string +} + +// GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the +// given file paths and uses that data as the arguments to GetExecUser. If the +// files cannot be opened for any reason, the error is ignored and a nil +// io.Reader is passed instead. +func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) { + passwd, err := os.Open(passwdPath) + if err != nil { + passwd = nil + } else { + defer passwd.Close() + } + + group, err := os.Open(groupPath) + if err != nil { + group = nil + } else { + defer group.Close() + } + + return GetExecUser(userSpec, defaults, passwd, group) +} + +// GetExecUser parses a user specification string (using the passwd and group +// readers as sources for /etc/passwd and /etc/group data, respectively). In +// the case of blank fields or missing data from the sources, the values in +// defaults is used. +// +// GetExecUser will return an error if a user or group literal could not be +// found in any entry in passwd and group respectively. +// +// Examples of valid user specifications are: +// * "" +// * "user" +// * "uid" +// * "user:group" +// * "uid:gid +// * "user:gid" +// * "uid:group" +func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) { + var ( + userArg, groupArg string + name string + ) + + if defaults == nil { + defaults = new(ExecUser) + } + + // Copy over defaults. + user := &ExecUser{ + Uid: defaults.Uid, + Gid: defaults.Gid, + Sgids: defaults.Sgids, + Home: defaults.Home, + } + + // Sgids slice *cannot* be nil. + if user.Sgids == nil { + user.Sgids = []int{} + } + + // allow for userArg to have either "user" syntax, or optionally "user:group" syntax + parseLine(userSpec, &userArg, &groupArg) + + users, err := ParsePasswdFilter(passwd, func(u User) bool { + if userArg == "" { + return u.Uid == user.Uid + } + return u.Name == userArg || strconv.Itoa(u.Uid) == userArg + }) + if err != nil && passwd != nil { + if userArg == "" { + userArg = strconv.Itoa(user.Uid) + } + return nil, fmt.Errorf("Unable to find user %v: %v", userArg, err) + } + + haveUser := users != nil && len(users) > 0 + if haveUser { + // if we found any user entries that matched our filter, let's take the first one as "correct" + name = users[0].Name + user.Uid = users[0].Uid + user.Gid = users[0].Gid + user.Home = users[0].Home + } else if userArg != "" { + // we asked for a user but didn't find them... let's check to see if we wanted a numeric user + user.Uid, err = strconv.Atoi(userArg) + if err != nil { + // not numeric - we have to bail + return nil, fmt.Errorf("Unable to find user %v", userArg) + } + + // Must be inside valid uid range. + if user.Uid < minId || user.Uid > maxId { + return nil, ErrRange + } + + // if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit + } + + if groupArg != "" || name != "" { + groups, err := ParseGroupFilter(group, func(g Group) bool { + // Explicit group format takes precedence. + if groupArg != "" { + return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg + } + + // Check if user is a member. + for _, u := range g.List { + if u == name { + return true + } + } + + return false + }) + if err != nil && group != nil { + return nil, fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err) + } + + haveGroup := groups != nil && len(groups) > 0 + if groupArg != "" { + if haveGroup { + // if we found any group entries that matched our filter, let's take the first one as "correct" + user.Gid = groups[0].Gid + } else { + // we asked for a group but didn't find id... let's check to see if we wanted a numeric group + user.Gid, err = strconv.Atoi(groupArg) + if err != nil { + // not numeric - we have to bail + return nil, fmt.Errorf("Unable to find group %v", groupArg) + } + + // Ensure gid is inside gid range. + if user.Gid < minId || user.Gid > maxId { + return nil, ErrRange + } + + // if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit + } + } else if haveGroup { + // If implicit group format, fill supplementary gids. + user.Sgids = make([]int, len(groups)) + for i, group := range groups { + user.Sgids[i] = group.Gid + } + } + } + + return user, nil +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go new file mode 100644 index 0000000000..4fe008fb39 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go @@ -0,0 +1,352 @@ +package user + +import ( + "io" + "reflect" + "strings" + "testing" +) + +func TestUserParseLine(t *testing.T) { + var ( + a, b string + c []string + d int + ) + + parseLine("", &a, &b) + if a != "" || b != "" { + t.Fatalf("a and b should be empty ('%v', '%v')", a, b) + } + + parseLine("a", &a, &b) + if a != "a" || b != "" { + t.Fatalf("a should be 'a' and b should be empty ('%v', '%v')", a, b) + } + + parseLine("bad boys:corny cows", &a, &b) + if a != "bad boys" || b != "corny cows" { + t.Fatalf("a should be 'bad boys' and b should be 'corny cows' ('%v', '%v')", a, b) + } + + parseLine("", &c) + if len(c) != 0 { + t.Fatalf("c should be empty (%#v)", c) + } + + parseLine("d,e,f:g:h:i,j,k", &c, &a, &b, &c) + if a != "g" || b != "h" || len(c) != 3 || c[0] != "i" || c[1] != "j" || c[2] != "k" { + t.Fatalf("a should be 'g', b should be 'h', and c should be ['i','j','k'] ('%v', '%v', '%#v')", a, b, c) + } + + parseLine("::::::::::", &a, &b, &c) + if a != "" || b != "" || len(c) != 0 { + t.Fatalf("a, b, and c should all be empty ('%v', '%v', '%#v')", a, b, c) + } + + parseLine("not a number", &d) + if d != 0 { + t.Fatalf("d should be 0 (%v)", d) + } + + parseLine("b:12:c", &a, &d, &b) + if a != "b" || b != "c" || d != 12 { + t.Fatalf("a should be 'b' and b should be 'c', and d should be 12 ('%v', '%v', %v)", a, b, d) + } +} + +func TestUserParsePasswd(t *testing.T) { + users, err := ParsePasswdFilter(strings.NewReader(` +root:x:0:0:root:/root:/bin/bash +adm:x:3:4:adm:/var/adm:/bin/false +this is just some garbage data +`), nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if len(users) != 3 { + t.Fatalf("Expected 3 users, got %v", len(users)) + } + if users[0].Uid != 0 || users[0].Name != "root" { + t.Fatalf("Expected users[0] to be 0 - root, got %v - %v", users[0].Uid, users[0].Name) + } + if users[1].Uid != 3 || users[1].Name != "adm" { + t.Fatalf("Expected users[1] to be 3 - adm, got %v - %v", users[1].Uid, users[1].Name) + } +} + +func TestUserParseGroup(t *testing.T) { + groups, err := ParseGroupFilter(strings.NewReader(` +root:x:0:root +adm:x:4:root,adm,daemon +this is just some garbage data +`), nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if len(groups) != 3 { + t.Fatalf("Expected 3 groups, got %v", len(groups)) + } + if groups[0].Gid != 0 || groups[0].Name != "root" || len(groups[0].List) != 1 { + t.Fatalf("Expected groups[0] to be 0 - root - 1 member, got %v - %v - %v", groups[0].Gid, groups[0].Name, len(groups[0].List)) + } + if groups[1].Gid != 4 || groups[1].Name != "adm" || len(groups[1].List) != 3 { + t.Fatalf("Expected groups[1] to be 4 - adm - 3 members, got %v - %v - %v", groups[1].Gid, groups[1].Name, len(groups[1].List)) + } +} + +func TestValidGetExecUser(t *testing.T) { + const passwdContent = ` +root:x:0:0:root user:/root:/bin/bash +adm:x:42:43:adm:/var/adm:/bin/false +this is just some garbage data +` + const groupContent = ` +root:x:0:root +adm:x:43: +grp:x:1234:root,adm +this is just some garbage data +` + defaultExecUser := ExecUser{ + Uid: 8888, + Gid: 8888, + Sgids: []int{8888}, + Home: "/8888", + } + + tests := []struct { + ref string + expected ExecUser + }{ + { + ref: "root", + expected: ExecUser{ + Uid: 0, + Gid: 0, + Sgids: []int{0, 1234}, + Home: "/root", + }, + }, + { + ref: "adm", + expected: ExecUser{ + Uid: 42, + Gid: 43, + Sgids: []int{1234}, + Home: "/var/adm", + }, + }, + { + ref: "root:adm", + expected: ExecUser{ + Uid: 0, + Gid: 43, + Sgids: defaultExecUser.Sgids, + Home: "/root", + }, + }, + { + ref: "adm:1234", + expected: ExecUser{ + Uid: 42, + Gid: 1234, + Sgids: defaultExecUser.Sgids, + Home: "/var/adm", + }, + }, + { + ref: "42:1234", + expected: ExecUser{ + Uid: 42, + Gid: 1234, + Sgids: defaultExecUser.Sgids, + Home: "/var/adm", + }, + }, + { + ref: "1337:1234", + expected: ExecUser{ + Uid: 1337, + Gid: 1234, + Sgids: defaultExecUser.Sgids, + Home: defaultExecUser.Home, + }, + }, + { + ref: "1337", + expected: ExecUser{ + Uid: 1337, + Gid: defaultExecUser.Gid, + Sgids: defaultExecUser.Sgids, + Home: defaultExecUser.Home, + }, + }, + { + ref: "", + expected: ExecUser{ + Uid: defaultExecUser.Uid, + Gid: defaultExecUser.Gid, + Sgids: defaultExecUser.Sgids, + Home: defaultExecUser.Home, + }, + }, + } + + for _, test := range tests { + passwd := strings.NewReader(passwdContent) + group := strings.NewReader(groupContent) + + execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group) + if err != nil { + t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error()) + t.Fail() + continue + } + + if !reflect.DeepEqual(test.expected, *execUser) { + t.Logf("got: %#v", execUser) + t.Logf("expected: %#v", test.expected) + t.Fail() + continue + } + } +} + +func TestInvalidGetExecUser(t *testing.T) { + const passwdContent = ` +root:x:0:0:root user:/root:/bin/bash +adm:x:42:43:adm:/var/adm:/bin/false +this is just some garbage data +` + const groupContent = ` +root:x:0:root +adm:x:43: +grp:x:1234:root,adm +this is just some garbage data +` + + tests := []string{ + // No such user/group. + "notuser", + "notuser:notgroup", + "root:notgroup", + "notuser:adm", + "8888:notgroup", + "notuser:8888", + + // Invalid user/group values. + "-1:0", + "0:-3", + "-5:-2", + } + + for _, test := range tests { + passwd := strings.NewReader(passwdContent) + group := strings.NewReader(groupContent) + + execUser, err := GetExecUser(test, nil, passwd, group) + if err == nil { + t.Logf("got unexpected success when parsing '%s': %#v", test, execUser) + t.Fail() + continue + } + } +} + +func TestGetExecUserNilSources(t *testing.T) { + const passwdContent = ` +root:x:0:0:root user:/root:/bin/bash +adm:x:42:43:adm:/var/adm:/bin/false +this is just some garbage data +` + const groupContent = ` +root:x:0:root +adm:x:43: +grp:x:1234:root,adm +this is just some garbage data +` + + defaultExecUser := ExecUser{ + Uid: 8888, + Gid: 8888, + Sgids: []int{8888}, + Home: "/8888", + } + + tests := []struct { + ref string + passwd, group bool + expected ExecUser + }{ + { + ref: "", + passwd: false, + group: false, + expected: ExecUser{ + Uid: 8888, + Gid: 8888, + Sgids: []int{8888}, + Home: "/8888", + }, + }, + { + ref: "root", + passwd: true, + group: false, + expected: ExecUser{ + Uid: 0, + Gid: 0, + Sgids: []int{8888}, + Home: "/root", + }, + }, + { + ref: "0", + passwd: false, + group: false, + expected: ExecUser{ + Uid: 0, + Gid: 8888, + Sgids: []int{8888}, + Home: "/8888", + }, + }, + { + ref: "0:0", + passwd: false, + group: false, + expected: ExecUser{ + Uid: 0, + Gid: 0, + Sgids: []int{8888}, + Home: "/8888", + }, + }, + } + + for _, test := range tests { + var passwd, group io.Reader + + if test.passwd { + passwd = strings.NewReader(passwdContent) + } + + if test.group { + group = strings.NewReader(groupContent) + } + + execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group) + if err != nil { + t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error()) + t.Fail() + continue + } + + if !reflect.DeepEqual(test.expected, *execUser) { + t.Logf("got: %#v", execUser) + t.Logf("expected: %#v", test.expected) + t.Fail() + continue + } + } +} diff --git a/drivers/virtualbox/virtualbox.go b/drivers/virtualbox/virtualbox.go index 0fde6bb5c5..f97c706852 100644 --- a/drivers/virtualbox/virtualbox.go +++ b/drivers/virtualbox/virtualbox.go @@ -11,6 +11,7 @@ import ( "net" "os" "os/exec" + "path" "path/filepath" "regexp" "runtime" @@ -19,6 +20,7 @@ import ( "time" "github.com/codegangsta/cli" + "github.com/docker/docker/pkg/homedir" "github.com/docker/machine/drivers" "github.com/docker/machine/log" "github.com/docker/machine/ssh" @@ -288,36 +290,47 @@ func (d *Driver) Create() error { return err } - // let VBoxService do nice magic automounting (when it's used) - if err := vbm("guestproperty", "set", d.MachineName, "/VirtualBox/GuestAdd/SharedFolders/MountPrefix", "/"); err != nil { - return err + var shareDir string + + homePath := homedir.Get() + + switch runtime.GOOS { + case "windows": + shareDir = homePath + mountDir, err := translateWindowsMount(homePath) + if err != nil { + return err + } + shareDir = mountDir + case "darwin": + shareDir = homePath + case "linux": + shareDir = homePath } + + shareName := shareDir + + log.Debugf("creating share: path=%s", shareDir) + + // let VBoxService do nice magic automounting (when it's used) if err := vbm("guestproperty", "set", d.MachineName, "/VirtualBox/GuestAdd/SharedFolders/MountDir", "/"); err != nil { return err } - - var shareName, shareDir string // TODO configurable at some point - switch runtime.GOOS { - case "windows": - shareName = "c/Users" - shareDir = "c:\\Users" - case "darwin": - shareName = "Users" - shareDir = "/Users" - // TODO "linux" + if err := vbm("guestproperty", "set", d.MachineName, "/VirtualBox/GuestAdd/SharedFolders/MountPrefix", "/"); err != nil { + return err } if shareDir != "" { if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { return err } else if !os.IsNotExist(err) { - if shareName == "" { + if shareName != "" { // parts of the VBox internal code are buggy with share names that start with "/" shareName = strings.TrimLeft(shareDir, "/") - // TODO do some basic Windows -> MSYS path conversion - // ie, s!^([a-z]+):[/\\]+!\1/!; s!\\!/!g } + log.Debugf("adding shared folder: name=%q dir=%q", shareName, shareDir) + // woo, shareDir exists! let's carry on! if err := vbm("sharedfolder", "add", d.MachineName, "--name", shareName, "--hostpath", shareDir, "--automount"); err != nil { return err @@ -336,13 +349,47 @@ func (d *Driver) Create() error { return err } + // use ssh to set keys + sshClient, err := d.getLocalSSHClient() + if err != nil { + return err + } + + // add pub key for user + pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) + if err != nil { + return err + } + + if out, err := sshClient.Output(fmt.Sprintf( + "mkdir -p /home/%s/.ssh", + d.GetSSHUsername(), + )); err != nil { + log.Error(out) + return err + } + + if out, err := sshClient.Output(fmt.Sprintf( + "printf '%%s' '%s' | tee /home/%s/.ssh/authorized_keys", + string(pubKey), + d.GetSSHUsername(), + )); err != nil { + log.Error(out) + return err + } + + ip, err := d.GetIP() + if err != nil { + return err + } + d.IPAddress = ip return nil } func (d *Driver) hostOnlyIpAvailable() bool { ip, err := d.GetIP() if err != nil { - log.Debug("ERROR getting IP: %s", err) + log.Debugf("ERROR getting IP: %s", err) return false } if ip != "" { @@ -385,8 +432,8 @@ func (d *Driver) Start() error { log.Infof("VM not in restartable state") } - // Wait for SSH over NAT to be available before returning to user - if err := drivers.WaitForSSH(d); err != nil { + addr, err := d.GetSSHHostname() + if err := ssh.WaitForTCP(fmt.Sprintf("%s:%d", addr, d.SSHPort)); err != nil { return err } @@ -395,8 +442,6 @@ func (d *Driver) Start() error { return err } - d.IPAddress, err = d.GetIP() - return err } @@ -502,11 +547,17 @@ func (d *Driver) GetIP() (string, error) { return "", drivers.ErrHostIsNotRunning } - output, err := drivers.RunSSHCommandFromDriver(d, "ip addr show dev eth1") + sshClient, err := d.getLocalSSHClient() if err != nil { return "", err } + output, err := sshClient.Output("ip addr show dev eth1") + if err != nil { + log.Debug(output) + return "", err + } + log.Debugf("SSH returned: %s\nEND SSH\n", output) // parse to find: inet 192.168.59.103/24 brd 192.168.59.255 scope global eth1 @@ -533,7 +584,7 @@ func (d *Driver) diskPath() string { func (d *Driver) generateDiskImage(size int) error { log.Debugf("Creating %d MB hard disk image...", size) - magicString := "boot2docker, please format-me" + magicString := "data" buf := new(bytes.Buffer) tw := tar.NewWriter(buf) @@ -543,36 +594,13 @@ func (d *Driver) generateDiskImage(size int) error { if err := tw.WriteHeader(file); err != nil { return err } + if _, err := tw.Write([]byte(magicString)); err != nil { return err } - // .ssh/key.pub => authorized_keys - file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} - if err := tw.WriteHeader(file); err != nil { - return err - } - pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) - if err != nil { - return err - } - file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} - if err := tw.WriteHeader(file); err != nil { - return err - } - if _, err := tw.Write([]byte(pubKey)); err != nil { - return err - } - file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} - if err := tw.WriteHeader(file); err != nil { - return err - } - if _, err := tw.Write([]byte(pubKey)); err != nil { - return err - } - if err := tw.Close(); err != nil { - return err - } + raw := bytes.NewReader(buf.Bytes()) + return createDiskImage(d.diskPath(), size, raw) } @@ -755,3 +783,35 @@ func getRandomIPinSubnet(baseIP net.IP) (net.IP, error) { return dhcpAddr, nil } + +func (d *Driver) getLocalSSHClient() (ssh.Client, error) { + sshAuth := &ssh.Auth{ + Passwords: []string{"docker"}, + Keys: []string{d.GetSSHKeyPath()}, + } + sshClient, err := ssh.NewNativeClient(d.GetSSHUsername(), "127.0.0.1", d.SSHPort, sshAuth) + if err != nil { + return nil, err + } + + return sshClient, nil +} + +func translateWindowsMount(p string) (string, error) { + re := regexp.MustCompile(`(?P.+):\\(?P.*)`) + m := re.FindStringSubmatch(p) + + var drive, fullPath string + + if len(m) < 3 { + return "", fmt.Errorf("unable to parse home directory") + } + + drive = m[1] + fullPath = m[2] + + nPath := strings.Replace(fullPath, "\\", "/", -1) + + tPath := path.Join("/", strings.ToLower(drive), nPath) + return tPath, nil +} diff --git a/drivers/virtualbox/virtualbox_test.go b/drivers/virtualbox/virtualbox_test.go index 5df5e5f4cb..7b63f3b08d 100644 --- a/drivers/virtualbox/virtualbox_test.go +++ b/drivers/virtualbox/virtualbox_test.go @@ -29,3 +29,39 @@ func TestGetRandomIPinSubnet(t *testing.T) { t.Fatalf("expected third octet of %d; received %d", testIP[2], newIP[2]) } } + +func TestTranslateWindowsMount(t *testing.T) { + p1 := `C:\Users\foo` + r, err := translateWindowsMount(p1) + if err != nil { + t.Fatal(err) + } + + if r != `/c/Users/foo` { + t.Fatalf("expected to match /c/Users/foo") + } +} + +func TestTranslateWindowsMountCustomDrive(t *testing.T) { + p1 := `D:\Users\foo` + r, err := translateWindowsMount(p1) + if err != nil { + t.Fatal(err) + } + + if r != `/d/Users/foo` { + t.Fatalf("expected to match /d/Users/foo") + } +} + +func TestTranslateWindowsMountLongPath(t *testing.T) { + p1 := `c:\path\to\users\foo` + r, err := translateWindowsMount(p1) + if err != nil { + t.Fatal(err) + } + + if r != `/c/path/to/users/foo` { + t.Fatalf("expected to match /c/path/to/users/foo") + } +} diff --git a/libmachine/provision/boot2docker.go b/libmachine/provision/boot2docker.go index 485cfb852e..bcbb681da3 100644 --- a/libmachine/provision/boot2docker.go +++ b/libmachine/provision/boot2docker.go @@ -1,19 +1,7 @@ package provision import ( - "bytes" - "fmt" - "path" - "text/template" - "github.com/docker/machine/drivers" - "github.com/docker/machine/libmachine/auth" - "github.com/docker/machine/libmachine/engine" - "github.com/docker/machine/libmachine/provision/pkgaction" - "github.com/docker/machine/libmachine/swarm" - "github.com/docker/machine/log" - "github.com/docker/machine/state" - "github.com/docker/machine/utils" ) func init() { @@ -23,204 +11,21 @@ func init() { } func NewBoot2DockerProvisioner(d drivers.Driver) Provisioner { - return &Boot2DockerProvisioner{ - Driver: d, + g := GenericProvisioner{ + DockerOptionsDir: "/etc/docker", + DaemonOptionsFile: "/etc/systemd/system/docker.service", + OsReleaseId: "docker", + Packages: []string{}, + Driver: d, } + p := &Boot2DockerProvisioner{ + DebianProvisioner{ + GenericProvisioner: g, + }, + } + return p } type Boot2DockerProvisioner struct { - OsReleaseInfo *OsRelease - Driver drivers.Driver - AuthOptions auth.AuthOptions - EngineOptions engine.EngineOptions - SwarmOptions swarm.SwarmOptions -} - -func (provisioner *Boot2DockerProvisioner) Service(name string, action pkgaction.ServiceAction) error { - var ( - err error - ) - - if _, err = provisioner.SSHCommand(fmt.Sprintf("sudo /etc/init.d/%s %s", name, action.String())); err != nil { - return err - } - - return nil -} - -func (provisioner *Boot2DockerProvisioner) upgradeIso() error { - log.Info("Stopping machine to do the upgrade...") - - if err := provisioner.Driver.Stop(); err != nil { - return err - } - - if err := utils.WaitFor(drivers.MachineInState(provisioner.Driver, state.Stopped)); err != nil { - return err - } - - machineName := provisioner.GetDriver().GetMachineName() - - log.Infof("Upgrading machine %s...", machineName) - - b2dutils := utils.NewB2dUtils("", "") - - // Usually we call this implicitly, but call it here explicitly to get - // the latest boot2docker ISO. - if err := b2dutils.DownloadLatestBoot2Docker(); err != nil { - return err - } - - // Copy the latest version of boot2docker ISO to the machine's directory - if err := b2dutils.CopyIsoToMachineDir("", machineName); err != nil { - return err - } - - log.Infof("Starting machine back up...") - - if err := provisioner.Driver.Start(); err != nil { - return err - } - - return utils.WaitFor(drivers.MachineInState(provisioner.Driver, state.Running)) -} - -func (provisioner *Boot2DockerProvisioner) Package(name string, action pkgaction.PackageAction) error { - if name == "docker" && action == pkgaction.Upgrade { - if err := provisioner.upgradeIso(); err != nil { - return err - } - } - return nil -} - -func (provisioner *Boot2DockerProvisioner) Hostname() (string, error) { - return provisioner.SSHCommand("hostname") -} - -func (provisioner *Boot2DockerProvisioner) SetHostname(hostname string) error { - if _, err := provisioner.SSHCommand(fmt.Sprintf( - "sudo /usr/bin/sethostname %s && echo %q | sudo tee /var/lib/boot2docker/etc/hostname", - hostname, - hostname, - )); err != nil { - return err - } - - return nil -} - -func (provisioner *Boot2DockerProvisioner) GetDockerOptionsDir() string { - return "/var/lib/boot2docker" -} - -func (provisioner *Boot2DockerProvisioner) GetAuthOptions() auth.AuthOptions { - return provisioner.AuthOptions -} - -func (provisioner *Boot2DockerProvisioner) GenerateDockerOptions(dockerPort int) (*DockerOptions, error) { - var ( - engineCfg bytes.Buffer - ) - - driverNameLabel := fmt.Sprintf("provider=%s", provisioner.Driver.DriverName()) - provisioner.EngineOptions.Labels = append(provisioner.EngineOptions.Labels, driverNameLabel) - - engineConfigTmpl := ` -EXTRA_ARGS=' -{{ range .EngineOptions.Labels }}--label {{.}} -{{ end }}{{ range .EngineOptions.InsecureRegistry }}--insecure-registry {{.}} -{{ end }}{{ range .EngineOptions.RegistryMirror }}--registry-mirror {{.}} -{{ end }}{{ range .EngineOptions.ArbitraryFlags }}--{{.}} -{{ end }} -' -CACERT={{.AuthOptions.CaCertRemotePath}} -DOCKER_HOST='-H tcp://0.0.0.0:{{.DockerPort}}' -DOCKER_STORAGE={{.EngineOptions.StorageDriver}} -DOCKER_TLS=auto -SERVERKEY={{.AuthOptions.ServerKeyRemotePath}} -SERVERCERT={{.AuthOptions.ServerCertRemotePath}} - -{{range .EngineOptions.Env}}export \"{{ printf "%q" . }}\" -{{end}} -` - t, err := template.New("engineConfig").Parse(engineConfigTmpl) - if err != nil { - return nil, err - } - - engineConfigContext := EngineConfigContext{ - DockerPort: dockerPort, - AuthOptions: provisioner.AuthOptions, - EngineOptions: provisioner.EngineOptions, - } - - t.Execute(&engineCfg, engineConfigContext) - - daemonOptsDir := path.Join(provisioner.GetDockerOptionsDir(), "profile") - return &DockerOptions{ - EngineOptions: engineCfg.String(), - EngineOptionsPath: daemonOptsDir, - }, nil -} - -func (provisioner *Boot2DockerProvisioner) CompatibleWithHost() bool { - return provisioner.OsReleaseInfo.Id == "boot2docker" -} - -func (provisioner *Boot2DockerProvisioner) SetOsReleaseInfo(info *OsRelease) { - provisioner.OsReleaseInfo = info -} - -func (provisioner *Boot2DockerProvisioner) GetOsReleaseInfo() (*OsRelease, error) { - return provisioner.OsReleaseInfo, nil -} - -func (provisioner *Boot2DockerProvisioner) Provision(swarmOptions swarm.SwarmOptions, authOptions auth.AuthOptions, engineOptions engine.EngineOptions) error { - provisioner.SwarmOptions = swarmOptions - provisioner.AuthOptions = authOptions - provisioner.EngineOptions = engineOptions - - if provisioner.EngineOptions.StorageDriver == "" { - provisioner.EngineOptions.StorageDriver = "aufs" - } - - if err := provisioner.SetHostname(provisioner.Driver.GetMachineName()); err != nil { - return err - } - - ip, err := provisioner.GetDriver().GetIP() - if err != nil { - return err - } - - // b2d hosts need to wait for the daemon to be up - // before continuing with provisioning - if err := utils.WaitForDocker(ip, 2376); err != nil { - return err - } - - if err := makeDockerOptionsDir(provisioner); err != nil { - return err - } - - provisioner.AuthOptions = setRemoteAuthOptions(provisioner) - - if err := ConfigureAuth(provisioner); err != nil { - return err - } - - if err := configureSwarm(provisioner, swarmOptions, provisioner.AuthOptions); err != nil { - return err - } - - return nil -} - -func (provisioner *Boot2DockerProvisioner) SSHCommand(args string) (string, error) { - return drivers.RunSSHCommandFromDriver(provisioner.Driver, args) -} - -func (provisioner *Boot2DockerProvisioner) GetDriver() drivers.Driver { - return provisioner.Driver + DebianProvisioner } diff --git a/libmachine/provision/debian.go b/libmachine/provision/debian.go index aa0691b600..964c75a676 100644 --- a/libmachine/provision/debian.go +++ b/libmachine/provision/debian.go @@ -44,7 +44,7 @@ func (provisioner *DebianProvisioner) Service(name string, action pkgaction.Serv return err } - command := fmt.Sprintf("sudo systemctl %s %s", action.String(), name) + command := fmt.Sprintf("sudo systemctl %s %s || true", action.String(), name) if _, err := provisioner.SSHCommand(command); err != nil { return err From e09f2fdd9c6446791eb6a48c60d9644626f70464 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Mon, 20 Jul 2015 20:24:08 -0400 Subject: [PATCH 15/36] vbox: change to get latest released iso instead of pre-release Signed-off-by: Evan Hazlett vbox: fix windows share dir for homedir Signed-off-by: Evan Hazlett vbox: update tests for b2d Signed-off-by: Evan Hazlett --- drivers/virtualbox/virtualbox.go | 34 ++++++++------------- libmachine/provision/debian.go | 2 +- libmachine/provision/utils_test.go | 48 +++++++++++++++++++++--------- utils/b2d.go | 14 +++++++-- utils/b2d_test.go | 2 +- 5 files changed, 60 insertions(+), 40 deletions(-) diff --git a/drivers/virtualbox/virtualbox.go b/drivers/virtualbox/virtualbox.go index f97c706852..ee9f1f4fbb 100644 --- a/drivers/virtualbox/virtualbox.go +++ b/drivers/virtualbox/virtualbox.go @@ -290,24 +290,7 @@ func (d *Driver) Create() error { return err } - var shareDir string - - homePath := homedir.Get() - - switch runtime.GOOS { - case "windows": - shareDir = homePath - mountDir, err := translateWindowsMount(homePath) - if err != nil { - return err - } - shareDir = mountDir - case "darwin": - shareDir = homePath - case "linux": - shareDir = homePath - } - + shareDir := homedir.Get() shareName := shareDir log.Debugf("creating share: path=%s", shareDir) @@ -321,12 +304,21 @@ func (d *Driver) Create() error { } if shareDir != "" { + log.Debugf("setting up shareDir") if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { + log.Debugf("setting up share failed: %s", err) return err } else if !os.IsNotExist(err) { - if shareName != "" { - // parts of the VBox internal code are buggy with share names that start with "/" - shareName = strings.TrimLeft(shareDir, "/") + // parts of the VBox internal code are buggy with share names that start with "/" + shareName = strings.TrimLeft(shareDir, "/") + + // translate to msys git path + if runtime.GOOS == "windows" { + mountName, err := translateWindowsMount(shareDir) + if err != nil { + return err + } + shareName = mountName } log.Debugf("adding shared folder: name=%q dir=%q", shareName, shareDir) diff --git a/libmachine/provision/debian.go b/libmachine/provision/debian.go index 964c75a676..aa0691b600 100644 --- a/libmachine/provision/debian.go +++ b/libmachine/provision/debian.go @@ -44,7 +44,7 @@ func (provisioner *DebianProvisioner) Service(name string, action pkgaction.Serv return err } - command := fmt.Sprintf("sudo systemctl %s %s || true", action.String(), name) + command := fmt.Sprintf("sudo systemctl %s %s", action.String(), name) if _, err := provisioner.SSHCommand(command); err != nil { return err diff --git a/libmachine/provision/utils_test.go b/libmachine/provision/utils_test.go index 10617e638d..7fbae961e6 100644 --- a/libmachine/provision/utils_test.go +++ b/libmachine/provision/utils_test.go @@ -8,11 +8,24 @@ import ( "github.com/docker/machine/drivers/fakedriver" "github.com/docker/machine/libmachine/auth" + "github.com/docker/machine/libmachine/engine" ) +func engineOptions() engine.EngineOptions { + return engine.EngineOptions{ + StorageDriver: "aufs", + } +} + func TestGenerateDockerOptionsBoot2Docker(t *testing.T) { + g := GenericProvisioner{ + Driver: &fakedriver.FakeDriver{}, + EngineOptions: engineOptions(), + } p := &Boot2DockerProvisioner{ - Driver: &fakedriver.FakeDriver{}, + DebianProvisioner{ + g, + }, } dockerPort := 1234 p.AuthOptions = auth.AuthOptions{ @@ -20,37 +33,38 @@ func TestGenerateDockerOptionsBoot2Docker(t *testing.T) { ServerKeyRemotePath: "/test/server-key", ServerCertRemotePath: "/test/server-cert", } - engineConfigPath := "/var/lib/boot2docker/profile" dockerCfg, err := p.GenerateDockerOptions(dockerPort) if err != nil { t.Fatal(err) } - if dockerCfg.EngineOptionsPath != engineConfigPath { - t.Fatalf("expected engine path %s; received %s", engineConfigPath, dockerCfg.EngineOptionsPath) - } - if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("-H tcp://0.0.0.0:%d", dockerPort)) == -1 { t.Fatalf("-H docker port invalid; expected %d", dockerPort) } - if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("CACERT=%s", p.AuthOptions.CaCertRemotePath)) == -1 { - t.Fatalf("CACERT option invalid; expected %s", p.AuthOptions.CaCertRemotePath) + if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("--tlscacert %s", p.AuthOptions.CaCertRemotePath)) == -1 { + t.Fatalf("--tlscacert option invalid; expected %s", p.AuthOptions.CaCertRemotePath) } - if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("SERVERKEY=%s", p.AuthOptions.ServerKeyRemotePath)) == -1 { - t.Fatalf("SERVERKEY option invalid; expected %s", p.AuthOptions.ServerKeyRemotePath) + if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("--tlscert %s", p.AuthOptions.ServerCertRemotePath)) == -1 { + t.Fatalf("--tlscert option invalid; expected %s", p.AuthOptions.ServerCertRemotePath) } - if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("SERVERCERT=%s", p.AuthOptions.ServerCertRemotePath)) == -1 { - t.Fatalf("SERVERCERT option invalid; expected %s", p.AuthOptions.ServerCertRemotePath) + if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("--tlskey %s", p.AuthOptions.ServerKeyRemotePath)) == -1 { + t.Fatalf("--tlskey option invalid; expected %s", p.AuthOptions.ServerKeyRemotePath) } } func TestMachinePortBoot2Docker(t *testing.T) { + g := GenericProvisioner{ + Driver: &fakedriver.FakeDriver{}, + EngineOptions: engineOptions(), + } p := &Boot2DockerProvisioner{ - Driver: &fakedriver.FakeDriver{}, + DebianProvisioner{ + g, + }, } dockerPort := 2376 bindUrl := fmt.Sprintf("tcp://0.0.0.0:%d", dockerPort) @@ -81,8 +95,14 @@ func TestMachinePortBoot2Docker(t *testing.T) { } func TestMachineCustomPortBoot2Docker(t *testing.T) { + g := GenericProvisioner{ + Driver: &fakedriver.FakeDriver{}, + EngineOptions: engineOptions(), + } p := &Boot2DockerProvisioner{ - Driver: &fakedriver.FakeDriver{}, + DebianProvisioner{ + g, + }, } dockerPort := 3376 bindUrl := fmt.Sprintf("tcp://0.0.0.0:%d", dockerPort) diff --git a/utils/b2d.go b/utils/b2d.go index e49651a2e4..19f9b3c8d3 100644 --- a/utils/b2d.go +++ b/utils/b2d.go @@ -80,7 +80,8 @@ func (b *B2dUtils) GetLatestBoot2DockerReleaseURL() (string, error) { defer rsp.Body.Close() var t []struct { - TagName string `json:"tag_name"` + TagName string `json:"tag_name"` + PreRelease bool `json:"prerelease"` } if err := json.NewDecoder(rsp.Body).Decode(&t); err != nil { return "", fmt.Errorf("Error demarshaling the Github API response: %s\nYou may be getting rate limited by Github.", err) @@ -89,8 +90,15 @@ func (b *B2dUtils) GetLatestBoot2DockerReleaseURL() (string, error) { return "", fmt.Errorf("no releases found") } - tag := t[0].TagName - isoUrl := fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/%s/boot2docker.iso", b.githubBaseUrl, tag) + // find the latest "released" release (i.e. not pre-release) + isoUrl := "" + for _, r := range t { + if !r.PreRelease { + tag := r.TagName + isoUrl = fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/%s/boot2docker.iso", b.githubBaseUrl, tag) + break + } + } return isoUrl, nil } diff --git a/utils/b2d_test.go b/utils/b2d_test.go index 2fe5a38103..67dbf19b42 100644 --- a/utils/b2d_test.go +++ b/utils/b2d_test.go @@ -11,7 +11,7 @@ import ( func TestGetLatestBoot2DockerReleaseUrl(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - respText := `[{"tag_name": "0.1"}]` + respText := `[{"tag_name": "0.2", "prerelease": true, "tag_name": "0.1", "prerelease": false}]` w.Write([]byte(respText)) })) defer ts.Close() From c6e98497837132b1ebde816bc2e411db35b28bc1 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Tue, 21 Jul 2015 17:16:44 -0400 Subject: [PATCH 16/36] vbox: restrict regex matching for msys translated paths Signed-off-by: Evan Hazlett vbox: remove tar loading from virtual disk creation Signed-off-by: Evan Hazlett tests: use btrfs instead of overlay for custom option test Signed-off-by: Evan Hazlett --- drivers/virtualbox/virtualbox.go | 96 +------------------ .../core/supported-engine-options.bats | 4 +- 2 files changed, 5 insertions(+), 95 deletions(-) diff --git a/drivers/virtualbox/virtualbox.go b/drivers/virtualbox/virtualbox.go index ee9f1f4fbb..a69853e072 100644 --- a/drivers/virtualbox/virtualbox.go +++ b/drivers/virtualbox/virtualbox.go @@ -1,16 +1,12 @@ package virtualbox import ( - "archive/tar" - "bytes" "errors" "fmt" - "io" "io/ioutil" "math/rand" "net" "os" - "os/exec" "path" "path/filepath" "regexp" @@ -205,9 +201,10 @@ func (d *Driver) Create() error { } log.Debugf("Creating disk image...") - if err := d.generateDiskImage(d.DiskSize); err != nil { + if err := vbm("createhd", "--size", fmt.Sprintf("%d", d.DiskSize), "--format", "VMDK", "--filename", d.diskPath()); err != nil { return err } + } if err := vbm("createvm", @@ -572,30 +569,6 @@ func (d *Driver) diskPath() string { return d.ResolveStorePath("disk.vmdk") } -// Make a boot2docker VM disk image. -func (d *Driver) generateDiskImage(size int) error { - log.Debugf("Creating %d MB hard disk image...", size) - - magicString := "data" - - buf := new(bytes.Buffer) - tw := tar.NewWriter(buf) - - // magicString first so the automount script knows to format the disk - file := &tar.Header{Name: magicString, Size: int64(len(magicString))} - if err := tw.WriteHeader(file); err != nil { - return err - } - - if _, err := tw.Write([]byte(magicString)); err != nil { - return err - } - - raw := bytes.NewReader(buf.Bytes()) - - return createDiskImage(d.diskPath(), size, raw) -} - func (d *Driver) setupHostOnlyNetwork(machineName string) error { hostOnlyCIDR := d.HostOnlyCIDR @@ -646,69 +619,6 @@ func (d *Driver) setupHostOnlyNetwork(machineName string) error { return nil } -// createDiskImage makes a disk image at dest with the given size in MB. If r is -// not nil, it will be read as a raw disk image to convert from. -func createDiskImage(dest string, size int, r io.Reader) error { - // Convert a raw image from stdin to the dest VMDK image. - sizeBytes := int64(size) << 20 // usually won't fit in 32-bit int (max 2GB) - // FIXME: why isn't this just using the vbm*() functions? - cmd := exec.Command(vboxManageCmd, "convertfromraw", "stdin", dest, - fmt.Sprintf("%d", sizeBytes), "--format", "VMDK") - - if os.Getenv("DEBUG") != "" { - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - } - - stdin, err := cmd.StdinPipe() - if err != nil { - return err - } - if err := cmd.Start(); err != nil { - return err - } - - n, err := io.Copy(stdin, r) - if err != nil { - return err - } - - // The total number of bytes written to stdin must match sizeBytes, or - // VBoxManage.exe on Windows will fail. Fill remaining with zeros. - if left := sizeBytes - n; left > 0 { - if err := zeroFill(stdin, left); err != nil { - return err - } - } - - // cmd won't exit until the stdin is closed. - if err := stdin.Close(); err != nil { - return err - } - - return cmd.Wait() -} - -// zeroFill writes n zero bytes into w. -func zeroFill(w io.Writer, n int64) error { - const blocksize = 32 << 10 - zeros := make([]byte, blocksize) - var k int - var err error - for n > 0 { - if n > blocksize { - k, err = w.Write(zeros) - } else { - k, err = w.Write(zeros[:n]) - } - if err != nil { - return err - } - n -= int64(k) - } - return nil -} - // Select an available port, trying the specified // port first, falling back on an OS selected port. func getAvailableTCPPort(port int) (int, error) { @@ -790,7 +700,7 @@ func (d *Driver) getLocalSSHClient() (ssh.Client, error) { } func translateWindowsMount(p string) (string, error) { - re := regexp.MustCompile(`(?P.+):\\(?P.*)`) + re := regexp.MustCompile(`(?P[^:]+):\\(?P.*)`) m := re.FindStringSubmatch(p) var drive, fullPath string diff --git a/test/integration/core/supported-engine-options.bats b/test/integration/core/supported-engine-options.bats index afa56678f9..3f043a115c 100644 --- a/test/integration/core/supported-engine-options.bats +++ b/test/integration/core/supported-engine-options.bats @@ -5,7 +5,7 @@ load ${BASE_TEST_DIR}/helpers.bash @test "$DRIVER: create with supported engine options" { run machine create -d $DRIVER \ --engine-label spam=eggs \ - --engine-storage-driver overlay \ + --engine-storage-driver btrfs \ --engine-insecure-registry registry.myco.com \ $NAME echo "$output" @@ -19,5 +19,5 @@ load ${BASE_TEST_DIR}/helpers.bash @test "$DRIVER: check for engine storage driver" { storage_driver_info=$(docker $(machine config $NAME) info | grep "Storage Driver") - [[ $storage_driver_info =~ "overlay" ]] + [[ $storage_driver_info =~ "btrfs" ]] } From 95c9b622ddc378516a0e76223d27415770708bc4 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Wed, 22 Jul 2015 14:39:30 -0400 Subject: [PATCH 17/36] fusion: update for b2d-ng Signed-off-by: Evan Hazlett --- drivers/vmwarefusion/fusion_darwin.go | 146 ++++++++++---------------- 1 file changed, 56 insertions(+), 90 deletions(-) diff --git a/drivers/vmwarefusion/fusion_darwin.go b/drivers/vmwarefusion/fusion_darwin.go index 30cc20c252..4be1dd8e59 100644 --- a/drivers/vmwarefusion/fusion_darwin.go +++ b/drivers/vmwarefusion/fusion_darwin.go @@ -5,17 +5,16 @@ package vmwarefusion import ( - "archive/tar" "fmt" "io/ioutil" "os" "regexp" - "runtime" "strings" "text/template" "time" "github.com/codegangsta/cli" + "github.com/docker/docker/pkg/homedir" "github.com/docker/machine/drivers" "github.com/docker/machine/log" "github.com/docker/machine/ssh" @@ -25,7 +24,7 @@ import ( const ( B2DUser = "docker" - B2DPass = "tcuser" + B2DPass = "docker" isoFilename = "boot2docker.iso" ) @@ -207,7 +206,7 @@ func (d *Driver) Create() error { log.Infof("Waiting for VM to come online...") for i := 1; i <= 60; i++ { - ip, err = d.getIPfromDHCPLease() + ip, err = d.GetIP() if err != nil { log.Debugf("Not there yet %d/%d, error: %s", i, 60, err) time.Sleep(2 * time.Second) @@ -227,40 +226,42 @@ func (d *Driver) Create() error { // we got an IP, let's copy ssh keys over d.IPAddress = ip - // Generate a tar keys bundle - if err := d.generateKeyBundle(); err != nil { + // use ssh to set keys + sshClient, err := d.getLocalSSHClient() + if err != nil { return err } - // Test if /var/lib/boot2docker exists - vmrun("-gu", B2DUser, "-gp", B2DPass, "directoryExistsInGuest", d.vmxPath(), "/var/lib/boot2docker") + // add pub key for user + pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) + if err != nil { + return err + } - // Copy SSH keys bundle - vmrun("-gu", B2DUser, "-gp", B2DPass, "CopyFileFromHostToGuest", d.vmxPath(), d.ResolveStorePath("userdata.tar"), "/home/docker/userdata.tar") + if out, err := sshClient.Output(fmt.Sprintf( + "mkdir -p /home/%s/.ssh", + d.GetSSHUsername(), + )); err != nil { + log.Error(out) + return err + } - // Expand tar file. - vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo /bin/mv /home/docker/userdata.tar /var/lib/boot2docker/userdata.tar && sudo tar xf /var/lib/boot2docker/userdata.tar -C /home/docker/ > /var/log/userdata.log 2>&1 && sudo chown -R docker:staff /home/docker") + if out, err := sshClient.Output(fmt.Sprintf( + "printf '%%s' '%s' | tee /home/%s/.ssh/authorized_keys", + string(pubKey), + d.GetSSHUsername(), + )); err != nil { + log.Error(out) + return err + } // Enable Shared Folders vmrun("-gu", B2DUser, "-gp", B2DPass, "enableSharedFolders", d.vmxPath()) - var shareName, shareDir string // TODO configurable at some point - switch runtime.GOOS { - case "darwin": - shareName = "Users" - shareDir = "/Users" - // TODO "linux" and "windows" + if err := d.setupSharedDirs(); err != nil { + return err } - if shareDir != "" { - if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { - return err - } else if !os.IsNotExist(err) { - // add shared folder, create mountpoint and mount it. - vmrun("-gu", B2DUser, "-gp", B2DPass, "addSharedFolder", d.vmxPath(), shareName, shareDir) - vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo mkdir "+shareDir+" && sudo mount -t vmhgfs .host:/"+shareName+" "+shareDir) - } - } return nil } @@ -269,21 +270,8 @@ func (d *Driver) Start() error { vmrun("start", d.vmxPath(), "nogui") log.Debugf("Mounting Shared Folders...") - var shareName, shareDir string // TODO configurable at some point - switch runtime.GOOS { - case "darwin": - shareName = "Users" - shareDir = "/Users" - // TODO "linux" and "windows" - } - - if shareDir != "" { - if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { - return err - } else if !os.IsNotExist(err) { - // create mountpoint and mount shared folder - vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo mkdir "+shareDir+" && sudo mount -t vmhgfs .host:/"+shareName+" "+shareDir) - } + if err := d.setupSharedDirs(); err != nil { + return err } return nil @@ -418,57 +406,35 @@ func (d *Driver) publicSSHKeyPath() string { return d.GetSSHKeyPath() + ".pub" } -// Make a boot2docker userdata.tar key bundle -func (d *Driver) generateKeyBundle() error { - log.Debugf("Creating Tar key bundle...") +func (d *Driver) setupSharedDirs() error { + shareDir := homedir.Get() + shareName := shareDir - magicString := "boot2docker, this is vmware speaking" - - tf, err := os.Create(d.ResolveStorePath("userdata.tar")) - if err != nil { - return err - } - defer tf.Close() - var fileWriter = tf - - tw := tar.NewWriter(fileWriter) - defer tw.Close() - - // magicString first so we can figure out who originally wrote the tar. - file := &tar.Header{Name: magicString, Size: int64(len(magicString))} - if err := tw.WriteHeader(file); err != nil { - return err - } - if _, err := tw.Write([]byte(magicString)); err != nil { - return err - } - // .ssh/key.pub => authorized_keys - file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} - if err := tw.WriteHeader(file); err != nil { - return err - } - pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) - if err != nil { - return err - } - file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} - if err := tw.WriteHeader(file); err != nil { - return err - } - if _, err := tw.Write([]byte(pubKey)); err != nil { - return err - } - file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} - if err := tw.WriteHeader(file); err != nil { - return err - } - if _, err := tw.Write([]byte(pubKey)); err != nil { - return err - } - if err := tw.Close(); err != nil { + if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { return err + } else if !os.IsNotExist(err) { + // add shared folder, create mountpoint and mount it. + vmrun("-gu", B2DUser, "-gp", B2DPass, "addSharedFolder", d.vmxPath(), shareName, shareDir) + vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo mkdir "+shareDir+" && sudo mount -t vmhgfs .host:"+shareName+" "+shareDir) } return nil - +} + +func (d *Driver) getLocalSSHClient() (ssh.Client, error) { + ip, err := d.GetIP() + if err != nil { + return nil, err + } + + sshAuth := &ssh.Auth{ + Passwords: []string{"docker"}, + Keys: []string{d.GetSSHKeyPath()}, + } + sshClient, err := ssh.NewNativeClient(d.GetSSHUsername(), ip, d.SSHPort, sshAuth) + if err != nil { + return nil, err + } + + return sshClient, nil } From cb80c434acb9a7fb20b792f449c1d5b7a0a89977 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Wed, 22 Jul 2015 16:36:07 -0400 Subject: [PATCH 18/36] b2d: use direct urls until release; devicemapper for engine opts tests Signed-off-by: Evan Hazlett b2d-ng: use driver names for iso versions Signed-off-by: Evan Hazlett --- drivers/hyperv/hyperv_windows.go | 6 +- drivers/virtualbox/virtualbox.go | 6 +- drivers/vmwarefusion/fusion_darwin.go | 4 +- drivers/vmwarevsphere/vsphere.go | 4 +- libmachine/provision/rancheros.go | 3 +- .../core/supported-engine-options.bats | 4 +- utils/b2d.go | 65 ++++++++++--------- utils/b2d_test.go | 17 +++-- 8 files changed, 62 insertions(+), 47 deletions(-) diff --git a/drivers/hyperv/hyperv_windows.go b/drivers/hyperv/hyperv_windows.go index 849cbb8ab2..d5ffd55346 100644 --- a/drivers/hyperv/hyperv_windows.go +++ b/drivers/hyperv/hyperv_windows.go @@ -17,6 +17,10 @@ import ( "github.com/docker/machine/utils" ) +const ( + isoFilename = "boot2docker-hyperv.iso" +) + type Driver struct { *drivers.BaseDriver boot2DockerURL string @@ -148,7 +152,7 @@ func (d *Driver) Create() error { var isoURL string - b2dutils := utils.NewB2dUtils("", "") + b2dutils := utils.NewB2dUtils("", "", isoFilename) if d.boot2DockerLoc == "" { if d.boot2DockerURL != "" { diff --git a/drivers/virtualbox/virtualbox.go b/drivers/virtualbox/virtualbox.go index a69853e072..3e91c097bb 100644 --- a/drivers/virtualbox/virtualbox.go +++ b/drivers/virtualbox/virtualbox.go @@ -25,7 +25,7 @@ import ( ) const ( - isoFilename = "boot2docker.iso" + isoFilename = "boot2docker-virtualbox.iso" defaultHostOnlyCIDR = "192.168.99.1/24" ) @@ -153,7 +153,7 @@ func (d *Driver) Create() error { return err } - b2dutils := utils.NewB2dUtils("", "") + b2dutils := utils.NewB2dUtils("", "", isoFilename) if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { return err } @@ -274,7 +274,7 @@ func (d *Driver) Create() error { "--port", "0", "--device", "0", "--type", "dvddrive", - "--medium", d.ResolveStorePath("boot2docker.iso")); err != nil { + "--medium", d.ResolveStorePath(isoFilename)); err != nil { return err } diff --git a/drivers/vmwarefusion/fusion_darwin.go b/drivers/vmwarefusion/fusion_darwin.go index 4be1dd8e59..3b36cba18a 100644 --- a/drivers/vmwarefusion/fusion_darwin.go +++ b/drivers/vmwarefusion/fusion_darwin.go @@ -25,7 +25,7 @@ import ( const ( B2DUser = "docker" B2DPass = "docker" - isoFilename = "boot2docker.iso" + isoFilename = "boot2docker-vmware.iso" ) // Driver for VMware Fusion @@ -160,7 +160,7 @@ func (d *Driver) PreCreateCheck() error { func (d *Driver) Create() error { - b2dutils := utils.NewB2dUtils("", "") + b2dutils := utils.NewB2dUtils("", "", isoFilename) if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { return err } diff --git a/drivers/vmwarevsphere/vsphere.go b/drivers/vmwarevsphere/vsphere.go index 829cd5e24e..2f5676f6c5 100644 --- a/drivers/vmwarevsphere/vsphere.go +++ b/drivers/vmwarevsphere/vsphere.go @@ -25,7 +25,7 @@ import ( ) const ( - isoFilename = "boot2docker.iso" + isoFilename = "boot2docker-vmware.iso" B2DISOName = isoFilename DefaultCPUNumber = 2 B2DUser = "docker" @@ -225,7 +225,7 @@ func (d *Driver) Create() error { return err } - b2dutils := utils.NewB2dUtils("", "") + b2dutils := utils.NewB2dUtils("", "", isoFilename) if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { return err } diff --git a/libmachine/provision/rancheros.go b/libmachine/provision/rancheros.go index c71a2adebe..be0ef5e3eb 100644 --- a/libmachine/provision/rancheros.go +++ b/libmachine/provision/rancheros.go @@ -19,6 +19,7 @@ import ( const ( versionsUrl = "http://releases.rancher.com/os/versions.yml" isoUrl = "https://github.com/rancherio/os/releases/download/%s/machine-rancheros.iso" + isoFilename = "rancheros.iso" hostnameTmpl = `sudo mkdir -p /var/lib/rancher/conf/cloud-config.d/ sudo tee /var/lib/rancher/conf/cloud-config.d/machine-hostname.yml << EOF #cloud-config @@ -175,7 +176,7 @@ func (provisioner *RancherProvisioner) upgradeIso() error { log.Infof("Upgrading machine %s...", machineName) - b2dutils := utils.NewB2dUtils("", "") + b2dutils := utils.NewB2dUtils("", "", isoFilename) url, err := provisioner.getLatestISOURL() if err != nil { diff --git a/test/integration/core/supported-engine-options.bats b/test/integration/core/supported-engine-options.bats index 3f043a115c..d6f06e8c2a 100644 --- a/test/integration/core/supported-engine-options.bats +++ b/test/integration/core/supported-engine-options.bats @@ -5,7 +5,7 @@ load ${BASE_TEST_DIR}/helpers.bash @test "$DRIVER: create with supported engine options" { run machine create -d $DRIVER \ --engine-label spam=eggs \ - --engine-storage-driver btrfs \ + --engine-storage-driver devicemapper \ --engine-insecure-registry registry.myco.com \ $NAME echo "$output" @@ -19,5 +19,5 @@ load ${BASE_TEST_DIR}/helpers.bash @test "$DRIVER: check for engine storage driver" { storage_driver_info=$(docker $(machine config $NAME) info | grep "Storage Driver") - [[ $storage_driver_info =~ "btrfs" ]] + [[ $storage_driver_info =~ "devicemapper" ]] } diff --git a/utils/b2d.go b/utils/b2d.go index 19f9b3c8d3..df130e1472 100644 --- a/utils/b2d.go +++ b/utils/b2d.go @@ -1,7 +1,7 @@ package utils import ( - "encoding/json" + //"encoding/json" "fmt" "io" "io/ioutil" @@ -45,11 +45,10 @@ type B2dUtils struct { githubBaseUrl string } -func NewB2dUtils(githubApiBaseUrl, githubBaseUrl string) *B2dUtils { +func NewB2dUtils(githubApiBaseUrl, githubBaseUrl, isoFilename string) *B2dUtils { defaultBaseApiUrl := "https://api.github.com" defaultBaseUrl := "https://github.com" imgCachePath := GetMachineCacheDir() - isoFilename := "boot2docker.iso" if githubApiBaseUrl == "" { githubApiBaseUrl = defaultBaseApiUrl @@ -71,35 +70,41 @@ func NewB2dUtils(githubApiBaseUrl, githubBaseUrl string) *B2dUtils { // Get the latest boot2docker release tag name (e.g. "v0.6.0"). // FIXME: find or create some other way to get the "latest release" of boot2docker since the GitHub API has a pretty low rate limit on API requests func (b *B2dUtils) GetLatestBoot2DockerReleaseURL() (string, error) { - client := getClient() - apiUrl := fmt.Sprintf("%s/repos/boot2docker/boot2docker/releases", b.githubApiBaseUrl) - rsp, err := client.Get(apiUrl) - if err != nil { - return "", err - } - defer rsp.Body.Close() + //client := getClient() + //apiUrl := fmt.Sprintf("%s/repos/boot2docker/boot2docker/releases", b.githubApiBaseUrl) + //rsp, err := client.Get(apiUrl) + //if err != nil { + // return "", err + //} + //defer rsp.Body.Close() - var t []struct { - TagName string `json:"tag_name"` - PreRelease bool `json:"prerelease"` - } - if err := json.NewDecoder(rsp.Body).Decode(&t); err != nil { - return "", fmt.Errorf("Error demarshaling the Github API response: %s\nYou may be getting rate limited by Github.", err) - } - if len(t) == 0 { - return "", fmt.Errorf("no releases found") - } + //var t []struct { + // TagName string `json:"tag_name"` + // PreRelease bool `json:"prerelease"` + //} + //if err := json.NewDecoder(rsp.Body).Decode(&t); err != nil { + // return "", fmt.Errorf("Error demarshaling the Github API response: %s\nYou may be getting rate limited by Github.", err) + //} + //if len(t) == 0 { + // return "", fmt.Errorf("no releases found") + //} + + //// find the latest "released" release (i.e. not pre-release) + //isoUrl := "" + //for _, r := range t { + // if !r.PreRelease { + // tag := r.TagName + // isoUrl = fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/%s/boot2docker.iso", b.githubBaseUrl, tag) + // break + // } + //} + //return isoUrl, nil + + // TODO: once we decide on the final versioning and location we will + // enable the above "check for latest" + u := fmt.Sprintf("https://s3.amazonaws.com/docker-mcn/public/b2d-next/%s", b.isoFilename) + return u, nil - // find the latest "released" release (i.e. not pre-release) - isoUrl := "" - for _, r := range t { - if !r.PreRelease { - tag := r.TagName - isoUrl = fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/%s/boot2docker.iso", b.githubBaseUrl, tag) - break - } - } - return isoUrl, nil } func removeFileIfExists(name string) error { diff --git a/utils/b2d_test.go b/utils/b2d_test.go index 67dbf19b42..e780a16e7b 100644 --- a/utils/b2d_test.go +++ b/utils/b2d_test.go @@ -1,7 +1,6 @@ package utils import ( - "fmt" "io/ioutil" "net/http" "net/http/httptest" @@ -16,15 +15,21 @@ func TestGetLatestBoot2DockerReleaseUrl(t *testing.T) { })) defer ts.Close() - b := NewB2dUtils(ts.URL, ts.URL) + b := NewB2dUtils(ts.URL, ts.URL, "virtualbox") isoUrl, err := b.GetLatestBoot2DockerReleaseURL() if err != nil { t.Fatal(err) } - expectedUrl := fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/0.1/boot2docker.iso", ts.URL) - if isoUrl != expectedUrl { - t.Fatalf("expected url %s; received %s", isoUrl) + // TODO: update to release URL once we get the releases worked + // out for b2d-ng + //expectedUrl := fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/0.1/boot2docker.iso", ts.URL) + //if isoUrl != expectedUrl { + // t.Fatalf("expected url %s; received %s", isoUrl) + //} + + if isoUrl == "" { + t.Fatalf("expected a url for the iso") } } @@ -42,7 +47,7 @@ func TestDownloadIso(t *testing.T) { t.Fatal(err) } - b := NewB2dUtils(ts.URL, ts.URL) + b := NewB2dUtils(ts.URL, ts.URL, "") if err := b.DownloadISO(tmpDir, filename, ts.URL); err != nil { t.Fatal(err) } From 5af751ecf11e19411e16db12be0c90b24d095b22 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Fri, 24 Jul 2015 09:38:01 -0400 Subject: [PATCH 19/36] b2d: re-enable upgrade for local Signed-off-by: Evan Hazlett --- libmachine/provision/boot2docker.go | 75 +++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/libmachine/provision/boot2docker.go b/libmachine/provision/boot2docker.go index bcbb681da3..dec955bcfa 100644 --- a/libmachine/provision/boot2docker.go +++ b/libmachine/provision/boot2docker.go @@ -1,7 +1,17 @@ package provision import ( + "errors" + "github.com/docker/machine/drivers" + "github.com/docker/machine/libmachine/provision/pkgaction" + "github.com/docker/machine/log" + "github.com/docker/machine/state" + "github.com/docker/machine/utils" +) + +var ( + ErrUnknownDriver = errors.New("unknown driver") ) func init() { @@ -29,3 +39,68 @@ func NewBoot2DockerProvisioner(d drivers.Driver) Provisioner { type Boot2DockerProvisioner struct { DebianProvisioner } + +func (provisioner *Boot2DockerProvisioner) upgradeIso() error { + log.Info("Stopping machine to do the upgrade...") + + if err := provisioner.Driver.Stop(); err != nil { + return err + + } + + if err := utils.WaitFor(drivers.MachineInState(provisioner.Driver, state.Stopped)); err != nil { + return err + + } + + machineName := provisioner.GetDriver().GetMachineName() + + log.Infof("Upgrading machine %s...", machineName) + + isoFilename := "" + switch provisioner.GetDriver().DriverName() { + case "virtualbox": + isoFilename = "boot2docker-virtualbox.iso" + case "vmwarefusion", "vmwarevsphere": + isoFilename = "boot2docker-vmware.iso" + case "hyperv": + isoFilename = "boot2docker-hyperv.iso" + default: + return ErrUnknownDriver + } + + b2dutils := utils.NewB2dUtils("", "", isoFilename) + + // Usually we call this implicitly, but call it here explicitly to get + // the latest boot2docker ISO. + if err := b2dutils.DownloadLatestBoot2Docker(); err != nil { + return err + + } + + // Copy the latest version of boot2docker ISO to the machine's directory + if err := b2dutils.CopyIsoToMachineDir("", machineName); err != nil { + return err + + } + + if err := provisioner.Driver.Start(); err != nil { + return err + + } + + return utils.WaitFor(drivers.MachineInState(provisioner.Driver, state.Running)) + +} + +func (provisioner *Boot2DockerProvisioner) Package(name string, action pkgaction.PackageAction) error { + if name == "docker" && action == pkgaction.Upgrade { + if err := provisioner.upgradeIso(); err != nil { + return err + + } + + } + return nil + +} From 7b5e887275f2d3153053f4a215a80da83f7f83a8 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Fri, 24 Jul 2015 10:20:38 -0400 Subject: [PATCH 20/36] hyperv: update to b2d-ng and some extra <3 Signed-off-by: Evan Hazlett --- drivers/hyperv/hyperv_windows.go | 218 +++++++++---------------------- 1 file changed, 65 insertions(+), 153 deletions(-) diff --git a/drivers/hyperv/hyperv_windows.go b/drivers/hyperv/hyperv_windows.go index d5ffd55346..458fdc8f40 100644 --- a/drivers/hyperv/hyperv_windows.go +++ b/drivers/hyperv/hyperv_windows.go @@ -1,12 +1,8 @@ package hyperv import ( - "archive/tar" - "bytes" "fmt" "io/ioutil" - "os" - "path/filepath" "time" "github.com/codegangsta/cli" @@ -150,55 +146,9 @@ func (d *Driver) Create() error { d.setMachineNameIfNotSet() - var isoURL string - b2dutils := utils.NewB2dUtils("", "", isoFilename) - - if d.boot2DockerLoc == "" { - if d.boot2DockerURL != "" { - isoURL = d.boot2DockerURL - log.Infof("Downloading boot2docker.iso from %s...", isoURL) - if err := b2dutils.DownloadISO(d.ResolveStorePath("."), "boot2docker.iso", isoURL); err != nil { - return err - } - } else { - // todo: check latest release URL, download if it's new - // until then always use "latest" - isoURL, err = b2dutils.GetLatestBoot2DockerReleaseURL() - if err != nil { - log.Warnf("Unable to check for the latest release: %s", err) - - } - // todo: use real constant for .docker - rootPath := filepath.Join(utils.GetDockerDir()) - imgPath := filepath.Join(rootPath, "images") - commonIsoPath := filepath.Join(imgPath, "boot2docker.iso") - if _, err := os.Stat(commonIsoPath); os.IsNotExist(err) { - log.Infof("Downloading boot2docker.iso to %s...", commonIsoPath) - // just in case boot2docker.iso has been manually deleted - if _, err := os.Stat(imgPath); os.IsNotExist(err) { - if err := os.Mkdir(imgPath, 0700); err != nil { - return err - - } - - } - if err := b2dutils.DownloadISO(imgPath, "boot2docker.iso", isoURL); err != nil { - return err - - } - - } - isoDest := d.ResolveStorePath("boot2docker.iso") - if err := utils.CopyFile(commonIsoPath, isoDest); err != nil { - return err - - } - } - } else { - if err := utils.CopyFile(d.boot2DockerLoc, d.ResolveStorePath("boot2docker.iso")); err != nil { - return err - } + if err := b2dutils.CopyIsoToMachineDir(d.boot2DockerURL, d.MachineName); err != nil { + return err } log.Infof("Creating SSH key...") @@ -214,8 +164,7 @@ func (d *Driver) Create() error { return err } - err = d.generateDiskImage() - if err != nil { + if err := d.generateDiskImage(); err != nil { return err } @@ -232,9 +181,8 @@ func (d *Driver) Create() error { command = []string{ "Set-VMDvdDrive", "-VMName", d.MachineName, - "-Path", fmt.Sprintf("'%s'", d.ResolveStorePath("boot2docker.iso"))} - _, err = execute(command) - if err != nil { + "-Path", fmt.Sprintf("'%s'", d.ResolveStorePath(isoFilename))} + if _, err = execute(command); err != nil { return err } @@ -242,8 +190,7 @@ func (d *Driver) Create() error { "Add-VMHardDiskDrive", "-VMName", d.MachineName, "-Path", fmt.Sprintf("'%s'", d.diskImage)} - _, err = execute(command) - if err != nil { + if _, err = execute(command); err != nil { return err } @@ -251,8 +198,7 @@ func (d *Driver) Create() error { "Connect-VMNetworkAdapter", "-VMName", d.MachineName, "-SwitchName", fmt.Sprintf("'%s'", virtualSwitch)} - _, err = execute(command) - if err != nil { + if _, err = execute(command); err != nil { return err } @@ -261,6 +207,35 @@ func (d *Driver) Create() error { return err } + // use ssh to set keys + sshClient, err := d.getLocalSSHClient() + if err != nil { + return err + } + + // add pub key for user + pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) + if err != nil { + return err + } + + if out, err := sshClient.Output(fmt.Sprintf( + "mkdir -p /home/%s/.ssh", + d.GetSSHUsername(), + )); err != nil { + log.Error(out) + return err + } + + if out, err := sshClient.Output(fmt.Sprintf( + "printf '%%s' '%s' | tee /home/%s/.ssh/authorized_keys", + string(pubKey), + d.GetSSHUsername(), + )); err != nil { + log.Error(out) + return err + } + return nil } @@ -298,8 +273,7 @@ func (d *Driver) Start() error { command := []string{ "Start-VM", "-Name", d.MachineName} - _, err := execute(command) - if err != nil { + if _, err := execute(command); err != nil { return err } @@ -307,16 +281,18 @@ func (d *Driver) Start() error { return err } - d.IPAddress, err = d.GetIP() - return err + if _, err := d.GetIP(); err != nil { + return err + } + + return nil } func (d *Driver) Stop() error { command := []string{ "Stop-VM", "-Name", d.MachineName} - _, err := execute(command) - if err != nil { + if _, err := execute(command); err != nil { return err } for { @@ -348,8 +324,11 @@ func (d *Driver) Remove() error { "Remove-VM", "-Name", d.MachineName, "-Force"} - _, err = execute(command) - return err + if _, err = execute(command); err != nil { + return err + } + + return nil } func (d *Driver) Restart() error { @@ -366,8 +345,7 @@ func (d *Driver) Kill() error { "Stop-VM", "-Name", d.MachineName, "-TurnOff"} - _, err := execute(command) - if err != nil { + if _, err := execute(command); err != nil { return err } for { @@ -413,101 +391,35 @@ func (d *Driver) publicSSHKeyPath() string { } func (d *Driver) generateDiskImage() error { - // Create a small fixed vhd, put the tar in, - // convert to dynamic, then resize - d.diskImage = d.ResolveStorePath("disk.vhd") - fixed := d.ResolveStorePath("fixed.vhd") log.Infof("Creating VHD") command := []string{ "New-VHD", - "-Path", fmt.Sprintf("'%s'", fixed), - "-SizeBytes", "10MB", - "-Fixed"} - _, err := execute(command) - if err != nil { - return err - } - - tarBuf, err := d.generateTar() - if err != nil { - return err - } - - file, err := os.OpenFile(fixed, os.O_WRONLY, 0644) - if err != nil { - return err - } - defer file.Close() - file.Seek(0, os.SEEK_SET) - _, err = file.Write(tarBuf.Bytes()) - if err != nil { - return err - } - file.Close() - - command = []string{ - "Convert-VHD", - "-Path", fmt.Sprintf("'%s'", fixed), - "-DestinationPath", fmt.Sprintf("'%s'", d.diskImage), - "-VHDType", "Dynamic"} - _, err = execute(command) - if err != nil { - return err - } - command = []string{ - "Resize-VHD", "-Path", fmt.Sprintf("'%s'", d.diskImage), - "-SizeBytes", fmt.Sprintf("%dMB", d.diskSize)} - _, err = execute(command) - if err != nil { + "-SizeBytes", fmt.Sprintf("%dMB", d.diskSize), + } + + if _, err := execute(command); err != nil { return err } - return err + return nil } -// Make a boot2docker VM disk image. -// See https://github.com/boot2docker/boot2docker/blob/master/rootfs/rootfs/etc/rc.d/automount -func (d *Driver) generateTar() (*bytes.Buffer, error) { - magicString := "boot2docker, please format-me" - - buf := new(bytes.Buffer) - tw := tar.NewWriter(buf) - - // magicString first so the automount script knows to format the disk - file := &tar.Header{Name: magicString, Size: int64(len(magicString))} - if err := tw.WriteHeader(file); err != nil { - return nil, err - } - if _, err := tw.Write([]byte(magicString)); err != nil { - return nil, err - } - // .ssh/key.pub => authorized_keys - file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} - if err := tw.WriteHeader(file); err != nil { - return nil, err - } - pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) +func (d *Driver) getLocalSSHClient() (ssh.Client, error) { + ip, err := d.GetIP() if err != nil { return nil, err } - file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} - if err := tw.WriteHeader(file); err != nil { + + sshAuth := &ssh.Auth{ + Passwords: []string{"docker"}, + Keys: []string{d.GetSSHKeyPath()}, + } + sshClient, err := ssh.NewNativeClient(d.GetSSHUsername(), ip, d.SSHPort, sshAuth) + if err != nil { return nil, err } - if _, err := tw.Write([]byte(pubKey)); err != nil { - return nil, err - } - file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} - if err := tw.WriteHeader(file); err != nil { - return nil, err - } - if _, err := tw.Write([]byte(pubKey)); err != nil { - return nil, err - } - if err := tw.Close(); err != nil { - return nil, err - } - return buf, nil + + return sshClient, nil } From 5f972ad759f63c0c8c45021ead4d3d30f65e8f71 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Fri, 24 Jul 2015 12:31:36 -0400 Subject: [PATCH 21/36] fusion: @tianon to the rescue <3; shared folders work again Signed-off-by: Evan Hazlett --- drivers/vmwarefusion/fusion_darwin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/vmwarefusion/fusion_darwin.go b/drivers/vmwarefusion/fusion_darwin.go index 3b36cba18a..48c75223a5 100644 --- a/drivers/vmwarefusion/fusion_darwin.go +++ b/drivers/vmwarefusion/fusion_darwin.go @@ -408,14 +408,14 @@ func (d *Driver) publicSSHKeyPath() string { func (d *Driver) setupSharedDirs() error { shareDir := homedir.Get() - shareName := shareDir + shareName := "Home" if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { return err } else if !os.IsNotExist(err) { // add shared folder, create mountpoint and mount it. vmrun("-gu", B2DUser, "-gp", B2DPass, "addSharedFolder", d.vmxPath(), shareName, shareDir) - vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo mkdir "+shareDir+" && sudo mount -t vmhgfs .host:"+shareName+" "+shareDir) + vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo mkdir "+shareDir+" && sudo mount -t vmhgfs .host:/"+shareName+" "+shareDir) } return nil From 030783af746653c8b19eb5a6a726bbb3b470d970 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Fri, 24 Jul 2015 13:15:00 -0400 Subject: [PATCH 22/36] swarm: use --advertise instead of --addr for join Signed-off-by: Evan Hazlett --- libmachine/provision/configure_swarm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmachine/provision/configure_swarm.go b/libmachine/provision/configure_swarm.go index 9335ba343d..1fa131cde5 100644 --- a/libmachine/provision/configure_swarm.go +++ b/libmachine/provision/configure_swarm.go @@ -101,7 +101,7 @@ manage \ --restart=always \ --name swarm-agent \ {{.SwarmImage}} \ -join --addr {{.Ip}}:{{.DockerPort}} {{.SwarmOptions.Discovery}} +join --advertise {{.Ip}}:{{.DockerPort}} {{.SwarmOptions.Discovery}} ` if swarmOptions.Master { From 65b5624221f644ca89db812935756a280b0a31dc Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Fri, 24 Jul 2015 13:33:46 -0400 Subject: [PATCH 23/36] proper check for hyper-v during upgrade Signed-off-by: Evan Hazlett --- libmachine/provision/boot2docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmachine/provision/boot2docker.go b/libmachine/provision/boot2docker.go index dec955bcfa..fcd09e3d2b 100644 --- a/libmachine/provision/boot2docker.go +++ b/libmachine/provision/boot2docker.go @@ -63,7 +63,7 @@ func (provisioner *Boot2DockerProvisioner) upgradeIso() error { isoFilename = "boot2docker-virtualbox.iso" case "vmwarefusion", "vmwarevsphere": isoFilename = "boot2docker-vmware.iso" - case "hyperv": + case "hyper-v": isoFilename = "boot2docker-hyperv.iso" default: return ErrUnknownDriver From 0b03589187f143d8113f629f2cb67ec19cdc6adb Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Fri, 24 Jul 2015 17:34:07 -0400 Subject: [PATCH 24/36] fusion: use mkdir -p for home dir mount Signed-off-by: Evan Hazlett --- drivers/vmwarefusion/fusion_darwin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/vmwarefusion/fusion_darwin.go b/drivers/vmwarefusion/fusion_darwin.go index 48c75223a5..fbfdef00ea 100644 --- a/drivers/vmwarefusion/fusion_darwin.go +++ b/drivers/vmwarefusion/fusion_darwin.go @@ -415,7 +415,7 @@ func (d *Driver) setupSharedDirs() error { } else if !os.IsNotExist(err) { // add shared folder, create mountpoint and mount it. vmrun("-gu", B2DUser, "-gp", B2DPass, "addSharedFolder", d.vmxPath(), shareName, shareDir) - vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo mkdir "+shareDir+" && sudo mount -t vmhgfs .host:/"+shareName+" "+shareDir) + vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo mkdir -p "+shareDir+" && sudo mount -t vmhgfs .host:/"+shareName+" "+shareDir) } return nil From 79b1af08e89f0f12fc4c70069281d5369295479d Mon Sep 17 00:00:00 2001 From: Joey Geiger Date: Sun, 26 Jul 2015 09:13:16 -0600 Subject: [PATCH 25/36] Remove specific version from link to binaries. Signed-off-by: Joey Geiger --- docs/install-machine.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install-machine.md b/docs/install-machine.md index 89e87a1aa9..41a40d334c 100644 --- a/docs/install-machine.md +++ b/docs/install-machine.md @@ -14,7 +14,7 @@ weight=3 Docker Machine is supported on Windows, OS X, and Linux and is installable as one standalone binary. The links to the binaries for the various platforms and architectures are available at the [Github -Release](https://github.com/docker/machine/releases/tag/v0.3.1) page. +Release](https://github.com/docker/machine/releases/) page. ### OS X and Linux From 8a02efe0c98566c97c94298775d7e57b79bac1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Czeraszkiewicz?= Date: Tue, 28 Jul 2015 15:21:38 +0200 Subject: [PATCH 26/36] Documentation typo update --- docs/reference/restart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/restart.md b/docs/reference/restart.md index 243804dc95..227ed03c87 100644 --- a/docs/reference/restart.md +++ b/docs/reference/restart.md @@ -11,7 +11,7 @@ parent="smn_machine_subcmds" # restart Restart a machine. Oftentimes this is equivalent to -`docker-machine stop; machine start`. +`docker-machine stop; docker-machine start`. ``` $ docker-machine restart dev From fb2e843e99e28088a217bbadd280e7574bd30bac Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Fri, 17 Jul 2015 18:02:02 -0700 Subject: [PATCH 27/36] - Re-introduce config migration; fix panics occurring from older configs - Introduce boilerplate for config.json migrations Signed-off-by: Nathan LeClaire --- drivers/openstack/client.go | 2 +- drivers/rackspace/client.go | 2 +- libmachine/filestore.go | 16 +- libmachine/host.go | 84 +++++----- libmachine/host_v0.go | 34 ++++ libmachine/migrate.go | 157 ++++++------------ libmachine/migrate_v0_v1.go | 113 +++++++++++++ ...{migrate_test.go => migrate_v0_v1_test.go} | 22 +-- main.go | 2 +- script/build | 2 +- version/version.go | 13 +- 11 files changed, 270 insertions(+), 177 deletions(-) create mode 100644 libmachine/host_v0.go create mode 100644 libmachine/migrate_v0_v1.go rename libmachine/{migrate_test.go => migrate_v0_v1_test.go} (85%) diff --git a/drivers/openstack/client.go b/drivers/openstack/client.go index c2964512e4..72caa262df 100644 --- a/drivers/openstack/client.go +++ b/drivers/openstack/client.go @@ -437,7 +437,7 @@ func (c *GenericClient) Authenticate(d *Driver) error { return err } - provider.UserAgent.Prepend(fmt.Sprintf("docker-machine/v%s", version.VERSION)) + provider.UserAgent.Prepend(fmt.Sprintf("docker-machine/v%s", version.Version)) if d.Insecure { // Configure custom TLS settings. diff --git a/drivers/rackspace/client.go b/drivers/rackspace/client.go index bca5a784cf..c506a4578c 100644 --- a/drivers/rackspace/client.go +++ b/drivers/rackspace/client.go @@ -42,7 +42,7 @@ func (c *Client) Authenticate(d *openstack.Driver) error { return err } - provider.UserAgent.Prepend(fmt.Sprintf("docker-machine/v%s", version.VERSION)) + provider.UserAgent.Prepend(fmt.Sprintf("docker-machine/v%s", version.Version)) err = rackspace.Authenticate(provider, opts) if err != nil { diff --git a/libmachine/filestore.go b/libmachine/filestore.go index 7cdebb6394..d2c4115848 100644 --- a/libmachine/filestore.go +++ b/libmachine/filestore.go @@ -19,7 +19,11 @@ type Filestore struct { } func NewFilestore(rootPath string, caCert string, privateKey string) *Filestore { - return &Filestore{path: rootPath, caCertPath: caCert, privateKeyPath: privateKey} + return &Filestore{ + path: rootPath, + caCertPath: caCert, + privateKeyPath: privateKey, + } } func (s Filestore) loadHost(name string) (*Host, error) { @@ -30,13 +34,15 @@ func (s Filestore) loadHost(name string) (*Host, error) { } } - host := &Host{Name: name, StorePath: hostPath} + host := &Host{ + Name: name, + StorePath: hostPath, + } if err := host.LoadConfig(); err != nil { return nil, err } - h := FillNestedHost(host) - return h, nil + return host, nil } func (s Filestore) GetPath() string { @@ -85,7 +91,7 @@ func (s Filestore) List() ([]*Host, error) { for _, file := range dir { // don't load hidden dirs; used for configs - if file.IsDir() && strings.Index(file.Name(), ".") != 0 { + if file.IsDir() && !strings.HasPrefix(file.Name(), ".") { host, err := s.Get(file.Name()) if err != nil { log.Errorf("error loading host %q: %s", file.Name(), err) diff --git a/libmachine/host.go b/libmachine/host.go index 83a1b46a61..671f150e57 100644 --- a/libmachine/host.go +++ b/libmachine/host.go @@ -19,6 +19,7 @@ import ( "github.com/docker/machine/ssh" "github.com/docker/machine/state" "github.com/docker/machine/utils" + "github.com/docker/machine/version" ) var ( @@ -28,22 +29,12 @@ var ( ) type Host struct { - Name string `json:"-"` - DriverName string - Driver drivers.Driver - StorePath string - HostOptions *HostOptions - - // deprecated options; these are left to assist in config migrations - SwarmHost string - SwarmMaster bool - SwarmDiscovery string - CaCertPath string - PrivateKeyPath string - ServerCertPath string - ServerKeyPath string - ClientCertPath string - ClientKeyPath string + ConfigVersion int + Driver drivers.Driver + DriverName string + HostOptions *HostOptions + Name string `json:"-"` + StorePath string } type HostOptions struct { @@ -56,14 +47,9 @@ type HostOptions struct { } type HostMetadata struct { - DriverName string - HostOptions HostOptions - StorePath string - CaCertPath string - PrivateKeyPath string - ServerCertPath string - ServerKeyPath string - ClientCertPath string + ConfigVersion int + DriverName string + HostOptions HostOptions } type HostListItem struct { @@ -75,6 +61,14 @@ type HostListItem struct { SwarmOptions swarm.SwarmOptions } +type ErrSavingConfig struct { + wrappedErr error +} + +func (e ErrSavingConfig) Error() string { + return fmt.Sprintf("Error saving config: %s", e.wrappedErr) +} + func NewHost(name, driverName string, hostOptions *HostOptions) (*Host, error) { authOptions := hostOptions.AuthOptions storePath := filepath.Join(utils.GetMachineDir(), name) @@ -83,11 +77,12 @@ func NewHost(name, driverName string, hostOptions *HostOptions) (*Host, error) { return nil, err } return &Host{ - Name: name, - DriverName: driverName, - Driver: driver, - StorePath: storePath, - HostOptions: hostOptions, + Name: name, + ConfigVersion: version.ConfigVersion, + DriverName: driverName, + Driver: driver, + StorePath: storePath, + HostOptions: hostOptions, }, nil } @@ -100,6 +95,7 @@ func LoadHost(name string, StorePath string) (*Host, error) { if err := host.LoadConfig(); err != nil { return nil, err } + return host, nil } @@ -287,26 +283,28 @@ func (h *Host) LoadConfig() error { return err } - // First pass: find the driver name and load the driver - var hostMetadata HostMetadata - if err := json.Unmarshal(data, &hostMetadata); err != nil { - return err - } + // Remember the machine name and store path so we don't have to pass it + // through each struct in the migration. + name := h.Name + storePath := h.StorePath - meta := FillNestedHostMetadata(&hostMetadata) + // If we end up performing a migration, we should save afterwards so we don't have to do it again on subsequent invocations. + migrationPerformed := h.ConfigVersion != version.ConfigVersion - authOptions := meta.HostOptions.AuthOptions - - driver, err := drivers.NewDriver(hostMetadata.DriverName, h.Name, h.StorePath, authOptions.CaCertPath, authOptions.PrivateKeyPath) + migratedHost, err := MigrateHost(h, data) if err != nil { - return err + return fmt.Errorf("Error getting migrated host: %s", err) } - h.Driver = driver + *h = *migratedHost - // Second pass: unmarshal driver config into correct driver - if err := json.Unmarshal(data, &h); err != nil { - return err + h.Name = name + h.StorePath = storePath + + if migrationPerformed { + if err := h.SaveConfig(); err != nil { + return fmt.Errorf("Error saving config after migration was performed: %s", err) + } } return nil diff --git a/libmachine/host_v0.go b/libmachine/host_v0.go new file mode 100644 index 0000000000..8c1d63839d --- /dev/null +++ b/libmachine/host_v0.go @@ -0,0 +1,34 @@ +package libmachine + +import "github.com/docker/machine/drivers" + +type HostV0 struct { + Name string `json:"-"` + Driver drivers.Driver + DriverName string + ConfigVersion int + HostOptions *HostOptions + + StorePath string + CaCertPath string + PrivateKeyPath string + ServerCertPath string + ServerKeyPath string + ClientCertPath string + SwarmHost string + SwarmMaster bool + SwarmDiscovery string + ClientKeyPath string +} + +type HostMetadataV0 struct { + HostOptions HostOptions + DriverName string + + StorePath string + CaCertPath string + PrivateKeyPath string + ServerCertPath string + ServerKeyPath string + ClientCertPath string +} diff --git a/libmachine/migrate.go b/libmachine/migrate.go index 69f062e016..80555ec810 100644 --- a/libmachine/migrate.go +++ b/libmachine/migrate.go @@ -1,122 +1,65 @@ package libmachine import ( - "path/filepath" + "encoding/json" + "fmt" - "github.com/docker/machine/libmachine/auth" - "github.com/docker/machine/libmachine/engine" - "github.com/docker/machine/libmachine/swarm" - "github.com/docker/machine/utils" + "github.com/docker/machine/drivers" + "github.com/docker/machine/version" ) -// In the 0.0.1 => 0.0.2 transition, the JSON representation of -// machines changed from a "flat" to a more "nested" structure -// for various options and configuration settings. To preserve -// compatibility with existing machines, these migration functions -// have been introduced. They preserve backwards compat at the expense -// of some duplicated information. +func getMigratedHostMetadata(data []byte) (*HostMetadata, error) { + // HostMetadata is for a "first pass" so we can then load the driver + var ( + hostMetadata *HostMetadataV0 + ) -// validates host config and modifies if needed -// this is used for configuration updates -func FillNestedHost(host *Host) *Host { - certInfo := getCertInfoFromHost(host) - - if host.HostOptions == nil { - host.HostOptions = &HostOptions{} - } - if host.HostOptions.EngineOptions == nil { - host.HostOptions.EngineOptions = &engine.EngineOptions{} + if err := json.Unmarshal(data, &hostMetadata); err != nil { + return &HostMetadata{}, err } - if host.HostOptions.SwarmOptions == nil { - host.HostOptions.SwarmOptions = &swarm.SwarmOptions{ - Address: "", - Discovery: host.SwarmDiscovery, - Host: host.SwarmHost, - Master: host.SwarmMaster, + migratedHostMetadata := MigrateHostMetadataV0ToHostMetadataV1(hostMetadata) + + return migratedHostMetadata, nil +} + +func MigrateHost(h *Host, data []byte) (*Host, error) { + migratedHostMetadata, err := getMigratedHostMetadata(data) + if err != nil { + return &Host{}, err + } + + authOptions := migratedHostMetadata.HostOptions.AuthOptions + + driver, err := drivers.NewDriver( + migratedHostMetadata.DriverName, + h.Name, + h.StorePath, + authOptions.CaCertPath, + authOptions.PrivateKeyPath, + ) + if err != nil { + return &Host{}, err + } + + for h.ConfigVersion = migratedHostMetadata.ConfigVersion; h.ConfigVersion < version.ConfigVersion; h.ConfigVersion++ { + switch h.ConfigVersion { + case 0: + hostV0 := &HostV0{ + Driver: driver, + } + if err := json.Unmarshal(data, &hostV0); err != nil { + return &Host{}, fmt.Errorf("Error unmarshalling host config version 0: %s", err) + } + h = MigrateHostV0ToHostV1(hostV0) + default: } } - host.HostOptions.AuthOptions = &auth.AuthOptions{ - StorePath: host.StorePath, - CaCertPath: certInfo.CaCertPath, - CaCertRemotePath: "", - ServerCertPath: certInfo.ServerCertPath, - ServerKeyPath: certInfo.ServerKeyPath, - ClientKeyPath: certInfo.ClientKeyPath, - ServerCertRemotePath: "", - ServerKeyRemotePath: "", - PrivateKeyPath: certInfo.CaKeyPath, - ClientCertPath: certInfo.ClientCertPath, + h.Driver = driver + if err := json.Unmarshal(data, &h); err != nil { + return &Host{}, fmt.Errorf("Error unmarshalling most recent host version: %s", err) } - return host -} - -// fills nested host metadata and modifies if needed -// this is used for configuration updates -func FillNestedHostMetadata(m *HostMetadata) *HostMetadata { - if m.HostOptions.EngineOptions == nil { - m.HostOptions.EngineOptions = &engine.EngineOptions{} - } - - if m.HostOptions.AuthOptions == nil { - m.HostOptions.AuthOptions = &auth.AuthOptions{ - StorePath: m.StorePath, - CaCertPath: m.CaCertPath, - CaCertRemotePath: "", - ServerCertPath: m.ServerCertPath, - ServerKeyPath: m.ServerKeyPath, - ClientKeyPath: "", - ServerCertRemotePath: "", - ServerKeyRemotePath: "", - PrivateKeyPath: m.PrivateKeyPath, - ClientCertPath: m.ClientCertPath, - } - } - - return m -} - -func getCertInfoFromHost(h *Host) CertPathInfo { - // setup cert paths - caCertPath := h.CaCertPath - caKeyPath := h.PrivateKeyPath - clientCertPath := h.ClientCertPath - clientKeyPath := h.ClientKeyPath - serverCertPath := h.ServerCertPath - serverKeyPath := h.ServerKeyPath - - if caCertPath == "" { - caCertPath = filepath.Join(utils.GetMachineCertDir(), "ca.pem") - } - - if caKeyPath == "" { - caKeyPath = filepath.Join(utils.GetMachineCertDir(), "ca-key.pem") - } - - if clientCertPath == "" { - clientCertPath = filepath.Join(utils.GetMachineCertDir(), "cert.pem") - } - - if clientKeyPath == "" { - clientKeyPath = filepath.Join(utils.GetMachineCertDir(), "key.pem") - } - - if serverCertPath == "" { - serverCertPath = filepath.Join(utils.GetMachineCertDir(), "server.pem") - } - - if serverKeyPath == "" { - serverKeyPath = filepath.Join(utils.GetMachineCertDir(), "server-key.pem") - } - - return CertPathInfo{ - CaCertPath: caCertPath, - CaKeyPath: caKeyPath, - ClientCertPath: clientCertPath, - ClientKeyPath: clientKeyPath, - ServerCertPath: serverCertPath, - ServerKeyPath: serverKeyPath, - } + return h, nil } diff --git a/libmachine/migrate_v0_v1.go b/libmachine/migrate_v0_v1.go new file mode 100644 index 0000000000..61916da407 --- /dev/null +++ b/libmachine/migrate_v0_v1.go @@ -0,0 +1,113 @@ +package libmachine + +import ( + "path/filepath" + + "github.com/docker/machine/libmachine/auth" + "github.com/docker/machine/libmachine/engine" + "github.com/docker/machine/libmachine/swarm" + "github.com/docker/machine/utils" +) + +// In the 0.0.1 => 0.0.2 transition, the JSON representation of +// machines changed from a "flat" to a more "nested" structure +// for various options and configuration settings. To preserve +// compatibility with existing machines, these migration functions +// have been introduced. They preserve backwards compat at the expense +// of some duplicated information. + +// validates host config and modifies if needed +// this is used for configuration updates +func MigrateHostV0ToHostV1(hostV0 *HostV0) *Host { + host := &Host{} + + certInfoV0 := getCertInfoFromHost(hostV0) + + host.HostOptions = &HostOptions{} + host.HostOptions.EngineOptions = &engine.EngineOptions{} + host.HostOptions.SwarmOptions = &swarm.SwarmOptions{ + Address: "", + Discovery: hostV0.SwarmDiscovery, + Host: hostV0.SwarmHost, + Master: hostV0.SwarmMaster, + } + host.HostOptions.AuthOptions = &auth.AuthOptions{ + StorePath: hostV0.StorePath, + CaCertPath: certInfoV0.CaCertPath, + CaCertRemotePath: "", + ServerCertPath: certInfoV0.ServerCertPath, + ServerKeyPath: certInfoV0.ServerKeyPath, + ClientKeyPath: certInfoV0.ClientKeyPath, + ServerCertRemotePath: "", + ServerKeyRemotePath: "", + PrivateKeyPath: certInfoV0.CaKeyPath, + ClientCertPath: certInfoV0.ClientCertPath, + } + + return host +} + +// fills nested host metadata and modifies if needed +// this is used for configuration updates +func MigrateHostMetadataV0ToHostMetadataV1(m *HostMetadataV0) *HostMetadata { + hostMetadata := &HostMetadata{} + hostMetadata.DriverName = m.DriverName + hostMetadata.HostOptions.EngineOptions = &engine.EngineOptions{} + hostMetadata.HostOptions.AuthOptions = &auth.AuthOptions{ + StorePath: m.StorePath, + CaCertPath: m.CaCertPath, + CaCertRemotePath: "", + ServerCertPath: m.ServerCertPath, + ServerKeyPath: m.ServerKeyPath, + ClientKeyPath: "", + ServerCertRemotePath: "", + ServerKeyRemotePath: "", + PrivateKeyPath: m.PrivateKeyPath, + ClientCertPath: m.ClientCertPath, + } + + return hostMetadata +} + +func getCertInfoFromHost(h *HostV0) CertPathInfo { + // setup cert paths + caCertPath := h.CaCertPath + caKeyPath := h.PrivateKeyPath + clientCertPath := h.ClientCertPath + clientKeyPath := h.ClientKeyPath + serverCertPath := h.ServerCertPath + serverKeyPath := h.ServerKeyPath + + if caCertPath == "" { + caCertPath = filepath.Join(utils.GetMachineCertDir(), "ca.pem") + } + + if caKeyPath == "" { + caKeyPath = filepath.Join(utils.GetMachineCertDir(), "ca-key.pem") + } + + if clientCertPath == "" { + clientCertPath = filepath.Join(utils.GetMachineCertDir(), "cert.pem") + } + + if clientKeyPath == "" { + clientKeyPath = filepath.Join(utils.GetMachineCertDir(), "key.pem") + } + + if serverCertPath == "" { + serverCertPath = filepath.Join(utils.GetMachineCertDir(), "server.pem") + } + + if serverKeyPath == "" { + serverKeyPath = filepath.Join(utils.GetMachineCertDir(), "server-key.pem") + } + + return CertPathInfo{ + CaCertPath: caCertPath, + CaKeyPath: caKeyPath, + ClientCertPath: clientCertPath, + ClientKeyPath: clientKeyPath, + ServerCertPath: serverCertPath, + ServerKeyPath: serverKeyPath, + } +} diff --git a/libmachine/migrate_test.go b/libmachine/migrate_v0_v1_test.go similarity index 85% rename from libmachine/migrate_test.go rename to libmachine/migrate_v0_v1_test.go index 57c726cf1f..9b6cacb15b 100644 --- a/libmachine/migrate_test.go +++ b/libmachine/migrate_v0_v1_test.go @@ -10,9 +10,9 @@ import ( "github.com/docker/machine/libmachine/swarm" ) -func TestFillNestedHost(t *testing.T) { +func TestMigrateHostV0ToV1(t *testing.T) { os.Setenv("MACHINE_STORAGE_PATH", "/tmp/migration") - originalHost := &Host{ + originalHost := &HostV0{ HostOptions: nil, SwarmDiscovery: "token://foobar", SwarmHost: "1.2.3.4:2376", @@ -42,13 +42,10 @@ func TestFillNestedHost(t *testing.T) { } expectedHost := &Host{ - SwarmHost: "1.2.3.4:2376", - SwarmDiscovery: "token://foobar", - SwarmMaster: true, - HostOptions: hostOptions, + HostOptions: hostOptions, } - host := FillNestedHost(originalHost) + host := MigrateHostV0ToHostV1(originalHost) if !reflect.DeepEqual(host, expectedHost) { t.Logf("\n%+v\n%+v", host, expectedHost) @@ -57,8 +54,8 @@ func TestFillNestedHost(t *testing.T) { } } -func TestFillNestedHostMetadata(t *testing.T) { - metadata := &HostMetadata{ +func TestMigrateHostMetadataV0ToV1(t *testing.T) { + metadata := &HostMetadataV0{ HostOptions: HostOptions{ EngineOptions: nil, AuthOptions: nil, @@ -78,12 +75,9 @@ func TestFillNestedHostMetadata(t *testing.T) { EngineOptions: &engine.EngineOptions{}, AuthOptions: expectedAuthOptions, }, - StorePath: "/tmp/store", - CaCertPath: "/tmp/store/certs/ca.pem", - ServerCertPath: "/tmp/store/certs/server.pem", } - m := FillNestedHostMetadata(metadata) + m := MigrateHostMetadataV0ToHostMetadataV1(metadata) if !reflect.DeepEqual(m, expectedMetadata) { t.Logf("\n%+v\n%+v", m, expectedMetadata) @@ -95,7 +89,7 @@ func TestFillNestedHostMetadata(t *testing.T) { // due to a schema migration from "flat" to a "nested" structure. func TestGetCertInfoFromHost(t *testing.T) { os.Setenv("MACHINE_STORAGE_PATH", "/tmp/migration") - host := &Host{ + host := &HostV0{ CaCertPath: "", PrivateKeyPath: "", ClientCertPath: "", diff --git a/main.go b/main.go index a37bf1d908..7f640babd0 100644 --- a/main.go +++ b/main.go @@ -67,7 +67,7 @@ func main() { app.Commands = commands.Commands app.CommandNotFound = cmdNotFound app.Usage = "Create and manage machines running Docker." - app.Version = version.VERSION + " (" + version.GITCOMMIT + ")" + app.Version = version.Version + " (" + version.GitCommit + ")" app.Flags = []cli.Flag{ cli.BoolFlag{ diff --git a/script/build b/script/build index 07efc57066..385333db23 100755 --- a/script/build +++ b/script/build @@ -21,4 +21,4 @@ fi # Get rid of existing binaries rm -f docker-machine* rm -rf Godeps/_workspace/pkg -docker run --rm -v `pwd`:/go/src/github.com/docker/machine docker-machine gox "${OS_PLATFORM_ARG[@]}" "${OS_ARCH_ARG[@]}" -output="docker-machine_{{.OS}}-{{.Arch}}" -ldflags="-w -X github.com/docker/machine/version.GITCOMMIT `git rev-parse --short HEAD`" +docker run --rm -v `pwd`:/go/src/github.com/docker/machine docker-machine gox "${OS_PLATFORM_ARG[@]}" "${OS_ARCH_ARG[@]}" -output="docker-machine_{{.OS}}-{{.Arch}}" -ldflags="-w -X github.com/docker/machine/version.GitCommit `git rev-parse --short HEAD`" diff --git a/version/version.go b/version/version.go index e1e0d6b9a9..1fef2e53db 100644 --- a/version/version.go +++ b/version/version.go @@ -1,9 +1,14 @@ package version var ( - // VERSION should be updated by hand at each release - VERSION = "0.4.0-dev" + // ConfigVersion dictates which version of the config.json format is + // used. It needs to be bumped if there is a breaking change, and + // therefore migration, introduced to the config file format. + ConfigVersion = 1 - // GITCOMMIT will be overwritten automatically by the build system - GITCOMMIT = "HEAD" + // Version should be updated by hand at each release + Version = "0.4.0-dev" + + // GitCommit will be overwritten automatically by the build system + GitCommit = "HEAD" ) From 802393f25376ec4c56e2c6f8128e0c5a5f51bee6 Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Tue, 28 Jul 2015 16:17:53 -0700 Subject: [PATCH 28/36] Skip and warn on wonky /etc/os-release lines If the lines don't split cleanly (occasionally STDERR gets mixed in, for instance, due to our current SSH output setup), we should simply log.Warn in the output instead of bailing completely. Signed-off-by: Nathan LeClaire --- libmachine/provision/os_release.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libmachine/provision/os_release.go b/libmachine/provision/os_release.go index 9cc3d8e563..e10e1077fb 100644 --- a/libmachine/provision/os_release.go +++ b/libmachine/provision/os_release.go @@ -69,7 +69,8 @@ func (osr *OsRelease) ParseOsRelease(osReleaseContents []byte) error { for scanner.Scan() { key, val, err := parseLine(scanner.Text()) if err != nil { - return err + log.Warn("Warning: got an invalid line error parsing /etc/os-release: %s", err) + continue } if err := osr.setIfPossible(key, val); err != nil { log.Debug(err) From fb0af73b806153d0aa6d0dadbc8970672eb8f1d2 Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Tue, 28 Jul 2015 20:47:39 -0400 Subject: [PATCH 29/36] Verifying the --swarm-discovery flag Signed-off-by: Dave Henderson --- commands/create.go | 22 ++++++++++++++++++++++ commands/create_test.go | 21 +++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/commands/create.go b/commands/create.go index e69ac3c539..5d9416b939 100644 --- a/commands/create.go +++ b/commands/create.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "path/filepath" + "regexp" "github.com/docker/machine/log" @@ -35,6 +36,10 @@ func cmdCreate(c *cli.Context) { log.Fatal("You must specify a machine name") } + if err := validateSwarmDiscovery(c.String("swarm-discovery")); err != nil { + log.Fatalf("Error parsing swarm discovery: %s", err) + } + certInfo := getCertPathInfo(c) if err := setupCertificates( @@ -118,3 +123,20 @@ func trimDriverFlags(driver string, cmds []cli.Command) ([]cli.Command, error) { return filteredCmds, nil } + +func validateSwarmDiscovery(discovery string) error { + if discovery == "" { + return nil + } + + matched, err := regexp.MatchString(`[^:]*://.*`, discovery) + if err != nil { + return err + } + + if matched { + return nil + } + + return fmt.Errorf("Swarm Discovery URL was in the wrong format: %s", discovery) +} diff --git a/commands/create_test.go b/commands/create_test.go index cdff10da75..a4e39d0252 100644 --- a/commands/create_test.go +++ b/commands/create_test.go @@ -1 +1,22 @@ package commands + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidateSwarmDiscoveryErrorsGivenInvalidURL(t *testing.T) { + err := validateSwarmDiscovery("foo") + assert.Error(t, err) +} + +func TestValidateSwarmDiscoveryAcceptsEmptyString(t *testing.T) { + err := validateSwarmDiscovery("") + assert.NoError(t, err) +} + +func TestValidateSwarmDiscoveryAcceptsValidFormat(t *testing.T) { + err := validateSwarmDiscovery("token://deadbeefcafe") + assert.NoError(t, err) +} From aba294cbb1ec4af8f35e9403fc3c4bd328255735 Mon Sep 17 00:00:00 2001 From: Soshi Katsuta Date: Thu, 30 Jul 2015 19:12:26 +0900 Subject: [PATCH 30/36] libmachine: return an error if DOCKER_HOST is empty Signed-off-by: Soshi Katsuta --- libmachine/filestore.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libmachine/filestore.go b/libmachine/filestore.go index d2c4115848..aa2651ff68 100644 --- a/libmachine/filestore.go +++ b/libmachine/filestore.go @@ -126,6 +126,10 @@ func (s Filestore) GetActive() (*Host, error) { } dockerHost := os.Getenv("DOCKER_HOST") + if dockerHost == "" { + return nil, errors.New("DOCKER_HOST not set") + } + hostListItems := GetHostListItems(hosts) for _, item := range hostListItems { From e9c285cf6a53199e4612441cdf3b99a29ff65514 Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Mon, 27 Jul 2015 14:23:47 -0700 Subject: [PATCH 31/36] Add Timeout state to ls command Signed-off-by: Nathan LeClaire --- libmachine/host.go | 29 +++++++++++++++++++++++++++-- state/state.go | 2 ++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/libmachine/host.go b/libmachine/host.go index 59a62845fe..d680d5d735 100644 --- a/libmachine/host.go +++ b/libmachine/host.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "regexp" + "time" "github.com/docker/machine/drivers" "github.com/docker/machine/libmachine/auth" @@ -25,6 +26,7 @@ var ( validHostNameChars = `[a-zA-Z0-9\-\.]` validHostNamePattern = regexp.MustCompile(`^` + validHostNameChars + `+$`) errMachineMustBeRunningForUpgrade = errors.New("Error: machine must be running to upgrade.") + stateTimeoutDuration = time.Second * 3 ) type Host struct { @@ -346,7 +348,7 @@ func WaitForSSH(h *Host) error { return drivers.WaitForSSH(h.Driver) } -func getHostState(host Host, hostListItemsChan chan<- HostListItem) { +func attemptGetHostState(host Host, stateQueryChan chan<- HostListItem) { currentState, err := host.Driver.GetState() if err != nil { log.Errorf("error getting state for host %s: %s", host.Name, err) @@ -363,7 +365,7 @@ func getHostState(host Host, hostListItemsChan chan<- HostListItem) { dockerHost := os.Getenv("DOCKER_HOST") - hostListItemsChan <- HostListItem{ + stateQueryChan <- HostListItem{ Name: host.Name, Active: dockerHost == url && currentState != state.Stopped, DriverName: host.Driver.DriverName(), @@ -373,6 +375,29 @@ func getHostState(host Host, hostListItemsChan chan<- HostListItem) { } } +func getHostState(host Host, hostListItemsChan chan<- HostListItem) { + // This channel is used to communicate the properties we are querying + // about the host in the case of a successful read. + stateQueryChan := make(chan HostListItem) + + go attemptGetHostState(host, stateQueryChan) + + select { + // If we get back useful information, great. Forward it straight to + // the original parent channel. + case hli := <-stateQueryChan: + hostListItemsChan <- hli + + // Otherwise, give up after a predetermined duration. + case <-time.After(stateTimeoutDuration): + hostListItemsChan <- HostListItem{ + Name: host.Name, + DriverName: host.Driver.DriverName(), + State: state.Timeout, + } + } +} + func GetHostListItems(hostList []*Host) []HostListItem { hostListItems := []HostListItem{} hostListItemsChan := make(chan HostListItem) diff --git a/state/state.go b/state/state.go index 18f95e1507..245f509a0c 100644 --- a/state/state.go +++ b/state/state.go @@ -12,6 +12,7 @@ const ( Stopping Starting Error + Timeout ) var states = []string{ @@ -23,6 +24,7 @@ var states = []string{ "Stopping", "Starting", "Error", + "Timeout", } // Given a State type, returns its string representation From 47a890f27f5bb09486e5f78e2b1479de358ea743 Mon Sep 17 00:00:00 2001 From: Soshi Katsuta Date: Fri, 31 Jul 2015 16:01:31 +0900 Subject: [PATCH 32/36] libmachine: change an error message in Filestore#GetActive() Signed-off-by: Soshi Katsuta --- libmachine/filestore.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmachine/filestore.go b/libmachine/filestore.go index aa2651ff68..c28cde6d5d 100644 --- a/libmachine/filestore.go +++ b/libmachine/filestore.go @@ -127,7 +127,7 @@ func (s Filestore) GetActive() (*Host, error) { dockerHost := os.Getenv("DOCKER_HOST") if dockerHost == "" { - return nil, errors.New("DOCKER_HOST not set") + return nil, errors.New("No active host found: DOCKER_HOST not set") } hostListItems := GetHostListItems(hosts) From c8987830b973770c144cd88ed55aed4b4ed10917 Mon Sep 17 00:00:00 2001 From: Soshi Katsuta Date: Fri, 31 Jul 2015 16:34:08 +0900 Subject: [PATCH 33/36] libmachine: fix a test to check the host name is right Signed-off-by: Soshi Katsuta --- libmachine/filestore_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmachine/filestore_test.go b/libmachine/filestore_test.go index f6f09a9a82..407c7583da 100644 --- a/libmachine/filestore_test.go +++ b/libmachine/filestore_test.go @@ -228,7 +228,7 @@ func TestStoreGetSetActive(t *testing.T) { if err != nil { t.Fatal(err) } - if host.Name != host.Name { + if host.Name != hostTestName { t.Fatalf("Active host is not 'test', got %s", host.Name) } } From 3f41f8ac4cceda8dabf50ef2a7dc4db5c658416a Mon Sep 17 00:00:00 2001 From: Soshi Katsuta Date: Fri, 31 Jul 2015 16:36:22 +0900 Subject: [PATCH 34/36] libmachine: fix a test error message to show an expected host name Signed-off-by: Soshi Katsuta --- libmachine/filestore_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmachine/filestore_test.go b/libmachine/filestore_test.go index 407c7583da..2bd9815010 100644 --- a/libmachine/filestore_test.go +++ b/libmachine/filestore_test.go @@ -229,6 +229,6 @@ func TestStoreGetSetActive(t *testing.T) { t.Fatal(err) } if host.Name != hostTestName { - t.Fatalf("Active host is not 'test', got %s", host.Name) + t.Fatalf("Active host is not '%s', got %s", hostTestName, host.Name) } } From 48f8d5b533d6e9bd45d36d0df1a69c87aa668e64 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 31 Jul 2015 22:34:49 +0200 Subject: [PATCH 35/36] Doc for static IP and preemptibility Signed-off-by: David Gageot --- docs/drivers/gce.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/drivers/gce.md b/docs/drivers/gce.md index 782598236d..380a59219e 100644 --- a/docs/drivers/gce.md +++ b/docs/drivers/gce.md @@ -24,6 +24,8 @@ Options: - `--google-scopes`: The scopes for OAuth 2.0 to Access Google APIs. See [Google Compute Engine Doc](https://cloud.google.com/storage/docs/authentication). - `--google-disk-size`: The disk size of instance. - `--google-disk-type`: The disk type of instance. + - `--google-address`: Instance's static external IP (name or IP). + - `--google-preemptible`: Instance preemptibility. The GCE driver will use the `ubuntu-1404-trusty-v20150316` instance type unless otherwise specified. @@ -39,3 +41,5 @@ Environment variables and default values: | `--google-scopes` | `GOOGLE_SCOPES` | `devstorage.read_only,logging.write` | | `--google-disk-size` | `GOOGLE_DISK_SIZE` | `10` | | `--google-disk-type` | `GOOGLE_DISK_TYPE` | `pd-standard` | +| `--google-address` | `GOOGLE_ADDRESS` | - | +| `--google-preemptible` | `GOOGLE_PREEMPTIBLE` | - | From 8ba5a5707204ae316948d96753ea639419c42661 Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Mon, 3 Aug 2015 14:42:46 -0700 Subject: [PATCH 36/36] Revert "Merge pull request #1552 from ehazlett/b2d-next" This reverts commit 736ebb14fbca35ba8bd554d3abcf8c5f35c5d854, reversing changes made to c6660bf62c403f71c4ae6ad32a14edd619618528. Signed-off-by: Nathan LeClaire --- Godeps/Godeps.json | 5 - .../docker/docker/pkg/homedir/homedir.go | 39 -- .../docker/docker/pkg/homedir/homedir_test.go | 24 -- .../docker/libcontainer/user/MAINTAINERS | 2 - .../docker/libcontainer/user/lookup.go | 108 ------ .../docker/libcontainer/user/lookup_unix.go | 30 -- .../libcontainer/user/lookup_unsupported.go | 21 -- .../docker/libcontainer/user/user.go | 350 ----------------- .../docker/libcontainer/user/user_test.go | 352 ------------------ drivers/hyperv/hyperv_windows.go | 226 +++++++---- drivers/virtualbox/virtualbox.go | 248 ++++++------ drivers/virtualbox/virtualbox_test.go | 36 -- drivers/vmwarefusion/fusion_darwin.go | 150 +++++--- drivers/vmwarevsphere/vsphere.go | 4 +- libmachine/provision/boot2docker.go | 200 ++++++++-- libmachine/provision/rancheros.go | 3 +- libmachine/provision/utils_test.go | 48 +-- .../core/supported-engine-options.bats | 4 +- utils/b2d.go | 57 ++- utils/b2d_test.go | 19 +- 20 files changed, 598 insertions(+), 1328 deletions(-) delete mode 100644 Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir.go delete mode 100644 Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir_test.go delete mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS delete mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup.go delete mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go delete mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go delete mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go delete mode 100644 Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index afd4c7a9bc..18dd12df0b 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -46,11 +46,6 @@ "Comment": "v1.5.0", "Rev": "a8a31eff10544860d2188dddabdee4d727545796" }, - { - "ImportPath": "github.com/docker/docker/pkg/homedir", - "Comment": "v1.7.0", - "Rev": "0baf60984522744eed290348f33f396c046b2f3a" - }, { "ImportPath": "github.com/docker/docker/pkg/ioutils", "Comment": "v1.5.0", diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir.go deleted file mode 100644 index 61137a8f5d..0000000000 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir.go +++ /dev/null @@ -1,39 +0,0 @@ -package homedir - -import ( - "os" - "runtime" - - "github.com/docker/libcontainer/user" -) - -// Key returns the env var name for the user's home dir based on -// the platform being run on -func Key() string { - if runtime.GOOS == "windows" { - return "USERPROFILE" - } - return "HOME" -} - -// Get returns the home directory of the current user with the help of -// environment variables depending on the target operating system. -// Returned path should be used with "path/filepath" to form new paths. -func Get() string { - home := os.Getenv(Key()) - if home == "" && runtime.GOOS != "windows" { - if u, err := user.CurrentUser(); err == nil { - return u.Home - } - } - return home -} - -// GetShortcutString returns the string that is shortcut to user's home directory -// in the native shell of the platform running on. -func GetShortcutString() string { - if runtime.GOOS == "windows" { - return "%USERPROFILE%" // be careful while using in format functions - } - return "~" -} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir_test.go deleted file mode 100644 index 7a95cb2bd7..0000000000 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package homedir - -import ( - "path/filepath" - "testing" -) - -func TestGet(t *testing.T) { - home := Get() - if home == "" { - t.Fatal("returned home directory is empty") - } - - if !filepath.IsAbs(home) { - t.Fatalf("returned path is not absolute: %s", home) - } -} - -func TestGetShortcutString(t *testing.T) { - shortcut := GetShortcutString() - if shortcut == "" { - t.Fatal("returned shortcut string is empty") - } -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS b/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS deleted file mode 100644 index edbe200669..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS +++ /dev/null @@ -1,2 +0,0 @@ -Tianon Gravi (@tianon) -Aleksa Sarai (@cyphar) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup.go deleted file mode 100644 index 6f8a982ff7..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup.go +++ /dev/null @@ -1,108 +0,0 @@ -package user - -import ( - "errors" - "fmt" - "syscall" -) - -var ( - // The current operating system does not provide the required data for user lookups. - ErrUnsupported = errors.New("user lookup: operating system does not provide passwd-formatted data") -) - -func lookupUser(filter func(u User) bool) (User, error) { - // Get operating system-specific passwd reader-closer. - passwd, err := GetPasswd() - if err != nil { - return User{}, err - } - defer passwd.Close() - - // Get the users. - users, err := ParsePasswdFilter(passwd, filter) - if err != nil { - return User{}, err - } - - // No user entries found. - if len(users) == 0 { - return User{}, fmt.Errorf("no matching entries in passwd file") - } - - // Assume the first entry is the "correct" one. - return users[0], nil -} - -// CurrentUser looks up the current user by their user id in /etc/passwd. If the -// user cannot be found (or there is no /etc/passwd file on the filesystem), -// then CurrentUser returns an error. -func CurrentUser() (User, error) { - return LookupUid(syscall.Getuid()) -} - -// LookupUser looks up a user by their username in /etc/passwd. If the user -// cannot be found (or there is no /etc/passwd file on the filesystem), then -// LookupUser returns an error. -func LookupUser(username string) (User, error) { - return lookupUser(func(u User) bool { - return u.Name == username - }) -} - -// LookupUid looks up a user by their user id in /etc/passwd. If the user cannot -// be found (or there is no /etc/passwd file on the filesystem), then LookupId -// returns an error. -func LookupUid(uid int) (User, error) { - return lookupUser(func(u User) bool { - return u.Uid == uid - }) -} - -func lookupGroup(filter func(g Group) bool) (Group, error) { - // Get operating system-specific group reader-closer. - group, err := GetGroup() - if err != nil { - return Group{}, err - } - defer group.Close() - - // Get the users. - groups, err := ParseGroupFilter(group, filter) - if err != nil { - return Group{}, err - } - - // No user entries found. - if len(groups) == 0 { - return Group{}, fmt.Errorf("no matching entries in group file") - } - - // Assume the first entry is the "correct" one. - return groups[0], nil -} - -// CurrentGroup looks up the current user's group by their primary group id's -// entry in /etc/passwd. If the group cannot be found (or there is no -// /etc/group file on the filesystem), then CurrentGroup returns an error. -func CurrentGroup() (Group, error) { - return LookupGid(syscall.Getgid()) -} - -// LookupGroup looks up a group by its name in /etc/group. If the group cannot -// be found (or there is no /etc/group file on the filesystem), then LookupGroup -// returns an error. -func LookupGroup(groupname string) (Group, error) { - return lookupGroup(func(g Group) bool { - return g.Name == groupname - }) -} - -// LookupGid looks up a group by its group id in /etc/group. If the group cannot -// be found (or there is no /etc/group file on the filesystem), then LookupGid -// returns an error. -func LookupGid(gid int) (Group, error) { - return lookupGroup(func(g Group) bool { - return g.Gid == gid - }) -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go deleted file mode 100644 index 758b734c22..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go +++ /dev/null @@ -1,30 +0,0 @@ -// +build darwin dragonfly freebsd linux netbsd openbsd solaris - -package user - -import ( - "io" - "os" -) - -// Unix-specific path to the passwd and group formatted files. -const ( - unixPasswdPath = "/etc/passwd" - unixGroupPath = "/etc/group" -) - -func GetPasswdPath() (string, error) { - return unixPasswdPath, nil -} - -func GetPasswd() (io.ReadCloser, error) { - return os.Open(unixPasswdPath) -} - -func GetGroupPath() (string, error) { - return unixGroupPath, nil -} - -func GetGroup() (io.ReadCloser, error) { - return os.Open(unixGroupPath) -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go deleted file mode 100644 index 7217948870..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go +++ /dev/null @@ -1,21 +0,0 @@ -// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris - -package user - -import "io" - -func GetPasswdPath() (string, error) { - return "", ErrUnsupported -} - -func GetPasswd() (io.ReadCloser, error) { - return nil, ErrUnsupported -} - -func GetGroupPath() (string, error) { - return "", ErrUnsupported -} - -func GetGroup() (io.ReadCloser, error) { - return nil, ErrUnsupported -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go deleted file mode 100644 index d7439f12e3..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go +++ /dev/null @@ -1,350 +0,0 @@ -package user - -import ( - "bufio" - "fmt" - "io" - "os" - "strconv" - "strings" -) - -const ( - minId = 0 - maxId = 1<<31 - 1 //for 32-bit systems compatibility -) - -var ( - ErrRange = fmt.Errorf("Uids and gids must be in range %d-%d", minId, maxId) -) - -type User struct { - Name string - Pass string - Uid int - Gid int - Gecos string - Home string - Shell string -} - -type Group struct { - Name string - Pass string - Gid int - List []string -} - -func parseLine(line string, v ...interface{}) { - if line == "" { - return - } - - parts := strings.Split(line, ":") - for i, p := range parts { - if len(v) <= i { - // if we have more "parts" than we have places to put them, bail for great "tolerance" of naughty configuration files - break - } - - switch e := v[i].(type) { - case *string: - // "root", "adm", "/bin/bash" - *e = p - case *int: - // "0", "4", "1000" - // ignore string to int conversion errors, for great "tolerance" of naughty configuration files - *e, _ = strconv.Atoi(p) - case *[]string: - // "", "root", "root,adm,daemon" - if p != "" { - *e = strings.Split(p, ",") - } else { - *e = []string{} - } - default: - // panic, because this is a programming/logic error, not a runtime one - panic("parseLine expects only pointers! argument " + strconv.Itoa(i) + " is not a pointer!") - } - } -} - -func ParsePasswdFile(path string) ([]User, error) { - passwd, err := os.Open(path) - if err != nil { - return nil, err - } - defer passwd.Close() - return ParsePasswd(passwd) -} - -func ParsePasswd(passwd io.Reader) ([]User, error) { - return ParsePasswdFilter(passwd, nil) -} - -func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) { - passwd, err := os.Open(path) - if err != nil { - return nil, err - } - defer passwd.Close() - return ParsePasswdFilter(passwd, filter) -} - -func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) { - if r == nil { - return nil, fmt.Errorf("nil source for passwd-formatted data") - } - - var ( - s = bufio.NewScanner(r) - out = []User{} - ) - - for s.Scan() { - if err := s.Err(); err != nil { - return nil, err - } - - text := strings.TrimSpace(s.Text()) - if text == "" { - continue - } - - // see: man 5 passwd - // name:password:UID:GID:GECOS:directory:shell - // Name:Pass:Uid:Gid:Gecos:Home:Shell - // root:x:0:0:root:/root:/bin/bash - // adm:x:3:4:adm:/var/adm:/bin/false - p := User{} - parseLine( - text, - &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell, - ) - - if filter == nil || filter(p) { - out = append(out, p) - } - } - - return out, nil -} - -func ParseGroupFile(path string) ([]Group, error) { - group, err := os.Open(path) - if err != nil { - return nil, err - } - defer group.Close() - return ParseGroup(group) -} - -func ParseGroup(group io.Reader) ([]Group, error) { - return ParseGroupFilter(group, nil) -} - -func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) { - group, err := os.Open(path) - if err != nil { - return nil, err - } - defer group.Close() - return ParseGroupFilter(group, filter) -} - -func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) { - if r == nil { - return nil, fmt.Errorf("nil source for group-formatted data") - } - - var ( - s = bufio.NewScanner(r) - out = []Group{} - ) - - for s.Scan() { - if err := s.Err(); err != nil { - return nil, err - } - - text := s.Text() - if text == "" { - continue - } - - // see: man 5 group - // group_name:password:GID:user_list - // Name:Pass:Gid:List - // root:x:0:root - // adm:x:4:root,adm,daemon - p := Group{} - parseLine( - text, - &p.Name, &p.Pass, &p.Gid, &p.List, - ) - - if filter == nil || filter(p) { - out = append(out, p) - } - } - - return out, nil -} - -type ExecUser struct { - Uid, Gid int - Sgids []int - Home string -} - -// GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the -// given file paths and uses that data as the arguments to GetExecUser. If the -// files cannot be opened for any reason, the error is ignored and a nil -// io.Reader is passed instead. -func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) { - passwd, err := os.Open(passwdPath) - if err != nil { - passwd = nil - } else { - defer passwd.Close() - } - - group, err := os.Open(groupPath) - if err != nil { - group = nil - } else { - defer group.Close() - } - - return GetExecUser(userSpec, defaults, passwd, group) -} - -// GetExecUser parses a user specification string (using the passwd and group -// readers as sources for /etc/passwd and /etc/group data, respectively). In -// the case of blank fields or missing data from the sources, the values in -// defaults is used. -// -// GetExecUser will return an error if a user or group literal could not be -// found in any entry in passwd and group respectively. -// -// Examples of valid user specifications are: -// * "" -// * "user" -// * "uid" -// * "user:group" -// * "uid:gid -// * "user:gid" -// * "uid:group" -func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) { - var ( - userArg, groupArg string - name string - ) - - if defaults == nil { - defaults = new(ExecUser) - } - - // Copy over defaults. - user := &ExecUser{ - Uid: defaults.Uid, - Gid: defaults.Gid, - Sgids: defaults.Sgids, - Home: defaults.Home, - } - - // Sgids slice *cannot* be nil. - if user.Sgids == nil { - user.Sgids = []int{} - } - - // allow for userArg to have either "user" syntax, or optionally "user:group" syntax - parseLine(userSpec, &userArg, &groupArg) - - users, err := ParsePasswdFilter(passwd, func(u User) bool { - if userArg == "" { - return u.Uid == user.Uid - } - return u.Name == userArg || strconv.Itoa(u.Uid) == userArg - }) - if err != nil && passwd != nil { - if userArg == "" { - userArg = strconv.Itoa(user.Uid) - } - return nil, fmt.Errorf("Unable to find user %v: %v", userArg, err) - } - - haveUser := users != nil && len(users) > 0 - if haveUser { - // if we found any user entries that matched our filter, let's take the first one as "correct" - name = users[0].Name - user.Uid = users[0].Uid - user.Gid = users[0].Gid - user.Home = users[0].Home - } else if userArg != "" { - // we asked for a user but didn't find them... let's check to see if we wanted a numeric user - user.Uid, err = strconv.Atoi(userArg) - if err != nil { - // not numeric - we have to bail - return nil, fmt.Errorf("Unable to find user %v", userArg) - } - - // Must be inside valid uid range. - if user.Uid < minId || user.Uid > maxId { - return nil, ErrRange - } - - // if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit - } - - if groupArg != "" || name != "" { - groups, err := ParseGroupFilter(group, func(g Group) bool { - // Explicit group format takes precedence. - if groupArg != "" { - return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg - } - - // Check if user is a member. - for _, u := range g.List { - if u == name { - return true - } - } - - return false - }) - if err != nil && group != nil { - return nil, fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err) - } - - haveGroup := groups != nil && len(groups) > 0 - if groupArg != "" { - if haveGroup { - // if we found any group entries that matched our filter, let's take the first one as "correct" - user.Gid = groups[0].Gid - } else { - // we asked for a group but didn't find id... let's check to see if we wanted a numeric group - user.Gid, err = strconv.Atoi(groupArg) - if err != nil { - // not numeric - we have to bail - return nil, fmt.Errorf("Unable to find group %v", groupArg) - } - - // Ensure gid is inside gid range. - if user.Gid < minId || user.Gid > maxId { - return nil, ErrRange - } - - // if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit - } - } else if haveGroup { - // If implicit group format, fill supplementary gids. - user.Sgids = make([]int, len(groups)) - for i, group := range groups { - user.Sgids[i] = group.Gid - } - } - } - - return user, nil -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go deleted file mode 100644 index 4fe008fb39..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go +++ /dev/null @@ -1,352 +0,0 @@ -package user - -import ( - "io" - "reflect" - "strings" - "testing" -) - -func TestUserParseLine(t *testing.T) { - var ( - a, b string - c []string - d int - ) - - parseLine("", &a, &b) - if a != "" || b != "" { - t.Fatalf("a and b should be empty ('%v', '%v')", a, b) - } - - parseLine("a", &a, &b) - if a != "a" || b != "" { - t.Fatalf("a should be 'a' and b should be empty ('%v', '%v')", a, b) - } - - parseLine("bad boys:corny cows", &a, &b) - if a != "bad boys" || b != "corny cows" { - t.Fatalf("a should be 'bad boys' and b should be 'corny cows' ('%v', '%v')", a, b) - } - - parseLine("", &c) - if len(c) != 0 { - t.Fatalf("c should be empty (%#v)", c) - } - - parseLine("d,e,f:g:h:i,j,k", &c, &a, &b, &c) - if a != "g" || b != "h" || len(c) != 3 || c[0] != "i" || c[1] != "j" || c[2] != "k" { - t.Fatalf("a should be 'g', b should be 'h', and c should be ['i','j','k'] ('%v', '%v', '%#v')", a, b, c) - } - - parseLine("::::::::::", &a, &b, &c) - if a != "" || b != "" || len(c) != 0 { - t.Fatalf("a, b, and c should all be empty ('%v', '%v', '%#v')", a, b, c) - } - - parseLine("not a number", &d) - if d != 0 { - t.Fatalf("d should be 0 (%v)", d) - } - - parseLine("b:12:c", &a, &d, &b) - if a != "b" || b != "c" || d != 12 { - t.Fatalf("a should be 'b' and b should be 'c', and d should be 12 ('%v', '%v', %v)", a, b, d) - } -} - -func TestUserParsePasswd(t *testing.T) { - users, err := ParsePasswdFilter(strings.NewReader(` -root:x:0:0:root:/root:/bin/bash -adm:x:3:4:adm:/var/adm:/bin/false -this is just some garbage data -`), nil) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - if len(users) != 3 { - t.Fatalf("Expected 3 users, got %v", len(users)) - } - if users[0].Uid != 0 || users[0].Name != "root" { - t.Fatalf("Expected users[0] to be 0 - root, got %v - %v", users[0].Uid, users[0].Name) - } - if users[1].Uid != 3 || users[1].Name != "adm" { - t.Fatalf("Expected users[1] to be 3 - adm, got %v - %v", users[1].Uid, users[1].Name) - } -} - -func TestUserParseGroup(t *testing.T) { - groups, err := ParseGroupFilter(strings.NewReader(` -root:x:0:root -adm:x:4:root,adm,daemon -this is just some garbage data -`), nil) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - if len(groups) != 3 { - t.Fatalf("Expected 3 groups, got %v", len(groups)) - } - if groups[0].Gid != 0 || groups[0].Name != "root" || len(groups[0].List) != 1 { - t.Fatalf("Expected groups[0] to be 0 - root - 1 member, got %v - %v - %v", groups[0].Gid, groups[0].Name, len(groups[0].List)) - } - if groups[1].Gid != 4 || groups[1].Name != "adm" || len(groups[1].List) != 3 { - t.Fatalf("Expected groups[1] to be 4 - adm - 3 members, got %v - %v - %v", groups[1].Gid, groups[1].Name, len(groups[1].List)) - } -} - -func TestValidGetExecUser(t *testing.T) { - const passwdContent = ` -root:x:0:0:root user:/root:/bin/bash -adm:x:42:43:adm:/var/adm:/bin/false -this is just some garbage data -` - const groupContent = ` -root:x:0:root -adm:x:43: -grp:x:1234:root,adm -this is just some garbage data -` - defaultExecUser := ExecUser{ - Uid: 8888, - Gid: 8888, - Sgids: []int{8888}, - Home: "/8888", - } - - tests := []struct { - ref string - expected ExecUser - }{ - { - ref: "root", - expected: ExecUser{ - Uid: 0, - Gid: 0, - Sgids: []int{0, 1234}, - Home: "/root", - }, - }, - { - ref: "adm", - expected: ExecUser{ - Uid: 42, - Gid: 43, - Sgids: []int{1234}, - Home: "/var/adm", - }, - }, - { - ref: "root:adm", - expected: ExecUser{ - Uid: 0, - Gid: 43, - Sgids: defaultExecUser.Sgids, - Home: "/root", - }, - }, - { - ref: "adm:1234", - expected: ExecUser{ - Uid: 42, - Gid: 1234, - Sgids: defaultExecUser.Sgids, - Home: "/var/adm", - }, - }, - { - ref: "42:1234", - expected: ExecUser{ - Uid: 42, - Gid: 1234, - Sgids: defaultExecUser.Sgids, - Home: "/var/adm", - }, - }, - { - ref: "1337:1234", - expected: ExecUser{ - Uid: 1337, - Gid: 1234, - Sgids: defaultExecUser.Sgids, - Home: defaultExecUser.Home, - }, - }, - { - ref: "1337", - expected: ExecUser{ - Uid: 1337, - Gid: defaultExecUser.Gid, - Sgids: defaultExecUser.Sgids, - Home: defaultExecUser.Home, - }, - }, - { - ref: "", - expected: ExecUser{ - Uid: defaultExecUser.Uid, - Gid: defaultExecUser.Gid, - Sgids: defaultExecUser.Sgids, - Home: defaultExecUser.Home, - }, - }, - } - - for _, test := range tests { - passwd := strings.NewReader(passwdContent) - group := strings.NewReader(groupContent) - - execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group) - if err != nil { - t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error()) - t.Fail() - continue - } - - if !reflect.DeepEqual(test.expected, *execUser) { - t.Logf("got: %#v", execUser) - t.Logf("expected: %#v", test.expected) - t.Fail() - continue - } - } -} - -func TestInvalidGetExecUser(t *testing.T) { - const passwdContent = ` -root:x:0:0:root user:/root:/bin/bash -adm:x:42:43:adm:/var/adm:/bin/false -this is just some garbage data -` - const groupContent = ` -root:x:0:root -adm:x:43: -grp:x:1234:root,adm -this is just some garbage data -` - - tests := []string{ - // No such user/group. - "notuser", - "notuser:notgroup", - "root:notgroup", - "notuser:adm", - "8888:notgroup", - "notuser:8888", - - // Invalid user/group values. - "-1:0", - "0:-3", - "-5:-2", - } - - for _, test := range tests { - passwd := strings.NewReader(passwdContent) - group := strings.NewReader(groupContent) - - execUser, err := GetExecUser(test, nil, passwd, group) - if err == nil { - t.Logf("got unexpected success when parsing '%s': %#v", test, execUser) - t.Fail() - continue - } - } -} - -func TestGetExecUserNilSources(t *testing.T) { - const passwdContent = ` -root:x:0:0:root user:/root:/bin/bash -adm:x:42:43:adm:/var/adm:/bin/false -this is just some garbage data -` - const groupContent = ` -root:x:0:root -adm:x:43: -grp:x:1234:root,adm -this is just some garbage data -` - - defaultExecUser := ExecUser{ - Uid: 8888, - Gid: 8888, - Sgids: []int{8888}, - Home: "/8888", - } - - tests := []struct { - ref string - passwd, group bool - expected ExecUser - }{ - { - ref: "", - passwd: false, - group: false, - expected: ExecUser{ - Uid: 8888, - Gid: 8888, - Sgids: []int{8888}, - Home: "/8888", - }, - }, - { - ref: "root", - passwd: true, - group: false, - expected: ExecUser{ - Uid: 0, - Gid: 0, - Sgids: []int{8888}, - Home: "/root", - }, - }, - { - ref: "0", - passwd: false, - group: false, - expected: ExecUser{ - Uid: 0, - Gid: 8888, - Sgids: []int{8888}, - Home: "/8888", - }, - }, - { - ref: "0:0", - passwd: false, - group: false, - expected: ExecUser{ - Uid: 0, - Gid: 0, - Sgids: []int{8888}, - Home: "/8888", - }, - }, - } - - for _, test := range tests { - var passwd, group io.Reader - - if test.passwd { - passwd = strings.NewReader(passwdContent) - } - - if test.group { - group = strings.NewReader(groupContent) - } - - execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group) - if err != nil { - t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error()) - t.Fail() - continue - } - - if !reflect.DeepEqual(test.expected, *execUser) { - t.Logf("got: %#v", execUser) - t.Logf("expected: %#v", test.expected) - t.Fail() - continue - } - } -} diff --git a/drivers/hyperv/hyperv_windows.go b/drivers/hyperv/hyperv_windows.go index 458fdc8f40..849cbb8ab2 100644 --- a/drivers/hyperv/hyperv_windows.go +++ b/drivers/hyperv/hyperv_windows.go @@ -1,8 +1,12 @@ package hyperv import ( + "archive/tar" + "bytes" "fmt" "io/ioutil" + "os" + "path/filepath" "time" "github.com/codegangsta/cli" @@ -13,10 +17,6 @@ import ( "github.com/docker/machine/utils" ) -const ( - isoFilename = "boot2docker-hyperv.iso" -) - type Driver struct { *drivers.BaseDriver boot2DockerURL string @@ -146,9 +146,55 @@ func (d *Driver) Create() error { d.setMachineNameIfNotSet() - b2dutils := utils.NewB2dUtils("", "", isoFilename) - if err := b2dutils.CopyIsoToMachineDir(d.boot2DockerURL, d.MachineName); err != nil { - return err + var isoURL string + + b2dutils := utils.NewB2dUtils("", "") + + if d.boot2DockerLoc == "" { + if d.boot2DockerURL != "" { + isoURL = d.boot2DockerURL + log.Infof("Downloading boot2docker.iso from %s...", isoURL) + if err := b2dutils.DownloadISO(d.ResolveStorePath("."), "boot2docker.iso", isoURL); err != nil { + return err + } + } else { + // todo: check latest release URL, download if it's new + // until then always use "latest" + isoURL, err = b2dutils.GetLatestBoot2DockerReleaseURL() + if err != nil { + log.Warnf("Unable to check for the latest release: %s", err) + + } + // todo: use real constant for .docker + rootPath := filepath.Join(utils.GetDockerDir()) + imgPath := filepath.Join(rootPath, "images") + commonIsoPath := filepath.Join(imgPath, "boot2docker.iso") + if _, err := os.Stat(commonIsoPath); os.IsNotExist(err) { + log.Infof("Downloading boot2docker.iso to %s...", commonIsoPath) + // just in case boot2docker.iso has been manually deleted + if _, err := os.Stat(imgPath); os.IsNotExist(err) { + if err := os.Mkdir(imgPath, 0700); err != nil { + return err + + } + + } + if err := b2dutils.DownloadISO(imgPath, "boot2docker.iso", isoURL); err != nil { + return err + + } + + } + isoDest := d.ResolveStorePath("boot2docker.iso") + if err := utils.CopyFile(commonIsoPath, isoDest); err != nil { + return err + + } + } + } else { + if err := utils.CopyFile(d.boot2DockerLoc, d.ResolveStorePath("boot2docker.iso")); err != nil { + return err + } } log.Infof("Creating SSH key...") @@ -164,7 +210,8 @@ func (d *Driver) Create() error { return err } - if err := d.generateDiskImage(); err != nil { + err = d.generateDiskImage() + if err != nil { return err } @@ -181,8 +228,9 @@ func (d *Driver) Create() error { command = []string{ "Set-VMDvdDrive", "-VMName", d.MachineName, - "-Path", fmt.Sprintf("'%s'", d.ResolveStorePath(isoFilename))} - if _, err = execute(command); err != nil { + "-Path", fmt.Sprintf("'%s'", d.ResolveStorePath("boot2docker.iso"))} + _, err = execute(command) + if err != nil { return err } @@ -190,7 +238,8 @@ func (d *Driver) Create() error { "Add-VMHardDiskDrive", "-VMName", d.MachineName, "-Path", fmt.Sprintf("'%s'", d.diskImage)} - if _, err = execute(command); err != nil { + _, err = execute(command) + if err != nil { return err } @@ -198,7 +247,8 @@ func (d *Driver) Create() error { "Connect-VMNetworkAdapter", "-VMName", d.MachineName, "-SwitchName", fmt.Sprintf("'%s'", virtualSwitch)} - if _, err = execute(command); err != nil { + _, err = execute(command) + if err != nil { return err } @@ -207,35 +257,6 @@ func (d *Driver) Create() error { return err } - // use ssh to set keys - sshClient, err := d.getLocalSSHClient() - if err != nil { - return err - } - - // add pub key for user - pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) - if err != nil { - return err - } - - if out, err := sshClient.Output(fmt.Sprintf( - "mkdir -p /home/%s/.ssh", - d.GetSSHUsername(), - )); err != nil { - log.Error(out) - return err - } - - if out, err := sshClient.Output(fmt.Sprintf( - "printf '%%s' '%s' | tee /home/%s/.ssh/authorized_keys", - string(pubKey), - d.GetSSHUsername(), - )); err != nil { - log.Error(out) - return err - } - return nil } @@ -273,7 +294,8 @@ func (d *Driver) Start() error { command := []string{ "Start-VM", "-Name", d.MachineName} - if _, err := execute(command); err != nil { + _, err := execute(command) + if err != nil { return err } @@ -281,18 +303,16 @@ func (d *Driver) Start() error { return err } - if _, err := d.GetIP(); err != nil { - return err - } - - return nil + d.IPAddress, err = d.GetIP() + return err } func (d *Driver) Stop() error { command := []string{ "Stop-VM", "-Name", d.MachineName} - if _, err := execute(command); err != nil { + _, err := execute(command) + if err != nil { return err } for { @@ -324,11 +344,8 @@ func (d *Driver) Remove() error { "Remove-VM", "-Name", d.MachineName, "-Force"} - if _, err = execute(command); err != nil { - return err - } - - return nil + _, err = execute(command) + return err } func (d *Driver) Restart() error { @@ -345,7 +362,8 @@ func (d *Driver) Kill() error { "Stop-VM", "-Name", d.MachineName, "-TurnOff"} - if _, err := execute(command); err != nil { + _, err := execute(command) + if err != nil { return err } for { @@ -391,35 +409,101 @@ func (d *Driver) publicSSHKeyPath() string { } func (d *Driver) generateDiskImage() error { + // Create a small fixed vhd, put the tar in, + // convert to dynamic, then resize + d.diskImage = d.ResolveStorePath("disk.vhd") + fixed := d.ResolveStorePath("fixed.vhd") log.Infof("Creating VHD") command := []string{ "New-VHD", - "-Path", fmt.Sprintf("'%s'", d.diskImage), - "-SizeBytes", fmt.Sprintf("%dMB", d.diskSize), - } - - if _, err := execute(command); err != nil { + "-Path", fmt.Sprintf("'%s'", fixed), + "-SizeBytes", "10MB", + "-Fixed"} + _, err := execute(command) + if err != nil { return err } - return nil + tarBuf, err := d.generateTar() + if err != nil { + return err + } + + file, err := os.OpenFile(fixed, os.O_WRONLY, 0644) + if err != nil { + return err + } + defer file.Close() + file.Seek(0, os.SEEK_SET) + _, err = file.Write(tarBuf.Bytes()) + if err != nil { + return err + } + file.Close() + + command = []string{ + "Convert-VHD", + "-Path", fmt.Sprintf("'%s'", fixed), + "-DestinationPath", fmt.Sprintf("'%s'", d.diskImage), + "-VHDType", "Dynamic"} + _, err = execute(command) + if err != nil { + return err + } + command = []string{ + "Resize-VHD", + "-Path", fmt.Sprintf("'%s'", d.diskImage), + "-SizeBytes", fmt.Sprintf("%dMB", d.diskSize)} + _, err = execute(command) + if err != nil { + return err + } + + return err } -func (d *Driver) getLocalSSHClient() (ssh.Client, error) { - ip, err := d.GetIP() +// Make a boot2docker VM disk image. +// See https://github.com/boot2docker/boot2docker/blob/master/rootfs/rootfs/etc/rc.d/automount +func (d *Driver) generateTar() (*bytes.Buffer, error) { + magicString := "boot2docker, please format-me" + + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + + // magicString first so the automount script knows to format the disk + file := &tar.Header{Name: magicString, Size: int64(len(magicString))} + if err := tw.WriteHeader(file); err != nil { + return nil, err + } + if _, err := tw.Write([]byte(magicString)); err != nil { + return nil, err + } + // .ssh/key.pub => authorized_keys + file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} + if err := tw.WriteHeader(file); err != nil { + return nil, err + } + pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) if err != nil { return nil, err } - - sshAuth := &ssh.Auth{ - Passwords: []string{"docker"}, - Keys: []string{d.GetSSHKeyPath()}, - } - sshClient, err := ssh.NewNativeClient(d.GetSSHUsername(), ip, d.SSHPort, sshAuth) - if err != nil { + file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { return nil, err } - - return sshClient, nil + if _, err := tw.Write([]byte(pubKey)); err != nil { + return nil, err + } + file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + return nil, err + } + if _, err := tw.Write([]byte(pubKey)); err != nil { + return nil, err + } + if err := tw.Close(); err != nil { + return nil, err + } + return buf, nil } diff --git a/drivers/virtualbox/virtualbox.go b/drivers/virtualbox/virtualbox.go index 3e91c097bb..0fde6bb5c5 100644 --- a/drivers/virtualbox/virtualbox.go +++ b/drivers/virtualbox/virtualbox.go @@ -1,13 +1,16 @@ package virtualbox import ( + "archive/tar" + "bytes" "errors" "fmt" + "io" "io/ioutil" "math/rand" "net" "os" - "path" + "os/exec" "path/filepath" "regexp" "runtime" @@ -16,7 +19,6 @@ import ( "time" "github.com/codegangsta/cli" - "github.com/docker/docker/pkg/homedir" "github.com/docker/machine/drivers" "github.com/docker/machine/log" "github.com/docker/machine/ssh" @@ -25,7 +27,7 @@ import ( ) const ( - isoFilename = "boot2docker-virtualbox.iso" + isoFilename = "boot2docker.iso" defaultHostOnlyCIDR = "192.168.99.1/24" ) @@ -153,7 +155,7 @@ func (d *Driver) Create() error { return err } - b2dutils := utils.NewB2dUtils("", "", isoFilename) + b2dutils := utils.NewB2dUtils("", "") if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { return err } @@ -201,10 +203,9 @@ func (d *Driver) Create() error { } log.Debugf("Creating disk image...") - if err := vbm("createhd", "--size", fmt.Sprintf("%d", d.DiskSize), "--format", "VMDK", "--filename", d.diskPath()); err != nil { + if err := d.generateDiskImage(d.DiskSize); err != nil { return err } - } if err := vbm("createvm", @@ -274,7 +275,7 @@ func (d *Driver) Create() error { "--port", "0", "--device", "0", "--type", "dvddrive", - "--medium", d.ResolveStorePath(isoFilename)); err != nil { + "--medium", d.ResolveStorePath("boot2docker.iso")); err != nil { return err } @@ -287,39 +288,36 @@ func (d *Driver) Create() error { return err } - shareDir := homedir.Get() - shareName := shareDir - - log.Debugf("creating share: path=%s", shareDir) - // let VBoxService do nice magic automounting (when it's used) - if err := vbm("guestproperty", "set", d.MachineName, "/VirtualBox/GuestAdd/SharedFolders/MountDir", "/"); err != nil { - return err - } if err := vbm("guestproperty", "set", d.MachineName, "/VirtualBox/GuestAdd/SharedFolders/MountPrefix", "/"); err != nil { return err } + if err := vbm("guestproperty", "set", d.MachineName, "/VirtualBox/GuestAdd/SharedFolders/MountDir", "/"); err != nil { + return err + } + + var shareName, shareDir string // TODO configurable at some point + switch runtime.GOOS { + case "windows": + shareName = "c/Users" + shareDir = "c:\\Users" + case "darwin": + shareName = "Users" + shareDir = "/Users" + // TODO "linux" + } if shareDir != "" { - log.Debugf("setting up shareDir") if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { - log.Debugf("setting up share failed: %s", err) return err } else if !os.IsNotExist(err) { - // parts of the VBox internal code are buggy with share names that start with "/" - shareName = strings.TrimLeft(shareDir, "/") - - // translate to msys git path - if runtime.GOOS == "windows" { - mountName, err := translateWindowsMount(shareDir) - if err != nil { - return err - } - shareName = mountName + if shareName == "" { + // parts of the VBox internal code are buggy with share names that start with "/" + shareName = strings.TrimLeft(shareDir, "/") + // TODO do some basic Windows -> MSYS path conversion + // ie, s!^([a-z]+):[/\\]+!\1/!; s!\\!/!g } - log.Debugf("adding shared folder: name=%q dir=%q", shareName, shareDir) - // woo, shareDir exists! let's carry on! if err := vbm("sharedfolder", "add", d.MachineName, "--name", shareName, "--hostpath", shareDir, "--automount"); err != nil { return err @@ -338,47 +336,13 @@ func (d *Driver) Create() error { return err } - // use ssh to set keys - sshClient, err := d.getLocalSSHClient() - if err != nil { - return err - } - - // add pub key for user - pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) - if err != nil { - return err - } - - if out, err := sshClient.Output(fmt.Sprintf( - "mkdir -p /home/%s/.ssh", - d.GetSSHUsername(), - )); err != nil { - log.Error(out) - return err - } - - if out, err := sshClient.Output(fmt.Sprintf( - "printf '%%s' '%s' | tee /home/%s/.ssh/authorized_keys", - string(pubKey), - d.GetSSHUsername(), - )); err != nil { - log.Error(out) - return err - } - - ip, err := d.GetIP() - if err != nil { - return err - } - d.IPAddress = ip return nil } func (d *Driver) hostOnlyIpAvailable() bool { ip, err := d.GetIP() if err != nil { - log.Debugf("ERROR getting IP: %s", err) + log.Debug("ERROR getting IP: %s", err) return false } if ip != "" { @@ -421,8 +385,8 @@ func (d *Driver) Start() error { log.Infof("VM not in restartable state") } - addr, err := d.GetSSHHostname() - if err := ssh.WaitForTCP(fmt.Sprintf("%s:%d", addr, d.SSHPort)); err != nil { + // Wait for SSH over NAT to be available before returning to user + if err := drivers.WaitForSSH(d); err != nil { return err } @@ -431,6 +395,8 @@ func (d *Driver) Start() error { return err } + d.IPAddress, err = d.GetIP() + return err } @@ -536,17 +502,11 @@ func (d *Driver) GetIP() (string, error) { return "", drivers.ErrHostIsNotRunning } - sshClient, err := d.getLocalSSHClient() + output, err := drivers.RunSSHCommandFromDriver(d, "ip addr show dev eth1") if err != nil { return "", err } - output, err := sshClient.Output("ip addr show dev eth1") - if err != nil { - log.Debug(output) - return "", err - } - log.Debugf("SSH returned: %s\nEND SSH\n", output) // parse to find: inet 192.168.59.103/24 brd 192.168.59.255 scope global eth1 @@ -569,6 +529,53 @@ func (d *Driver) diskPath() string { return d.ResolveStorePath("disk.vmdk") } +// Make a boot2docker VM disk image. +func (d *Driver) generateDiskImage(size int) error { + log.Debugf("Creating %d MB hard disk image...", size) + + magicString := "boot2docker, please format-me" + + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + + // magicString first so the automount script knows to format the disk + file := &tar.Header{Name: magicString, Size: int64(len(magicString))} + if err := tw.WriteHeader(file); err != nil { + return err + } + if _, err := tw.Write([]byte(magicString)); err != nil { + return err + } + // .ssh/key.pub => authorized_keys + file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} + if err := tw.WriteHeader(file); err != nil { + return err + } + pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) + if err != nil { + return err + } + file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + return err + } + if _, err := tw.Write([]byte(pubKey)); err != nil { + return err + } + file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + return err + } + if _, err := tw.Write([]byte(pubKey)); err != nil { + return err + } + if err := tw.Close(); err != nil { + return err + } + raw := bytes.NewReader(buf.Bytes()) + return createDiskImage(d.diskPath(), size, raw) +} + func (d *Driver) setupHostOnlyNetwork(machineName string) error { hostOnlyCIDR := d.HostOnlyCIDR @@ -619,6 +626,69 @@ func (d *Driver) setupHostOnlyNetwork(machineName string) error { return nil } +// createDiskImage makes a disk image at dest with the given size in MB. If r is +// not nil, it will be read as a raw disk image to convert from. +func createDiskImage(dest string, size int, r io.Reader) error { + // Convert a raw image from stdin to the dest VMDK image. + sizeBytes := int64(size) << 20 // usually won't fit in 32-bit int (max 2GB) + // FIXME: why isn't this just using the vbm*() functions? + cmd := exec.Command(vboxManageCmd, "convertfromraw", "stdin", dest, + fmt.Sprintf("%d", sizeBytes), "--format", "VMDK") + + if os.Getenv("DEBUG") != "" { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } + + stdin, err := cmd.StdinPipe() + if err != nil { + return err + } + if err := cmd.Start(); err != nil { + return err + } + + n, err := io.Copy(stdin, r) + if err != nil { + return err + } + + // The total number of bytes written to stdin must match sizeBytes, or + // VBoxManage.exe on Windows will fail. Fill remaining with zeros. + if left := sizeBytes - n; left > 0 { + if err := zeroFill(stdin, left); err != nil { + return err + } + } + + // cmd won't exit until the stdin is closed. + if err := stdin.Close(); err != nil { + return err + } + + return cmd.Wait() +} + +// zeroFill writes n zero bytes into w. +func zeroFill(w io.Writer, n int64) error { + const blocksize = 32 << 10 + zeros := make([]byte, blocksize) + var k int + var err error + for n > 0 { + if n > blocksize { + k, err = w.Write(zeros) + } else { + k, err = w.Write(zeros[:n]) + } + if err != nil { + return err + } + n -= int64(k) + } + return nil +} + // Select an available port, trying the specified // port first, falling back on an OS selected port. func getAvailableTCPPort(port int) (int, error) { @@ -685,35 +755,3 @@ func getRandomIPinSubnet(baseIP net.IP) (net.IP, error) { return dhcpAddr, nil } - -func (d *Driver) getLocalSSHClient() (ssh.Client, error) { - sshAuth := &ssh.Auth{ - Passwords: []string{"docker"}, - Keys: []string{d.GetSSHKeyPath()}, - } - sshClient, err := ssh.NewNativeClient(d.GetSSHUsername(), "127.0.0.1", d.SSHPort, sshAuth) - if err != nil { - return nil, err - } - - return sshClient, nil -} - -func translateWindowsMount(p string) (string, error) { - re := regexp.MustCompile(`(?P[^:]+):\\(?P.*)`) - m := re.FindStringSubmatch(p) - - var drive, fullPath string - - if len(m) < 3 { - return "", fmt.Errorf("unable to parse home directory") - } - - drive = m[1] - fullPath = m[2] - - nPath := strings.Replace(fullPath, "\\", "/", -1) - - tPath := path.Join("/", strings.ToLower(drive), nPath) - return tPath, nil -} diff --git a/drivers/virtualbox/virtualbox_test.go b/drivers/virtualbox/virtualbox_test.go index 7b63f3b08d..5df5e5f4cb 100644 --- a/drivers/virtualbox/virtualbox_test.go +++ b/drivers/virtualbox/virtualbox_test.go @@ -29,39 +29,3 @@ func TestGetRandomIPinSubnet(t *testing.T) { t.Fatalf("expected third octet of %d; received %d", testIP[2], newIP[2]) } } - -func TestTranslateWindowsMount(t *testing.T) { - p1 := `C:\Users\foo` - r, err := translateWindowsMount(p1) - if err != nil { - t.Fatal(err) - } - - if r != `/c/Users/foo` { - t.Fatalf("expected to match /c/Users/foo") - } -} - -func TestTranslateWindowsMountCustomDrive(t *testing.T) { - p1 := `D:\Users\foo` - r, err := translateWindowsMount(p1) - if err != nil { - t.Fatal(err) - } - - if r != `/d/Users/foo` { - t.Fatalf("expected to match /d/Users/foo") - } -} - -func TestTranslateWindowsMountLongPath(t *testing.T) { - p1 := `c:\path\to\users\foo` - r, err := translateWindowsMount(p1) - if err != nil { - t.Fatal(err) - } - - if r != `/c/path/to/users/foo` { - t.Fatalf("expected to match /c/path/to/users/foo") - } -} diff --git a/drivers/vmwarefusion/fusion_darwin.go b/drivers/vmwarefusion/fusion_darwin.go index fbfdef00ea..30cc20c252 100644 --- a/drivers/vmwarefusion/fusion_darwin.go +++ b/drivers/vmwarefusion/fusion_darwin.go @@ -5,16 +5,17 @@ package vmwarefusion import ( + "archive/tar" "fmt" "io/ioutil" "os" "regexp" + "runtime" "strings" "text/template" "time" "github.com/codegangsta/cli" - "github.com/docker/docker/pkg/homedir" "github.com/docker/machine/drivers" "github.com/docker/machine/log" "github.com/docker/machine/ssh" @@ -24,8 +25,8 @@ import ( const ( B2DUser = "docker" - B2DPass = "docker" - isoFilename = "boot2docker-vmware.iso" + B2DPass = "tcuser" + isoFilename = "boot2docker.iso" ) // Driver for VMware Fusion @@ -160,7 +161,7 @@ func (d *Driver) PreCreateCheck() error { func (d *Driver) Create() error { - b2dutils := utils.NewB2dUtils("", "", isoFilename) + b2dutils := utils.NewB2dUtils("", "") if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { return err } @@ -206,7 +207,7 @@ func (d *Driver) Create() error { log.Infof("Waiting for VM to come online...") for i := 1; i <= 60; i++ { - ip, err = d.GetIP() + ip, err = d.getIPfromDHCPLease() if err != nil { log.Debugf("Not there yet %d/%d, error: %s", i, 60, err) time.Sleep(2 * time.Second) @@ -226,42 +227,40 @@ func (d *Driver) Create() error { // we got an IP, let's copy ssh keys over d.IPAddress = ip - // use ssh to set keys - sshClient, err := d.getLocalSSHClient() - if err != nil { + // Generate a tar keys bundle + if err := d.generateKeyBundle(); err != nil { return err } - // add pub key for user - pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) - if err != nil { - return err - } + // Test if /var/lib/boot2docker exists + vmrun("-gu", B2DUser, "-gp", B2DPass, "directoryExistsInGuest", d.vmxPath(), "/var/lib/boot2docker") - if out, err := sshClient.Output(fmt.Sprintf( - "mkdir -p /home/%s/.ssh", - d.GetSSHUsername(), - )); err != nil { - log.Error(out) - return err - } + // Copy SSH keys bundle + vmrun("-gu", B2DUser, "-gp", B2DPass, "CopyFileFromHostToGuest", d.vmxPath(), d.ResolveStorePath("userdata.tar"), "/home/docker/userdata.tar") - if out, err := sshClient.Output(fmt.Sprintf( - "printf '%%s' '%s' | tee /home/%s/.ssh/authorized_keys", - string(pubKey), - d.GetSSHUsername(), - )); err != nil { - log.Error(out) - return err - } + // Expand tar file. + vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo /bin/mv /home/docker/userdata.tar /var/lib/boot2docker/userdata.tar && sudo tar xf /var/lib/boot2docker/userdata.tar -C /home/docker/ > /var/log/userdata.log 2>&1 && sudo chown -R docker:staff /home/docker") // Enable Shared Folders vmrun("-gu", B2DUser, "-gp", B2DPass, "enableSharedFolders", d.vmxPath()) - if err := d.setupSharedDirs(); err != nil { - return err + var shareName, shareDir string // TODO configurable at some point + switch runtime.GOOS { + case "darwin": + shareName = "Users" + shareDir = "/Users" + // TODO "linux" and "windows" } + if shareDir != "" { + if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { + return err + } else if !os.IsNotExist(err) { + // add shared folder, create mountpoint and mount it. + vmrun("-gu", B2DUser, "-gp", B2DPass, "addSharedFolder", d.vmxPath(), shareName, shareDir) + vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo mkdir "+shareDir+" && sudo mount -t vmhgfs .host:/"+shareName+" "+shareDir) + } + } return nil } @@ -270,8 +269,21 @@ func (d *Driver) Start() error { vmrun("start", d.vmxPath(), "nogui") log.Debugf("Mounting Shared Folders...") - if err := d.setupSharedDirs(); err != nil { - return err + var shareName, shareDir string // TODO configurable at some point + switch runtime.GOOS { + case "darwin": + shareName = "Users" + shareDir = "/Users" + // TODO "linux" and "windows" + } + + if shareDir != "" { + if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { + return err + } else if !os.IsNotExist(err) { + // create mountpoint and mount shared folder + vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo mkdir "+shareDir+" && sudo mount -t vmhgfs .host:/"+shareName+" "+shareDir) + } } return nil @@ -406,35 +418,57 @@ func (d *Driver) publicSSHKeyPath() string { return d.GetSSHKeyPath() + ".pub" } -func (d *Driver) setupSharedDirs() error { - shareDir := homedir.Get() - shareName := "Home" +// Make a boot2docker userdata.tar key bundle +func (d *Driver) generateKeyBundle() error { + log.Debugf("Creating Tar key bundle...") - if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { + magicString := "boot2docker, this is vmware speaking" + + tf, err := os.Create(d.ResolveStorePath("userdata.tar")) + if err != nil { + return err + } + defer tf.Close() + var fileWriter = tf + + tw := tar.NewWriter(fileWriter) + defer tw.Close() + + // magicString first so we can figure out who originally wrote the tar. + file := &tar.Header{Name: magicString, Size: int64(len(magicString))} + if err := tw.WriteHeader(file); err != nil { + return err + } + if _, err := tw.Write([]byte(magicString)); err != nil { + return err + } + // .ssh/key.pub => authorized_keys + file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} + if err := tw.WriteHeader(file); err != nil { + return err + } + pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) + if err != nil { + return err + } + file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + return err + } + if _, err := tw.Write([]byte(pubKey)); err != nil { + return err + } + file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + return err + } + if _, err := tw.Write([]byte(pubKey)); err != nil { + return err + } + if err := tw.Close(); err != nil { return err - } else if !os.IsNotExist(err) { - // add shared folder, create mountpoint and mount it. - vmrun("-gu", B2DUser, "-gp", B2DPass, "addSharedFolder", d.vmxPath(), shareName, shareDir) - vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo mkdir -p "+shareDir+" && sudo mount -t vmhgfs .host:/"+shareName+" "+shareDir) } return nil -} - -func (d *Driver) getLocalSSHClient() (ssh.Client, error) { - ip, err := d.GetIP() - if err != nil { - return nil, err - } - - sshAuth := &ssh.Auth{ - Passwords: []string{"docker"}, - Keys: []string{d.GetSSHKeyPath()}, - } - sshClient, err := ssh.NewNativeClient(d.GetSSHUsername(), ip, d.SSHPort, sshAuth) - if err != nil { - return nil, err - } - - return sshClient, nil + } diff --git a/drivers/vmwarevsphere/vsphere.go b/drivers/vmwarevsphere/vsphere.go index 2f5676f6c5..829cd5e24e 100644 --- a/drivers/vmwarevsphere/vsphere.go +++ b/drivers/vmwarevsphere/vsphere.go @@ -25,7 +25,7 @@ import ( ) const ( - isoFilename = "boot2docker-vmware.iso" + isoFilename = "boot2docker.iso" B2DISOName = isoFilename DefaultCPUNumber = 2 B2DUser = "docker" @@ -225,7 +225,7 @@ func (d *Driver) Create() error { return err } - b2dutils := utils.NewB2dUtils("", "", isoFilename) + b2dutils := utils.NewB2dUtils("", "") if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { return err } diff --git a/libmachine/provision/boot2docker.go b/libmachine/provision/boot2docker.go index fcd09e3d2b..485cfb852e 100644 --- a/libmachine/provision/boot2docker.go +++ b/libmachine/provision/boot2docker.go @@ -1,19 +1,21 @@ package provision import ( - "errors" + "bytes" + "fmt" + "path" + "text/template" "github.com/docker/machine/drivers" + "github.com/docker/machine/libmachine/auth" + "github.com/docker/machine/libmachine/engine" "github.com/docker/machine/libmachine/provision/pkgaction" + "github.com/docker/machine/libmachine/swarm" "github.com/docker/machine/log" "github.com/docker/machine/state" "github.com/docker/machine/utils" ) -var ( - ErrUnknownDriver = errors.New("unknown driver") -) - func init() { Register("boot2docker", &RegisteredProvisioner{ New: NewBoot2DockerProvisioner, @@ -21,23 +23,29 @@ func init() { } func NewBoot2DockerProvisioner(d drivers.Driver) Provisioner { - g := GenericProvisioner{ - DockerOptionsDir: "/etc/docker", - DaemonOptionsFile: "/etc/systemd/system/docker.service", - OsReleaseId: "docker", - Packages: []string{}, - Driver: d, + return &Boot2DockerProvisioner{ + Driver: d, } - p := &Boot2DockerProvisioner{ - DebianProvisioner{ - GenericProvisioner: g, - }, - } - return p } type Boot2DockerProvisioner struct { - DebianProvisioner + OsReleaseInfo *OsRelease + Driver drivers.Driver + AuthOptions auth.AuthOptions + EngineOptions engine.EngineOptions + SwarmOptions swarm.SwarmOptions +} + +func (provisioner *Boot2DockerProvisioner) Service(name string, action pkgaction.ServiceAction) error { + var ( + err error + ) + + if _, err = provisioner.SSHCommand(fmt.Sprintf("sudo /etc/init.d/%s %s", name, action.String())); err != nil { + return err + } + + return nil } func (provisioner *Boot2DockerProvisioner) upgradeIso() error { @@ -45,62 +53,174 @@ func (provisioner *Boot2DockerProvisioner) upgradeIso() error { if err := provisioner.Driver.Stop(); err != nil { return err - } if err := utils.WaitFor(drivers.MachineInState(provisioner.Driver, state.Stopped)); err != nil { return err - } machineName := provisioner.GetDriver().GetMachineName() log.Infof("Upgrading machine %s...", machineName) - isoFilename := "" - switch provisioner.GetDriver().DriverName() { - case "virtualbox": - isoFilename = "boot2docker-virtualbox.iso" - case "vmwarefusion", "vmwarevsphere": - isoFilename = "boot2docker-vmware.iso" - case "hyper-v": - isoFilename = "boot2docker-hyperv.iso" - default: - return ErrUnknownDriver - } - - b2dutils := utils.NewB2dUtils("", "", isoFilename) + b2dutils := utils.NewB2dUtils("", "") // Usually we call this implicitly, but call it here explicitly to get // the latest boot2docker ISO. if err := b2dutils.DownloadLatestBoot2Docker(); err != nil { return err - } // Copy the latest version of boot2docker ISO to the machine's directory if err := b2dutils.CopyIsoToMachineDir("", machineName); err != nil { return err - } + log.Infof("Starting machine back up...") + if err := provisioner.Driver.Start(); err != nil { return err - } return utils.WaitFor(drivers.MachineInState(provisioner.Driver, state.Running)) - } func (provisioner *Boot2DockerProvisioner) Package(name string, action pkgaction.PackageAction) error { if name == "docker" && action == pkgaction.Upgrade { if err := provisioner.upgradeIso(); err != nil { return err - } - } return nil - +} + +func (provisioner *Boot2DockerProvisioner) Hostname() (string, error) { + return provisioner.SSHCommand("hostname") +} + +func (provisioner *Boot2DockerProvisioner) SetHostname(hostname string) error { + if _, err := provisioner.SSHCommand(fmt.Sprintf( + "sudo /usr/bin/sethostname %s && echo %q | sudo tee /var/lib/boot2docker/etc/hostname", + hostname, + hostname, + )); err != nil { + return err + } + + return nil +} + +func (provisioner *Boot2DockerProvisioner) GetDockerOptionsDir() string { + return "/var/lib/boot2docker" +} + +func (provisioner *Boot2DockerProvisioner) GetAuthOptions() auth.AuthOptions { + return provisioner.AuthOptions +} + +func (provisioner *Boot2DockerProvisioner) GenerateDockerOptions(dockerPort int) (*DockerOptions, error) { + var ( + engineCfg bytes.Buffer + ) + + driverNameLabel := fmt.Sprintf("provider=%s", provisioner.Driver.DriverName()) + provisioner.EngineOptions.Labels = append(provisioner.EngineOptions.Labels, driverNameLabel) + + engineConfigTmpl := ` +EXTRA_ARGS=' +{{ range .EngineOptions.Labels }}--label {{.}} +{{ end }}{{ range .EngineOptions.InsecureRegistry }}--insecure-registry {{.}} +{{ end }}{{ range .EngineOptions.RegistryMirror }}--registry-mirror {{.}} +{{ end }}{{ range .EngineOptions.ArbitraryFlags }}--{{.}} +{{ end }} +' +CACERT={{.AuthOptions.CaCertRemotePath}} +DOCKER_HOST='-H tcp://0.0.0.0:{{.DockerPort}}' +DOCKER_STORAGE={{.EngineOptions.StorageDriver}} +DOCKER_TLS=auto +SERVERKEY={{.AuthOptions.ServerKeyRemotePath}} +SERVERCERT={{.AuthOptions.ServerCertRemotePath}} + +{{range .EngineOptions.Env}}export \"{{ printf "%q" . }}\" +{{end}} +` + t, err := template.New("engineConfig").Parse(engineConfigTmpl) + if err != nil { + return nil, err + } + + engineConfigContext := EngineConfigContext{ + DockerPort: dockerPort, + AuthOptions: provisioner.AuthOptions, + EngineOptions: provisioner.EngineOptions, + } + + t.Execute(&engineCfg, engineConfigContext) + + daemonOptsDir := path.Join(provisioner.GetDockerOptionsDir(), "profile") + return &DockerOptions{ + EngineOptions: engineCfg.String(), + EngineOptionsPath: daemonOptsDir, + }, nil +} + +func (provisioner *Boot2DockerProvisioner) CompatibleWithHost() bool { + return provisioner.OsReleaseInfo.Id == "boot2docker" +} + +func (provisioner *Boot2DockerProvisioner) SetOsReleaseInfo(info *OsRelease) { + provisioner.OsReleaseInfo = info +} + +func (provisioner *Boot2DockerProvisioner) GetOsReleaseInfo() (*OsRelease, error) { + return provisioner.OsReleaseInfo, nil +} + +func (provisioner *Boot2DockerProvisioner) Provision(swarmOptions swarm.SwarmOptions, authOptions auth.AuthOptions, engineOptions engine.EngineOptions) error { + provisioner.SwarmOptions = swarmOptions + provisioner.AuthOptions = authOptions + provisioner.EngineOptions = engineOptions + + if provisioner.EngineOptions.StorageDriver == "" { + provisioner.EngineOptions.StorageDriver = "aufs" + } + + if err := provisioner.SetHostname(provisioner.Driver.GetMachineName()); err != nil { + return err + } + + ip, err := provisioner.GetDriver().GetIP() + if err != nil { + return err + } + + // b2d hosts need to wait for the daemon to be up + // before continuing with provisioning + if err := utils.WaitForDocker(ip, 2376); err != nil { + return err + } + + if err := makeDockerOptionsDir(provisioner); err != nil { + return err + } + + provisioner.AuthOptions = setRemoteAuthOptions(provisioner) + + if err := ConfigureAuth(provisioner); err != nil { + return err + } + + if err := configureSwarm(provisioner, swarmOptions, provisioner.AuthOptions); err != nil { + return err + } + + return nil +} + +func (provisioner *Boot2DockerProvisioner) SSHCommand(args string) (string, error) { + return drivers.RunSSHCommandFromDriver(provisioner.Driver, args) +} + +func (provisioner *Boot2DockerProvisioner) GetDriver() drivers.Driver { + return provisioner.Driver } diff --git a/libmachine/provision/rancheros.go b/libmachine/provision/rancheros.go index be0ef5e3eb..c71a2adebe 100644 --- a/libmachine/provision/rancheros.go +++ b/libmachine/provision/rancheros.go @@ -19,7 +19,6 @@ import ( const ( versionsUrl = "http://releases.rancher.com/os/versions.yml" isoUrl = "https://github.com/rancherio/os/releases/download/%s/machine-rancheros.iso" - isoFilename = "rancheros.iso" hostnameTmpl = `sudo mkdir -p /var/lib/rancher/conf/cloud-config.d/ sudo tee /var/lib/rancher/conf/cloud-config.d/machine-hostname.yml << EOF #cloud-config @@ -176,7 +175,7 @@ func (provisioner *RancherProvisioner) upgradeIso() error { log.Infof("Upgrading machine %s...", machineName) - b2dutils := utils.NewB2dUtils("", "", isoFilename) + b2dutils := utils.NewB2dUtils("", "") url, err := provisioner.getLatestISOURL() if err != nil { diff --git a/libmachine/provision/utils_test.go b/libmachine/provision/utils_test.go index 7fbae961e6..10617e638d 100644 --- a/libmachine/provision/utils_test.go +++ b/libmachine/provision/utils_test.go @@ -8,24 +8,11 @@ import ( "github.com/docker/machine/drivers/fakedriver" "github.com/docker/machine/libmachine/auth" - "github.com/docker/machine/libmachine/engine" ) -func engineOptions() engine.EngineOptions { - return engine.EngineOptions{ - StorageDriver: "aufs", - } -} - func TestGenerateDockerOptionsBoot2Docker(t *testing.T) { - g := GenericProvisioner{ - Driver: &fakedriver.FakeDriver{}, - EngineOptions: engineOptions(), - } p := &Boot2DockerProvisioner{ - DebianProvisioner{ - g, - }, + Driver: &fakedriver.FakeDriver{}, } dockerPort := 1234 p.AuthOptions = auth.AuthOptions{ @@ -33,38 +20,37 @@ func TestGenerateDockerOptionsBoot2Docker(t *testing.T) { ServerKeyRemotePath: "/test/server-key", ServerCertRemotePath: "/test/server-cert", } + engineConfigPath := "/var/lib/boot2docker/profile" dockerCfg, err := p.GenerateDockerOptions(dockerPort) if err != nil { t.Fatal(err) } + if dockerCfg.EngineOptionsPath != engineConfigPath { + t.Fatalf("expected engine path %s; received %s", engineConfigPath, dockerCfg.EngineOptionsPath) + } + if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("-H tcp://0.0.0.0:%d", dockerPort)) == -1 { t.Fatalf("-H docker port invalid; expected %d", dockerPort) } - if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("--tlscacert %s", p.AuthOptions.CaCertRemotePath)) == -1 { - t.Fatalf("--tlscacert option invalid; expected %s", p.AuthOptions.CaCertRemotePath) + if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("CACERT=%s", p.AuthOptions.CaCertRemotePath)) == -1 { + t.Fatalf("CACERT option invalid; expected %s", p.AuthOptions.CaCertRemotePath) } - if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("--tlscert %s", p.AuthOptions.ServerCertRemotePath)) == -1 { - t.Fatalf("--tlscert option invalid; expected %s", p.AuthOptions.ServerCertRemotePath) + if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("SERVERKEY=%s", p.AuthOptions.ServerKeyRemotePath)) == -1 { + t.Fatalf("SERVERKEY option invalid; expected %s", p.AuthOptions.ServerKeyRemotePath) } - if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("--tlskey %s", p.AuthOptions.ServerKeyRemotePath)) == -1 { - t.Fatalf("--tlskey option invalid; expected %s", p.AuthOptions.ServerKeyRemotePath) + if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("SERVERCERT=%s", p.AuthOptions.ServerCertRemotePath)) == -1 { + t.Fatalf("SERVERCERT option invalid; expected %s", p.AuthOptions.ServerCertRemotePath) } } func TestMachinePortBoot2Docker(t *testing.T) { - g := GenericProvisioner{ - Driver: &fakedriver.FakeDriver{}, - EngineOptions: engineOptions(), - } p := &Boot2DockerProvisioner{ - DebianProvisioner{ - g, - }, + Driver: &fakedriver.FakeDriver{}, } dockerPort := 2376 bindUrl := fmt.Sprintf("tcp://0.0.0.0:%d", dockerPort) @@ -95,14 +81,8 @@ func TestMachinePortBoot2Docker(t *testing.T) { } func TestMachineCustomPortBoot2Docker(t *testing.T) { - g := GenericProvisioner{ - Driver: &fakedriver.FakeDriver{}, - EngineOptions: engineOptions(), - } p := &Boot2DockerProvisioner{ - DebianProvisioner{ - g, - }, + Driver: &fakedriver.FakeDriver{}, } dockerPort := 3376 bindUrl := fmt.Sprintf("tcp://0.0.0.0:%d", dockerPort) diff --git a/test/integration/core/supported-engine-options.bats b/test/integration/core/supported-engine-options.bats index d6f06e8c2a..afa56678f9 100644 --- a/test/integration/core/supported-engine-options.bats +++ b/test/integration/core/supported-engine-options.bats @@ -5,7 +5,7 @@ load ${BASE_TEST_DIR}/helpers.bash @test "$DRIVER: create with supported engine options" { run machine create -d $DRIVER \ --engine-label spam=eggs \ - --engine-storage-driver devicemapper \ + --engine-storage-driver overlay \ --engine-insecure-registry registry.myco.com \ $NAME echo "$output" @@ -19,5 +19,5 @@ load ${BASE_TEST_DIR}/helpers.bash @test "$DRIVER: check for engine storage driver" { storage_driver_info=$(docker $(machine config $NAME) info | grep "Storage Driver") - [[ $storage_driver_info =~ "devicemapper" ]] + [[ $storage_driver_info =~ "overlay" ]] } diff --git a/utils/b2d.go b/utils/b2d.go index df130e1472..e49651a2e4 100644 --- a/utils/b2d.go +++ b/utils/b2d.go @@ -1,7 +1,7 @@ package utils import ( - //"encoding/json" + "encoding/json" "fmt" "io" "io/ioutil" @@ -45,10 +45,11 @@ type B2dUtils struct { githubBaseUrl string } -func NewB2dUtils(githubApiBaseUrl, githubBaseUrl, isoFilename string) *B2dUtils { +func NewB2dUtils(githubApiBaseUrl, githubBaseUrl string) *B2dUtils { defaultBaseApiUrl := "https://api.github.com" defaultBaseUrl := "https://github.com" imgCachePath := GetMachineCacheDir() + isoFilename := "boot2docker.iso" if githubApiBaseUrl == "" { githubApiBaseUrl = defaultBaseApiUrl @@ -70,41 +71,27 @@ func NewB2dUtils(githubApiBaseUrl, githubBaseUrl, isoFilename string) *B2dUtils // Get the latest boot2docker release tag name (e.g. "v0.6.0"). // FIXME: find or create some other way to get the "latest release" of boot2docker since the GitHub API has a pretty low rate limit on API requests func (b *B2dUtils) GetLatestBoot2DockerReleaseURL() (string, error) { - //client := getClient() - //apiUrl := fmt.Sprintf("%s/repos/boot2docker/boot2docker/releases", b.githubApiBaseUrl) - //rsp, err := client.Get(apiUrl) - //if err != nil { - // return "", err - //} - //defer rsp.Body.Close() + client := getClient() + apiUrl := fmt.Sprintf("%s/repos/boot2docker/boot2docker/releases", b.githubApiBaseUrl) + rsp, err := client.Get(apiUrl) + if err != nil { + return "", err + } + defer rsp.Body.Close() - //var t []struct { - // TagName string `json:"tag_name"` - // PreRelease bool `json:"prerelease"` - //} - //if err := json.NewDecoder(rsp.Body).Decode(&t); err != nil { - // return "", fmt.Errorf("Error demarshaling the Github API response: %s\nYou may be getting rate limited by Github.", err) - //} - //if len(t) == 0 { - // return "", fmt.Errorf("no releases found") - //} - - //// find the latest "released" release (i.e. not pre-release) - //isoUrl := "" - //for _, r := range t { - // if !r.PreRelease { - // tag := r.TagName - // isoUrl = fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/%s/boot2docker.iso", b.githubBaseUrl, tag) - // break - // } - //} - //return isoUrl, nil - - // TODO: once we decide on the final versioning and location we will - // enable the above "check for latest" - u := fmt.Sprintf("https://s3.amazonaws.com/docker-mcn/public/b2d-next/%s", b.isoFilename) - return u, nil + var t []struct { + TagName string `json:"tag_name"` + } + if err := json.NewDecoder(rsp.Body).Decode(&t); err != nil { + return "", fmt.Errorf("Error demarshaling the Github API response: %s\nYou may be getting rate limited by Github.", err) + } + if len(t) == 0 { + return "", fmt.Errorf("no releases found") + } + tag := t[0].TagName + isoUrl := fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/%s/boot2docker.iso", b.githubBaseUrl, tag) + return isoUrl, nil } func removeFileIfExists(name string) error { diff --git a/utils/b2d_test.go b/utils/b2d_test.go index e780a16e7b..2fe5a38103 100644 --- a/utils/b2d_test.go +++ b/utils/b2d_test.go @@ -1,6 +1,7 @@ package utils import ( + "fmt" "io/ioutil" "net/http" "net/http/httptest" @@ -10,26 +11,20 @@ import ( func TestGetLatestBoot2DockerReleaseUrl(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - respText := `[{"tag_name": "0.2", "prerelease": true, "tag_name": "0.1", "prerelease": false}]` + respText := `[{"tag_name": "0.1"}]` w.Write([]byte(respText)) })) defer ts.Close() - b := NewB2dUtils(ts.URL, ts.URL, "virtualbox") + b := NewB2dUtils(ts.URL, ts.URL) isoUrl, err := b.GetLatestBoot2DockerReleaseURL() if err != nil { t.Fatal(err) } - // TODO: update to release URL once we get the releases worked - // out for b2d-ng - //expectedUrl := fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/0.1/boot2docker.iso", ts.URL) - //if isoUrl != expectedUrl { - // t.Fatalf("expected url %s; received %s", isoUrl) - //} - - if isoUrl == "" { - t.Fatalf("expected a url for the iso") + expectedUrl := fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/0.1/boot2docker.iso", ts.URL) + if isoUrl != expectedUrl { + t.Fatalf("expected url %s; received %s", isoUrl) } } @@ -47,7 +42,7 @@ func TestDownloadIso(t *testing.T) { t.Fatal(err) } - b := NewB2dUtils(ts.URL, ts.URL, "") + b := NewB2dUtils(ts.URL, ts.URL) if err := b.DownloadISO(tmpDir, filename, ts.URL); err != nil { t.Fatal(err) }