Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cloudProject): creation and import for cloudProject in the US #455

Merged
merged 1 commit into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions ovh/order.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,3 +494,25 @@ func waitForOrder(c *ovh.Client, id int64) resource.StateRefreshFunc {
return r, r, nil
}
}

func orderDetailOperations(c *ovh.Client, orderId int64, orderDetailId int64) ([]*MeOrderDetailOperation, error) {
log.Printf("[DEBUG] Will list order detail operations %d/%d", orderId, orderDetailId)
operationsIds := []int64{}
endpoint := fmt.Sprintf("/me/order/%d/details/%d/operations", orderId, orderDetailId)
if err := c.Get(endpoint, &operationsIds); err != nil {
return nil, fmt.Errorf("calling get %s:\n\t %q", endpoint, err)
}

operations := make([]*MeOrderDetailOperation, len(operationsIds))
for i, operationId := range operationsIds {
operation := &MeOrderDetailOperation{}
log.Printf("[DEBUG] Will read order detail operations %d/%d/%d", orderId, orderDetailId, operationId)
endpoint := fmt.Sprintf("/me/order/%d/details/%d/operations/%d", orderId, orderDetailId, operationId)
if err := c.Get(endpoint, operation); err != nil {
return nil, fmt.Errorf("calling get %s:\n\t %q", endpoint, err)
}

operations[i] = operation
}
return operations, nil
}
62 changes: 59 additions & 3 deletions ovh/resource_cloud_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ import (
"fmt"
"log"
"net/url"
"regexp"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/ovh/go-ovh/ovh"
"github.com/ovh/terraform-provider-ovh/ovh/helpers"
)

var (
publicCloudProjectNameFormatRegex = regexp.MustCompile("^[0-9a-f]{12}4[0-9a-f]{19}$")
)

func resourceCloudProject() *schema.Resource {
return &schema.Resource{
Create: resourceCloudProjectCreate,
Expand Down Expand Up @@ -74,14 +79,31 @@ func resourceCloudProjectCreate(d *schema.ResourceData, meta interface{}) error
}

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

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

// in the US, for reasons too long to be detailled here, cloud project order domain is not the public cloud project id, but "*".
// There have been discussions to align US & EU, but they've failed.
// So we end up making a few extra queries to fetch the project id from operations details.
if !publicCloudProjectNameFormatRegex.MatchString(serviceName) {
orderDetailId := details[0].OrderDetailId
operations, err := orderDetailOperations(config.OVHClient, order.OrderId, orderDetailId)
if err != nil {
return fmt.Errorf("Could not read cloudProject order details operations: %q", err)
}
for _, operation := range operations {
if !publicCloudProjectNameFormatRegex.MatchString(operation.Resource.Name) {
continue
}
serviceName = operation.Resource.Name
}
}

log.Printf("[DEBUG] Will update cloudProject: %s", serviceName)
opts := (&CloudProjectUpdateOpts{}).FromResource(d)
endpoint := fmt.Sprintf("/cloud/project/%s", serviceName)
Expand All @@ -93,14 +115,31 @@ func resourceCloudProjectUpdate(d *schema.ResourceData, meta interface{}) error
}

func resourceCloudProjectRead(d *schema.ResourceData, meta interface{}) error {
_, details, err := orderRead(d, meta)
order, details, err := orderRead(d, meta)
if err != nil {
return fmt.Errorf("Could not read cloudProject order: %q", err)
}

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

// in the US, for reasons too long to be detailled here, cloud project order domain is not the public cloud project id, but "*".
// There have been discussions to align US & EU, but they've failed.
// So we end up making a few extra queries to fetch the project id from operations details.
if !publicCloudProjectNameFormatRegex.MatchString(serviceName) {
orderDetailId := details[0].OrderDetailId
operations, err := orderDetailOperations(config.OVHClient, order.OrderId, orderDetailId)
if err != nil {
return fmt.Errorf("Could not read cloudProject order details operations: %q", err)
}
for _, operation := range operations {
if !publicCloudProjectNameFormatRegex.MatchString(operation.Resource.Name) {
continue
}
serviceName = operation.Resource.Name
}
}

log.Printf("[DEBUG] Will read cloudProject: %s", serviceName)
r := &CloudProject{}
endpoint := fmt.Sprintf("/cloud/project/%s", serviceName)
Expand All @@ -119,14 +158,31 @@ func resourceCloudProjectRead(d *schema.ResourceData, meta interface{}) error {
}

func resourceCloudProjectDelete(d *schema.ResourceData, meta interface{}) error {
_, details, err := orderRead(d, meta)
order, details, err := orderRead(d, meta)
if err != nil {
return fmt.Errorf("Could not read cloudProject order: %q", err)
}

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

// in the US, for reasons too long to be detailled here, cloud project order domain is not the public cloud project id, but "*".
// There have been discussions to align US & EU, but they've failed.
// So we end up making a few extra queries to fetch the project id from operations details.
if !publicCloudProjectNameFormatRegex.MatchString(serviceName) {
orderDetailId := details[0].OrderDetailId
operations, err := orderDetailOperations(config.OVHClient, order.OrderId, orderDetailId)
if err != nil {
return fmt.Errorf("Could not read cloudProject order details operations: %q", err)
}
for _, operation := range operations {
if !publicCloudProjectNameFormatRegex.MatchString(operation.Resource.Name) {
continue
}
serviceName = operation.Resource.Name
}
}

id := d.Id()

terminate := func() (string, error) {
Expand Down
14 changes: 14 additions & 0 deletions ovh/types_me_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ func (v MeOrderDetail) ToMap() map[string]interface{} {
return obj
}

type MeOrderDetailOperation struct {
Status string `json:"status"`
ID int `json:"id"`
Type string `json:"type"`
Resource MeOrderDetailOperationResource `json:"resource"`
Quantity int `json:"quantity"`
}

type MeOrderDetailOperationResource struct {
Name string `json:"name"`
State string `json:"state"`
DisplayName string `json:"displayName"`
}

type MeOrderPaymentOpts struct {
PaymentMean string `json:"paymentMean"`
PaymentMeanId *int64 `json:"paymentMeanId,omitEmpty"`
Expand Down
5 changes: 4 additions & 1 deletion website/docs/r/cloud_project.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ data "ovh_order_cart_product_plan" "cloud" {
price_capacity = "renew"
product = "cloud"
plan_code = "project.2018"
# plan_code = "project" # when running in the US
}

resource "ovh_cloud_project" "my_cloud_project" {
Expand All @@ -37,6 +38,8 @@ resource "ovh_cloud_project" "my_cloud_project" {
}
```

-> __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.

## Argument Reference

The following arguments are supported:
Expand All @@ -46,7 +49,7 @@ The following arguments are supported:
* `ovh_subsidiary` - (Required) OVHcloud Subsidiary
* `plan` - (Required) Product Plan to order
* `duration` - (Required) duration
* `plan_code` - (Required) Plan code
* `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`.
* `pricing_mode` - (Required) Pricing model identifier
* `catalog_name` - Catalog name
* `configuration` - (Optional) Representation of a configuration item for personalizing product
Expand Down