provider/ovh: new provider (#12669)

* vendor: add go-ovh

* provider/ovh: new provider

* provider/ovh: Adding PublicCloud User Resource

* provider/ovh: Adding VRack Attachment Resource

* provider/ovh: Adding PublicCloud Network Resource

* provider/ovh: Adding PublicCloud Subnet Resource

* provider/ovh: Adding PublicCloud Regions Datasource

* provider/ovh: Adding PublicCloud Region Datasource

* provider/ovh: Fix Acctests using project's regions
This commit is contained in:
yanndegat 2017-05-16 16:26:43 +02:00 committed by Paul Stack
parent 048767d591
commit 21d8125d5c
33 changed files with 3137 additions and 0 deletions

View File

@ -0,0 +1,12 @@
package main
import (
"github.com/hashicorp/terraform/builtin/providers/ovh"
"github.com/hashicorp/terraform/plugin"
)
func main() {
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: ovh.Provider,
})
}

View File

@ -0,0 +1,67 @@
package ovh
import (
"fmt"
"log"
"github.com/ovh/go-ovh/ovh"
)
type Config struct {
Endpoint string
ApplicationKey string
ApplicationSecret string
ConsumerKey string
OVHClient *ovh.Client
}
/* type used to verify client access to ovh api
*/
type PartialMe struct {
Firstname string `json:"firstname"`
}
func clientDefault(c *Config) (*ovh.Client, error) {
client, err := ovh.NewClient(
c.Endpoint,
c.ApplicationKey,
c.ApplicationSecret,
c.ConsumerKey,
)
if err != nil {
return nil, err
}
return client, nil
}
func (c *Config) loadAndValidate() error {
validEndpoint := false
ovhEndpoints := [2]string{ovh.OvhEU, ovh.OvhCA}
for _, e := range ovhEndpoints {
if ovh.Endpoints[c.Endpoint] == e {
validEndpoint = true
}
}
if !validEndpoint {
return fmt.Errorf("%s must be one of %#v endpoints\n", c.Endpoint, ovh.Endpoints)
}
targetClient, err := clientDefault(c)
if err != nil {
return fmt.Errorf("Error getting ovh client: %q\n", err)
}
var me PartialMe
err = targetClient.Get("/me", &me)
if err != nil {
return fmt.Errorf("OVH client seems to be misconfigured: %q\n", err)
}
log.Printf("[DEBUG] Logged in on OVH API as %s!", me.Firstname)
c.OVHClient = targetClient
return nil
}

View File

@ -0,0 +1,99 @@
package ovh
import (
"fmt"
"log"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
)
func dataSourcePublicCloudRegion() *schema.Resource {
return &schema.Resource{
Read: dataSourcePublicCloudRegionRead,
Schema: map[string]*schema.Schema{
"project_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
DefaultFunc: schema.EnvDefaultFunc("OVH_PROJECT_ID", nil),
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"services": &schema.Schema{
Type: schema.TypeSet,
Set: publicCloudServiceHash,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"status": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
},
},
"continentCode": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"datacenterLocation": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func dataSourcePublicCloudRegionRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
projectId := d.Get("project_id").(string)
name := d.Get("name").(string)
log.Printf("[DEBUG] Will read public cloud region %s for project: %s", name, projectId)
d.Partial(true)
response := &PublicCloudRegionResponse{}
endpoint := fmt.Sprintf("/cloud/project/%s/region/%s", projectId, name)
err := config.OVHClient.Get(endpoint, response)
if err != nil {
return fmt.Errorf("Error calling %s:\n\t %q", endpoint, err)
}
d.Set("datacenterLocation", response.DatacenterLocation)
d.Set("continentCode", response.ContinentCode)
services := &schema.Set{
F: publicCloudServiceHash,
}
for i := range response.Services {
service := map[string]interface{}{
"name": response.Services[i].Name,
"status": response.Services[i].Status,
}
services.Add(service)
}
d.Set("services", services)
d.Partial(false)
d.SetId(fmt.Sprintf("%s_%s", projectId, name))
return nil
}
func publicCloudServiceHash(v interface{}) int {
r := v.(map[string]interface{})
return hashcode.String(r["name"].(string))
}

View File

@ -0,0 +1,55 @@
package ovh
import (
"fmt"
"os"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccPublicCloudRegionDataSource_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccPublicCloudRegionDatasourceConfig,
Check: resource.ComposeTestCheckFunc(
testAccPublicCloudRegionDatasource("data.ovh_publiccloud_region.region_attr.0"),
testAccPublicCloudRegionDatasource("data.ovh_publiccloud_region.region_attr.1"),
testAccPublicCloudRegionDatasource("data.ovh_publiccloud_region.region_attr.2"),
),
},
},
})
}
func testAccPublicCloudRegionDatasource(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Can't find regions data source: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("Cannot find region attributes for project %s and region %s", rs.Primary.Attributes["project_id"], rs.Primary.Attributes["region"])
}
return nil
}
}
var testAccPublicCloudRegionDatasourceConfig = fmt.Sprintf(`
data "ovh_publiccloud_regions" "regions" {
project_id = "%s"
}
data "ovh_publiccloud_region" "region_attr" {
count = 3
project_id = "${data.ovh_publiccloud_regions.regions.project_id}"
name = "${element(data.ovh_publiccloud_regions.regions.names, count.index)}"
}
`, os.Getenv("OVH_PUBLIC_CLOUD"))

View File

@ -0,0 +1,51 @@
package ovh
import (
"fmt"
"log"
"github.com/hashicorp/terraform/helper/schema"
)
func dataSourcePublicCloudRegions() *schema.Resource {
return &schema.Resource{
Read: dataSourcePublicCloudRegionsRead,
Schema: map[string]*schema.Schema{
"project_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
DefaultFunc: schema.EnvDefaultFunc("OVH_PROJECT_ID", nil),
},
"names": &schema.Schema{
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
}
}
func dataSourcePublicCloudRegionsRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
projectId := d.Get("project_id").(string)
log.Printf("[DEBUG] Will read public cloud regions for project: %s", projectId)
d.Partial(true)
endpoint := fmt.Sprintf("/cloud/project/%s/region", projectId)
names := make([]string, 0)
err := config.OVHClient.Get(endpoint, &names)
if err != nil {
return fmt.Errorf("Error calling %s:\n\t %q", endpoint, err)
}
d.Set("names", names)
d.Partial(false)
d.SetId(projectId)
log.Printf("[DEBUG] Read Public Cloud Regions %s", names)
return nil
}

View File

@ -0,0 +1,47 @@
package ovh
import (
"fmt"
"os"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccPublicCloudRegionsDataSource_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccPublicCloudRegionsDatasourceConfig,
Check: resource.ComposeTestCheckFunc(
testAccPublicCloudRegionsDatasource("data.ovh_publiccloud_regions.regions"),
),
},
},
})
}
func testAccPublicCloudRegionsDatasource(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Can't find regions data source: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("Cannot find regions for project %s", rs.Primary.Attributes["project_id"])
}
return nil
}
}
var testAccPublicCloudRegionsDatasourceConfig = fmt.Sprintf(`
data "ovh_publiccloud_regions" "regions" {
project_id = "%s"
}
`, os.Getenv("OVH_PUBLIC_CLOUD"))

View File

@ -0,0 +1,80 @@
package ovh
import (
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
// Provider returns a schema.Provider for OVH.
func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"endpoint": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("OVH_ENDPOINT", nil),
Description: descriptions["endpoint"],
},
"application_key": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("OVH_APPLICATION_KEY", ""),
Description: descriptions["application_key"],
},
"application_secret": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("OVH_APPLICATION_SECRET", ""),
Description: descriptions["application_secret"],
},
"consumer_key": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("OVH_CONSUMER_KEY", ""),
Description: descriptions["consumer_key"],
},
},
DataSourcesMap: map[string]*schema.Resource{
"ovh_publiccloud_region": dataSourcePublicCloudRegion(),
"ovh_publiccloud_regions": dataSourcePublicCloudRegions(),
},
ResourcesMap: map[string]*schema.Resource{
"ovh_publiccloud_private_network": resourcePublicCloudPrivateNetwork(),
"ovh_publiccloud_private_network_subnet": resourcePublicCloudPrivateNetworkSubnet(),
"ovh_publiccloud_user": resourcePublicCloudUser(),
"ovh_vrack_publiccloud_attachment": resourceVRackPublicCloudAttachment(),
},
ConfigureFunc: configureProvider,
}
}
var descriptions map[string]string
func init() {
descriptions = map[string]string{
"endpoint": "The OVH API endpoint to target (ex: \"ovh-eu\").",
"application_key": "The OVH API Application Key.",
"application_secret": "The OVH API Application Secret.",
"consumer_key": "The OVH API Consumer key.",
}
}
func configureProvider(d *schema.ResourceData) (interface{}, error) {
config := Config{
Endpoint: d.Get("endpoint").(string),
ApplicationKey: d.Get("application_key").(string),
ApplicationSecret: d.Get("application_secret").(string),
ConsumerKey: d.Get("consumer_key").(string),
}
if err := config.loadAndValidate(); err != nil {
return nil, err
}
return &config, nil
}

View File

