Skip to content

Commit 2185cb6

Browse files
authored
Add docker_org_access_token resource for Docker Hub organization access tokens (#127)
* Add organization access token resource * Address review feedback for org access token resource * Fix OAT test paths and document repo path rules * Trim org access token resource surface
1 parent 9b3823c commit 2185cb6

7 files changed

Lines changed: 833 additions & 6 deletions

File tree

docs/resources/org_access_token.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "docker_org_access_token Resource - docker"
4+
subcategory: ""
5+
description: |-
6+
Manages organization access tokens.
7+
-> Note: This resource is only available when authenticated with a username and password as an owner of the org.
8+
Example Usage
9+
10+
resource "docker_org_access_token" "example" {
11+
org_name = "my-organization"
12+
label = "ci-token"
13+
description = "Token for CI pulls"
14+
resources = [
15+
{
16+
type = "TYPE_REPO"
17+
path = "my-organization/*"
18+
scopes = ["scope-image-pull"]
19+
}
20+
]
21+
expires_at = "2027-12-31T23:59:59Z"
22+
}
23+
24+
For TYPE_REPO resources, path must point to an existing repository or a supported glob such as my-organization/*.
25+
26+
Public-Only Repositories
27+
Use the special path */*/public to scope the token to public repositories only.
28+
29+
resource "docker_org_access_token" "public_pull" {
30+
org_name = "my-organization"
31+
label = "public-pull-token"
32+
33+
resources = [
34+
{
35+
type = "TYPE_REPO"
36+
path = "*/*/public"
37+
scopes = ["scope-image-pull"]
38+
}
39+
]
40+
}
41+
---
42+
43+
# docker_org_access_token (Resource)
44+
45+
Manages organization access tokens.
46+
47+
-> **Note**: This resource is only available when authenticated with a username and password as an owner of the org.
48+
49+
## Example Usage
50+
51+
```hcl
52+
resource "docker_org_access_token" "example" {
53+
org_name = "my-organization"
54+
label = "ci-token"
55+
description = "Token for CI pulls"
56+
resources = [
57+
{
58+
type = "TYPE_REPO"
59+
path = "my-organization/*"
60+
scopes = ["scope-image-pull"]
61+
}
62+
]
63+
expires_at = "2027-12-31T23:59:59Z"
64+
}
65+
```
66+
67+
For `TYPE_REPO` resources, `path` must point to an existing repository or a supported glob such as `my-organization/*`.
68+
69+
## Public-Only Repositories
70+
71+
Use the special path `*/*/public` to scope the token to public repositories only.
72+
73+
```hcl
74+
resource "docker_org_access_token" "public_pull" {
75+
org_name = "my-organization"
76+
label = "public-pull-token"
77+
78+
resources = [
79+
{
80+
type = "TYPE_REPO"
81+
path = "*/*/public"
82+
scopes = ["scope-image-pull"]
83+
}
84+
]
85+
}
86+
```
87+
88+
89+
90+
<!-- schema generated by tfplugindocs -->
91+
## Schema
92+
93+
### Required
94+
95+
- `label` (String) Label for the access token
96+
- `org_name` (String) The organization namespace
97+
- `resources` (Attributes List) Resources this token has access to (see [below for nested schema](#nestedatt--resources))
98+
99+
### Optional
100+
101+
- `description` (String) Description for the access token
102+
- `expires_at` (String) Expiration date for the token. Changing this value recreates the token.
103+
104+
### Read-Only
105+
106+
- `id` (String) The ID of the organization access token
107+
- `token` (String, Sensitive) The organization access token. This value is only returned during creation.
108+
109+
<a id="nestedatt--resources"></a>
110+
### Nested Schema for `resources`
111+
112+
Required:
113+
114+
- `path` (String) The path of the resource. For TYPE_REPO, this must point to an existing repository or a supported glob such as `my-organization/*`. Use `*/*/public` for public repositories only.
115+
- `scopes` (List of String) The scopes this token has access to
116+
- `type` (String) The type of resource
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
Copyright 2024 Docker Terraform Provider authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package hubclient
18+
19+
import (
20+
"bytes"
21+
"context"
22+
"encoding/json"
23+
"fmt"
24+
)
25+
26+
const (
27+
OrgAccessTokenTypeRepo = "TYPE_REPO"
28+
OrgAccessTokenTypeOrg = "TYPE_ORG"
29+
)
30+
31+
type OrgAccessToken struct {
32+
ID string `json:"id"`
33+
Label string `json:"label"`
34+
Description string `json:"description,omitempty"`
35+
CreatedBy string `json:"created_by"`
36+
IsActive bool `json:"is_active"`
37+
CreatedAt string `json:"created_at"`
38+
ExpiresAt string `json:"expires_at,omitempty"`
39+
LastUsedAt string `json:"last_used_at,omitempty"`
40+
Token string `json:"token,omitempty"`
41+
Resources []OrgAccessTokenResource `json:"resources,omitempty"`
42+
}
43+
44+
type OrgAccessTokenResource struct {
45+
Type string `json:"type"`
46+
Path string `json:"path"`
47+
Scopes []string `json:"scopes"`
48+
}
49+
50+
type OrgAccessTokenCreateParams struct {
51+
Label string `json:"label"`
52+
Description string `json:"description"`
53+
Resources []OrgAccessTokenResource `json:"resources"`
54+
ExpiresAt string `json:"expires_at,omitempty"`
55+
}
56+
57+
type OrgAccessTokenUpdateParams struct {
58+
Label string `json:"label"`
59+
Description string `json:"description"`
60+
Resources []OrgAccessTokenResource `json:"resources"`
61+
}
62+
63+
func (c *Client) CreateOrgAccessToken(ctx context.Context, orgName string, params OrgAccessTokenCreateParams) (OrgAccessToken, error) {
64+
if orgName == "" {
65+
return OrgAccessToken{}, fmt.Errorf("orgName is required")
66+
}
67+
68+
buf := bytes.NewBuffer(nil)
69+
if err := json.NewEncoder(buf).Encode(params); err != nil {
70+
return OrgAccessToken{}, err
71+
}
72+
73+
var accessToken OrgAccessToken
74+
err := c.sendRequest(ctx, "POST", fmt.Sprintf("/orgs/%s/access-tokens", orgName), buf.Bytes(), &accessToken)
75+
return accessToken, err
76+
}
77+
78+
func (c *Client) GetOrgAccessToken(ctx context.Context, orgName, accessTokenID string) (OrgAccessToken, error) {
79+
if orgName == "" {
80+
return OrgAccessToken{}, fmt.Errorf("orgName is required")
81+
}
82+
if accessTokenID == "" {
83+
return OrgAccessToken{}, fmt.Errorf("accessTokenID is required")
84+
}
85+
86+
var accessToken OrgAccessToken
87+
err := c.sendRequest(ctx, "GET", fmt.Sprintf("/orgs/%s/access-tokens/%s", orgName, accessTokenID), nil, &accessToken)
88+
return accessToken, err
89+
}
90+
91+
func (c *Client) UpdateOrgAccessToken(ctx context.Context, orgName, accessTokenID string, params OrgAccessTokenUpdateParams) (OrgAccessToken, error) {
92+
if orgName == "" {
93+
return OrgAccessToken{}, fmt.Errorf("orgName is required")
94+
}
95+
if accessTokenID == "" {
96+
return OrgAccessToken{}, fmt.Errorf("accessTokenID is required")
97+
}
98+
99+
buf := bytes.NewBuffer(nil)
100+
if err := json.NewEncoder(buf).Encode(params); err != nil {
101+
return OrgAccessToken{}, err
102+
}
103+
104+
var accessToken OrgAccessToken
105+
err := c.sendRequest(ctx, "PATCH", fmt.Sprintf("/orgs/%s/access-tokens/%s", orgName, accessTokenID), buf.Bytes(), &accessToken)
106+
return accessToken, err
107+
}
108+
109+
func (c *Client) DeleteOrgAccessToken(ctx context.Context, orgName, accessTokenID string) error {
110+
if orgName == "" {
111+
return fmt.Errorf("orgName is required")
112+
}
113+
if accessTokenID == "" {
114+
return fmt.Errorf("accessTokenID is required")
115+
}
116+
117+
return c.sendRequest(ctx, "DELETE", fmt.Sprintf("/orgs/%s/access-tokens/%s", orgName, accessTokenID), nil, nil)
118+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright 2024 Docker Terraform Provider authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package provider
18+
19+
import (
20+
"regexp"
21+
22+
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
23+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
24+
)
25+
26+
var accessTokenExpiresAtValidator validator.String = stringvalidator.RegexMatches(
27+
regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?Z$`),
28+
"must be in ISO 8601 format, e.g., 2021-10-28T18:30:19.520861Z",
29+
)

internal/provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ func getConfigfileKey(host string) string {
382382
func (p *DockerProvider) Resources(ctx context.Context) []func() resource.Resource {
383383
return []func() resource.Resource{
384384
NewAccessTokenResource,
385+
NewOrgAccessTokenResource,
385386
NewOrgSettingImageAccessManagementResource,
386387
NewOrgSettingRegistryAccessManagementResource,
387388
NewOrgTeamResource,

internal/provider/resource_access_token.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,8 @@ package provider
1919
import (
2020
"context"
2121
"fmt"
22-
"regexp"
2322

2423
"github.com/docker/terraform-provider-docker/internal/hubclient"
25-
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
2624
"github.com/hashicorp/terraform-plugin-framework/path"
2725
"github.com/hashicorp/terraform-plugin-framework/resource"
2826
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
@@ -123,10 +121,7 @@ resource "docker_access_token" "example" {
123121
MarkdownDescription: "Time the token expires. If not set, the token will not expire",
124122
Optional: true,
125123
Validators: []validator.String{
126-
stringvalidator.RegexMatches(
127-
regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?Z$`),
128-
"must be in ISO 8601 format, e.g., 2021-10-28T18:30:19.520861Z",
129-
),
124+
accessTokenExpiresAtValidator,
130125
},
131126
},
132127
},

0 commit comments

Comments
 (0)