Merge pull request #7034 from zenjoy/hcloud/rescue_mode

[Hetzner Cloud] Support for builds using rescue mode
This commit is contained in:
Megan Marsh 2018-11-26 11:39:34 -08:00 committed by GitHub
commit 8cc3320977
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 131 additions and 50 deletions

View File

@ -35,6 +35,8 @@ type Config struct {
UserDataFile string `mapstructure:"user_data_file"`
SSHKeys []string `mapstructure:"ssh_keys"`
RescueMode string `mapstructure:"rescue"`
ctx interpolate.Context
}

View File

@ -67,21 +67,31 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu
// Store the server id for later
state.Put("server_id", serverCreateResult.Server.ID)
_, errCh := client.Action.WatchProgress(context.TODO(), serverCreateResult.Action)
for {
select {
case err1 := <-errCh:
if err1 == nil {
return multistep.ActionContinue
} else {
err := fmt.Errorf("Error creating server: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if err := waitForAction(context.TODO(), client, serverCreateResult.Action); err != nil {
err := fmt.Errorf("Error creating server: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
for _, nextAction := range serverCreateResult.NextActions {
if err := waitForAction(context.TODO(), client, nextAction); err != nil {
err := fmt.Errorf("Error creating server: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
if c.RescueMode != "" {
if err := setRescue(context.TODO(), client, serverCreateResult.Server, c.RescueMode, sshKeys); err != nil {
err := fmt.Errorf("Error enabling rescue mode: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
}
func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
@ -101,3 +111,48 @@ func (s *stepCreateServer) Cleanup(state multistep.StateBag) {
"Error destroying server. Please destroy it manually: %s", err))
}
}
func setRescue(ctx context.Context, client *hcloud.Client, server *hcloud.Server, rescue string, sshKeys []*hcloud.SSHKey) error {
rescueChanged := false
if server.RescueEnabled {
rescueChanged = true
action, _, err := client.Server.DisableRescue(ctx, server)
if err != nil {
return err
}
if err := waitForAction(ctx, client, action); err != nil {
return err
}
}
if rescue != "" {
rescueChanged = true
res, _, err := client.Server.EnableRescue(ctx, server, hcloud.ServerEnableRescueOpts{
Type: hcloud.ServerRescueType(rescue),
SSHKeys: sshKeys,
})
if err != nil {
return err
}
if err := waitForAction(ctx, client, res.Action); err != nil {
return err
}
}
if rescueChanged {
action, _, err := client.Server.Reset(ctx, server)
if err != nil {
return err
}
if err := waitForAction(ctx, client, action); err != nil {
return err
}
}
return nil
}
func waitForAction(ctx context.Context, client *hcloud.Client, action *hcloud.Action) error {
_, errCh := client.Action.WatchProgress(ctx, action)
if err := <-errCh; err != nil {
return err
}
return nil
}

0
vendor/github.com/hetznercloud/hcloud-go/LICENSE generated vendored Normal file → Executable file
View File

2
vendor/github.com/hetznercloud/hcloud-go/hcloud/action.go generated vendored Normal file → Executable file
View File

@ -74,7 +74,7 @@ type ActionClient struct {
client *Client
}
// GetByID retrieves an action by its ID.
// GetByID retrieves an action by its ID. If the action does not exist, nil is returned.
func (c *ActionClient) GetByID(ctx context.Context, id int) (*Action, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/actions/%d", id), nil)
if err != nil {

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/client.go generated vendored Normal file → Executable file
View File

7
vendor/github.com/hetznercloud/hcloud-go/hcloud/datacenter.go generated vendored Normal file → Executable file
View File

@ -29,7 +29,7 @@ type DatacenterClient struct {
client *Client
}
// GetByID retrieves a datacenter by its ID.
// GetByID retrieves a datacenter by its ID. If the datacenter does not exist, nil is returned.
func (c *DatacenterClient) GetByID(ctx context.Context, id int) (*Datacenter, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/datacenters/%d", id), nil)
if err != nil {
@ -47,7 +47,7 @@ func (c *DatacenterClient) GetByID(ctx context.Context, id int) (*Datacenter, *R
return DatacenterFromSchema(body.Datacenter), resp, nil
}
// GetByName retrieves an datacenter by its name.
// GetByName retrieves an datacenter by its name. If the datacenter does not exist, nil is returned.
func (c *DatacenterClient) GetByName(ctx context.Context, name string) (*Datacenter, *Response, error) {
path := "/datacenters?name=" + url.QueryEscape(name)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
@ -67,7 +67,8 @@ func (c *DatacenterClient) GetByName(ctx context.Context, name string) (*Datacen
return DatacenterFromSchema(body.Datacenters[0]), resp, nil
}
// Get retrieves a datacenter by its ID if the input can be parsed as an integer, otherwise it retrieves a datacenter by its name.
// Get retrieves a datacenter by its ID if the input can be parsed as an integer, otherwise it
// retrieves a datacenter by its name. If the datacenter does not exist, nil is returned.
func (c *DatacenterClient) Get(ctx context.Context, idOrName string) (*Datacenter, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/error.go generated vendored Normal file → Executable file
View File

3
vendor/github.com/hetznercloud/hcloud-go/hcloud/floating_ip.go generated vendored Normal file → Executable file
View File

@ -50,7 +50,8 @@ type FloatingIPClient struct {
client *Client
}
// GetByID retrieves a Floating IP by its ID.
// GetByID retrieves a Floating IP by its ID. If the Floating IP does not exist,
// nil is returned.
func (c *FloatingIPClient) GetByID(ctx context.Context, id int) (*FloatingIP, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/floating_ips/%d", id), nil)
if err != nil {

2
vendor/github.com/hetznercloud/hcloud-go/hcloud/hcloud.go generated vendored Normal file → Executable file
View File

@ -2,4 +2,4 @@
package hcloud
// Version is the library's version following Semantic Versioning.
const Version = "1.9.0"
const Version = "1.11.0"

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/helper.go generated vendored Normal file → Executable file
View File

7
vendor/github.com/hetznercloud/hcloud-go/hcloud/image.go generated vendored Normal file → Executable file
View File

@ -71,7 +71,7 @@ type ImageClient struct {
client *Client
}
// GetByID retrieves an image by its ID.
// GetByID retrieves an image by its ID. If the image does not exist, nil is returned.
func (c *ImageClient) GetByID(ctx context.Context, id int) (*Image, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/images/%d", id), nil)
if err != nil {
@ -89,7 +89,7 @@ func (c *ImageClient) GetByID(ctx context.Context, id int) (*Image, *Response, e
return ImageFromSchema(body.Image), resp, nil
}
// GetByName retrieves an image by its name.
// GetByName retrieves an image by its name. If the image does not exist, nil is returned.
func (c *ImageClient) GetByName(ctx context.Context, name string) (*Image, *Response, error) {
path := "/images?name=" + url.QueryEscape(name)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
@ -109,7 +109,8 @@ func (c *ImageClient) GetByName(ctx context.Context, name string) (*Image, *Resp
return ImageFromSchema(body.Images[0]), resp, nil
}
// Get retrieves an image by its ID if the input can be parsed as an integer, otherwise it retrieves an image by its name.
// Get retrieves an image by its ID if the input can be parsed as an integer, otherwise it
// retrieves an image by its name. If the image does not exist, nil is returned.
func (c *ImageClient) Get(ctx context.Context, idOrName string) (*Image, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/iso.go generated vendored Normal file → Executable file
View File

7
vendor/github.com/hetznercloud/hcloud-go/hcloud/location.go generated vendored Normal file → Executable file
View File

@ -25,7 +25,7 @@ type LocationClient struct {
client *Client
}
// GetByID retrieves a location by its ID.
// GetByID retrieves a location by its ID. If the location does not exist, nil is returned.
func (c *LocationClient) GetByID(ctx context.Context, id int) (*Location, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/locations/%d", id), nil)
if err != nil {
@ -43,7 +43,7 @@ func (c *LocationClient) GetByID(ctx context.Context, id int) (*Location, *Respo
return LocationFromSchema(body.Location), resp, nil
}
// GetByName retrieves an location by its name.
// GetByName retrieves an location by its name. If the location does not exist, nil is returned.
func (c *LocationClient) GetByName(ctx context.Context, name string) (*Location, *Response, error) {
path := "/locations?name=" + url.QueryEscape(name)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
@ -63,7 +63,8 @@ func (c *LocationClient) GetByName(ctx context.Context, name string) (*Location,
return LocationFromSchema(body.Locations[0]), resp, nil
}
// Get retrieves a location by its ID if the input can be parsed as an integer, otherwise it retrieves a location by its name.
// Get retrieves a location by its ID if the input can be parsed as an integer, otherwise it
// retrieves a location by its name. If the location does not exist, nil is returned.
func (c *LocationClient) Get(ctx context.Context, idOrName string) (*Location, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/pricing.go generated vendored Normal file → Executable file
View File

9
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema.go generated vendored Normal file → Executable file
View File

@ -35,6 +35,15 @@ func ActionFromSchema(s schema.Action) *Action {
return action
}
// ActionsFromSchema converts a slice of schema.Action to a slice of Action.
func ActionsFromSchema(s []schema.Action) []*Action {
var actions []*Action
for _, a := range s {
actions = append(actions, ActionFromSchema(a))
}
return actions
}
// FloatingIPFromSchema converts a schema.FloatingIP to a FloatingIP.
func FloatingIPFromSchema(s schema.FloatingIP) *FloatingIP {
f := &FloatingIP{

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/action.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/datacenter.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/error.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/floating_ip.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/image.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/iso.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/location.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/meta.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/pricing.go generated vendored Normal file → Executable file
View File

7
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/server.go generated vendored Normal file → Executable file
View File

@ -90,9 +90,10 @@ type ServerCreateRequest struct {
// ServerCreateResponse defines the schema of the response when
// creating a server.
type ServerCreateResponse struct {
Server Server `json:"server"`
Action Action `json:"action"`
RootPassword *string `json:"root_password"`
Server Server `json:"server"`
Action Action `json:"action"`
RootPassword *string `json:"root_password"`
NextActions []Action `json:"next_actions"`
}
// ServerUpdateRequest defines the schema of the request to update a server.

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/server_type.go generated vendored Normal file → Executable file
View File

0
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/ssh_key.go generated vendored Normal file → Executable file
View File

5
vendor/github.com/hetznercloud/hcloud-go/hcloud/schema/volume.go generated vendored Normal file → Executable file
View File

@ -28,8 +28,9 @@ type VolumeCreateRequest struct {
// VolumeCreateResponse defines the schema of the response
// when creating a volume.
type VolumeCreateResponse struct {
Volume Volume `json:"volume"`
Action *Action `json:"action"`
Volume Volume `json:"volume"`
Action *Action `json:"action"`
NextActions []Action `json:"next_actions"`
}
// VolumeListResponse defines the schema of the response

13
vendor/github.com/hetznercloud/hcloud-go/hcloud/server.go generated vendored Normal file → Executable file
View File

@ -97,7 +97,7 @@ type ServerClient struct {
client *Client
}
// GetByID retrieves a server by its ID.
// GetByID retrieves a server by its ID. If the server does not exist, nil is returned.
func (c *ServerClient) GetByID(ctx context.Context, id int) (*Server, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/servers/%d", id), nil)
if err != nil {
@ -115,7 +115,7 @@ func (c *ServerClient) GetByID(ctx context.Context, id int) (*Server, *Response,
return ServerFromSchema(body.Server), resp, nil
}
// GetByName retreives a server by its name.
// GetByName retreives a server by its name. If the server does not exist, nil is returned.
func (c *ServerClient) GetByName(ctx context.Context, name string) (*Server, *Response, error) {
path := "/servers?name=" + url.QueryEscape(name)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
@ -135,7 +135,8 @@ func (c *ServerClient) GetByName(ctx context.Context, name string) (*Server, *Re
return ServerFromSchema(body.Servers[0]), resp, nil
}
// Get retrieves a server by its ID if the input can be parsed as an integer, otherwise it retrieves a server by its name.
// Get retrieves a server by its ID if the input can be parsed as an integer, otherwise it
// retrieves a server by its name. If the server does not exist, nil is returned.
func (c *ServerClient) Get(ctx context.Context, idOrName string) (*Server, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))
@ -228,6 +229,7 @@ type ServerCreateResult struct {
Server *Server
Action *Action
RootPassword string
NextActions []*Action
}
// Create creates a new server.
@ -286,8 +288,9 @@ func (c *ServerClient) Create(ctx context.Context, opts ServerCreateOpts) (Serve
return ServerCreateResult{}, resp, err
}
result := ServerCreateResult{
Server: ServerFromSchema(respBody.Server),
Action: ActionFromSchema(respBody.Action),
Server: ServerFromSchema(respBody.Server),
Action: ActionFromSchema(respBody.Action),
NextActions: ActionsFromSchema(respBody.NextActions),
}
if respBody.RootPassword != nil {
result.RootPassword = *respBody.RootPassword

7
vendor/github.com/hetznercloud/hcloud-go/hcloud/server_type.go generated vendored Normal file → Executable file
View File

@ -49,7 +49,7 @@ type ServerTypeClient struct {
client *Client
}
// GetByID retrieves a server type by its ID.
// GetByID retrieves a server type by its ID. If the server type does not exist, nil is returned.
func (c *ServerTypeClient) GetByID(ctx context.Context, id int) (*ServerType, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/server_types/%d", id), nil)
if err != nil {
@ -67,7 +67,7 @@ func (c *ServerTypeClient) GetByID(ctx context.Context, id int) (*ServerType, *R
return ServerTypeFromSchema(body.ServerType), resp, nil
}
// GetByName retrieves a server type by its name.
// GetByName retrieves a server type by its name. If the server type does not exist, nil is returned.
func (c *ServerTypeClient) GetByName(ctx context.Context, name string) (*ServerType, *Response, error) {
path := "/server_types?name=" + url.QueryEscape(name)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
@ -87,7 +87,8 @@ func (c *ServerTypeClient) GetByName(ctx context.Context, name string) (*ServerT
return ServerTypeFromSchema(body.ServerTypes[0]), resp, nil
}
// Get retrieves a server type by its ID if the input can be parsed as an integer, otherwise it retrieves a server type by its name.
// Get retrieves a server type by its ID if the input can be parsed as an integer, otherwise it
// retrieves a server type by its name. If the server type does not exist, nil is returned.
func (c *ServerTypeClient) Get(ctx context.Context, idOrName string) (*ServerType, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))

9
vendor/github.com/hetznercloud/hcloud-go/hcloud/ssh_key.go generated vendored Normal file → Executable file
View File

@ -26,7 +26,7 @@ type SSHKeyClient struct {
client *Client
}
// GetByID retrieves a SSH key by its ID.
// GetByID retrieves a SSH key by its ID. If the SSH key does not exist, nil is returned.
func (c *SSHKeyClient) GetByID(ctx context.Context, id int) (*SSHKey, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/ssh_keys/%d", id), nil)
if err != nil {
@ -44,7 +44,7 @@ func (c *SSHKeyClient) GetByID(ctx context.Context, id int) (*SSHKey, *Response,
return SSHKeyFromSchema(body.SSHKey), resp, nil
}
// GetByName retrieves a SSH key by its name.
// GetByName retrieves a SSH key by its name. If the SSH key does not exist, nil is returned.
func (c *SSHKeyClient) GetByName(ctx context.Context, name string) (*SSHKey, *Response, error) {
path := "/ssh_keys?name=" + url.QueryEscape(name)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
@ -64,7 +64,7 @@ func (c *SSHKeyClient) GetByName(ctx context.Context, name string) (*SSHKey, *Re
return SSHKeyFromSchema(body.SSHKeys[0]), resp, nil
}
// GetByFingerprint retreives a SSH key by its fingerprint.
// GetByFingerprint retreives a SSH key by its fingerprint. If the SSH key does not exist, nil is returned.
func (c *SSHKeyClient) GetByFingerprint(ctx context.Context, fingerprint string) (*SSHKey, *Response, error) {
path := "/ssh_keys?fingerprint=" + url.QueryEscape(fingerprint)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
@ -84,7 +84,8 @@ func (c *SSHKeyClient) GetByFingerprint(ctx context.Context, fingerprint string)
return SSHKeyFromSchema(body.SSHKeys[0]), resp, nil
}
// Get retrieves a SSH key by its ID if the input can be parsed as an integer, otherwise it retrieves a SSH key by its name.
// Get retrieves a SSH key by its ID if the input can be parsed as an integer, otherwise it
// retrieves a SSH key by its name. If the SSH key does not exist, nil is returned.
func (c *SSHKeyClient) Get(ctx context.Context, idOrName string) (*SSHKey, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))

18
vendor/github.com/hetznercloud/hcloud-go/hcloud/volume.go generated vendored Normal file → Executable file
View File

@ -36,7 +36,7 @@ type VolumeClient struct {
client *Client
}
// GetByID retrieves a volume by its ID.
// GetByID retrieves a volume by its ID. If the volume does not exist, nil is returned.
func (c *VolumeClient) GetByID(ctx context.Context, id int) (*Volume, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/volumes/%d", id), nil)
if err != nil {
@ -54,7 +54,7 @@ func (c *VolumeClient) GetByID(ctx context.Context, id int) (*Volume, *Response,
return VolumeFromSchema(body.Volume), resp, nil
}
// GetByName retrieves a volume by its name.
// GetByName retrieves a volume by its name. If the volume does not exist, nil is returned.
func (c *VolumeClient) GetByName(ctx context.Context, name string) (*Volume, *Response, error) {
path := "/volumes?name=" + url.QueryEscape(name)
req, err := c.client.NewRequest(ctx, "GET", path, nil)
@ -74,8 +74,8 @@ func (c *VolumeClient) GetByName(ctx context.Context, name string) (*Volume, *Re
return VolumeFromSchema(body.Volumes[0]), resp, nil
}
// Get retrieves a volume by its ID if the input can be parsed as an integer,
// otherwise it retrieves a volume by its name.
// Get retrieves a volume by its ID if the input can be parsed as an integer, otherwise it
// retrieves a volume by its name. If the volume does not exist, nil is returned.
func (c *VolumeClient) Get(ctx context.Context, idOrName string) (*Volume, *Response, error) {
if id, err := strconv.Atoi(idOrName); err == nil {
return c.GetByID(ctx, int(id))
@ -161,8 +161,9 @@ func (o VolumeCreateOpts) Validate() error {
// VolumeCreateResult is the result of creating a volume.
type VolumeCreateResult struct {
Volume *Volume
Action *Action
Volume *Volume
Action *Action
NextActions []*Action
}
// Create creates a new volume with the given options.
@ -209,8 +210,9 @@ func (c *VolumeClient) Create(ctx context.Context, opts VolumeCreateOpts) (Volum
}
return VolumeCreateResult{
Volume: VolumeFromSchema(respBody.Volume),
Action: action,
Volume: VolumeFromSchema(respBody.Volume),
Action: action,
NextActions: ActionsFromSchema(respBody.NextActions),
}, resp, nil
}

View File

@ -72,6 +72,8 @@ builder.
- `ssh_keys` (array of strings) - List of SSH keys by name or id to be added
to image on launch.
- `rescue` (string) - Enable and boot in to the specified rescue system. This enables simple installation of custom operating systems. `linux64` `linux32` or `freebsd64`
## Basic Example
Here is a basic example. It is completely valid as soon as you enter your own