@ -0,0 +1,119 @@
package ovh
import (
"fmt"
"log"
"os"
"testing"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
"github.com/ovh/go-ovh/ovh"
)
var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider
var testAccOVHClient *ovh.Client
func init() {
log.SetOutput(os.Stdout)
testAccProvider = Provider().(*schema.Provider)
testAccProviders = map[string]terraform.ResourceProvider{
"ovh": testAccProvider,
}
}
func TestProvider(t *testing.T) {
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestProvider_impl(t *testing.T) {
var _ terraform.ResourceProvider = Provider()
}
func testAccPreCheck(t *testing.T) {
v := os.Getenv("OVH_ENDPOINT")
if v == "" {
t.Fatal("OVH_ENDPOINT must be set for acceptance tests")
}
v = os.Getenv("OVH_APPLICATION_KEY")
if v == "" {
t.Fatal("OVH_APPLICATION_KEY must be set for acceptance tests")
}
v = os.Getenv("OVH_APPLICATION_SECRET")
if v == "" {
t.Fatal("OVH_APPLICATION_SECRET must be set for acceptance tests")
}
v = os.Getenv("OVH_CONSUMER_KEY")
if v == "" {
t.Fatal("OVH_CONSUMER_KEY must be set for acceptance tests")
}
v = os.Getenv("OVH_VRACK")
if v == "" {
t.Fatal("OVH_VRACK must be set for acceptance tests")
}
v = os.Getenv("OVH_PUBLIC_CLOUD")
if v == "" {
t.Fatal("OVH_PUBLIC_CLOUD must be set for acceptance tests")
}
if testAccOVHClient == nil {
config := Config{
Endpoint: os.Getenv("OVH_ENDPOINT"),
ApplicationKey: os.Getenv("OVH_APPLICATION_KEY"),
ApplicationSecret: os.Getenv("OVH_APPLICATION_SECRET"),
ConsumerKey: os.Getenv("OVH_CONSUMER_KEY"),
}
if err := config.loadAndValidate(); err != nil {
t.Fatalf("couln't load OVH Client: %s", err)
} else {
testAccOVHClient = config.OVHClient
}
}
}
func testAccCheckVRackExists(t *testing.T) {
type vrackResponse struct {
Name string `json:"name"`
Description string `json:"description"`
}
r := vrackResponse{}
endpoint := fmt.Sprintf("/vrack/%s", os.Getenv("OVH_VRACK"))
err := testAccOVHClient.Get(endpoint, &r)
if err != nil {
t.Fatalf("Error: %q\n", err)
}
t.Logf("Read VRack %s -> name:'%s', desc:'%s' ", endpoint, r.Name, r.Description)
}
func testAccCheckPublicCloudExists(t *testing.T) {
type cloudProjectResponse struct {
ID string `json:"project_id"`
Status string `json:"status"`
Description string `json:"description"`
}
r := cloudProjectResponse{}
endpoint := fmt.Sprintf("/cloud/project/%s", os.Getenv("OVH_PUBLIC_CLOUD"))
err := testAccOVHClient.Get(endpoint, &r)
if err != nil {
t.Fatalf("Error: %q\n", err)
}
t.Logf("Read Cloud Project %s -> status: '%s', desc: '%s'", endpoint, r.Status, r.Description)
}

View File

@ -0,0 +1,297 @@
package ovh
import (
"fmt"
"log"
"time"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/ovh/go-ovh/ovh"
)
func resourcePublicCloudPrivateNetwork() *schema.Resource {
return &schema.Resource{
Create: resourcePublicCloudPrivateNetworkCreate,
Read: resourcePublicCloudPrivateNetworkRead,
Update: resourcePublicCloudPrivateNetworkUpdate,
Delete: resourcePublicCloudPrivateNetworkDelete,
Importer: &schema.ResourceImporter{
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
return []*schema.ResourceData{d}, nil
},
},
Schema: map[string]*schema.Schema{
"project_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
DefaultFunc: schema.EnvDefaultFunc("OVH_PROJECT_ID", nil),
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: false,
},
"vlan_id": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
Default: 0,
},
"regions": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Computed: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"regions_status": &schema.Schema{
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"status": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"region": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
},
},
"status": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"type": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourcePublicCloudPrivateNetworkCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
projectId := d.Get("project_id").(string)
params := &PublicCloudPrivateNetworkCreateOpts{
ProjectId: d.Get("project_id").(string),
VlanId: d.Get("vlan_id").(int),
Name: d.Get("name").(string),
Regions: regionsOptsFromSchema(d),
}
r := &PublicCloudPrivateNetworkResponse{}
log.Printf("[DEBUG] Will create public cloud private network: %s", params)
endpoint := fmt.Sprintf("/cloud/project/%s/network/private", params.ProjectId)
err := config.OVHClient.Post(endpoint, params, r)
if err != nil {
return fmt.Errorf("calling %s with params %s:\n\t %q", endpoint, params, err)
}
log.Printf("[DEBUG] Waiting for Private Network %s:", r)
stateConf := &resource.StateChangeConf{
Pending: []string{"BUILDING"},
Target: []string{"ACTIVE"},
Refresh: waitForPublicCloudPrivateNetworkActive(config.OVHClient, projectId, r.Id),
Timeout: 10 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("waiting for private network (%s): %s", params, err)
}
log.Printf("[DEBUG] Created Private Network %s", r)
//set id
d.SetId(r.Id)
return nil
}
func resourcePublicCloudPrivateNetworkRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
projectId := d.Get("project_id").(string)
r := &PublicCloudPrivateNetworkResponse{}
log.Printf("[DEBUG] Will read public cloud private network for project: %s, id: %s", projectId, d.Id())
endpoint := fmt.Sprintf("/cloud/project/%s/network/private/%s", projectId, d.Id())
d.Partial(true)
err := config.OVHClient.Get(endpoint, r)
if err != nil {
return fmt.Errorf("Error calling %s:\n\t %q", endpoint, err)
}
err = readPublicCloudPrivateNetwork(config, d, r)
if err != nil {
return err
}
d.Partial(false)
log.Printf("[DEBUG] Read Public Cloud Private Network %s", r)
return nil
}
func resourcePublicCloudPrivateNetworkUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
projectId := d.Get("project_id").(string)
params := &PublicCloudPrivateNetworkUpdateOpts{
Name: d.Get("name").(string),
}
log.Printf("[DEBUG] Will update public cloud private network: %s", params)
endpoint := fmt.Sprintf("/cloud/project/%s/network/private/%s", projectId, d.Id())
err := config.OVHClient.Put(endpoint, params, nil)
if err != nil {
return fmt.Errorf("calling %s with params %s:\n\t %q", endpoint, params, err)
}
log.Printf("[DEBUG] Updated Public cloud %s Private Network %s:", projectId, d.Id())
return resourcePublicCloudPrivateNetworkRead(d, meta)
}
func resourcePublicCloudPrivateNetworkDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
projectId := d.Get("project_id").(string)
id := d.Id()
log.Printf("[DEBUG] Will delete public cloud private network for project: %s, id: %s", projectId, id)
endpoint := fmt.Sprintf("/cloud/project/%s/network/private/%s", projectId, id)
err := config.OVHClient.Delete(endpoint, nil)
if err != nil {
return fmt.Errorf("calling %s:\n\t %q", endpoint, err)
}
stateConf := &resource.StateChangeConf{
Pending: []string{"DELETING"},
Target: []string{"DELETED"},
Refresh: waitForPublicCloudPrivateNetworkDelete(config.OVHClient, projectId, id),
Timeout: 10 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("deleting for private network (%s): %s", id, err)
}
d.SetId("")
log.Printf("[DEBUG] Deleted Public Cloud %s Private Network %s", projectId, id)
return nil
}
func regionsOptsFromSchema(d *schema.ResourceData) []string {
var regions []string
if v := d.Get("regions"); v != nil {
rs := v.(*schema.Set).List()
if len(rs) > 0 {
for _, v := range v.(*schema.Set).List() {
regions = append(regions, v.(string))
}
}
}
return regions
}
func readPublicCloudPrivateNetwork(config *Config, d *schema.ResourceData, r *PublicCloudPrivateNetworkResponse) error {
d.Set("name", r.Name)
d.Set("status", r.Status)
d.Set("type", r.Type)
d.Set("vlan_id", r.Vlanid)
regions_status := make([]map[string]interface{}, 0)
regions := make([]string, 0)
for i := range r.Regions {
region := make(map[string]interface{})
region["region"] = r.Regions[i].Region
region["status"] = r.Regions[i].Status
regions_status = append(regions_status, region)
regions = append(regions, fmt.Sprintf(r.Regions[i].Region))
}
d.Set("regions_status", regions_status)
d.Set("regions", regions)
d.SetId(r.Id)
return nil
}
func publicCloudPrivateNetworkExists(projectId, id string, c *ovh.Client) error {
r := &PublicCloudPrivateNetworkResponse{}
log.Printf("[DEBUG] Will read public cloud private network for project: %s, id: %s", projectId, id)
endpoint := fmt.Sprintf("/cloud/project/%s/network/private/%s", projectId, id)
err := c.Get(endpoint, r)
if err != nil {
return fmt.Errorf("calling %s:\n\t %q", endpoint, err)
}
log.Printf("[DEBUG] Read public cloud private network: %s", r)
return nil
}
// AttachmentStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
// an Attachment Task.
func waitForPublicCloudPrivateNetworkActive(c *ovh.Client, projectId, PublicCloudPrivateNetworkId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
r := &PublicCloudPrivateNetworkResponse{}
endpoint := fmt.Sprintf("/cloud/project/%s/network/private/%s", projectId, PublicCloudPrivateNetworkId)
err := c.Get(endpoint, r)
if err != nil {
return r, "", err
}
log.Printf("[DEBUG] Pending Private Network: %s", r)
return r, r.Status, nil
}
}
// AttachmentStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
// an Attachment Task.
func waitForPublicCloudPrivateNetworkDelete(c *ovh.Client, projectId, PublicCloudPrivateNetworkId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
r := &PublicCloudPrivateNetworkResponse{}
endpoint := fmt.Sprintf("/cloud/project/%s/network/private/%s", projectId, PublicCloudPrivateNetworkId)
err := c.Get(endpoint, r)
if err != nil {
if err.(*ovh.APIError).Code == 404 {
log.Printf("[DEBUG] private network id %s on project %s deleted", PublicCloudPrivateNetworkId, projectId)
return r, "DELETED", nil
} else {
return r, "", err
}
}
log.Printf("[DEBUG] Pending Private Network: %s", r)
return r, r.Status, nil
}
}

View File

