Skip to content
This repository has been archived by the owner on Sep 6, 2023. It is now read-only.

Commit

Permalink
Merge pull request #147 from rawkode/feat/project-api-keys
Browse files Browse the repository at this point in the history
feat: add project API key resource
  • Loading branch information
displague authored Jul 20, 2021
2 parents 9c257c8 + 85e85ea commit 2ede959
Show file tree
Hide file tree
Showing 7 changed files with 391 additions and 0 deletions.
37 changes: 37 additions & 0 deletions docs/resources/project_api_key.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
page_title: "Equinix Metal: Metal Project API Key"
subcategory: ""
description: |-
Create Equinix Metal Project API Keys
---
# metal_project_api_key

Use this resource to create Metal Project API Key resources in Equinix Metal. Project API keys can be used to create and read resources in a single project. Each API key contains a token which can be used for authentication in Equinix Metal HTTP API (in HTTP request header `X-Auth-Token`).


Read-only keys only allow to list and view existing resources, read-write keys can also be used to create resources.


## Example Usage

```hcl
# Create a new read-only API key in existing project
resource "metal_project_api_key" "test" {
project_id = local.existing_project_id
description = "Read-only key scoped to a projct"
read_only = true
}
```

## Argument Reference

* `project_id` - UUID of the project where the API key is scoped to
* `description` - Description string for the Project API Key resource
* `read-only` - Flag indicating whether the API key shoud be read-only

## Attributes Reference

* `token` - API token which can be used in Equinix Metal API clients
34 changes: 34 additions & 0 deletions docs/resources/user_api_key.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
page_title: "Equinix Metal: Metal User API Key"
subcategory: ""
description: |-
Create Equinix Metal User API Keys
---
# metal_user_api_key

Use this resource to create Metal User API Key resources in Equinix Metal. Each API key contains a token which can be used for authentication in Equinix Metal HTTP API (in HTTP request header `X-Auth-Token`).

Read-only keys only allow to list and view existing resources, read-write keys can also be used to create resources.

## Example Usage

```hcl
# Create a new read-only API key
resource "metal_user_api_key" "test" {
description = "Read-only user key"
read_only = true
}
```

## Argument Reference

* `description` - Description string for the User API Key resource
* `read-only` - Flag indicating whether the API key shoud be read-only

## Attributes Reference

* `user_id` - UUID of the owner of the API key
* `token` - API token which can be used in Equinix Metal API clients
2 changes: 2 additions & 0 deletions metal/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ func Provider() *schema.Provider {
},

ResourcesMap: map[string]*schema.Resource{
"metal_user_api_key": resourceMetalUserAPIKey(),
"metal_project_api_key": resourceMetalProjectAPIKey(),
"metal_connection": resourceMetalConnection(),
"metal_device": resourceMetalDevice(),
"metal_device_network_type": resourceMetalDeviceNetworkType(),
Expand Down
143 changes: 143 additions & 0 deletions metal/resource_metal_project_api_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package metal

import (
"log"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/packethost/packngo"
)

func schemaMetalAPIKey() map[string]*schema.Schema {
return map[string]*schema.Schema{
"read_only": {
Type: schema.TypeBool,
ForceNew: true,
Required: true,
Description: "Flag indicating whether the API key shoud be read-only",
},
"description": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Description string for the API key",
},
"token": {
Type: schema.TypeString,
Sensitive: true,
Computed: true,
Description: "API token for API clients",
},
}
}

func resourceMetalProjectAPIKey() *schema.Resource {
projectKeySchema := schemaMetalAPIKey()
projectKeySchema["project_id"] = &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "UUID of project which the new API key is scoped to",
}
return &schema.Resource{
Create: resourceMetalAPIKeyCreate,
Read: resourceMetalAPIKeyRead,
Delete: resourceMetalAPIKeyDelete,
Schema: projectKeySchema,
}
}

func resourceMetalAPIKeyCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*packngo.Client)

projectId := ""

projectIdRaw, projectIdOk := d.GetOk("project_id")
if projectIdOk {
projectId = projectIdRaw.(string)
}

