Skip to content

Commit

Permalink
Merge pull request #228 from tosuke/framework-role-metadata
Browse files Browse the repository at this point in the history
Reimplement role metadata with tf-framework
  • Loading branch information
azukiazusa1 authored Aug 9, 2024
2 parents 9a863e6 + 576d3ad commit e56828c
Show file tree
Hide file tree
Showing 9 changed files with 638 additions and 1 deletion.
142 changes: 142 additions & 0 deletions internal/mackerel/role_metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package mackerel

import (
"context"
"encoding/json"
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/mackerelio/mackerel-client-go"
)

type RoleMetadataModel struct {
ID types.String `tfsdk:"id"`
ServiceName types.String `tfsdk:"service"`
RoleName types.String `tfsdk:"role"`
Namespace types.String `tfsdk:"namespace"`
MetadataJSON jsontypes.Normalized `tfsdk:"metadata_json"`
}

func roleMetadataID(serviceName, roleName, namespace string) string {
return fmt.Sprintf("%s:%s/%s", serviceName, roleName, namespace)
}

func parseRoleMetadataID(id string) (serviceName, roleName, namespace string, err error) {
sn, rest, foundColon := strings.Cut(id, ":")
rn, ns, foundSlash := strings.Cut(rest, "/")
if !foundColon || !foundSlash {
return "", "", "", fmt.Errorf("The ID is expected to have `<service>:<role>/<namespace>` format, but got: '%s'.", id)
}
return sn, rn, ns, nil
}

func ReadRoleMetadata(ctx context.Context, client *Client, serviceName, roleName, namespace string) (RoleMetadataModel, error) {
return readRoleMetadata(client, serviceName, roleName, namespace)
}

type roleMetadataReader interface {
GetRoleMetaData(serviceName, roleName, namespace string) (*mackerel.RoleMetaDataResp, error)
}

func readRoleMetadata(client roleMetadataReader, serviceName, roleName, namespace string) (RoleMetadataModel, error) {
metadataResp, err := client.GetRoleMetaData(serviceName, roleName, namespace)
if err != nil {
return RoleMetadataModel{}, err
}

metadataJSON, err := json.Marshal(metadataResp.RoleMetaData)
if err != nil {
return RoleMetadataModel{}, fmt.Errorf("failed to marshal result: %w", err)
}

id := roleMetadataID(serviceName, roleName, namespace)
return RoleMetadataModel{
ID: types.StringValue(id),
ServiceName: types.StringValue(serviceName),
RoleName: types.StringValue(roleName),
Namespace: types.StringValue(namespace),
MetadataJSON: jsontypes.NewNormalizedValue(string(metadataJSON)),
}, nil
}

func ImportRoleMetadata(id string) (RoleMetadataModel, error) {
serviceName, roleName, namespace, err := parseRoleMetadataID(id)
if err != nil {
return RoleMetadataModel{}, err
}
return RoleMetadataModel{
ID: types.StringValue(id),
ServiceName: types.StringValue(serviceName),
RoleName: types.StringValue(roleName),
Namespace: types.StringValue(namespace),
}, nil
}

func (m *RoleMetadataModel) Create(ctx context.Context, client *Client) error {
return m.create(client)
}

func (m *RoleMetadataModel) create(client roleMetadataUpdator) error {
if err := m.update(client); err != nil {
return err
}

m.ID = types.StringValue(
roleMetadataID(m.ServiceName.ValueString(), m.RoleName.ValueString(), m.Namespace.ValueString()),
)

return nil
}

func (m *RoleMetadataModel) Read(ctx context.Context, client *Client) error {
data, err := readRoleMetadata(
client,
m.ServiceName.ValueString(),
m.RoleName.ValueString(),
m.Namespace.ValueString(),
)
if err != nil {
return err
}

m.ID = data.ID // computed
m.MetadataJSON = data.MetadataJSON
return nil
}

func (m RoleMetadataModel) Update(ctx context.Context, client *Client) error {
return m.update(client)
}

type roleMetadataUpdator interface {
PutRoleMetaData(serviceName, roleName, namespace string, metadata mackerel.RoleMetaData) error
}

func (m *RoleMetadataModel) update(client roleMetadataUpdator) error {
var metadata mackerel.RoleMetaData
if err := json.Unmarshal([]byte(m.MetadataJSON.ValueString()), &metadata); err != nil {
return fmt.Errorf("failed to unmarshal metadata: %w", err)
}
if err := client.PutRoleMetaData(
m.ServiceName.ValueString(),
m.RoleName.ValueString(),
m.Namespace.ValueString(),
metadata,
); err != nil {
return err
}
return nil
}

func (m RoleMetadataModel) Delete(_ context.Context, client *Client) error {
if err := client.DeleteRoleMetaData(
m.ServiceName.ValueString(),
m.RoleName.ValueString(),
m.Namespace.ValueString(),
); err != nil {
return err
}
return nil
}
181 changes: 181 additions & 0 deletions internal/mackerel/role_metadata_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package mackerel