@ -0,0 +1,281 @@
package ovh
import (
"fmt"
"log"
"github.com/hashicorp/terraform/helper/schema"
"net"
"github.com/ovh/go-ovh/ovh"
)
func resourcePublicCloudPrivateNetworkSubnet() *schema.Resource {
return &schema.Resource{
Create: resourcePublicCloudPrivateNetworkSubnetCreate,
Read: resourcePublicCloudPrivateNetworkSubnetRead,
Delete: resourcePublicCloudPrivateNetworkSubnetDelete,
Importer: &schema.ResourceImporter{
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
return []*schema.ResourceData{d}, nil
},
},
Schema: map[string]*schema.Schema{
"project_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
DefaultFunc: schema.EnvDefaultFunc("OVH_PROJECT_ID", ""),
},
"network_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"dhcp": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
Default: false,
},
"start": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: resourcePubliccloudPrivateNetworkSubnetValidateIP,
},
"end": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: resourcePubliccloudPrivateNetworkSubnetValidateIP,
},
"network": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: resourcePubliccloudPrivateNetworkSubnetValidateNetwork,
},
"region": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"no_gateway": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
Default: false,
},
"gateway_ip": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"cidr": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"ip_pools": &schema.Schema{
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"network": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"region": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"dhcp": &schema.Schema{
Type: schema.TypeBool,
Computed: true,
},
"end": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"start": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
},
},
},
}
}
func resourcePublicCloudPrivateNetworkSubnetCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
projectId := d.Get("project_id").(string)
networkId := d.Get("network_id").(string)
params := &PublicCloudPrivateNetworksCreateOpts{
ProjectId: projectId,
NetworkId: networkId,
Dhcp: d.Get("dhcp").(bool),
NoGateway: d.Get("no_gateway").(bool),
Start: d.Get("start").(string),
End: d.Get("end").(string),
Network: d.Get("network").(string),
Region: d.Get("region").(string),
}
r := &PublicCloudPrivateNetworksResponse{}
log.Printf("[DEBUG] Will create public cloud private network subnet: %s", params)
endpoint := fmt.Sprintf("/cloud/project/%s/network/private/%s/subnet", projectId, networkId)
err := config.OVHClient.Post(endpoint, params, r)
if err != nil {
return fmt.Errorf("calling %s with params %s:\n\t %q", endpoint, params, err)
}
log.Printf("[DEBUG] Created Private Network Subnet %s", r)
//set id
d.SetId(r.Id)
return nil
}
func resourcePublicCloudPrivateNetworkSubnetRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
projectId := d.Get("project_id").(string)
networkId := d.Get("network_id").(string)
r := []*PublicCloudPrivateNetworksResponse{}
log.Printf("[DEBUG] Will read public cloud private network subnet for project: %s, network: %s, id: %s", projectId, networkId, d.Id())
endpoint := fmt.Sprintf("/cloud/project/%s/network/private/%s/subnet", projectId, networkId)
err := config.OVHClient.Get(endpoint, &r)
if err != nil {
return fmt.Errorf("calling %s:\n\t %q", endpoint, err)
}
err = readPublicCloudPrivateNetworkSubnet(d, r)
if err != nil {
return err
}
log.Printf("[DEBUG] Read Public Cloud Private Network %v", r)
return nil
}
func resourcePublicCloudPrivateNetworkSubnetDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
projectId := d.Get("project_id").(string)
networkId := d.Get("network_id").(string)
id := d.Id()
log.Printf("[DEBUG] Will delete public cloud private network subnet for project: %s, network: %s, id: %s", projectId, networkId, id)
endpoint := fmt.Sprintf("/cloud/project/%s/network/private/%s/subnet/%s", projectId, id, id)
err := config.OVHClient.Delete(endpoint, nil)
if err != nil {
return fmt.Errorf("calling %s:\n\t %q", endpoint, err)
}
d.SetId("")
log.Printf("[DEBUG] Deleted Public Cloud %s Private Network %s Subnet %s", projectId, networkId, id)
return nil
}
func publicCloudPrivateNetworkSubnetExists(projectId, networkId, id string, c *ovh.Client) error {
r := []*PublicCloudPrivateNetworksResponse{}
log.Printf("[DEBUG] Will read public cloud private network subnet for project: %s, network: %s, id: %s", projectId, networkId, id)
endpoint := fmt.Sprintf("/cloud/project/%s/network/private/%s/subnet", projectId, networkId)
err := c.Get(endpoint, &r)
if err != nil {
return fmt.Errorf("calling %s:\n\t %q", endpoint, err)
}
s := findPublicCloudPrivateNetworkSubnet(r, id)
if s == nil {
return fmt.Errorf("Subnet %s doesn't exists for project %s and network %s", id, projectId, networkId)
}
return nil
}
func findPublicCloudPrivateNetworkSubnet(rs []*PublicCloudPrivateNetworksResponse, id string) *PublicCloudPrivateNetworksResponse {
for i := range rs {
if rs[i].Id == id {
return rs[i]
}
}
return nil
}
func readPublicCloudPrivateNetworkSubnet(d *schema.ResourceData, rs []*PublicCloudPrivateNetworksResponse) error {
r := findPublicCloudPrivateNetworkSubnet(rs, d.Id())
if r == nil {
return fmt.Errorf("%s subnet not found", d.Id())
}
d.Set("gateway_ip", r.GatewayIp)
d.Set("cidr", r.Cidr)
ippools := make([]map[string]interface{}, 0)
for i := range r.IPPools {
ippool := make(map[string]interface{})
ippool["network"] = r.IPPools[i].Network
ippool["region"] = r.IPPools[i].Region
ippool["dhcp"] = r.IPPools[i].Dhcp
ippool["start"] = r.IPPools[i].Start
ippool["end"] = r.IPPools[i].End
ippools = append(ippools, ippool)
}
d.Set("network", ippools[0]["network"])
d.Set("region", ippools[0]["region"])
d.Set("dhcp", ippools[0]["dhcp"])
d.Set("start", ippools[0]["start"])
d.Set("end", ippools[0]["end"])
d.Set("ip_pools", ippools)
if r.GatewayIp == "" {
d.Set("no_gateway", true)
} else {
d.Set("no_gateway", false)
}
d.SetId(r.Id)
return nil
}
func resourcePubliccloudPrivateNetworkSubnetValidateIP(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
ip := net.ParseIP(value)
if ip == nil {
errors = append(errors, fmt.Errorf("%q must be a valid IP", k))
}
return
}
func resourcePubliccloudPrivateNetworkSubnetValidateNetwork(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
_, _, err := net.ParseCIDR(value)
if err != nil {
errors = append(errors, fmt.Errorf("%q is not a valid network value: %#v", k, err))
}
return
}

View File

@ -0,0 +1,120 @@
package ovh
import (
"fmt"
"os"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
var testAccPublicCloudPrivateNetworkSubnetConfig = fmt.Sprintf(`
resource "ovh_vrack_publiccloud_attachment" "attach" {
vrack_id = "%s"
project_id = "%s"
}
data "ovh_publiccloud_regions" "regions" {
project_id = "${ovh_vrack_publiccloud_attachment.attach.project_id}"
}
data "ovh_publiccloud_region" "region_attr" {
count = 2
project_id = "${data.ovh_publiccloud_regions.regions.project_id}"
name = "${element(data.ovh_publiccloud_regions.regions.names, count.index)}"
}
resource "ovh_publiccloud_private_network" "network" {
project_id = "${ovh_vrack_publiccloud_attachment.attach.project_id}"
vlan_id = 0
name = "terraform_testacc_private_net"
regions = ["${data.ovh_publiccloud_regions.regions.names}"]
}
resource "ovh_publiccloud_private_network_subnet" "subnet" {
project_id = "${ovh_publiccloud_private_network.network.project_id}"
network_id = "${ovh_publiccloud_private_network.network.id}"
region = "${element(data.ovh_publiccloud_regions.regions.names, 0)}"
start = "192.168.168.100"
end = "192.168.168.200"
network = "192.168.168.0/24"
dhcp = true
no_gateway = false
}
`, os.Getenv("OVH_VRACK"), os.Getenv("OVH_PUBLIC_CLOUD"))
func TestAccPublicCloudPrivateNetworkSubnet_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccCheckPublicCloudPrivateNetworkSubnetPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckPublicCloudPrivateNetworkSubnetDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccPublicCloudPrivateNetworkSubnetConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckVRackPublicCloudAttachmentExists("ovh_vrack_publiccloud_attachment.attach", t),
testAccCheckPublicCloudPrivateNetworkExists("ovh_publiccloud_private_network.network", t),
testAccCheckPublicCloudPrivateNetworkSubnetExists("ovh_publiccloud_private_network_subnet.subnet", t),
),
},
},
})
}
func testAccCheckPublicCloudPrivateNetworkSubnetPreCheck(t *testing.T) {
testAccPreCheck(t)
testAccCheckPublicCloudExists(t)
}
func testAccCheckPublicCloudPrivateNetworkSubnetExists(n string, t *testing.T) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
if rs.Primary.Attributes["project_id"] == "" {
return fmt.Errorf("No Project ID is set")
}
if rs.Primary.Attributes["network_id"] == "" {
return fmt.Errorf("No Network ID is set")
}
return publicCloudPrivateNetworkSubnetExists(
rs.Primary.Attributes["project_id"],
rs.Primary.Attributes["network_id"],
rs.Primary.ID,
config.OVHClient,
)
}
}
func testAccCheckPublicCloudPrivateNetworkSubnetDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
for _, rs := range s.RootModule().Resources {
if rs.Type != "ovh_publiccloud_private_network_subnet" {
continue
}
err := publicCloudPrivateNetworkSubnetExists(
rs.Primary.Attributes["project_id"],
rs.Primary.Attributes["network_id"],
rs.Primary.ID,
config.OVHClient,
)
if err == nil {
return fmt.Errorf("VRack > Public Cloud Private Network Subnet still exists")
}
}
return nil
}

View File

@ -0,0 +1,93 @@
package ovh
import (
"fmt"
"os"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
var testAccPublicCloudPrivateNetworkConfig = fmt.Sprintf(`
resource "ovh_vrack_publiccloud_attachment" "attach" {
vrack_id = "%s"
project_id = "%s"
}
data "ovh_publiccloud_regions" "regions" {
project_id = "${ovh_vrack_publiccloud_attachment.attach.project_id}"
}
data "ovh_publiccloud_region" "region_attr" {
count = 2
project_id = "${data.ovh_publiccloud_regions.regions.project_id}"
name = "${element(data.ovh_publiccloud_regions.regions.names, count.index)}"
}
resource "ovh_publiccloud_private_network" "network" {
project_id = "${ovh_vrack_publiccloud_attachment.attach.project_id}"
vlan_id = 0
name = "terraform_testacc_private_net"
regions = ["${data.ovh_publiccloud_regions.regions.names}"]
}
`, os.Getenv("OVH_VRACK"), os.Getenv("OVH_PUBLIC_CLOUD"))
func TestAccPublicCloudPrivateNetwork_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccCheckPublicCloudPrivateNetworkPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckPublicCloudPrivateNetworkDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccPublicCloudPrivateNetworkConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckVRackPublicCloudAttachmentExists("ovh_vrack_publiccloud_attachment.attach", t),
testAccCheckPublicCloudPrivateNetworkExists("ovh_publiccloud_private_network.network", t),
),
},
},
})
}
func testAccCheckPublicCloudPrivateNetworkPreCheck(t *testing.T) {
testAccPreCheck(t)
testAccCheckPublicCloudExists(t)
}
func testAccCheckPublicCloudPrivateNetworkExists(n string, t *testing.T) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
if rs.Primary.Attributes["project_id"] == "" {
return fmt.Errorf("No Project ID is set")
}
return publicCloudPrivateNetworkExists(rs.Primary.Attributes["project_id"], rs.Primary.ID, config.OVHClient)
}
}
func testAccCheckPublicCloudPrivateNetworkDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
for _, rs := range s.RootModule().Resources {
if rs.Type != "ovh_publiccloud_private_network" {
continue
}
err := publicCloudPrivateNetworkExists(rs.Primary.Attributes["project_id"], rs.Primary.ID, config.OVHClient)
if err == nil {
return fmt.Errorf("VRack > Public Cloud Private Network still exists")
}
}
return nil
}

View File

@ -0,0 +1,289 @@
package ovh
import (
"fmt"
"log"
"regexp"
"strconv"
"time"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/ovh/go-ovh/ovh"
)
func resourcePublicCloudUser() *schema.Resource {
return &schema.Resource{
Create: resourcePublicCloudUserCreate,
Read: resourcePublicCloudUserRead,
Delete: resourcePublicCloudUserDelete,
Importer: &schema.ResourceImporter{
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
return []*schema.ResourceData{d}, nil
},
},
Schema: map[string]*schema.Schema{
"project_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
DefaultFunc: schema.EnvDefaultFunc("OVH_PROJECT_ID", nil),
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"username": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"password": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Sensitive: true,
},
"status": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"creation_date": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"openstack_rc": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
Computed: true,
},
},
}
}
func resourcePublicCloudUserCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
projectId := d.Get("project_id").(string)
params := &PublicCloudUserCreateOpts{
ProjectId: projectId,
Description: d.Get("description").(string),
}
r := &PublicCloudUserResponse{}
log.Printf("[DEBUG] Will create public cloud user: %s", params)
// Resource is partial because we will also compute Openstack RC & creds
d.Partial(true)
endpoint := fmt.Sprintf("/cloud/project/%s/user", params.ProjectId)
err := config.OVHClient.Post(endpoint, params, r)
if err != nil {
return fmt.Errorf("calling Post %s with params %s:\n\t %q", endpoint, params, err)
}
log.Printf("[DEBUG] Waiting for User %s:", r)
stateConf := &resource.StateChangeConf{
Pending: []string{"creating"},
Target: []string{"ok"},
Refresh: waitForPublicCloudUserActive(config.OVHClient, projectId, strconv.Itoa(r.Id)),
Timeout: 10 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("waiting for user (%s): %s", params, err)
}
log.Printf("[DEBUG] Created User %s", r)
readPublicCloudUser(d, r, true)
openstackrc := make(map[string]string)
err = publicCloudUserGetOpenstackRC(projectId, d.Id(), config.OVHClient, openstackrc)
if err != nil {
return fmt.Errorf("Creating openstack creds for user %s: %s", d.Id(), err)
}
d.Set("openstack_rc", &openstackrc)
d.Partial(false)
return nil
}
func resourcePublicCloudUserRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
projectId := d.Get("project_id").(string)
d.Partial(true)
r := &PublicCloudUserResponse{}
log.Printf("[DEBUG] Will read public cloud user %s from project: %s", d.Id(), projectId)
endpoint := fmt.Sprintf("/cloud/project/%s/user/%s", projectId, d.Id())
err := config.OVHClient.Get(endpoint, r)
if err != nil {
return fmt.Errorf("calling Get %s:\n\t %q", endpoint, err)
}
readPublicCloudUser(d, r, false)
openstackrc := make(map[string]string)
err = publicCloudUserGetOpenstackRC(projectId, d.Id(), config.OVHClient, openstackrc)
if err != nil {
return fmt.Errorf("Reading openstack creds for user %s: %s", d.Id(), err)
}
d.Set("openstack_rc", &openstackrc)
d.Partial(false)
log.Printf("[DEBUG] Read Public Cloud User %s", r)
return nil
}
func resourcePublicCloudUserDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
projectId := d.Get("project_id").(string)
id := d.Id()
log.Printf("[DEBUG] Will delete public cloud user %s from project: %s", id, projectId)
endpoint := fmt.Sprintf("/cloud/project/%s/user/%s", projectId, id)
err := config.OVHClient.Delete(endpoint, nil)
if err != nil {
return fmt.Errorf("calling Delete %s:\n\t %q", endpoint, err)
}
log.Printf("[DEBUG] Deleting Public Cloud User %s from project %s:", id, projectId)
stateConf := &resource.StateChangeConf{
Pending: []string{"deleting"},
Target: []string{"deleted"},
Refresh: waitForPublicCloudUserDelete(config.OVHClient, projectId, id),
Timeout: 10 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("Deleting Public Cloud user %s from project %s", id, projectId)
}
log.Printf("[DEBUG] Deleted Public Cloud User %s from project %s", id, projectId)
d.SetId("")
return nil
}
func publicCloudUserExists(projectId, id string, c *ovh.Client) error {
r := &PublicCloudUserResponse{}
log.Printf("[DEBUG] Will read public cloud user for project: %s, id: %s", projectId, id)
endpoint := fmt.Sprintf("/cloud/project/%s/user/%s", projectId, id)
err := c.Get(endpoint, r)
if err != nil {
return fmt.Errorf("calling Get %s:\n\t %q", endpoint, err)
}
log.Printf("[DEBUG] Read public cloud user: %s", r)
return nil
}
var publicCloudUserOSTenantName = regexp.MustCompile("export OS_TENANT_NAME=\"?([[:alnum:]]+)\"?")
var publicCloudUserOSTenantId = regexp.MustCompile("export OS_TENANT_ID=\"??([[:alnum:]]+)\"??")
var publicCloudUserOSAuthURL = regexp.MustCompile("export OS_AUTH_URL=\"??([[:^space:]]+)\"??")
var publicCloudUserOSUsername = regexp.MustCompile("export OS_USERNAME=\"?([[:alnum:]]+)\"?")
func publicCloudUserGetOpenstackRC(projectId, id string, c *ovh.Client, rc map[string]string) error {
log.Printf("[DEBUG] Will read public cloud user openstack rc for project: %s, id: %s", projectId, id)
endpoint := fmt.Sprintf("/cloud/project/%s/user/%s/openrc?region=to_be_overriden", projectId, id)
r := &PublicCloudUserOpenstackRC{}
err := c.Get(endpoint, r)
if err != nil {
return fmt.Errorf("calling Get %s:\n\t %q", endpoint, err)
}
authURL := publicCloudUserOSAuthURL.FindStringSubmatch(r.Content)
if authURL == nil {
return fmt.Errorf("couln't extract OS_AUTH_URL from content: \n\t%s", r.Content)
}
tenantName := publicCloudUserOSTenantName.FindStringSubmatch(r.Content)
if tenantName == nil {
return fmt.Errorf("couln't extract OS_TENANT_NAME from content: \n\t%s", r.Content)
}
tenantId := publicCloudUserOSTenantId.FindStringSubmatch(r.Content)
if tenantId == nil {
return fmt.Errorf("couln't extract OS_TENANT_ID from content: \n\t%s", r.Content)
}
username := publicCloudUserOSUsername.FindStringSubmatch(r.Content)
if username == nil {
return fmt.Errorf("couln't extract OS_USERNAME from content: \n\t%s", r.Content)
}
rc["OS_AUTH_URL"] = authURL[1]
rc["OS_TENANT_ID"] = tenantId[1]
rc["OS_TENANT_NAME"] = tenantName[1]
rc["OS_USERNAME"] = username[1]
return nil
}
func readPublicCloudUser(d *schema.ResourceData, r *PublicCloudUserResponse, setPassword bool) {
d.Set("description", r.Description)
d.Set("status", r.Status)
d.Set("creation_date", r.CreationDate)
d.Set("username", r.Username)
if setPassword {
d.Set("password", r.Password)
}
d.SetId(strconv.Itoa(r.Id))
}
func waitForPublicCloudUserActive(c *ovh.Client, projectId, PublicCloudUserId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
r := &PublicCloudUserResponse{}
endpoint := fmt.Sprintf("/cloud/project/%s/user/%s", projectId, PublicCloudUserId)
err := c.Get(endpoint, r)
if err != nil {
return r, "", err
}
log.Printf("[DEBUG] Pending User: %s", r)
return r, r.Status, nil
}
}
func waitForPublicCloudUserDelete(c *ovh.Client, projectId, PublicCloudUserId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
r := &PublicCloudUserResponse{}
endpoint := fmt.Sprintf("/cloud/project/%s/user/%s", projectId, PublicCloudUserId)
err := c.Get(endpoint, r)
if err != nil {
if err.(*ovh.APIError).Code == 404 {
log.Printf("[DEBUG] user id %s on project %s deleted", PublicCloudUserId, projectId)
return r, "deleted", nil
} else {
return r, "", err
}
}
log.Printf("[DEBUG] Pending User: %s", r)
return r, r.Status, nil
}
}

View File

@ -0,0 +1,107 @@
package ovh
import (
"fmt"
"os"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
var testAccPublicCloudUserConfig = fmt.Sprintf(`
resource "ovh_publiccloud_user" "user" {
project_id = "%s"
description = "my user for acceptance tests"
}
`, os.Getenv("OVH_PUBLIC_CLOUD"))
func TestAccPublicCloudUser_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccCheckPublicCloudUserPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckPublicCloudUserDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccPublicCloudUserConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckPublicCloudUserExists("ovh_publiccloud_user.user", t),
testAccCheckPublicCloudUserOpenRC("ovh_publiccloud_user.user", t),
),
},
},
})
}
func testAccCheckPublicCloudUserPreCheck(t *testing.T) {
testAccPreCheck(t)
testAccCheckPublicCloudExists(t)
}
func testAccCheckPublicCloudUserExists(n string, t *testing.T) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
if rs.Primary.Attributes["project_id"] == "" {
return fmt.Errorf("No Project ID is set")
}
return publicCloudUserExists(rs.Primary.Attributes["project_id"], rs.Primary.ID, config.OVHClient)
}
}
func testAccCheckPublicCloudUserOpenRC(n string, t *testing.T) 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 ID is set")
}
if rs.Primary.Attributes["openstack_rc.OS_AUTH_URL"] == "" {
return fmt.Errorf("No openstack_rc.OS_AUTH_URL is set")
}
if rs.Primary.Attributes["openstack_rc.OS_TENANT_ID"] == "" {
return fmt.Errorf("No openstack_rc.OS_TENANT_ID is set")
}
if rs.Primary.Attributes["openstack_rc.OS_TENANT_NAME"] == "" {
return fmt.Errorf("No openstack_rc.OS_TENANT_NAME is set")
}
if rs.Primary.Attributes["openstack_rc.OS_USERNAME"] == "" {
return fmt.Errorf("No openstack_rc.OS_USERNAME is set")
}
return nil
}
}
func testAccCheckPublicCloudUserDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
for _, rs := range s.RootModule().Resources {
if rs.Type != "ovh_publiccloud_user" {
continue
}
err := publicCloudUserExists(rs.Primary.Attributes["project_id"], rs.Primary.ID, config.OVHClient)
if err == nil {
return fmt.Errorf("VRack > Public Cloud User still exists")
}
}
return nil
}

View File

@ -0,0 +1,172 @@
package ovh
import (
"fmt"
"log"
"time"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/ovh/go-ovh/ovh"
)
func resourceVRackPublicCloudAttachment() *schema.Resource {
return &schema.Resource{
Create: resourceVRackPublicCloudAttachmentCreate,
Read: resourceVRackPublicCloudAttachmentRead,
Delete: resourceVRackPublicCloudAttachmentDelete,
Schema: map[string]*schema.Schema{
"vrack_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
DefaultFunc: schema.EnvDefaultFunc("OVH_VRACK_ID", ""),
},
"project_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
DefaultFunc: schema.EnvDefaultFunc("OVH_PROJECT_ID", ""),
},
},
}
}
func resourceVRackPublicCloudAttachmentCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
vrackId := d.Get("vrack_id").(string)
projectId := d.Get("project_id").(string)
if err := vrackPublicCloudAttachmentExists(vrackId, projectId, config.OVHClient); err == nil {
//set id
d.SetId(fmt.Sprintf("vrack_%s-cloudproject_%s-attach", vrackId, projectId))
return nil
}
params := &VRackAttachOpts{Project: projectId}
r := VRackAttachTaskResponse{}
log.Printf("[DEBUG] Will Attach VRack %s -> PublicCloud %s", vrackId, params.Project)
endpoint := fmt.Sprintf("/vrack/%s/cloudProject", vrackId)
err := config.OVHClient.Post(endpoint, params, &r)
if err != nil {
return fmt.Errorf("Error calling %s with params %s:\n\t %q", endpoint, params, err)
}
log.Printf("[DEBUG] Waiting for Attachement Task id %d: VRack %s -> PublicCloud %s", r.Id, vrackId, params.Project)
stateConf := &resource.StateChangeConf{
Pending: []string{"init", "todo", "doing"},
Target: []string{"completed"},
Refresh: waitForVRackTaskCompleted(config.OVHClient, vrackId, r.Id),
Timeout: 10 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("Error waiting for vrack (%s) to attach to public cloud (%s): %s", vrackId, params.Project, err)
}
log.Printf("[DEBUG] Created Attachement Task id %d: VRack %s -> PublicCloud %s", r.Id, vrackId, params.Project)
//set id
d.SetId(fmt.Sprintf("vrack_%s-cloudproject_%s-attach", vrackId, params.Project))
return nil
}
func resourceVRackPublicCloudAttachmentRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
vrackId := d.Get("vrack_id").(string)
params := &VRackAttachOpts{Project: d.Get("project_id").(string)}
r := VRackAttachTaskResponse{}
endpoint := fmt.Sprintf("/vrack/%s/cloudProject/%s", vrackId, params.Project)
err := config.OVHClient.Get(endpoint, &r)
if err != nil {
return err
}
log.Printf("[DEBUG] Read VRack %s -> PublicCloud %s", vrackId, params.Project)
return nil
}
func resourceVRackPublicCloudAttachmentDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
vrackId := d.Get("vrack_id").(string)
params := &VRackAttachOpts{Project: d.Get("project_id").(string)}
r := VRackAttachTaskResponse{}
endpoint := fmt.Sprintf("/vrack/%s/cloudProject/%s", vrackId, params.Project)
err := config.OVHClient.Delete(endpoint, &r)
if err != nil {
return err
}
log.Printf("[DEBUG] Waiting for Attachment Deletion Task id %d: VRack %s -> PublicCloud %s", r.Id, vrackId, params.Project)
stateConf := &resource.StateChangeConf{
Pending: []string{"init", "todo", "doing"},
Target: []string{"completed"},
Refresh: waitForVRackTaskCompleted(config.OVHClient, vrackId, r.Id),
Timeout: 10 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("Error waiting for vrack (%s) to attach to public cloud (%s): %s", vrackId, params.Project, err)
}
log.Printf("[DEBUG] Removed Attachement id %d: VRack %s -> PublicCloud %s", r.Id, vrackId, params.Project)
d.SetId("")
return nil
}
func vrackPublicCloudAttachmentExists(vrackId, projectId string, c *ovh.Client) error {
type attachResponse struct {
VRack string `json:"vrack"`
Project string `json:"project"`
}
r := attachResponse{}
endpoint := fmt.Sprintf("/vrack/%s/cloudProject/%s", vrackId, projectId)
err := c.Get(endpoint, &r)
if err != nil {
return fmt.Errorf("Error while querying %s: %q\n", endpoint, err)
}
log.Printf("[DEBUG] Read Attachment %s -> VRack:%s, Cloud Project: %s", endpoint, r.VRack, r.Project)
return nil
}
// AttachmentStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
// an Attachment Task.
func waitForVRackTaskCompleted(c *ovh.Client, serviceName string, taskId int) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
r := VRackAttachTaskResponse{}
endpoint := fmt.Sprintf("/vrack/%s/task/%d", serviceName, taskId)
err := c.Get(endpoint, &r)
if err != nil {
if err.(*ovh.APIError).Code == 404 {
log.Printf("[DEBUG] Task id %d on VRack %s completed", taskId, serviceName)
return taskId, "completed", nil
} else {
return taskId, "", err
}
}
log.Printf("[DEBUG] Pending Task id %d on VRack %s status: %s", r.Id, serviceName, r.Status)
return taskId, r.Status, nil
}
}

View File

@ -0,0 +1,76 @@
package ovh
import (
"fmt"
"os"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
var testAccVRackPublicCloudAttachmentConfig = fmt.Sprintf(`
resource "ovh_vrack_publiccloud_attachment" "attach" {
vrack_id = "%s"
project_id = "%s"
}
`, os.Getenv("OVH_VRACK"), os.Getenv("OVH_PUBLIC_CLOUD"))
func TestAccVRackPublicCloudAttachment_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccCheckVRackPublicCloudAttachmentPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckVRackPublicCloudAttachmentDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccVRackPublicCloudAttachmentConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckVRackPublicCloudAttachmentExists("ovh_vrack_publiccloud_attachment.attach", t),
),
},
},
})
}
func testAccCheckVRackPublicCloudAttachmentPreCheck(t *testing.T) {
testAccPreCheck(t)
testAccCheckVRackExists(t)
testAccCheckPublicCloudExists(t)
}
func testAccCheckVRackPublicCloudAttachmentExists(n string, t *testing.T) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.Attributes["vrack_id"] == "" {
return fmt.Errorf("No VRack ID is set")
}
if rs.Primary.Attributes["project_id"] == "" {
return fmt.Errorf("No Project ID is set")
}
return vrackPublicCloudAttachmentExists(rs.Primary.Attributes["vrack_id"], rs.Primary.Attributes["project_id"], config.OVHClient)
}
}
func testAccCheckVRackPublicCloudAttachmentDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
for _, rs := range s.RootModule().Resources {
if rs.Type != "ovh_vrack_publiccloud_attachment" {
continue
}
err := vrackPublicCloudAttachmentExists(rs.Primary.Attributes["vrack_id"], rs.Primary.Attributes["project_id"], config.OVHClient)
if err == nil {
return fmt.Errorf("VRack > Public Cloud Attachment still exists")
}
}
return nil
}

View File

@ -0,0 +1,154 @@
package ovh
import (
"fmt"
"time"
)
// Opts
type PublicCloudPrivateNetworkCreateOpts struct {
ProjectId string `json:"serviceName"`
VlanId int `json:"vlanId"`
Name string `json:"name"`
Regions []string `json:"regions"`
}
func (p *PublicCloudPrivateNetworkCreateOpts) String() string {
return fmt.Sprintf("projectId: %s, vlanId:%d, name: %s, regions: %s", p.ProjectId, p.VlanId, p.Name, p.Regions)
}
// Opts
type PublicCloudPrivateNetworkUpdateOpts struct {
Name string `json:"name"`
}
type PublicCloudPrivateNetworkRegion struct {
Status string `json:"status"`
Region string `json:"region"`
}
func (p *PublicCloudPrivateNetworkRegion) String() string {
return fmt.Sprintf("Status:%s, Region: %s", p.Status, p.Region)
}
type PublicCloudPrivateNetworkResponse struct {
Id string `json:"id"`
Status string `json:"status"`
Vlanid int `json:"vlanId"`
Name string `json:"name"`
Type string `json:"type"`
Regions []*PublicCloudPrivateNetworkRegion `json:"regions"`
}
func (p *PublicCloudPrivateNetworkResponse) String() string {
return fmt.Sprintf("Id: %s, Status: %s, Name: %s, Vlanid: %d, Type: %s, Regions: %s", p.Id, p.Status, p.Name, p.Vlanid, p.Type, p.Regions)
}
// Opts
type PublicCloudPrivateNetworksCreateOpts struct {
ProjectId string `json:"serviceName"`
NetworkId string `json:"networkId"`
Dhcp bool `json:"dhcp"`
NoGateway bool `json:"noGateway"`
Start string `json:"start"`
End string `json:"end"`
Network string `json:"network"`
Region string `json:"region"`
}
func (p *PublicCloudPrivateNetworksCreateOpts) String() string {
return fmt.Sprintf("PCPNSCreateOpts[projectId: %s, networkId:%s, dchp: %v, noGateway: %v, network: %s, start: %s, end: %s, region: %s]",
p.ProjectId, p.NetworkId, p.Dhcp, p.NoGateway, p.Network, p.Start, p.End, p.Region)
}
type IPPool struct {
Network string `json:"network"`
Region string `json:"region"`
Dhcp bool `json:"dhcp"`
Start string `json:"start"`
End string `json:"end"`
}
func (p *IPPool) String() string {
return fmt.Sprintf("IPPool[Network: %s, Region: %s, Dhcp: %v, Start: %s, End: %s]", p.Network, p.Region, p.Dhcp, p.Start, p.End)
}
type PublicCloudPrivateNetworksResponse struct {
Id string `json:"id"`
GatewayIp string `json:"gatewayIp"`
Cidr string `json:"cidr"`
IPPools []*IPPool `json:"ipPools"`
}
func (p *PublicCloudPrivateNetworksResponse) String() string {
return fmt.Sprintf("PCPNSResponse[Id: %s, GatewayIp: %s, Cidr: %s, IPPools: %s]", p.Id, p.GatewayIp, p.Cidr, p.IPPools)
}
// Opts
type PublicCloudUserCreateOpts struct {
ProjectId string `json:"serviceName"`
Description string `json:"description"`
}
func (p *PublicCloudUserCreateOpts) String() string {
return fmt.Sprintf("UserOpts[projectId: %s, description:%s]", p.ProjectId, p.Description)
}
type PublicCloudUserResponse struct {
Id int `json:"id"`
Username string `json:"username"`
Status string `json:"status"`
Description string `json:"description"`
Password string `json:"password"`
CreationDate string `json:"creationDate"`
}
func (p *PublicCloudUserResponse) String() string {
return fmt.Sprintf("UserResponse[Id: %v, Username: %s, Status: %s, Description: %s, CreationDate: %s]", p.Id, p.Username, p.Status, p.Description, p.CreationDate)
}
type PublicCloudUserOpenstackRC struct {
Content string `json:"content"`
}
// Opts
type VRackAttachOpts struct {
Project string `json:"project"`
}
// Task Opts
type TaskOpts struct {
ServiceName string `json:"serviceName"`
TaskId string `json:"taskId"`
}
type VRackAttachTaskResponse struct {
Id int `json:"id"`
Function string `json:"function"`
TargetDomain string `json:"targetDomain"`
Status string `json:"status"`
ServiceName string `json:"serviceName"`
OrderId int `json:"orderId"`
LastUpdate time.Time `json:"lastUpdate"`
TodoDate time.Time `json:"TodoDate"`
}
type PublicCloudRegionResponse struct {
ContinentCode string `json:"continentCode"`
DatacenterLocation string `json:"datacenterLocation"`
Name string `json:"name"`
Services []PublicCloudServiceStatusResponse `json:"services"`
}
func (r *PublicCloudRegionResponse) String() string {
return fmt.Sprintf("Region: %s, Services: %s", r.Name, r.Services)
}
type PublicCloudServiceStatusResponse struct {
Status string `json:"status"`
Name string `json:"name"`
}
func (s *PublicCloudServiceStatusResponse) String() string {
return fmt.Sprintf("%s: %s", s.Name, s.Status)
}

