This repository has been archived by the owner on Oct 4, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Works fine, not all features tested, installation instructions semi-incomplete, missing a few minor features + more documentation
- Loading branch information
Sam Stenvall
committed
Jul 12, 2016
1 parent
d439875
commit 803d3e6
Showing
11 changed files
with
747 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1 @@ | ||
# Compiled Object files, Static and Dynamic libs (Shared Objects) | ||
*.o | ||
*.a | ||
*.so | ||
|
||
# Folders | ||
_obj | ||
_test | ||
|
||
# Architecture specific extensions/prefixes | ||
*.[568vq] | ||
[568vq].out | ||
|
||
*.cgo1.go | ||
*.cgo2.c | ||
_cgo_defun.c | ||
_cgo_gotypes.go | ||
_cgo_export.* | ||
|
||
_testmain.go | ||
|
||
*.exe | ||
*.test | ||
*.prof | ||
.idea |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,68 @@ | ||
# packer-builder-upcloud | ||
A Packer builder for UpCloud | ||
|
||
This is a Packer builder which can be used to generate storage templates on UpCloud. | ||
|
||
## Installation | ||
|
||
To install the builder you'll need to download it, install it, then copy the binary to Packer's plugin directory. | ||
|
||
``` | ||
go get github.com/jalle19/packer-builder-upcloud | ||
cd $GOPATH/src/github.com/jalle19/packer-builder-upcloud | ||
go install | ||
cp $GOPATH/bin/packer-builder-upcloud ~/.packer.d/plugins | ||
``` | ||
|
||
## Usage | ||
|
||
Here is a sample template: | ||
|
||
```json | ||
{ | ||
"variables": { | ||
"UPCLOUD_API_USERNAME": "your username", | ||
"UPCLOUD_API_PASSWORD": "your password" | ||
}, | ||
"builders": [ | ||
{ | ||
"type": "upcloud", | ||
"username": "{{ user `UPCLOUD_API_USERNAME` }}", | ||
"password": "{{ user `UPCLOUD_API_PASSWORD` }}", | ||
"plan": "1xCPU-1GB", | ||
"zone": "fi-hel1", | ||
"storage_uuid": "01000000-0000-4000-8000-000030040200" | ||
} | ||
] | ||
} | ||
``` | ||
|
||
If everything goes according to plan, you should see something like this: | ||
|
||
``` | ||
packer build packer.json | ||
upcloud output will be in this color. | ||
==> upcloud: Creating temporary SSH key ... | ||
==> upcloud: Creating server "packer-builder-upcloud-1468327456" ... | ||
==> upcloud: Waiting for server "packer-builder-upcloud-1468327456" to enter the "started" state ... | ||
==> upcloud: Server "packer-builder-upcloud-1468327456" is now in "started" state | ||
==> upcloud: Waiting for SSH to become available... | ||
==> upcloud: Connected to SSH! | ||
==> upcloud: Provisioning with shell script: scripts/update.sh | ||
... | ||
==> upcloud: Stopping server "packer-builder-upcloud-1468327456" ... | ||
==> upcloud: Waiting for server "packer-builder-upcloud-1468327456" to enter the "stopped" state ... | ||
==> upcloud: Server "packer-builder-upcloud-1468327456" is now in "stopped" state | ||
==> upcloud: Templatizing storage device "packer-builder-upcloud-1468327456-disk1" ... | ||
==> upcloud: Waiting for storage "packer-builder-upcloud-1468327456-disk1-template-1468327515" to enter the "online" state | ||
==> upcloud: Waiting for server "packer-builder-upcloud-1468327456" to exit the "maintenance" state ... | ||
==> upcloud: Deleting server "packer-builder-upcloud-1468327456" ... | ||
Build 'upcloud' finished. | ||
==> Builds finished. The artifacts of successful builds are: | ||
--> upcloud: Private template (UUID: 01875f67-4eb5-4d90-982c-d7a164646fcb, Title: packer-builder-upcloud-1468327456-disk1-template-1468327515, Zone: fi-hel1) | ||
``` | ||
|
||
## License | ||
|
||
MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package upcloud | ||
|
||
import ( | ||
"fmt" | ||
"github.com/jalle19/upcloud-go-sdk/upcloud/request" | ||
"github.com/jalle19/upcloud-go-sdk/upcloud/service" | ||
"log" | ||
) | ||
|
||
// Artifact represents a template of a storage as the result of a Packer build | ||
type Artifact struct { | ||
UUID string | ||
Zone string | ||
Title string | ||
service *service.Service | ||
} | ||
|
||
// BuilderId returns the unique identifier of this builder | ||
func (*Artifact) BuilderId() string { | ||
return BuilderId | ||
} | ||
|
||
// Destroy destroys the template | ||
func (a *Artifact) Destroy() error { | ||
log.Printf("Deleting template \"%s\"", a.Title) | ||
|
||
return a.service.DeleteStorage(&request.DeleteStorageRequest{ | ||
UUID: a.UUID, | ||
}) | ||
} | ||
|
||
// Files returns the files represented by the artifact | ||
func (*Artifact) Files() []string { | ||
return nil | ||
} | ||
|
||
func (a *Artifact) Id() string { | ||
return a.UUID | ||
} | ||
|
||
func (*Artifact) State(name string) interface{} { | ||
return nil | ||
} | ||
|
||
// String returns the string representation of the artifact. It is printed at the end of builds. | ||
func (a *Artifact) String() string { | ||
return fmt.Sprintf("Private template (UUID: %s, Title: %s, Zone: %s)", a.UUID, a.Title, a.Zone) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
package upcloud | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"github.com/jalle19/upcloud-go-sdk/upcloud" | ||
"github.com/jalle19/upcloud-go-sdk/upcloud/client" | ||
"github.com/jalle19/upcloud-go-sdk/upcloud/service" | ||
"github.com/mitchellh/multistep" | ||
"github.com/mitchellh/packer/common" | ||
"github.com/mitchellh/packer/helper/communicator" | ||
"github.com/mitchellh/packer/helper/config" | ||
"github.com/mitchellh/packer/packer" | ||
"github.com/mitchellh/packer/template/interpolate" | ||
"log" | ||
"time" | ||
) | ||
|
||
// The unique ID for this builder. | ||
const BuilderId = "jalle19.upcloud" | ||
|
||
// Config represents the configuration for this builder | ||
type Config struct { | ||
common.PackerConfig `mapstructure:",squash"` | ||
Comm communicator.Config `mapstructure:",squash"` | ||
|
||
Username string `mapstructure:"username"` | ||
Password string `mapstructure:"password"` | ||
|
||
Plan string `mapstructure:"plan"` | ||
CoreNumber int `mapstructure:"core_number"` | ||
MemoryAmount int `mapstructure:"memory_amount"` | ||
Title string `mapstructure:"title"` | ||
Hostname string `mapstructure:"hostname"` | ||
Zone string `mapstructure:"zone"` | ||
StorageUUID string `mapstructure:"storage_uuid"` | ||
StorageTitle string `mapstructure:"storage_title"` | ||
StorageSize int `mapstructure:"storage_size"` | ||
StorageTier string `mapstructure:"storage_tier"` | ||
|
||
RawStateTimeoutDuration string `mapstructure:"state_timeout_duration"` | ||
StateTimeoutDuration time.Duration | ||
|
||
ctx interpolate.Context | ||
} | ||
|
||
// Builder represents a Packer Builder. | ||
type Builder struct { | ||
config Config | ||
runner multistep.Runner | ||
} | ||
|
||
// Prepare processes the build configuration parameters and validates the configuration | ||
func (self *Builder) Prepare(raws ...interface{}) (parms []string, retErr error) { | ||
// Parse configuration | ||
err := config.Decode(&self.config, &config.DecodeOpts{ | ||
Interpolate: true, | ||
InterpolateContext: &self.config.ctx, | ||
}, raws...) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Assign default values if possible | ||
if self.config.Title == "" { | ||
self.config.Title = fmt.Sprintf("packer-builder-upcloud-%d", time.Now().Unix()) | ||
} | ||
|
||
if self.config.Hostname == "" { | ||
self.config.Hostname = fmt.Sprintf("%s.example.com", self.config.Title) | ||
} | ||
|
||
if self.config.StorageSize == 0 { | ||
self.config.StorageSize = 30 | ||
} | ||
|
||
if self.config.StorageTitle == "" { | ||
self.config.StorageTitle = fmt.Sprintf("%s-disk1", self.config.Title) | ||
} | ||
|
||
if self.config.StorageTier == "" { | ||
self.config.StorageTier = upcloud.StorageTierMaxIOPS | ||
} | ||
|
||
if self.config.Comm.SSHUsername == "" { | ||
self.config.Comm.SSHUsername = "root" | ||
} | ||
|
||
if self.config.RawStateTimeoutDuration == "" { | ||
self.config.RawStateTimeoutDuration = "5m" | ||
} | ||
|
||
// Validation | ||
var errs *packer.MultiError | ||
errs = packer.MultiErrorAppend(errs, self.config.Comm.Prepare(&self.config.ctx)...) | ||
|
||
// Check for required configurations that will display errors if not set | ||
if self.config.Username == "" { | ||
errs = packer.MultiErrorAppend( | ||
errs, errors.New("\"username\" must be specified")) | ||
} | ||
|
||
if self.config.Password == "" { | ||
errs = packer.MultiErrorAppend( | ||
errs, errors.New("\"password\" must be specified")) | ||
} | ||
|
||
if self.config.Plan == "" && (self.config.CoreNumber == 0 || self.config.MemoryAmount == 0) { | ||
errs = packer.MultiErrorAppend( | ||
errs, errors.New("\"core_number\" and \"memory_amount\" must be specified if \"plan\" is not specified")) | ||
} | ||
|
||
if self.config.Plan != "" && (self.config.CoreNumber > 0 || self.config.MemoryAmount > 0) { | ||
errs = packer.MultiErrorAppend( | ||
errs, errors.New("\"core_number\" and \"memory_amount\" must not be specified when \"plan\" is specified")) | ||
} | ||
|
||
if self.config.Zone == "" { | ||
errs = packer.MultiErrorAppend( | ||
errs, errors.New("\"zone\" must be specified")) | ||
} | ||
|
||
if self.config.StorageUUID == "" { | ||
errs = packer.MultiErrorAppend( | ||
errs, errors.New("\"storage_uuid\" must be specified")) | ||
} | ||
|
||
stateTimeout, err := time.ParseDuration(self.config.RawStateTimeoutDuration) | ||
if err != nil { | ||
errs = packer.MultiErrorAppend( | ||
errs, fmt.Errorf("Failed to parse state_timeout_duration: %s", err)) | ||
} | ||
self.config.StateTimeoutDuration = stateTimeout | ||
|
||
log.Println(common.ScrubConfig(self.config, self.config.Password, self.config.Username)) | ||
|
||
if len(errs.Errors) > 0 { | ||
retErr = errors.New(errs.Error()) | ||
} | ||
|
||
return nil, retErr | ||
} | ||
|
||
// Run executes the actual build steps | ||
func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { | ||
// Create the service | ||
client := client.New(self.config.Username, self.config.Password) | ||
service := service.New(client) | ||
|
||
// Set up the state which is used to share state between the steps | ||
state := new(multistep.BasicStateBag) | ||
state.Put("config", self.config) | ||
state.Put("service", *service) | ||
state.Put("hook", hook) | ||
state.Put("ui", ui) | ||
|
||
// Build the steps | ||
steps := []multistep.Step{ | ||
&StepCreateSSHKey{ | ||
Debug: self.config.PackerDebug, | ||
DebugKeyPath: fmt.Sprintf("packer-builder-upcloud-%s.pem", self.config.PackerBuildName), | ||
}, | ||
new(StepCreateServer), | ||
&communicator.StepConnect{ | ||
Config: &self.config.Comm, | ||
Host: sshHostCallback, | ||
SSHConfig: sshConfigCallback, | ||
}, | ||
new(common.StepProvision), | ||
new(StepTemplatizeStorage), | ||
} | ||
|
||
// Create the runner which will run the steps we just build | ||
self.runner = &multistep.BasicRunner{Steps: steps} | ||
self.runner.Run(state) | ||
|
||
if rawErr, ok := state.GetOk("error"); ok { | ||
return nil, rawErr.(error) | ||
} | ||
|
||
// Extract the final storage details from the state | ||
rawDetails, ok := state.GetOk("storage_details") | ||
|
||
if !ok { | ||
log.Println("No storage details found in state, the build was probably cancelled") | ||
return nil, nil | ||
} | ||
|
||
storageDetails := rawDetails.(*upcloud.StorageDetails) | ||
|
||
// Create an artifact and return it | ||
artifact := &Artifact{ | ||
UUID: storageDetails.UUID, | ||
Zone: storageDetails.Zone, | ||
Title: storageDetails.Title, | ||
service: service, | ||
} | ||
|
||
return artifact, nil | ||
} | ||
|
||
// Cancel is called when the build is cancelled | ||
func (self *Builder) Cancel() { | ||
if self.runner != nil { | ||
log.Println("Cancelling the step runner ...") | ||
self.runner.Cancel() | ||
} | ||
|
||
fmt.Println("Cancelling the builder ...") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package upcloud | ||
|
||
import ( | ||
"github.com/mitchellh/multistep" | ||
"github.com/mitchellh/packer/packer" | ||
) | ||
|
||
// Helper for easily bailing out of the execution flow | ||
func handleError(err error, state multistep.StateBag) multistep.StepAction { | ||
state.Put("error", err) | ||
ui := state.Get("ui").(packer.Ui) | ||
ui.Error(err.Error()) | ||
|
||
return multistep.ActionHalt | ||
} |
Oops, something went wrong.