mirror of
https://github.com/hashicorp/packer.git
synced 2026-06-08 16:22:15 -04:00
Merge branch 'master' into hyperone
This commit is contained in:
commit
94a7a07c87
128 changed files with 5228 additions and 601 deletions
26
CHANGELOG.md
26
CHANGELOG.md
|
|
@ -1,5 +1,31 @@
|
|||
## 1.3.5 (upcoming)
|
||||
|
||||
### IMPROVEMENTS:
|
||||
* builder/amazon: AWS users can now use the Vault AWS engine to generate temporary credentials. [GH-7282]
|
||||
* builder/virtualbox: New "guest_additions_interface" option to enable attaching via a SATA interface. [GH-7298]
|
||||
* bulder/openstac: Deprecated compute/v2/images API [GH-7268]
|
||||
* post-processor/manifest: Add "custom_data" key to packer manifest post-processor [GH-7248]
|
||||
* builder/vmware: Add `cores` option for specifying the number of cores per socket. [GH-7191]
|
||||
* post-processor/googlecompute-export: Extend auth for the GCE-post-processors to act like the GCE builder. [GH-7222]
|
||||
* post-processor/googlecompute-import: Extend auth for the GCE-post-processors to act like the GCE builder. [GH-7222]
|
||||
|
||||
### BUG FIXES:
|
||||
* provisioner/salt: Force powershell to overwrite duplicate files [GH-7281]
|
||||
* builder/vmware-esxi: Should properly strip whitespace from end of names of files stored on esxi. [GH-7310]
|
||||
* builder/hyper-v: Fix regression where we improperly handled spaces in switch names [GH-7266]
|
||||
* core: clean up Makefile [GH-7254][GH-7265]
|
||||
* builder/cloudstack: Updated sdk version; can now use ostype name in template_os option. [GH-7264]
|
||||
* builder/azure: Fixed Azure interactive authentication [GH-7276]
|
||||
* builder/hyper-v: Fix integer overflows in 32-bit builds [GH-7251]
|
||||
* builder/google: Change metadata url to use a FQDN fixing bug stemming from differing DNS/search domains. [GH-7260]
|
||||
* core: Fixes mismatches in checksums for dependencies for Go 1.11.4+ [GH-7261]
|
||||
* core: make sure 'only' option is completely ignored by post-processors [GH-7262]
|
||||
|
||||
### Features:
|
||||
**new provisioner**`inspec` Added inspec.io provisioner #[GH-7180]
|
||||
**new post-processor** `digitalocean-import`Add digitalocean-import post-processor. [GH-7060]
|
||||
**new builder** `vagrant` allows users to call vagrant to provision starting from vagrant boxes and save them as new vagrant boxes. [GH-7221]
|
||||
|
||||
## 1.3.4 (January 30, 2019)
|
||||
### IMPROVEMENTS:
|
||||
* builder/alicloud: delete copied image and snapshots if corresponding options
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
/builder/alicloud/ @chhaj5236
|
||||
/builder/amazon/ebssurrogate/ @jen20
|
||||
/builder/amazon/ebsvolume/ @jen20
|
||||
/builder/azure/ @boumenot
|
||||
/builder/azure/ @paulmey
|
||||
/builder/hyperv/ @taliesins
|
||||
/builder/lxc/ @ChrisLundquist
|
||||
/builder/lxd/ @ChrisLundquist
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -11,7 +11,7 @@ GOPATH=$(shell go env GOPATH)
|
|||
# gofmt
|
||||
UNFORMATTED_FILES=$(shell find . -not -path "./vendor/*" -name "*.go" | xargs gofmt -s -l)
|
||||
|
||||
EXECUTABLE_FILES=$(shell find . -type f -executable | egrep -v '^\./(website/[vendor|tmp]|vendor/|\.git|bin/|scripts/|pkg/)' | egrep -v '.*(\.sh|\.bats|\.git)' | egrep -v './provisioner/ansible/test-fixtures/exit1')
|
||||
EXECUTABLE_FILES=$(shell find . -type f -executable | egrep -v '^\./(website/[vendor|tmp]|vendor/|\.git|bin/|scripts/|pkg/)' | egrep -v '.*(\.sh|\.bats|\.git)' | egrep -v './provisioner/(ansible|inspec)/test-fixtures/exit1')
|
||||
|
||||
# Get the git commit
|
||||
GIT_DIRTY=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true)
|
||||
|
|
|
|||
|
|
@ -205,6 +205,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("access_config", &b.config.AccessConfig)
|
||||
state.Put("ami_config", &b.config.AMIConfig)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("awsSession", session)
|
||||
state.Put("hook", hook)
|
||||
|
|
|
|||
|
|
@ -16,8 +16,21 @@ import (
|
|||
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
||||
commonhelper "github.com/hashicorp/packer/helper/common"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
vaultapi "github.com/hashicorp/vault/api"
|
||||
)
|
||||
|
||||
type VaultAWSEngineOptions struct {
|
||||
Name string `mapstructure:"name"`
|
||||
RoleARN string `mapstructure:"role_arn"`
|
||||
TTL string `mapstructure:"ttl"`
|
||||
EngineName string `mapstructure:"engine_name"`
|
||||
}
|
||||
|
||||
func (v *VaultAWSEngineOptions) Empty() bool {
|
||||
return len(v.Name) == 0 && len(v.RoleARN) == 0 &&
|
||||
len(v.EngineName) == 0 && len(v.TTL) == 0
|
||||
}
|
||||
|
||||
// AccessConfig is for common configuration related to AWS access
|
||||
type AccessConfig struct {
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
|
|
@ -32,6 +45,7 @@ type AccessConfig struct {
|
|||
SkipMetadataApiCheck bool `mapstructure:"skip_metadata_api_check"`
|
||||
Token string `mapstructure:"token"`
|
||||
session *session.Session
|
||||
VaultAWSEngine VaultAWSEngineOptions `mapstructure:"vault_aws_engine"`
|
||||
|
||||
getEC2Connection func() ec2iface.EC2API
|
||||
}
|
||||
|
|
@ -44,6 +58,7 @@ func (c *AccessConfig) Session() (*session.Session, error) {
|
|||
}
|
||||
|
||||
config := aws.NewConfig().WithCredentialsChainVerboseErrors(true)
|
||||
|
||||
staticCreds := credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
|
||||
if _, err := staticCreds.Get(); err != credentials.ErrStaticCredentialsEmpty {
|
||||
config.WithCredentials(staticCreds)
|
||||
|
|
@ -122,14 +137,62 @@ func (c *AccessConfig) IsChinaCloud() bool {
|
|||
return strings.HasPrefix(c.SessionRegion(), "cn-")
|
||||
}
|
||||
|
||||
func (c *AccessConfig) GetCredsFromVault() error {
|
||||
// const EnvVaultAddress = "VAULT_ADDR"
|
||||
// const EnvVaultToken = "VAULT_TOKEN"
|
||||
vaultConfig := vaultapi.DefaultConfig()
|
||||
cli, err := vaultapi.NewClient(vaultConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting Vault client: %s", err)
|
||||
}
|
||||
if c.VaultAWSEngine.EngineName == "" {
|
||||
c.VaultAWSEngine.EngineName = "aws"
|
||||
}
|
||||
path := fmt.Sprintf("/%s/creds/%s", c.VaultAWSEngine.EngineName,
|
||||
c.VaultAWSEngine.Name)
|
||||
secret, err := cli.Logical().Read(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading vault secret: %s", err)
|
||||
}
|
||||
if secret == nil {
|
||||
return fmt.Errorf("Vault Secret does not exist at the given path.")
|
||||
}
|
||||
|
||||
c.AccessKey = secret.Data["access_key"].(string)
|
||||
c.SecretKey = secret.Data["secret_key"].(string)
|
||||
token := secret.Data["security_token"]
|
||||
if token != nil {
|
||||
c.Token = token.(string)
|
||||
} else {
|
||||
c.Token = ""
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
|
||||
if c.SkipMetadataApiCheck {
|
||||
log.Println("(WARN) skip_metadata_api_check ignored.")
|
||||
}
|
||||
// Either both access and secret key must be set or neither of them should
|
||||
// be.
|
||||
|
||||
// Make sure it's obvious from the config how we're getting credentials:
|
||||
// Vault, Packer config, or environemnt.
|
||||
if !c.VaultAWSEngine.Empty() {
|
||||
if len(c.AccessKey) > 0 {
|
||||
errs = append(errs,
|
||||
fmt.Errorf("If you have set vault_aws_engine, you must not set"+
|
||||
" the access_key or secret_key."))
|
||||
}
|
||||
// Go ahead and grab those credentials from Vault now, so we can set
|
||||
// the keys and token now.
|
||||
err := c.GetCredsFromVault()
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if (len(c.AccessKey) > 0) != (len(c.SecretKey) > 0) {
|
||||
errs = append(errs,
|
||||
fmt.Errorf("`access_key` and `secret_key` must both be either set or not set."))
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@ package common
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
retry "github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
|
@ -20,6 +23,51 @@ type StepPreValidate struct {
|
|||
|
||||
func (s *StepPreValidate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if accessConfig, ok := state.GetOk("access_config"); ok {
|
||||
accessconf := accessConfig.(*AccessConfig)
|
||||
if !accessconf.VaultAWSEngine.Empty() {
|
||||
// loop over the authentication a few times to give vault-created creds
|
||||
// time to become eventually-consistent
|
||||
ui.Say("You're using Vault-generated AWS credentials. It may take a " +
|
||||
"few moments for them to become available on AWS. Waiting...")
|
||||
err := retry.Retry(0.2, 30, 11, func(_ uint) (bool, error) {
|
||||
ec2conn, err := accessconf.NewEC2Connection()
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
_, err = listEC2Regions(ec2conn)
|
||||
if err != nil {
|
||||
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "AuthFailure" {
|
||||
log.Printf("Waiting for Vault-generated AWS credentials" +
|
||||
" to pass authentication... trying again.")
|
||||
return false, nil
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Was unable to Authenticate to AWS using Vault-"+
|
||||
"Generated Credentials within the retry timeout."))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
if amiConfig, ok := state.GetOk("ami_config"); ok {
|
||||
amiconf := amiConfig.(*AMIConfig)
|
||||
if !amiconf.AMISkipRegionValidation {
|
||||
regionsToValidate := append(amiconf.AMIRegions, accessconf.RawRegion)
|
||||
err := accessconf.ValidateRegion(regionsToValidate...)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("error validating regions: %v", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s.ForceDeregister {
|
||||
ui.Say("Force Deregister flag found, skipping prevalidating AMI Name")
|
||||
return multistep.ActionContinue
|
||||
|
|
|
|||
|
|
@ -92,13 +92,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !b.config.AMISkipRegionValidation {
|
||||
regionsToValidate := append(b.config.AMIRegions, b.config.RawRegion)
|
||||
err := b.config.AccessConfig.ValidateRegion(regionsToValidate...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error validating regions: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
ec2conn := ec2.New(session, &aws.Config{
|
||||
HTTPClient: commonhelper.HttpClientWithEnvironmentProxy(),
|
||||
})
|
||||
|
|
@ -106,6 +100,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("access_config", &b.config.AccessConfig)
|
||||
state.Put("ami_config", &b.config.AMIConfig)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("awsSession", session)
|
||||
state.Put("hook", hook)
|
||||
|
|
|
|||
|
|
@ -114,6 +114,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("access_config", &b.config.AccessConfig)
|
||||
state.Put("ami_config", &b.config.AMIConfig)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("awsSession", session)
|
||||
state.Put("hook", hook)
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("access_config", &b.config.AccessConfig)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
|
|
|||
|
|
@ -184,6 +184,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("access_config", &b.config.AccessConfig)
|
||||
state.Put("ami_config", &b.config.AMIConfig)
|
||||
state.Put("ec2", ec2conn)
|
||||
state.Put("awsSession", session)
|
||||
state.Put("hook", hook)
|
||||
|
|
|
|||
|
|
@ -12,16 +12,16 @@ import (
|
|||
|
||||
type Artifact struct {
|
||||
// The name of the snapshot
|
||||
snapshotName string
|
||||
SnapshotName string
|
||||
|
||||
// The ID of the image
|
||||
snapshotId int
|
||||
SnapshotId int
|
||||
|
||||
// The name of the region
|
||||
regionNames []string
|
||||
RegionNames []string
|
||||
|
||||
// The client for making API calls
|
||||
client *godo.Client
|
||||
Client *godo.Client
|
||||
}
|
||||
|
||||
func (*Artifact) BuilderId() string {
|
||||
|
|
@ -34,11 +34,11 @@ func (*Artifact) Files() []string {
|
|||
}
|
||||
|
||||
func (a *Artifact) Id() string {
|
||||
return fmt.Sprintf("%s:%s", strings.Join(a.regionNames[:], ","), strconv.FormatUint(uint64(a.snapshotId), 10))
|
||||
return fmt.Sprintf("%s:%s", strings.Join(a.RegionNames[:], ","), strconv.FormatUint(uint64(a.SnapshotId), 10))
|
||||
}
|
||||
|
||||
func (a *Artifact) String() string {
|
||||
return fmt.Sprintf("A snapshot was created: '%v' (ID: %v) in regions '%v'", a.snapshotName, a.snapshotId, strings.Join(a.regionNames[:], ","))
|
||||
return fmt.Sprintf("A snapshot was created: '%v' (ID: %v) in regions '%v'", a.SnapshotName, a.SnapshotId, strings.Join(a.RegionNames[:], ","))
|
||||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
|
|
@ -46,7 +46,7 @@ func (a *Artifact) State(name string) interface{} {
|
|||
}
|
||||
|
||||
func (a *Artifact) Destroy() error {
|
||||
log.Printf("Destroying image: %d (%s)", a.snapshotId, a.snapshotName)
|
||||
_, err := a.client.Images.Delete(context.TODO(), a.snapshotId)
|
||||
log.Printf("Destroying image: %d (%s)", a.SnapshotId, a.SnapshotName)
|
||||
_, err := a.Client.Images.Delete(context.TODO(), a.SnapshotId)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,10 +113,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
}
|
||||
|
||||
artifact := &Artifact{
|
||||
snapshotName: state.Get("snapshot_name").(string),
|
||||
snapshotId: state.Get("snapshot_image_id").(int),
|
||||
regionNames: state.Get("regions").([]string),
|
||||
client: client,
|
||||
SnapshotName: state.Get("snapshot_name").(string),
|
||||
SnapshotId: state.Get("snapshot_image_id").(int),
|
||||
RegionNames: state.Get("regions").([]string),
|
||||
Client: client,
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ func (s *stepSnapshot) Run(_ context.Context, state multistep.StateBag) multiste
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
ui.Say(fmt.Sprintf("transferring Snapshot ID: %d", imageTransfer.ID))
|
||||
if err := waitForImageState(godo.ActionCompleted, imageTransfer.ID, action.ID,
|
||||
if err := WaitForImageState(godo.ActionCompleted, imageTransfer.ID, action.ID,
|
||||
client, 20*time.Minute); err != nil {
|
||||
// If we get an error the first time, actually report it
|
||||
err := fmt.Errorf("Error waiting for snapshot transfer: %s", err)
|
||||
|
|
|
|||
|
|
@ -158,9 +158,9 @@ func waitForActionState(
|
|||
}
|
||||
}
|
||||
|
||||
// waitForImageState simply blocks until the image action is in
|
||||
// WaitForImageState simply blocks until the image action is in
|
||||
// a state we expect, while eventually timing out.
|
||||
func waitForImageState(
|
||||
func WaitForImageState(
|
||||
desiredState string, imageId, actionId int,
|
||||
client *godo.Client, timeout time.Duration) error {
|
||||
done := make(chan struct{})
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"log"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
|
||||
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
|
||||
)
|
||||
|
||||
// Artifact is an artifact implementation that contains built images.
|
||||
|
|
|
|||
|
|
@ -71,6 +71,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
return nil, fmt.Errorf("Error initializing compute client: %s", err)
|
||||
}
|
||||
|
||||
imageClient, err := b.config.imageV2Client()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error initializing image client: %s", err)
|
||||
}
|
||||
|
||||
// Setup the state bag and initial state for the steps
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
|
|
@ -165,7 +170,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
artifact := &Artifact{
|
||||
ImageId: state.Get("image").(string),
|
||||
BuilderIdValue: BuilderId,
|
||||
Client: computeClient,
|
||||
Client: imageClient,
|
||||
}
|
||||
|
||||
return artifact, nil
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import (
|
|||
"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
|
@ -25,13 +25,21 @@ func (s *stepCreateImage) Run(_ context.Context, state multistep.StateBag) multi
|
|||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// We need the v2 compute client
|
||||
client, err := config.computeV2Client()
|
||||
computeClient, err := config.computeV2Client()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error initializing compute client: %s", err)
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// We need the v2 image client
|
||||
imageClient, err := config.imageV2Client()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error initializing image service client: %s", err)
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Create the image.
|
||||
// Image source depends on the type of the Compute instance. It can be
|
||||
// Block Storage service volume or regular Compute service local volume.
|
||||
|
|
@ -58,7 +66,7 @@ func (s *stepCreateImage) Run(_ context.Context, state multistep.StateBag) multi
|
|||
}
|
||||
imageId = image.ImageID
|
||||
} else {
|
||||
imageId, err = servers.CreateImage(client, server.ID, servers.CreateImageOpts{
|
||||
imageId, err = servers.CreateImage(computeClient, server.ID, servers.CreateImageOpts{
|
||||
Name: config.ImageName,
|
||||
Metadata: config.ImageMetadata,
|
||||
}).ExtractImageID()
|
||||
|
|
@ -76,7 +84,7 @@ func (s *stepCreateImage) Run(_ context.Context, state multistep.StateBag) multi
|
|||
|
||||
// Wait for the image to become ready
|
||||
ui.Say(fmt.Sprintf("Waiting for image %s (image id: %s) to become ready...", config.ImageName, imageId))
|
||||
if err := WaitForImage(client, imageId); err != nil {
|
||||
if err := WaitForImage(imageClient, imageId); err != nil {
|
||||
err := fmt.Errorf("Error waiting for image: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
|
|
@ -113,11 +121,11 @@ func WaitForImage(client *gophercloud.ServiceClient, imageId string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if image.Status == "ACTIVE" {
|
||||
if image.Status == "active" {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Waiting for image creation status: %s (%d%%)", image.Status, image.Progress)
|
||||
log.Printf("Waiting for image creation status: %s", image.Status)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
50
builder/vagrant/artifact.go
Normal file
50
builder/vagrant/artifact.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// This is the common builder ID to all of these artifacts.
|
||||
const BuilderId = "vagrant"
|
||||
|
||||
// Artifact is the result of running the vagrant builder, namely a set
|
||||
// of files associated with the resulting machine.
|
||||
type artifact struct {
|
||||
OutputDir string
|
||||
BoxName string
|
||||
}
|
||||
|
||||
// NewArtifact returns a vagrant artifact containing the .box file
|
||||
func NewArtifact(dir string) (packer.Artifact, error) {
|
||||
return &artifact{
|
||||
OutputDir: dir,
|
||||
BoxName: "package.box",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (*artifact) BuilderId() string {
|
||||
return BuilderId
|
||||
}
|
||||
|
||||
func (a *artifact) Files() []string {
|
||||
return []string{a.BoxName}
|
||||
}
|
||||
|
||||
func (a *artifact) Id() string {
|
||||
return filepath.Join(a.OutputDir, a.BoxName)
|
||||
}
|
||||
|
||||
func (a *artifact) String() string {
|
||||
return fmt.Sprintf("Vagrant box is %s", a.Id())
|
||||
}
|
||||
|
||||
func (a *artifact) State(name string) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *artifact) Destroy() error {
|
||||
return nil
|
||||
}
|
||||
47
builder/vagrant/artifact_test.go
Normal file
47
builder/vagrant/artifact_test.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestArtifact_Impl(t *testing.T) {
|
||||
var raw interface{} = &artifact{}
|
||||
|
||||
if _, ok := raw.(packer.Artifact); !ok {
|
||||
t.Fatalf("Artifact does not implement packer.Artifact")
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactId(t *testing.T) {
|
||||
a := &artifact{
|
||||
OutputDir: "/my/dir",
|
||||
BoxName: "package.box",
|
||||
}
|
||||
|
||||
expected := "/my/dir/package.box"
|
||||
if runtime.GOOS == "windows" {
|
||||
expected = strings.Replace(expected, "/", "\\", -1)
|
||||
}
|
||||
if strings.Compare(a.Id(), expected) != 0 {
|
||||
t.Fatalf("artifact ID should match: expected: %s received: %s", expected, a.Id())
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactString(t *testing.T) {
|
||||
a := &artifact{
|
||||
OutputDir: "/my/dir",
|
||||
BoxName: "package.box",
|
||||
}
|
||||
expected := "Vagrant box is /my/dir/package.box"
|
||||
if runtime.GOOS == "windows" {
|
||||
expected = strings.Replace(expected, "/", "\\", -1)
|
||||
}
|
||||
|
||||
if strings.Compare(a.String(), expected) != 0 {
|
||||
t.Fatalf("artifact string should match: expected: %s received: %s", expected, a.String())
|
||||
}
|
||||
}
|
||||
276
builder/vagrant/builder.go
Normal file
276
builder/vagrant/builder.go
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/bootcommand"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// Builder implements packer.Builder and builds the actual VirtualBox
|
||||
// images.
|
||||
type Builder struct {
|
||||
config *Config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
type SSHConfig struct {
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
common.HTTPConfig `mapstructure:",squash"`
|
||||
common.ISOConfig `mapstructure:",squash"`
|
||||
common.FloppyConfig `mapstructure:",squash"`
|
||||
bootcommand.BootConfig `mapstructure:",squash"`
|
||||
SSHConfig `mapstructure:",squash"`
|
||||
|
||||
// This is the name of the new virtual machine.
|
||||
// By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build.
|
||||
OutputDir string `mapstructure:"output_dir"`
|
||||
SourceBox string `mapstructure:"source_path"`
|
||||
GlobalID string `mapstructure:"global_id"`
|
||||
Checksum string `mapstructure:"checksum"`
|
||||
ChecksumType string `mapstructure:"checksum_type"`
|
||||
BoxName string `mapstructure:"box_name"`
|
||||
|
||||
Provider string `mapstructure:"provider"`
|
||||
|
||||
Communicator string `mapstructure:"communicator"`
|
||||
|
||||
// What vagrantfile to use
|
||||
VagrantfileTpl string `mapstructure:"vagrantfile_template"`
|
||||
|
||||
// Whether to Halt, Suspend, or Destroy the box
|
||||
TeardownMethod string `mapstructure:"teardown_method"`
|
||||
|
||||
// Options for the "vagrant init" command
|
||||
BoxVersion string `mapstructure:"box_version"`
|
||||
Template string `mapstructure:"template"`
|
||||
SyncedFolder string `mapstructure:"synced_folder"`
|
||||
|
||||
// Options for the "vagrant box add" command
|
||||
SkipAdd bool `mapstructure:"skip_add"`
|
||||
AddCACert string `mapstructure:"add_cacert"`
|
||||
AddCAPath string `mapstructure:"add_capath"`
|
||||
AddCert string `mapstructure:"add_cert"`
|
||||
AddClean bool `mapstructure:"add_clean"`
|
||||
AddForce bool `mapstructure:"add_force"`
|
||||
AddInsecure bool `mapstructure:"add_insecure"`
|
||||
|
||||
// Don't package the Vagrant box after build.
|
||||
SkipPackage bool `mapstructure:"skip_package"`
|
||||
OutputVagrantfile string `mapstructure:"output_vagrantfile"`
|
||||
PackageInclude []string `mapstructure:"package_include"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
// Prepare processes the build configuration parameters.
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
b.config = new(Config)
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"boot_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Accumulate any errors and warnings
|
||||
var errs *packer.MultiError
|
||||
warnings := make([]string, 0)
|
||||
|
||||
if b.config.OutputDir == "" {
|
||||
b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName)
|
||||
}
|
||||
|
||||
if b.config.Comm.SSHTimeout == 0 {
|
||||
b.config.Comm.SSHTimeout = 10 * time.Minute
|
||||
}
|
||||
|
||||
if b.config.Comm.Type != "ssh" {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf(`The Vagrant builder currently only supports the ssh communicator"`))
|
||||
}
|
||||
// The box isn't a namespace like you'd pull from vagrant cloud
|
||||
if b.config.BoxName == "" {
|
||||
b.config.BoxName = fmt.Sprintf("packer_%s", b.config.PackerBuildName)
|
||||
}
|
||||
|
||||
if b.config.SourceBox == "" {
|
||||
if b.config.GlobalID == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required unless you have set global_id"))
|
||||
}
|
||||
} else {
|
||||
if b.config.GlobalID != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("You may either set global_id or source_path but not both"))
|
||||
}
|
||||
if strings.HasSuffix(b.config.SourceBox, ".box") {
|
||||
b.config.SourceBox, err = common.ValidatedURL(b.config.SourceBox)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is invalid: %s", err))
|
||||
}
|
||||
fileOK := common.FileExistsLocally(b.config.SourceBox)
|
||||
if !fileOK {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Source file '%s' needs to exist at time of config validation!", b.config.SourceBox))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if b.config.TeardownMethod == "" {
|
||||
// If we're using a box that's already opened on the system, don't
|
||||
// automatically destroy it. If we open the box ourselves, then go ahead
|
||||
// and kill it by default.
|
||||
if b.config.GlobalID != "" {
|
||||
b.config.TeardownMethod = "halt"
|
||||
} else {
|
||||
b.config.TeardownMethod = "destroy"
|
||||
}
|
||||
} else {
|
||||
matches := false
|
||||
for _, name := range []string{"halt", "suspend", "destroy"} {
|
||||
if strings.ToLower(b.config.TeardownMethod) == name {
|
||||
matches = true
|
||||
}
|
||||
}
|
||||
if !matches {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf(`TeardownMethod must be "halt", "suspend", or "destroy"`))
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return warnings, errs
|
||||
}
|
||||
|
||||
return warnings, nil
|
||||
}
|
||||
|
||||
// Run executes a Packer build and returns a packer.Artifact representing
|
||||
// a VirtualBox appliance.
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
// Create the driver that we'll use to communicate with VirtualBox
|
||||
VagrantCWD, err := filepath.Abs(b.config.OutputDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
driver, err := NewDriver(VagrantCWD)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed creating VirtualBox driver: %s", err)
|
||||
}
|
||||
|
||||
// Set up the state.
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", b.config)
|
||||
state.Put("debug", b.config.PackerDebug)
|
||||
state.Put("driver", driver)
|
||||
state.Put("cache", cache)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
|
||||
// Build the steps.
|
||||
steps := []multistep.Step{}
|
||||
// Download if source box isn't from vagrant cloud.
|
||||
if strings.HasSuffix(b.config.SourceBox, ".box") {
|
||||
steps = append(steps, &common.StepDownload{
|
||||
Checksum: b.config.Checksum,
|
||||
ChecksumType: b.config.ChecksumType,
|
||||
Description: "Box",
|
||||
Extension: "box",
|
||||
ResultKey: "box_path",
|
||||
Url: []string{b.config.SourceBox},
|
||||
})
|
||||
}
|
||||
steps = append(steps,
|
||||
&common.StepOutputDir{
|
||||
Force: b.config.PackerForce,
|
||||
Path: b.config.OutputDir,
|
||||
},
|
||||
&StepCreateVagrantfile{
|
||||
Template: b.config.Template,
|
||||
SyncedFolder: b.config.SyncedFolder,
|
||||
SourceBox: b.config.SourceBox,
|
||||
OutputDir: b.config.OutputDir,
|
||||
GlobalID: b.config.GlobalID,
|
||||
},
|
||||
&StepAddBox{
|
||||
BoxVersion: b.config.BoxVersion,
|
||||
CACert: b.config.AddCACert,
|
||||
CAPath: b.config.AddCAPath,
|
||||
DownloadCert: b.config.AddCert,
|
||||
Clean: b.config.AddClean,
|
||||
Force: b.config.AddForce,
|
||||
Insecure: b.config.AddInsecure,
|
||||
Provider: b.config.Provider,
|
||||
SourceBox: b.config.SourceBox,
|
||||
BoxName: b.config.BoxName,
|
||||
GlobalID: b.config.GlobalID,
|
||||
SkipAdd: b.config.SkipAdd,
|
||||
},
|
||||
&StepUp{
|
||||
TeardownMethod: b.config.TeardownMethod,
|
||||
Provider: b.config.Provider,
|
||||
GlobalID: b.config.GlobalID,
|
||||
},
|
||||
&StepSSHConfig{
|
||||
b.config.GlobalID,
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.SSHConfig.Comm,
|
||||
Host: CommHost(),
|
||||
SSHConfig: b.config.SSHConfig.Comm.SSHConfigFunc(),
|
||||
},
|
||||
new(common.StepProvision),
|
||||
&StepPackage{
|
||||
SkipPackage: b.config.SkipPackage,
|
||||
Include: b.config.PackageInclude,
|
||||
Vagrantfile: b.config.OutputVagrantfile,
|
||||
GlobalID: b.config.GlobalID,
|
||||
})
|
||||
|
||||
// Run the steps.
|
||||
b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state)
|
||||
b.runner.Run(state)
|
||||
|
||||
// Report any errors.
|
||||
if rawErr, ok := state.GetOk("error"); ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
// If we were interrupted or cancelled, then just exit.
|
||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||
return nil, errors.New("Build was cancelled.")
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk(multistep.StateHalted); ok {
|
||||
return nil, errors.New("Build was halted.")
|
||||
}
|
||||
|
||||
return NewArtifact(b.config.OutputDir)
|
||||
}
|
||||
|
||||
// Cancel.
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
||||
91
builder/vagrant/builder_test.go
Normal file
91
builder/vagrant/builder_test.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestBuilder_ImplementsBuilder(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Builder{}
|
||||
if _, ok := raw.(packer.Builder); !ok {
|
||||
t.Fatalf("Builder should be a builder")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder_Prepare_ValidateSource(t *testing.T) {
|
||||
b := &Builder{}
|
||||
type testCase struct {
|
||||
config map[string]interface{}
|
||||
errExpected bool
|
||||
reason string
|
||||
}
|
||||
|
||||
cases := []testCase{
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"global_id": "a3559ec",
|
||||
},
|
||||
errExpected: true,
|
||||
reason: "Need to set SSH communicator.",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"global_id": "a3559ec",
|
||||
"communicator": "ssh",
|
||||
},
|
||||
errExpected: false,
|
||||
reason: "Shouldn't fail because we've set global_id",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"communicator": "ssh",
|
||||
},
|
||||
errExpected: true,
|
||||
reason: "Should fail because we must set source_path or global_id",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"source_path": "./mybox",
|
||||
"communicator": "ssh",
|
||||
},
|
||||
errExpected: false,
|
||||
reason: "Source path is set; we should be fine",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"source_path": "./mybox",
|
||||
"communicator": "ssh",
|
||||
"global_id": "a3559ec",
|
||||
},
|
||||
errExpected: true,
|
||||
reason: "Both source path and global are set: we should error.",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"communicator": "ssh",
|
||||
"global_id": "a3559ec",
|
||||
"teardown_method": "suspend",
|
||||
},
|
||||
errExpected: false,
|
||||
reason: "Valid argument for teardown method",
|
||||
},
|
||||
{
|
||||
config: map[string]interface{}{
|
||||
"communicator": "ssh",
|
||||
"global_id": "a3559ec",
|
||||
"teardown_method": "surspernd",
|
||||
},
|
||||
errExpected: true,
|
||||
reason: "Inalid argument for teardown method",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
_, err := b.Prepare(tc.config)
|
||||
if (err != nil) != tc.errExpected {
|
||||
t.Fatalf("Unexpected behavior from test case %#v; %s.", tc.config, tc.reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
67
builder/vagrant/driver.go
Normal file
67
builder/vagrant/driver.go
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// A driver is able to talk to Vagrant and perform certain
|
||||
// operations with it.
|
||||
|
||||
type VagrantDriver interface {
|
||||
// Calls "vagrant init"
|
||||
Init([]string) error
|
||||
|
||||
// Calls "vagrant add"
|
||||
Add([]string) error
|
||||
|
||||
// Calls "vagrant up"
|
||||
Up([]string) (string, string, error)
|
||||
|
||||
// Calls "vagrant halt"
|
||||
Halt(string) error
|
||||
|
||||
// Calls "vagrant suspend"
|
||||
Suspend(string) error
|
||||
|
||||
SSHConfig(string) (*VagrantSSHConfig, error)
|
||||
|
||||
// Calls "vagrant destroy"
|
||||
Destroy(string) error
|
||||
|
||||
// Calls "vagrant package"[
|
||||
Package([]string) error
|
||||
|
||||
// Verify checks to make sure that this driver should function
|
||||
// properly. If there is any indication the driver can't function,
|
||||
// this will return an error.
|
||||
Verify() error
|
||||
|
||||
// Version reads the version of VirtualBox that is installed.
|
||||
Version() (string, error)
|
||||
}
|
||||
|
||||
func NewDriver(outputDir string) (VagrantDriver, error) {
|
||||
// Hardcode path for now while I'm developing. Obviously this path needs
|
||||
// to be discovered based on OS.
|
||||
vagrantBinary := "vagrant"
|
||||
if runtime.GOOS == "windows" {
|
||||
vagrantBinary = "vagrant.exe"
|
||||
}
|
||||
|
||||
if _, err := exec.LookPath(vagrantBinary); err != nil {
|
||||
return nil, fmt.Errorf("Error: Packer cannot find Vagrant in the path: %s", err.Error())
|
||||
}
|
||||
|
||||
driver := &Vagrant_2_2_Driver{
|
||||
vagrantBinary: vagrantBinary,
|
||||
VagrantCWD: outputDir,
|
||||
}
|
||||
|
||||
if err := driver.Verify(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return driver, nil
|
||||
}
|
||||
199
builder/vagrant/driver_2_2.go
Normal file
199
builder/vagrant/driver_2_2.go
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
const VAGRANT_MIN_VERSION = ">= 2.0.2"
|
||||
|
||||
type Vagrant_2_2_Driver struct {
|
||||
vagrantBinary string
|
||||
VagrantCWD string
|
||||
}
|
||||
|
||||
// Calls "vagrant init"
|
||||
func (d *Vagrant_2_2_Driver) Init(args []string) error {
|
||||
_, _, err := d.vagrantCmd(append([]string{"init"}, args...)...)
|
||||
return err
|
||||
}
|
||||
|
||||
// Calls "vagrant add"
|
||||
func (d *Vagrant_2_2_Driver) Add(args []string) error {
|
||||
// vagrant box add partyvm ubuntu-14.04.vmware.box
|
||||
_, _, err := d.vagrantCmd(append([]string{"box", "add"}, args...)...)
|
||||
return err
|
||||
}
|
||||
|
||||
// Calls "vagrant up"
|
||||
func (d *Vagrant_2_2_Driver) Up(args []string) (string, string, error) {
|
||||
stdout, stderr, err := d.vagrantCmd(append([]string{"up"}, args...)...)
|
||||
return stdout, stderr, err
|
||||
}
|
||||
|
||||
// Calls "vagrant halt"
|
||||
func (d *Vagrant_2_2_Driver) Halt(id string) error {
|
||||
args := []string{"halt"}
|
||||
if id != "" {
|
||||
args = append(args, id)
|
||||
}
|
||||
_, _, err := d.vagrantCmd(args...)
|
||||
return err
|
||||
}
|
||||
|
||||
// Calls "vagrant suspend"
|
||||
func (d *Vagrant_2_2_Driver) Suspend(id string) error {
|
||||
args := []string{"suspend"}
|
||||
if id != "" {
|
||||
args = append(args, id)
|
||||
}
|
||||
_, _, err := d.vagrantCmd(args...)
|
||||
return err
|
||||
}
|
||||
|
||||
// Calls "vagrant destroy"
|
||||
func (d *Vagrant_2_2_Driver) Destroy(id string) error {
|
||||
args := []string{"destroy", "-f"}
|
||||
if id != "" {
|
||||
args = append(args, id)
|
||||
}
|
||||
_, _, err := d.vagrantCmd(args...)
|
||||
return err
|
||||
}
|
||||
|
||||
// Calls "vagrant package"
|
||||
func (d *Vagrant_2_2_Driver) Package(args []string) error {
|
||||
args = append(args, "--output", filepath.Join(d.VagrantCWD, "package.box"))
|
||||
_, _, err := d.vagrantCmd(append([]string{"package"}, args...)...)
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify makes sure that Vagrant exists at the given path
|
||||
func (d *Vagrant_2_2_Driver) Verify() error {
|
||||
vagrantPath, err := exec.LookPath(d.vagrantBinary)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't find Vagrant binary!")
|
||||
}
|
||||
_, err = os.Stat(vagrantPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't find Vagrant binary.")
|
||||
}
|
||||
|
||||
constraints, err := version.NewConstraint(VAGRANT_MIN_VERSION)
|
||||
vers, err := d.Version()
|
||||
v, err := version.NewVersion(vers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error figuring out Vagrant version.")
|
||||
}
|
||||
|
||||
if !constraints.Check(v) {
|
||||
return fmt.Errorf("installed Vagrant version must be >=2.0.2")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type VagrantSSHConfig struct {
|
||||
Hostname string
|
||||
User string
|
||||
Port string
|
||||
UserKnownHostsFile string
|
||||
StrictHostKeyChecking bool
|
||||
PasswordAuthentication bool
|
||||
IdentityFile string
|
||||
IdentitiesOnly bool
|
||||
LogLevel string
|
||||
}
|
||||
|
||||
func parseSSHConfig(lines []string, value string) string {
|
||||
out := ""
|
||||
for _, line := range lines {
|
||||
if index := strings.Index(line, value); index != -1 {
|
||||
out = line[index+len(value):]
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func yesno(yn string) bool {
|
||||
if yn == "no" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *Vagrant_2_2_Driver) SSHConfig(id string) (*VagrantSSHConfig, error) {
|
||||
// vagrant ssh-config --host 8df7860
|
||||
args := []string{"ssh-config"}
|
||||
if id != "" {
|
||||
args = append(args, id)
|
||||
}
|
||||
stdout, _, err := d.vagrantCmd(args...)
|
||||
sshConf := &VagrantSSHConfig{}
|
||||
|
||||
lines := strings.Split(stdout, "\n")
|
||||
sshConf.Hostname = parseSSHConfig(lines, "HostName ")
|
||||
sshConf.User = parseSSHConfig(lines, "User ")
|
||||
sshConf.Port = parseSSHConfig(lines, "Port ")
|
||||
sshConf.UserKnownHostsFile = parseSSHConfig(lines, "UserKnownHostsFile ")
|
||||
sshConf.IdentityFile = parseSSHConfig(lines, "IdentityFile ")
|
||||
sshConf.LogLevel = parseSSHConfig(lines, "LogLevel ")
|
||||
|
||||
// handle the booleans
|
||||
sshConf.StrictHostKeyChecking = yesno(parseSSHConfig(lines, "StrictHostKeyChecking "))
|
||||
sshConf.PasswordAuthentication = yesno(parseSSHConfig(lines, "PasswordAuthentication "))
|
||||
sshConf.IdentitiesOnly = yesno((parseSSHConfig(lines, "IdentitiesOnly ")))
|
||||
|
||||
return sshConf, err
|
||||
}
|
||||
|
||||
// Version reads the version of VirtualBox that is installed.
|
||||
func (d *Vagrant_2_2_Driver) Version() (string, error) {
|
||||
stdoutString, _, err := d.vagrantCmd([]string{"--version"}...)
|
||||
// Example stdout:
|
||||
|
||||
// Installed Version: 2.2.3
|
||||
//
|
||||
// Vagrant was unable to check for the latest version of Vagrant.
|
||||
// Please check manually at https://www.vagrantup.com
|
||||
|
||||
// Use regex to find version
|
||||
reg := regexp.MustCompile(`(\d+\.)?(\d+\.)?(\*|\d+)`)
|
||||
version := reg.FindString(stdoutString)
|
||||
if version == "" {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func (d *Vagrant_2_2_Driver) vagrantCmd(args ...string) (string, string, error) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
log.Printf("Calling Vagrant CLI: %#v", args)
|
||||
cmd := exec.Command(d.vagrantBinary, args...)
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("VAGRANT_CWD=%s", d.VagrantCWD))
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
|
||||
stdoutString := strings.TrimSpace(stdout.String())
|
||||
stderrString := strings.TrimSpace(stderr.String())
|
||||
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
err = fmt.Errorf("Vagrant error: %s", stderrString)
|
||||
}
|
||||
|
||||
log.Printf("[vagrant driver] stdout: %s", stdoutString)
|
||||
log.Printf("[vagrant driver] stderr: %s", stderrString)
|
||||
|
||||
return stdoutString, stderrString, err
|
||||
}
|
||||
19
builder/vagrant/ssh.go
Normal file
19
builder/vagrant/ssh.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
||||
func CommHost() func(multistep.StateBag) (string, error) {
|
||||
return func(state multistep.StateBag) (string, error) {
|
||||
config := state.Get("config").(*Config)
|
||||
return config.Comm.SSHHost, nil
|
||||
}
|
||||
}
|
||||
|
||||
func SSHPort() func(multistep.StateBag) (int, error) {
|
||||
return func(state multistep.StateBag) (int, error) {
|
||||
config := state.Get("config").(*Config)
|
||||
return config.Comm.SSHPort, nil
|
||||
}
|
||||
}
|
||||
100
builder/vagrant/step_add_box.go
Normal file
100
builder/vagrant/step_add_box.go
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepAddBox struct {
|
||||
BoxVersion string
|
||||
CACert string
|
||||
CAPath string
|
||||
DownloadCert string
|
||||
Clean bool
|
||||
Force bool
|
||||
Insecure bool
|
||||
Provider string
|
||||
SourceBox string
|
||||
BoxName string
|
||||
GlobalID string
|
||||
SkipAdd bool
|
||||
}
|
||||
|
||||
func (s *StepAddBox) generateAddArgs() []string {
|
||||
addArgs := []string{}
|
||||
|
||||
if strings.HasSuffix(s.SourceBox, ".box") {
|
||||
addArgs = append(addArgs, s.BoxName)
|
||||
}
|
||||
|
||||
addArgs = append(addArgs, s.SourceBox)
|
||||
|
||||
if s.BoxVersion != "" {
|
||||
addArgs = append(addArgs, "--box-version", s.BoxVersion)
|
||||
}
|
||||
|
||||
if s.CACert != "" {
|
||||
addArgs = append(addArgs, "--cacert", s.CACert)
|
||||
}
|
||||
|
||||
if s.CAPath != "" {
|
||||
addArgs = append(addArgs, "--capath", s.CAPath)
|
||||
}
|
||||
|
||||
if s.DownloadCert != "" {
|
||||
addArgs = append(addArgs, "--cert", s.DownloadCert)
|
||||
}
|
||||
|
||||
if s.Clean {
|
||||
addArgs = append(addArgs, "--clean")
|
||||
}
|
||||
|
||||
if s.Force {
|
||||
addArgs = append(addArgs, "--force")
|
||||
}
|
||||
|
||||
if s.Insecure {
|
||||
addArgs = append(addArgs, "--insecure")
|
||||
}
|
||||
|
||||
if s.Provider != "" {
|
||||
addArgs = append(addArgs, "--provider", s.Provider)
|
||||
}
|
||||
|
||||
return addArgs
|
||||
}
|
||||
|
||||
func (s *StepAddBox) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
driver := state.Get("driver").(VagrantDriver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if s.SkipAdd {
|
||||
ui.Say("skip_add was set so we assume the box is already in Vagrant...")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.GlobalID != "" {
|
||||
ui.Say("Using a global-id; skipping Vagrant add command...")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say("Adding box using vagrant box add..")
|
||||
addArgs := s.generateAddArgs()
|
||||
|
||||
log.Printf("[vagrant] Calling box add with following args %s", strings.Join(addArgs, " "))
|
||||
// Call vagrant using prepared arguments
|
||||
err := driver.Add(addArgs)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepAddBox) Cleanup(state multistep.StateBag) {
|
||||
}
|
||||
62
builder/vagrant/step_add_box_test.go
Normal file
62
builder/vagrant/step_add_box_test.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
||||
func TestStepAdd_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepAddBox)
|
||||
if _, ok := raw.(multistep.Step); !ok {
|
||||
t.Fatalf("initialize should be a step")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepAddArgs(t *testing.T) {
|
||||
type testArgs struct {
|
||||
Step StepAddBox
|
||||
Expected []string
|
||||
}
|
||||
addTests := []testArgs{
|
||||
{
|
||||
Step: StepAddBox{
|
||||
SourceBox: "my_source_box.box",
|
||||
BoxName: "AWESOME BOX",
|
||||
},
|
||||
Expected: []string{"AWESOME BOX", "my_source_box.box"},
|
||||
},
|
||||
{
|
||||
Step: StepAddBox{
|
||||
SourceBox: "my_source_box",
|
||||
BoxName: "AWESOME BOX",
|
||||
},
|
||||
Expected: []string{"my_source_box"},
|
||||
},
|
||||
{
|
||||
Step: StepAddBox{
|
||||
BoxVersion: "eleventyone",
|
||||
CACert: "adfasdf",
|
||||
CAPath: "adfasdf",
|
||||
DownloadCert: "adfasdf",
|
||||
Clean: true,
|
||||
Force: true,
|
||||
Insecure: true,
|
||||
Provider: "virtualbox",
|
||||
SourceBox: "bananabox.box",
|
||||
BoxName: "bananas",
|
||||
},
|
||||
Expected: []string{"bananas", "bananabox.box", "--box-version", "eleventyone", "--cacert", "adfasdf", "--capath", "adfasdf", "--cert", "adfasdf", "--clean", "--force", "--insecure", "--provider", "virtualbox"},
|
||||
},
|
||||
}
|
||||
for _, addTest := range addTests {
|
||||
addArgs := addTest.Step.generateAddArgs()
|
||||
for i, val := range addTest.Expected {
|
||||
if strings.Compare(addArgs[i], val) != 0 {
|
||||
t.Fatalf("expected %#v but received %#v", addTest.Expected, addArgs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
builder/vagrant/step_create_vagrantfile.go
Normal file
96
builder/vagrant/step_create_vagrantfile.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepCreateVagrantfile struct {
|
||||
Template string
|
||||
SourceBox string
|
||||
OutputDir string
|
||||
SyncedFolder string
|
||||
GlobalID string
|
||||
}
|
||||
|
||||
var DEFAULT_TEMPLATE = `Vagrant.configure("2") do |config|
|
||||
config.vm.box = "{{.BoxName}}"
|
||||
{{ if ne .SyncedFolder "" -}}
|
||||
config.vm.synced_folder "{{.SyncedFolder}}", "/vagrant"
|
||||
{{- else -}}
|
||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
{{- end}}
|
||||
end`
|
||||
|
||||
type VagrantfileOptions struct {
|
||||
SyncedFolder string
|
||||
BoxName string
|
||||
}
|
||||
|
||||
func (s *StepCreateVagrantfile) createVagrantfile() (string, error) {
|
||||
tplPath := filepath.Join(s.OutputDir, "Vagrantfile")
|
||||
templateFile, err := os.Create(tplPath)
|
||||
if err != nil {
|
||||
retErr := fmt.Errorf("Error creating vagrantfile %s", err.Error())
|
||||
return "", retErr
|
||||
}
|
||||
|
||||
var tpl *template.Template
|
||||
if s.Template == "" {
|
||||
// Generate vagrantfile template based on our default
|
||||
tpl = template.Must(template.New("VagrantTpl").Parse(DEFAULT_TEMPLATE))
|
||||
} else {
|
||||
// Read in the template from provided file.
|
||||
tpl, err = template.ParseFiles(s.Template)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
opts := &VagrantfileOptions{
|
||||
SyncedFolder: s.SyncedFolder,
|
||||
BoxName: s.SourceBox,
|
||||
}
|
||||
|
||||
err = tpl.Execute(templateFile, opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
abspath, err := filepath.Abs(tplPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return abspath, nil
|
||||
}
|
||||
|
||||
func (s *StepCreateVagrantfile) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
// Skip the initialize step if we're trying to launch from a global ID.
|
||||
if s.GlobalID != "" {
|
||||
ui.Say("Using a global-id; skipping Vagrant init in this directory...")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Say("Creating a Vagrantfile in the build directory...")
|
||||
vagrantfilePath, err := s.createVagrantfile()
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
log.Printf("Created vagrantfile at %s", vagrantfilePath)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepCreateVagrantfile) Cleanup(state multistep.StateBag) {
|
||||
}
|
||||
60
builder/vagrant/step_create_vagrantfile_test.go
Normal file
60
builder/vagrant/step_create_vagrantfile_test.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
||||
func TestStepCreateVagrantfile_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(StepCreateVagrantfile)
|
||||
if _, ok := raw.(multistep.Step); !ok {
|
||||
t.Fatalf("initialize should be a step")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateFile(t *testing.T) {
|
||||
testy := StepCreateVagrantfile{
|
||||
OutputDir: "./",
|
||||
SourceBox: "bananas",
|
||||
}
|
||||
templatePath, err := testy.createVagrantfile()
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
defer os.Remove(templatePath)
|
||||
contents, err := ioutil.ReadFile(templatePath)
|
||||
actual := string(contents)
|
||||
expected := `Vagrant.configure("2") do |config|
|
||||
config.vm.box = "bananas"
|
||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
end`
|
||||
if ok := strings.Compare(actual, expected); ok != 0 {
|
||||
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateFile_customSync(t *testing.T) {
|
||||
testy := StepCreateVagrantfile{
|
||||
OutputDir: "./",
|
||||
SyncedFolder: "myfolder/foldertimes",
|
||||
}
|
||||
templatePath, err := testy.createVagrantfile()
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
defer os.Remove(templatePath)
|
||||
contents, err := ioutil.ReadFile(templatePath)
|
||||
actual := string(contents)
|
||||
expected := `Vagrant.configure("2") do |config|
|
||||
config.vm.box = ""
|
||||
config.vm.synced_folder "myfolder/foldertimes", "/vagrant"
|
||||
end`
|
||||
if ok := strings.Compare(actual, expected); ok != 0 {
|
||||
t.Fatalf("EXPECTED: \n%s\n\n RECEIVED: \n%s\n\n", expected, actual)
|
||||
}
|
||||
}
|
||||
49
builder/vagrant/step_package.go
Normal file
49
builder/vagrant/step_package.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepPackage struct {
|
||||
SkipPackage bool
|
||||
Include []string
|
||||
Vagrantfile string
|
||||
GlobalID string
|
||||
}
|
||||
|
||||
func (s *StepPackage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
driver := state.Get("driver").(VagrantDriver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if s.SkipPackage {
|
||||
ui.Say("skip_package flag set; not going to call Vagrant package on this box.")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
ui.Say("Packaging box...")
|
||||
packageArgs := []string{}
|
||||
if s.GlobalID != "" {
|
||||
packageArgs = append(packageArgs, s.GlobalID)
|
||||
}
|
||||
|
||||
if len(s.Include) > 0 {
|
||||
packageArgs = append(packageArgs, "--include", strings.Join(s.Include, ","))
|
||||
}
|
||||
if s.Vagrantfile != "" {
|
||||
packageArgs = append(packageArgs, "--vagrantfile", s.Vagrantfile)
|
||||
}
|
||||
|
||||
err := driver.Package(packageArgs)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepPackage) Cleanup(state multistep.StateBag) {
|
||||
}
|
||||
53
builder/vagrant/step_ssh_config.go
Normal file
53
builder/vagrant/step_ssh_config.go
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
||||
// Vagrant already sets up ssh on the guests; our job is to find out what
|
||||
// it did. We can do that with the ssh-config command. Example output:
|
||||
|
||||
// $ vagrant ssh-config
|
||||
// Host default
|
||||
// HostName 172.16.41.194
|
||||
// User vagrant
|
||||
// Port 22
|
||||
// UserKnownHostsFile /dev/null
|
||||
// StrictHostKeyChecking no
|
||||
// PasswordAuthentication no
|
||||
// IdentityFile /Users/mmarsh/Projects/vagrant-boxes/ubuntu/.vagrant/machines/default/vmware_fusion/private_key
|
||||
// IdentitiesOnly yes
|
||||
// LogLevel FATAL
|
||||
|
||||
type StepSSHConfig struct {
|
||||
GlobalID string
|
||||
}
|
||||
|
||||
func (s *StepSSHConfig) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
driver := state.Get("driver").(VagrantDriver)
|
||||
config := state.Get("config").(*Config)
|
||||
|
||||
sshConfig, err := driver.SSHConfig(s.GlobalID)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
config.Comm.SSHPrivateKeyFile = sshConfig.IdentityFile
|
||||
config.Comm.SSHUsername = sshConfig.User
|
||||
config.Comm.SSHHost = sshConfig.Hostname
|
||||
port, err := strconv.Atoi(sshConfig.Port)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
config.Comm.SSHPort = port
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepSSHConfig) Cleanup(state multistep.StateBag) {
|
||||
}
|
||||
62
builder/vagrant/step_up.go
Normal file
62
builder/vagrant/step_up.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package vagrant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepUp struct {
|
||||
TeardownMethod string
|
||||
Provider string
|
||||
GlobalID string
|
||||
}
|
||||
|
||||
func (s *StepUp) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
driver := state.Get("driver").(VagrantDriver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Calling Vagrant Up...")
|
||||
|
||||
var args []string
|
||||
if s.GlobalID != "" {
|
||||
args = append(args, s.GlobalID)
|
||||
}
|
||||
|
||||
if s.Provider != "" {
|
||||
args = append(args, fmt.Sprintf("--provider=%s", s.Provider))
|
||||
}
|
||||
|
||||
_, _, err := driver.Up(args)
|
||||
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepUp) Cleanup(state multistep.StateBag) {
|
||||
driver := state.Get("driver").(VagrantDriver)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say(fmt.Sprintf("%sing Vagrant box...", s.TeardownMethod))
|
||||
|
||||
var err error
|
||||
if s.TeardownMethod == "halt" {
|
||||
err = driver.Halt(s.GlobalID)
|
||||
} else if s.TeardownMethod == "suspend" {
|
||||
err = driver.Suspend(s.GlobalID)
|
||||
} else if s.TeardownMethod == "destroy" {
|
||||
err = driver.Destroy(s.GlobalID)
|
||||
} else {
|
||||
// Should never get here because of template validation
|
||||
state.Put("error", fmt.Errorf("Invalid teardown method selected; must be either halt, suspend, or destory."))
|
||||
}
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("Error halting Vagrant machine; please try to do this manually"))
|
||||
}
|
||||
}
|
||||
|
|
@ -21,8 +21,9 @@ import (
|
|||
//
|
||||
// Produces:
|
||||
type StepAttachGuestAdditions struct {
|
||||
attachedPath string
|
||||
GuestAdditionsMode string
|
||||
attachedPath string
|
||||
GuestAdditionsMode string
|
||||
GuestAdditionsInterface string
|
||||
}
|
||||
|
||||
func (s *StepAttachGuestAdditions) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
|
|
@ -40,12 +41,22 @@ func (s *StepAttachGuestAdditions) Run(_ context.Context, state multistep.StateB
|
|||
guestAdditionsPath := state.Get("guest_additions_path").(string)
|
||||
|
||||
// Attach the guest additions to the computer
|
||||
|
||||
controllerName := "IDE Controller"
|
||||
port := "1"
|
||||
device := "0"
|
||||
if s.GuestAdditionsInterface == "sata" {
|
||||
controllerName = "SATA Controller"
|
||||
port = "2"
|
||||
device = "0"
|
||||
}
|
||||
|
||||
log.Println("Attaching guest additions ISO onto IDE controller...")
|
||||
command := []string{
|
||||
"storageattach", vmName,
|
||||
"--storagectl", "IDE Controller",
|
||||
"--port", "1",
|
||||
"--device", "0",
|
||||
"--storagectl", controllerName,
|
||||
"--port", port,
|
||||
"--device", device,
|
||||
"--type", "dvddrive",
|
||||
"--medium", guestAdditionsPath,
|
||||
}
|
||||
|
|
@ -71,11 +82,20 @@ func (s *StepAttachGuestAdditions) Cleanup(state multistep.StateBag) {
|
|||
driver := state.Get("driver").(Driver)
|
||||
vmName := state.Get("vmName").(string)
|
||||
|
||||
controllerName := "IDE Controller"
|
||||
port := "1"
|
||||
device := "0"
|
||||
if s.GuestAdditionsInterface == "sata" {
|
||||
controllerName = "SATA Controller"
|
||||
port = "2"
|
||||
device = "0"
|
||||
}
|
||||
|
||||
command := []string{
|
||||
"storageattach", vmName,
|
||||
"--storagectl", "IDE Controller",
|
||||
"--port", "1",
|
||||
"--device", "0",
|
||||
"--storagectl", controllerName,
|
||||
"--port", port,
|
||||
"--device", device,
|
||||
"--medium", "none",
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ import (
|
|||
//
|
||||
// Produces:
|
||||
type StepRemoveDevices struct {
|
||||
Bundling VBoxBundleConfig
|
||||
Bundling VBoxBundleConfig
|
||||
GuestAdditionsInterface string
|
||||
}
|
||||
|
||||
func (s *StepRemoveDevices) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
|
|
@ -100,11 +101,19 @@ func (s *StepRemoveDevices) Run(_ context.Context, state multistep.StateBag) mul
|
|||
|
||||
if _, ok := state.GetOk("guest_additions_attached"); ok {
|
||||
ui.Message("Removing guest additions drive...")
|
||||
controllerName := "IDE Controller"
|
||||
port := "1"
|
||||
device := "0"
|
||||
if s.GuestAdditionsInterface == "sata" {
|
||||
controllerName = "SATA Controller"
|
||||
port = "2"
|
||||
device = "0"
|
||||
}
|
||||
command := []string{
|
||||
"storageattach", vmName,
|
||||
"--storagectl", "IDE Controller",
|
||||
"--port", "1",
|
||||
"--device", "0",
|
||||
"--storagectl", controllerName,
|
||||
"--port", port,
|
||||
"--device", device,
|
||||
"--medium", "none",
|
||||
}
|
||||
if err := driver.VBoxManage(command...); err != nil {
|
||||
|
|
|
|||
|
|
@ -41,20 +41,21 @@ type Config struct {
|
|||
vboxcommon.VBoxVersionConfig `mapstructure:",squash"`
|
||||
vboxcommon.VBoxBundleConfig `mapstructure:",squash"`
|
||||
|
||||
DiskSize uint `mapstructure:"disk_size"`
|
||||
GuestAdditionsMode string `mapstructure:"guest_additions_mode"`
|
||||
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
|
||||
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
|
||||
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
HardDriveDiscard bool `mapstructure:"hard_drive_discard"`
|
||||
HardDriveInterface string `mapstructure:"hard_drive_interface"`
|
||||
SATAPortCount int `mapstructure:"sata_port_count"`
|
||||
HardDriveNonrotational bool `mapstructure:"hard_drive_nonrotational"`
|
||||
ISOInterface string `mapstructure:"iso_interface"`
|
||||
KeepRegistered bool `mapstructure:"keep_registered"`
|
||||
SkipExport bool `mapstructure:"skip_export"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
DiskSize uint `mapstructure:"disk_size"`
|
||||
GuestAdditionsMode string `mapstructure:"guest_additions_mode"`
|
||||
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
|
||||
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
|
||||
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
|
||||
GuestAdditionsInterface string `mapstructure:"guest_additions_interface"`
|
||||
GuestOSType string `mapstructure:"guest_os_type"`
|
||||
HardDriveDiscard bool `mapstructure:"hard_drive_discard"`
|
||||
HardDriveInterface string `mapstructure:"hard_drive_interface"`
|
||||
SATAPortCount int `mapstructure:"sata_port_count"`
|
||||
HardDriveNonrotational bool `mapstructure:"hard_drive_nonrotational"`
|
||||
ISOInterface string `mapstructure:"iso_interface"`
|
||||
KeepRegistered bool `mapstructure:"keep_registered"`
|
||||
SkipExport bool `mapstructure:"skip_export"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
|
@ -125,6 +126,10 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
|||
b.config.ISOInterface = "ide"
|
||||
}
|
||||
|
||||
if b.config.GuestAdditionsInterface == "" {
|
||||
b.config.GuestAdditionsInterface = b.config.ISOInterface
|
||||
}
|
||||
|
||||
if b.config.VMName == "" {
|
||||
b.config.VMName = fmt.Sprintf(
|
||||
"packer-%s-%d", b.config.PackerBuildName, interpolate.InitTime.Unix())
|
||||
|
|
@ -227,7 +232,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
new(stepCreateDisk),
|
||||
new(stepAttachISO),
|
||||
&vboxcommon.StepAttachGuestAdditions{
|
||||
GuestAdditionsMode: b.config.GuestAdditionsMode,
|
||||
GuestAdditionsMode: b.config.GuestAdditionsMode,
|
||||
GuestAdditionsInterface: b.config.GuestAdditionsInterface,
|
||||
},
|
||||
&vboxcommon.StepConfigureVRDP{
|
||||
VRDPBindAddress: b.config.VRDPBindAddress,
|
||||
|
|
@ -280,7 +286,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
Delay: b.config.PostShutdownDelay,
|
||||
},
|
||||
&vboxcommon.StepRemoveDevices{
|
||||
Bundling: b.config.VBoxBundleConfig,
|
||||
Bundling: b.config.VBoxBundleConfig,
|
||||
GuestAdditionsInterface: b.config.GuestAdditionsInterface,
|
||||
},
|
||||
&vboxcommon.StepVBoxManage{
|
||||
Commands: b.config.VBoxManagePost,
|
||||
|
|
|
|||
|
|
@ -84,7 +84,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
ImportFlags: b.config.ImportFlags,
|
||||
},
|
||||
&vboxcommon.StepAttachGuestAdditions{
|
||||
GuestAdditionsMode: b.config.GuestAdditionsMode,
|
||||
GuestAdditionsMode: b.config.GuestAdditionsMode,
|
||||
GuestAdditionsInterface: b.config.GuestAdditionsInterface,
|
||||
},
|
||||
&vboxcommon.StepConfigureVRDP{
|
||||
VRDPBindAddress: b.config.VRDPBindAddress,
|
||||
|
|
@ -136,7 +137,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
Timeout: b.config.ShutdownTimeout,
|
||||
Delay: b.config.PostShutdownDelay,
|
||||
},
|
||||
new(vboxcommon.StepRemoveDevices),
|
||||
&vboxcommon.StepRemoveDevices{
|
||||
GuestAdditionsInterface: b.config.GuestAdditionsInterface,
|
||||
},
|
||||
&vboxcommon.StepVBoxManage{
|
||||
Commands: b.config.VBoxManagePost,
|
||||
Ctx: b.config.ctx,
|
||||
|
|
|
|||
|
|
@ -28,19 +28,20 @@ type Config struct {
|
|||
vboxcommon.VBoxManagePostConfig `mapstructure:",squash"`
|
||||
vboxcommon.VBoxVersionConfig `mapstructure:",squash"`
|
||||
|
||||
Checksum string `mapstructure:"checksum"`
|
||||
ChecksumType string `mapstructure:"checksum_type"`
|
||||
GuestAdditionsMode string `mapstructure:"guest_additions_mode"`
|
||||
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
|
||||
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
|
||||
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
|
||||
ImportFlags []string `mapstructure:"import_flags"`
|
||||
ImportOpts string `mapstructure:"import_opts"`
|
||||
SourcePath string `mapstructure:"source_path"`
|
||||
TargetPath string `mapstructure:"target_path"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
KeepRegistered bool `mapstructure:"keep_registered"`
|
||||
SkipExport bool `mapstructure:"skip_export"`
|
||||
Checksum string `mapstructure:"checksum"`
|
||||
ChecksumType string `mapstructure:"checksum_type"`
|
||||
GuestAdditionsMode string `mapstructure:"guest_additions_mode"`
|
||||
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
|
||||
GuestAdditionsInterface string `mapstructure:"guest_additions_interface"`
|
||||
GuestAdditionsSHA256 string `mapstructure:"guest_additions_sha256"`
|
||||
GuestAdditionsURL string `mapstructure:"guest_additions_url"`
|
||||
ImportFlags []string `mapstructure:"import_flags"`
|
||||
ImportOpts string `mapstructure:"import_opts"`
|
||||
SourcePath string `mapstructure:"source_path"`
|
||||
TargetPath string `mapstructure:"target_path"`
|
||||
VMName string `mapstructure:"vm_name"`
|
||||
KeepRegistered bool `mapstructure:"keep_registered"`
|
||||
SkipExport bool `mapstructure:"skip_export"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
|
@ -72,6 +73,9 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
|||
if c.GuestAdditionsPath == "" {
|
||||
c.GuestAdditionsPath = "VBoxGuestAdditions.iso"
|
||||
}
|
||||
if c.GuestAdditionsInterface == "" {
|
||||
c.GuestAdditionsInterface = "ide"
|
||||
}
|
||||
|
||||
if c.VMName == "" {
|
||||
c.VMName = fmt.Sprintf(
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ func (c *DriverConfig) Validate(SkipExport bool) error {
|
|||
// now, so that we don't fail for a simple mistake after a long
|
||||
// build
|
||||
ovftool := GetOVFTool()
|
||||
ovfToolArgs := []string{"--verifyOnly", fmt.Sprintf("vi://%s:%s@%s",
|
||||
ovfToolArgs := []string{"--noSSLVerify", "--verifyOnly", fmt.Sprintf("vi://%s:%s@%s",
|
||||
url.QueryEscape(c.RemoteUser),
|
||||
url.QueryEscape(c.RemotePassword),
|
||||
c.RemoteHost)}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ type ESX5Driver struct {
|
|||
|
||||
func (d *ESX5Driver) Clone(dst, src string, linked bool) error {
|
||||
|
||||
linesToArray := func(lines string) []string { return strings.Split(strings.Trim(lines, "\n"), "\n") }
|
||||
linesToArray := func(lines string) []string { return strings.Split(strings.Trim(lines, "\r\n"), "\n") }
|
||||
|
||||
d.SetOutputDir(path.Dir(filepath.ToSlash(dst)))
|
||||
srcVmx := d.datastorePath(src)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import (
|
|||
scalewaybuilder "github.com/hashicorp/packer/builder/scaleway"
|
||||
tencentcloudcvmbuilder "github.com/hashicorp/packer/builder/tencentcloud/cvm"
|
||||
tritonbuilder "github.com/hashicorp/packer/builder/triton"
|
||||
vagrantbuilder "github.com/hashicorp/packer/builder/vagrant"
|
||||
virtualboxisobuilder "github.com/hashicorp/packer/builder/virtualbox/iso"
|
||||
virtualboxovfbuilder "github.com/hashicorp/packer/builder/virtualbox/ovf"
|
||||
vmwareisobuilder "github.com/hashicorp/packer/builder/vmware/iso"
|
||||
|
|
@ -53,6 +54,7 @@ import (
|
|||
artificepostprocessor "github.com/hashicorp/packer/post-processor/artifice"
|
||||
checksumpostprocessor "github.com/hashicorp/packer/post-processor/checksum"
|
||||
compresspostprocessor "github.com/hashicorp/packer/post-processor/compress"
|
||||
digitaloceanimportpostprocessor "github.com/hashicorp/packer/post-processor/digitalocean-import"
|
||||
dockerimportpostprocessor "github.com/hashicorp/packer/post-processor/docker-import"
|
||||
dockerpushpostprocessor "github.com/hashicorp/packer/post-processor/docker-push"
|
||||
dockersavepostprocessor "github.com/hashicorp/packer/post-processor/docker-save"
|
||||
|
|
@ -72,6 +74,7 @@ import (
|
|||
chefsoloprovisioner "github.com/hashicorp/packer/provisioner/chef-solo"
|
||||
convergeprovisioner "github.com/hashicorp/packer/provisioner/converge"
|
||||
fileprovisioner "github.com/hashicorp/packer/provisioner/file"
|
||||
inspecprovisioner "github.com/hashicorp/packer/provisioner/inspec"
|
||||
powershellprovisioner "github.com/hashicorp/packer/provisioner/powershell"
|
||||
puppetmasterlessprovisioner "github.com/hashicorp/packer/provisioner/puppet-masterless"
|
||||
puppetserverprovisioner "github.com/hashicorp/packer/provisioner/puppet-server"
|
||||
|
|
@ -118,6 +121,7 @@ var Builders = map[string]packer.Builder{
|
|||
"scaleway": new(scalewaybuilder.Builder),
|
||||
"tencentcloud-cvm": new(tencentcloudcvmbuilder.Builder),
|
||||
"triton": new(tritonbuilder.Builder),
|
||||
"vagrant": new(vagrantbuilder.Builder),
|
||||
"virtualbox-iso": new(virtualboxisobuilder.Builder),
|
||||
"virtualbox-ovf": new(virtualboxovfbuilder.Builder),
|
||||
"vmware-iso": new(vmwareisobuilder.Builder),
|
||||
|
|
@ -132,6 +136,7 @@ var Provisioners = map[string]packer.Provisioner{
|
|||
"chef-solo": new(chefsoloprovisioner.Provisioner),
|
||||
"converge": new(convergeprovisioner.Provisioner),
|
||||
"file": new(fileprovisioner.Provisioner),
|
||||
"inspec": new(inspecprovisioner.Provisioner),
|
||||
"powershell": new(powershellprovisioner.Provisioner),
|
||||
"puppet-masterless": new(puppetmasterlessprovisioner.Provisioner),
|
||||
"puppet-server": new(puppetserverprovisioner.Provisioner),
|
||||
|
|
@ -148,6 +153,7 @@ var PostProcessors = map[string]packer.PostProcessor{
|
|||
"artifice": new(artificepostprocessor.PostProcessor),
|
||||
"checksum": new(checksumpostprocessor.PostProcessor),
|
||||
"compress": new(compresspostprocessor.PostProcessor),
|
||||
"digitalocean-import": new(digitaloceanimportpostprocessor.PostProcessor),
|
||||
"docker-import": new(dockerimportpostprocessor.PostProcessor),
|
||||
"docker-push": new(dockerpushpostprocessor.PostProcessor),
|
||||
"docker-save": new(dockersavepostprocessor.PostProcessor),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package ansible
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
@ -17,7 +17,7 @@ import (
|
|||
|
||||
// An adapter satisfies SSH requests (from an Ansible client) by delegating SSH
|
||||
// exec and subsystem commands to a packer.Communicator.
|
||||
type adapter struct {
|
||||
type Adapter struct {
|
||||
done <-chan struct{}
|
||||
l net.Listener
|
||||
config *ssh.ServerConfig
|
||||
|
|
@ -26,8 +26,8 @@ type adapter struct {
|
|||
comm packer.Communicator
|
||||
}
|
||||
|
||||
func newAdapter(done <-chan struct{}, l net.Listener, config *ssh.ServerConfig, sftpCmd string, ui packer.Ui, comm packer.Communicator) *adapter {
|
||||
return &adapter{
|
||||
func NewAdapter(done <-chan struct{}, l net.Listener, config *ssh.ServerConfig, sftpCmd string, ui packer.Ui, comm packer.Communicator) *Adapter {
|
||||
return &Adapter{
|
||||
done: done,
|
||||
l: l,
|
||||
config: config,
|
||||
|
|
@ -37,7 +37,7 @@ func newAdapter(done <-chan struct{}, l net.Listener, config *ssh.ServerConfig,
|
|||
}
|
||||
}
|
||||
|
||||
func (c *adapter) Serve() {
|
||||
func (c *Adapter) Serve() {
|
||||
log.Printf("SSH proxy: serving on %s", c.l.Addr())
|
||||
|
||||
for {
|
||||
|
|
@ -62,7 +62,7 @@ func (c *adapter) Serve() {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *adapter) Handle(conn net.Conn, ui packer.Ui) error {
|
||||
func (c *Adapter) Handle(conn net.Conn, ui packer.Ui) error {
|
||||
log.Print("SSH proxy: accepted connection")
|
||||
_, chans, reqs, err := ssh.NewServerConn(conn, c.config)
|
||||
if err != nil {
|
||||
|
|
@ -89,7 +89,7 @@ func (c *adapter) Handle(conn net.Conn, ui packer.Ui) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *adapter) handleSession(newChannel ssh.NewChannel) error {
|
||||
func (c *Adapter) handleSession(newChannel ssh.NewChannel) error {
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -182,11 +182,11 @@ func (c *adapter) handleSession(newChannel ssh.NewChannel) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *adapter) Shutdown() {
|
||||
func (c *Adapter) Shutdown() {
|
||||
c.l.Close()
|
||||
}
|
||||
|
||||
func (c *adapter) exec(command string, in io.Reader, out io.Writer, err io.Writer) int {
|
||||
func (c *Adapter) exec(command string, in io.Reader, out io.Writer, err io.Writer) int {
|
||||
var exitStatus int
|
||||
switch {
|
||||
case strings.HasPrefix(command, "scp ") && serveSCP(command[4:]):
|
||||
|
|
@ -206,7 +206,7 @@ func serveSCP(args string) bool {
|
|||
return bytes.IndexAny(opts, "tf") >= 0
|
||||
}
|
||||
|
||||
func (c *adapter) scpExec(args string, in io.Reader, out io.Writer) error {
|
||||
func (c *Adapter) scpExec(args string, in io.Reader, out io.Writer) error {
|
||||
opts, rest := scpOptions(args)
|
||||
|
||||
// remove the quoting that ansible added to rest for shell safety.
|
||||
|
|
@ -226,7 +226,7 @@ func (c *adapter) scpExec(args string, in io.Reader, out io.Writer) error {
|
|||
return errors.New("no scp mode specified")
|
||||
}
|
||||
|
||||
func (c *adapter) remoteExec(command string, in io.Reader, out io.Writer, err io.Writer) int {
|
||||
func (c *Adapter) remoteExec(command string, in io.Reader, out io.Writer, err io.Writer) int {
|
||||
cmd := &packer.RemoteCmd{
|
||||
Stdin: in,
|
||||
Stdout: out,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package ansible
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
|
@ -26,7 +26,7 @@ func TestAdapter_Serve(t *testing.T) {
|
|||
|
||||
ui := new(packer.NoopUi)
|
||||
|
||||
sut := newAdapter(done, &l, config, "", newUi(ui), communicator{})
|
||||
sut := NewAdapter(done, &l, config, "", ui, communicator{})
|
||||
go func() {
|
||||
i := 0
|
||||
for range acceptC {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package ansible
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
|
@ -58,6 +58,11 @@ func (c *ISOConfig) Prepare(ctx *interpolate.Context) (warnings []string, errs [
|
|||
|
||||
// If iso_checksum has no value use iso_checksum_url instead.
|
||||
if c.ISOChecksum == "" {
|
||||
if strings.HasSuffix(strings.ToLower(c.ISOChecksumURL), ".iso") {
|
||||
errs = append(errs, fmt.Errorf("Error parsing checksum:"+
|
||||
" .iso is not a valid checksum extension"))
|
||||
return warnings, errs
|
||||
}
|
||||
u, err := url.Parse(c.ISOChecksumURL)
|
||||
if err != nil {
|
||||
errs = append(errs,
|
||||
|
|
|
|||
|
|
@ -239,6 +239,16 @@ func TestISOConfigPrepare_ISOChecksumURL(t *testing.T) {
|
|||
if i.ISOChecksum != "bar0" {
|
||||
t.Fatalf("should've found \"bar0\" got: %s", i.ISOChecksum)
|
||||
}
|
||||
|
||||
// Test that we won't try to read an iso into memory because of a user
|
||||
// error
|
||||
i = testISOConfig()
|
||||
i.ISOChecksumURL = "file:///not_read.iso"
|
||||
i.ISOChecksum = ""
|
||||
warns, err = i.Prepare(nil)
|
||||
if err == nil {
|
||||
t.Fatalf("should have error because iso is bad filetype: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestISOConfigPrepare_ISOChecksumType(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,9 @@ type Config struct {
|
|||
WinRMInsecure bool `mapstructure:"winrm_insecure"`
|
||||
WinRMUseNTLM bool `mapstructure:"winrm_use_ntlm"`
|
||||
WinRMTransportDecorator func() winrm.Transporter
|
||||
|
||||
// Delay
|
||||
PauseBeforeConnect time.Duration `mapstructure:"pause_before_connecting"`
|
||||
}
|
||||
|
||||
// ReadSSHPrivateKeyFile returns the SSH private key bytes
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/communicator/none"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
|
|
@ -44,9 +45,28 @@ type StepConnect struct {
|
|||
substep multistep.Step
|
||||
}
|
||||
|
||||
func (s *StepConnect) pause(pauseLen time.Duration, ctx context.Context) bool {
|
||||
// Use a select to determine if we get cancelled during the wait
|
||||
log.Printf("Pausing before connecting...")
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return true
|
||||
case <-time.After(pauseLen):
|
||||
}
|
||||
log.Printf("Pause over; connecting...")
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *StepConnect) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if s.Config.PauseBeforeConnect > 0 {
|
||||
cancelled := s.pause(s.Config.PauseBeforeConnect, ctx)
|
||||
if cancelled {
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
typeMap := map[string]multistep.Step{
|
||||
"none": nil,
|
||||
"ssh": &StepConnectSSH{
|
||||
|
|
|
|||
47
packer/ui.go
47
packer/ui.go
|
|
@ -343,7 +343,7 @@ func (u *MachineReadableUi) ProgressBar() ProgressBar {
|
|||
return new(NoopProgressBar)
|
||||
}
|
||||
|
||||
// TimestampedUi is a UI that wraps another UI implementation and prefixes
|
||||
// TimestampedUi is a UI that wraps another UI implementation and
|
||||
// prefixes each message with an RFC3339 timestamp
|
||||
type TimestampedUi struct {
|
||||
Ui Ui
|
||||
|
|
@ -376,3 +376,48 @@ func (u *TimestampedUi) ProgressBar() ProgressBar { return u.Ui.ProgressBar() }
|
|||
func (u *TimestampedUi) timestampLine(string string) string {
|
||||
return fmt.Sprintf("%v: %v", time.Now().Format(time.RFC3339), string)
|
||||
}
|
||||
|
||||
// Safe is a UI that wraps another UI implementation and
|
||||
// provides concurrency-safe access
|
||||
type SafeUi struct {
|
||||
Sem chan int
|
||||
Ui Ui
|
||||
}
|
||||
|
||||
var _ Ui = new(SafeUi)
|
||||
|
||||
func (u *SafeUi) Ask(s string) (string, error) {
|
||||
u.Sem <- 1
|
||||
ret, err := u.Ui.Ask(s)
|
||||
<-u.Sem
|
||||
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (u *SafeUi) Say(s string) {
|
||||
u.Sem <- 1
|
||||
u.Ui.Say(s)
|
||||
<-u.Sem
|
||||
}
|
||||
|
||||
func (u *SafeUi) Message(s string) {
|
||||
u.Sem <- 1
|
||||
u.Ui.Message(s)
|
||||
<-u.Sem
|
||||
}
|
||||
|
||||
func (u *SafeUi) Error(s string) {
|
||||
u.Sem <- 1
|
||||
u.Ui.Error(s)
|
||||
<-u.Sem
|
||||
}
|
||||
|
||||
func (u *SafeUi) Machine(t string, args ...string) {
|
||||
u.Sem <- 1
|
||||
u.Ui.Machine(t, args...)
|
||||
<-u.Sem
|
||||
}
|
||||
|
||||
func (u *SafeUi) ProgressBar() ProgressBar {
|
||||
return new(NoopProgressBar)
|
||||
}
|
||||
|
|
|
|||
365
post-processor/digitalocean-import/post-processor.go
Normal file
365
post-processor/digitalocean-import/post-processor.go
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
package digitaloceanimport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"golang.org/x/oauth2"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/digitalocean/godo"
|
||||
|
||||
"github.com/hashicorp/packer/builder/digitalocean"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
const BuilderId = "packer.post-processor.digitalocean-import"
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
|
||||
APIToken string `mapstructure:"api_token"`
|
||||
SpacesKey string `mapstructure:"spaces_key"`
|
||||
SpacesSecret string `mapstructure:"spaces_secret"`
|
||||
|
||||
SpacesRegion string `mapstructure:"spaces_region"`
|
||||
SpaceName string `mapstructure:"space_name"`
|
||||
ObjectName string `mapstructure:"space_object_name"`
|
||||
SkipClean bool `mapstructure:"skip_clean"`
|
||||
Tags []string `mapstructure:"image_tags"`
|
||||
Name string `mapstructure:"image_name"`
|
||||
Description string `mapstructure:"image_description"`
|
||||
Distribution string `mapstructure:"image_distribution"`
|
||||
ImageRegions []string `mapstructure:"image_regions"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type PostProcessor struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
type apiTokenSource struct {
|
||||
AccessToken string
|
||||
}
|
||||
|
||||
type logger struct {
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func (t *apiTokenSource) Token() (*oauth2.Token, error) {
|
||||
return &oauth2.Token{
|
||||
AccessToken: t.AccessToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l logger) Log(args ...interface{}) {
|
||||
l.logger.Println(args...)
|
||||
}
|
||||
|
||||
func (p *PostProcessor) Configure(raws ...interface{}) error {
|
||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &p.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{"space_object_name"},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.config.SpacesKey == "" {
|
||||
p.config.SpacesKey = os.Getenv("DIGITALOCEAN_SPACES_ACCESS_KEY")
|
||||
}
|
||||
|
||||
if p.config.SpacesSecret == "" {
|
||||
p.config.SpacesSecret = os.Getenv("DIGITALOCEAN_SPACES_SECRET_KEY")
|
||||
}
|
||||
|
||||
if p.config.APIToken == "" {
|
||||
p.config.APIToken = os.Getenv("DIGITALOCEAN_API_TOKEN")
|
||||
}
|
||||
|
||||
if p.config.ObjectName == "" {
|
||||
p.config.ObjectName = "packer-import-{{timestamp}}"
|
||||
}
|
||||
|
||||
if p.config.Distribution == "" {
|
||||
p.config.Distribution = "Unkown"
|
||||
}
|
||||
|
||||
if p.config.Timeout == 0 {
|
||||
p.config.Timeout = 20 * time.Minute
|
||||
}
|
||||
|
||||
errs := new(packer.MultiError)
|
||||
|
||||
if err = interpolate.Validate(p.config.ObjectName, &p.config.ctx); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error parsing space_object_name template: %s", err))
|
||||
}
|
||||
|
||||
requiredArgs := map[string]*string{
|
||||
"api_token": &p.config.APIToken,
|
||||
"spaces_key": &p.config.SpacesKey,
|
||||
"spaces_secret": &p.config.SpacesSecret,
|
||||
"spaces_region": &p.config.SpacesRegion,
|
||||
"space_name": &p.config.SpaceName,
|
||||
"image_name": &p.config.Name,
|
||||
"image_regions": &p.config.ImageRegions[0],
|
||||
}
|
||||
for key, ptr := range requiredArgs {
|
||||
if *ptr == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("%s must be set", key))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(p.config.SpacesKey, p.config.SpacesSecret, p.config.APIToken)
|
||||
log.Println(p.config)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
|
||||
var err error
|
||||
|
||||
p.config.ObjectName, err = interpolate.Render(p.config.ObjectName, &p.config.ctx)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Error rendering space_object_name template: %s", err)
|
||||
}
|
||||
log.Printf("Rendered space_object_name as %s", p.config.ObjectName)
|
||||
|
||||
source := ""
|
||||
artifacts := artifact.Files()
|
||||
log.Println("Looking for image in artifact")
|
||||
if len(artifacts) > 1 {
|
||||
validSuffix := []string{"raw", "img", "qcow2", "vhdx", "vdi", "vmdk", "tar.bz2", "tar.xz", "tar.gz"}
|
||||
for _, path := range artifact.Files() {
|
||||
for _, suffix := range validSuffix {
|
||||
if strings.HasSuffix(path, suffix) {
|
||||
source = path
|
||||
break
|
||||
}
|
||||
}
|
||||
if source != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
source = artifact.Files()[0]
|
||||
}
|
||||
|
||||
if source == "" {
|
||||
return nil, false, fmt.Errorf("Image file not found")
|
||||
}
|
||||
|
||||
spacesCreds := credentials.NewStaticCredentials(p.config.SpacesKey, p.config.SpacesSecret, "")
|
||||
spacesEndpoint := fmt.Sprintf("https://%s.digitaloceanspaces.com", p.config.SpacesRegion)
|
||||
spacesConfig := &aws.Config{
|
||||
Credentials: spacesCreds,
|
||||
Endpoint: aws.String(spacesEndpoint),
|
||||
Region: aws.String(p.config.SpacesRegion),
|
||||
LogLevel: aws.LogLevel(aws.LogDebugWithSigning),
|
||||
Logger: &logger{
|
||||
logger: log.New(os.Stderr, "", log.LstdFlags),
|
||||
},
|
||||
}
|
||||
sess := session.New(spacesConfig)
|
||||
|
||||
ui.Message(fmt.Sprintf("Uploading %s to spaces://%s/%s", source, p.config.SpaceName, p.config.ObjectName))
|
||||
err = uploadImageToSpaces(source, p, sess)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
ui.Message(fmt.Sprintf("Completed upload of %s to spaces://%s/%s", source, p.config.SpaceName, p.config.ObjectName))
|
||||
|
||||
client := godo.NewClient(oauth2.NewClient(oauth2.NoContext, &apiTokenSource{
|
||||
AccessToken: p.config.APIToken,
|
||||
}))
|
||||
|
||||
ui.Message(fmt.Sprintf("Started import of spaces://%s/%s", p.config.SpaceName, p.config.ObjectName))
|
||||
image, err := importImageFromSpaces(p, client)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Waiting for import of image %s to complete (may take a while)", p.config.Name))
|
||||
err = waitUntilImageAvailable(client, image.ID, p.config.Timeout)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Import of image %s failed with error: %s", p.config.Name, err)
|
||||
}
|
||||
ui.Message(fmt.Sprintf("Import of image %s complete", p.config.Name))
|
||||
|
||||
if len(p.config.ImageRegions) > 1 {
|
||||
// Remove the first region from the slice as the image is already there.
|
||||
regions := p.config.ImageRegions
|
||||
regions[0] = regions[len(regions)-1]
|
||||
regions[len(regions)-1] = ""
|
||||
regions = regions[:len(regions)-1]
|
||||
|
||||
ui.Message(fmt.Sprintf("Distributing image %s to additional regions: %v", p.config.Name, regions))
|
||||
err = distributeImageToRegions(client, image.ID, regions, p.config.Timeout)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Adding created image ID %v to output artifacts", image.ID)
|
||||
artifact = &digitalocean.Artifact{
|
||||
SnapshotName: image.Name,
|
||||
SnapshotId: image.ID,
|
||||
RegionNames: p.config.ImageRegions,
|
||||
Client: client,
|
||||
}
|
||||
|
||||
if !p.config.SkipClean {
|
||||
ui.Message(fmt.Sprintf("Deleting import source spaces://%s/%s", p.config.SpaceName, p.config.ObjectName))
|
||||
err = deleteImageFromSpaces(p, sess)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
return artifact, false, nil
|
||||
}
|
||||
|
||||
func uploadImageToSpaces(source string, p *PostProcessor, s *session.Session) (err error) {
|
||||
file, err := os.Open(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to open %s: %s", source, err)
|
||||
}
|
||||
|
||||
uploader := s3manager.NewUploader(s)
|
||||
_, err = uploader.Upload(&s3manager.UploadInput{
|
||||
Body: file,
|
||||
Bucket: &p.config.SpaceName,
|
||||
Key: &p.config.ObjectName,
|
||||
ACL: aws.String("public-read"),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to upload %s: %s", source, err)
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func importImageFromSpaces(p *PostProcessor, client *godo.Client) (image *godo.Image, err error) {
|
||||
log.Printf("Importing custom image from spaces://%s/%s", p.config.SpaceName, p.config.ObjectName)
|
||||
|
||||
url := fmt.Sprintf("https://%s.%s.digitaloceanspaces.com/%s", p.config.SpaceName, p.config.SpacesRegion, p.config.ObjectName)
|
||||
createRequest := &godo.CustomImageCreateRequest{
|
||||
Name: p.config.Name,
|
||||
Url: url,
|
||||
Region: p.config.ImageRegions[0],
|
||||
Distribution: p.config.Distribution,
|
||||
Description: p.config.Description,
|
||||
Tags: p.config.Tags,
|
||||
}
|
||||
|
||||
image, _, err = client.Images.Create(context.TODO(), createRequest)
|
||||
if err != nil {
|
||||
return image, fmt.Errorf("Failed to import from spaces://%s/%s: %s", p.config.SpaceName, p.config.ObjectName, err)
|
||||
}
|
||||
|
||||
return image, nil
|
||||
}
|
||||
|
||||
func waitUntilImageAvailable(client *godo.Client, imageId int, timeout time.Duration) (err error) {
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
attempts := 0
|
||||
for {
|
||||
attempts += 1
|
||||
|
||||
log.Printf("Waiting for image to become available... (attempt: %d)", attempts)
|
||||
image, _, err := client.Images.GetByID(context.TODO(), imageId)
|
||||
if err != nil {
|
||||
result <- err
|
||||
return
|
||||
}
|
||||
|
||||
if image.Status == "available" {
|
||||
result <- nil
|
||||
return
|
||||
}
|
||||
|
||||
if image.ErrorMessage != "" {
|
||||
result <- fmt.Errorf("%v", image.ErrorMessage)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
log.Printf("Waiting for up to %d seconds for image to become available", timeout/time.Second)
|
||||
select {
|
||||
case err := <-result:
|
||||
return err
|
||||
case <-time.After(timeout):
|
||||
err := fmt.Errorf("Timeout while waiting to for action to become available")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func distributeImageToRegions(client *godo.Client, imageId int, regions []string, timeout time.Duration) (err error) {
|
||||
for _, region := range regions {
|
||||
transferRequest := &godo.ActionRequest{
|
||||
"type": "transfer",
|
||||
"region": region,
|
||||
}
|
||||
log.Printf("Transferring image to %s", region)
|
||||
action, _, err := client.ImageActions.Transfer(context.TODO(), imageId, transferRequest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error transferring image: %s", err)
|
||||
}
|
||||
|
||||
if err := digitalocean.WaitForImageState(godo.ActionCompleted, imageId, action.ID, client, timeout); err != nil {
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error transferring image: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteImageFromSpaces(p *PostProcessor, s *session.Session) (err error) {
|
||||
s3conn := s3.New(s)
|
||||
_, err = s3conn.DeleteObject(&s3.DeleteObjectInput{
|
||||
Bucket: &p.config.SpaceName,
|
||||
Key: &p.config.ObjectName,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to delete spaces://%s/%s: %s", p.config.SpaceName, p.config.ObjectName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
32
post-processor/digitalocean-import/post-processor_test.go
Normal file
32
post-processor/digitalocean-import/post-processor_test.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package digitaloceanimport
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
func testPP(t *testing.T) *PostProcessor {
|
||||
var p PostProcessor
|
||||
if err := p.Configure(testConfig()); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return &p
|
||||
}
|
||||
|
||||
func testUi() *packer.BasicUi {
|
||||
return &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
|
||||
var _ packer.PostProcessor = new(PostProcessor)
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/adapter"
|
||||
commonhelper "github.com/hashicorp/packer/helper/common"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
|
|
@ -63,7 +64,7 @@ type Config struct {
|
|||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
adapter *adapter
|
||||
adapter *adapter.Adapter
|
||||
done chan struct{}
|
||||
ansibleVersion string
|
||||
ansibleMajVersion uint
|
||||
|
|
@ -285,8 +286,11 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
|||
return err
|
||||
}
|
||||
|
||||
ui = newUi(ui)
|
||||
p.adapter = newAdapter(p.done, localListener, config, p.config.SFTPCmd, ui, comm)
|
||||
ui = &packer.SafeUi{
|
||||
Sem: make(chan int, 1),
|
||||
Ui: ui,
|
||||
}
|
||||
p.adapter = adapter.NewAdapter(p.done, localListener, config, p.config.SFTPCmd, ui, comm)
|
||||
|
||||
defer func() {
|
||||
log.Print("shutting down the SSH proxy")
|
||||
|
|
@ -556,49 +560,3 @@ func getWinRMPassword(buildName string) string {
|
|||
packer.LogSecretFilter.Set(winRMPass)
|
||||
return winRMPass
|
||||
}
|
||||
|
||||
// Ui provides concurrency-safe access to packer.Ui.
|
||||
type Ui struct {
|
||||
sem chan int
|
||||
ui packer.Ui
|
||||
}
|
||||
|
||||
func newUi(ui packer.Ui) packer.Ui {
|
||||
return &Ui{sem: make(chan int, 1), ui: ui}
|
||||
}
|
||||
|
||||
func (ui *Ui) Ask(s string) (string, error) {
|
||||
ui.sem <- 1
|
||||
ret, err := ui.ui.Ask(s)
|
||||
<-ui.sem
|
||||
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (ui *Ui) Say(s string) {
|
||||
ui.sem <- 1
|
||||
ui.ui.Say(s)
|
||||
<-ui.sem
|
||||
}
|
||||
|
||||
func (ui *Ui) Message(s string) {
|
||||
ui.sem <- 1
|
||||
ui.ui.Message(s)
|
||||
<-ui.sem
|
||||
}
|
||||
|
||||
func (ui *Ui) Error(s string) {
|
||||
ui.sem <- 1
|
||||
ui.ui.Error(s)
|
||||
<-ui.sem
|
||||
}
|
||||
|
||||
func (ui *Ui) Machine(t string, args ...string) {
|
||||
ui.sem <- 1
|
||||
ui.ui.Machine(t, args...)
|
||||
<-ui.sem
|
||||
}
|
||||
|
||||
func (ui *Ui) ProgressBar() packer.ProgressBar {
|
||||
return new(packer.NoopProgressBar)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ var guestOSTypeCommands = map[string]guestOSTypeCommand{
|
|||
mkdir: "powershell.exe -Command \"New-Item -ItemType directory -Force -ErrorAction SilentlyContinue -Path %s\"",
|
||||
removeDir: "powershell.exe -Command \"rm %s -recurse -force\"",
|
||||
statPath: "powershell.exe -Command { if (test-path %s) { exit 0 } else { exit 1 } }",
|
||||
mv: "powershell.exe -Command \"mv %s %s\"",
|
||||
mv: "powershell.exe -Command \"mv %s %s -force\"",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -183,13 +183,13 @@ func TestMovePath(t *testing.T) {
|
|||
t.Fatalf("Failed to create new GuestCommands for OS: %s", WindowsOSType)
|
||||
}
|
||||
cmd = guestCmd.MovePath("C:\\Temp\\SomeDir", "C:\\Temp\\NewDir")
|
||||
if cmd != "powershell.exe -Command \"mv C:\\Temp\\SomeDir C:\\Temp\\NewDir\"" {
|
||||
if cmd != "powershell.exe -Command \"mv C:\\Temp\\SomeDir C:\\Temp\\NewDir -force\"" {
|
||||
t.Fatalf("Unexpected Windows remove dir cmd: %s", cmd)
|
||||
}
|
||||
|
||||
// Windows OS w/ space in path
|
||||
cmd = guestCmd.MovePath("C:\\Temp\\Some Dir", "C:\\Temp\\New Dir")
|
||||
if cmd != "powershell.exe -Command \"mv C:\\Temp\\Some` Dir C:\\Temp\\New` Dir\"" {
|
||||
if cmd != "powershell.exe -Command \"mv C:\\Temp\\Some` Dir C:\\Temp\\New` Dir -force\"" {
|
||||
t.Fatalf("Unexpected Windows remove dir cmd: %s", cmd)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
524
provisioner/inspec/provisioner.go
Normal file
524
provisioner/inspec/provisioner.go
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
package inspec
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/adapter"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
var SupportedBackends = map[string]bool{"docker": true, "local": true, "ssh": true, "winrm": true}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
ctx interpolate.Context
|
||||
|
||||
// The command to run inspec
|
||||
Command string
|
||||
SubCommand string
|
||||
|
||||
// Extra options to pass to the inspec command
|
||||
ExtraArguments []string `mapstructure:"extra_arguments"`
|
||||
InspecEnvVars []string `mapstructure:"inspec_env_vars"`
|
||||
|
||||
// The profile to execute.
|
||||
Profile string `mapstructure:"profile"`
|
||||
AttributesDirectory string `mapstructure:"attributes_directory"`
|
||||
AttributesFiles []string `mapstructure:"attributes"`
|
||||
Backend string `mapstructure:"backend"`
|
||||
User string `mapstructure:"user"`
|
||||
Host string `mapstructure:"host"`
|
||||
LocalPort uint `mapstructure:"local_port"`
|
||||
SSHHostKeyFile string `mapstructure:"ssh_host_key_file"`
|
||||
SSHAuthorizedKeyFile string `mapstructure:"ssh_authorized_key_file"`
|
||||
}
|
||||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
adapter *adapter.Adapter
|
||||
done chan struct{}
|
||||
inspecVersion string
|
||||
inspecMajVersion uint
|
||||
}
|
||||
|
||||
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
p.done = make(chan struct{})
|
||||
|
||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &p.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Defaults
|
||||
if p.config.Command == "" {
|
||||
p.config.Command = "inspec"
|
||||
}
|
||||
|
||||
if p.config.SubCommand == "" {
|
||||
p.config.SubCommand = "exec"
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
err = validateProfileConfig(p.config.Profile)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
|
||||
// Check that the authorized key file exists
|
||||
if len(p.config.SSHAuthorizedKeyFile) > 0 {
|
||||
err = validateFileConfig(p.config.SSHAuthorizedKeyFile, "ssh_authorized_key_file", true)
|
||||
if err != nil {
|
||||
log.Println(p.config.SSHAuthorizedKeyFile, "does not exist")
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
}
|
||||
if len(p.config.SSHHostKeyFile) > 0 {
|
||||
err = validateFileConfig(p.config.SSHHostKeyFile, "ssh_host_key_file", true)
|
||||
if err != nil {
|
||||
log.Println(p.config.SSHHostKeyFile, "does not exist")
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.Backend == "" {
|
||||
p.config.Backend = "ssh"
|
||||
}
|
||||
|
||||
if _, ok := SupportedBackends[p.config.Backend]; !ok {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("backend: %s must be a valid backend", p.config.Backend))
|
||||
}
|
||||
|
||||
if p.config.Backend == "docker" && p.config.Host == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("backend: host must be specified for docker backend"))
|
||||
}
|
||||
|
||||
if p.config.Host == "" {
|
||||
p.config.Host = "127.0.0.1"
|
||||
}
|
||||
|
||||
if p.config.LocalPort > 65535 {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("local_port: %d must be a valid port", p.config.LocalPort))
|
||||
}
|
||||
|
||||
if len(p.config.AttributesDirectory) > 0 {
|
||||
err = validateDirectoryConfig(p.config.AttributesDirectory, "attrs")
|
||||
if err != nil {
|
||||
log.Println(p.config.AttributesDirectory, "does not exist")
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.User == "" {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
} else {
|
||||
p.config.User = usr.Username
|
||||
}
|
||||
}
|
||||
if p.config.User == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("user: could not determine current user from environment."))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) getVersion() error {
|
||||
out, err := exec.Command(p.config.Command, "version").Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"Error running \"%s version\": %s", p.config.Command, err.Error())
|
||||
}
|
||||
|
||||
versionRe := regexp.MustCompile(`\w (\d+\.\d+[.\d+]*)`)
|
||||
matches := versionRe.FindStringSubmatch(string(out))
|
||||
if matches == nil {
|
||||
return fmt.Errorf(
|
||||
"Could not find %s version in output:\n%s", p.config.Command, string(out))
|
||||
}
|
||||
|
||||
version := matches[1]
|
||||
log.Printf("%s version: %s", p.config.Command, version)
|
||||
p.inspecVersion = version
|
||||
|
||||
majVer, err := strconv.ParseUint(strings.Split(version, ".")[0], 10, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not parse major version from \"%s\".", version)
|
||||
}
|
||||
p.inspecMajVersion = uint(majVer)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||
ui.Say("Provisioning with Inspec...")
|
||||
|
||||
for i, envVar := range p.config.InspecEnvVars {
|
||||
envVar, err := interpolate.Render(envVar, &p.config.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not interpolate inspec env vars: %s", err)
|
||||
}
|
||||
p.config.InspecEnvVars[i] = envVar
|
||||
}
|
||||
|
||||
for i, arg := range p.config.ExtraArguments {
|
||||
arg, err := interpolate.Render(arg, &p.config.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not interpolate inspec extra arguments: %s", err)
|
||||
}
|
||||
p.config.ExtraArguments[i] = arg
|
||||
}
|
||||
|
||||
for i, arg := range p.config.AttributesFiles {
|
||||
arg, err := interpolate.Render(arg, &p.config.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not interpolate inspec attributes: %s", err)
|
||||
}
|
||||
p.config.AttributesFiles[i] = arg
|
||||
}
|
||||
|
||||
k, err := newUserKey(p.config.SSHAuthorizedKeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hostSigner, err := newSigner(p.config.SSHHostKeyFile)
|
||||
// Remove the private key file
|
||||
if len(k.privKeyFile) > 0 {
|
||||
defer os.Remove(k.privKeyFile)
|
||||
}
|
||||
|
||||
keyChecker := ssh.CertChecker{
|
||||
UserKeyFallback: func(conn ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
if user := conn.User(); user != p.config.User {
|
||||
return nil, errors.New(fmt.Sprintf("authentication failed: %s is not a valid user", user))
|
||||
}
|
||||
|
||||
if !bytes.Equal(k.Marshal(), pubKey.Marshal()) {
|
||||
return nil, errors.New("authentication failed: unauthorized key")
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
config := &ssh.ServerConfig{
|
||||
AuthLogCallback: func(conn ssh.ConnMetadata, method string, err error) {
|
||||
log.Printf("authentication attempt from %s to %s as %s using %s", conn.RemoteAddr(), conn.LocalAddr(), conn.User(), method)
|
||||
},
|
||||
PublicKeyCallback: keyChecker.Authenticate,
|
||||
//NoClientAuth: true,
|
||||
}
|
||||
|
||||
config.AddHostKey(hostSigner)
|
||||
|
||||
localListener, err := func() (net.Listener, error) {
|
||||
|
||||
port := p.config.LocalPort
|
||||
tries := 1
|
||||
if port != 0 {
|
||||
tries = 10
|
||||
}
|
||||
for i := 0; i < tries; i++ {
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
port++
|
||||
if err != nil {
|
||||
ui.Say(err.Error())
|
||||
continue
|
||||
}
|
||||
_, portStr, err := net.SplitHostPort(l.Addr().String())
|
||||
if err != nil {
|
||||
ui.Say(err.Error())
|
||||
continue
|
||||
}
|
||||
portUint64, err := strconv.ParseUint(portStr, 10, 0)
|
||||
if err != nil {
|
||||
ui.Say(err.Error())
|
||||
continue
|
||||
}
|
||||
p.config.LocalPort = uint(portUint64)
|
||||
return l, nil
|
||||
}
|
||||
return nil, errors.New("Error setting up SSH proxy connection")
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui = &packer.SafeUi{
|
||||
Sem: make(chan int, 1),
|
||||
Ui: ui,
|
||||
}
|
||||
p.adapter = adapter.NewAdapter(p.done, localListener, config, "", ui, comm)
|
||||
|
||||
defer func() {
|
||||
log.Print("shutting down the SSH proxy")
|
||||
close(p.done)
|
||||
p.adapter.Shutdown()
|
||||
}()
|
||||
|
||||
go p.adapter.Serve()
|
||||
|
||||
tf, err := ioutil.TempFile(p.config.AttributesDirectory, "packer-provisioner-inspec.*.yml")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error preparing packer attributes file: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
w := bufio.NewWriter(tf)
|
||||
w.WriteString(fmt.Sprintf("packer_build_name: %s\n", p.config.PackerBuildName))
|
||||
w.WriteString(fmt.Sprintf("packer_builder_type: %s\n", p.config.PackerBuilderType))
|
||||
|
||||
if err := w.Flush(); err != nil {
|
||||
tf.Close()
|
||||
return fmt.Errorf("Error preparing packer attributes file: %s", err)
|
||||
}
|
||||
tf.Close()
|
||||
p.config.AttributesFiles = append(p.config.AttributesFiles, tf.Name())
|
||||
|
||||
if err := p.executeInspec(ui, comm, k.privKeyFile); err != nil {
|
||||
return fmt.Errorf("Error executing Inspec: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (p *Provisioner) Cancel() {
|
||||
if p.done != nil {
|
||||
close(p.done)
|
||||
}
|
||||
if p.adapter != nil {
|
||||
p.adapter.Shutdown()
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func (p *Provisioner) executeInspec(ui packer.Ui, comm packer.Communicator, privKeyFile string) error {
|
||||
var envvars []string
|
||||
|
||||
args := []string{p.config.SubCommand, p.config.Profile}
|
||||
args = append(args, "--backend", p.config.Backend)
|
||||
args = append(args, "--host", p.config.Host)
|
||||
|
||||
if p.config.Backend == "ssh" {
|
||||
if len(privKeyFile) > 0 {
|
||||
args = append(args, "--key-files", privKeyFile)
|
||||
}
|
||||
args = append(args, "--user", p.config.User)
|
||||
args = append(args, "--port", strconv.FormatUint(uint64(p.config.LocalPort), 10))
|
||||
}
|
||||
|
||||
args = append(args, "--attrs")
|
||||
args = append(args, p.config.AttributesFiles...)
|
||||
args = append(args, p.config.ExtraArguments...)
|
||||
|
||||
if len(p.config.InspecEnvVars) > 0 {
|
||||
envvars = append(envvars, p.config.InspecEnvVars...)
|
||||
}
|
||||
|
||||
cmd := exec.Command(p.config.Command, args...)
|
||||
|
||||
cmd.Env = os.Environ()
|
||||
if len(envvars) > 0 {
|
||||
cmd.Env = append(cmd.Env, envvars...)
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
repeat := func(r io.ReadCloser) {
|
||||
reader := bufio.NewReader(r)
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if line != "" {
|
||||
line = strings.TrimRightFunc(line, unicode.IsSpace)
|
||||
ui.Message(line)
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else {
|
||||
ui.Error(err.Error())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
wg.Add(2)
|
||||
go repeat(stdout)
|
||||
go repeat(stderr)
|
||||
|
||||
ui.Say(fmt.Sprintf("Executing Inspec: %s", strings.Join(cmd.Args, " ")))
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
wg.Wait()
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Non-zero exit status: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateFileConfig(name string, config string, req bool) error {
|
||||
if req {
|
||||
if name == "" {
|
||||
return fmt.Errorf("%s must be specified.", config)
|
||||
}
|
||||
}
|
||||
info, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s is invalid: %s", config, name, err)
|
||||
} else if info.IsDir() {
|
||||
return fmt.Errorf("%s: %s must point to a file", config, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateProfileConfig(name string) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("profile must be specified.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDirectoryConfig(name string, config string) error {
|
||||
info, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s is invalid: %s", config, name, err)
|
||||
} else if !info.IsDir() {
|
||||
return fmt.Errorf("%s: %s must point to a directory", config, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type userKey struct {
|
||||
ssh.PublicKey
|
||||
privKeyFile string
|
||||
}
|
||||
|
||||
func newUserKey(pubKeyFile string) (*userKey, error) {
|
||||
userKey := new(userKey)
|
||||
if len(pubKeyFile) > 0 {
|
||||
pubKeyBytes, err := ioutil.ReadFile(pubKeyFile)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to read public key")
|
||||
}
|
||||
userKey.PublicKey, _, _, _, err = ssh.ParseAuthorizedKey(pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to parse authorized key")
|
||||
}
|
||||
|
||||
return userKey, nil
|
||||
}
|
||||
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to generate key pair")
|
||||
}
|
||||
userKey.PublicKey, err = ssh.NewPublicKey(key.Public())
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to extract public key from generated key pair")
|
||||
}
|
||||
|
||||
// To support Inspec calling back to us we need to write
|
||||
// this file down
|
||||
privateKeyDer := x509.MarshalPKCS1PrivateKey(key)
|
||||
privateKeyBlock := pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Headers: nil,
|
||||
Bytes: privateKeyDer,
|
||||
}
|
||||
tf, err := ioutil.TempFile("", "packer-provisioner-inspec.*.key")
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to create temp file for generated key")
|
||||
}
|
||||
_, err = tf.Write(pem.EncodeToMemory(&privateKeyBlock))
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to write private key to temp file")
|
||||
}
|
||||
|
||||
err = tf.Close()
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to close private key temp file")
|
||||
}
|
||||
userKey.privKeyFile = tf.Name()
|
||||
|
||||
return userKey, nil
|
||||
}
|
||||
|
||||
type signer struct {
|
||||
ssh.Signer
|
||||
}
|
||||
|
||||
func newSigner(privKeyFile string) (*signer, error) {
|
||||
signer := new(signer)
|
||||
|
||||
if len(privKeyFile) > 0 {
|
||||
privateBytes, err := ioutil.ReadFile(privKeyFile)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to load private host key")
|
||||
}
|
||||
|
||||
signer.Signer, err = ssh.ParsePrivateKey(privateBytes)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to parse private host key")
|
||||
}
|
||||
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to generate server key pair")
|
||||
}
|
||||
|
||||
signer.Signer, err = ssh.NewSignerFromKey(key)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to extract private key from generated key pair")
|
||||
}
|
||||
|
||||
return signer, nil
|
||||
}
|
||||
293
provisioner/inspec/provisioner_test.go
Normal file
293
provisioner/inspec/provisioner_test.go
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
package inspec
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// Be sure to remove the InSpec stub file in each test with:
|
||||
// defer os.Remove(config["command"].(string))
|
||||
func testConfig(t *testing.T) map[string]interface{} {
|
||||
m := make(map[string]interface{})
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
inspec_stub := path.Join(wd, "packer-inspec-stub.sh")
|
||||
|
||||
err = ioutil.WriteFile(inspec_stub, []byte("#!/usr/bin/env bash\necho 2.2.16"), 0777)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
m["command"] = inspec_stub
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func TestProvisioner_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Provisioner{}
|
||||
if _, ok := raw.(packer.Provisioner); !ok {
|
||||
t.Fatalf("must be a Provisioner")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_Defaults(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig(t)
|
||||
defer os.Remove(config["command"].(string))
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
|
||||
hostkey_file, err := ioutil.TempFile("", "hostkey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(hostkey_file.Name())
|
||||
|
||||
publickey_file, err := ioutil.TempFile("", "publickey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(publickey_file.Name())
|
||||
|
||||
profile_file, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
config["ssh_host_key_file"] = hostkey_file.Name()
|
||||
config["ssh_authorized_key_file"] = publickey_file.Name()
|
||||
config["profile"] = profile_file.Name()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
err = os.Unsetenv("USER")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_ProfileFile(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig(t)
|
||||
defer os.Remove(config["command"].(string))
|
||||
|
||||
hostkey_file, err := ioutil.TempFile("", "hostkey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(hostkey_file.Name())
|
||||
|
||||
publickey_file, err := ioutil.TempFile("", "publickey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(publickey_file.Name())
|
||||
|
||||
config["ssh_host_key_file"] = hostkey_file.Name()
|
||||
config["ssh_authorized_key_file"] = publickey_file.Name()
|
||||
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
profile_file, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
config["profile"] = profile_file.Name()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
test_dir, err := ioutil.TempDir("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(test_dir)
|
||||
|
||||
config["profile"] = test_dir
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_HostKeyFile(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig(t)
|
||||
defer os.Remove(config["command"].(string))
|
||||
|
||||
publickey_file, err := ioutil.TempFile("", "publickey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(publickey_file.Name())
|
||||
|
||||
profile_file, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
filename := make([]byte, 10)
|
||||
n, err := io.ReadFull(rand.Reader, filename)
|
||||
if n != len(filename) || err != nil {
|
||||
t.Fatal("could not create random file name")
|
||||
}
|
||||
|
||||
config["ssh_host_key_file"] = fmt.Sprintf("%x", filename)
|
||||
config["ssh_authorized_key_file"] = publickey_file.Name()
|
||||
config["profile"] = profile_file.Name()
|
||||
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should error if ssh_host_key_file does not exist")
|
||||
}
|
||||
|
||||
hostkey_file, err := ioutil.TempFile("", "hostkey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(hostkey_file.Name())
|
||||
|
||||
config["ssh_host_key_file"] = hostkey_file.Name()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_AuthorizedKeyFiles(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig(t)
|
||||
defer os.Remove(config["command"].(string))
|
||||
|
||||
hostkey_file, err := ioutil.TempFile("", "hostkey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(hostkey_file.Name())
|
||||
|
||||
profile_file, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
filename := make([]byte, 10)
|
||||
n, err := io.ReadFull(rand.Reader, filename)
|
||||
if n != len(filename) || err != nil {
|
||||
t.Fatal("could not create random file name")
|
||||
}
|
||||
|
||||
config["ssh_host_key_file"] = hostkey_file.Name()
|
||||
config["profile"] = profile_file.Name()
|
||||
config["ssh_authorized_key_file"] = fmt.Sprintf("%x", filename)
|
||||
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Errorf("should error if ssh_authorized_key_file does not exist")
|
||||
}
|
||||
|
||||
publickey_file, err := ioutil.TempFile("", "publickey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(publickey_file.Name())
|
||||
|
||||
config["ssh_authorized_key_file"] = publickey_file.Name()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_LocalPort(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig(t)
|
||||
defer os.Remove(config["command"].(string))
|
||||
|
||||
hostkey_file, err := ioutil.TempFile("", "hostkey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(hostkey_file.Name())
|
||||
|
||||
publickey_file, err := ioutil.TempFile("", "publickey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(publickey_file.Name())
|
||||
|
||||
profile_file, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
config["ssh_host_key_file"] = hostkey_file.Name()
|
||||
config["ssh_authorized_key_file"] = publickey_file.Name()
|
||||
config["profile"] = profile_file.Name()
|
||||
|
||||
config["local_port"] = uint(65537)
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["local_port"] = uint(22222)
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspecGetVersion(t *testing.T) {
|
||||
if os.Getenv("PACKER_ACC") == "" {
|
||||
t.Skip("This test is only run with PACKER_ACC=1 and it requires InSpec to be installed")
|
||||
}
|
||||
|
||||
var p Provisioner
|
||||
p.config.Command = "inspec exec"
|
||||
err := p.getVersion()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspecGetVersionError(t *testing.T) {
|
||||
var p Provisioner
|
||||
p.config.Command = "./test-fixtures/exit1"
|
||||
err := p.getVersion()
|
||||
if err == nil {
|
||||
t.Fatal("Should return error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "./test-fixtures/exit1 version") {
|
||||
t.Fatal("Error message should include command name")
|
||||
}
|
||||
}
|
||||
3
provisioner/inspec/test-fixtures/exit1
Executable file
3
provisioner/inspec/test-fixtures/exit1
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
exit 1
|
||||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
# This script builds the application from source for multiple platforms.
|
||||
# Determine the arch/os combos we're building for
|
||||
ALL_XC_ARCH="386 amd64 arm arm64 ppc64le"
|
||||
ALL_XC_OS="linux darwin windows freebsd openbsd solaris"
|
||||
ALL_XC_ARCH=${ALL_XC_ARCH:-"386 amd64 arm arm64 ppc64le"}
|
||||
ALL_XC_OS=${ALL_XC_OS:-"linux darwin windows freebsd openbsd solaris"}
|
||||
|
||||
# Exit immediately if a command fails
|
||||
set -e
|
||||
|
|
|
|||
56
vendor/github.com/digitalocean/godo/CHANGELOG.md
generated
vendored
56
vendor/github.com/digitalocean/godo/CHANGELOG.md
generated
vendored
|
|
@ -1,5 +1,61 @@
|
|||
# Change Log
|
||||
|
||||
## [v1.6.0] - 2018-10-16
|
||||
|
||||
- #185 Projects support [beta] - @mchitten
|
||||
|
||||
## [v1.5.0] - 2018-10-01
|
||||
|
||||
- #181 Adding tagging images support - @hugocorbucci
|
||||
|
||||
## [v1.4.2] - 2018-08-30
|
||||
|
||||
- #178 Allowing creating domain records with weight of 0 - @TFaga
|
||||
- #177 Adding `VolumeLimit` to account - @lxfontes
|
||||
|
||||
## [v1.4.1] - 2018-08-23
|
||||
|
||||
- #176 Fix cdn flush cache API endpoint - @sunny-b
|
||||
|
||||
## [v1.4.0] - 2018-08-22
|
||||
|
||||
- #175 Add support for Spaces CDN - @sunny-b
|
||||
|
||||
## [v1.3.0] - 2018-05-24
|
||||
|
||||
- #170 Add support for volume formatting - @adamwg
|
||||
|
||||
## [v1.2.0] - 2018-05-08
|
||||
|
||||
- #166 Remove support for Go 1.6 - @iheanyi
|
||||
- #165 Add support for Let's Encrypt Certificates - @viola
|
||||
|
||||
## [v1.1.3] - 2018-03-07
|
||||
|
||||
- #156 Handle non-json errors from the API - @aknuds1
|
||||
- #158 Update droplet example to use latest instance type - @dan-v
|
||||
|
||||
## [v1.1.2] - 2018-03-06
|
||||
|
||||
- #157 storage: list volumes should handle only name or only region params - @andrewsykim
|
||||
- #154 docs: replace first example with fully-runnable example - @xmudrii
|
||||
- #152 Handle flags & tag properties of domain record - @jaymecd
|
||||
|
||||
## [v1.1.1] - 2017-09-29
|
||||
|
||||
- #151 Following user agent field recommendations - @joonas
|
||||
- #148 AsRequest method to create load balancers requests - @lukegb
|
||||
|
||||
## [v1.1.0] - 2017-06-06
|
||||
|
||||
### Added
|
||||
- #145 Add FirewallsService for managing Firewalls with the DigitalOcean API. - @viola
|
||||
- #139 Add TTL field to the Domains. - @xmudrii
|
||||
|
||||
### Fixed
|
||||
- #143 Fix oauth2.NoContext depreciation. - @jbowens
|
||||
- #141 Fix DropletActions on tagged resources. - @xmudrii
|
||||
|
||||
## [v1.0.0] - 2017-03-10
|
||||
|
||||
### Added
|
||||
|
|
|
|||
1
vendor/github.com/digitalocean/godo/CONTRIBUTING.md
generated
vendored
1
vendor/github.com/digitalocean/godo/CONTRIBUTING.md
generated
vendored
|
|
@ -12,6 +12,7 @@ Assuming your `$GOPATH` is set up according to your desires, run:
|
|||
|
||||
```sh
|
||||
go get github.com/digitalocean/godo
|
||||
go get -u github.com/stretchr/testify/assert
|
||||
```
|
||||
|
||||
## Running tests
|
||||
|
|
|
|||
36
vendor/github.com/digitalocean/godo/README.md
generated
vendored
36
vendor/github.com/digitalocean/godo/README.md
generated
vendored
|
|
@ -27,25 +27,37 @@ at the DigitalOcean Control Panel [Applications Page](https://cloud.digitalocean
|
|||
You can then use your token to create a new client:
|
||||
|
||||
```go
|
||||
import "golang.org/x/oauth2"
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/digitalocean/godo"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
pat = "mytoken"
|
||||
)
|
||||
|
||||
pat := "mytoken"
|
||||
type TokenSource struct {
|
||||
AccessToken string
|
||||
AccessToken string
|
||||
}
|
||||
|
||||
func (t *TokenSource) Token() (*oauth2.Token, error) {
|
||||
token := &oauth2.Token{
|
||||
AccessToken: t.AccessToken,
|
||||
}
|
||||
return token, nil
|
||||
token := &oauth2.Token{
|
||||
AccessToken: t.AccessToken,
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
tokenSource := &TokenSource{
|
||||
AccessToken: pat,
|
||||
func main() {
|
||||
tokenSource := &TokenSource{
|
||||
AccessToken: pat,
|
||||
}
|
||||
|
||||
oauthClient := oauth2.NewClient(context.Background(), tokenSource)
|
||||
client := godo.NewClient(oauthClient)
|
||||
}
|
||||
oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource)
|
||||
client := godo.NewClient(oauthClient)
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
|
@ -59,7 +71,7 @@ dropletName := "super-cool-droplet"
|
|||
createRequest := &godo.DropletCreateRequest{
|
||||
Name: dropletName,
|
||||
Region: "nyc3",
|
||||
Size: "512mb",
|
||||
Size: "s-1vcpu-1gb",
|
||||
Image: godo.DropletCreateImage{
|
||||
Slug: "ubuntu-14-04-x64",
|
||||
},
|
||||
|
|
|
|||
10
vendor/github.com/digitalocean/godo/account.go
generated
vendored
10
vendor/github.com/digitalocean/godo/account.go
generated
vendored
|
|
@ -1,6 +1,9 @@
|
|||
package godo
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// AccountService is an interface for interfacing with the Account
|
||||
// endpoints of the DigitalOcean API
|
||||
|
|
@ -21,6 +24,7 @@ var _ AccountService = &AccountServiceOp{}
|
|||
type Account struct {
|
||||
DropletLimit int `json:"droplet_limit,omitempty"`
|
||||
FloatingIPLimit int `json:"floating_ip_limit,omitempty"`
|
||||
VolumeLimit int `json:"volume_limit,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
EmailVerified bool `json:"email_verified,omitempty"`
|
||||
|
|
@ -41,13 +45,13 @@ func (s *AccountServiceOp) Get(ctx context.Context) (*Account, *Response, error)
|
|||
|
||||
path := "v2/account"
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(accountRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
|
|||
9
vendor/github.com/digitalocean/godo/action.go
generated
vendored
9
vendor/github.com/digitalocean/godo/action.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package godo
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -60,13 +61,13 @@ func (s *ActionsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Action
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -84,13 +85,13 @@ func (s *ActionsServiceOp) Get(ctx context.Context, id int) (*Action, *Response,
|
|||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%d", actionsBasePath, id)
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
|
|||
195
vendor/github.com/digitalocean/godo/cdn.go
generated
vendored
Normal file
195
vendor/github.com/digitalocean/godo/cdn.go
generated
vendored
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
package godo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const cdnBasePath = "v2/cdn/endpoints"
|
||||
|
||||
// CDNService is an interface for managing Spaces CDN with the DigitalOcean API.
|
||||
type CDNService interface {
|
||||
List(context.Context, *ListOptions) ([]CDN, *Response, error)
|
||||
Get(context.Context, string) (*CDN, *Response, error)
|
||||
Create(context.Context, *CDNCreateRequest) (*CDN, *Response, error)
|
||||
UpdateTTL(context.Context, string, *CDNUpdateRequest) (*CDN, *Response, error)
|
||||
FlushCache(context.Context, string, *CDNFlushCacheRequest) (*Response, error)
|
||||
Delete(context.Context, string) (*Response, error)
|
||||
}
|
||||
|
||||
// CDNServiceOp handles communication with the CDN related methods of the
|
||||
// DigitalOcean API.
|
||||
type CDNServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
var _ CDNService = &CDNServiceOp{}
|
||||
|
||||
// CDN represents a DigitalOcean CDN
|
||||
type CDN struct {
|
||||
ID string `json:"id"`
|
||||
Origin string `json:"origin"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
TTL uint32 `json:"ttl"`
|
||||
}
|
||||
|
||||
// CDNRoot represents a response from the DigitalOcean API
|
||||
type cdnRoot struct {
|
||||
Endpoint *CDN `json:"endpoint"`
|
||||
}
|
||||
|
||||
type cdnsRoot struct {
|
||||
Endpoints []CDN `json:"endpoints"`
|
||||
Links *Links `json:"links"`
|
||||
}
|
||||
|
||||
// CDNCreateRequest represents a request to create a CDN.
|
||||
type CDNCreateRequest struct {
|
||||
Origin string `json:"origin"`
|
||||
TTL uint32 `json:"ttl"`
|
||||
}
|
||||
|
||||
// CDNUpdateRequest represents a request to update the ttl of a CDN.
|
||||
type CDNUpdateRequest struct {
|
||||
TTL uint32 `json:"ttl"`
|
||||
}
|
||||
|
||||
// CDNFlushCacheRequest represents a request to flush cache of a CDN.
|
||||
type CDNFlushCacheRequest struct {
|
||||
Files []string `json:"files"`
|
||||
}
|
||||
|
||||
// List all CDN endpoints
|
||||
func (c CDNServiceOp) List(ctx context.Context, opt *ListOptions) ([]CDN, *Response, error) {
|
||||
path, err := addOptions(cdnBasePath, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := c.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(cdnsRoot)
|
||||
resp, err := c.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
if l := root.Links; l != nil {
|
||||
resp.Links = l
|
||||
}
|
||||
|
||||
return root.Endpoints, resp, err
|
||||
}
|
||||
|
||||
// Get individual CDN. It requires a non-empty cdn id.
|
||||
func (c CDNServiceOp) Get(ctx context.Context, id string) (*CDN, *Response, error) {
|
||||
if len(id) == 0 {
|
||||
return nil, nil, NewArgError("id", "cannot be an empty string")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%s", cdnBasePath, id)
|
||||
|
||||
req, err := c.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(cdnRoot)
|
||||
resp, err := c.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Endpoint, resp, err
|
||||
}
|
||||
|
||||
// Create a new CDN
|
||||
func (c CDNServiceOp) Create(ctx context.Context, createRequest *CDNCreateRequest) (*CDN, *Response, error) {
|
||||
if createRequest == nil {
|
||||
return nil, nil, NewArgError("createRequest", "cannot be nil")
|
||||
}
|
||||
|
||||
req, err := c.client.NewRequest(ctx, http.MethodPost, cdnBasePath, createRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(cdnRoot)
|
||||
resp, err := c.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Endpoint, resp, err
|
||||
}
|
||||
|
||||
// UpdateTTL updates the ttl of individual CDN
|
||||
func (c CDNServiceOp) UpdateTTL(ctx context.Context, id string, updateRequest *CDNUpdateRequest) (*CDN, *Response, error) {
|
||||
if updateRequest == nil {
|
||||
return nil, nil, NewArgError("updateRequest", "cannot be nil")
|
||||
}
|
||||
|
||||
if len(id) == 0 {
|
||||
return nil, nil, NewArgError("id", "cannot be an empty string")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%s", cdnBasePath, id)
|
||||
|
||||
req, err := c.client.NewRequest(ctx, http.MethodPut, path, updateRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(cdnRoot)
|
||||
resp, err := c.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Endpoint, resp, err
|
||||
}
|
||||
|
||||
// FlushCache flushes the cache of an individual CDN. Requires a non-empty slice of file paths and/or wildcards
|
||||
func (c CDNServiceOp) FlushCache(ctx context.Context, id string, flushCacheRequest *CDNFlushCacheRequest) (*Response, error) {
|
||||
if flushCacheRequest == nil {
|
||||
return nil, NewArgError("flushCacheRequest", "cannot be nil")
|
||||
}
|
||||
|
||||
if len(id) == 0 {
|
||||
return nil, NewArgError("id", "cannot be an empty string")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%s/cache", cdnBasePath, id)
|
||||
|
||||
req, err := c.client.NewRequest(ctx, http.MethodDelete, path, flushCacheRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(ctx, req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Delete an individual CDN
|
||||
func (c CDNServiceOp) Delete(ctx context.Context, id string) (*Response, error) {
|
||||
if len(id) == 0 {
|
||||
return nil, NewArgError("id", "cannot be an empty string")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%s", cdnBasePath, id)
|
||||
|
||||
req, err := c.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(ctx, req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
40
vendor/github.com/digitalocean/godo/certificates.go
generated
vendored
40
vendor/github.com/digitalocean/godo/certificates.go
generated
vendored
|
|
@ -2,6 +2,7 @@ package godo
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
|
|
@ -18,19 +19,24 @@ type CertificatesService interface {
|
|||
|
||||
// Certificate represents a DigitalOcean certificate configuration.
|
||||
type Certificate struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
NotAfter string `json:"not_after,omitempty"`
|
||||
SHA1Fingerprint string `json:"sha1_fingerprint,omitempty"`
|
||||
Created string `json:"created_at,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
DNSNames []string `json:"dns_names,omitempty"`
|
||||
NotAfter string `json:"not_after,omitempty"`
|
||||
SHA1Fingerprint string `json:"sha1_fingerprint,omitempty"`
|
||||
Created string `json:"created_at,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// CertificateRequest represents configuration for a new certificate.
|
||||
type CertificateRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
PrivateKey string `json:"private_key,omitempty"`
|
||||
LeafCertificate string `json:"leaf_certificate,omitempty"`
|
||||
CertificateChain string `json:"certificate_chain,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
DNSNames []string `json:"dns_names,omitempty"`
|
||||
PrivateKey string `json:"private_key,omitempty"`
|
||||
LeafCertificate string `json:"leaf_certificate,omitempty"`
|
||||
CertificateChain string `json:"certificate_chain,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
type certificateRoot struct {
|
||||
|
|
@ -53,13 +59,13 @@ var _ CertificatesService = &CertificatesServiceOp{}
|
|||
func (c *CertificatesServiceOp) Get(ctx context.Context, cID string) (*Certificate, *Response, error) {
|
||||
urlStr := path.Join(certificatesBasePath, cID)
|
||||
|
||||
req, err := c.client.NewRequest(ctx, "GET", urlStr, nil)
|
||||
req, err := c.client.NewRequest(ctx, http.MethodGet, urlStr, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(certificateRoot)
|
||||
resp, err := c.client.Do(req, root)
|
||||
resp, err := c.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -74,13 +80,13 @@ func (c *CertificatesServiceOp) List(ctx context.Context, opt *ListOptions) ([]C
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := c.client.NewRequest(ctx, "GET", urlStr, nil)
|
||||
req, err := c.client.NewRequest(ctx, http.MethodGet, urlStr, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(certificatesRoot)
|
||||
resp, err := c.client.Do(req, root)
|
||||
resp, err := c.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -93,13 +99,13 @@ func (c *CertificatesServiceOp) List(ctx context.Context, opt *ListOptions) ([]C
|
|||
|
||||
// Create a new certificate with provided configuration.
|
||||
func (c *CertificatesServiceOp) Create(ctx context.Context, cr *CertificateRequest) (*Certificate, *Response, error) {
|
||||
req, err := c.client.NewRequest(ctx, "POST", certificatesBasePath, cr)
|
||||
req, err := c.client.NewRequest(ctx, http.MethodPost, certificatesBasePath, cr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(certificateRoot)
|
||||
resp, err := c.client.Do(req, root)
|
||||
resp, err := c.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -111,10 +117,10 @@ func (c *CertificatesServiceOp) Create(ctx context.Context, cr *CertificateReque
|
|||
func (c *CertificatesServiceOp) Delete(ctx context.Context, cID string) (*Response, error) {
|
||||
urlStr := path.Join(certificatesBasePath, cID)
|
||||
|
||||
req, err := c.client.NewRequest(ctx, "DELETE", urlStr, nil)
|
||||
req, err := c.client.NewRequest(ctx, http.MethodDelete, urlStr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.client.Do(req, nil)
|
||||
return c.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
|
|
|||
55
vendor/github.com/digitalocean/godo/domains.go
generated
vendored
55
vendor/github.com/digitalocean/godo/domains.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package godo
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const domainsBasePath = "v2/domains"
|
||||
|
|
@ -51,7 +52,7 @@ type domainsRoot struct {
|
|||
// DomainCreateRequest respresents a request to create a domain.
|
||||
type DomainCreateRequest struct {
|
||||
Name string `json:"name"`
|
||||
IPAddress string `json:"ip_address"`
|
||||
IPAddress string `json:"ip_address,omitempty"`
|
||||
}
|
||||
|
||||
// DomainRecordRoot is the root of an individual Domain Record response
|
||||
|
|
@ -71,9 +72,12 @@ type DomainRecord struct {
|
|||
Type string `json:"type,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Data string `json:"data,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
Port int `json:"port,omitempty"`
|
||||
Weight int `json:"weight,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
Weight int `json:"weight"`
|
||||
Flags int `json:"flags"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
}
|
||||
|
||||
// DomainRecordEditRequest represents a request to update a domain record.
|
||||
|
|
@ -81,15 +85,22 @@ type DomainRecordEditRequest struct {
|
|||
Type string `json:"type,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Data string `json:"data,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
Port int `json:"port,omitempty"`
|
||||
Weight int `json:"weight,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
Weight int `json:"weight"`
|
||||
Flags int `json:"flags"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
}
|
||||
|
||||
func (d Domain) String() string {
|
||||
return Stringify(d)
|
||||
}
|
||||
|
||||
func (d Domain) URN() string {
|
||||
return ToURN("Domain", d.Name)
|
||||
}
|
||||
|
||||
// List all domains.
|
||||
func (s DomainsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Domain, *Response, error) {
|
||||
path := domainsBasePath
|
||||
|
|
@ -98,13 +109,13 @@ func (s DomainsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Domain,
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(domainsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -123,13 +134,13 @@ func (s *DomainsServiceOp) Get(ctx context.Context, name string) (*Domain, *Resp
|
|||
|
||||
path := fmt.Sprintf("%s/%s", domainsBasePath, name)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(domainRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -145,13 +156,13 @@ func (s *DomainsServiceOp) Create(ctx context.Context, createRequest *DomainCrea
|
|||
|
||||
path := domainsBasePath
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "POST", path, createRequest)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, path, createRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(domainRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -166,12 +177,12 @@ func (s *DomainsServiceOp) Delete(ctx context.Context, name string) (*Response,
|
|||
|
||||
path := fmt.Sprintf("%s/%s", domainsBasePath, name)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
resp, err := s.client.Do(ctx, req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
|
@ -198,13 +209,13 @@ func (s *DomainsServiceOp) Records(ctx context.Context, domain string, opt *List
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(domainRecordsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -227,13 +238,13 @@ func (s *DomainsServiceOp) Record(ctx context.Context, domain string, id int) (*
|
|||
|
||||
path := fmt.Sprintf("%s/%s/records/%d", domainsBasePath, domain, id)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
record := new(domainRecordRoot)
|
||||
resp, err := s.client.Do(req, record)
|
||||
resp, err := s.client.Do(ctx, req, record)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -253,12 +264,12 @@ func (s *DomainsServiceOp) DeleteRecord(ctx context.Context, domain string, id i
|
|||
|
||||
path := fmt.Sprintf("%s/%s/records/%d", domainsBasePath, domain, id)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
resp, err := s.client.Do(ctx, req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
|
@ -289,7 +300,7 @@ func (s *DomainsServiceOp) EditRecord(ctx context.Context,
|
|||
}
|
||||
|
||||
d := new(DomainRecord)
|
||||
resp, err := s.client.Do(req, d)
|
||||
resp, err := s.client.Do(ctx, req, d)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -310,14 +321,14 @@ func (s *DomainsServiceOp) CreateRecord(ctx context.Context,
|
|||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%s/records", domainsBasePath, domain)
|
||||
req, err := s.client.NewRequest(ctx, "POST", path, createRequest)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, path, createRequest)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
d := new(domainRecordRoot)
|
||||
resp, err := s.client.Do(req, d)
|
||||
resp, err := s.client.Do(ctx, req, d)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
|
|||
62
vendor/github.com/digitalocean/godo/droplet_actions.go
generated
vendored
62
vendor/github.com/digitalocean/godo/droplet_actions.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package godo
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
|
|
@ -14,32 +15,31 @@ type ActionRequest map[string]interface{}
|
|||
// See: https://developers.digitalocean.com/documentation/v2#droplet-actions
|
||||
type DropletActionsService interface {
|
||||
Shutdown(context.Context, int) (*Action, *Response, error)
|
||||
ShutdownByTag(context.Context, string) (*Action, *Response, error)
|
||||
ShutdownByTag(context.Context, string) ([]Action, *Response, error)
|
||||
PowerOff(context.Context, int) (*Action, *Response, error)
|
||||
PowerOffByTag(context.Context, string) (*Action, *Response, error)
|
||||
PowerOffByTag(context.Context, string) ([]Action, *Response, error)
|
||||
PowerOn(context.Context, int) (*Action, *Response, error)
|
||||
PowerOnByTag(context.Context, string) (*Action, *Response, error)
|
||||
PowerOnByTag(context.Context, string) ([]Action, *Response, error)
|
||||
PowerCycle(context.Context, int) (*Action, *Response, error)
|
||||
PowerCycleByTag(context.Context, string) (*Action, *Response, error)
|
||||
PowerCycleByTag(context.Context, string) ([]Action, *Response, error)
|
||||
Reboot(context.Context, int) (*Action, *Response, error)
|
||||
Restore(context.Context, int, int) (*Action, *Response, error)
|
||||
Resize(context.Context, int, string, bool) (*Action, *Response, error)
|
||||
Rename(context.Context, int, string) (*Action, *Response, error)
|
||||
Snapshot(context.Context, int, string) (*Action, *Response, error)
|
||||
SnapshotByTag(context.Context, string, string) (*Action, *Response, error)
|
||||
SnapshotByTag(context.Context, string, string) ([]Action, *Response, error)
|
||||
EnableBackups(context.Context, int) (*Action, *Response, error)
|
||||
EnableBackupsByTag(context.Context, string) (*Action, *Response, error)
|
||||
EnableBackupsByTag(context.Context, string) ([]Action, *Response, error)
|
||||
DisableBackups(context.Context, int) (*Action, *Response, error)
|
||||
DisableBackupsByTag(context.Context, string) (*Action, *Response, error)
|
||||
DisableBackupsByTag(context.Context, string) ([]Action, *Response, error)
|
||||
PasswordReset(context.Context, int) (*Action, *Response, error)
|
||||
RebuildByImageID(context.Context, int, int) (*Action, *Response, error)
|
||||
RebuildByImageSlug(context.Context, int, string) (*Action, *Response, error)
|
||||
ChangeKernel(context.Context, int, int) (*Action, *Response, error)
|
||||
EnableIPv6(context.Context, int) (*Action, *Response, error)
|
||||
EnableIPv6ByTag(context.Context, string) (*Action, *Response, error)
|
||||
EnableIPv6ByTag(context.Context, string) ([]Action, *Response, error)
|
||||
EnablePrivateNetworking(context.Context, int) (*Action, *Response, error)
|
||||
EnablePrivateNetworkingByTag(context.Context, string) (*Action, *Response, error)
|
||||
Upgrade(context.Context, int) (*Action, *Response, error)
|
||||
EnablePrivateNetworkingByTag(context.Context, string) ([]Action, *Response, error)
|
||||
Get(context.Context, int, int) (*Action, *Response, error)
|
||||
GetByURI(context.Context, string) (*Action, *Response, error)
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ func (s *DropletActionsServiceOp) Shutdown(ctx context.Context, id int) (*Action
|
|||
}
|
||||
|
||||
// ShutdownByTag shuts down Droplets matched by a Tag.
|
||||
func (s *DropletActionsServiceOp) ShutdownByTag(ctx context.Context, tag string) (*Action, *Response, error) {
|
||||
func (s *DropletActionsServiceOp) ShutdownByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "shutdown"}
|
||||
return s.doActionByTag(ctx, tag, request)
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ func (s *DropletActionsServiceOp) PowerOff(ctx context.Context, id int) (*Action
|
|||
}
|
||||
|
||||
// PowerOffByTag powers off Droplets matched by a Tag.
|
||||
func (s *DropletActionsServiceOp) PowerOffByTag(ctx context.Context, tag string) (*Action, *Response, error) {
|
||||
func (s *DropletActionsServiceOp) PowerOffByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "power_off"}
|
||||
return s.doActionByTag(ctx, tag, request)
|
||||
}
|
||||
|
|
@ -83,7 +83,7 @@ func (s *DropletActionsServiceOp) PowerOn(ctx context.Context, id int) (*Action,
|
|||
}
|
||||
|
||||
// PowerOnByTag powers on Droplets matched by a Tag.
|
||||
func (s *DropletActionsServiceOp) PowerOnByTag(ctx context.Context, tag string) (*Action, *Response, error) {
|
||||
func (s *DropletActionsServiceOp) PowerOnByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "power_on"}
|
||||
return s.doActionByTag(ctx, tag, request)
|
||||
}
|
||||
|
|
@ -95,7 +95,7 @@ func (s *DropletActionsServiceOp) PowerCycle(ctx context.Context, id int) (*Acti
|
|||
}
|
||||
|
||||
// PowerCycleByTag power cycles Droplets matched by a Tag.
|
||||
func (s *DropletActionsServiceOp) PowerCycleByTag(ctx context.Context, tag string) (*Action, *Response, error) {
|
||||
func (s *DropletActionsServiceOp) PowerCycleByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "power_cycle"}
|
||||
return s.doActionByTag(ctx, tag, request)
|
||||
}
|
||||
|
|
@ -148,7 +148,7 @@ func (s *DropletActionsServiceOp) Snapshot(ctx context.Context, id int, name str
|
|||
}
|
||||
|
||||
// SnapshotByTag snapshots Droplets matched by a Tag.
|
||||
func (s *DropletActionsServiceOp) SnapshotByTag(ctx context.Context, tag string, name string) (*Action, *Response, error) {
|
||||
func (s *DropletActionsServiceOp) SnapshotByTag(ctx context.Context, tag string, name string) ([]Action, *Response, error) {
|
||||
requestType := "snapshot"
|
||||
request := &ActionRequest{
|
||||
"type": requestType,
|
||||
|
|
@ -164,7 +164,7 @@ func (s *DropletActionsServiceOp) EnableBackups(ctx context.Context, id int) (*A
|
|||
}
|
||||
|
||||
// EnableBackupsByTag enables backups for Droplets matched by a Tag.
|
||||
func (s *DropletActionsServiceOp) EnableBackupsByTag(ctx context.Context, tag string) (*Action, *Response, error) {
|
||||
func (s *DropletActionsServiceOp) EnableBackupsByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "enable_backups"}
|
||||
return s.doActionByTag(ctx, tag, request)
|
||||
}
|
||||
|
|
@ -176,7 +176,7 @@ func (s *DropletActionsServiceOp) DisableBackups(ctx context.Context, id int) (*
|
|||
}
|
||||
|
||||
// DisableBackupsByTag disables backups for Droplet matched by a Tag.
|
||||
func (s *DropletActionsServiceOp) DisableBackupsByTag(ctx context.Context, tag string) (*Action, *Response, error) {
|
||||
func (s *DropletActionsServiceOp) DisableBackupsByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "disable_backups"}
|
||||
return s.doActionByTag(ctx, tag, request)
|
||||
}
|
||||
|
|
@ -212,7 +212,7 @@ func (s *DropletActionsServiceOp) EnableIPv6(ctx context.Context, id int) (*Acti
|
|||
}
|
||||
|
||||
// EnableIPv6ByTag enables IPv6 for Droplets matched by a Tag.
|
||||
func (s *DropletActionsServiceOp) EnableIPv6ByTag(ctx context.Context, tag string) (*Action, *Response, error) {
|
||||
func (s *DropletActionsServiceOp) EnableIPv6ByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "enable_ipv6"}
|
||||
return s.doActionByTag(ctx, tag, request)
|
||||
}
|
||||
|
|
@ -224,17 +224,11 @@ func (s *DropletActionsServiceOp) EnablePrivateNetworking(ctx context.Context, i
|
|||
}
|
||||
|
||||
// EnablePrivateNetworkingByTag enables private networking for Droplets matched by a Tag.
|
||||
func (s *DropletActionsServiceOp) EnablePrivateNetworkingByTag(ctx context.Context, tag string) (*Action, *Response, error) {
|
||||
func (s *DropletActionsServiceOp) EnablePrivateNetworkingByTag(ctx context.Context, tag string) ([]Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "enable_private_networking"}
|
||||
return s.doActionByTag(ctx, tag, request)
|
||||
}
|
||||
|
||||
// Upgrade a Droplet.
|
||||
func (s *DropletActionsServiceOp) Upgrade(ctx context.Context, id int) (*Action, *Response, error) {
|
||||
request := &ActionRequest{"type": "upgrade"}
|
||||
return s.doAction(ctx, id, request)
|
||||
}
|
||||
|
||||
func (s *DropletActionsServiceOp) doAction(ctx context.Context, id int, request *ActionRequest) (*Action, *Response, error) {
|
||||
if id < 1 {
|
||||
return nil, nil, NewArgError("id", "cannot be less than 1")
|
||||
|
|
@ -246,13 +240,13 @@ func (s *DropletActionsServiceOp) doAction(ctx context.Context, id int, request
|
|||
|
||||
path := dropletActionPath(id)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "POST", path, request)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, path, request)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -260,7 +254,7 @@ func (s *DropletActionsServiceOp) doAction(ctx context.Context, id int, request
|
|||
return root.Event, resp, err
|
||||
}
|
||||
|
||||
func (s *DropletActionsServiceOp) doActionByTag(ctx context.Context, tag string, request *ActionRequest) (*Action, *Response, error) {
|
||||
func (s *DropletActionsServiceOp) doActionByTag(ctx context.Context, tag string, request *ActionRequest) ([]Action, *Response, error) {
|
||||
if tag == "" {
|
||||
return nil, nil, NewArgError("tag", "cannot be empty")
|
||||
}
|
||||
|
|
@ -271,18 +265,18 @@ func (s *DropletActionsServiceOp) doActionByTag(ctx context.Context, tag string,
|
|||
|
||||
path := dropletActionPathByTag(tag)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "POST", path, request)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, path, request)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
root := new(actionsRoot)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Event, resp, err
|
||||
return root.Actions, resp, err
|
||||
}
|
||||
|
||||
// Get an action for a particular Droplet by id.
|
||||
|
|
@ -311,13 +305,13 @@ func (s *DropletActionsServiceOp) GetByURI(ctx context.Context, rawurl string) (
|
|||
}
|
||||
|
||||
func (s *DropletActionsServiceOp) get(ctx context.Context, path string) (*Action, *Response, error) {
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
|
|||
45
vendor/github.com/digitalocean/godo/droplets.go
generated
vendored
45
vendor/github.com/digitalocean/godo/droplets.go
generated
vendored
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const dropletBasePath = "v2/droplets"
|
||||
|
|
@ -124,6 +125,10 @@ func (d Droplet) String() string {
|
|||
return Stringify(d)
|
||||
}
|
||||
|
||||
func (d Droplet) URN() string {
|
||||
return ToURN("Droplet", d.ID)
|
||||
}
|
||||
|
||||
// DropletRoot represents a Droplet root
|
||||
type dropletRoot struct {
|
||||
Droplet *Droplet `json:"droplet"`
|
||||
|
|
@ -274,13 +279,13 @@ func (n NetworkV6) String() string {
|
|||
|
||||
// Performs a list request given a path.
|
||||
func (s *DropletsServiceOp) list(ctx context.Context, path string) ([]Droplet, *Response, error) {
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(dropletsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -321,13 +326,13 @@ func (s *DropletsServiceOp) Get(ctx context.Context, dropletID int) (*Droplet, *
|
|||
|
||||
path := fmt.Sprintf("%s/%d", dropletBasePath, dropletID)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(dropletRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -343,13 +348,13 @@ func (s *DropletsServiceOp) Create(ctx context.Context, createRequest *DropletCr
|
|||
|
||||
path := dropletBasePath
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "POST", path, createRequest)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, path, createRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(dropletRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -368,13 +373,13 @@ func (s *DropletsServiceOp) CreateMultiple(ctx context.Context, createRequest *D
|
|||
|
||||
path := dropletBasePath
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "POST", path, createRequest)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, path, createRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(dropletsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -387,12 +392,12 @@ func (s *DropletsServiceOp) CreateMultiple(ctx context.Context, createRequest *D
|
|||
|
||||
// Performs a delete request given a path
|
||||
func (s *DropletsServiceOp) delete(ctx context.Context, path string) (*Response, error) {
|
||||
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
resp, err := s.client.Do(ctx, req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
|
@ -431,13 +436,13 @@ func (s *DropletsServiceOp) Kernels(ctx context.Context, dropletID int, opt *Lis
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(kernelsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if l := root.Links; l != nil {
|
||||
resp.Links = l
|
||||
}
|
||||
|
|
@ -457,13 +462,13 @@ func (s *DropletsServiceOp) Actions(ctx context.Context, dropletID int, opt *Lis
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -486,13 +491,13 @@ func (s *DropletsServiceOp) Backups(ctx context.Context, dropletID int, opt *Lis
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(backupsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -515,13 +520,13 @@ func (s *DropletsServiceOp) Snapshots(ctx context.Context, dropletID int, opt *L
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(dropletSnapshotsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -540,13 +545,13 @@ func (s *DropletsServiceOp) Neighbors(ctx context.Context, dropletID int) ([]Dro
|
|||
|
||||
path := fmt.Sprintf("%s/%d/neighbors", dropletBasePath, dropletID)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(dropletsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
|
|||
267
vendor/github.com/digitalocean/godo/firewalls.go
generated
vendored
Normal file
267
vendor/github.com/digitalocean/godo/firewalls.go
generated
vendored
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
package godo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const firewallsBasePath = "/v2/firewalls"
|
||||
|
||||
// FirewallsService is an interface for managing Firewalls with the DigitalOcean API.
|
||||
// See: https://developers.digitalocean.com/documentation/documentation/v2/#firewalls
|
||||
type FirewallsService interface {
|
||||
Get(context.Context, string) (*Firewall, *Response, error)
|
||||
Create(context.Context, *FirewallRequest) (*Firewall, *Response, error)
|
||||
Update(context.Context, string, *FirewallRequest) (*Firewall, *Response, error)
|
||||
Delete(context.Context, string) (*Response, error)
|
||||
List(context.Context, *ListOptions) ([]Firewall, *Response, error)
|
||||
ListByDroplet(context.Context, int, *ListOptions) ([]Firewall, *Response, error)
|
||||
AddDroplets(context.Context, string, ...int) (*Response, error)
|
||||
RemoveDroplets(context.Context, string, ...int) (*Response, error)
|
||||
AddTags(context.Context, string, ...string) (*Response, error)
|
||||
RemoveTags(context.Context, string, ...string) (*Response, error)
|
||||
AddRules(context.Context, string, *FirewallRulesRequest) (*Response, error)
|
||||
RemoveRules(context.Context, string, *FirewallRulesRequest) (*Response, error)
|
||||
}
|
||||
|
||||
// FirewallsServiceOp handles communication with Firewalls methods of the DigitalOcean API.
|
||||
type FirewallsServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Firewall represents a DigitalOcean Firewall configuration.
|
||||
type Firewall struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
InboundRules []InboundRule `json:"inbound_rules"`
|
||||
OutboundRules []OutboundRule `json:"outbound_rules"`
|
||||
DropletIDs []int `json:"droplet_ids"`
|
||||
Tags []string `json:"tags"`
|
||||
Created string `json:"created_at"`
|
||||
PendingChanges []PendingChange `json:"pending_changes"`
|
||||
}
|
||||
|
||||
// String creates a human-readable description of a Firewall.
|
||||
func (fw Firewall) String() string {
|
||||
return Stringify(fw)
|
||||
}
|
||||
|
||||
func (fw Firewall) URN() string {
|
||||
return ToURN("Firewall", fw.ID)
|
||||
}
|
||||
|
||||
// FirewallRequest represents the configuration to be applied to an existing or a new Firewall.
|
||||
type FirewallRequest struct {
|
||||
Name string `json:"name"`
|
||||
InboundRules []InboundRule `json:"inbound_rules"`
|
||||
OutboundRules []OutboundRule `json:"outbound_rules"`
|
||||
DropletIDs []int `json:"droplet_ids"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
// FirewallRulesRequest represents rules configuration to be applied to an existing Firewall.
|
||||
type FirewallRulesRequest struct {
|
||||
InboundRules []InboundRule `json:"inbound_rules"`
|
||||
OutboundRules []OutboundRule `json:"outbound_rules"`
|
||||
}
|
||||
|
||||
// InboundRule represents a DigitalOcean Firewall inbound rule.
|
||||
type InboundRule struct {
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
PortRange string `json:"ports,omitempty"`
|
||||
Sources *Sources `json:"sources"`
|
||||
}
|
||||
|
||||
// OutboundRule represents a DigitalOcean Firewall outbound rule.
|
||||
type OutboundRule struct {
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
PortRange string `json:"ports,omitempty"`
|
||||
Destinations *Destinations `json:"destinations"`
|
||||
}
|
||||
|
||||
// Sources represents a DigitalOcean Firewall InboundRule sources.
|
||||
type Sources struct {
|
||||
Addresses []string `json:"addresses,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
DropletIDs []int `json:"droplet_ids,omitempty"`
|
||||
LoadBalancerUIDs []string `json:"load_balancer_uids,omitempty"`
|
||||
}
|
||||
|
||||
// PendingChange represents a DigitalOcean Firewall status details.
|
||||
type PendingChange struct {
|
||||
DropletID int `json:"droplet_id,omitempty"`
|
||||
Removing bool `json:"removing,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// Destinations represents a DigitalOcean Firewall OutboundRule destinations.
|
||||
type Destinations struct {
|
||||
Addresses []string `json:"addresses,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
DropletIDs []int `json:"droplet_ids,omitempty"`
|
||||
LoadBalancerUIDs []string `json:"load_balancer_uids,omitempty"`
|
||||
}
|
||||
|
||||
var _ FirewallsService = &FirewallsServiceOp{}
|
||||
|
||||
// Get an existing Firewall by its identifier.
|
||||
func (fw *FirewallsServiceOp) Get(ctx context.Context, fID string) (*Firewall, *Response, error) {
|
||||
path := path.Join(firewallsBasePath, fID)
|
||||
|
||||
req, err := fw.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(firewallRoot)
|
||||
resp, err := fw.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Firewall, resp, err
|
||||
}
|
||||
|
||||
// Create a new Firewall with a given configuration.
|
||||
func (fw *FirewallsServiceOp) Create(ctx context.Context, fr *FirewallRequest) (*Firewall, *Response, error) {
|
||||
req, err := fw.client.NewRequest(ctx, http.MethodPost, firewallsBasePath, fr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(firewallRoot)
|
||||
resp, err := fw.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Firewall, resp, err
|
||||
}
|
||||
|
||||
// Update an existing Firewall with new configuration.
|
||||
func (fw *FirewallsServiceOp) Update(ctx context.Context, fID string, fr *FirewallRequest) (*Firewall, *Response, error) {
|
||||
path := path.Join(firewallsBasePath, fID)
|
||||
|
||||
req, err := fw.client.NewRequest(ctx, "PUT", path, fr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(firewallRoot)
|
||||
resp, err := fw.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Firewall, resp, err
|
||||
}
|
||||
|
||||
// Delete a Firewall by its identifier.
|
||||
func (fw *FirewallsServiceOp) Delete(ctx context.Context, fID string) (*Response, error) {
|
||||
path := path.Join(firewallsBasePath, fID)
|
||||
return fw.createAndDoReq(ctx, http.MethodDelete, path, nil)
|
||||
}
|
||||
|
||||
// List Firewalls.
|
||||
func (fw *FirewallsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Firewall, *Response, error) {
|
||||
path, err := addOptions(firewallsBasePath, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return fw.listHelper(ctx, path)
|
||||
}
|
||||
|
||||
// ListByDroplet Firewalls.
|
||||
func (fw *FirewallsServiceOp) ListByDroplet(ctx context.Context, dID int, opt *ListOptions) ([]Firewall, *Response, error) {
|
||||
basePath := path.Join(dropletBasePath, strconv.Itoa(dID), "firewalls")
|
||||
path, err := addOptions(basePath, opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return fw.listHelper(ctx, path)
|
||||
}
|
||||
|
||||
// AddDroplets to a Firewall.
|
||||
func (fw *FirewallsServiceOp) AddDroplets(ctx context.Context, fID string, dropletIDs ...int) (*Response, error) {
|
||||
path := path.Join(firewallsBasePath, fID, "droplets")
|
||||
return fw.createAndDoReq(ctx, http.MethodPost, path, &dropletsRequest{IDs: dropletIDs})
|
||||
}
|
||||
|
||||
// RemoveDroplets from a Firewall.
|
||||
func (fw *FirewallsServiceOp) RemoveDroplets(ctx context.Context, fID string, dropletIDs ...int) (*Response, error) {
|
||||
path := path.Join(firewallsBasePath, fID, "droplets")
|
||||
return fw.createAndDoReq(ctx, http.MethodDelete, path, &dropletsRequest{IDs: dropletIDs})
|
||||
}
|
||||
|
||||
// AddTags to a Firewall.
|
||||
func (fw *FirewallsServiceOp) AddTags(ctx context.Context, fID string, tags ...string) (*Response, error) {
|
||||
path := path.Join(firewallsBasePath, fID, "tags")
|
||||
return fw.createAndDoReq(ctx, http.MethodPost, path, &tagsRequest{Tags: tags})
|
||||
}
|
||||
|
||||
// RemoveTags from a Firewall.
|
||||
func (fw *FirewallsServiceOp) RemoveTags(ctx context.Context, fID string, tags ...string) (*Response, error) {
|
||||
path := path.Join(firewallsBasePath, fID, "tags")
|
||||
return fw.createAndDoReq(ctx, http.MethodDelete, path, &tagsRequest{Tags: tags})
|
||||
}
|
||||
|
||||
// AddRules to a Firewall.
|
||||
func (fw *FirewallsServiceOp) AddRules(ctx context.Context, fID string, rr *FirewallRulesRequest) (*Response, error) {
|
||||
path := path.Join(firewallsBasePath, fID, "rules")
|
||||
return fw.createAndDoReq(ctx, http.MethodPost, path, rr)
|
||||
}
|
||||
|
||||
// RemoveRules from a Firewall.
|
||||
func (fw *FirewallsServiceOp) RemoveRules(ctx context.Context, fID string, rr *FirewallRulesRequest) (*Response, error) {
|
||||
path := path.Join(firewallsBasePath, fID, "rules")
|
||||
return fw.createAndDoReq(ctx, http.MethodDelete, path, rr)
|
||||
}
|
||||
|
||||
type dropletsRequest struct {
|
||||
IDs []int `json:"droplet_ids"`
|
||||
}
|
||||
|
||||
type tagsRequest struct {
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
type firewallRoot struct {
|
||||
Firewall *Firewall `json:"firewall"`
|
||||
}
|
||||
|
||||
type firewallsRoot struct {
|
||||
Firewalls []Firewall `json:"firewalls"`
|
||||
Links *Links `json:"links"`
|
||||
}
|
||||
|
||||
func (fw *FirewallsServiceOp) createAndDoReq(ctx context.Context, method, path string, v interface{}) (*Response, error) {
|
||||
req, err := fw.client.NewRequest(ctx, method, path, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fw.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
func (fw *FirewallsServiceOp) listHelper(ctx context.Context, path string) ([]Firewall, *Response, error) {
|
||||
req, err := fw.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(firewallsRoot)
|
||||
resp, err := fw.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
if l := root.Links; l != nil {
|
||||
resp.Links = l
|
||||
}
|
||||
|
||||
return root.Firewalls, resp, err
|
||||
}
|
||||
21
vendor/github.com/digitalocean/godo/floating_ips.go
generated
vendored
21
vendor/github.com/digitalocean/godo/floating_ips.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package godo
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const floatingBasePath = "v2/floating_ips"
|
||||
|
|
@ -36,6 +37,10 @@ func (f FloatingIP) String() string {
|
|||
return Stringify(f)
|
||||
}
|
||||
|
||||
func (f FloatingIP) URN() string {
|
||||
return ToURN("FloatingIP", f.IP)
|
||||
}
|
||||
|
||||
type floatingIPsRoot struct {
|
||||
FloatingIPs []FloatingIP `json:"floating_ips"`
|
||||
Links *Links `json:"links"`
|
||||
|
|
@ -62,13 +67,13 @@ func (f *FloatingIPsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Fl
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := f.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := f.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(floatingIPsRoot)
|
||||
resp, err := f.client.Do(req, root)
|
||||
resp, err := f.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -83,13 +88,13 @@ func (f *FloatingIPsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Fl
|
|||
func (f *FloatingIPsServiceOp) Get(ctx context.Context, ip string) (*FloatingIP, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", floatingBasePath, ip)
|
||||
|
||||
req, err := f.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := f.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(floatingIPRoot)
|
||||
resp, err := f.client.Do(req, root)
|
||||
resp, err := f.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -102,13 +107,13 @@ func (f *FloatingIPsServiceOp) Get(ctx context.Context, ip string) (*FloatingIP,
|
|||
func (f *FloatingIPsServiceOp) Create(ctx context.Context, createRequest *FloatingIPCreateRequest) (*FloatingIP, *Response, error) {
|
||||
path := floatingBasePath
|
||||
|
||||
req, err := f.client.NewRequest(ctx, "POST", path, createRequest)
|
||||
req, err := f.client.NewRequest(ctx, http.MethodPost, path, createRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(floatingIPRoot)
|
||||
resp, err := f.client.Do(req, root)
|
||||
resp, err := f.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -123,12 +128,12 @@ func (f *FloatingIPsServiceOp) Create(ctx context.Context, createRequest *Floati
|
|||
func (f *FloatingIPsServiceOp) Delete(ctx context.Context, ip string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", floatingBasePath, ip)
|
||||
|
||||
req, err := f.client.NewRequest(ctx, "DELETE", path, nil)
|
||||
req, err := f.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := f.client.Do(req, nil)
|
||||
resp, err := f.client.Do(ctx, req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
|
|
|||
13
vendor/github.com/digitalocean/godo/floating_ips_actions.go
generated
vendored
13
vendor/github.com/digitalocean/godo/floating_ips_actions.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package godo
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// FloatingIPActionsService is an interface for interfacing with the
|
||||
|
|
@ -56,13 +57,13 @@ func (s *FloatingIPActionsServiceOp) List(ctx context.Context, ip string, opt *L
|
|||
func (s *FloatingIPActionsServiceOp) doAction(ctx context.Context, ip string, request *ActionRequest) (*Action, *Response, error) {
|
||||
path := floatingIPActionPath(ip)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "POST", path, request)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, path, request)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -71,13 +72,13 @@ func (s *FloatingIPActionsServiceOp) doAction(ctx context.Context, ip string, re
|
|||
}
|
||||
|
||||
func (s *FloatingIPActionsServiceOp) get(ctx context.Context, path string) (*Action, *Response, error) {
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -86,13 +87,13 @@ func (s *FloatingIPActionsServiceOp) get(ctx context.Context, path string) (*Act
|
|||
}
|
||||
|
||||
func (s *FloatingIPActionsServiceOp) list(ctx context.Context, path string) ([]Action, *Response, error) {
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
|
|||
40
vendor/github.com/digitalocean/godo/godo.go
generated
vendored
40
vendor/github.com/digitalocean/godo/godo.go
generated
vendored
|
|
@ -18,7 +18,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
libraryVersion = "1.0.0"
|
||||
libraryVersion = "1.6.0"
|
||||
defaultBaseURL = "https://api.digitalocean.com/"
|
||||
userAgent = "godo/" + libraryVersion
|
||||
mediaType = "application/json"
|
||||
|
|
@ -46,6 +46,7 @@ type Client struct {
|
|||
// Services used for communicating with the API
|
||||
Account AccountService
|
||||
Actions ActionsService
|
||||
CDNs CDNService
|
||||
Domains DomainsService
|
||||
Droplets DropletsService
|
||||
DropletActions DropletActionsService
|
||||
|
|
@ -62,6 +63,9 @@ type Client struct {
|
|||
Tags TagsService
|
||||
LoadBalancers LoadBalancersService
|
||||
Certificates CertificatesService
|
||||
Firewalls FirewallsService
|
||||
Projects ProjectsService
|
||||
Kubernetes KubernetesService
|
||||
|
||||
// Optional function called after every successful request made to the DO APIs
|
||||
onRequestCompleted RequestCompletionCallback
|
||||
|
|
@ -156,22 +160,26 @@ func NewClient(httpClient *http.Client) *Client {
|
|||
c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent}
|
||||
c.Account = &AccountServiceOp{client: c}
|
||||
c.Actions = &ActionsServiceOp{client: c}
|
||||
c.CDNs = &CDNServiceOp{client: c}
|
||||
c.Certificates = &CertificatesServiceOp{client: c}
|
||||
c.Domains = &DomainsServiceOp{client: c}
|
||||
c.Droplets = &DropletsServiceOp{client: c}
|
||||
c.DropletActions = &DropletActionsServiceOp{client: c}
|
||||
c.Firewalls = &FirewallsServiceOp{client: c}
|
||||
c.FloatingIPs = &FloatingIPsServiceOp{client: c}
|
||||
c.FloatingIPActions = &FloatingIPActionsServiceOp{client: c}
|
||||
c.Images = &ImagesServiceOp{client: c}
|
||||
c.ImageActions = &ImageActionsServiceOp{client: c}
|
||||
c.Keys = &KeysServiceOp{client: c}
|
||||
c.LoadBalancers = &LoadBalancersServiceOp{client: c}
|
||||
c.Projects = &ProjectsServiceOp{client: c}
|
||||
c.Regions = &RegionsServiceOp{client: c}
|
||||
c.Snapshots = &SnapshotsServiceOp{client: c}
|
||||
c.Sizes = &SizesServiceOp{client: c}
|
||||
c.Snapshots = &SnapshotsServiceOp{client: c}
|
||||
c.Storage = &StorageServiceOp{client: c}
|
||||
c.StorageActions = &StorageActionsServiceOp{client: c}
|
||||
c.Tags = &TagsServiceOp{client: c}
|
||||
c.LoadBalancers = &LoadBalancersServiceOp{client: c}
|
||||
c.Certificates = &CertificatesServiceOp{client: c}
|
||||
c.Kubernetes = &KubernetesServiceOp{client: c}
|
||||
|
||||
return c
|
||||
}
|
||||
|
|
@ -207,7 +215,7 @@ func SetBaseURL(bu string) ClientOpt {
|
|||
// SetUserAgent is a client option for setting the user agent.
|
||||
func SetUserAgent(ua string) ClientOpt {
|
||||
return func(c *Client) error {
|
||||
c.UserAgent = fmt.Sprintf("%s+%s", ua, c.UserAgent)
|
||||
c.UserAgent = fmt.Sprintf("%s %s", ua, c.UserAgent)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -236,7 +244,6 @@ func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body int
|
|||
return nil, err
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
req.Header.Add("Content-Type", mediaType)
|
||||
req.Header.Add("Accept", mediaType)
|
||||
req.Header.Add("User-Agent", c.UserAgent)
|
||||
|
|
@ -293,8 +300,8 @@ func (r *Response) populateRate() {
|
|||
// Do sends an API request and returns the API response. The API response is JSON decoded and stored in the value
|
||||
// pointed to by v, or returned as an error if an API error has occurred. If v implements the io.Writer interface,
|
||||
// the raw response will be written to v, without attempting to decode it.
|
||||
func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
|
||||
resp, err := c.client.Do(req)
|
||||
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
|
||||
resp, err := DoRequestWithClient(ctx, c.client, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -332,6 +339,21 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
|
|||
|
||||
return response, err
|
||||
}
|
||||
|
||||
// DoRequest submits an HTTP request.
|
||||
func DoRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
|
||||
return DoRequestWithClient(ctx, http.DefaultClient, req)
|
||||
}
|
||||
|
||||
// DoRequestWithClient submits an HTTP request using the specified client.
|
||||
func DoRequestWithClient(
|
||||
ctx context.Context,
|
||||
client *http.Client,
|
||||
req *http.Request) (*http.Response, error) {
|
||||
req = req.WithContext(ctx)
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
func (r *ErrorResponse) Error() string {
|
||||
if r.RequestID != "" {
|
||||
return fmt.Sprintf("%v %v: %d (request %q) %v",
|
||||
|
|
@ -354,7 +376,7 @@ func CheckResponse(r *http.Response) error {
|
|||
if err == nil && len(data) > 0 {
|
||||
err := json.Unmarshal(data, errorResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
errorResponse.Message = string(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
13
vendor/github.com/digitalocean/godo/image_actions.go
generated
vendored
13
vendor/github.com/digitalocean/godo/image_actions.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package godo
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ImageActionsService is an interface for interfacing with the image actions
|
||||
|
|
@ -34,13 +35,13 @@ func (i *ImageActionsServiceOp) Transfer(ctx context.Context, imageID int, trans
|
|||
|
||||
path := fmt.Sprintf("v2/images/%d/actions", imageID)
|
||||
|
||||
req, err := i.client.NewRequest(ctx, "POST", path, transferRequest)
|
||||
req, err := i.client.NewRequest(ctx, http.MethodPost, path, transferRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionRoot)
|
||||
resp, err := i.client.Do(req, root)
|
||||
resp, err := i.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -60,13 +61,13 @@ func (i *ImageActionsServiceOp) Convert(ctx context.Context, imageID int) (*Acti
|
|||
"type": "convert",
|
||||
}
|
||||
|
||||
req, err := i.client.NewRequest(ctx, "POST", path, convertRequest)
|
||||
req, err := i.client.NewRequest(ctx, http.MethodPost, path, convertRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionRoot)
|
||||
resp, err := i.client.Do(req, root)
|
||||
resp, err := i.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -86,13 +87,13 @@ func (i *ImageActionsServiceOp) Get(ctx context.Context, imageID, actionID int)
|
|||
|
||||
path := fmt.Sprintf("v2/images/%d/actions/%d", imageID, actionID)
|
||||
|
||||
req, err := i.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := i.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionRoot)
|
||||
resp, err := i.client.Do(req, root)
|
||||
resp, err := i.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
|
|||
78
vendor/github.com/digitalocean/godo/images.go
generated
vendored
78
vendor/github.com/digitalocean/godo/images.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package godo
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const imageBasePath = "v2/images"
|
||||
|
|
@ -15,8 +16,10 @@ type ImagesService interface {
|
|||
ListDistribution(ctx context.Context, opt *ListOptions) ([]Image, *Response, error)
|
||||
ListApplication(ctx context.Context, opt *ListOptions) ([]Image, *Response, error)
|
||||
ListUser(ctx context.Context, opt *ListOptions) ([]Image, *Response, error)
|
||||
ListByTag(ctx context.Context, tag string, opt *ListOptions) ([]Image, *Response, error)
|
||||
GetByID(context.Context, int) (*Image, *Response, error)
|
||||
GetBySlug(context.Context, string) (*Image, *Response, error)
|
||||
Create(context.Context, *CustomImageCreateRequest) (*Image, *Response, error)
|
||||
Update(context.Context, int, *ImageUpdateRequest) (*Image, *Response, error)
|
||||
Delete(context.Context, int) (*Response, error)
|
||||
}
|
||||
|
|
@ -31,15 +34,20 @@ var _ ImagesService = &ImagesServiceOp{}
|
|||
|
||||
// Image represents a DigitalOcean Image
|
||||
type Image struct {
|
||||
ID int `json:"id,float64,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Distribution string `json:"distribution,omitempty"`
|
||||
Slug string `json:"slug,omitempty"`
|
||||
Public bool `json:"public,omitempty"`
|
||||
Regions []string `json:"regions,omitempty"`
|
||||
MinDiskSize int `json:"min_disk_size,omitempty"`
|
||||
Created string `json:"created_at,omitempty"`
|
||||
ID int `json:"id,float64,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Distribution string `json:"distribution,omitempty"`
|
||||
Slug string `json:"slug,omitempty"`
|
||||
Public bool `json:"public,omitempty"`
|
||||
Regions []string `json:"regions,omitempty"`
|
||||
MinDiskSize int `json:"min_disk_size,omitempty"`
|
||||
SizeGigaBytes float64 `json:"size_gigabytes,omitempty"`
|
||||
Created string `json:"created_at,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
}
|
||||
|
||||
// ImageUpdateRequest represents a request to update an image.
|
||||
|
|
@ -47,6 +55,16 @@ type ImageUpdateRequest struct {
|
|||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// CustomImageCreateRequest represents a request to create a custom image.
|
||||
type CustomImageCreateRequest struct {
|
||||
Name string `json:"name"`
|
||||
Url string `json:"url"`
|
||||
Region string `json:"region"`
|
||||
Distribution string `json:"distribution,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
type imageRoot struct {
|
||||
Image *Image
|
||||
}
|
||||
|
|
@ -59,6 +77,7 @@ type imagesRoot struct {
|
|||
type listImageOptions struct {
|
||||
Private bool `url:"private,omitempty"`
|
||||
Type string `url:"type,omitempty"`
|
||||
Tag string `url:"tag_name,omitempty"`
|
||||
}
|
||||
|
||||
func (i Image) String() string {
|
||||
|
|
@ -88,6 +107,12 @@ func (s *ImagesServiceOp) ListUser(ctx context.Context, opt *ListOptions) ([]Ima
|
|||
return s.list(ctx, opt, &listOpt)
|
||||
}
|
||||
|
||||
// ListByTag lists all images with a specific tag applied.
|
||||
func (s *ImagesServiceOp) ListByTag(ctx context.Context, tag string, opt *ListOptions) ([]Image, *Response, error) {
|
||||
listOpt := listImageOptions{Tag: tag}
|
||||
return s.list(ctx, opt, &listOpt)
|
||||
}
|
||||
|
||||
// GetByID retrieves an image by id.
|
||||
func (s *ImagesServiceOp) GetByID(ctx context.Context, imageID int) (*Image, *Response, error) {
|
||||
if imageID < 1 {
|
||||
|
|
@ -106,6 +131,25 @@ func (s *ImagesServiceOp) GetBySlug(ctx context.Context, slug string) (*Image, *
|
|||
return s.get(ctx, interface{}(slug))
|
||||
}
|
||||
|
||||
func (s *ImagesServiceOp) Create(ctx context.Context, createRequest *CustomImageCreateRequest) (*Image, *Response, error) {
|
||||
if createRequest == nil {
|
||||
return nil, nil, NewArgError("createRequest", "cannot be nil")
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, imageBasePath, createRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(imageRoot)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Image, resp, err
|
||||
}
|
||||
|
||||
// Update an image name.
|
||||
func (s *ImagesServiceOp) Update(ctx context.Context, imageID int, updateRequest *ImageUpdateRequest) (*Image, *Response, error) {
|
||||
if imageID < 1 {
|
||||
|
|
@ -117,13 +161,13 @@ func (s *ImagesServiceOp) Update(ctx context.Context, imageID int, updateRequest
|
|||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%d", imageBasePath, imageID)
|
||||
req, err := s.client.NewRequest(ctx, "PUT", path, updateRequest)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPut, path, updateRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(imageRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -139,12 +183,12 @@ func (s *ImagesServiceOp) Delete(ctx context.Context, imageID int) (*Response, e
|
|||
|
||||
path := fmt.Sprintf("%s/%d", imageBasePath, imageID)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
resp, err := s.client.Do(ctx, req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
|
@ -153,13 +197,13 @@ func (s *ImagesServiceOp) Delete(ctx context.Context, imageID int) (*Response, e
|
|||
func (s *ImagesServiceOp) get(ctx context.Context, ID interface{}) (*Image, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%v", imageBasePath, ID)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(imageRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -179,13 +223,13 @@ func (s *ImagesServiceOp) list(ctx context.Context, opt *ListOptions, listOpt *l
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(imagesRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
|
|||
21
vendor/github.com/digitalocean/godo/keys.go
generated
vendored
21
vendor/github.com/digitalocean/godo/keys.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package godo
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const keysBasePath = "v2/account/keys"
|
||||
|
|
@ -69,13 +70,13 @@ func (s *KeysServiceOp) List(ctx context.Context, opt *ListOptions) ([]Key, *Res
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(keysRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -88,13 +89,13 @@ func (s *KeysServiceOp) List(ctx context.Context, opt *ListOptions) ([]Key, *Res
|
|||
|
||||
// Performs a get given a path
|
||||
func (s *KeysServiceOp) get(ctx context.Context, path string) (*Key, *Response, error) {
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(keyRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -128,13 +129,13 @@ func (s *KeysServiceOp) Create(ctx context.Context, createRequest *KeyCreateRequ
|
|||
return nil, nil, NewArgError("createRequest", "cannot be nil")
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "POST", keysBasePath, createRequest)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, keysBasePath, createRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(keyRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -159,7 +160,7 @@ func (s *KeysServiceOp) UpdateByID(ctx context.Context, keyID int, updateRequest
|
|||
}
|
||||
|
||||
root := new(keyRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -184,7 +185,7 @@ func (s *KeysServiceOp) UpdateByFingerprint(ctx context.Context, fingerprint str
|
|||
}
|
||||
|
||||
root := new(keyRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -194,12 +195,12 @@ func (s *KeysServiceOp) UpdateByFingerprint(ctx context.Context, fingerprint str
|
|||
|
||||
// Delete key using a path
|
||||
func (s *KeysServiceOp) delete(ctx context.Context, path string) (*Response, error) {
|
||||
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
resp, err := s.client.Do(ctx, req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
|
|
|||
380
vendor/github.com/digitalocean/godo/kubernetes.go
generated
vendored
Normal file
380
vendor/github.com/digitalocean/godo/kubernetes.go
generated
vendored
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
package godo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
kubernetesBasePath = "/v2/kubernetes"
|
||||
kubernetesClustersPath = kubernetesBasePath + "/clusters"
|
||||
kubernetesOptionsPath = kubernetesBasePath + "/options"
|
||||
)
|
||||
|
||||
// KubernetesService is an interface for interfacing with the kubernetes endpoints
|
||||
// of the DigitalOcean API.
|
||||
// See: https://developers.digitalocean.com/documentation/v2#kubernetes
|
||||
type KubernetesService interface {
|
||||
Create(context.Context, *KubernetesClusterCreateRequest) (*KubernetesCluster, *Response, error)
|
||||
Get(context.Context, string) (*KubernetesCluster, *Response, error)
|
||||
GetKubeConfig(context.Context, string) (*KubernetesClusterConfig, *Response, error)
|
||||
List(context.Context, *ListOptions) ([]*KubernetesCluster, *Response, error)
|
||||
Update(context.Context, string, *KubernetesClusterUpdateRequest) (*KubernetesCluster, *Response, error)
|
||||
Delete(context.Context, string) (*Response, error)
|
||||
|
||||
CreateNodePool(ctx context.Context, clusterID string, req *KubernetesNodePoolCreateRequest) (*KubernetesNodePool, *Response, error)
|
||||
GetNodePool(ctx context.Context, clusterID, poolID string) (*KubernetesNodePool, *Response, error)
|
||||
ListNodePools(ctx context.Context, clusterID string, opts *ListOptions) ([]*KubernetesNodePool, *Response, error)
|
||||
UpdateNodePool(ctx context.Context, clusterID, poolID string, req *KubernetesNodePoolUpdateRequest) (*KubernetesNodePool, *Response, error)
|
||||
RecycleNodePoolNodes(ctx context.Context, clusterID, poolID string, req *KubernetesNodePoolRecycleNodesRequest) (*Response, error)
|
||||
DeleteNodePool(ctx context.Context, clusterID, poolID string) (*Response, error)
|
||||
|
||||
GetOptions(context.Context) (*KubernetesOptions, *Response, error)
|
||||
}
|
||||
|
||||
var _ KubernetesService = &KubernetesServiceOp{}
|
||||
|
||||
// KubernetesServiceOp handles communication with Kubernetes methods of the DigitalOcean API.
|
||||
type KubernetesServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// KubernetesClusterCreateRequest represents a request to create a Kubernetes cluster.
|
||||
type KubernetesClusterCreateRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
RegionSlug string `json:"region,omitempty"`
|
||||
VersionSlug string `json:"version,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
|
||||
NodePools []*KubernetesNodePoolCreateRequest `json:"node_pools,omitempty"`
|
||||
}
|
||||
|
||||
// KubernetesClusterUpdateRequest represents a request to update a Kubernetes cluster.
|
||||
type KubernetesClusterUpdateRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
// KubernetesNodePoolCreateRequest represents a request to create a node pool for a
|
||||
// Kubernetes cluster.
|
||||
type KubernetesNodePoolCreateRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Size string `json:"size,omitempty"`
|
||||
Count int `json:"count,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
// KubernetesNodePoolUpdateRequest represents a request to update a node pool in a
|
||||
// Kubernetes cluster.
|
||||
type KubernetesNodePoolUpdateRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Count int `json:"count,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
// KubernetesNodePoolRecycleNodesRequest represents a request to recycle a set of
|
||||
// nodes in a node pool. This will recycle the nodes by ID.
|
||||
type KubernetesNodePoolRecycleNodesRequest struct {
|
||||
Nodes []string `json:"nodes,omitempty"`
|
||||
}
|
||||
|
||||
// KubernetesCluster represents a Kubernetes cluster.
|
||||
type KubernetesCluster struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
RegionSlug string `json:"region,omitempty"`
|
||||
VersionSlug string `json:"version,omitempty"`
|
||||
ClusterSubnet string `json:"cluster_subnet,omitempty"`
|
||||
ServiceSubnet string `json:"service_subnet,omitempty"`
|
||||
IPv4 string `json:"ipv4,omitempty"`
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
|
||||
NodePools []*KubernetesNodePool `json:"node_pools,omitempty"`
|
||||
|
||||
Status *KubernetesClusterStatus `json:"status,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
// KubernetesClusterStatus describes the status of a cluster.
|
||||
type KubernetesClusterStatus struct {
|
||||
State string `json:"state,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// KubernetesNodePool represents a node pool in a Kubernetes cluster.
|
||||
type KubernetesNodePool struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Size string `json:"size,omitempty"`
|
||||
Count int `json:"count,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
|
||||
Nodes []*KubernetesNode `json:"nodes,omitempty"`
|
||||
}
|
||||
|
||||
// KubernetesNode represents a Node in a node pool in a Kubernetes cluster.
|
||||
type KubernetesNode struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Status *KubernetesNodeStatus `json:"status,omitempty"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
// KubernetesNodeStatus represents the status of a particular Node in a Kubernetes cluster.
|
||||
type KubernetesNodeStatus struct {
|
||||
State string `json:"state,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// KubernetesOptions represents options available for creating Kubernetes clusters.
|
||||
type KubernetesOptions struct {
|
||||
Versions []*KubernetesVersion `json:"versions,omitempty"`
|
||||
}
|
||||
|
||||
// KubernetesVersion is a DigitalOcean Kubernetes release.
|
||||
type KubernetesVersion struct {
|
||||
Slug string `json:"slug,omitempty"`
|
||||
KubernetesVersion string `json:"kubernetes_version,omitempty"`
|
||||
}
|
||||
|
||||
type kubernetesClustersRoot struct {
|
||||
Clusters []*KubernetesCluster `json:"kubernetes_clusters,omitempty"`
|
||||
Links *Links `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
type kubernetesClusterRoot struct {
|
||||
Cluster *KubernetesCluster `json:"kubernetes_cluster,omitempty"`
|
||||
}
|
||||
|
||||
type kubernetesNodePoolRoot struct {
|
||||
NodePool *KubernetesNodePool `json:"node_pool,omitempty"`
|
||||
}
|
||||
|
||||
type kubernetesNodePoolsRoot struct {
|
||||
NodePools []*KubernetesNodePool `json:"node_pools,omitempty"`
|
||||
Links *Links `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
// Get retrieves the details of a Kubernetes cluster.
|
||||
func (svc *KubernetesServiceOp) Get(ctx context.Context, clusterID string) (*KubernetesCluster, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", kubernetesClustersPath, clusterID)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
root := new(kubernetesClusterRoot)
|
||||
resp, err := svc.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return root.Cluster, resp, nil
|
||||
}
|
||||
|
||||
// Create creates a Kubernetes cluster.
|
||||
func (svc *KubernetesServiceOp) Create(ctx context.Context, create *KubernetesClusterCreateRequest) (*KubernetesCluster, *Response, error) {
|
||||
path := kubernetesClustersPath
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodPost, path, create)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
root := new(kubernetesClusterRoot)
|
||||
resp, err := svc.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return root.Cluster, resp, nil
|
||||
}
|
||||
|
||||
// Delete deletes a Kubernetes cluster. There is no way to recover a cluster
|
||||
// once it has been destroyed.
|
||||
func (svc *KubernetesServiceOp) Delete(ctx context.Context, clusterID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", kubernetesClustersPath, clusterID)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := svc.client.Do(ctx, req, nil)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// List returns a list of the Kubernetes clusters visible with the caller's API token.
|
||||
func (svc *KubernetesServiceOp) List(ctx context.Context, opts *ListOptions) ([]*KubernetesCluster, *Response, error) {
|
||||
path := kubernetesClustersPath
|
||||
path, err := addOptions(path, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
root := new(kubernetesClustersRoot)
|
||||
resp, err := svc.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return root.Clusters, resp, nil
|
||||
}
|
||||
|
||||
// KubernetesClusterConfig is the content of a Kubernetes config file, which can be
|
||||
// used to interact with your Kubernetes cluster using `kubectl`.
|
||||
// See: https://kubernetes.io/docs/tasks/tools/install-kubectl/
|
||||
type KubernetesClusterConfig struct {
|
||||
KubeconfigYAML []byte
|
||||
}
|
||||
|
||||
// GetKubeConfig returns a Kubernetes config file for the specified cluster.
|
||||
func (svc *KubernetesServiceOp) GetKubeConfig(ctx context.Context, clusterID string) (*KubernetesClusterConfig, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/kubeconfig", kubernetesClustersPath, clusterID)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
configBytes := bytes.NewBuffer(nil)
|
||||
resp, err := svc.client.Do(ctx, req, configBytes)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
res := &KubernetesClusterConfig{
|
||||
KubeconfigYAML: configBytes.Bytes(),
|
||||
}
|
||||
return res, resp, nil
|
||||
}
|
||||
|
||||
// Update updates a Kubernetes cluster's properties.
|
||||
func (svc *KubernetesServiceOp) Update(ctx context.Context, clusterID string, update *KubernetesClusterUpdateRequest) (*KubernetesCluster, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", kubernetesClustersPath, clusterID)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodPut, path, update)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
root := new(kubernetesClusterRoot)
|
||||
resp, err := svc.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return root.Cluster, resp, nil
|
||||
}
|
||||
|
||||
// CreateNodePool creates a new node pool in an existing Kubernetes cluster.
|
||||
func (svc *KubernetesServiceOp) CreateNodePool(ctx context.Context, clusterID string, create *KubernetesNodePoolCreateRequest) (*KubernetesNodePool, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/node_pools", kubernetesClustersPath, clusterID)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodPost, path, create)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
root := new(kubernetesNodePoolRoot)
|
||||
resp, err := svc.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return root.NodePool, resp, nil
|
||||
}
|
||||
|
||||
// GetNodePool retrieves an existing node pool in a Kubernetes cluster.
|
||||
func (svc *KubernetesServiceOp) GetNodePool(ctx context.Context, clusterID, poolID string) (*KubernetesNodePool, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/node_pools/%s", kubernetesClustersPath, clusterID, poolID)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
root := new(kubernetesNodePoolRoot)
|
||||
resp, err := svc.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return root.NodePool, resp, nil
|
||||
}
|
||||
|
||||
// ListNodePools lists all the node pools found in a Kubernetes cluster.
|
||||
func (svc *KubernetesServiceOp) ListNodePools(ctx context.Context, clusterID string, opts *ListOptions) ([]*KubernetesNodePool, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/node_pools", kubernetesClustersPath, clusterID)
|
||||
path, err := addOptions(path, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
root := new(kubernetesNodePoolsRoot)
|
||||
resp, err := svc.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return root.NodePools, resp, nil
|
||||
}
|
||||
|
||||
// UpdateNodePool updates the details of an existing node pool.
|
||||
func (svc *KubernetesServiceOp) UpdateNodePool(ctx context.Context, clusterID, poolID string, update *KubernetesNodePoolUpdateRequest) (*KubernetesNodePool, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/node_pools/%s", kubernetesClustersPath, clusterID, poolID)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodPut, path, update)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
root := new(kubernetesNodePoolRoot)
|
||||
resp, err := svc.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return root.NodePool, resp, nil
|
||||
}
|
||||
|
||||
// RecycleNodePoolNodes schedules nodes in a node pool for recycling.
|
||||
func (svc *KubernetesServiceOp) RecycleNodePoolNodes(ctx context.Context, clusterID, poolID string, recycle *KubernetesNodePoolRecycleNodesRequest) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/node_pools/%s/recycle", kubernetesClustersPath, clusterID, poolID)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodPost, path, recycle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := svc.client.Do(ctx, req, nil)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteNodePool deletes a node pool, and subsequently all the nodes in that pool.
|
||||
func (svc *KubernetesServiceOp) DeleteNodePool(ctx context.Context, clusterID, poolID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/node_pools/%s", kubernetesClustersPath, clusterID, poolID)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := svc.client.Do(ctx, req, nil)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type kubernetesOptionsRoot struct {
|
||||
Options *KubernetesOptions `json:"options,omitempty"`
|
||||
Links *Links `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
// GetOptions returns options about the Kubernetes service, such as the versions available for
|
||||
// cluster creation.
|
||||
func (svc *KubernetesServiceOp) GetOptions(ctx context.Context) (*KubernetesOptions, *Response, error) {
|
||||
path := kubernetesOptionsPath
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
root := new(kubernetesOptionsRoot)
|
||||
resp, err := svc.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return root.Options, resp, nil
|
||||
}
|
||||
66
vendor/github.com/digitalocean/godo/load_balancers.go
generated
vendored
66
vendor/github.com/digitalocean/godo/load_balancers.go
generated
vendored
|
|
@ -3,10 +3,12 @@ package godo
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const loadBalancersBasePath = "/v2/load_balancers"
|
||||
const forwardingRulesPath = "forwarding_rules"
|
||||
|
||||
const dropletsPath = "droplets"
|
||||
|
||||
// LoadBalancersService is an interface for managing load balancers with the DigitalOcean API.
|
||||
|
|
@ -45,6 +47,36 @@ func (l LoadBalancer) String() string {
|
|||
return Stringify(l)
|
||||
}
|
||||
|
||||
func (l LoadBalancer) URN() string {
|
||||
return ToURN("LoadBalancer", l.ID)
|
||||
}
|
||||
|
||||
// AsRequest creates a LoadBalancerRequest that can be submitted to Update with the current values of the LoadBalancer.
|
||||
// Modifying the returned LoadBalancerRequest will not modify the original LoadBalancer.
|
||||
func (l LoadBalancer) AsRequest() *LoadBalancerRequest {
|
||||
r := LoadBalancerRequest{
|
||||
Name: l.Name,
|
||||
Algorithm: l.Algorithm,
|
||||
ForwardingRules: append([]ForwardingRule(nil), l.ForwardingRules...),
|
||||
DropletIDs: append([]int(nil), l.DropletIDs...),
|
||||
Tag: l.Tag,
|
||||
RedirectHttpToHttps: l.RedirectHttpToHttps,
|
||||
HealthCheck: l.HealthCheck,
|
||||
}
|
||||
if l.HealthCheck != nil {
|
||||
r.HealthCheck = &HealthCheck{}
|
||||
*r.HealthCheck = *l.HealthCheck
|
||||
}
|
||||
if l.StickySessions != nil {
|
||||
r.StickySessions = &StickySessions{}
|
||||
*r.StickySessions = *l.StickySessions
|
||||
}
|
||||
if l.Region != nil {
|
||||
r.Region = l.Region.Slug
|
||||
}
|
||||
return &r
|
||||
}
|
||||
|
||||
// ForwardingRule represents load balancer forwarding rules.
|
||||
type ForwardingRule struct {
|
||||
EntryProtocol string `json:"entry_protocol,omitempty"`
|
||||
|
|
@ -142,13 +174,13 @@ var _ LoadBalancersService = &LoadBalancersServiceOp{}
|
|||
func (l *LoadBalancersServiceOp) Get(ctx context.Context, lbID string) (*LoadBalancer, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", loadBalancersBasePath, lbID)
|
||||
|
||||
req, err := l.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := l.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(loadBalancerRoot)
|
||||
resp, err := l.client.Do(req, root)
|
||||
resp, err := l.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -163,13 +195,13 @@ func (l *LoadBalancersServiceOp) List(ctx context.Context, opt *ListOptions) ([]
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := l.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := l.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(loadBalancersRoot)
|
||||
resp, err := l.client.Do(req, root)
|
||||
resp, err := l.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -182,13 +214,13 @@ func (l *LoadBalancersServiceOp) List(ctx context.Context, opt *ListOptions) ([]
|
|||
|
||||
// Create a new load balancer with a given configuration.
|
||||
func (l *LoadBalancersServiceOp) Create(ctx context.Context, lbr *LoadBalancerRequest) (*LoadBalancer, *Response, error) {
|
||||
req, err := l.client.NewRequest(ctx, "POST", loadBalancersBasePath, lbr)
|
||||
req, err := l.client.NewRequest(ctx, http.MethodPost, loadBalancersBasePath, lbr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(loadBalancerRoot)
|
||||
resp, err := l.client.Do(req, root)
|
||||
resp, err := l.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -206,7 +238,7 @@ func (l *LoadBalancersServiceOp) Update(ctx context.Context, lbID string, lbr *L
|
|||
}
|
||||
|
||||
root := new(loadBalancerRoot)
|
||||
resp, err := l.client.Do(req, root)
|
||||
resp, err := l.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -218,58 +250,58 @@ func (l *LoadBalancersServiceOp) Update(ctx context.Context, lbID string, lbr *L
|
|||
func (l *LoadBalancersServiceOp) Delete(ctx context.Context, ldID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", loadBalancersBasePath, ldID)
|
||||
|
||||
req, err := l.client.NewRequest(ctx, "DELETE", path, nil)
|
||||
req, err := l.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l.client.Do(req, nil)
|
||||
return l.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// AddDroplets adds droplets to a load balancer.
|
||||
func (l *LoadBalancersServiceOp) AddDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, dropletsPath)
|
||||
|
||||
req, err := l.client.NewRequest(ctx, "POST", path, &dropletIDsRequest{IDs: dropletIDs})
|
||||
req, err := l.client.NewRequest(ctx, http.MethodPost, path, &dropletIDsRequest{IDs: dropletIDs})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l.client.Do(req, nil)
|
||||
return l.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// RemoveDroplets removes droplets from a load balancer.
|
||||
func (l *LoadBalancersServiceOp) RemoveDroplets(ctx context.Context, lbID string, dropletIDs ...int) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, dropletsPath)
|
||||
|
||||
req, err := l.client.NewRequest(ctx, "DELETE", path, &dropletIDsRequest{IDs: dropletIDs})
|
||||
req, err := l.client.NewRequest(ctx, http.MethodDelete, path, &dropletIDsRequest{IDs: dropletIDs})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l.client.Do(req, nil)
|
||||
return l.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// AddForwardingRules adds forwarding rules to a load balancer.
|
||||
func (l *LoadBalancersServiceOp) AddForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, forwardingRulesPath)
|
||||
|
||||
req, err := l.client.NewRequest(ctx, "POST", path, &forwardingRulesRequest{Rules: rules})
|
||||
req, err := l.client.NewRequest(ctx, http.MethodPost, path, &forwardingRulesRequest{Rules: rules})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l.client.Do(req, nil)
|
||||
return l.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// RemoveForwardingRules removes forwarding rules from a load balancer.
|
||||
func (l *LoadBalancersServiceOp) RemoveForwardingRules(ctx context.Context, lbID string, rules ...ForwardingRule) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/%s", loadBalancersBasePath, lbID, forwardingRulesPath)
|
||||
|
||||
req, err := l.client.NewRequest(ctx, "DELETE", path, &forwardingRulesRequest{Rules: rules})
|
||||
req, err := l.client.NewRequest(ctx, http.MethodDelete, path, &forwardingRulesRequest{Rules: rules})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l.client.Do(req, nil)
|
||||
return l.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
|
|
|||
302
vendor/github.com/digitalocean/godo/projects.go
generated
vendored
Normal file
302
vendor/github.com/digitalocean/godo/projects.go
generated
vendored
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
package godo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultProject is the ID you should use if you are working with your
|
||||
// default project.
|
||||
DefaultProject = "default"
|
||||
|
||||
projectsBasePath = "/v2/projects"
|
||||
)
|
||||
|
||||
// ProjectsService is an interface for creating and managing Projects with the DigitalOcean API.
|
||||
// See: https://developers.digitalocean.com/documentation/documentation/v2/#projects
|
||||
type ProjectsService interface {
|
||||
List(context.Context, *ListOptions) ([]Project, *Response, error)
|
||||
GetDefault(context.Context) (*Project, *Response, error)
|
||||
Get(context.Context, string) (*Project, *Response, error)
|
||||
Create(context.Context, *CreateProjectRequest) (*Project, *Response, error)
|
||||
Update(context.Context, string, *UpdateProjectRequest) (*Project, *Response, error)
|
||||
Delete(context.Context, string) (*Response, error)
|
||||
|
||||
ListResources(context.Context, string, *ListOptions) ([]ProjectResource, *Response, error)
|
||||
AssignResources(context.Context, string, ...interface{}) ([]ProjectResource, *Response, error)
|
||||
}
|
||||
|
||||
// ProjectsServiceOp handles communication with Projects methods of the DigitalOcean API.
|
||||
type ProjectsServiceOp struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Project represents a DigitalOcean Project configuration.
|
||||
type Project struct {
|
||||
ID string `json:"id"`
|
||||
OwnerUUID string `json:"owner_uuid"`
|
||||
OwnerID uint64 `json:"owner_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Purpose string `json:"purpose"`
|
||||
Environment string `json:"environment"`
|
||||
IsDefault bool `json:"is_default"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// String creates a human-readable description of a Project.
|
||||
func (p Project) String() string {
|
||||
return Stringify(p)
|
||||
}
|
||||
|
||||
// CreateProjectRequest represents the request to create a new project.
|
||||
type CreateProjectRequest struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Purpose string `json:"purpose"`
|
||||
Environment string `json:"environment"`
|
||||
}
|
||||
|
||||
// UpdateProjectRequest represents the request to update project information.
|
||||
// This type expects certain attribute types, but is built this way to allow
|
||||
// nil values as well. See `updateProjectRequest` for the "real" types.
|
||||
type UpdateProjectRequest struct {
|
||||
Name interface{}
|
||||
Description interface{}
|
||||
Purpose interface{}
|
||||
Environment interface{}
|
||||
IsDefault interface{}
|
||||
}
|
||||
|
||||
type updateProjectRequest struct {
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
Purpose *string `json:"purpose"`
|
||||
Environment *string `json:"environment"`
|
||||
IsDefault *bool `json:"is_default"`
|
||||
}
|
||||
|
||||
// MarshalJSON takes an UpdateRequest and converts it to the "typed" request
|
||||
// which is sent to the projects API. This is a PATCH request, which allows
|
||||
// partial attributes, so `null` values are OK.
|
||||
func (upr *UpdateProjectRequest) MarshalJSON() ([]byte, error) {
|
||||
d := &updateProjectRequest{}
|
||||
if str, ok := upr.Name.(string); ok {
|
||||
d.Name = &str
|
||||
}
|
||||
if str, ok := upr.Description.(string); ok {
|
||||
d.Description = &str
|
||||
}
|
||||
if str, ok := upr.Purpose.(string); ok {
|
||||
d.Purpose = &str
|
||||
}
|
||||
if str, ok := upr.Environment.(string); ok {
|
||||
d.Environment = &str
|
||||
}
|
||||
if val, ok := upr.IsDefault.(bool); ok {
|
||||
d.IsDefault = &val
|
||||
}
|
||||
|
||||
return json.Marshal(d)
|
||||
}
|
||||
|
||||
type assignResourcesRequest struct {
|
||||
Resources []string `json:"resources"`
|
||||
}
|
||||
|
||||
// ProjectResource is the projects API's representation of a resource.
|
||||
type ProjectResource struct {
|
||||
URN string `json:"urn"`
|
||||
AssignedAt string `json:"assigned_at"`
|
||||
Links *ProjectResourceLinks `json:"links"`
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// ProjetResourceLinks specify the link for more information about the resource.
|
||||
type ProjectResourceLinks struct {
|
||||
Self string `json:"self"`
|
||||
}
|
||||
|
||||
type projectsRoot struct {
|
||||
Projects []Project `json:"projects"`
|
||||
Links *Links `json:"links"`
|
||||
}
|
||||
|
||||
type projectRoot struct {
|
||||
Project *Project `json:"project"`
|
||||
}
|
||||
|
||||
type projectResourcesRoot struct {
|
||||
Resources []ProjectResource `json:"resources"`
|
||||
Links *Links `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
var _ ProjectsService = &ProjectsServiceOp{}
|
||||
|
||||
// List Projects.
|
||||
func (p *ProjectsServiceOp) List(ctx context.Context, opts *ListOptions) ([]Project, *Response, error) {
|
||||
path, err := addOptions(projectsBasePath, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := p.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(projectsRoot)
|
||||
resp, err := p.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
if l := root.Links; l != nil {
|
||||
resp.Links = l
|
||||
}
|
||||
|
||||
return root.Projects, resp, err
|
||||
}
|
||||
|
||||
// GetDefault project.
|
||||
func (p *ProjectsServiceOp) GetDefault(ctx context.Context) (*Project, *Response, error) {
|
||||
return p.getHelper(ctx, "default")
|
||||
}
|
||||
|
||||
// Get retrieves a single project by its ID.
|
||||
func (p *ProjectsServiceOp) Get(ctx context.Context, projectID string) (*Project, *Response, error) {
|
||||
return p.getHelper(ctx, projectID)
|
||||
}
|
||||
|
||||
// Create a new project.
|
||||
func (p *ProjectsServiceOp) Create(ctx context.Context, cr *CreateProjectRequest) (*Project, *Response, error) {
|
||||
req, err := p.client.NewRequest(ctx, http.MethodPost, projectsBasePath, cr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(projectRoot)
|
||||
resp, err := p.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Project, resp, err
|
||||
}
|
||||
|
||||
// Update an existing project.
|
||||
func (p *ProjectsServiceOp) Update(ctx context.Context, projectID string, ur *UpdateProjectRequest) (*Project, *Response, error) {
|
||||
path := path.Join(projectsBasePath, projectID)
|
||||
req, err := p.client.NewRequest(ctx, http.MethodPatch, path, ur)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(projectRoot)
|
||||
resp, err := p.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Project, resp, err
|
||||
}
|
||||
|
||||
// Delete an existing project. You cannot have any resources in a project
|
||||
// before deleting it. See the API documentation for more details.
|
||||
func (p *ProjectsServiceOp) Delete(ctx context.Context, projectID string) (*Response, error) {
|
||||
path := path.Join(projectsBasePath, projectID)
|
||||
req, err := p.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// ListResources lists all resources in a project.
|
||||
func (p *ProjectsServiceOp) ListResources(ctx context.Context, projectID string, opts *ListOptions) ([]ProjectResource, *Response, error) {
|
||||
basePath := path.Join(projectsBasePath, projectID, "resources")
|
||||
path, err := addOptions(basePath, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := p.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(projectResourcesRoot)
|
||||
resp, err := p.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
if l := root.Links; l != nil {
|
||||
resp.Links = l
|
||||
}
|
||||
|
||||
return root.Resources, resp, err
|
||||
}
|
||||
|
||||
// AssignResources assigns one or more resources to a project. AssignResources
|
||||
// accepts resources in two possible formats:
|
||||
|
||||
// 1. The resource type, like `&Droplet{ID: 1}` or `&FloatingIP{IP: "1.2.3.4"}`
|
||||
// 2. A valid DO URN as a string, like "do:droplet:1234"
|
||||
//
|
||||
// There is no unassign. To move a resource to another project, just assign
|
||||
// it to that other project.
|
||||
func (p *ProjectsServiceOp) AssignResources(ctx context.Context, projectID string, resources ...interface{}) ([]ProjectResource, *Response, error) {
|
||||
path := path.Join(projectsBasePath, projectID, "resources")
|
||||
|
||||
ar := &assignResourcesRequest{
|
||||
Resources: make([]string, len(resources)),
|
||||
}
|
||||
|
||||
for i, resource := range resources {
|
||||
switch resource.(type) {
|
||||
case ResourceWithURN:
|
||||
ar.Resources[i] = resource.(ResourceWithURN).URN()
|
||||
case string:
|
||||
ar.Resources[i] = resource.(string)
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("%T must either be a string or have a valid URN method", resource)
|
||||
}
|
||||
}
|
||||
req, err := p.client.NewRequest(ctx, http.MethodPost, path, ar)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(projectResourcesRoot)
|
||||
resp, err := p.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
if l := root.Links; l != nil {
|
||||
resp.Links = l
|
||||
}
|
||||
|
||||
return root.Resources, resp, err
|
||||
}
|
||||
|
||||
func (p *ProjectsServiceOp) getHelper(ctx context.Context, projectID string) (*Project, *Response, error) {
|
||||
path := path.Join(projectsBasePath, projectID)
|
||||
|
||||
req, err := p.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(projectRoot)
|
||||
resp, err := p.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
return root.Project, resp, err
|
||||
}
|
||||
9
vendor/github.com/digitalocean/godo/regions.go
generated
vendored
9
vendor/github.com/digitalocean/godo/regions.go
generated
vendored
|
|
@ -1,6 +1,9 @@
|
|||
package godo
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// RegionsService is an interface for interfacing with the regions
|
||||
// endpoints of the DigitalOcean API
|
||||
|
|
@ -43,13 +46,13 @@ func (s *RegionsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Region
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(regionsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
|
|||
9
vendor/github.com/digitalocean/godo/sizes.go
generated
vendored
9
vendor/github.com/digitalocean/godo/sizes.go
generated
vendored
|
|
@ -1,6 +1,9 @@
|
|||
package godo
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// SizesService is an interface for interfacing with the size
|
||||
// endpoints of the DigitalOcean API
|
||||
|
|
@ -47,13 +50,13 @@ func (s *SizesServiceOp) List(ctx context.Context, opt *ListOptions) ([]Size, *R
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(sizesRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
|
|||
13
vendor/github.com/digitalocean/godo/snapshots.go
generated
vendored
13
vendor/github.com/digitalocean/godo/snapshots.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package godo
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const snapshotBasePath = "v2/snapshots"
|
||||
|
|
@ -81,12 +82,12 @@ func (s *SnapshotsServiceOp) Get(ctx context.Context, snapshotID string) (*Snaps
|
|||
func (s *SnapshotsServiceOp) Delete(ctx context.Context, snapshotID string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", snapshotBasePath, snapshotID)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
resp, err := s.client.Do(ctx, req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
|
@ -95,13 +96,13 @@ func (s *SnapshotsServiceOp) Delete(ctx context.Context, snapshotID string) (*Re
|
|||
func (s *SnapshotsServiceOp) get(ctx context.Context, ID string) (*Snapshot, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", snapshotBasePath, ID)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(snapshotRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -121,13 +122,13 @@ func (s *SnapshotsServiceOp) list(ctx context.Context, opt *ListOptions, listOpt
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(snapshotsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
|
|||
69
vendor/github.com/digitalocean/godo/storage.go
generated
vendored
69
vendor/github.com/digitalocean/godo/storage.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package godo
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -43,19 +44,25 @@ var _ StorageService = &StorageServiceOp{}
|
|||
|
||||
// Volume represents a Digital Ocean block store volume.
|
||||
type Volume struct {
|
||||
ID string `json:"id"`
|
||||
Region *Region `json:"region"`
|
||||
Name string `json:"name"`
|
||||
SizeGigaBytes int64 `json:"size_gigabytes"`
|
||||
Description string `json:"description"`
|
||||
DropletIDs []int `json:"droplet_ids"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ID string `json:"id"`
|
||||
Region *Region `json:"region"`
|
||||
Name string `json:"name"`
|
||||
SizeGigaBytes int64 `json:"size_gigabytes"`
|
||||
Description string `json:"description"`
|
||||
DropletIDs []int `json:"droplet_ids"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
FilesystemType string `json:"filesystem_type"`
|
||||
FilesystemLabel string `json:"filesystem_label"`
|
||||
}
|
||||
|
||||
func (f Volume) String() string {
|
||||
return Stringify(f)
|
||||
}
|
||||
|
||||
func (f Volume) URN() string {
|
||||
return ToURN("Volume", f.ID)
|
||||
}
|
||||
|
||||
type storageVolumesRoot struct {
|
||||
Volumes []Volume `json:"volumes"`
|
||||
Links *Links `json:"links"`
|
||||
|
|
@ -69,11 +76,13 @@ type storageVolumeRoot struct {
|
|||
// VolumeCreateRequest represents a request to create a block store
|
||||
// volume.
|
||||
type VolumeCreateRequest struct {
|
||||
Region string `json:"region"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
SizeGigaBytes int64 `json:"size_gigabytes"`
|
||||
SnapshotID string `json:"snapshot_id"`
|
||||
Region string `json:"region"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
SizeGigaBytes int64 `json:"size_gigabytes"`
|
||||
SnapshotID string `json:"snapshot_id"`
|
||||
FilesystemType string `json:"filesystem_type"`
|
||||
FilesystemLabel string `json:"filesystem_label"`
|
||||
}
|
||||
|
||||
// ListVolumes lists all storage volumes.
|
||||
|
|
@ -82,6 +91,10 @@ func (svc *StorageServiceOp) ListVolumes(ctx context.Context, params *ListVolume
|
|||
if params != nil {
|
||||
if params.Region != "" && params.Name != "" {
|
||||
path = fmt.Sprintf("%s?name=%s®ion=%s", path, params.Name, params.Region)
|
||||
} else if params.Region != "" {
|
||||
path = fmt.Sprintf("%s?region=%s", path, params.Region)
|
||||
} else if params.Name != "" {
|
||||
path = fmt.Sprintf("%s?name=%s", path, params.Name)
|
||||
}
|
||||
|
||||
if params.ListOptions != nil {
|
||||
|
|
@ -93,13 +106,13 @@ func (svc *StorageServiceOp) ListVolumes(ctx context.Context, params *ListVolume
|
|||
}
|
||||
}
|
||||
|
||||
req, err := svc.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(storageVolumesRoot)
|
||||
resp, err := svc.client.Do(req, root)
|
||||
resp, err := svc.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -115,13 +128,13 @@ func (svc *StorageServiceOp) ListVolumes(ctx context.Context, params *ListVolume
|
|||
func (svc *StorageServiceOp) CreateVolume(ctx context.Context, createRequest *VolumeCreateRequest) (*Volume, *Response, error) {
|
||||
path := storageAllocPath
|
||||
|
||||
req, err := svc.client.NewRequest(ctx, "POST", path, createRequest)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(storageVolumeRoot)
|
||||
resp, err := svc.client.Do(req, root)
|
||||
resp, err := svc.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -132,13 +145,13 @@ func (svc *StorageServiceOp) CreateVolume(ctx context.Context, createRequest *Vo
|
|||
func (svc *StorageServiceOp) GetVolume(ctx context.Context, id string) (*Volume, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", storageAllocPath, id)
|
||||
|
||||
req, err := svc.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(storageVolumeRoot)
|
||||
resp, err := svc.client.Do(req, root)
|
||||
resp, err := svc.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -150,11 +163,11 @@ func (svc *StorageServiceOp) GetVolume(ctx context.Context, id string) (*Volume,
|
|||
func (svc *StorageServiceOp) DeleteVolume(ctx context.Context, id string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", storageAllocPath, id)
|
||||
|
||||
req, err := svc.client.NewRequest(ctx, "DELETE", path, nil)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return svc.client.Do(req, nil)
|
||||
return svc.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// SnapshotCreateRequest represents a request to create a block store
|
||||
|
|
@ -173,13 +186,13 @@ func (svc *StorageServiceOp) ListSnapshots(ctx context.Context, volumeID string,
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := svc.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(snapshotsRoot)
|
||||
resp, err := svc.client.Do(req, root)
|
||||
resp, err := svc.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -195,13 +208,13 @@ func (svc *StorageServiceOp) ListSnapshots(ctx context.Context, volumeID string,
|
|||
func (svc *StorageServiceOp) CreateSnapshot(ctx context.Context, createRequest *SnapshotCreateRequest) (*Snapshot, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s/snapshots", storageAllocPath, createRequest.VolumeID)
|
||||
|
||||
req, err := svc.client.NewRequest(ctx, "POST", path, createRequest)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(snapshotRoot)
|
||||
resp, err := svc.client.Do(req, root)
|
||||
resp, err := svc.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -212,13 +225,13 @@ func (svc *StorageServiceOp) CreateSnapshot(ctx context.Context, createRequest *
|
|||
func (svc *StorageServiceOp) GetSnapshot(ctx context.Context, id string) (*Snapshot, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", storageSnapPath, id)
|
||||
|
||||
req, err := svc.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(snapshotRoot)
|
||||
resp, err := svc.client.Do(req, root)
|
||||
resp, err := svc.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -230,9 +243,9 @@ func (svc *StorageServiceOp) GetSnapshot(ctx context.Context, id string) (*Snaps
|
|||
func (svc *StorageServiceOp) DeleteSnapshot(ctx context.Context, id string) (*Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", storageSnapPath, id)
|
||||
|
||||
req, err := svc.client.NewRequest(ctx, "DELETE", path, nil)
|
||||
req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return svc.client.Do(req, nil)
|
||||
return svc.client.Do(ctx, req, nil)
|
||||
}
|
||||
|
|
|
|||
13
vendor/github.com/digitalocean/godo/storage_actions.go
generated
vendored
13
vendor/github.com/digitalocean/godo/storage_actions.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package godo
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// StorageActionsService is an interface for interfacing with the
|
||||
|
|
@ -76,13 +77,13 @@ func (s *StorageActionsServiceOp) Resize(ctx context.Context, volumeID string, s
|
|||
func (s *StorageActionsServiceOp) doAction(ctx context.Context, volumeID string, request *ActionRequest) (*Action, *Response, error) {
|
||||
path := storageAllocationActionPath(volumeID)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "POST", path, request)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, path, request)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -91,13 +92,13 @@ func (s *StorageActionsServiceOp) doAction(ctx context.Context, volumeID string,
|
|||
}
|
||||
|
||||
func (s *StorageActionsServiceOp) get(ctx context.Context, path string) (*Action, *Response, error) {
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -106,13 +107,13 @@ func (s *StorageActionsServiceOp) get(ctx context.Context, path string) (*Action
|
|||
}
|
||||
|
||||
func (s *StorageActionsServiceOp) list(ctx context.Context, path string) ([]Action, *Response, error) {
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(actionsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
|
|||
10
vendor/github.com/digitalocean/godo/strings.go
generated
vendored
10
vendor/github.com/digitalocean/godo/strings.go
generated
vendored
|
|
@ -5,10 +5,20 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var timestampType = reflect.TypeOf(Timestamp{})
|
||||
|
||||
type ResourceWithURN interface {
|
||||
URN() string
|
||||
}
|
||||
|
||||
// ToURN converts the resource type and ID to a valid DO API URN.
|
||||
func ToURN(resourceType string, id interface{}) string {
|
||||
return fmt.Sprintf("%s:%s:%v", "do", strings.ToLower(resourceType), id)
|
||||
}
|
||||
|
||||
// Stringify attempts to create a string representation of DigitalOcean types
|
||||
func Stringify(message interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
|
|
|
|||
43
vendor/github.com/digitalocean/godo/tags.go
generated
vendored
43
vendor/github.com/digitalocean/godo/tags.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package godo
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const tagsBasePath = "v2/tags"
|
||||
|
|
@ -34,6 +35,8 @@ type ResourceType string
|
|||
const (
|
||||
//DropletResourceType holds the string representing our ResourceType of Droplet.
|
||||
DropletResourceType ResourceType = "droplet"
|
||||
//ImageResourceType holds the string representing our ResourceType of Image.
|
||||
ImageResourceType ResourceType = "image"
|
||||
)
|
||||
|
||||
// Resource represent a single resource for associating/disassociating with tags
|
||||
|
|
@ -44,13 +47,23 @@ type Resource struct {
|
|||
|
||||
// TaggedResources represent the set of resources a tag is attached to
|
||||
type TaggedResources struct {
|
||||
Droplets *TaggedDropletsResources `json:"droplets,omitempty"`
|
||||
Count int `json:"count"`
|
||||
LastTaggedURI string `json:"last_tagged_uri,omitempty"`
|
||||
Droplets *TaggedDropletsResources `json:"droplets,omitempty"`
|
||||
Images *TaggedImagesResources `json:"images"`
|
||||
}
|
||||
|
||||
// TaggedDropletsResources represent the droplet resources a tag is attached to
|
||||
type TaggedDropletsResources struct {
|
||||
Count int `json:"count,float64,omitempty"`
|
||||
LastTagged *Droplet `json:"last_tagged,omitempty"`
|
||||
Count int `json:"count,float64,omitempty"`
|
||||
LastTagged *Droplet `json:"last_tagged,omitempty"`
|
||||
LastTaggedURI string `json:"last_tagged_uri,omitempty"`
|
||||
}
|
||||
|
||||
// TaggedImagesResources represent the image resources a tag is attached to
|
||||
type TaggedImagesResources struct {
|
||||
Count int `json:"count,float64,omitempty"`
|
||||
LastTaggedURI string `json:"last_tagged_uri,omitempty"`
|
||||
}
|
||||
|
||||
// Tag represent DigitalOcean tag
|
||||
|
|
@ -92,13 +105,13 @@ func (s *TagsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Tag, *Res
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(tagsRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -113,13 +126,13 @@ func (s *TagsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Tag, *Res
|
|||
func (s *TagsServiceOp) Get(ctx context.Context, name string) (*Tag, *Response, error) {
|
||||
path := fmt.Sprintf("%s/%s", tagsBasePath, name)
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "GET", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(tagRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -133,13 +146,13 @@ func (s *TagsServiceOp) Create(ctx context.Context, createRequest *TagCreateRequ
|
|||
return nil, nil, NewArgError("createRequest", "cannot be nil")
|
||||
}
|
||||
|
||||
req, err := s.client.NewRequest(ctx, "POST", tagsBasePath, createRequest)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, tagsBasePath, createRequest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
root := new(tagRoot)
|
||||
resp, err := s.client.Do(req, root)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
@ -154,12 +167,12 @@ func (s *TagsServiceOp) Delete(ctx context.Context, name string) (*Response, err
|
|||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%s", tagsBasePath, name)
|
||||
req, err := s.client.NewRequest(ctx, "DELETE", path, nil)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
resp, err := s.client.Do(ctx, req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
|
@ -175,12 +188,12 @@ func (s *TagsServiceOp) TagResources(ctx context.Context, name string, tagReques
|
|||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%s/resources", tagsBasePath, name)
|
||||
req, err := s.client.NewRequest(ctx, "POST", path, tagRequest)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, path, tagRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
resp, err := s.client.Do(ctx, req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
|
@ -196,12 +209,12 @@ func (s *TagsServiceOp) UntagResources(ctx context.Context, name string, untagRe
|
|||
}
|
||||
|
||||
path := fmt.Sprintf("%s/%s/resources", tagsBasePath, name)
|
||||
req, err := s.client.NewRequest(ctx, "DELETE", path, untagRequest)
|
||||
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, untagRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req, nil)
|
||||
resp, err := s.client.Do(ctx, req, nil)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
|
|
|||
18
vendor/vendor.json
vendored
18
vendor/vendor.json
vendored
|
|
@ -717,11 +717,11 @@
|
|||
"revisionTime": "2018-03-08T23:13:08Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "W1LGm0UNirwMDVCMFv5vZrOpUJI=",
|
||||
"checksumSHA1": "u6kWbz1BRsZT433AooftjLNNtdw=",
|
||||
"comment": "v0.9.0-24-g6ca5b77",
|
||||
"path": "github.com/digitalocean/godo",
|
||||
"revision": "4c04abe183f449bd9ede285f0e5c7ee575d0dbe4",
|
||||
"revisionTime": "2017-04-07T15:15:42Z"
|
||||
"revision": "c4ae66932b5d6fb727d54e9ded9ee722f67f3ca9",
|
||||
"revisionTime": "2018-11-13T19:55:25Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "1n5MBJthemxmfqU2gN3qLCd8s04=",
|
||||
|
|
@ -1487,18 +1487,18 @@
|
|||
"revision": "2d205ac6ec17a839a94bdbfd16d2fa6c6dada2e0",
|
||||
"revisionTime": "2016-03-31T20:48:55Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "6JP37UqrI0H80Gpk0Y2P+KXgn5M=",
|
||||
"path": "github.com/ryanuber/go-glob",
|
||||
"revision": "256dc444b735e061061cf46c809487313d5b0065",
|
||||
"revisionTime": "2017-01-28T01:21:29Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "4gVuzkHbQoznf+lCSJhIJnvS5tc=",
|
||||
"path": "github.com/rwtodd/Go.Sed/sed",
|
||||
"revision": "d6d5d585814e4c3560c684f52e3d8aeed721313d",
|
||||
"revisionTime": "2017-05-07T04:53:31Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "6JP37UqrI0H80Gpk0Y2P+KXgn5M=",
|
||||
"path": "github.com/ryanuber/go-glob",
|
||||
"revision": "256dc444b735e061061cf46c809487313d5b0065",
|
||||
"revisionTime": "2017-01-28T01:21:29Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "zmC8/3V4ls53DJlNTKDZwPSC/dA=",
|
||||
"path": "github.com/satori/go.uuid",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ still distributed with Packer.
|
|||
## Provisioners
|
||||
|
||||
- File
|
||||
- InSpec
|
||||
- PowerShell
|
||||
- Shell
|
||||
- Windows Restart
|
||||
|
|
|
|||
|
|
@ -147,28 +147,30 @@ builder.
|
|||
|
||||
Snapshots from on or before July 15, 2013 cannot be used to create a
|
||||
disk.
|
||||
|
||||
- `image_ignore_data_disks`(boolean) - If this value is true, the image created
|
||||
will not include any snapshot of data disks. This option would be useful for
|
||||
any circumstance that default data disks with instance types are not concerned.
|
||||
The default value is false.
|
||||
|
||||
- `wait_snapshot_ready_timeout`(number) - Timeout of creating snapshot(s). The
|
||||
default timeout is 3600 seconds if this option is not set or is set to 0. For
|
||||
those disks containing lots of data, it may require a higher timeout value.
|
||||
- `image_ignore_data_disks`(boolean) - If this value is true, the image
|
||||
created will not include any snapshot of data disks. This option would be
|
||||
useful for any circumstance that default data disks with instance types are
|
||||
not concerned. The default value is false.
|
||||
|
||||
- `wait_snapshot_ready_timeout`(number) - Timeout of creating snapshot(s).
|
||||
The default timeout is 3600 seconds if this option is not set or is set
|
||||
to 0. For those disks containing lots of data, it may require a higher
|
||||
timeout value.
|
||||
|
||||
- `image_force_delete` (boolean) - If this value is true, when the target
|
||||
image names including those copied are duplicated with existing images,
|
||||
it will delete the existing images and then create the target images,
|
||||
otherwise, the creation will fail. The default value is false. Check
|
||||
`image_name` and `image_copy_names` options for names of target images.
|
||||
If [-force](https://packer.io/docs/commands/build.html#force) option is provided
|
||||
in `build` command, this option can be omitted and taken as true.
|
||||
image names including those copied are duplicated with existing images, it
|
||||
will delete the existing images and then create the target images,
|
||||
otherwise, the creation will fail. The default value is false. Check
|
||||
`image_name` and `image_copy_names` options for names of target images. If
|
||||
[-force](https://packer.io/docs/commands/build.html#force) option is
|
||||
provided in `build` command, this option can be omitted and taken as true.
|
||||
|
||||
- `image_force_delete_snapshots` (boolean) - If this value is true, when
|
||||
delete the duplicated existing images, the source snapshots of those images
|
||||
will be delete either. If [-force](https://packer.io/docs/commands/build.html#force)
|
||||
option is provided in `build` command, this option can be omitted and taken as true.
|
||||
will be delete either. If
|
||||
[-force](https://packer.io/docs/commands/build.html#force) option is
|
||||
provided in `build` command, this option can be omitted and taken as true.
|
||||
|
||||
- `image_share_account` (array of string) - The IDs of to-be-added Aliyun
|
||||
accounts to which the image is shared. The number of accounts is 1 to 10.
|
||||
|
|
@ -299,10 +301,13 @@ Here is a basic example for Alicloud.
|
|||
\~> Note: Images can become deprecated after a while; run
|
||||
`aliyun ecs DescribeImages` to find one that exists.
|
||||
|
||||
\~> Note: Since WinRM is closed by default in the system image. If you are planning
|
||||
to use Windows as the base image, you need enable it by userdata in order to connect to
|
||||
the instance, check [alicloud_windows.json](https://github.com/hashicorp/packer/tree/master/examples/alicloud/basic/alicloud_windows.json)
|
||||
and [winrm_enable_userdata.ps1](https://github.com/hashicorp/packer/tree/master/examples/alicloud/basic/winrm_enable_userdata.ps1) for details.
|
||||
\~> Note: Since WinRM is closed by default in the system image. If you are
|
||||
planning to use Windows as the base image, you need enable it by userdata in
|
||||
order to connect to the instance, check
|
||||
[alicloud\_windows.json](https://github.com/hashicorp/packer/tree/master/examples/alicloud/basic/alicloud_windows.json)
|
||||
and
|
||||
[winrm\_enable\_userdata.ps1](https://github.com/hashicorp/packer/tree/master/examples/alicloud/basic/winrm_enable_userdata.ps1)
|
||||
for details.
|
||||
|
||||
See the
|
||||
[examples/alicloud](https://github.com/hashicorp/packer/tree/master/examples/alicloud)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ builder is able to build an EBS-backed AMI without launching a new EC2
|
|||
instance. This can dramatically speed up AMI builds for organizations who need
|
||||
the extra fast build.
|
||||
|
||||
~> **This is an advanced builder** If you're just getting started with
|
||||
\~> **This is an advanced builder** If you're just getting started with
|
||||
Packer, we recommend starting with the [amazon-ebs
|
||||
builder](/docs/builders/amazon-ebs.html), which is much easier to use.
|
||||
|
||||
|
|
@ -154,8 +154,8 @@ each category, the available configuration keys are alphabetized.
|
|||
associated with AMIs, which have been deregistered by `force_deregister`.
|
||||
Default `false`.
|
||||
|
||||
- `insecure_skip_tls_verify` (boolean) - This allows skipping TLS verification of
|
||||
the AWS EC2 endpoint. The default is `false`.
|
||||
- `insecure_skip_tls_verify` (boolean) - This allows skipping TLS
|
||||
verification of the AWS EC2 endpoint. The default is `false`.
|
||||
|
||||
- `kms_key_id` (string) - ID, alias or ARN of the KMS key to use for boot
|
||||
volume encryption. This only applies to the main `region`, other regions
|
||||
|
|
@ -362,6 +362,34 @@ each category, the available configuration keys are alphabetized.
|
|||
[template engine](/docs/templates/engine.html), see [Build template
|
||||
data](#build-template-data) for more information.
|
||||
|
||||
- `vault_aws_engine` (object) - Get credentials from Hashicorp Vault's aws
|
||||
secrets engine. You must already have created a role to use. For more
|
||||
information about generating credentials via the Vault engine, see the
|
||||
[Vault
|
||||
docs.](https://www.vaultproject.io/api/secret/aws/index.html#generate-credentials)
|
||||
If you set this flag, you must also set the below options:
|
||||
- `name` (string) - Required. Specifies the name of the role to generate
|
||||
credentials against. This is part of the request URL.
|
||||
- `engine_name` (string) - The name of the aws secrets engine. In the
|
||||
Vault docs, this is normally referred to as "aws", and Packer will
|
||||
default to "aws" if `engine_name` is not set.
|
||||
- `role_arn` (string)- The ARN of the role to assume if credential\_type
|
||||
on the Vault role is assumed\_role. Must match one of the allowed role
|
||||
ARNs in the Vault role. Optional if the Vault role only allows a single
|
||||
AWS role ARN; required otherwise.
|
||||
- `ttl` (string) - Specifies the TTL for the use of the STS token. This
|
||||
is specified as a string with a duration suffix. Valid only when
|
||||
credential\_type is assumed\_role or federation\_token. When not
|
||||
specified, the default\_sts\_ttl set for the role will be used. If that
|
||||
is also not set, then the default value of 3600s will be used. AWS
|
||||
places limits on the maximum TTL allowed. See the AWS documentation on
|
||||
the DurationSeconds parameter for AssumeRole (for assumed\_role
|
||||
credential types) and GetFederationToken (for federation\_token
|
||||
credential types) for more details.
|
||||
|
||||
Example:
|
||||
`json { "vault_aws_engine": { "name": "myrole", "role_arn": "myarn", "ttl": "3600s" } }`
|
||||
|
||||
## Basic Example
|
||||
|
||||
Here is a basic example. It is completely valid except for the access keys:
|
||||
|
|
@ -457,8 +485,8 @@ services:
|
|||
|
||||
### Ansible provisioner
|
||||
|
||||
Running ansible against `amazon-chroot` requires changing the Ansible connection
|
||||
to chroot and running Ansible as root/sudo.
|
||||
Running ansible against `amazon-chroot` requires changing the Ansible
|
||||
connection to chroot and running Ansible as root/sudo.
|
||||
|
||||
### Using Instances with NVMe block devices.
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,9 @@ builder.
|
|||
### Required:
|
||||
|
||||
- `access_key` (string) - The access key used to communicate with AWS. [Learn
|
||||
how to set this](amazon.html#specifying-amazon-credentials)
|
||||
how to set this](amazon.html#specifying-amazon-credentials). This is not
|
||||
required if you are using `use_vault_aws_engine` for authentication
|
||||
instead.
|
||||
|
||||
- `ami_name` (string) - The name of the resulting AMI that will appear when
|
||||
managing AMIs in the AWS console or via APIs. This must be unique. To help
|
||||
|
|
@ -60,7 +62,9 @@ builder.
|
|||
to launch the EC2 instance to create the AMI.
|
||||
|
||||
- `secret_key` (string) - The secret key used to communicate with AWS. [Learn
|
||||
how to set this](amazon.html#specifying-amazon-credentials)
|
||||
how to set this](amazon.html#specifying-amazon-credentials). This is not
|
||||
required if you are using `use_vault_aws_engine` for authentication
|
||||
instead.
|
||||
|
||||
- `source_ami` (string) - The initial AMI used as a base for the newly
|
||||
created machine. `source_ami_filter` may be used instead to populate this
|
||||
|
|
@ -505,6 +509,41 @@ builder.
|
|||
- `user_data_file` (string) - Path to a file that will be used for the user
|
||||
data when launching the instance.
|
||||
|
||||
- `vault_aws_engine` (object) - Get credentials from Hashicorp Vault's aws
|
||||
secrets engine. You must already have created a role to use. For more
|
||||
information about generating credentials via the Vault engine, see the
|
||||
[Vault
|
||||
docs.](https://www.vaultproject.io/api/secret/aws/index.html#generate-credentials)
|
||||
If you set this flag, you must also set the below options:
|
||||
- `name` (string) - Required. Specifies the name of the role to generate
|
||||
credentials against. This is part of the request URL.
|
||||
- `engine_name` (string) - The name of the aws secrets engine. In the
|
||||
Vault docs, this is normally referred to as "aws", and Packer will
|
||||
default to "aws" if `engine_name` is not set.
|
||||
- `role_arn` (string)- The ARN of the role to assume if credential\_type
|
||||
on the Vault role is assumed\_role. Must match one of the allowed role
|
||||
ARNs in the Vault role. Optional if the Vault role only allows a single
|
||||
AWS role ARN; required otherwise.
|
||||
- `ttl` (string) - Specifies the TTL for the use of the STS token. This
|
||||
is specified as a string with a duration suffix. Valid only when
|
||||
credential\_type is assumed\_role or federation\_token. When not
|
||||
specified, the default\_sts\_ttl set for the role will be used. If that
|
||||
is also not set, then the default value of 3600s will be used. AWS
|
||||
places limits on the maximum TTL allowed. See the AWS documentation on
|
||||
the DurationSeconds parameter for AssumeRole (for assumed\_role
|
||||
credential types) and GetFederationToken (for federation\_token
|
||||
credential types) for more details.
|
||||
|
||||
``` json
|
||||
{
|
||||
"vault_aws_engine": {
|
||||
"name": "myrole",
|
||||
"role_arn": "myarn",
|
||||
"ttl": "3600s"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `vpc_id` (string) - If launching into a VPC subnet, Packer needs the VPC ID
|
||||
in order to create a temporary security group within the VPC. Requires
|
||||
`subnet_id` to be set. If this field is left blank, Packer will try to get
|
||||
|
|
|
|||
|
|
@ -497,6 +497,33 @@ builder.
|
|||
- `user_data_file` (string) - Path to a file that will be used for the user
|
||||
data when launching the instance.
|
||||
|
||||
- `vault_aws_engine` (object) - Get credentials from Hashicorp Vault's aws
|
||||
secrets engine. You must already have created a role to use. For more
|
||||
information about generating credentials via the Vault engine, see the
|
||||
[Vault
|
||||
docs.](https://www.vaultproject.io/api/secret/aws/index.html#generate-credentials)
|
||||
If you set this flag, you must also set the below options:
|
||||
- `name` (string) - Required. Specifies the name of the role to generate
|
||||
credentials against. This is part of the request URL.
|
||||
- `engine_name` (string) - The name of the aws secrets engine. In the Vault
|
||||
docs, this is normally referred to as "aws", and Packer will default to
|
||||
"aws" if `engine_name` is not set.
|
||||
- `role_arn` (string)- The ARN of the role to assume if credential\_type on
|
||||
the Vault role is assumed\_role. Must match one of the allowed role ARNs in
|
||||
the Vault role. Optional if the Vault role only allows a single AWS role
|
||||
ARN; required otherwise.
|
||||
- `ttl` (string) - Specifies the TTL for the use of the STS token. This is
|
||||
specified as a string with a duration suffix. Valid only when
|
||||
credential\_type is assumed\_role or federation\_token. When not specified,
|
||||
the default\_sts\_ttl set for the role will be used. If that is also not
|
||||
set, then the default value of 3600s will be used. AWS places limits on the
|
||||
maximum TTL allowed. See the AWS documentation on the DurationSeconds
|
||||
parameter for AssumeRole (for assumed\_role credential types) and
|
||||
GetFederationToken (for federation\_token credential types) for more
|
||||
details.
|
||||
|
||||
Example:
|
||||
`json { "vault_aws_engine": { "name": "myrole", "role_arn": "myarn", "ttl": "3600s" } }`
|
||||
- `vpc_id` (string) - If launching into a VPC subnet, Packer needs the VPC ID
|
||||
in order to create a temporary security group within the VPC. Requires
|
||||
`subnet_id` to be set. If this field is left blank, Packer will try to get
|
||||
|
|
|
|||
|
|
@ -407,6 +407,43 @@ builder.
|
|||
- `user_data_file` (string) - Path to a file that will be used for the user
|
||||
data when launching the instance.
|
||||
|
||||
- `vault_aws_engine` (object) - Get credentials from Hashicorp Vault's aws
|
||||
secrets engine. You must already have created a role to use. For more
|
||||
information about generating credentials via the Vault engine, see the
|
||||
[Vault docs.]
|
||||
(https://www.vaultproject.io/api/secret/aws/index.html#generate-credentials)
|
||||
If you set this
|
||||
flag, you must also set the below options:
|
||||
- `name` (string) - Required. Specifies the name of the role to generate
|
||||
credentials against. This is part of the request URL.
|
||||
- `engine_name` (string) - The name of the aws secrets engine. In the Vault
|
||||
docs, this is normally referred to as "aws", and Packer will default to
|
||||
"aws" if `engine_name` is not set.
|
||||
- `role_arn` (string)- The ARN of the role to assume if credential_type on
|
||||
the Vault role is assumed_role. Must match one of the allowed role ARNs
|
||||
in the Vault role. Optional if the Vault role only allows a single AWS
|
||||
role ARN; required otherwise.
|
||||
- `ttl` (string) - Specifies the TTL for the use of the STS token. This is
|
||||
specified as a string with a duration suffix. Valid only when
|
||||
credential_type is assumed_role or federation_token. When not specified,
|
||||
the default_sts_ttl set for the role will be used. If that is also not
|
||||
set, then the default value of 3600s will be used. AWS places limits on
|
||||
the maximum TTL allowed. See the AWS documentation on the DurationSeconds
|
||||
parameter for AssumeRole (for assumed_role credential types) and
|
||||
GetFederationToken (for federation_token credential types) for more
|
||||
details.
|
||||
|
||||
Example:
|
||||
``` json
|
||||
{
|
||||
"vault_aws_engine": {
|
||||
"name": "myrole",
|
||||
"role_arn": "myarn",
|
||||
"ttl": "3600s"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `vpc_id` (string) - If launching into a VPC subnet, Packer needs the VPC ID
|
||||
in order to create a temporary security group within the VPC. Requires
|
||||
`subnet_id` to be set. If this field is left blank, Packer will try to get
|
||||
|
|
|
|||
|
|
@ -489,6 +489,34 @@ builder.
|
|||
- `user_data_file` (string) - Path to a file that will be used for the user
|
||||
data when launching the instance.
|
||||
|
||||
- `vault_aws_engine` (object) - Get credentials from Hashicorp Vault's aws
|
||||
secrets engine. You must already have created a role to use. For more
|
||||
information about generating credentials via the Vault engine, see the
|
||||
[Vault
|
||||
docs.](https://www.vaultproject.io/api/secret/aws/index.html#generate-credentials)
|
||||
If you set this flag, you must also set the below options:
|
||||
- `name` (string) - Required. Specifies the name of the role to generate
|
||||
credentials against. This is part of the request URL.
|
||||
- `engine_name` (string) - The name of the aws secrets engine. In the
|
||||
Vault docs, this is normally referred to as "aws", and Packer will
|
||||
default to "aws" if `engine_name` is not set.
|
||||
- `role_arn` (string)- The ARN of the role to assume if credential\_type
|
||||
on the Vault role is assumed\_role. Must match one of the allowed role
|
||||
ARNs in the Vault role. Optional if the Vault role only allows a single
|
||||
AWS role ARN; required otherwise.
|
||||
- `ttl` (string) - Specifies the TTL for the use of the STS token. This
|
||||
is specified as a string with a duration suffix. Valid only when
|
||||
credential\_type is assumed\_role or federation\_token. When not
|
||||
specified, the default\_sts\_ttl set for the role will be used. If that
|
||||
is also not set, then the default value of 3600s will be used. AWS
|
||||
places limits on the maximum TTL allowed. See the AWS documentation on
|
||||
the DurationSeconds parameter for AssumeRole (for assumed\_role
|
||||
credential types) and GetFederationToken (for federation\_token
|
||||
credential types) for more details.
|
||||
|
||||
Example:
|
||||
`json { "vault_aws_engine": { "name": "myrole", "role_arn": "myarn", "ttl": "3600s" } }`
|
||||
|
||||
- `vpc_id` (string) - If launching into a VPC subnet, Packer needs the VPC ID
|
||||
in order to create a temporary security group within the VPC. Requires
|
||||
`subnet_id` to be set. If this field is left blank, Packer will try to get
|
||||
|
|
|
|||
|
|
@ -151,6 +151,7 @@ for Packer to work:
|
|||
"ec2:DescribeImageAttribute",
|
||||
"ec2:DescribeImages",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeInstanceStatus",
|
||||
"ec2:DescribeRegions",
|
||||
"ec2:DescribeSecurityGroups",
|
||||
"ec2:DescribeSnapshots",
|
||||
|
|
@ -218,9 +219,9 @@ fail. If that's the case, you might see an error like this:
|
|||
==> amazon-ebs: Error querying AMI: AuthFailure: AWS was not able to validate the provided access credentials
|
||||
|
||||
If you suspect your system's date is wrong, you can compare it against
|
||||
<http://www.time.gov/>. On Linux/OS X, you can run the `date` command to get
|
||||
the current time. If you're on Linux, you can try setting the time with ntp by
|
||||
running `sudo ntpd -q`.
|
||||
<http://www.time.gov/>. On
|
||||
Linux/OS X, you can run the `date` command to get the current time. If you're
|
||||
on Linux, you can try setting the time with ntp by running `sudo ntpd -q`.
|
||||
|
||||
### `exceeded wait attempts` while waiting for tasks to complete
|
||||
|
||||
|
|
|
|||
|
|
@ -206,8 +206,8 @@ Providing `temp_resource_group_name` or `location` in combination with
|
|||
- `os_disk_size_gb` (number) Specify the size of the OS disk in GB
|
||||
(gigabytes). Values of zero or less than zero are ignored.
|
||||
|
||||
- `disk_caching_type` (string) Specify the disk caching type. Valid values are None, ReadOnly,
|
||||
and ReadWrite. The default value is ReadWrite.
|
||||
- `disk_caching_type` (string) Specify the disk caching type. Valid values
|
||||
are None, ReadOnly, and ReadWrite. The default value is ReadWrite.
|
||||
|
||||
- `disk_additional_size` (array of integers) - The size(s) of any additional
|
||||
hard disks for the VM in gigabytes. If this is not specified then the VM
|
||||
|
|
@ -325,13 +325,14 @@ Providing `temp_resource_group_name` or `location` in combination with
|
|||
value and defaults to false. **Important** Setting this true means that
|
||||
your builds are faster, however any failed deletes are not reported.
|
||||
|
||||
- `managed_image_os_disk_snapshot_name` (string) If managed\_image\_os\_disk\_snapshot\_name
|
||||
is set, a snapshot of the OS disk is created with the same name as this value before the
|
||||
VM is captured.
|
||||
- `managed_image_os_disk_snapshot_name` (string) If
|
||||
managed\_image\_os\_disk\_snapshot\_name is set, a snapshot of the OS disk
|
||||
is created with the same name as this value before the VM is captured.
|
||||
|
||||
- `managed_image_data_disk_snapshot_prefix` (string) If managed\_image\_data\_disk\_snapshot\_prefix
|
||||
is set, snapshot of the data disk(s) is created with the same prefix as this value before the VM
|
||||
is captured.
|
||||
- `managed_image_data_disk_snapshot_prefix` (string) If
|
||||
managed\_image\_data\_disk\_snapshot\_prefix is set, snapshot of the data
|
||||
disk(s) is created with the same prefix as this value before the VM is
|
||||
captured.
|
||||
|
||||
## Basic Example
|
||||
|
||||
|
|
@ -403,14 +404,14 @@ here](https://technet.microsoft.com/en-us/library/hh824815.aspx)
|
|||
}
|
||||
```
|
||||
|
||||
The Windows Guest Agent participates in the Sysprep process. The
|
||||
agent must be fully installed before the VM can be sysprep'ed. To
|
||||
ensure this is true all agent services must be running before
|
||||
executing sysprep.exe. The above JSON snippet shows one way to do this
|
||||
in the PowerShell provisioner. This snippet is **only** required if
|
||||
the VM is configured to install the agent, which is the default. To
|
||||
learn more about disabling the Windows Guest Agent please see [Install the VM Agent](https://docs.microsoft.com/en-us/azure/virtual-machines/extensions/agent-windows#install-the-vm-agent).
|
||||
|
||||
The Windows Guest Agent participates in the Sysprep process. The agent must be
|
||||
fully installed before the VM can be sysprep'ed. To ensure this is true all
|
||||
agent services must be running before executing sysprep.exe. The above JSON
|
||||
snippet shows one way to do this in the PowerShell provisioner. This snippet is
|
||||
**only** required if the VM is configured to install the agent, which is the
|
||||
default. To learn more about disabling the Windows Guest Agent please see
|
||||
[Install the VM
|
||||
Agent](https://docs.microsoft.com/en-us/azure/virtual-machines/extensions/agent-windows#install-the-vm-agent).
|
||||
|
||||
### Linux
|
||||
|
||||
|
|
|
|||
|
|
@ -184,8 +184,8 @@ You must specify (only) one of `commit`, `discard`, or `export_path`.
|
|||
`login_username`, and `login_password` will be ignored. For more
|
||||
information see the [section on ECR](#amazon-ec2-container-registry).
|
||||
|
||||
- `exec_user` (string) - Username or UID (format:
|
||||
<name\\\|uid>\[:<group\\\|gid>\]) to run remote commands with.
|
||||
- `exec_user` (string) - Username (UID) to run remote commands with. You can
|
||||
also set the group name/ID if you want: (UID or UID:GID).
|
||||
You may need this if you get permission errors trying to run the `shell` or
|
||||
other provisioners.
|
||||
|
||||
|
|
@ -380,8 +380,8 @@ portable provisioning scripts.
|
|||
|
||||
## Overriding the host directory
|
||||
|
||||
By default, Packer creates a temporary folder under your home directory, and
|
||||
uses that to stage files for uploading into the container. If you would like to
|
||||
By default, Packer creates a temporary folder under your home directory, and
|
||||
uses that to stage files for uploading into the container. If you would like to
|
||||
change the path to this temporary folder, you can set the `PACKER_TMP_DIR`.
|
||||
This can be useful, for example, if you have your home directory permissions
|
||||
set up to disallow access from the docker daemon.
|
||||
|
|
|
|||
|
|
@ -18,9 +18,8 @@ based on existing images.
|
|||
|
||||
It is possible to build images from scratch, but not with the `googlecompute`
|
||||
Packer builder. The process is recommended only for advanced users, please see
|
||||
\[Building GCE Images from Scratch\]
|
||||
(<https://cloud.google.com/compute/docs/tutorials/building-images>) and the
|
||||
[Google Compute Import
|
||||
[Building GCE Images from Scratch](https://cloud.google.com/compute/docs/tutorials/building-images)
|
||||
and the [Google Compute Import
|
||||
Post-Processor](/docs/post-processors/googlecompute-import.html) for more
|
||||
information.
|
||||
|
||||
|
|
@ -139,7 +138,7 @@ using the gcloud command.
|
|||
gcloud compute firewall-rules create allow-winrm --allow tcp:5986
|
||||
|
||||
Or alternatively by navigating to
|
||||
<https://console.cloud.google.com/networking/firewalls/list>.
|
||||
<a href="https://console.cloud.google.com/networking/firewalls/list" class="uri">https://console.cloud.google.com/networking/firewalls/list</a>.
|
||||
|
||||
Once this is set up, the following is a complete working packer config after
|
||||
setting a valid `account_file` and `project_id`:
|
||||
|
|
|
|||
|
|
@ -75,7 +75,9 @@ 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`
|
||||
- `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
|
||||
|
||||
|
|
@ -84,11 +86,13 @@ access tokens:
|
|||
|
||||
``` json
|
||||
{
|
||||
"type": "hcloud",
|
||||
"token": "YOUR API KEY",
|
||||
"image": "ubuntu-18.04",
|
||||
"location": "nbg1",
|
||||
"server_type": "cx11",
|
||||
"ssh_username": "root"
|
||||
"builders": [{
|
||||
"type": "hcloud",
|
||||
"token": "YOUR API KEY",
|
||||
"image": "ubuntu-18.04",
|
||||
"location": "nbg1",
|
||||
"server_type": "cx11",
|
||||
"ssh_username": "root"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ as a tar.gz of the root file system.
|
|||
The LXC builder requires a modern linux kernel and the `lxc` or `lxc1` package.
|
||||
This builder does not work with LXD.
|
||||
|
||||
~> Note: to build Centos images on a Debian family host, you will need the
|
||||
\~> Note: to build Centos images on a Debian family host, you will need the
|
||||
`yum` package installed. <br>Some provisioners such as `ansible-local` get
|
||||
confused when running in a container of a different family. E.G. it will
|
||||
attempt to use `apt-get` to install packages, when running in a Centos
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ Below is a fully functioning example.
|
|||
container. This can be a (local or remote) image (name or fingerprint).
|
||||
E.G. `my-base-image`, `ubuntu-daily:x`, `08fababf6f27`, ...
|
||||
|
||||
~> Note: The builder may appear to pause if required to download a
|
||||
\~> Note: The builder may appear to pause if required to download a
|
||||
remote image, as they are usually 100-200MB. `/var/log/lxd/lxd.log` will
|
||||
mention starting such downloads.
|
||||
|
||||
|
|
@ -68,8 +68,8 @@ Below is a fully functioning example.
|
|||
- `publish_properties` (map\[string\]string) - Pass key values to the publish
|
||||
step to be set as properties on the output image. This is most helpful to
|
||||
set the description, but can be used to set anything needed. See
|
||||
<https://stgraber.org/2016/03/30/lxd-2-0-image-management-512/> for more
|
||||
properties.
|
||||
<a href="https://stgraber.org/2016/03/30/lxd-2-0-image-management-512/" class="uri">https://stgraber.org/2016/03/30/lxd-2-0-image-management-512/</a>
|
||||
for more properties.
|
||||
|
||||
- `launch_config` (map\[string\]string) - List of key/value pairs you wish to
|
||||
pass to `lxc launch` via `--config`. Defaults to empty.
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ Here is a basic example for windows server.
|
|||
]
|
||||
}
|
||||
|
||||
|
||||
Here is a basic example for linux server.
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -278,8 +278,8 @@ builder.
|
|||
isn't specified, the default enforced by your OpenStack cluster will be
|
||||
used.
|
||||
|
||||
- `volume_size` (int) - Size of the Block Storage service volume in GB. If this
|
||||
isn't specified, it is set to source image min disk value (if set) or
|
||||
- `volume_size` (int) - Size of the Block Storage service volume in GB. If
|
||||
this isn't specified, it is set to source image min disk value (if set) or
|
||||
calculated from the source image bytes size. Note that in some cases this
|
||||
needs to be specified, if `use_blockstorage_volume` is true.
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue