alexkappa/terraform-provider-auth0

View on GitHub
auth0/resource_auth0_action.go

Summary

Maintainability
B
4 hrs
Test Coverage
B
81%
package auth0

import (
    "fmt"
    "net/http"
    "strings"

    "github.com/alexkappa/terraform-provider-auth0/auth0/internal/hash"
    "github.com/hashicorp/terraform-plugin-sdk/helper/resource"
    "github.com/hashicorp/terraform-plugin-sdk/helper/schema"
    "github.com/hashicorp/terraform-plugin-sdk/helper/validation"

    "gopkg.in/auth0.v5/management"
)

func newAction() *schema.Resource {
    return &schema.Resource{

        Create: createAction,
        Read:   readAction,
        Update: updateAction,
        Delete: deleteAction,
        Importer: &schema.ResourceImporter{
            State: schema.ImportStatePassthrough,
        },

        Schema: map[string]*schema.Schema{
            "name": {
                Type:        schema.TypeString,
                Required:    true,
                Description: "The name of an action",
            },
            "supported_triggers": {
                Type:     schema.TypeList,
                Required: true,
                MinItems: 1,
                MaxItems: 1, // NOTE: Changes must be made together with expandAction()
                Elem: &schema.Resource{
                    Schema: map[string]*schema.Schema{
                        "id": {
                            Type:        schema.TypeString,
                            Required:    true,
                            Description: "Trigger ID",
                        },
                        "version": {
                            Type:        schema.TypeString,
                            Required:    true,
                            Description: "Trigger version",
                        },
                    },
                },
                Description: "List of triggers that this action supports. At " +
                    "this time, an action can only target a single trigger at" +
                    " a time",
            },
            "code": {
                Type:        schema.TypeString,
                Required:    true,
                Description: "The source code of the action.",
            },
            "dependencies": {
                Type:     schema.TypeSet,
                Optional: true,
                MinItems: 1,
                Elem: &schema.Resource{
                    Schema: map[string]*schema.Schema{
                        "name": {
                            Type:        schema.TypeString,
                            Required:    true,
                            Description: "Dependency name. For example lodash",
                        },
                        "version": {
                            Type:        schema.TypeString,
                            Required:    true,
                            Description: "Dependency version. For example `latest` or `4.17.21`",
                        },
                    },
                },
                Set:         hash.StringKey("name"),
                Description: "List of third party npm modules, and their versions, that this action depends on",
            },
            "runtime": {
                Type:     schema.TypeString,
                Optional: true,
                Computed: true,
                ValidateFunc: validation.StringInSlice([]string{
                    "node12",
                    "node16",
                }, false),
                Description: "The Node runtime. For example `node16`, defaults to `node12`",
            },
            "secrets": {
                Type:     schema.TypeList,
                Optional: true,
                MinItems: 1,
                Elem: &schema.Resource{
                    Schema: map[string]*schema.Schema{
                        "name": {
                            Type:        schema.TypeString,
                            Required:    true,
                            Description: "Secret name",
                        },
                        "value": {
                            Type:        schema.TypeString,
                            Required:    true,
                            Sensitive:   true,
                            Description: "Secret value",
                        },
                    },
                },
                Description: "List of secrets that are included in an action or a version of an action",
            },
            "deploy": {
                Type:     schema.TypeBool,
                Optional: true,
                Default:  false,
                Description: "Deploying an action will create a new immutable" +
                    " version of the action. If the action is currently bound" +
                    " to a trigger, then the system will begin executing the " +
                    "newly deployed version of the action immediately",
            },
            "version_id": {
                Type:        schema.TypeString,
                Computed:    true,
                Description: "Version ID of the action. This value is available if `deploy` is set to true",
            },
        },
    }
}

func createAction(d *schema.ResourceData, m interface{}) error {
    api := m.(*management.Management)
    a := expandAction(d)
    err := api.Action.Create(a)
    if err != nil {
        return err
    }
    d.SetId(a.GetID())

    d.Partial(true)
    err = deployAction(d, m)
    if err != nil {
        return err
    }
    d.Partial(false)

    return readAction(d, m)
}

