Skip to content

Commit f9cf081

Browse files
authored
Merge pull request #455 from Stoakes/fix-cloudproject-creation-in-the-us
fix(cloudProject): creation and import for cloudProject in the US
2 parents 3748118 + 9d9f25e commit f9cf081

File tree

4 files changed

+99
-4
lines changed

4 files changed

+99
-4
lines changed

ovh/order.go

+22
Original file line numberDiff line numberDiff line change
@@ -494,3 +494,25 @@ func waitForOrder(c *ovh.Client, id int64) resource.StateRefreshFunc {
494494
return r, r, nil
495495
}
496496
}
497+
498+
func orderDetailOperations(c *ovh.Client, orderId int64, orderDetailId int64) ([]*MeOrderDetailOperation, error) {
499+
log.Printf("[DEBUG] Will list order detail operations %d/%d", orderId, orderDetailId)
500+
operationsIds := []int64{}
501+
endpoint := fmt.Sprintf("/me/order/%d/details/%d/operations", orderId, orderDetailId)
502+
if err := c.Get(endpoint, &operationsIds); err != nil {
503+
return nil, fmt.Errorf("calling get %s:\n\t %q", endpoint, err)
504+
}
505+
506+
operations := make([]*MeOrderDetailOperation, len(operationsIds))
507+
for i, operationId := range operationsIds {
508+
operation := &MeOrderDetailOperation{}
509+
log.Printf("[DEBUG] Will read order detail operations %d/%d/%d", orderId, orderDetailId, operationId)
510+
endpoint := fmt.Sprintf("/me/order/%d/details/%d/operations/%d", orderId, orderDetailId, operationId)
511+
if err := c.Get(endpoint, operation); err != nil {
512+
return nil, fmt.Errorf("calling get %s:\n\t %q", endpoint, err)
513+
}
514+
515+
operations[i] = operation
516+
}
517+
return operations, nil
518+
}

ovh/resource_cloud_project.go

+59-3
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@ import (
44
"fmt"
55
"log"
66
"net/url"
7+
"regexp"
78

89
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
910
"github.com/ovh/go-ovh/ovh"
1011
"github.com/ovh/terraform-provider-ovh/ovh/helpers"
1112
)
1213