View File

@ -53,6 +53,7 @@ import (
opcprovider "github.com/hashicorp/terraform/builtin/providers/opc"
openstackprovider "github.com/hashicorp/terraform/builtin/providers/openstack"
opsgenieprovider "github.com/hashicorp/terraform/builtin/providers/opsgenie"
ovhprovider "github.com/hashicorp/terraform/builtin/providers/ovh"
packetprovider "github.com/hashicorp/terraform/builtin/providers/packet"
pagerdutyprovider "github.com/hashicorp/terraform/builtin/providers/pagerduty"
postgresqlprovider "github.com/hashicorp/terraform/builtin/providers/postgresql"
@ -134,6 +135,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{
"opc": opcprovider.Provider,
"openstack": openstackprovider.Provider,
"opsgenie": opsgenieprovider.Provider,
"ovh": ovhprovider.Provider,
"packet": packetprovider.Provider,
"pagerduty": pagerdutyprovider.Provider,
"postgresql": postgresqlprovider.Provider,

26
vendor/github.com/ovh/go-ovh/LICENSE generated vendored Normal file
View File

@ -0,0 +1,26 @@
Copyright (c) 2015-2017, OVH SAS.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of OVH SAS nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY OVH SAS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL OVH SAS AND CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

123
vendor/github.com/ovh/go-ovh/ovh/configuration.go generated vendored Normal file
View File

@ -0,0 +1,123 @@
package ovh
import (
"fmt"
"os"
"os/user"
"path/filepath"
"strings"
"gopkg.in/ini.v1"
)
// Use variables for easier test overload
var (
systemConfigPath = "/etc/ovh.conf"
userConfigPath = "/.ovh.conf" // prefixed with homeDir
localConfigPath = "./ovh.conf"
)
// currentUserHome attempts to get current user's home directory
func currentUserHome() (string, error) {
usr, err := user.Current()
if err != nil {
return "", err
}
return usr.HomeDir, nil
}
// appendConfigurationFile only if it exists. We need to do this because
// ini package will fail to load configuration at all if a configuration
// file is missing. This is racy, but better than always failing.
func appendConfigurationFile(cfg *ini.File, path string) {
if file, err := os.Open(path); err == nil {
file.Close()
cfg.Append(path)
}
}
// loadConfig loads client configuration from params, environments or configuration
// files (by order of decreasing precedence).
//
// loadConfig will check OVH_CONSUMER_KEY, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET
// and OVH_ENDPOINT environment variables. If any is present, it will take precedence
// over any configuration from file.
//
// Configuration files are ini files. They share the same format as python-ovh,
// node-ovh, php-ovh and all other wrappers. If any wrapper is configured, all
// can re-use the same configuration. loadConfig will check for configuration in:
//
// - ./ovh.conf
// - $HOME/.ovh.conf
// - /etc/ovh.conf
//
func (c *Client) loadConfig(endpointName string) error {
// Load configuration files by order of increasing priority. All configuration
// files are optional. Only load file from user home if home could be resolve
cfg := ini.Empty()
appendConfigurationFile(cfg, systemConfigPath)
if home, err := currentUserHome(); err == nil {
userConfigFullPath := filepath.Join(home, userConfigPath)
appendConfigurationFile(cfg, userConfigFullPath)
}
appendConfigurationFile(cfg, localConfigPath)
// Canonicalize configuration
if endpointName == "" {
endpointName = getConfigValue(cfg, "default", "endpoint", "ovh-eu")
}
if c.AppKey == "" {
c.AppKey = getConfigValue(cfg, endpointName, "application_key", "")
}
if c.AppSecret == "" {
c.AppSecret = getConfigValue(cfg, endpointName, "application_secret", "")
}
if c.ConsumerKey == "" {
c.ConsumerKey = getConfigValue(cfg, endpointName, "consumer_key", "")
}
// Load real endpoint URL by name. If endpoint contains a '/', consider it as a URL
if strings.Contains(endpointName, "/") {
c.endpoint = endpointName
} else {
c.endpoint = Endpoints[endpointName]
}
// If we still have no valid endpoint, AppKey or AppSecret, return an error
if c.endpoint == "" {
return fmt.Errorf("Unknown endpoint '%s'. Consider checking 'Endpoints' list of using an URL.", endpointName)
}
if c.AppKey == "" {
return fmt.Errorf("Missing application key. Please check your configuration or consult the documentation to create one.")
}
if c.AppSecret == "" {
return fmt.Errorf("Missing application secret. Please check your configuration or consult the documentation to create one.")
}
return nil
}
// getConfigValue returns the value of OVH_<NAME> or ``name`` value from ``section``. If
// the value could not be read from either env or any configuration files, return 'def'
func getConfigValue(cfg *ini.File, section, name, def string) string {
// Attempt to load from environment
fromEnv := os.Getenv("OVH_" + strings.ToUpper(name))
if len(fromEnv) > 0 {
return fromEnv
}
// Attempt to load from configuration
fromSection := cfg.Section(section)
if fromSection == nil {
return def
}
fromSectionKey := fromSection.Key(name)
if fromSectionKey == nil {
return def
}
return fromSectionKey.String()
}

113
vendor/github.com/ovh/go-ovh/ovh/consumer_key.go generated vendored Normal file
View File

@ -0,0 +1,113 @@
package ovh
import (
"fmt"
"strings"
)
// Map user friendly access level names to corresponding HTTP verbs
var (
ReadOnly = []string{"GET"}
ReadWrite = []string{"GET", "POST", "PUT", "DELETE"}
ReadWriteSafe = []string{"GET", "POST", "PUT"}
)
// AccessRule represents a method allowed for a path
type AccessRule struct {
// Allowed HTTP Method for the requested AccessRule.
// Can be set to GET/POST/PUT/DELETE.
Method string `json:"method"`
// Allowed path.
// Can be an exact string or a string with '*' char.
// Example :
// /me : only /me is authorized
// /* : all calls are authorized
Path string `json:"path"`
}
// CkValidationState represents the response when asking a new consumerKey.
type CkValidationState struct {
// Consumer key, which need to be validated by customer.
ConsumerKey string `json:"consumerKey"`
// Current status, should be always "pendingValidation".
State string `json:"state"`
// URL to redirect user in order to log in.
ValidationURL string `json:"validationUrl"`
}
// CkRequest represents the parameters to fill in order to ask a new
// consumerKey.
type CkRequest struct {
client *Client
AccessRules []AccessRule `json:"accessRules"`
Redirection string `json:"redirection,omitempty"`
}
func (ck *CkValidationState) String() string {
return fmt.Sprintf("CK: %q\nStatus: %q\nValidation URL: %q\n",
ck.ConsumerKey,
ck.State,
ck.ValidationURL,
)
}
// NewCkRequest helps create a new ck request
func (c *Client) NewCkRequest() *CkRequest {
return &CkRequest{
client: c,
AccessRules: []AccessRule{},
}
}
// NewCkRequestWithRedirection helps create a new ck request with a redirect URL
func (c *Client) NewCkRequestWithRedirection(redirection string) *CkRequest {
return &CkRequest{
client: c,
AccessRules: []AccessRule{},
Redirection: redirection,
}
}
// AddRule adds a new rule to the ckRequest
func (ck *CkRequest) AddRule(method, path string) {
ck.AccessRules = append(ck.AccessRules, AccessRule{
Method: method,
Path: path,
})
}
// AddRules adds grant requests on "path" for all methods. "ReadOnly",
// "ReadWrite" and "ReadWriteSafe" should be used for "methods" unless
// specific access are required.
func (ck *CkRequest) AddRules(methods []string, path string) {
for _, method := range methods {
ck.AddRule(method, path)
}
}
// AddRecursiveRules adds grant requests on "path" and "path/*", for all
// methods "ReadOnly", "ReadWrite" and "ReadWriteSafe" should be used for
// "methods" unless specific access are required.
func (ck *CkRequest) AddRecursiveRules(methods []string, path string) {
path = strings.TrimRight(path, "/")
// Add rules. Skip base rule when requesting access to "/"
if path != "" {
ck.AddRules(methods, path)
}
ck.AddRules(methods, path+"/*")
}
// Do executes the request. On success, set the consumer key in the client
// and return the URL the user needs to visit to validate the key
func (ck *CkRequest) Do() (*CkValidationState, error) {
state := CkValidationState{}
err := ck.client.PostUnAuth("/auth/credential", ck, &state)
if err == nil {
ck.client.ConsumerKey = state.ConsumerKey
}
return &state, err
}

17
vendor/github.com/ovh/go-ovh/ovh/error.go generated vendored Normal file
View File

@ -0,0 +1,17 @@
package ovh
import "fmt"
// APIError represents an error that can occurred while calling the API.
type APIError struct {
// Error message.
Message string
// HTTP code.
Code int
// ID of the request
QueryID string
}
func (err *APIError) Error() string {
return fmt.Sprintf("Error %d: %q", err.Code, err.Message)
}

331
vendor/github.com/ovh/go-ovh/ovh/ovh.go generated vendored Normal file
View File

@ -0,0 +1,331 @@
// Package ovh provides a HTTP wrapper for the OVH API.
package ovh
import (
"bytes"
"crypto/sha1"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"sync"
"time"
)
// DefaultTimeout api requests after 180s
const DefaultTimeout = 180 * time.Second
// Endpoints
const (
OvhEU = "https://eu.api.ovh.com/1.0"
OvhCA = "https://ca.api.ovh.com/1.0"
KimsufiEU = "https://eu.api.kimsufi.com/1.0"
KimsufiCA = "https://ca.api.kimsufi.com/1.0"
SoyoustartEU = "https://eu.api.soyoustart.com/1.0"
SoyoustartCA = "https://ca.api.soyoustart.com/1.0"
RunaboveCA = "https://api.runabove.com/1.0"
)
// Endpoints conveniently maps endpoints names to their URI for external configuration
var Endpoints = map[string]string{
"ovh-eu": OvhEU,
"ovh-ca": OvhCA,
"kimsufi-eu": KimsufiEU,
"kimsufi-ca": KimsufiCA,
"soyoustart-eu": SoyoustartEU,
"soyoustart-ca": SoyoustartCA,
"runabove-ca": RunaboveCA,
}
// Errors
var (
ErrAPIDown = errors.New("go-vh: the OVH API is down, it does't respond to /time anymore")
)
// Client represents a client to call the OVH API
type Client struct {
// Self generated tokens. Create one by visiting
// https://eu.api.ovh.com/createApp/
// AppKey holds the Application key
AppKey string
// AppSecret holds the Application secret key
AppSecret string
// ConsumerKey holds the user/app specific token. It must have been validated before use.
ConsumerKey string
// API endpoint
endpoint string
// Client is the underlying HTTP client used to run the requests. It may be overloaded but a default one is instanciated in ``NewClient`` by default.
Client *http.Client
// Ensures that the timeDelta function is only ran once
// sync.Once would consider init done, even in case of error
// hence a good old flag
timeDeltaMutex *sync.Mutex
timeDeltaDone bool
timeDelta time.Duration
Timeout time.Duration
}
// NewClient represents a new client to call the API
func NewClient(endpoint, appKey, appSecret, consumerKey string) (*Client, error) {
client := Client{
AppKey: appKey,
AppSecret: appSecret,
ConsumerKey: consumerKey,
Client: &http.Client{},
timeDeltaMutex: &sync.Mutex{},
timeDeltaDone: false,
Timeout: time.Duration(DefaultTimeout),
}
// Get and check the configuration
if err := client.loadConfig(endpoint); err != nil {
return nil, err
}
return &client, nil
}
// NewEndpointClient will create an API client for specified
// endpoint and load all credentials from environment or
// configuration files
func NewEndpointClient(endpoint string) (*Client, error) {
return NewClient(endpoint, "", "", "")
}
// NewDefaultClient will load all it's parameter from environment
// or configuration files
func NewDefaultClient() (*Client, error) {
return NewClient("", "", "", "")
}
//
// High level helpers
//
// Ping performs a ping to OVH API.
// In fact, ping is just a /auth/time call, in order to check if API is up.
func (c *Client) Ping() error {
_, err := c.getTime()
return err
}
// TimeDelta represents the delay between the machine that runs the code and the
// OVH API. The delay shouldn't change, let's do it only once.
func (c *Client) TimeDelta() (time.Duration, error) {
return c.getTimeDelta()
}
// Time returns time from the OVH API, by asking GET /auth/time.
func (c *Client) Time() (*time.Time, error) {
return c.getTime()
}
//
// Common request wrappers
//
// Get is a wrapper for the GET method
func (c *Client) Get(url string, resType interface{}) error {
return c.CallAPI("GET", url, nil, resType, true)
}
// GetUnAuth is a wrapper for the unauthenticated GET method
func (c *Client) GetUnAuth(url string, resType interface{}) error {
return c.CallAPI("GET", url, nil, resType, false)
}
// Post is a wrapper for the POST method
func (c *Client) Post(url string, reqBody, resType interface{}) error {
return c.CallAPI("POST", url, reqBody, resType, true)
}
// PostUnAuth is a wrapper for the unauthenticated POST method
func (c *Client) PostUnAuth(url string, reqBody, resType interface{}) error {
return c.CallAPI("POST", url, reqBody, resType, false)
}
// Put is a wrapper for the PUT method
func (c *Client) Put(url string, reqBody, resType interface{}) error {
return c.CallAPI("PUT", url, reqBody, resType, true)
}
// PutUnAuth is a wrapper for the unauthenticated PUT method
func (c *Client) PutUnAuth(url string, reqBody, resType interface{}) error {
return c.CallAPI("PUT", url, reqBody, resType, false)
}
// Delete is a wrapper for the DELETE method
func (c *Client) Delete(url string, resType interface{}) error {
return c.CallAPI("DELETE", url, nil, resType, true)
}
// DeleteUnAuth is a wrapper for the unauthenticated DELETE method
func (c *Client) DeleteUnAuth(url string, resType interface{}) error {
return c.CallAPI("DELETE", url, nil, resType, false)
}
//
// Low level API access
//
// getResult check the response and unmarshals it into the response type if needed.
// Helper function, called from CallAPI.
func (c *Client) getResponse(response *http.Response, resType interface{}) error {
// Read all the response body
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return err
}
// < 200 && >= 300 : API error
if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
apiError := &APIError{Code: response.StatusCode}
if err = json.Unmarshal(body, apiError); err != nil {
return err
}
apiError.QueryID = response.Header.Get("X-Ovh-QueryID")
return apiError
}
// Nothing to unmarshal
if len(body) == 0 || resType == nil {
return nil
}
return json.Unmarshal(body, &resType)
}
// timeDelta returns the time delta between the host and the remote API
func (c *Client) getTimeDelta() (time.Duration, error) {
if !c.timeDeltaDone {
// Ensure only one thread is updating
c.timeDeltaMutex.Lock()
// Did we wait ? Maybe no more needed
if !c.timeDeltaDone {
ovhTime, err := c.getTime()
if err != nil {
return 0, err
}
c.timeDelta = time.Since(*ovhTime)
c.timeDeltaDone = true
}
c.timeDeltaMutex.Unlock()
}
return c.timeDelta, nil
}
// getTime t returns time from for a given api client endpoint
func (c *Client) getTime() (*time.Time, error) {
var timestamp int64
err := c.GetUnAuth("/auth/time", &timestamp)
if err != nil {
return nil, err
}
serverTime := time.Unix(timestamp, 0)
return &serverTime, nil
}
// getLocalTime is a function to be overwritten during the tests, it return the time
// on the the local machine
var getLocalTime = func() time.Time {
return time.Now()
}
// getEndpointForSignature is a function to be overwritten during the tests, it returns a
// the endpoint
var getEndpointForSignature = func(c *Client) string {
return c.endpoint
}
// CallAPI is the lowest level call helper. If needAuth is true,
// inject authentication headers and sign the request.
//
// Request signature is a sha1 hash on following fields, joined by '+':
// - applicationSecret (from Client instance)
// - consumerKey (from Client instance)
// - capitalized method (from arguments)
// - full request url, including any query string argument
// - full serialized request body
// - server current time (takes time delta into account)
//
// Call will automatically assemble the target url from the endpoint
// configured in the client instance and the path argument. If the reqBody
// argument is not nil, it will also serialize it as json and inject
// the required Content-Type header.
//
// If everyrthing went fine, unmarshall response into resType and return nil
// otherwise, return the error
func (c *Client) CallAPI(method, path string, reqBody, resType interface{}, needAuth bool) error {
var body []byte
var err error
if reqBody != nil {
body, err = json.Marshal(reqBody)
if err != nil {
return err
}
}
target := fmt.Sprintf("%s%s", c.endpoint, path)
req, err := http.NewRequest(method, target, bytes.NewReader(body))
if err != nil {
return err
}
// Inject headers
if body != nil {
req.Header.Add("Content-Type", "application/json;charset=utf-8")
}
req.Header.Add("X-Ovh-Application", c.AppKey)
req.Header.Add("Accept", "application/json")
// Inject signature. Some methods do not need authentication, especially /time,
// /auth and some /order methods are actually broken if authenticated.
if needAuth {
timeDelta, err := c.TimeDelta()
if err != nil {
return err
}
timestamp := getLocalTime().Add(-timeDelta).Unix()
req.Header.Add("X-Ovh-Timestamp", strconv.FormatInt(timestamp, 10))
req.Header.Add("X-Ovh-Consumer", c.ConsumerKey)
h := sha1.New()
h.Write([]byte(fmt.Sprintf("%s+%s+%s+%s%s+%s+%d",
c.AppSecret,
c.ConsumerKey,
method,
getEndpointForSignature(c),
path,
body,
timestamp,
)))
req.Header.Add("X-Ovh-Signature", fmt.Sprintf("$1$%x", h.Sum(nil)))
}
// Send the request with requested timeout
c.Client.Timeout = c.Timeout
response, err := c.Client.Do(req)
if err != nil {
return err
}
// Unmarshal the result into the resType if possible
return c.getResponse(response, resType)
}