func readAction(d *schema.ResourceData, m interface{}) error {
    api := m.(*management.Management)
    a, err := api.Action.Read(d.Id())
    if err != nil {
        if mErr, ok := err.(management.Error); ok {
            if mErr.Status() == http.StatusNotFound {
                d.SetId("")
                return nil
            }
        }
        return err
    }

    d.Set("name", a.Name)
    d.Set("supported_triggers", flattenActionTriggers(a.SupportedTriggers))
    d.Set("code", a.Code)
    d.Set("dependencies", flattenActionDependencies(a.Dependencies))
    d.Set("runtime", a.Runtime)

    if a.DeployedVersion != nil {
        d.Set("version_id", a.DeployedVersion.GetID())
    }

    return nil
}

func updateAction(d *schema.ResourceData, m interface{}) error {
    a := expandAction(d)
    api := m.(*management.Management)
    err := api.Action.Update(d.Id(), a)
    if err != nil {
        return err
    }
    d.Partial(true)
    err = deployAction(d, m)
    if err != nil {
        return err
    }
    d.Partial(false)
    return readAction(d, m)
}

func deployAction(d *schema.ResourceData, m interface{}) error {

    if d.Get("deploy").(bool) == true {

        api := m.(*management.Management)

        err := resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError {

            a, err := api.Action.Read(d.Id())
            if err != nil {
                return resource.NonRetryableError(err)
            }

            if strings.ToLower(a.GetStatus()) != "built" {
                return resource.RetryableError(
                    fmt.Errorf(`Expected action status %q to equal "built"`, a.GetStatus()),
                )
            }

            return nil
        })
        if err != nil {
            return fmt.Errorf("Action never reached built state. %w", err)
        }

        v, err := api.Action.Deploy(d.Id())
        if err != nil {
            return err
        }

        d.Set("version_id", v.GetID())
    }
    return nil
}

func deleteAction(d *schema.ResourceData, m interface{}) error {
    api := m.(*management.Management)
    if err := api.Action.Delete(d.Id()); err != nil {
        if mErr, ok := err.(management.Error); ok {
            if mErr.Status() == http.StatusNotFound {
                d.SetId("")
                return nil
            }
        }
        return err
    }
    return nil
}

func expandAction(d *schema.ResourceData) *management.Action {

    a := &management.Action{
        Name:    String(d, "name"),
        Code:    String(d, "code"),
        Runtime: String(d, "runtime"),
    }

    List(d, "supported_triggers").Elem(func(d ResourceData) {
        a.SupportedTriggers = []*management.ActionTrigger{
            {
                ID:      String(d, "id"),
                Version: String(d, "version"),
            },
        }
    })

    Set(d, "dependencies").Elem(func(d ResourceData) {
        a.Dependencies = append(a.Dependencies, &management.ActionDependency{
            Name:    String(d, "name"),
            Version: String(d, "version"),
        })
    })

    List(d, "secrets").Elem(func(d ResourceData) {
        a.Secrets = append(a.Secrets, &management.ActionSecret{
            Name:  String(d, "name"),
            Value: String(d, "value"),
        })
    })

    return a
}

func flattenActionTriggers(triggers []*management.ActionTrigger) (ret []interface{}) {
    for _, trigger := range triggers {
        ret = append(ret, map[string]interface{}{
            "id":      trigger.ID,
            "version": trigger.Version,
        })
    }
    return
}

func flattenActionDependencies(dependencies []*management.ActionDependency) (ret []interface{}) {
    for _, dependency := range dependencies {
        ret = append(ret, map[string]interface{}{
            "name":    dependency.Name,
            "version": dependency.Version,
        })
    }
    return
}

func flattenActionSecrets(secrets []*management.ActionSecret) (ret []interface{}) {
    for _, secret := range secrets {
        ret = append(ret, map[string]interface{}{
            "name":  secret.Name,
            "value": secret.Value,
        })
    }
    return
}