14+
var (
15+
publicCloudProjectNameFormatRegex = regexp.MustCompile("^[0-9a-f]{12}4[0-9a-f]{19}$")
16+
)
17+
1318
func resourceCloudProject() *schema.Resource {
1419
return &schema.Resource{
1520
Create: resourceCloudProjectCreate,
@@ -74,14 +79,31 @@ func resourceCloudProjectCreate(d *schema.ResourceData, meta interface{}) error
7479
}
7580

7681
func resourceCloudProjectUpdate(d *schema.ResourceData, meta interface{}) error {
77-
_, details, err := orderRead(d, meta)
82+
order, details, err := orderRead(d, meta)
7883
if err != nil {
7984
return fmt.Errorf("Could not read cloud project order: %q", err)
8085
}
8186

8287
config := meta.(*Config)
8388
serviceName := details[0].Domain
8489

90+
// in the US, for reasons too long to be detailled here, cloud project order domain is not the public cloud project id, but "*".
91+
// There have been discussions to align US & EU, but they've failed.
92+
// So we end up making a few extra queries to fetch the project id from operations details.
93+
if !publicCloudProjectNameFormatRegex.MatchString(serviceName) {
94+
orderDetailId := details[0].OrderDetailId
95+
operations, err := orderDetailOperations(config.OVHClient, order.OrderId, orderDetailId)
96+
if err != nil {
97+
return fmt.Errorf("Could not read cloudProject order details operations: %q", err)
98+
}
99+
for _, operation := range operations {
100+
if !publicCloudProjectNameFormatRegex.MatchString(operation.Resource.Name) {
101+
continue
102+
}
103+
serviceName = operation.Resource.Name
104+
}
105+
}
106+
85107
log.Printf("[DEBUG] Will update cloudProject: %s", serviceName)
86108
opts := (&CloudProjectUpdateOpts{}).FromResource(d)
87109
endpoint := fmt.Sprintf("/cloud/project/%s", serviceName)
@@ -93,14 +115,31 @@ func resourceCloudProjectUpdate(d *schema.ResourceData, meta interface{}) error
93115
}
94116

95117
func resourceCloudProjectRead(d *schema.ResourceData, meta interface{}) error {
96-
_, details, err := orderRead(d, meta)
118+
order, details, err := orderRead(d, meta)
97119
if err != nil {
98120
return fmt.Errorf("Could not read cloudProject order: %q", err)
99121
}
100122

101123
config := meta.(*Config)
102124
serviceName := details[0].Domain
103125

126+
// in the US, for reasons too long to be detailled here, cloud project order domain is not the public cloud project id, but "*".
127+
// There have been discussions to align US & EU, but they've failed.
128+
// So we end up making a few extra queries to fetch the project id from operations details.
129+
if !publicCloudProjectNameFormatRegex.MatchString(serviceName) {
130+
orderDetailId := details[0].OrderDetailId
131+
operations, err := orderDetailOperations(config.OVHClient, order.OrderId, orderDetailId)
132+
if err != nil {
133+
return fmt.Errorf("Could not read cloudProject order details operations: %q", err)
134+
}
135+
for _, operation := range operations {
136+
if !publicCloudProjectNameFormatRegex.MatchString(operation.Resource.Name) {
137+
continue
138+
}
139+
serviceName = operation.Resource.Name
140+
}
141+
}
142+
104143
log.Printf("[DEBUG] Will read cloudProject: %s", serviceName)
105144
r := &CloudProject{}
106145
endpoint := fmt.Sprintf("/cloud/project/%s", serviceName)
@@ -119,14 +158,31 @@ func resourceCloudProjectRead(d *schema.ResourceData, meta interface{}) error {
119158
}
120159

121160
func resourceCloudProjectDelete(d *schema.ResourceData, meta interface{}) error {
122-
_, details, err := orderRead(d, meta)
161+
order, details, err := orderRead(d, meta)
123162
if err != nil {
124163
return fmt.Errorf("Could not read cloudProject order: %q", err)
125164
}
126165

127166
config := meta.(*Config)
128167
serviceName := details[0].Domain
129168

169+
// in the US, for reasons too long to be detailled here, cloud project order domain is not the public cloud project id, but "*".
170+
// There have been discussions to align US & EU, but they've failed.
171+
// So we end up making a few extra queries to fetch the project id from operations details.
172+
if !publicCloudProjectNameFormatRegex.MatchString(serviceName) {
173+
orderDetailId := details[0].OrderDetailId
174+
operations, err := orderDetailOperations(config.OVHClient, order.OrderId, orderDetailId)
175+
if err != nil {
176+
return fmt.Errorf("Could not read cloudProject order details operations: %q", err)
177+
}
178+
for _, operation := range operations {
179+
if !publicCloudProjectNameFormatRegex.MatchString(operation.Resource.Name) {
180+
continue
181+
}
182+
serviceName = operation.Resource.Name
183+
}
184+
}
185+
130186
id := d.Id()
131187

132188
terminate := func() (string, error) {

ovh/types_me_order.go

+14
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ func (v MeOrderDetail) ToMap() map[string]interface{} {
3030
return obj
3131
}
3232

33+
type MeOrderDetailOperation struct {
34+
Status string `json:"status"`
35+
ID int `json:"id"`
36+
Type string `json:"type"`
37+
Resource MeOrderDetailOperationResource `json:"resource"`
38+
Quantity int `json:"quantity"`
39+
}
40+
41+
type MeOrderDetailOperationResource struct {
42+
Name string `json:"name"`
43+
State string `json:"state"`
44+
DisplayName string `json:"displayName"`
45+
}
46+
3347
type MeOrderPaymentOpts struct {
3448
PaymentMean string `json:"paymentMean"`
3549
PaymentMeanId *int64 `json:"paymentMeanId,omitEmpty"`

website/docs/r/cloud_project.html.markdown

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ data "ovh_order_cart_product_plan" "cloud" {
2323
price_capacity = "renew"
2424
product = "cloud"
2525
plan_code = "project.2018"
26+
# plan_code = "project" # when running in the US
2627
}
2728
2829
resource "ovh_cloud_project" "my_cloud_project" {
@@ -37,6 +38,8 @@ resource "ovh_cloud_project" "my_cloud_project" {
3738
}
3839
```
3940

41+
-> __WARNING__ Currently, the OVHcloud Terraform provider does not support deletion of a public cloud project in the US. Removal is possible by manually deleting the project and then manually removing the public cloud project from terraform state.
42+
4043
## Argument Reference
4144

4245
The following arguments are supported:
@@ -46,7 +49,7 @@ The following arguments are supported:
4649
* `ovh_subsidiary` - (Required) OVHcloud Subsidiary. Country of OVHcloud legal entity you'll be billed by. List of supported subsidiaries available on API at [/1.0/me.json under `models.nichandle.OvhSubsidiaryEnum`](https://eu.api.ovh.com/1.0/me.json)
4750
* `plan` - (Required) Product Plan to order
4851
* `duration` - (Required) duration
49-
* `plan_code` - (Required) Plan code
52+
* `plan_code` - (Required) Plan code. This value must be adapted depending on your `OVH_ENDPOINT` value. It's `project.2018` for `ovh-{eu,ca}` and `project` when using `ovh-us`.
5053
* `pricing_mode` - (Required) Pricing model identifier
5154
* `catalog_name` - Catalog name
5255
* `configuration` - (Optional) Representation of a configuration item for personalizing product

0 commit comments

Comments
 (0)