OpsLevel / terraform-provider-opslevel

Terraform provider for OpsLevel.com
https://registry.terraform.io/providers/OpsLevel/opslevel/latest/docs
MIT License
8 stars 5 forks source link

update reconcileAliases to use opslevel-go implementation #383

Closed davidbloss closed 1 month ago

davidbloss commented 1 month ago

Issues

Combine reconcileAliases

Changelog

Add Config warning when creating a resource with aliases where slug is missing. Use new ReconcileAliases function from opslevel-go

Tophatting

With this Terraform config:

resource "opslevel_infrastructure" "example" {
  aliases = []
  schema  = "Database"
  owner   = opslevel_team.example.id
  provider_data = {
    account = "dev2"
  }
  data = jsonencode({
    name = "my-database"
  })
}

resource "opslevel_service" "example" {
  name    = "foo25"
  aliases = []

  description = "The test service"
  framework   = "something fancy"
  language    = "elixir"

  lifecycle_alias = "beta"
  tier_alias      = "tier_2"
  owner           = opslevel_team.example.id

  api_document_path             = "/swagger.json"
  preferred_api_document_source = "PULL" //or "PUSH"
}

resource "opslevel_team" "example" {
  name             = "foo4"
  aliases          = []
  responsibilities = "Responsible for foo frontend and backend"

  member {
    email = "david+pat@opslevel.com"
    role  = "contributor"
  }
  member {
    email = "taimoor+pat@opslevel.com"
    role  = "contributor"
  }
  member {
    email = "kyle+pat@opslevel.com"
    role  = "manager"
  }
}

Create resources with aliases = [], terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # opslevel_infrastructure.example will be created
  + resource "opslevel_infrastructure" "example" {
      + aliases       = []
      + data          = jsonencode(
            {
              + name = "my-database"
            }
        )
      + id            = (known after apply)
      + owner         = (known after apply)
      + provider_data = {
          + account = "dev2"
        }
      + schema        = "Database"
    }

  # opslevel_service.example will be created
  + resource "opslevel_service" "example" {
      + aliases                       = []
      + api_document_path             = "/swagger.json"
      + description                   = "The test service"
      + framework                     = "something fancy"
      + id                            = (known after apply)
      + language                      = "elixir"
      + lifecycle_alias               = "beta"
      + name                          = "foo25"
      + owner                         = (known after apply)
      + preferred_api_document_source = "PULL"
      + tier_alias                    = "tier_2"
    }

  # opslevel_team.example will be created
  + resource "opslevel_team" "example" {
      + aliases          = []
      + id               = (known after apply)
      + name             = "foo4"
      + responsibilities = "Responsible for foo frontend and backend"

      + member {
          + email = "david+pat@opslevel.com"
          + role  = "contributor"
        }
      + member {
          + email = "kyle+pat@opslevel.com"
          + role  = "manager"
        }
      + member {
          + email = "taimoor+pat@opslevel.com"
          + role  = "contributor"
        }
    }

Plan: 3 to add, 0 to change, 0 to destroy.
opslevel_team.example: Creating...
opslevel_team.example: Creation complete after 0s [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xOTUxOA]
opslevel_infrastructure.example: Creating...
opslevel_service.example: Creating...
opslevel_infrastructure.example: Creation complete after 1s [id=Z2lkOi8vb3BzbGV2ZWwvRW50aXR5T2JqZWN0LzI3NTUwNTg]
opslevel_service.example: Creation complete after 2s [id=Z2lkOi8vb3BzbGV2ZWwvU2VydmljZS8xMjU3MjI]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Destroy resources, terraform destroy


On create, Warn users that aliases are created and how they can work around an infinite apply loop

With config aliases set, but without value in name

resource "opslevel_team" "example" {
  name             = "foo4"
  aliases          = ["newnewnew"]   # <-- slug is missing here
  responsibilities = "Responsible for foo frontend and backend"

  member {
    email = "david+pat@opslevel.com"
    role  = "contributor"
  }
  member {
    email = "taimoor+pat@opslevel.com"
    role  = "contributor"
  }
  member {
    email = "kyle+pat@opslevel.com"
    role  = "manager"
  }
}

Run terraform apply, note missing alias foo4_11