createRequest := &packngo.APIKeyCreateRequest{
ProjectID: projectId,
ReadOnly: d.Get("read_only").(bool),
Description: d.Get("description").(string),
}

apiKey, _, err := client.APIKeys.Create(createRequest)
if err != nil {
return friendlyError(err)
}

d.SetId(apiKey.ID)

return resourceMetalAPIKeyRead(d, meta)
}

func projectIdFromResourceData(d *schema.ResourceData) string {
projectIdRaw, projectIdOk := d.GetOk("project_id")
if projectIdOk {
return projectIdRaw.(string)
}
return ""
}

func resourceMetalAPIKeyRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*packngo.Client)

projectId := projectIdFromResourceData(d)

var apiKey *packngo.APIKey
var err error

// if project has been set in the resource, look up project API key
// (this is the reason project API key can't be imported)
if projectId != "" {
apiKey, err = client.APIKeys.ProjectGet(projectId, d.Id(),
&packngo.GetOptions{Includes: []string{"project"}})
} else {
apiKey, err = client.APIKeys.UserGet(d.Id(),
&packngo.GetOptions{Includes: []string{"user"}})
}

if err != nil {
err = friendlyError(err)
// If the key is somehow already destroyed, mark as
// succesfully gone
if isNotFound(err) {
log.Printf("[WARN] Project APIKey (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}
return err
}

d.SetId(apiKey.ID)
attrMap := map[string]interface{}{
"description": apiKey.Description,
"read_only": apiKey.ReadOnly,
"token": apiKey.Token,
}

// this is kind of unnecessary as the project ID most likely already set,
// because project API key can't be imported. But let's refresh the
// project ID for future-proofing
if apiKey.Project != nil && apiKey.Project.ID != "" {
attrMap["project_id"] = apiKey.Project.ID
}
if apiKey.User != nil && apiKey.User.ID != "" {
attrMap["user_id"] = apiKey.User.ID
}

return setMap(d, attrMap)
}

func resourceMetalAPIKeyDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*packngo.Client)

resp, err := client.APIKeys.Delete(d.Id())
if ignoreResponseErrors(httpForbidden, httpNotFound)(resp, err) != nil {
return friendlyError(err)
}

d.SetId("")
return nil
}
57 changes: 57 additions & 0 deletions metal/resource_metal_project_api_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package metal

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/packethost/packngo"
)

func testAccMetalProjectAPIKeyDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*packngo.Client)
for _, rs := range s.RootModule().Resources {
if rs.Type != "metal_project_api_key" {
continue
}
if _, err := client.APIKeys.ProjectGet(rs.Primary.ID, rs.Primary.Attributes["project_id"], nil); err == nil {
return fmt.Errorf("ProjectAPI key still exists")
}
}
return nil
}

func testAccMetalProjectAPIKeyConfig_Basic() string {
return fmt.Sprintf(`
resource "metal_project" "test" {
name = "tfacc-project-key-test"
}
resource "metal_project_api_key" "test" {
project_id = metal_project.test.id
description = "tfacc-project-key"
read_only = true
}`)
}

func TestAccMetalProjectAPIKey_Basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccMetalProjectAPIKeyDestroy,
Steps: []resource.TestStep{
{
Config: testAccMetalProjectAPIKeyConfig_Basic(),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(
"metal_project_api_key.test", "token"),
resource.TestCheckResourceAttrPair(
"metal_project_api_key.test", "project_id",
"metal_project.test", "id"),
),
},
},
})
}
24 changes: 24 additions & 0 deletions metal/resource_metal_user_api_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package metal

import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceMetalUserAPIKey() *schema.Resource {
userKeySchema := schemaMetalAPIKey()
userKeySchema["user_id"] = &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: "UUID of user owning this key",
}
return &schema.Resource{
Create: resourceMetalAPIKeyCreate,
Read: resourceMetalAPIKeyRead,
Delete: resourceMetalAPIKeyDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: userKeySchema,
}
}
Loading

0 comments on commit 2ede959

Please sign in to comment.