diff --git a/docs/data-sources/jaas_group.md b/docs/data-sources/jaas_group.md new file mode 100644 index 00000000..15e7a77d --- /dev/null +++ b/docs/data-sources/jaas_group.md @@ -0,0 +1,40 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "juju_jaas_group Data Source - terraform-provider-juju" +subcategory: "" +description: |- + A data source representing a Juju JAAS Group. +--- + +# juju_jaas_group (Data Source) + +A data source representing a Juju JAAS Group. + +## Example Usage + +```terraform +resource "juju_jaas_group" "test" { + name = "group-0" +} + +data "juju_jaas_group" "test" { + name = juju_jaas_group.test.name + // from a separate plan use a string literal + // name = "group-0" +} + +output "group_uuid" { + value = data.juju_jaas_group.test.uuid +} +``` + + +## Schema + +### Required + +- `name` (String) The name of the group. + +### Read-Only + +- `uuid` (String) The UUID of the group. diff --git a/examples/data-sources/juju_jaas_group/data-source.tf b/examples/data-sources/juju_jaas_group/data-source.tf new file mode 100644 index 00000000..9904c9b2 --- /dev/null +++ b/examples/data-sources/juju_jaas_group/data-source.tf @@ -0,0 +1,13 @@ +resource "juju_jaas_group" "test" { + name = "group-0" +} + +data "juju_jaas_group" "test" { + name = juju_jaas_group.test.name + // from a separate plan use a string literal + // name = "group-0" +} + +output "group_uuid" { + value = data.juju_jaas_group.test.uuid +} diff --git a/go.mod b/go.mod index 3b42818b..d88cdd4e 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( ) require ( - github.com/canonical/jimm-go-sdk/v3 v3.0.4 + github.com/canonical/jimm-go-sdk/v3 v3.0.5 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 diff --git a/go.sum b/go.sum index d4285593..90ae0439 100644 --- a/go.sum +++ b/go.sum @@ -97,8 +97,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.4 h1:cWNL6GIFbwB2W//PaOTjoVuRVloQ8P+3YIm26KiBs10= -github.com/canonical/jimm-go-sdk/v3 v3.0.4/go.mod h1:xcJrWTpLHSw3Z16/1Zcvh31awlwIzjXdrYUYCVZhc5s= +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/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= diff --git a/internal/juju/jaas.go b/internal/juju/jaas.go index d4deec71..7784ae90 100644 --- a/internal/juju/jaas.go +++ b/internal/juju/jaas.go @@ -150,7 +150,7 @@ func (jc *jaasClient) ReadRelations(ctx context.Context, tuple *JaasTuple) ([]Ja } // AddGroup attempts to create a new group with the provided name. -func (jc *jaasClient) AddGroup(ctx context.Context, name string) (string, error) { +func (jc *jaasClient) AddGroup(name string) (string, error) { conn, err := jc.GetConnection(nil) if err != nil { return "", err @@ -167,8 +167,17 @@ func (jc *jaasClient) AddGroup(ctx context.Context, name string) (string, error) return resp.UUID, nil } -// ReadGroup attempts to read a group that matches the provided UUID. -func (jc *jaasClient) ReadGroup(ctx context.Context, uuid string) (*JaasGroup, error) { +// ReadGroupByUUID attempts to read a group that matches the provided UUID. +func (jc *jaasClient) ReadGroupByUUID(uuid string) (*JaasGroup, error) { + return jc.readGroup(¶ms.GetGroupRequest{UUID: uuid}) +} + +// ReadGroupByName attempts to read a group that matches the provided name. +func (jc *jaasClient) ReadGroupByName(name string) (*JaasGroup, error) { + return jc.readGroup(¶ms.GetGroupRequest{Name: name}) +} + +func (jc *jaasClient) readGroup(req *params.GetGroupRequest) (*JaasGroup, error) { conn, err := jc.GetConnection(nil) if err != nil { return nil, err @@ -176,8 +185,7 @@ func (jc *jaasClient) ReadGroup(ctx context.Context, uuid string) (*JaasGroup, e defer func() { _ = conn.Close() }() client := jc.getJaasApiClient(conn) - req := params.GetGroupRequest{UUID: uuid} - resp, err := client.GetGroup(&req) + resp, err := client.GetGroup(req) if err != nil { return nil, err } @@ -185,7 +193,7 @@ func (jc *jaasClient) ReadGroup(ctx context.Context, uuid string) (*JaasGroup, e } // RenameGroup attempts to rename a group that matches the provided name. -func (jc *jaasClient) RenameGroup(ctx context.Context, name, newName string) error { +func (jc *jaasClient) RenameGroup(name, newName string) error { conn, err := jc.GetConnection(nil) if err != nil { return err @@ -198,7 +206,7 @@ func (jc *jaasClient) RenameGroup(ctx context.Context, name, newName string) err } // RemoveGroup attempts to remove a group that matches the provided name. -func (jc *jaasClient) RemoveGroup(ctx context.Context, name string) error { +func (jc *jaasClient) RemoveGroup(name string) error { conn, err := jc.GetConnection(nil) if err != nil { return err diff --git a/internal/juju/jaas_test.go b/internal/juju/jaas_test.go index a8a9c2b9..6f4e7f67 100644 --- a/internal/juju/jaas_test.go +++ b/internal/juju/jaas_test.go @@ -163,7 +163,7 @@ func (s *JaasSuite) TestAddGroup() { s.mockJaasClient.EXPECT().AddGroup(req).Return(resp, nil) client := s.getJaasClient() - uuid, err := client.AddGroup(context.Background(), name) + uuid, err := client.AddGroup(name) s.Require().NoError(err) s.Require().Equal(resp.UUID, uuid) } @@ -179,7 +179,7 @@ func (s *JaasSuite) TestGetGroup() { s.mockJaasClient.EXPECT().GetGroup(req).Return(resp, nil) client := s.getJaasClient() - gotGroup, err := client.ReadGroup(context.Background(), uuid) + gotGroup, err := client.ReadGroupByUUID(uuid) s.Require().NoError(err) s.Require().Equal(*gotGroup, JaasGroup{UUID: uuid, Name: name}) } @@ -193,7 +193,7 @@ func (s *JaasSuite) TestGetGroupNotFound() { s.mockJaasClient.EXPECT().GetGroup(req).Return(params.GetGroupResponse{}, errors.New("group not found")) client := s.getJaasClient() - gotGroup, err := client.ReadGroup(context.Background(), uuid) + gotGroup, err := client.ReadGroupByUUID(uuid) s.Require().Error(err) s.Require().Nil(gotGroup) } @@ -207,7 +207,7 @@ func (s *JaasSuite) TestRenameGroup() { s.mockJaasClient.EXPECT().RenameGroup(req).Return(nil) client := s.getJaasClient() - err := client.RenameGroup(context.Background(), name, newName) + err := client.RenameGroup(name, newName) s.Require().NoError(err) } @@ -219,7 +219,7 @@ func (s *JaasSuite) TestRemoveGroup() { s.mockJaasClient.EXPECT().RemoveGroup(req).Return(nil) client := s.getJaasClient() - err := client.RemoveGroup(context.Background(), name) + err := client.RemoveGroup(name) s.Require().NoError(err) } diff --git a/internal/provider/data_source_jaas_group.go b/internal/provider/data_source_jaas_group.go new file mode 100644 index 00000000..462a839d --- /dev/null +++ b/internal/provider/data_source_jaas_group.go @@ -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 jaasGroupDataSource struct { + client *juju.Client + + // subCtx is the context created with the new tflog subsystem for applications. + subCtx context.Context +} + +// NewJAASGroupDataSource returns a new JAAS group data source instance. +func NewJAASGroupDataSource() datasource.DataSource { + return &jaasGroupDataSource{} +} + +type jaasGroupDataSourceModel struct { + Name types.String `tfsdk:"name"` + UUID types.String `tfsdk:"uuid"` +} + +// Metadata returns the metadata for the JAAS group data source. +func (d *jaasGroupDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_jaas_group" +} + +// Schema defines the schema for JAAS groups. +func (d *jaasGroupDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "A data source representing a Juju JAAS Group.", + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "The name of the group.", + Required: true, + }, + "uuid": schema.StringAttribute{ + Description: "The UUID of the group.", + Computed: true, + }, + }, + } +} + +// Configure sets up the JAAS group data source with the provider data. +func (d *jaasGroupDataSource) 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, LogDataSourceJAASGroup) +} + +// Read updates the group data source with the latest data from JAAS. +func (d *jaasGroupDataSource) 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-group") + return + } + + var data jaasGroupDataSourceModel + + // Read Terraform configuration state into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Update the group with the latest data from JAAS + group, err := d.client.Jaas.ReadGroupByName(data.Name.String()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read group, got error: %v", err)) + return + } + data.UUID = types.StringValue(group.UUID) + d.trace(fmt.Sprintf("read group %q data source", data.Name)) + + // Save the updated group back to the state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (d *jaasGroupDataSource) trace(msg string, additionalFields ...map[string]interface{}) { + if d.subCtx == nil { + return + } + + //SubsystemTrace(subCtx, "datasource-jaas-group", "hello, world", map[string]interface{}{"foo": 123}) + // Output: + // {"@level":"trace","@message":"hello, world","@module":"juju.datasource-jaas-group","foo":123} + tflog.SubsystemTrace(d.subCtx, LogDataSourceJAASGroup, msg, additionalFields...) +} diff --git a/internal/provider/data_source_jaas_group_test.go b/internal/provider/data_source_jaas_group_test.go new file mode 100644 index 00000000..98753fc7 --- /dev/null +++ b/internal/provider/data_source_jaas_group_test.go @@ -0,0 +1,49 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the Apache License, Version 2.0, see LICENCE file for details. + +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + internaltesting "github.com/juju/terraform-provider-juju/internal/testing" +) + +func TestAcc_DataSourceJAASGroup(t *testing.T) { + OnlyTestAgainstJAAS(t) + groupName := acctest.RandomWithPrefix("tf-jaas-group") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: frameworkProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceJAASGroup(groupName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.juju_jaas_group.test", "name", groupName), + resource.TestCheckResourceAttrSet("data.juju_jaas_group.test", "uuid"), + resource.TestCheckResourceAttrPair("juju_jaas_group.test", "uuid", "data.juju_jaas_group.test", "uuid"), + ), + }, + }, + }) +} + +func testAccDataSourceJAASGroup(name string) string { + return internaltesting.GetStringFromTemplateWithData( + "testAccDataSourceJAASGroup", + ` +resource "juju_jaas_group" "test" { + name = "{{ .Name }}" +} + +data "juju_jaas_group" "test" { + name = juju_jaas_group.test.name +} +`, internaltesting.TemplateData{ + "Name": name, + }) +} diff --git a/internal/provider/helpers.go b/internal/provider/helpers.go index 1da27011..0ac70790 100644 --- a/internal/provider/helpers.go +++ b/internal/provider/helpers.go @@ -33,6 +33,8 @@ const ( LogResourceSecret = "resource-secret" LogResourceAccessSecret = "resource-access-secret" + LogDataSourceJAASGroup = "datasource-jaas-group" + LogResourceJAASAccessModel = "resource-jaas-access-model" LogResourceJAASAccessCloud = "resource-jaas-access-cloud" LogResourceJAASAccessGroup = "resource-jaas-access-group" diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 6b0a3d56..4507dd78 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -393,6 +393,7 @@ func (p *jujuProvider) DataSources(_ context.Context) []func() datasource.DataSo func() datasource.DataSource { return NewModelDataSource() }, func() datasource.DataSource { return NewOfferDataSource() }, func() datasource.DataSource { return NewSecretDataSource() }, + func() datasource.DataSource { return NewJAASGroupDataSource() }, } } diff --git a/internal/provider/resource_access_jaas_offer_test.go b/internal/provider/resource_access_jaas_offer_test.go index 699149aa..531875b9 100644 --- a/internal/provider/resource_access_jaas_offer_test.go +++ b/internal/provider/resource_access_jaas_offer_test.go @@ -38,7 +38,7 @@ func TestAcc_ResourceJaasAccessOffer(t *testing.T) { // Objects for checking access groupRelationF := func(s string) string { return jimmnames.NewGroupTag(s).String() + "#member" } groupCheck := newCheckAttribute(groupResourcename, "uuid", groupRelationF) - offerRelationF := func(s string) string { return jimmnames.NewApplicationOfferTag(s).String() } + offerRelationF := func(s string) string { return names.NewApplicationOfferTag(s).String() } offerCheck := newCheckAttribute(offerAccessResourceName, "offer_url", offerRelationF) userTag := names.NewUserTag(user).String() svcAccTag := names.NewUserTag(svcAccWithDomain).String() diff --git a/internal/provider/resource_jaas_group.go b/internal/provider/resource_jaas_group.go index dd9bcdb9..a6e38e5a 100644 --- a/internal/provider/resource_jaas_group.go +++ b/internal/provider/resource_jaas_group.go @@ -106,7 +106,7 @@ func (resource *jaasGroupResource) Create(ctx context.Context, req resource.Crea } // Add the group to JAAS - uuid, err := resource.client.Jaas.AddGroup(ctx, plan.Name.ValueString()) + uuid, err := resource.client.Jaas.AddGroup(plan.Name.ValueString()) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to add group %q, got error: %s", plan.Name.ValueString(), err)) return @@ -133,7 +133,7 @@ func (resource *jaasGroupResource) Read(ctx context.Context, req resource.ReadRe } // Read the group from JAAS - group, err := resource.client.Jaas.ReadGroup(ctx, state.UUID.ValueString()) + group, err := resource.client.Jaas.ReadGroupByUUID(state.UUID.ValueString()) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get group %q, got error: %s", state.Name.ValueString(), err)) return @@ -172,7 +172,7 @@ func (resource *jaasGroupResource) Update(ctx context.Context, req resource.Upda } // Rename the group in JAAS - err := resource.client.Jaas.RenameGroup(ctx, state.Name.ValueString(), plan.Name.ValueString()) + err := resource.client.Jaas.RenameGroup(state.Name.ValueString(), plan.Name.ValueString()) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to rename group %q to %q, got error: %s", state.Name.ValueString(), plan.Name.ValueString(), err)) return @@ -199,7 +199,7 @@ func (resource *jaasGroupResource) Delete(ctx context.Context, req resource.Dele } // Delete the group from JAAS - err := resource.client.Jaas.RemoveGroup(ctx, state.Name.ValueString()) + err := resource.client.Jaas.RemoveGroup(state.Name.ValueString()) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to remove group %q, got error: %s", state.Name.ValueString(), err)) return diff --git a/internal/provider/resource_jaas_group_test.go b/internal/provider/resource_jaas_group_test.go index 03fd3362..f8a653f1 100644 --- a/internal/provider/resource_jaas_group_test.go +++ b/internal/provider/resource_jaas_group_test.go @@ -4,7 +4,6 @@ package provider import ( - "context" "errors" "fmt" "regexp" @@ -77,7 +76,7 @@ func testAccCheckJaasGroupExists(resourceName string, checkExists bool) resource return errors.New("No group uuid is set") } - _, err := TestClient.Jaas.ReadGroup(context.Background(), uuid) + _, err := TestClient.Jaas.ReadGroupByUUID(uuid) if checkExists && err != nil { return fmt.Errorf("Group with uuid %q does not exist", uuid) } else if !checkExists && err == nil {