diff --git a/builtin/providers/packet/provider.go b/builtin/providers/packet/provider.go index 82f7dbf77dea..f3a848337006 100644 --- a/builtin/providers/packet/provider.go +++ b/builtin/providers/packet/provider.go @@ -21,6 +21,7 @@ func Provider() terraform.ResourceProvider { "packet_device": resourcePacketDevice(), "packet_ssh_key": resourcePacketSSHKey(), "packet_project": resourcePacketProject(), + "packet_volume": resourcePacketVolume(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/packet/resource_packet_device.go b/builtin/providers/packet/resource_packet_device.go index 2c6e3de54856..adae42575af3 100644 --- a/builtin/providers/packet/resource_packet_device.go +++ b/builtin/providers/packet/resource_packet_device.go @@ -208,13 +208,13 @@ func resourcePacketDeviceRead(d *schema.ResourceData, meta interface{}) error { network := map[string]interface{}{ "address": ip.Address, "gateway": ip.Gateway, - "family": ip.Family, + "family": ip.AddressFamily, "cidr": ip.Cidr, "public": ip.Public, } networks = append(networks, network) - if ip.Family == 4 && ip.Public == true { + if ip.AddressFamily == 4 && ip.Public == true { host = ip.Address } } diff --git a/builtin/providers/packet/resource_packet_project_test.go b/builtin/providers/packet/resource_packet_project_test.go index ff1b45f7c68d..1ba91b1fab19 100644 --- a/builtin/providers/packet/resource_packet_project_test.go +++ b/builtin/providers/packet/resource_packet_project_test.go @@ -38,7 +38,7 @@ func testAccCheckPacketProjectDestroy(s *terraform.State) error { continue } if _, _, err := client.Projects.Get(rs.Primary.ID); err == nil { - return fmt.Errorf("Project cstill exists") + return fmt.Errorf("Project still exists") } } diff --git a/builtin/providers/packet/resource_packet_volume.go b/builtin/providers/packet/resource_packet_volume.go new file mode 100644 index 000000000000..c5dc0a8876fe --- /dev/null +++ b/builtin/providers/packet/resource_packet_volume.go @@ -0,0 +1,281 @@ +package packet + +import ( + "errors" + "fmt" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/packethost/packngo" +) + +func resourcePacketVolume() *schema.Resource { + return &schema.Resource{ + Create: resourcePacketVolumeCreate, + Read: resourcePacketVolumeRead, + Update: resourcePacketVolumeUpdate, + Delete: resourcePacketVolumeDelete, + + Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "project_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "name": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Required: false, + Optional: true, + }, + + "size": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + + "facility": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "plan": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "billing_cycle": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + + "state": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "locked": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + + "snapshot_policies": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "snapshot_frequency": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "snapshot_count": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + }, + }, + }, + + "attachments": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "href": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "created": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "updated": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourcePacketVolumeCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*packngo.Client) + + createRequest := &packngo.VolumeCreateRequest{ + PlanID: d.Get("plan").(string), + FacilityID: d.Get("facility").(string), + ProjectID: d.Get("project_id").(string), + Size: d.Get("size").(int), + } + + if attr, ok := d.GetOk("billing_cycle"); ok { + createRequest.BillingCycle = attr.(string) + } else { + createRequest.BillingCycle = "hourly" + } + + if attr, ok := d.GetOk("description"); ok { + createRequest.Description = attr.(string) + } + + snapshot_count := d.Get("snapshot_policies.#").(int) + if snapshot_count > 0 { + createRequest.SnapshotPolicies = make([]*packngo.SnapshotPolicy, 0, snapshot_count) + for i := 0; i < snapshot_count; i++ { + policy := new(packngo.SnapshotPolicy) + policy.SnapshotFrequency = d.Get(fmt.Sprintf("snapshot_policies.%d.snapshot_frequency", i)).(string) + policy.SnapshotCount = d.Get(fmt.Sprintf("snapshot_policies.%d.snapshot_count", i)).(int) + createRequest.SnapshotPolicies = append(createRequest.SnapshotPolicies, policy) + } + } + + newVolume, _, err := client.Volumes.Create(createRequest) + if err != nil { + return friendlyError(err) + } + + d.SetId(newVolume.ID) + + _, err = waitForVolumeAttribute(d, "active", []string{"queued", "provisioning"}, "state", meta) + if err != nil { + if isForbidden(err) { + // If the volume doesn't get to the active state, we can't recover it from here. + d.SetId("") + + return errors.New("provisioning time limit exceeded; the Packet team will investigate") + } + return err + } + + return resourcePacketVolumeRead(d, meta) +} + +func waitForVolumeAttribute(d *schema.ResourceData, target string, pending []string, attribute string, meta interface{}) (interface{}, error) { + stateConf := &resource.StateChangeConf{ + Pending: pending, + Target: []string{target}, + Refresh: newVolumeStateRefreshFunc(d, attribute, meta), + Timeout: 60 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + return stateConf.WaitForState() +} + +func newVolumeStateRefreshFunc(d *schema.ResourceData, attribute string, meta interface{}) resource.StateRefreshFunc { + client := meta.(*packngo.Client) + + return func() (interface{}, string, error) { + if err := resourcePacketVolumeRead(d, meta); err != nil { + return nil, "", err + } + + if attr, ok := d.GetOk(attribute); ok { + volume, _, err := client.Volumes.Get(d.Id()) + if err != nil { + return nil, "", friendlyError(err) + } + return &volume, attr.(string), nil + } + + return nil, "", nil + } +} + +func resourcePacketVolumeRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*packngo.Client) + + volume, _, err := client.Volumes.Get(d.Id()) + if err != nil { + err = friendlyError(err) + + // If the volume somehow already destroyed, mark as succesfully gone. + if isNotFound(err) { + d.SetId("") + return nil + } + + return err + } + + d.Set("name", volume.Name) + d.Set("description", volume.Description) + d.Set("size", volume.Size) + d.Set("plan", volume.Plan.Slug) + d.Set("facility", volume.Facility.Code) + d.Set("state", volume.State) + d.Set("billing_cycle", volume.BillingCycle) + d.Set("locked", volume.Locked) + d.Set("created", volume.Created) + d.Set("updated", volume.Updated) + + snapshot_policies := make([]map[string]interface{}, 0, len(volume.SnapshotPolicies)) + for _, snapshot_policy := range volume.SnapshotPolicies { + policy := map[string]interface{}{ + "snapshot_frequency": snapshot_policy.SnapshotFrequency, + "snapshot_count": snapshot_policy.SnapshotCount, + } + snapshot_policies = append(snapshot_policies, policy) + } + d.Set("snapshot_policies", snapshot_policies) + + attachments := make([]*packngo.Attachment, 0, len(volume.Attachments)) + for _, attachment := range volume.Attachments { + attachments = append(attachments, attachment) + } + d.Set("attachments", attachments) + + return nil +} + +func resourcePacketVolumeUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*packngo.Client) + + updateRequest := &packngo.VolumeUpdateRequest{ + ID: d.Get("id").(string), + } + + if attr, ok := d.GetOk("description"); ok { + updateRequest.Description = attr.(string) + } + + if attr, ok := d.GetOk("plan"); ok { + updateRequest.Plan = attr.(string) + } + + _, _, err := client.Volumes.Update(updateRequest) + if err != nil { + return friendlyError(err) + } + + return resourcePacketVolumeRead(d, meta) +} + +func resourcePacketVolumeDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*packngo.Client) + + if _, err := client.Volumes.Delete(d.Id()); err != nil { + return friendlyError(err) + } + + return nil +} diff --git a/builtin/providers/packet/resource_packet_volume_test.go b/builtin/providers/packet/resource_packet_volume_test.go new file mode 100644 index 000000000000..cffd55f13ea6 --- /dev/null +++ b/builtin/providers/packet/resource_packet_volume_test.go @@ -0,0 +1,103 @@ +package packet + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/packethost/packngo" +) + +func TestAccPacketVolume_Basic(t *testing.T) { + var volume packngo.Volume + + project_id := os.Getenv("PACKET_PROJECT_ID") + facility := os.Getenv("PACKET_FACILITY") + + resource.Test(t, resource.TestCase{ + PreCheck: testAccPacketVolumePreCheck(t), + Providers: testAccProviders, + CheckDestroy: testAccCheckPacketVolumeDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testAccCheckPacketVolumeConfig_basic, project_id, facility), + Check: resource.ComposeTestCheckFunc( + testAccCheckPacketVolumeExists("packet_volume.foobar", &volume), + resource.TestCheckResourceAttr( + "packet_volume.foobar", "project_id", project_id), + resource.TestCheckResourceAttr( + "packet_volume.foobar", "plan", "storage_1"), + resource.TestCheckResourceAttr( + "packet_volume.foobar", "billing_cycle", "hourly"), + resource.TestCheckResourceAttr( + "packet_volume.foobar", "size", "100"), + ), + }, + }, + }) +} + +func testAccCheckPacketVolumeDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*packngo.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "packet_volume" { + continue + } + if _, _, err := client.Volumes.Get(rs.Primary.ID); err == nil { + return fmt.Errorf("Volume still exists") + } + } + + return nil +} + +func testAccCheckPacketVolumeExists(n string, volume *packngo.Volume) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No Record ID is set") + } + + client := testAccProvider.Meta().(*packngo.Client) + + foundVolume, _, err := client.Volumes.Get(rs.Primary.ID) + if err != nil { + return err + } + if foundVolume.ID != rs.Primary.ID { + return fmt.Errorf("Record not found: %v - %v", rs.Primary.ID, foundVolume) + } + + *volume = *foundVolume + + return nil + } +} + +func testAccPacketVolumePreCheck(t *testing.T) func() { + return func() { + testAccPreCheck(t) + if os.Getenv("PACKET_PROJECT_ID") == "" { + t.Fatal("PACKET_PROJECT_ID must be set") + } + if os.Getenv("PACKET_FACILITY") == "" { + t.Fatal("PACKET_FACILITY must be set") + } + } +} + +const testAccCheckPacketVolumeConfig_basic = ` +resource "packet_volume" "foobar" { + plan = "storage_1" + billing_cycle = "hourly" + size = 100 + project_id = "%s" + facility = "%s" + snapshot_policies = { snapshot_frequency = "1day", snapshot_count = 7 } +}` diff --git a/command/internal_plugin_list.go b/command/internal_plugin_list.go index e4807b799276..e3fc743c3201 100644 --- a/command/internal_plugin_list.go +++ b/command/internal_plugin_list.go @@ -61,6 +61,7 @@ import ( ) var InternalProviders = map[string]plugin.ProviderFunc{ + "archive": archiveprovider.Provider, "atlas": atlasprovider.Provider, "aws": awsprovider.Provider, "azure": azureprovider.Provider, @@ -105,7 +106,6 @@ var InternalProviders = map[string]plugin.ProviderFunc{ "ultradns": ultradnsprovider.Provider, "vcd": vcdprovider.Provider, "vsphere": vsphereprovider.Provider, - "archive": archiveprovider.Provider, } var InternalProvisioners = map[string]plugin.ProvisionerFunc{ diff --git a/vendor/github.com/packethost/packngo/README.md b/vendor/github.com/packethost/packngo/README.md new file mode 100644 index 000000000000..d359a10d65db --- /dev/null +++ b/vendor/github.com/packethost/packngo/README.md @@ -0,0 +1,9 @@ +# packngo +Packet Go Api Client + +![](https://www.packet.net/media/labs/images/1679091c5a880faf6fb5e6087eb1b2dc/ULY7-hero.png) + +Committing +---------- + +Before committing, it's a good idea to run `gofmt -w *.go`. ([gofmt](https://golang.org/cmd/gofmt/)) diff --git a/vendor/github.com/packethost/packngo/devices.go b/vendor/github.com/packethost/packngo/devices.go index 7607c1250eb3..f411bf65cb7b 100644 --- a/vendor/github.com/packethost/packngo/devices.go +++ b/vendor/github.com/packethost/packngo/devices.go @@ -69,19 +69,6 @@ func (d DeviceActionRequest) String() string { return Stringify(d) } -// IPAddress used to execute actions on devices -type IPAddress struct { - Family int `json:"address_family"` - Cidr int `json:"cidr"` - Address string `json:"address"` - Gateway string `json:"gateway"` - Public bool `json:"public"` -} - -func (n IPAddress) String() string { - return Stringify(n) -} - // DeviceServiceOp implements DeviceService type DeviceServiceOp struct { client *Client diff --git a/vendor/github.com/packethost/packngo/email.go b/vendor/github.com/packethost/packngo/email.go index 98ed87147c2e..ef53ee4bb020 100644 --- a/vendor/github.com/packethost/packngo/email.go +++ b/vendor/github.com/packethost/packngo/email.go @@ -9,11 +9,12 @@ type EmailService interface { // Email represents a user's email address type Email struct { - ID string `json:"id"` - Address string `json:"address"` - Default bool `json:"default,omitempty"` - URL string `json:"href,omitempty"` + ID string `json:"id"` + Address string `json:"address"` + Default bool `json:"default,omitempty"` + URL string `json:"href,omitempty"` } + func (e Email) String() string { return Stringify(e) } @@ -23,7 +24,7 @@ type EmailServiceOp struct { client *Client } -// Get retrieves an email by id +// Get retrieves an email by id func (s *EmailServiceOp) Get(emailID string) (*Email, *Response, error) { req, err := s.client.NewRequest("GET", emailBasePath, nil) if err != nil { diff --git a/vendor/github.com/packethost/packngo/facilities.go b/vendor/github.com/packethost/packngo/facilities.go index a6deab895bfe..42a3c7cb1155 100644 --- a/vendor/github.com/packethost/packngo/facilities.go +++ b/vendor/github.com/packethost/packngo/facilities.go @@ -1,4 +1,4 @@ -package packngo +package packngo const facilityBasePath = "/facilities" @@ -13,13 +13,14 @@ type facilityRoot struct { // Facility represents a Packet facility type Facility struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Code string `json:"code,omitempty"` - Features []string `json:"features,omitempty"` - Address *Address `json:"address,omitempty"` - URL string `json:"href,omitempty"` + ID string `json:"id"` + Name string `json:"name,omitempty"` + Code string `json:"code,omitempty"` + Features []string `json:"features,omitempty"` + Address *Address `json:"address,omitempty"` + URL string `json:"href,omitempty"` } + func (f Facility) String() string { return Stringify(f) } @@ -28,11 +29,11 @@ func (f Facility) String() string { type Address struct { ID string `json:"id,omitempty"` } + func (a Address) String() string { return Stringify(a) } - // FacilityServiceOp implements FacilityService type FacilityServiceOp struct { client *Client diff --git a/vendor/github.com/packethost/packngo/ip.go b/vendor/github.com/packethost/packngo/ip.go new file mode 100644 index 000000000000..e65be774501d --- /dev/null +++ b/vendor/github.com/packethost/packngo/ip.go @@ -0,0 +1,203 @@ +package packngo + +import "fmt" + +const ipBasePath = "/ips" + +// IPService interface defines available IP methods +type IPService interface { + Assign(deviceID string, assignRequest *IPAddressAssignRequest) (*IPAddress, *Response, error) + Unassign(ipAddressID string) (*Response, error) + Get(ipAddressID string) (*IPAddress, *Response, error) +} + +// IPAddress represents a ip address +type IPAddress struct { + ID string `json:"id"` + Address string `json:"address"` + Gateway string `json:"gateway"` + Network string `json:"network"` + AddressFamily int `json:"address_family"` + Netmask string `json:"netmask"` + Public bool `json:"public"` + Cidr int `json:"cidr"` + AssignedTo map[string]string `json:"assigned_to"` + Created string `json:"created_at,omitempty"` + Updated string `json:"updated_at,omitempty"` + Href string `json:"href"` +} + +// IPAddressAssignRequest represents the body if a ip assign request +type IPAddressAssignRequest struct { + Address string `json:"address"` +} + +func (i IPAddress) String() string { + return Stringify(i) +} + +// IPServiceOp implements IPService +type IPServiceOp struct { + client *Client +} + +// Get returns IpAddress by ID +func (i *IPServiceOp) Get(ipAddressID string) (*IPAddress, *Response, error) { + path := fmt.Sprintf("%s/%s", ipBasePath, ipAddressID) + + req, err := i.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + ip := new(IPAddress) + resp, err := i.client.Do(req, ip) + if err != nil { + return nil, resp, err + } + + return ip, resp, err +} + +// Unassign unassigns an IP address record. This will remove the relationship between an IP +// and the device and will make the IP address available to be assigned to another device. +func (i *IPServiceOp) Unassign(ipAddressID string) (*Response, error) { + path := fmt.Sprintf("%s/%s", ipBasePath, ipAddressID) + + req, err := i.client.NewRequest("DELETE", path, nil) + if err != nil { + return nil, err + } + + resp, err := i.client.Do(req, nil) + return resp, err +} + +// Assign assigns an IP address to a device. The IP address must be in one of the IP ranges assigned to the device’s project. +func (i *IPServiceOp) Assign(deviceID string, assignRequest *IPAddressAssignRequest) (*IPAddress, *Response, error) { + path := fmt.Sprintf("%s/%s%s", deviceBasePath, deviceID, ipBasePath) + + req, err := i.client.NewRequest("POST", path, assignRequest) + + ip := new(IPAddress) + resp, err := i.client.Do(req, ip) + if err != nil { + return nil, resp, err + } + + return ip, resp, err +} + +// IP RESERVATIONS API + +// IPReservationService interface defines available IPReservation methods +type IPReservationService interface { + List(projectID string) ([]IPReservation, *Response, error) + RequestMore(projectID string, ipReservationReq *IPReservationRequest) (*IPReservation, *Response, error) + Get(ipReservationID string) (*IPReservation, *Response, error) + Remove(ipReservationID string) (*Response, error) +} + +// IPReservationServiceOp implements the IPReservationService interface +type IPReservationServiceOp struct { + client *Client +} + +// IPReservationRequest represents the body of a reservation request +type IPReservationRequest struct { + Type string `json:"type"` + Quantity int `json:"quantity"` + Comments string `json:"comments"` +} + +// IPReservation represent an IP reservation for a single project +type IPReservation struct { + ID string `json:"id"` + Network string `json:"network"` + Address string `json:"address"` + AddressFamily int `json:"address_family"` + Netmask string `json:"netmask"` + Public bool `json:"public"` + Cidr int `json:"cidr"` + Management bool `json:"management"` + Manageable bool `json:"manageable"` + Addon bool `json:"addon"` + Bill bool `json:"bill"` + Assignments []map[string]string `json:"assignments"` + Created string `json:"created_at,omitempty"` + Updated string `json:"updated_at,omitempty"` + Href string `json:"href"` +} + +type ipReservationRoot struct { + IPReservations []IPReservation `json:"ip_addresses"` +} + +// List provides a list of IP resevations for a single project. +func (i *IPReservationServiceOp) List(projectID string) ([]IPReservation, *Response, error) { + path := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, ipBasePath) + + req, err := i.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + reservations := new(ipReservationRoot) + resp, err := i.client.Do(req, reservations) + if err != nil { + return nil, resp, err + } + return reservations.IPReservations, resp, err +} + +// RequestMore requests more IP space for a project in order to have additional IP addresses to assign to devices +func (i *IPReservationServiceOp) RequestMore(projectID string, ipReservationReq *IPReservationRequest) (*IPReservation, *Response, error) { + path := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, ipBasePath) + + req, err := i.client.NewRequest("POST", path, &ipReservationReq) + if err != nil { + return nil, nil, err + } + + ip := new(IPReservation) + resp, err := i.client.Do(req, ip) + if err != nil { + return nil, resp, err + } + return ip, resp, err +} + +// Get returns a single IP reservation object +func (i *IPReservationServiceOp) Get(ipReservationID string) (*IPReservation, *Response, error) { + path := fmt.Sprintf("%s/%s", ipBasePath, ipReservationID) + + req, err := i.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + reservation := new(IPReservation) + resp, err := i.client.Do(req, reservation) + if err != nil { + return nil, nil, err + } + + return reservation, resp, err +} + +// Remove removes an IP reservation from the project. +func (i *IPReservationServiceOp) Remove(ipReservationID string) (*Response, error) { + path := fmt.Sprintf("%s/%s", ipBasePath, ipReservationID) + + req, err := i.client.NewRequest("DELETE", path, nil) + if err != nil { + return nil, err + } + + resp, err := i.client.Do(req, nil) + if err != nil { + return nil, err + } + + return resp, err +} diff --git a/vendor/github.com/packethost/packngo/operatingsystems.go b/vendor/github.com/packethost/packngo/operatingsystems.go index bad59e86c410..4200a40cf888 100644 --- a/vendor/github.com/packethost/packngo/operatingsystems.go +++ b/vendor/github.com/packethost/packngo/operatingsystems.go @@ -18,16 +18,17 @@ type OS struct { Distro string `json:"distro"` Version string `json:"version"` } + func (o OS) String() string { return Stringify(o) } -// OSServiceOp implements OSService +// OSServiceOp implements OSService type OSServiceOp struct { client *Client } -// List returns all available operating systems +// List returns all available operating systems func (s *OSServiceOp) List() ([]OS, *Response, error) { req, err := s.client.NewRequest("GET", osBasePath, nil) if err != nil { diff --git a/vendor/github.com/packethost/packngo/packngo.go b/vendor/github.com/packethost/packngo/packngo.go index c3d70a58246f..21a0279414f2 100644 --- a/vendor/github.com/packethost/packngo/packngo.go +++ b/vendor/github.com/packethost/packngo/packngo.go @@ -89,6 +89,9 @@ type Client struct { Projects ProjectService Facilities FacilityService OperatingSystems OSService + Ips IPService + IpReservations IPReservationService + Volumes VolumeService } // NewRequest inits a new http request with the proper headers @@ -165,6 +168,10 @@ func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { // an older version of Go, pass in a custom http.Client with a custom TLS configuration // that sets "InsecureSkipVerify" to "true" func NewClient(consumerToken string, apiKey string, httpClient *http.Client) *Client { + client, _ := NewClientWithBaseURL(consumerToken, apiKey, httpClient, baseURL) + return client +} +func NewClientWithBaseURL(consumerToken string, apiKey string, httpClient *http.Client, apiBaseURL string) (*Client, error) { if httpClient == nil { // Don't fall back on http.DefaultClient as it's not nice to adjust state // implicitly. If the client wants to use http.DefaultClient, they can @@ -172,9 +179,12 @@ func NewClient(consumerToken string, apiKey string, httpClient *http.Client) *Cl httpClient = &http.Client{} } - BaseURL, _ := url.Parse(baseURL) + u, err := url.Parse(apiBaseURL) + if err != nil { + return nil, err + } - c := &Client{client: httpClient, BaseURL: BaseURL, UserAgent: userAgent, ConsumerToken: consumerToken, APIKey: apiKey} + c := &Client{client: httpClient, BaseURL: u, UserAgent: userAgent, ConsumerToken: consumerToken, APIKey: apiKey} c.Plans = &PlanServiceOp{client: c} c.Users = &UserServiceOp{client: c} c.Emails = &EmailServiceOp{client: c} @@ -183,8 +193,11 @@ func NewClient(consumerToken string, apiKey string, httpClient *http.Client) *Cl c.Projects = &ProjectServiceOp{client: c} c.Facilities = &FacilityServiceOp{client: c} c.OperatingSystems = &OSServiceOp{client: c} + c.Ips = &IPServiceOp{client: c} + c.IpReservations = &IPReservationServiceOp{client: c} + c.Volumes = &VolumeServiceOp{client: c} - return c + return c, nil } func checkResponse(r *http.Response) error { diff --git a/vendor/github.com/packethost/packngo/plans.go b/vendor/github.com/packethost/packngo/plans.go index 6350443e75d7..5b3fa86fdf9e 100644 --- a/vendor/github.com/packethost/packngo/plans.go +++ b/vendor/github.com/packethost/packngo/plans.go @@ -1,4 +1,4 @@ -package packngo +package packngo const planBasePath = "/plans" @@ -21,18 +21,20 @@ type Plan struct { Specs *Specs `json:"specs,omitempty"` Pricing *Pricing `json:"pricing,omitempty"` } + func (p Plan) String() string { return Stringify(p) } // Specs - the server specs for a plan type Specs struct { - Cpus []*Cpus `json:"cpus,omitempty"` - Memory *Memory `json:"memory,omitempty"` - Drives []*Drives `json:"drives,omitempty"` - Nics []*Nics `json:"nics,omitempty"` - Features *Features `json:"features,omitempty"` + Cpus []*Cpus `json:"cpus,omitempty"` + Memory *Memory `json:"memory,omitempty"` + Drives []*Drives `json:"drives,omitempty"` + Nics []*Nics `json:"nics,omitempty"` + Features *Features `json:"features,omitempty"` } + func (s Specs) String() string { return Stringify(s) } @@ -42,6 +44,7 @@ type Cpus struct { Count int `json:"count,omitempty"` Type string `json:"type,omitempty"` } + func (c Cpus) String() string { return Stringify(c) } @@ -50,6 +53,7 @@ func (c Cpus) String() string { type Memory struct { Total string `json:"total,omitempty"` } + func (m Memory) String() string { return Stringify(m) } @@ -60,6 +64,7 @@ type Drives struct { Size string `json:"size,omitempty"` Type string `json:"type,omitempty"` } + func (d Drives) String() string { return Stringify(d) } @@ -69,6 +74,7 @@ type Nics struct { Count int `json:"count,omitempty"` Type string `json:"type,omitempty"` } + func (n Nics) String() string { return Stringify(n) } @@ -78,6 +84,7 @@ type Features struct { Raid bool `json:"raid,omitempty"` Txt bool `json:"txt,omitempty"` } + func (f Features) String() string { return Stringify(f) } @@ -87,6 +94,7 @@ type Pricing struct { Hourly float32 `json:"hourly,omitempty"` Monthly float32 `json:"monthly,omitempty"` } + func (p Pricing) String() string { return Stringify(p) } diff --git a/vendor/github.com/packethost/packngo/projects.go b/vendor/github.com/packethost/packngo/projects.go index 391a4b3ce810..9c539a8ce025 100644 --- a/vendor/github.com/packethost/packngo/projects.go +++ b/vendor/github.com/packethost/packngo/projects.go @@ -11,11 +11,22 @@ type ProjectService interface { Create(*ProjectCreateRequest) (*Project, *Response, error) Update(*ProjectUpdateRequest) (*Project, *Response, error) Delete(string) (*Response, error) + ListIPAddresses(string) ([]IPAddress, *Response, error) + ListVolumes(string) ([]Volume, *Response, error) +} + +type ipsRoot struct { + IPAddresses []IPAddress `json:"ip_addresses"` +} + +type volumesRoot struct { + Volumes []Volume `json:"volumes"` } type projectsRoot struct { Projects []Project `json:"projects"` } + // Project represents a Packet project type Project struct { ID string `json:"id"` @@ -27,6 +38,7 @@ type Project struct { SSHKeys []SSHKey `json:"ssh_keys,omitempty"` URL string `json:"href,omitempty"` } + func (p Project) String() string { return Stringify(p) } @@ -36,6 +48,7 @@ type ProjectCreateRequest struct { Name string `json:"name"` PaymentMethod string `json:"payment_method,omitempty"` } + func (p ProjectCreateRequest) String() string { return Stringify(p) } @@ -46,6 +59,7 @@ type ProjectUpdateRequest struct { Name string `json:"name,omitempty"` PaymentMethod string `json:"payment_method,omitempty"` } + func (p ProjectUpdateRequest) String() string { return Stringify(p) } @@ -55,6 +69,22 @@ type ProjectServiceOp struct { client *Client } +func (s *ProjectServiceOp) ListIPAddresses(projectID string) ([]IPAddress, *Response, error) { + url := fmt.Sprintf("%s/%s/ips", projectBasePath, projectID) + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + root := new(ipsRoot) + resp, err := s.client.Do(req, root) + if err != nil { + return nil, resp, err + } + + return root.IPAddresses, resp, err +} + // List returns the user's projects func (s *ProjectServiceOp) List() ([]Project, *Response, error) { req, err := s.client.NewRequest("GET", projectBasePath, nil) @@ -134,3 +164,20 @@ func (s *ProjectServiceOp) Delete(projectID string) (*Response, error) { return resp, err } + +// List returns Volumes for a project +func (s *ProjectServiceOp) ListVolumes(projectID string) ([]Volume, *Response, error) { + url := fmt.Sprintf("%s/%s%s", projectBasePath, projectID, volumeBasePath) + req, err := s.client.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + + root := new(volumesRoot) + resp, err := s.client.Do(req, root) + if err != nil { + return nil, resp, err + } + + return root.Volumes, resp, err +} diff --git a/vendor/github.com/packethost/packngo/rate.go b/vendor/github.com/packethost/packngo/rate.go index 4dc55acaa793..965967d4557c 100644 --- a/vendor/github.com/packethost/packngo/rate.go +++ b/vendor/github.com/packethost/packngo/rate.go @@ -2,10 +2,11 @@ package packngo // Rate provides the API request rate limit details type Rate struct { - RequestLimit int `json:"request_limit"` - RequestsRemaining int `json:"requests_remaining"` - Reset Timestamp `json:"rate_reset"` + RequestLimit int `json:"request_limit"` + RequestsRemaining int `json:"requests_remaining"` + Reset Timestamp `json:"rate_reset"` } + func (r Rate) String() string { return Stringify(r) } diff --git a/vendor/github.com/packethost/packngo/sshkeys.go b/vendor/github.com/packethost/packngo/sshkeys.go index 33c45e169058..06d9962be8d7 100644 --- a/vendor/github.com/packethost/packngo/sshkeys.go +++ b/vendor/github.com/packethost/packngo/sshkeys.go @@ -19,34 +19,37 @@ type sshKeyRoot struct { // SSHKey represents a user's ssh key type SSHKey struct { - ID string `json:"id"` - Label string `json:"label"` - Key string `json:"key"` - FingerPrint string `json:"fingerprint"` - Created string `json:"created_at"` - Updated string `json:"updated_at"` - User User `json:"user,omitempty"` - URL string `json:"href,omitempty"` + ID string `json:"id"` + Label string `json:"label"` + Key string `json:"key"` + FingerPrint string `json:"fingerprint"` + Created string `json:"created_at"` + Updated string `json:"updated_at"` + User User `json:"user,omitempty"` + URL string `json:"href,omitempty"` } + func (s SSHKey) String() string { return Stringify(s) } // SSHKeyCreateRequest type used to create an ssh key type SSHKeyCreateRequest struct { - Label string `json:"label"` - Key string `json:"key"` + Label string `json:"label"` + Key string `json:"key"` } + func (s SSHKeyCreateRequest) String() string { return Stringify(s) } // SSHKeyUpdateRequest type used to update an ssh key type SSHKeyUpdateRequest struct { - ID string `json:"id"` - Label string `json:"label"` - Key string `json:"key"` + ID string `json:"id"` + Label string `json:"label"` + Key string `json:"key"` } + func (s SSHKeyUpdateRequest) String() string { return Stringify(s) } diff --git a/vendor/github.com/packethost/packngo/user.go b/vendor/github.com/packethost/packngo/user.go index 5bd2d11fa151..36e03d909097 100644 --- a/vendor/github.com/packethost/packngo/user.go +++ b/vendor/github.com/packethost/packngo/user.go @@ -9,23 +9,24 @@ type UserService interface { // User represents a Packet user type User struct { - ID string `json:"id"` - FirstName string `json:"first_name,omitempty"` - LastName string `json:"last_name,omitempty"` - FullName string `json:"full_name,omitempty"` - Email string `json:"email,omitempty"` - TwoFactor string `json:"two_factor_auth,omitempty"` - AvatarURL string `json:"avatar_url,omitempty"` - Facebook string `json:"twitter,omitempty"` - Twitter string `json:"facebook,omitempty"` - LinkedIn string `json:"linkedin,omitempty"` - Created string `json:"created_at,omitempty"` - Updated string `json:"updated_at,omitempty"` - TimeZone string `json:"timezone,omitempty"` - Emails []Email `json:"email,omitempty"` - PhoneNumber string `json:"phone_number,omitempty"` - URL string `json:"href,omitempty"` + ID string `json:"id"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + FullName string `json:"full_name,omitempty"` + Email string `json:"email,omitempty"` + TwoFactor string `json:"two_factor_auth,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` + Facebook string `json:"twitter,omitempty"` + Twitter string `json:"facebook,omitempty"` + LinkedIn string `json:"linkedin,omitempty"` + Created string `json:"created_at,omitempty"` + Updated string `json:"updated_at,omitempty"` + TimeZone string `json:"timezone,omitempty"` + Emails []Email `json:"email,omitempty"` + PhoneNumber string `json:"phone_number,omitempty"` + URL string `json:"href,omitempty"` } + func (u User) String() string { return Stringify(u) } diff --git a/vendor/github.com/packethost/packngo/volumes.go b/vendor/github.com/packethost/packngo/volumes.go new file mode 100644 index 000000000000..96ab3b6e8e42 --- /dev/null +++ b/vendor/github.com/packethost/packngo/volumes.go @@ -0,0 +1,146 @@ +package packngo + +import "fmt" + +const volumeBasePath = "/storage" + +// VolumeService interface defines available Volume methods +type VolumeService interface { + Get(string) (*Volume, *Response, error) + Update(*VolumeUpdateRequest) (*Volume, *Response, error) + Delete(string) (*Response, error) + Create(*VolumeCreateRequest) (*Volume, *Response, error) +} + +// Volume represents a volume +type Volume struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Size int `json:"size,omitempty"` + State string `json:"state,omitempty"` + Locked bool `json:"locked,omitempty"` + BillingCycle string `json:"billing_cycle,omitempty"` + Created string `json:"created_at,omitempty"` + Updated string `json:"updated_at,omitempty"` + Href string `json:"href,omitempty"` + SnapshotPolicies []*SnapshotPolicy `json:"snapshot_policies,omitempty"` + Attachments []*Attachment `json:"attachments,omitempty"` + Plan *Plan `json:"plan,omitempty"` + Facility *Facility `json:"facility,omitempty"` + Project *Project `json:"project,omitempty"` +} + +// SnapshotPolicy used to execute actions on volume +type SnapshotPolicy struct { + ID string `json:"id"` + Href string `json:"href"` + SnapshotFrequency string `json:"snapshot_frequency,omitempty"` + SnapshotCount int `json:"snapshot_count,omitempty"` +} + +// Attachment used to execute actions on volume +type Attachment struct { + ID string `json:"id"` + Href string `json:"href"` +} + +func (v Volume) String() string { + return Stringify(v) +} + +// VolumeCreateRequest type used to create a Packet volume +type VolumeCreateRequest struct { + Size int `json:"size"` + BillingCycle string `json:"billing_cycle"` + ProjectID string `json:"project_id"` + PlanID string `json:"plan_id"` + FacilityID string `json:"facility_id"` + Description string `json:"description,omitempty"` + SnapshotPolicies []*SnapshotPolicy `json:"snapshot_policies,omitempty"` +} + +func (v VolumeCreateRequest) String() string { + return Stringify(v) +} + +// VolumeUpdateRequest type used to update a Packet volume +type VolumeUpdateRequest struct { + ID string `json:"id"` + Description string `json:"description,omitempty"` + Plan string `json:"plan,omitempty"` +} + +func (v VolumeUpdateRequest) String() string { + return Stringify(v) +} + +// VolumeServiceOp implements VolumeService +type VolumeServiceOp struct { + client *Client +} + +// Get returns a volume by id +func (v *VolumeServiceOp) Get(volumeID string) (*Volume, *Response, error) { + path := fmt.Sprintf("%s/%s?include=facility,snapshot_policies,attachments.device", volumeBasePath, volumeID) + req, err := v.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + volume := new(Volume) + resp, err := v.client.Do(req, volume) + if err != nil { + return nil, resp, err + } + + return volume, resp, err +} + +// Update updates a volume +func (v *VolumeServiceOp) Update(updateRequest *VolumeUpdateRequest) (*Volume, *Response, error) { + path := fmt.Sprintf("%s/%s", volumeBasePath, updateRequest.ID) + req, err := v.client.NewRequest("PATCH", path, updateRequest) + if err != nil { + return nil, nil, err + } + + volume := new(Volume) + resp, err := v.client.Do(req, volume) + if err != nil { + return nil, resp, err + } + + return volume, resp, err +} + +// Delete deletes a volume +func (v *VolumeServiceOp) Delete(volumeID string) (*Response, error) { + path := fmt.Sprintf("%s/%s", volumeBasePath, volumeID) + + req, err := v.client.NewRequest("DELETE", path, nil) + if err != nil { + return nil, err + } + + resp, err := v.client.Do(req, nil) + + return resp, err +} + +// Create creates a new volume for a project +func (v *VolumeServiceOp) Create(createRequest *VolumeCreateRequest) (*Volume, *Response, error) { + url := fmt.Sprintf("%s/%s%s", projectBasePath, createRequest.ProjectID, volumeBasePath) + req, err := v.client.NewRequest("POST", url, createRequest) + if err != nil { + return nil, nil, err + } + + volume := new(Volume) + resp, err := v.client.Do(req, volume) + if err != nil { + return nil, resp, err + } + + return volume, resp, err +} diff --git a/vendor/vendor.json b/vendor/vendor.json index ee31a419fd65..d8c7daa5f0f9 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1464,8 +1464,10 @@ "revision": "3d184cea22ee1c41ec1697e0d830ff0c78f7ea97" }, { + "checksumSHA1": "MexE5QPVAwVfQcJBnMGMgD+s9L0=", "path": "github.com/packethost/packngo", - "revision": "f03d7dc788a8b57b62d301ccb98c950c325756f8" + "revision": "7cd5fed006859e86dd5641a6cf9812e855b7574a", + "revisionTime": "2016-08-11T16:27:25Z" }, { "path": "github.com/pborman/uuid", diff --git a/website/source/docs/providers/packet/r/volume.html b/website/source/docs/providers/packet/r/volume.html new file mode 100644 index 000000000000..3179518eaef5 --- /dev/null +++ b/website/source/docs/providers/packet/r/volume.html @@ -0,0 +1,60 @@ +--- +layout: "packet" +page_title: "Packet: packet_volume" +sidebar_current: "docs-packet-resource-volume" +description: |- + Provides a Packet Block Storage Volume Resource. +--- + +# packet\_volume + +Provides a Packet Block Storage Volume resource to allow you to +manage block volumes on your account. +Once created by Terraform, they must then be attached and mounted +using the api and `packet_block_attach` and `packet_block_detach` +scripts. + +## Example Usage + +``` +# Create a new block volume +resource "packet_volume" "volume1" { + description = "terraform-volume-1" + facility = "ewr1" + project_id = "${packet_project.cool_project.id}" + plan = 'storage_1' + size = 100 + billing_cycle = "hourly" + snapshot_policies = { snapshot_frequency = "1day", snapshot_count = 7 } + snapshot_policies = { snapshot_frequency = "1month", snapshot_count = 6 } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `plan` - (Required) The service plan slug of the volume +* `facility` - (Required) The facility to create the volume in +* `project_id` - (Required) The packet project ID to deploy the volume in +* `size` - (Required) The size in GB to make the volume +* `billing_cycle` - The billing cycle, defaults to "hourly" +* `description` - Optional description for the volume +* `snapshot_policies` - Optional list of snapshot policies + +## Attributes Reference + +The following attributes are exported: + +* `id` - The unique ID of the volume +* `name` - The name of the volume +* `description` - The description of the volume +* `size` - The size in GB of the volume +* `plan` - Performance plan the volume is on +* `billing_cycle` - The billing cycle, defaults to hourly +* `facility` - The facility slug the volume resides in +* `state` - The state of the volume +* `locked` - Whether the volume is locked or not +* `project_id ` - The project id the volume is in +* `created` - The timestamp for when the volume was created +* `updated` - The timestamp for the last time the volume was updated diff --git a/website/source/layouts/packet.erb b/website/source/layouts/packet.erb index f7464f198e5e..8591e23e3d23 100644 --- a/website/source/layouts/packet.erb +++ b/website/source/layouts/packet.erb @@ -22,6 +22,9 @@ > packet_ssh_key + > + packet_volume +