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

Use the API backend for online validation #201

Merged
merged 13 commits into from
May 3, 2023
25 changes: 11 additions & 14 deletions cloudamqp/data_source_cloudamqp_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ func dataSourceInstance() *schema.Resource {
Computed: true,
Description: "If default alarms set or not for the instance",
},
"backend": {
Type: schema.TypeString,
Computed: true,
Description: "Software backend used, determined by subscription plan",
},
},
}
}
Expand All @@ -123,14 +128,6 @@ func dataSourceInstanceRead(d *schema.ResourceData, meta interface{}) error {
if k == "vpc" {
err = d.Set("vpc_id", v.(map[string]interface{})["id"])
err = d.Set("vpc_subnet", v.(map[string]interface{})["subnet"])
} else if k == "nodes" {
plan := d.Get("plan").(string)
if is2020Plan(plan) {
nodes := numberOfNodes(plan)
err = d.Set(k, nodes)
} else {
err = d.Set(k, v)
}
} else {
err = d.Set(k, v)
}
Expand All @@ -141,6 +138,12 @@ func dataSourceInstanceRead(d *schema.ResourceData, meta interface{}) error {
}
}

if v, ok := d.Get("nodes").(int); ok && v > 0 {
d.Set("dedicated", true)
} else {
d.Set("dedicated", false)
}

if err = d.Set("host", data["hostname_external"].(string)); err != nil {
return fmt.Errorf("error setting host for resource %s: %s", d.Id(), err)
}
Expand All @@ -153,12 +156,6 @@ func dataSourceInstanceRead(d *schema.ResourceData, meta interface{}) error {
d.Set("no_default_alarms", false)
}

planType, _ := getPlanType(d.Get("plan").(string))
dedicated := planType == "dedicated"
if err = d.Set("dedicated", dedicated); err != nil {
return fmt.Errorf("error setting dedicated for resource %s: %s", d.Id(), err)
}

