Skip to content

Commit

Permalink
feat(role-jaas): in this pr we add support for jaas's roles in the te…
Browse files Browse the repository at this point in the history
…rraform provider
  • Loading branch information
SimoneDutto committed Dec 17, 2024
1 parent 46aadad commit 28502e5
Show file tree
Hide file tree
Showing 28 changed files with 1,070 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test_integration_jaas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
uses: canonical/jimm/.github/actions/test-server@v3
id: jaas
with:
jimm-version: v3.1.10
jimm-version: v3.1.13
juju-channel: 3/stable
ghcr-pat: ${{ secrets.GITHUB_TOKEN }}
- name: Setup microk8s for juju_kubernetes_cloud test
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
)

require (
github.com/canonical/jimm-go-sdk/v3 v3.0.5
github.com/canonical/jimm-go-sdk/v3 v3.0.6
github.com/dustin/go-humanize v1.0.1
github.com/hashicorp/terraform-json v0.22.1
github.com/hashicorp/terraform-plugin-framework v1.11.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZ
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
github.com/canonical/go-dqlite v1.21.0 h1:4gLDdV2GF+vg0yv9Ff+mfZZNQ1JGhnQ3GnS2GeZPHfA=
github.com/canonical/go-dqlite v1.21.0/go.mod h1:Uvy943N8R4CFUAs59A1NVaziWY9nJ686lScY7ywurfg=
github.com/canonical/jimm-go-sdk/v3 v3.0.5 h1:eQvn35wlmv+uNfyB7FHm+SkCigBu0x2VS1FlsaNor4Q=
github.com/canonical/jimm-go-sdk/v3 v3.0.5/go.mod h1:xcJrWTpLHSw3Z16/1Zcvh31awlwIzjXdrYUYCVZhc5s=
github.com/canonical/jimm-go-sdk/v3 v3.0.6 h1:ovQAEb5R5sSl7Edn27QTi/IyCX93xd87jE9ygj14mG0=
github.com/canonical/jimm-go-sdk/v3 v3.0.6/go.mod h1:xcJrWTpLHSw3Z16/1Zcvh31awlwIzjXdrYUYCVZhc5s=
github.com/canonical/lxd v0.0.0-20231214113525-e676fc63c50a h1:Tfo/MzXK5GeG7gzSHqxGeY/669Mhh5ea43dn1mRDnk8=
github.com/canonical/lxd v0.0.0-20231214113525-e676fc63c50a/go.mod h1:UxfHGKFoRjgu1NUA9EFiR++dKvyAiT0h9HT0ffMlzjc=
github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c=
Expand Down
4 changes: 4 additions & 0 deletions internal/juju/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ type JaasAPIClient interface {
GetGroup(req *jaasparams.GetGroupRequest) (jaasparams.GetGroupResponse, error)
RenameGroup(req *jaasparams.RenameGroupRequest) error
RemoveGroup(req *jaasparams.RemoveGroupRequest) error
AddRole(req *jaasparams.AddRoleRequest) (jaasparams.AddRoleResponse, error)
GetRole(req *jaasparams.GetRoleRequest) (jaasparams.GetRoleResponse, error)
RenameRole(req *jaasparams.RenameRoleRequest) error
RemoveRole(req *jaasparams.RemoveRoleRequest) error
}

// KubernetesCloudAPIClient defines the set of methods that the Kubernetes cloud API provides.
Expand Down
74 changes: 74 additions & 0 deletions internal/juju/jaas.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,77 @@ func (jc *jaasClient) RemoveGroup(name string) error {
req := params.RemoveGroupRequest{Name: name}
return client.RemoveGroup(&req)
}

type JaasRole struct {
Name string
UUID string
}

// AddRole attempts to create a new role with the provided name.
func (jc *jaasClient) AddRole(name string) (string, error) {
conn, err := jc.GetConnection(nil)
if err != nil {
return "", err
}
defer func() { _ = conn.Close() }()

client := jc.getJaasApiClient(conn)
req := params.AddRoleRequest{Name: name}

resp, err := client.AddRole(&req)
if err != nil {
return "", err
}
return resp.UUID, nil
}

// ReadRoleByUUID attempts to read a role that matches the provided UUID.
func (jc *jaasClient) ReadRoleByUUID(uuid string) (*JaasRole, error) {
return jc.readRole(&params.GetRoleRequest{UUID: uuid})
}

// ReadRoleByName attempts to read a role that matches the provided name.
func (jc *jaasClient) ReadRoleByName(name string) (*JaasRole, error) {
return jc.readRole(&params.GetRoleRequest{Name: name})
}

func (jc *jaasClient) readRole(req *params.GetRoleRequest) (*JaasRole, error) {
conn, err := jc.GetConnection(nil)
if err != nil {
return nil, err
}
defer func() { _ = conn.Close() }()

client := jc.getJaasApiClient(conn)
resp, err := client.GetRole(req)
if err != nil {
return nil, err
}
return &JaasRole{Name: resp.Name, UUID: resp.UUID}, nil
}

// RenameRole attempts to rename a role that matches the provided name.
func (jc *jaasClient) RenameRole(name, newName string) error {
conn, err := jc.GetConnection(nil)
if err != nil {
return err
}
defer func() { _ = conn.Close() }()

client := jc.getJaasApiClient(conn)
req := params.RenameRoleRequest{Name: name, NewName: newName}
return client.RenameRole(&req)
}

// RemoveRole attempts to remove a role that matches the provided name.
func (jc *jaasClient) RemoveRole(name string) error {
conn, err := jc.GetConnection(nil)
if err != nil {
return err
}
defer func() { _ = conn.Close() }()

client := jc.getJaasApiClient(conn)
req := params.RemoveRoleRequest{Name: name}
return client.RemoveRole(&req)
}
70 changes: 70 additions & 0 deletions internal/juju/jaas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,76 @@ func (s *JaasSuite) TestRemoveGroup() {
s.Require().NoError(err)
}

func (s *JaasSuite) TestAddRole() {
defer s.setupMocks(s.T()).Finish()

name := "role"
req := &params.AddRoleRequest{Name: name}
resp := params.AddRoleResponse{Role: params.Role{UUID: "uuid", Name: name}}

s.mockJaasClient.EXPECT().AddRole(req).Return(resp, nil)

client := s.getJaasClient()
uuid, err := client.AddRole(name)
s.Require().NoError(err)
s.Require().Equal(resp.UUID, uuid)
}

func (s *JaasSuite) TestGetRole() {
defer s.setupMocks(s.T()).Finish()

uuid := "uuid"
name := "role"

req := &params.GetRoleRequest{UUID: uuid}
resp := params.GetRoleResponse{Role: params.Role{UUID: uuid, Name: name}}
s.mockJaasClient.EXPECT().GetRole(req).Return(resp, nil)

client := s.getJaasClient()
gotRole, err := client.ReadRoleByUUID(uuid)
s.Require().NoError(err)
s.Require().Equal(*gotRole, JaasRole{UUID: uuid, Name: name})
}

func (s *JaasSuite) TestGetRoleNotFound() {
defer s.setupMocks(s.T()).Finish()

uuid := "uuid"

req := &params.GetRoleRequest{UUID: uuid}
s.mockJaasClient.EXPECT().GetRole(req).Return(params.GetRoleResponse{}, errors.New("role not found"))

client := s.getJaasClient()
gotRole, err := client.ReadRoleByUUID(uuid)
s.Require().Error(err)
s.Require().Nil(gotRole)
}

func (s *JaasSuite) TestRenameRole() {
defer s.setupMocks(s.T()).Finish()

name := "name"
newName := "new-name"
req := &params.RenameRoleRequest{Name: name, NewName: newName}
s.mockJaasClient.EXPECT().RenameRole(req).Return(nil)

client := s.getJaasClient()
err := client.RenameRole(name, newName)
s.Require().NoError(err)
}

func (s *JaasSuite) TestRemoveRole() {
defer s.setupMocks(s.T()).Finish()

name := "role"
req := &params.RemoveRoleRequest{Name: name}
s.mockJaasClient.EXPECT().RemoveRole(req).Return(nil)

client := s.getJaasClient()
err := client.RemoveRole(name)
s.Require().NoError(err)
}

// In order for 'go test' to run this suite, we need to create
// a normal test function and pass our suite to suite.Run
func TestJaasSuite(t *testing.T) {
Expand Down
58 changes: 58 additions & 0 deletions internal/juju/mock_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

115 changes: 115 additions & 0 deletions internal/provider/data_source_jaas_role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2024 Canonical Ltd.
// Licensed under the Apache License, Version 2.0, see LICENCE file for details.

package provider

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"

"github.com/juju/terraform-provider-juju/internal/juju"
)

type jaasRoleDataSource struct {
client *juju.Client

// subCtx is the context created with the new tflog subsystem for applications.
subCtx context.Context
}

// NewJAASRoleDataSource returns a new JAAS role data source instance.
func NewJAASRoleDataSource() datasource.DataSource {
return &jaasRoleDataSource{}
}

type jaasRoleDataSourceModel struct {
Name types.String `tfsdk:"name"`
UUID types.String `tfsdk:"uuid"`
}

// Metadata returns the metadata for the JAAS role data source.
func (d *jaasRoleDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_jaas_role"
}

// Schema defines the schema for JAAS roles.
func (d *jaasRoleDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "A data source representing a Juju JAAS Role.",
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Description: "The name of the role.",
Required: true,
},
"uuid": schema.StringAttribute{
Description: "The UUID of the role.",
Computed: true,
},
},
}
}