6
vendor/vendor.json vendored
View File

@ -2774,6 +2774,12 @@
"revision": "2654b36ec837f913b546aec27f7717b5ac664cc8",
"revisionTime": "2017-01-24T12:11:56Z"
},
{
"checksumSHA1": "9vQ5+o0oO/0deG+uokvvdpeSv0Y=",
"path": "github.com/ovh/go-ovh/ovh",
"revision": "d2207178e10e4527e8f222fd8707982df8c3af17",
"revisionTime": "2017-01-02T12:05:21Z"
},
{
"checksumSHA1": "d1VHczIGt2bhYR0BNkTedIS/9uk=",
"path": "github.com/packer-community/winrmcp/winrmcp",

View File

@ -0,0 +1,43 @@
---
layout: "ovh"
page_title: "OVH: publiccloud_region"
sidebar_current: "docs-ovh-datasource-publiccloud-region"
description: |-
Get information & status of a region associated with a public cloud project.
---
# publiccloud\_region
Use this data source to retrieve information about a region associated with a
public cloud project. The region must be associated with the project.
## Example Usage
```hcl
data "ovh_publiccloud_region" "GRA1" {
project_id = "XXXXXX"
region = "GRA1"
}
```
## Argument Reference
* `project_id` - (Required) The id of the public cloud project. If omitted,
the `OVH_PROJECT_ID` environment variable is used.
* `region` - (Required) The name of the region associated with the public cloud
project.
## Attributes Reference
`id` is set to the ID of the project concatenated with the name of the region.
In addition, the following attributes are exported:
* `continentCode` - the code of the geographic continent the region is running.
E.g.: EU for Europe, US for America...
* `datacenterLocation` - The location code of the datacenter.
E.g.: "GRA", meaning Gravelines, for region "GRA1"
* `services` - The list of public cloud services running within the region
* `name` - the name of the public cloud service
* `status` - the status of the service

View File

@ -0,0 +1,32 @@
---
layout: "ovh"
page_title: "OVH: publiccloud_regions"
sidebar_current: "docs-ovh-datasource-publiccloud-regions"
description: |-
Get the list of regions associated with a public cloud project.
---
# publiccloud\_regions
Use this data source to get the regions of a public cloud project.
## Example Usage
```hcl
data "ovh_publiccloud_regions" "regions" {
project_id = "XXXXXX"
}
```
## Argument Reference
* `project_id` - (Required) The id of the public cloud project. If omitted,
the `OVH_PROJECT_ID` environment variable is used.
## Attributes Reference
`id` is set to the ID of the project. In addition, the following attributes
are exported:
* `regions` - The list of regions associated with the project

View File

@ -0,0 +1,62 @@
---
layout: "ovh"
page_title: "Provider: OVH"
sidebar_current: "docs-ovh-index"
description: |-
The OVH provider is used to interact with the many resources supported by OVH. The provider needs to be configured with the proper credentials before it can be used.
---
# OVH Provider
The OVH provider is used to interact with the
many resources supported by OVH. The provider needs to be configured
with the proper credentials before it can be used.
Use the navigation to the left to read about the available resources.
## Example Usage
```
# Configure the OVH Provider
provider "ovh" {
endpoint = "ovh-eu"
application_key = "yyyyyy"
application_secret = "xxxxxxxxxxxxxx"
consumer_key = "zzzzzzzzzzzzzz"
}
# Create a public cloud user
resource "ovh_publiccloud_user" "user-test" {
# ...
}
```
## Configuration Reference
The following arguments are supported:
* `endpoint` - (Required) Specify which API endpoint to use.
It can be set using the OVH_ENDPOINT environment
variable. Value can be set to either "ovh-eu" or "ovh-ca".
* `application_key` - (Required) The API Application Key. If omitted,
the `OVH_APPLICATION_KEY` environment variable is used.
* `application_secret` - (Required) The API Application Secret. If omitted,
the `OVH_APPLICATION_SECRET` environment variable is used.
* `consumer_key` - (Required) The API Consumer key. If omitted,
the `OVH_CONSUMER_KEY` environment variable is used.
## Testing and Development
In order to run the Acceptance Tests for development, the following environment
variables must also be set:
* `OVH_VRACK` - The id of the vrack to use.
* `OVH_PUBLIC_CLOUD` - The id of the public cloud project.
You should be able to use any OVH environment to develop on as long as the
above environment variables are set.

View File

@ -0,0 +1,50 @@
---
layout: "ovh"
page_title: "OVH: publiccloud_private_network"
sidebar_current: "docs-ovh-resource-publiccloud-private-network"
description: |-
Creates a private network in a public cloud project.
---
# ovh_publiccloud\_private_network
Creates a private network in a public cloud project.
## Example Usage
```
resource "ovh_publiccloud_private_network" "net" {
project_id = "67890"
name = "admin_network"
regions = ["GRA1", "BHS1"]
}
```
## Argument Reference
The following arguments are supported:
* `project_id` - (Required) The id of the public cloud project. If omitted,
the `OVH_PROJECT_ID` environment variable is used.
* `name` - (Required) The name of the network.
* `vlan_id` - a vlan id to associate with the network.
Changing this value recreates the resource. Defaults to 0.
* `regions` - an array of valid OVH public cloud region ID in which the network
will be available. Ex.: "GRA1". Defaults to all public cloud regions.
## Attributes Reference
The following attributes are exported:
* `project_id` - See Argument Reference above.
* `name` - See Argument Reference above.
* `vland_id` - See Argument Reference above.
* `regions` - See Argument Reference above.
* `regions_status` - A map representing the status of the network per region.
* `regions_status/region` - The id of the region.
* `regions_status/status` - The status of the network in the region.
* `status` - the status of the network. should be normally set to 'ACTIVE'.
* `type` - the type of the network. Either 'private' or 'public'.

View File

@ -0,0 +1,76 @@
---
layout: "ovh"
page_title: "OVH: publiccloud_private_network_subnet"
sidebar_current: "docs-ovh-resource-publiccloud-private-network-subnet"
description: |-
Creates a subnet in a private network of a public cloud project.
---
# ovh_publiccloud\_private_network\_subnet
Creates a subnet in a private network of a public cloud project.
## Example Usage
```
resource "ovh_publiccloud_private_network_subnet" "subnet" {
project_id = "67890"
network_id = "0234543"
region = "GRA1"
start = "192.168.168.100"
end = "192.168.168.200"
network = "192.168.168.0/24"
dhcp = true
no_gateway = false
}
```
## Argument Reference
The following arguments are supported:
* `project_id` - (Required) The id of the public cloud project. If omitted,
the `OVH_PROJECT_ID` environment variable is used.
Changing this forces a new resource to be created.
* `network_id` - (Required) The id of the network.
Changing this forces a new resource to be created.
* `dhcp` - (Optional) Enable DHCP.
Changing this forces a new resource to be created. Defaults to false.
_
* `start` - (Required) First ip for this region.
Changing this value recreates the subnet.
* `end` - (Required) Last ip for this region.
Changing this value recreates the subnet.
* `network` - (Required) Global network in CIDR format.
Changing this value recreates the subnet
* `region` - The region in which the network subnet will be created.
Ex.: "GRA1". Changing this value recreates the resource.
* `no_gateway` - Set to true if you don't want to set a default gateway IP.
Changing this value recreates the resource. Defaults to false.
## Attributes Reference
The following attributes are exported:
* `project_id` - See Argument Reference above.
* `network_id` - See Argument Reference above.
* `dhcp_id` - See Argument Reference above.
* `start` - See Argument Reference above.
* `end` - See Argument Reference above.
* `network` - See Argument Reference above.
* `region` - See Argument Reference above.
* `no_gateway` - See Argument Reference above.
* `cidr` - Ip Block representing the subnet cidr.
* `ip_pools` - List of ip pools allocated in the subnet.
* `ip_pools/network` - Global network with cidr.
* `ip_pools/region` - Region where this subnet is created.
* `ip_pools/dhcp` - DHCP enabled.
* `ip_pools/end` - Last ip for this region.
* `ip_pools/start` - First ip for this region.

View File

@ -0,0 +1,44 @@
---
layout: "ovh"
page_title: "OVH: publiccloud_user"
sidebar_current: "docs-ovh-resource-publiccloud-user"
description: |-
Creates a user in a public cloud project.
---
# ovh_publiccloud\_user
Creates a user in a public cloud project.
## Example Usage
```
resource "ovh_publiccloud_user" "user1" {
project_id = "67890"
}
```
## Argument Reference
The following arguments are supported:
* `project_id` - (Required) The id of the public cloud project. If omitted,
the `OVH_PROJECT_ID` environment variable is used.
* `description` - A description associated with the user.
## Attributes Reference
The following attributes are exported:
* `project_id` - See Argument Reference above.
* `description` - See Argument Reference above.
* `username` - the username generated for the user. This username can be used with
the Openstack API.
* `password` - (Sensitive) the password generated for the user. The password can
be used with the Openstack API. This attribute is sensitive and will only be
retrieve once during creation.
* `status` - the status of the user. should be normally set to 'ok'.
* `creation_date` - the date the user was created.
* `openstack_rc` - a convenient map representing an openstack_rc file.
Note: no password nor sensitive token is set in this map.

View File

@ -0,0 +1,46 @@
---
layout: "ovh"
page_title: "OVH: vrack_publiccloud_attachment"
sidebar_current: "docs-ovh-resource-vrack-publiccloud-attachment"
description: |-
Attach an existing PublicCloud project to an existing VRack.
---
# ovh_vrack\_publiccloud\_attachment
Attach an existing PublicCloud project to an existing VRack.
## Example Usage
```
resource "ovh_vrack_publiccloud_attachment" "attach" {
vrack_id = "12345"
project_id = "67890"
}
```
## Argument Reference
The following arguments are supported:
* `vrack_id` - (Required) The id of the vrack. If omitted, the `OVH_VRACK_ID`
environment variable is used.
* `project_id` - (Required) The id of the public cloud project. If omitted,
the `OVH_VRACK_ID` environment variable is used.
## Attributes Reference
The following attributes are exported:
* `vrack_id` - See Argument Reference above.
* `project_id` - See Argument Reference above.
## Notes
The vrack attachment isn't a proper resource with an ID. As such, the resource id will
be forged from the vrack and project ids and there's no correct way to import the
resource in terraform. When the resource is created by terraform, it first checks if the
attachment already exists within OVH infrastructure; if it exists it set the resource id
without modifying anything. Otherwise, it will try to attach the vrack with the public
cloud project.

View File

@ -364,6 +364,10 @@
<a href="/docs/providers/opsgenie/index.html">OpsGenie</a>
</li>
<li<%= sidebar_current("docs-providers-ovh") %>>
<a href="/docs/providers/ovh/index.html">OVH</a>
</li>
<li<%= sidebar_current("docs-providers-packet") %>>
<a href="/docs/providers/packet/index.html">Packet</a>
</li>

View File

@ -0,0 +1,43 @@
<% wrap_layout :inner do %>
<% content_for :sidebar do %>
<div class="docs-sidebar hidden-print affix-top" role="complementary">
<ul class="nav docs-sidenav">
<li<%= sidebar_current("docs-home") %>>
<a href="/docs/providers/index.html">&laquo; Documentation Home</a>
</li>
<li<%= sidebar_current("docs-ovh-index") %>>
<a href="/docs/providers/ovh/index.html">OVH Provider</a>
</li>
<li<%= sidebar_current(/^docs-ovh-resource-vrack/) %>>
<a href="#">VRack Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-ovh-resource-vrack-publicloud-attachment") %>>
<a href="/docs/providers/ovh/r/vrack_publiccloud_attachment.html">ovh_vrack_publiccloud_attachment</a>
</li>
</ul>
</li>
<li<%= sidebar_current(/^docs-ovh-publiccloud/) %>>
<a href="#">Public Cloud Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-ovh-resource-publiccloud-private-network") %>>
<a href="/docs/providers/ovh/r/publiccloud_private_network.html">ovh_publiccloud_private_network</a>
</li>
<li<%= sidebar_current("docs-ovh-resource-publiccloud-private-network-subnet") %>>
<a href="/docs/providers/ovh/r/publiccloud_private_network_subnet.html">ovh_publiccloud_private_network_subnet</a>
</li>
<li<%= sidebar_current("docs-ovh-resource-publiccloud-user") %>>
<a href="/docs/providers/ovh/r/publiccloud_user.html">ovh_publiccloud_user</a>
</li>
</ul>
</li>
</ul>
</div>
<% end %>
<%= yield %>
<% end %>