# ... truncated output
Plan: 1 to add, 0 to change, 0 to destroy.
opslevel_team.example: Creating...
╷
│ Warning: opslevel client error
│ 
│   with opslevel_team.example,
│   on main.tf line 29, in resource "opslevel_team" "example":
│   29: resource "opslevel_team" "example" {
│ 
│ warning while reconciling team aliases: '[newnewnew]'
│ OpsLevel API Errors:
│       - 'alias' slug is locked, it cannot be deleted
│ 
│ OpsLevel API Errors:
│       - 'alias' Alias 'newnewnew' has already been taken
│ 
╵
╷
│ Warning: Config warning
│ 
│   with opslevel_team.example,
│   on main.tf line 29, in resource "opslevel_team" "example":
│   29: resource "opslevel_team" "example" {
│ 
│ On create, OpsLevel API creates a new alias for teams. If this causes issues, create team with empty 'aliases'. Then update team with 'aliases'
╵
╷
│ Error: Config error
│ 
│   with opslevel_team.example,
│   on main.tf line 29, in resource "opslevel_team" "example":
│   29: resource "opslevel_team" "example" {
│ 
│ default aliases from API need to be added to config: [foo4_11 newnewnew]

Add missing alias, terraform apply again (still fails - needs alias foo4_12 now)

# ... truncated output
│ Warning: Config warning
│ 
│   with opslevel_team.example,
│   on main.tf line 29, in resource "opslevel_team" "example":
│   29: resource "opslevel_team" "example" {
│ 
│ On create, OpsLevel API creates a new alias for teams. If this causes issues, create team with empty 'aliases'. Then update team with 'aliases'
╵
╷
│ Error: Config error
│ 
│   with opslevel_team.example,
│   on main.tf line 29, in resource "opslevel_team" "example":
│   29: resource "opslevel_team" "example" {
│ 
│ default aliases from API need to be added to config: [foo4_12 foo4_11 newnewnew]

Happy path

With this config, note each resource has aliases = []

resource "opslevel_infrastructure" "example" {
  aliases = []
  schema  = "Database"
  owner   = opslevel_team.example.id
  provider_data = {
    account = "dev2"
  }
  data = jsonencode({
    name = "my-database"
  })
}

resource "opslevel_service" "example" {
  name    = "foo25"
  aliases = []

  description = "The test service"
  framework   = "something fancy"
  language    = "elixir"

  lifecycle_alias = "beta"
  tier_alias      = "tier_2"
  owner           = opslevel_team.example.id

  api_document_path             = "/swagger.json"
  preferred_api_document_source = "PULL" //or "PUSH"
}

resource "opslevel_team" "example" {
  name             = "foo4"
  aliases          = []
  responsibilities = "Responsible for foo frontend and backend"

  member {
    email = "david+pat@opslevel.com"
    role  = "contributor"
  }
  member {
    email = "taimoor+pat@opslevel.com"
    role  = "contributor"
  }
  member {
    email = "kyle+pat@opslevel.com"
    role  = "manager"
  }
}

Create resources, terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # opslevel_infrastructure.example will be created
  + resource "opslevel_infrastructure" "example" {
      + aliases       = []
      + data          = jsonencode(
            {
              + name = "my-database"
            }
        )
      + id            = (known after apply)
      + owner         = (known after apply)
      + provider_data = {
          + account = "dev2"
        }
      + schema        = "Database"
    }

  # opslevel_service.example will be created
  + resource "opslevel_service" "example" {
      + aliases                       = []
      + api_document_path             = "/swagger.json"
      + description                   = "The test service"
      + framework                     = "something fancy"
      + id                            = (known after apply)
      + language                      = "elixir"
      + lifecycle_alias               = "beta"
      + name                          = "foo25"
      + owner                         = (known after apply)
      + preferred_api_document_source = "PULL"
      + tier_alias                    = "tier_2"
    }

  # opslevel_team.example will be created
  + resource "opslevel_team" "example" {
      + aliases          = []
      + id               = (known after apply)
      + name             = "foo4"
      + responsibilities = "Responsible for foo frontend and backend"

      + member {
          + email = "david+pat@opslevel.com"
          + role  = "contributor"
        }
      + member {
          + email = "kyle+pat@opslevel.com"
          + role  = "manager"
        }
      + member {
          + email = "taimoor+pat@opslevel.com"
          + role  = "contributor"
        }
    }

Plan: 3 to add, 0 to change, 0 to destroy.
opslevel_team.example: Creating...
opslevel_team.example: Creation complete after 1s [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xOTUzOA]
opslevel_infrastructure.example: Creating...
opslevel_service.example: Creating...
opslevel_infrastructure.example: Creation complete after 0s [id=Z2lkOi8vb3BzbGV2ZWwvRW50aXR5T2JqZWN0LzI3NTY2NTc]
opslevel_service.example: Creation complete after 1s [id=Z2lkOi8vb3BzbGV2ZWwvU2VydmljZS8xMjU3NDA]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Update config, aliases are set but slug is missing

resource "opslevel_infrastructure" "example" {
  aliases = ["infratest"]
  schema  = "Database"
  owner   = opslevel_team.example.id
  provider_data = {
    account = "dev2"
  }
  data = jsonencode({
    name = "my-database"
  })
}

resource "opslevel_service" "example" {
  name    = "foo25"
  aliases = ["servicetest"]

  description = "The test service"
  framework   = "something fancy"
  language    = "elixir"

  lifecycle_alias = "beta"
  tier_alias      = "tier_2"
  owner           = opslevel_team.example.id

  api_document_path             = "/swagger.json"
  preferred_api_document_source = "PULL" //or "PUSH"
}

resource "opslevel_team" "example" {
  name             = "foo4"
  aliases          = ["teamtest"]
  responsibilities = "Responsible for foo frontend and backend"

  member {
    email = "david+pat@opslevel.com"
    role  = "contributor"
  }
  member {
    email = "taimoor+pat@opslevel.com"
    role  = "contributor"
  }
  member {
    email = "kyle+pat@opslevel.com"
    role  = "manager"
  }
}

Attempt to updated resources, needs slugs raised by Config error

# ...truncted output
│ Error: Config error
│ 
│   with opslevel_team.example,
│   on main.tf line 29, in resource "opslevel_team" "example":
│   29: resource "opslevel_team" "example" {
│ 
│ default aliases from API need to be added to config: [foo4_13]

### Add `foo4_13` to `aliases` then `terraform apply`. Infra and Team are created, service has same alias issue
```tf
opslevel_team.example: Refreshing state... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xOTUzOA]
opslevel_infrastructure.example: Refreshing state... [id=Z2lkOi8vb3BzbGV2ZWwvRW50aXR5T2JqZWN0LzI3NTY2NTc]
opslevel_service.example: Refreshing state... [id=Z2lkOi8vb3BzbGV2ZWwvU2VydmljZS8xMjU3NDA]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # opslevel_infrastructure.example will be updated in-place
  ~ resource "opslevel_infrastructure" "example" {
      ~ aliases       = [
          + "infratest",
        ]
        id            = "Z2lkOi8vb3BzbGV2ZWwvRW50aXR5T2JqZWN0LzI3NTY2NTc"
        # (4 unchanged attributes hidden)
    }

  # opslevel_service.example will be updated in-place
  ~ resource "opslevel_service" "example" {
      ~ aliases                       = [
          + "servicetest",
        ]
        id                            = "Z2lkOi8vb3BzbGV2ZWwvU2VydmljZS8xMjU3NDA"
        name                          = "foo25"
        # (8 unchanged attributes hidden)
    }

  # opslevel_team.example will be updated in-place
  ~ resource "opslevel_team" "example" {
      + aliases          = [
          + "foo4_13",
          + "teamtest",
        ]
        id               = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xOTUzOA"
        name             = "foo4"
        # (1 unchanged attribute hidden)

      + member {
          + email = "david+pat@opslevel.com"
          + role  = "contributor"
        }
      + member {
          + email = "kyle+pat@opslevel.com"
          + role  = "manager"
        }
      + member {
          + email = "taimoor+pat@opslevel.com"
          + role  = "contributor"
        }
    }

Plan: 0 to add, 3 to change, 0 to destroy.
opslevel_team.example: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xOTUzOA]
opslevel_team.example: Modifications complete after 1s [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xOTUzOA]
opslevel_infrastructure.example: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvRW50aXR5T2JqZWN0LzI3NTY2NTc]
opslevel_service.example: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvU2VydmljZS8xMjU3NDA]
opslevel_infrastructure.example: Modifications complete after 1s [id=Z2lkOi8vb3BzbGV2ZWwvRW50aXR5T2JqZWN0LzI3NTY2NTc]
╷
│ Warning: opslevel client error
│ 
│   with opslevel_service.example,
│   on main.tf line 13, in resource "opslevel_service" "example":
│   13: resource "opslevel_service" "example" {
│ 
│ Unable to reconcile service aliases: '[servicetest]'
│ OpsLevel API Errors:
│       - 'alias' slug is locked, it cannot be deleted
│ 
╵
╷
│ Error: Config error
│ 
│   with opslevel_service.example,
│   on main.tf line 13, in resource "opslevel_service" "example":
│   13: resource "opslevel_service" "example" {
│ 
│ default aliases from API need to be added to config: [foo25]

Fix service alias, aliases = ["servicetest", "foo25"], terraform apply

opslevel_team.example: Refreshing state... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xOTUzOA]
opslevel_infrastructure.example: Refreshing state... [id=Z2lkOi8vb3BzbGV2ZWwvRW50aXR5T2JqZWN0LzI3NTY2NTc]
opslevel_service.example: Refreshing state... [id=Z2lkOi8vb3BzbGV2ZWwvU2VydmljZS8xMjU3NDA]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # opslevel_service.example will be updated in-place
  ~ resource "opslevel_service" "example" {
      ~ aliases                       = [
          + "foo25",
          + "servicetest",
        ]
        id                            = "Z2lkOi8vb3BzbGV2ZWwvU2VydmljZS8xMjU3NDA"
        name                          = "foo25"
        # (8 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.
opslevel_service.example: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvU2VydmljZS8xMjU3NDA]
opslevel_service.example: Modifications complete after 1s [id=Z2lkOi8vb3BzbGV2ZWwvU2VydmljZS8xMjU3NDA]

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

Unset aliases (aliases = null) for all resources then terraform apply

opslevel_team.example: Refreshing state... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xOTUzOA]
opslevel_service.example: Refreshing state... [id=Z2lkOi8vb3BzbGV2ZWwvU2VydmljZS8xMjU3NDA]
opslevel_infrastructure.example: Refreshing state... [id=Z2lkOi8vb3BzbGV2ZWwvRW50aXR5T2JqZWN0LzI3NTY2NTc]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # opslevel_infrastructure.example will be updated in-place
  ~ resource "opslevel_infrastructure" "example" {
      - aliases       = [
          - "infratest",
        ] -> null
        id            = "Z2lkOi8vb3BzbGV2ZWwvRW50aXR5T2JqZWN0LzI3NTY2NTc"
        # (4 unchanged attributes hidden)
    }

  # opslevel_service.example will be updated in-place
  ~ resource "opslevel_service" "example" {
      - aliases                       = [
          - "foo25",
          - "servicetest",
        ] -> null
        id                            = "Z2lkOi8vb3BzbGV2ZWwvU2VydmljZS8xMjU3NDA"
        name                          = "foo25"
        # (8 unchanged attributes hidden)
    }

  # opslevel_team.example will be updated in-place
  ~ resource "opslevel_team" "example" {
      - aliases          = [
          - "foo4_13",
          - "teamtest",
        ] -> null
        id               = "Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xOTUzOA"
        name             = "foo4"
        # (1 unchanged attribute hidden)

        # (3 unchanged blocks hidden)
    }

Plan: 0 to add, 3 to change, 0 to destroy.
opslevel_team.example: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xOTUzOA]
opslevel_team.example: Modifications complete after 1s [id=Z2lkOi8vb3BzbGV2ZWwvVGVhbS8xOTUzOA]
opslevel_infrastructure.example: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvRW50aXR5T2JqZWN0LzI3NTY2NTc]
opslevel_service.example: Modifying... [id=Z2lkOi8vb3BzbGV2ZWwvU2VydmljZS8xMjU3NDA]
opslevel_infrastructure.example: Modifications complete after 1s [id=Z2lkOi8vb3BzbGV2ZWwvRW50aXR5T2JqZWN0LzI3NTY2NTc]
opslevel_service.example: Modifications complete after 1s [id=Z2lkOi8vb3BzbGV2ZWwvU2VydmljZS8xMjU3NDA]
╷
│ Warning: opslevel client error
│ 
│   with opslevel_team.example,
│   on main.tf line 29, in resource "opslevel_team" "example":
│   29: resource "opslevel_team" "example" {
│ 
│ warning while reconciling team aliases: '[]'
│ OpsLevel API Errors:
│       - 'alias' slug is locked, it cannot be deleted
│ 
╵

Apply complete! Resources: 0 added, 3 changed, 0 destroyed.

terraform destroy