Skip to content

Commit 5af008f

Browse files
authored
Add kubernetes_token_request resource (#2024)
* initial schema * add structures with flatteners and headers * Implement Create and Delete * add serviceAccount attribute * final updated schema * set uid attribute and expiration default value * set empty boundObjectRef to nil * add changelog-entry * add docs * update docs and tests * format fix * add copyright headers * fix tf config format * push requested changes * fix small bugs in test * Refactor changes * add spec flatten and switch spec to optional * update name in tfconfig tokenrequest docs * make attributes in tokenspecfields computed * make spec computed * add forcedNew to spec attributes
1 parent f0d0f53 commit 5af008f

File tree

7 files changed

+421
-0
lines changed

7 files changed

+421
-0
lines changed

.changelog/2024.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-resource
2+
`kubernetes/resource_kubernetes_tokenrequest.go`: Add `kubernetes_token_request_v1` resource
3+
```

kubernetes/provider.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,9 @@ func Provider() *schema.Provider {
342342
// provider helper resources
343343
"kubernetes_labels": resourceKubernetesLabels(),
344344
"kubernetes_annotations": resourceKubernetesAnnotations(),
345+
346+
// authentication
347+
"kubernetes_token_request_v1": resourceKubernetesTokenRequestV1(),
345348
},
346349
}
347350

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package kubernetes
5+
6+
import (
7+
"context"
8+
"log"
9+
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
12+
apiv1 "k8s.io/api/authentication/v1"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
)
15+
16+
func resourceKubernetesTokenRequestV1() *schema.Resource {
17+
return &schema.Resource{
18+
CreateContext: resourceKubernetesTokenRequestCreateV1,
19+
ReadContext: resourceKubernetesTokenRequestReadV1,
20+
UpdateContext: resourceKubernetesTokenRequestUpdateV1,
21+
DeleteContext: resourceKubernetesTokenDeleteV1,
22+
23+
Schema: map[string]*schema.Schema{
24+
"metadata": namespacedMetadataSchema("token request", true),
25+
"spec": {
26+
Type: schema.TypeList,
27+
Description: apiv1.TokenRequest{}.Spec.SwaggerDoc()["spec"],
28+
Optional: true,
29+
Computed: true,
30+
ForceNew: true,
31+
MaxItems: 1,
32+
Elem: &schema.Resource{
33+
Schema: tokenRequestSpecFields(),
34+
},
35+
},
36+
"token": {
37+
Type: schema.TypeString,
38+
Description: "Token is the opaque bearer token.",
39+
Computed: true,
40+
Sensitive: true,
41+
},
42+
},
43+
}
44+
}
45+
46+
func resourceKubernetesTokenRequestCreateV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
47+
conn, err := meta.(KubeClientsets).MainClientset()
48+
if err != nil {
49+
return diag.FromErr(err)
50+
}
51+
52+
metadata := expandMetadata(d.Get("metadata").([]interface{}))
53+
spec := expandTokenRequestSpec(d.Get("spec").([]interface{}))
54+
saName := d.Get("metadata.0.name").(string)
55+
56+
request := apiv1.TokenRequest{
57+
ObjectMeta: metadata,
58+
Spec: *spec,
59+
}
60+
61+
log.Printf("[INFO] Creating new TokenRequest: %#v", request)
62+
out, err := conn.CoreV1().ServiceAccounts(metadata.Namespace).CreateToken(ctx, saName, &request, metav1.CreateOptions{})
63+
if err != nil {
64+
return diag.FromErr(err)
65+
}
66+
d.Set("token", out.Status.Token)
67+
s, err := flattenTokenRequestSpec(out.Spec, d, meta)
68+
if err != nil {
69+
return diag.FromErr(err)
70+
}
71+
d.Set("spec", s)
72+
73+
log.Printf("[INFO] Submitted new TokenRequest: %#v", out)
74+
d.SetId(buildId(out.ObjectMeta))
75+
76+
return resourceKubernetesTokenRequestReadV1(ctx, d, meta)
77+
}
78+
79+
func resourceKubernetesTokenRequestReadV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
80+
81+
return nil
82+
}
83+
84+
func resourceKubernetesTokenRequestUpdateV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
85+
86+
return resourceKubernetesRoleRead(ctx, d, meta)
87+
}
88+
89+
func resourceKubernetesTokenDeleteV1(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
90+
91+
return nil
92+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package kubernetes
5+
6+
import (
7+
"fmt"
8+
"testing"
9+
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
11+
api "k8s.io/api/core/v1"
12+
)
13+
14+
func TestAccKubernetesTokenRequest_basic(t *testing.T) {
15+
var conf api.ServiceAccount
16+
resourceName := "kubernetes_service_account_v1.tokentest"
17+
tokenName := "kubernetes_token_request_v1.test"
18+
resource.Test(t, resource.TestCase{
19+
PreCheck: func() { testAccPreCheck(t) },
20+
IDRefreshName: resourceName,
21+
IDRefreshIgnore: []string{"metadata.0.resource_version"},
22+
ProviderFactories: testAccProviderFactories,
23+
CheckDestroy: testAccCheckKubernetesServiceAccountDestroy,
24+
Steps: []resource.TestStep{
25+
{
26+
Config: testAccKubernetesTokenRequestConfig_basic(),
27+
Check: resource.ComposeAggregateTestCheckFunc(
28+
testAccCheckKubernetesServiceAccountExists(resourceName, &conf),
29+
resource.TestCheckResourceAttr(resourceName, "metadata.0.name", "tokentest"),
30+
resource.TestCheckResourceAttr(tokenName, "metadata.0.name", "tokentest"),
31+
resource.TestCheckResourceAttr(tokenName, "spec.0.audiences.0", "api"),
32+
resource.TestCheckResourceAttr(tokenName, "spec.0.audiences.1", "vault"),
33+
resource.TestCheckResourceAttr(tokenName, "spec.0.audiences.2", "factors"),
34+
resource.TestCheckResourceAttrSet(tokenName, "token"),
35+
),
36+
},
37+
},
38+
})
39+
}
40+
41+
func testAccKubernetesTokenRequestConfig_basic() string {
42+
return fmt.Sprintf(`resource "kubernetes_service_account_v1" "tokentest" {
43+
metadata {
44+
name = "tokentest"
45+
}
46+
}
47+
48+
resource "kubernetes_token_request_v1" "test" {
49+
metadata {
50+
name = kubernetes_service_account_v1.tokentest.metadata.0.name
51+
}
52+
spec {
53+
audiences = [
54+
"api",
55+
"vault",
56+
"factors"
57+
]
58+
}
59+
}
60+
61+
62+
`)
63+
}

kubernetes/schema_token_request.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package kubernetes
5+
6+
import (
7+
"errors"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
apiv1 "k8s.io/api/authentication/v1"
11+
)
12+
13+
func tokenRequestSpecFields() map[string]*schema.Schema {
14+
s := map[string]*schema.Schema{
15+
"audiences": {
16+
Type: schema.TypeList,
17+
Computed: true,
18+
Optional: true,
19+
ForceNew: true,
20+
Description: "Audiences are the intendend audiences of the token. A recipient of a token must identify themself with an identifier in the list of audiences of the token, and otherwise should reject the token. A token issued for multiple audiences may be used to authenticate against any of the audiences listed but implies a high degree of trust between the target audiences.",
21+
Elem: &schema.Schema{
22+
Type: schema.TypeString,
23+
},
24+
},
25+
"bound_object_ref": {
26+
Type: schema.TypeList,
27+
Optional: true,
28+
ForceNew: true,
29+
Computed: true,
30+
MaxItems: 1,
31+
Description: apiv1.TokenRequest{}.Spec.SwaggerDoc()["boundObjectRef"],
32+
Elem: &schema.Resource{
33+
Schema: map[string]*schema.Schema{
34+
"api_version": {
35+
Type: schema.TypeString,
36+
Optional: true,
37+
ForceNew: true,
38+
Description: "API version of the referent.",
39+
},
40+
"kind": {
41+
Type: schema.TypeString,
42+
Optional: true,
43+
ForceNew: true,
44+
Description: "Kind of the referent. Valid kinds are 'Pod' and 'Secret'.",
45+
},
46+
"name": {
47+
Type: schema.TypeString,
48+
Optional: true,
49+
ForceNew: true,
50+
Description: "Name of the referent.",
51+
},
52+
"uid": {
53+
Type: schema.TypeString,
54+
Optional: true,
55+
ForceNew: true,
56+
Description: "UID of the referent.",
57+
},
58+
},
59+
},
60+
},
61+
"expiration_seconds": {
62+
Type: schema.TypeInt,
63+
Computed: true,
64+
ForceNew: true,
65+
Optional: true,
66+
Description: "expiration_seconds is the requested duration of validity of the request. The token issuer may return a token with a different validity duration so a client needs to check the 'expiration' field in a response. The expiration can't be less than 10 minutes.",
67+
ValidateFunc: func(value interface{}, key string) ([]string, []error) {
68+
v := value.(int)
69+
if v < 600 || v > 4294967296 {
70+
return nil, []error{errors.New("must be between 600 and 4294967296 ")}
71+
}
72+
return nil, nil
73+
},
74+
},
75+
}
76+
return s
77+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package kubernetes
5+
6+
import (
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8+
v1 "k8s.io/api/authentication/v1"
9+
"k8s.io/apimachinery/pkg/types"
10+
)
11+
12+
// Flatteners
13+
14+
func flattenTokenRequestSpec(in v1.TokenRequestSpec, d *schema.ResourceData, meta interface{}) ([]interface{}, error) {
15+
att := make(map[string]interface{})
16+
17+
att["audiences"] = in.Audiences
18+
19+
if in.BoundObjectRef != nil {
20+
bndObjRef, err := flattenBoundObjectReference(*in.BoundObjectRef, d, meta)
21+
if err != nil {
22+
return nil, err
23+
}
24+
att["bound_object_ref"] = bndObjRef
25+
}
26+
27+
if in.ExpirationSeconds != nil {
28+
att["expiration_seconds"] = int(*in.ExpirationSeconds)
29+
}
30+
31+
return []interface{}{att}, nil
32+
}
33+
34+
func flattenBoundObjectReference(in v1.BoundObjectReference, d *schema.ResourceData, meta interface{}) ([]interface{}, error) {
35+
att := make(map[string]interface{})
36+
37+
att["api_version"] = in.APIVersion
38+
39+
att["kind"] = in.Kind
40+
41+
att["name"] = in.Name
42+
43+
att["uid"] = in.UID
44+
45+
return []interface{}{att}, nil
46+
}
47+
48+
// Expanders
49+
50+
func expandTokenRequestSpec(p []interface{}) *v1.TokenRequestSpec {
51+
obj := &v1.TokenRequestSpec{}
52+
if len(p) == 0 || p[0] == nil {
53+
return obj
54+
}
55+
in := p[0].(map[string]interface{})
56+
57+
if v, ok := in["audiences"].([]interface{}); ok && len(v) > 0 {
58+
obj.Audiences = expandStringSlice(v)
59+
}
60+
61+
bdObjRef, err := expandBoundObjectReference(in["bound_object_ref"].([]interface{}))
62+
if err != nil {
63+
return obj
64+
}
65+
obj.BoundObjectRef = bdObjRef
66+
67+
if v, ok := in["expiration_seconds"].(int); v != 0 && ok {
68+
obj.ExpirationSeconds = ptrToInt64(int64(v))
69+
}
70+
71+
return obj
72+
}
73+
74+
func expandBoundObjectReference(p []interface{}) (*v1.BoundObjectReference, error) {
75+
obj := &v1.BoundObjectReference{}
76+
if len(p) == 0 || p[0] == nil {
77+
return nil, nil
78+
}
79+
in := p[0].(map[string]interface{})
80+
81+
if v, ok := in["api_version"]; ok {
82+
obj.APIVersion = v.(string)
83+
}
84+
85+
if v, ok := in["kind"]; ok {
86+
obj.Kind = v.(string)
87+
}
88+
89+
if v, ok := in["name"]; ok {
90+
obj.Name = v.(string)
91+
}
92+
93+
if v, ok := in["uid"]; ok {
94+
obj.UID = types.UID(v.(string))
95+
}
96+
97+
return obj, nil
98+
}

0 commit comments

Comments
 (0)