data = api.UrlInformation(data["url"].(string))
for k, v := range data {
if validateInstanceSchemaAttribute(k) {
Expand Down
139 changes: 43 additions & 96 deletions cloudamqp/resource_cloudamqp_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ package cloudamqp

import (
"fmt"
"regexp"
"strconv"

"github.com/84codes/go-api/api"
"github.com/hashicorp/terraform-plugin-sdk/helper/customdiff"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)

func resourceInstance() *schema.Resource {
Expand All @@ -27,10 +24,9 @@ func resourceInstance() *schema.Resource {
Description: "Name of the instance",
},
"plan": {
Type: schema.TypeString,
Required: true,
Description: "Name of the plan, see documentation for valid plans",
ValidateFunc: validatePlanName(),
Type: schema.TypeString,
Required: true,
Description: "Name of the plan, see documentation for valid plans",
},
"region": {
Type: schema.TypeString,
Expand Down Expand Up @@ -121,14 +117,33 @@ func resourceInstance() *schema.Resource {
Default: false,
Description: "Keep associated VPC when deleting instance",
},
"backend": {
Type: schema.TypeString,
Computed: true,
Description: "Software backend used, determined by subscription plan",
},
},
CustomizeDiff: customdiff.All(
customdiff.ForceNewIfChange("plan", func(old, new, meta interface{}) bool {
// Recreate instance if changing plan type (from dedicated to shared or vice versa)
oldPlanType, _ := getPlanType(old.(string))
newPlanType, _ := getPlanType(new.(string))
api := meta.(*api.API)
oldPlanType, newPlanType := api.PlanTypes(old.(string), new.(string))
return !(oldPlanType == newPlanType)
}),
customdiff.ValidateChange("plan", func(old, new, meta interface{}) error {
if old == new {
return nil
}
api := meta.(*api.API)
return api.ValidatePlan(new.(string))
}),
customdiff.ValidateChange("region", func(old, new, meta interface{}) error {
if old == new {
return nil
}
api := meta.(*api.API)
return api.ValidateRegion(new.(string))
}),
),
}
}
Expand All @@ -147,23 +162,18 @@ func resourceCreate(d *schema.ResourceData, meta interface{}) error {
params[k] = false
}

if k == "nodes" {
// Remove keys from params
switch k {
case "nodes":
plan := d.Get("plan").(string)
if is2020Plan(plan) {
nodes := numberOfNodes(plan)
params[k] = nodes
} else if isSharedPlan(plan) {
if isSharedPlan(plan) || !isLegacyPlan(plan) {
delete(params, k)
}
}

if k == "vpc_id" {
case "vpc_id":
if d.Get(k).(int) == 0 {
delete(params, k)
}
}

if k == "vpc_subnet" {
case "vpc_subnet":
if d.Get(k) == "" {
delete(params, k)
}
Expand Down Expand Up @@ -191,16 +201,6 @@ func resourceRead(d *schema.ResourceData, meta interface{}) error {
if validateInstanceSchemaAttribute(k) {
if k == "vpc" {
err = d.Set("vpc_id", v.(map[string]interface{})["id"])
} else if k == "nodes" {
plan := d.Get("plan").(string)
if is2020Plan(plan) {
nodes := numberOfNodes(plan)
err = d.Set(k, nodes)
} else if isSharedPlan(plan) {
continue
} else {
err = d.Set(k, v)
}
} else {
err = d.Set(k, v)
}
Expand All @@ -211,6 +211,12 @@ func resourceRead(d *schema.ResourceData, meta interface{}) error {
}
}

if v, ok := d.Get("nodes").(int); ok && v > 0 {
d.Set("dedicated", true)
} else {
d.Set("dedicated", false)
}

if err = d.Set("host", data["hostname_external"].(string)); err != nil {
return fmt.Errorf("error setting host for resource %s: %s", d.Id(), err)
}
Expand All @@ -219,12 +225,6 @@ func resourceRead(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("error setting host for resource %s: %s", d.Id(), err)
}

planType, _ := getPlanType(d.Get("plan").(string))
dedicated := planType == "dedicated"
if err = d.Set("dedicated", dedicated); err != nil {
return fmt.Errorf("error setting dedicated for resource %s: %s", d.Id(), err)
}

data = api.UrlInformation(data["url"].(string))
for k, v := range data {
if validateInstanceSchemaAttribute(k) {
Expand All @@ -246,10 +246,7 @@ func resourceUpdate(d *schema.ResourceData, meta interface{}) error {
}
if k == "nodes" {
plan := d.Get("plan").(string)
if is2020Plan(plan) {
nodes := numberOfNodes(plan)
params[k] = nodes
} else if isSharedPlan(plan) {
if isSharedPlan(plan) || !isLegacyPlan(plan) {
delete(params, k)
}
}
Expand Down Expand Up @@ -280,84 +277,34 @@ func validateInstanceSchemaAttribute(key string) bool {
"tags",
"vhost",
"no_default_alarms",
"ready":
"ready",
"backend":
return true
}
return false
}

func getPlanType(plan string) (string, error) {
switch plan {
case "lemur", "tiger", "lemming":
return "shared", nil
// Legacy plans
case "bunny", "rabbit", "panda", "ape", "hippo", "lion",
// 2020 plans
"squirrel-1",
"hare-1", "hare-3",
"bunny-1", "bunny-3",
"rabbit-1", "rabbit-3", "rabbit-5",
"panda-1", "panda-3", "panda-5",
"ape-1", "ape-3", "ape-5",
"hippo-1", "hippo-3", "hippo-5",
"lion-1", "lion-3", "lion-5",
"rhino-1":
return "dedicated", nil
}
return "", fmt.Errorf("couldn't find a matching plan type for: %s", plan)
}

func validatePlanName() schema.SchemaValidateFunc {
return validation.StringInSlice([]string{
"lemur", "tiger", "lemming",
"bunny", "rabbit", "panda", "ape", "hippo", "lion",
"squirrel-1",
"hare-1", "hare-3",
"bunny-1", "bunny-3",
"rabbit-1", "rabbit-3", "rabbit-5",
"panda-1", "panda-3", "panda-5",
"ape-1", "ape-3", "ape-5",
"hippo-1", "hippo-3", "hippo-5",
"lion-1", "lion-3", "lion-5",
"rhino-1",
}, true)
}

func isSharedPlan(plan string) bool {
switch plan {
case
"lemur",
"tiger",
"lemming":
"lemming",
"ermine":
return true
}
return false
}

func is2020Plan(plan string) bool {
func isLegacyPlan(plan string) bool {
switch plan {
case
"squirrel-1",
"hare-1", "hare-3",
"bunny-1", "bunny-3",
"rabbit-1", "rabbit-3", "rabbit-5",
"panda-1", "panda-3", "panda-5",
"ape-1", "ape-3", "ape-5",
"hippo-1", "hippo-3", "hippo-5",
"lion-1", "lion-3", "lion-5",
"rhino-1":
"bunny", "rabbit", "panda", "ape", "hippo", "lion":
return true
}
return false
}

func numberOfNodes(plan string) int {
r := regexp.MustCompile("[135]")
match := r.FindString(plan)
nodes, _ := strconv.Atoi(match)
return nodes
}

func instanceCreateAttributeKeys() []string {
return []string{
"name",
Expand Down
2 changes: 2 additions & 0 deletions docs/data-sources/instance.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ All attributes reference are computed
* `host` - The external hostname for the CloudAMQP instance.
* `host_internal` - The internal hostname for the CloudAMQP instance.
* `vhost` - The virtual host configured in Rabbit MQ.
* `dedicated` - Information if the CloudAMQP instance is shared or dedicated.
* `backend` - Information if the CloudAMQP instance runs either RabbitMQ or LavinMQ.
43 changes: 41 additions & 2 deletions docs/guides/info_plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,23 @@ description: |-

# Subscription plans

Table below shows subscription plans for CloudAMQP. `Lemur`is free of charge, for full price list see [cloudamqp](https://www.cloudamqp.com/plans.html). `Lemur`and `Tiger` are shared instances and share underlying hardware with other instances. They are also limited to which CloudAMQP provider resources that can be used. Further information on availability on each resource page.
Tables below shows general subscription plans for CloudAMQP for either `RabbitMQ` or `LavinMQ`, for full price list see [cloudamqp](https://www.cloudamqp.com/plans.html).

*Information can differ from your actually valid plans, e.g. your team have been given preview access to unreleased plans. The up to date collection of available plans can be retrieved with your team API access key.*

```shell
curl -u :xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx \
https://customer.cloudamqp.com/api/plans
```

## Plans using RabbitMQ

`Lemur` and `Tiger` are shared instances and share underlying hardware with other instances. They are also limited to which CloudAMQP provider resources that can be used. Further information on availability on each resource page.

Name | Plan | Type | Nodes
---- | ---- | ---- | ----
Little lemur | lemur | shared
Tough Tiger | tiger | shared
Lemming (Beta) | lemming | shared
Sassy Squirrel | squirrel-1 | dedicated | 1
Big Bunny | bunny-1,3 | dedicated | 1,3
Roaring Rabbit | rabbit-1,3,5 | dedicated | 1,3,5
Expand All @@ -24,7 +34,36 @@ Heavy Hippo | hippo-1,3,5 | dedicated | 1,3,5
Loud Lion | lion-1,3,5 | dedicated | 1,3,5
Raging Rhino | rhino-1 | dedicated | 1

```shell
# Filter out available plans for RabbitMQ
curl -u :xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx \
https://customer.cloudamqp.com/api/plans?backend=rabbitmq
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each of these examples with filtered requests would require: https://github.com/84codes/customer-console/pull/164

Or if we should just reference our docs.cloudamqp.com.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we should document it there and reference it

```

## Plans using LavinMQ

`Lemming`and `Ermine` are shared instances and share underlying hardware with other instances. They are also limited to which CloudAMQP provider resources that can be used. Further information on availability on each resource page.

Name | Plan | Type | Nodes
---- | ---- | ---- | ----
Loyal Lemming | lemming | shared
Elegant Ermine | ermine | shared
Passionate Puffin | puffin-1 | dedicated | 1
Playful Penguin | penguin-1 | dedicated | 1
Lively Lynx | lynx-1 | dedicated | 1
Wild Wolverine | wolverine-1 | dedicated | 1
Remarkable Reindeer | reindeer-1 | dedicated | 1
Brave Bear | bear-1 | dedicated | 1
Outstanding Orca | orca-1 | dedicated | 1

```shell
# Filter out available plans for LavinMQ
curl -u :xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx \
https://customer.cloudamqp.com/api/plans?backend=lavinmq
```

<br>

# Legacy subscription plans

Table below shows deprecated subscription plans for CloudAMQP. Existing plans will still work, but there will not be possible to create new ones.
Expand Down
12 changes: 12 additions & 0 deletions docs/guides/info_region.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ description: |-

CloudAMQP support hosting by multiple cloud platform providers and over multiple regions. Below a few examples of supported platforms and regions. For fully updated list see [CloudAMQP plans](https://www.cloudamqp.com/plans.html) and scroll to the bottom and extend `List all available regions`. Platforms and regions with shared servers are also listed, for AWS we try to have at least one shared server supported for each region.

*The complete list can also be retrieved with your team API access key.*

```shell
# List all supported platform providers and regions
curl -u :xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx \
https://customer.cloudamqp.com/api/regions

# Filter regions by platform provider
curl -u :xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx \
https://customer.cloudamqp.com/api/regions?provider=amazon-web-services
```

Format used on instance regions are as follow `{provider}::{region}`

```hcl
Expand Down
Loading