import (
"fmt"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/mackerelio/mackerel-client-go"
)

func Test_ReadRoleMetadata(t *testing.T) {
t.Parallel()

defaultClient := func(serviceName, roleName, namespace string) (*mackerel.RoleMetaDataResp, error) {
if serviceName != "service" || roleName != "role" || namespace != "namespace" {
return nil, fmt.Errorf("no metadata found")
}
return &mackerel.RoleMetaDataResp{
RoleMetaData: map[string]any{"v": 1},
}, nil
}

cases := map[string]struct {
inClient roleMetadataReaderFunc
inServiceName string
inRoleName string
inNamespace string

wants RoleMetadataModel
}{
"basic": {
inClient: defaultClient,
inServiceName: "service",
inRoleName: "role",
inNamespace: "namespace",

wants: RoleMetadataModel{
ID: types.StringValue("service:role/namespace"),
ServiceName: types.StringValue("service"),
RoleName: types.StringValue("role"),
Namespace: types.StringValue("namespace"),
MetadataJSON: jsontypes.NewNormalizedValue(`{"v":1}`),
},
},
}

for name, tt := range cases {
t.Run(name, func(t *testing.T) {
t.Parallel()

data, err := readRoleMetadata(tt.inClient, tt.inServiceName, tt.inRoleName, tt.inNamespace)
if err != nil {
t.Errorf("unexpected error: %+v", err)
return
}

if diff := cmp.Diff(data, tt.wants); diff != "" {
t.Error(diff)
}
})
}
}

func Test_ImportRoleMetadata(t *testing.T) {
t.Parallel()

cases := map[string]struct {
inID string

wants RoleMetadataModel
wantErr bool
}{
"valid": {
inID: "service:role/namespace",

wants: RoleMetadataModel{
ID: types.StringValue("service:role/namespace"),
ServiceName: types.StringValue("service"),
RoleName: types.StringValue("role"),
Namespace: types.StringValue("namespace"),
},
},
"invalid": {
inID: "invalidid",

wantErr: true,
},
}

for name, tt := range cases {
t.Run(name, func(t *testing.T) {
t.Parallel()

data, err := ImportRoleMetadata(tt.inID)
if (err != nil) != tt.wantErr {
t.Errorf("unexpected error: %+v", err)
}
if err != nil {
return
}

if diff := cmp.Diff(data, tt.wants); diff != "" {
t.Error(diff)
}
})

}
}

func Test_RoleMetadata_Create(t *testing.T) {
t.Parallel()

cases := map[string]struct {
in RoleMetadataModel
inClient roleMetadataUpdatorFunc

wants RoleMetadataModel
}{
"basic": {
in: RoleMetadataModel{
ServiceName: types.StringValue("service"),
RoleName: types.StringValue("role"),
Namespace: types.StringValue("namespace"),
MetadataJSON: jsontypes.NewNormalizedValue(`{"v":1}`),
},
inClient: func(serviceName, roleName, namespace string, metadata mackerel.RoleMetaData) error {
if serviceName != "service" {
return fmt.Errorf("unexpected service name: %s", serviceName)
}
if roleName != "role" {
return fmt.Errorf("unexpected role name: %s", roleName)
}
if namespace != "namespace" {
return fmt.Errorf("unexpected namespace: %s", namespace)
}
if diff := cmp.Diff(metadata, map[string]any{"v": 1.}); diff != "" {
return fmt.Errorf("unexpected metadata: %s", diff)
}
return nil
},

wants: RoleMetadataModel{
ID: types.StringValue("service:role/namespace"),
ServiceName: types.StringValue("service"),
RoleName: types.StringValue("role"),
Namespace: types.StringValue("namespace"),
MetadataJSON: jsontypes.NewNormalizedValue(`{"v":1}`),
},
},
}

for name, tt := range cases {
t.Run(name, func(t *testing.T) {
t.Parallel()

data := tt.in
if err := data.create(tt.inClient); err != nil {
t.Errorf("unexpected error: %+v", err)
return
}

if diff := cmp.Diff(data, tt.wants); diff != "" {
t.Error(diff)
}
})
}
}

type roleMetadataReaderFunc func(serviceName, roleName, namespace string) (*mackerel.RoleMetaDataResp, error)

func (f roleMetadataReaderFunc) GetRoleMetaData(serviceName, roleName, namespace string) (*mackerel.RoleMetaDataResp, error) {
return f(serviceName, roleName, namespace)
}

type roleMetadataUpdatorFunc func(serviceName, roleName, namespace string, metadata mackerel.RoleMetaData) error

func (f roleMetadataUpdatorFunc) PutRoleMetaData(serviceName, roleName, namespace string, metadata mackerel.RoleMetaData) error {
return f(serviceName, roleName, namespace, metadata)
}
Loading

0 comments on commit e56828c

Please sign in to comment.