// Configure sets up the JAAS role data source with the provider data.
func (d *jaasRoleDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(*juju.Client)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)
return
}

d.client = client
d.subCtx = tflog.NewSubsystem(ctx, LogDataSourceJAASRole)
}

// Read updates the role data source with the latest data from JAAS.
func (d *jaasRoleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
// Prevent panic if the provider has not been configured.
if d.client == nil {
addDSClientNotConfiguredError(&resp.Diagnostics, "jaas-role")
return
}

var data jaasRoleDataSourceModel

// Read Terraform configuration state into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

// Update the role with the latest data from JAAS
role, err := d.client.Jaas.ReadRoleByName(data.Name.String())
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read role, got error: %v", err))
return
}
data.UUID = types.StringValue(role.UUID)
d.trace(fmt.Sprintf("read role %q data source", data.Name))

// Save the updated role back to the state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (d *jaasRoleDataSource) trace(msg string, additionalFields ...map[string]interface{}) {
if d.subCtx == nil {
return
}

//SubsystemTrace(subCtx, "datasource-jaas-role", "hello, world", map[string]interface{}{"foo": 123})
// Output:
// {"@level":"trace","@message":"hello, world","@module":"juju.datasource-jaas-role","foo":123}
tflog.SubsystemTrace(d.subCtx, LogDataSourceJAASRole, msg, additionalFields...)
}
Loading

0 comments on commit 28502e5

Please sign in to comment.