Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data source juju_application #630

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/data-sources/application.md

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe docs/index.md also needs to be adjusted to include the application data source.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "juju_application Data Source - terraform-provider-juju"
subcategory: ""
description: |-
A data source that represents a single Juju application deployment from a charm.
---

# juju_application (Data Source)

A data source that represents a single Juju application deployment from a charm.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `model` (String) The name of the model where the application is deployed.
- `name` (String) Name of the application deployment.

### Read-Only

- `id` (String) The ID of this resource.
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Today this provider allows you to manage the following via resources:

and refer to the following via data sources:

* Applications
* Machines
* Models
* Offers
Expand Down
148 changes: 148 additions & 0 deletions internal/provider/data_source_application.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// 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"
)

// Ensure provider defined types fully satisfy framework interfaces.
var _ datasource.DataSourceWithConfigure = &applicationDataSource{}

// NewApplicationDataSource returns a new data source for a Juju application.
func NewApplicationDataSource() datasource.DataSourceWithConfigure {
shipperizer marked this conversation as resolved.
Show resolved Hide resolved
return &applicationDataSource{}
}

type applicationDataSource struct {
client *juju.Client

// context for the logging subsystem.
subCtx context.Context
}

type applicationDataSourceModel struct {
ApplicationName types.String `tfsdk:"name"`
ModelName types.String `tfsdk:"model"`

// ID required by the testing framework
ID types.String `tfsdk:"id"`
}

// Metadata returns the full data source name as used in terraform plans.
func (d *applicationDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_application"
}

// Schema returns the schema for the application data source.
func (d *applicationDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "A data source that represents a single Juju application deployment from a charm.",
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Description: "Name of the application deployment.",
Required: true,
},
"model": schema.StringAttribute{
Description: "The name of the model where the application is deployed.",
Required: true,
},
},
}
}

// Configure enables provider-level data or clients to be set in the
// provider-defined DataSource type. It is separately executed for each
// ReadDataSource RPC.
func (d *applicationDataSource) 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 *juju.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)
return
}

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

// Read is called when the provider must read resource values in order
// to update state. Planned state values should be read from the
// ReadRequest and new state values set on the ReadResponse.
// Take the juju api input from the ID, it may not exist in the plan.
// Only set optional values if they exist.
func (d *applicationDataSource) 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, "application")
return
}
var data applicationDataSourceModel

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

appName := data.ApplicationName.ValueString()
modelName := data.ModelName.ValueString()
d.trace("Read", map[string]interface{}{
"Model": modelName,
"Name": appName,
})

response, err := d.client.Applications.ReadApplication(&juju.ReadApplicationInput{
ModelName: modelName,
AppName: appName,
})
if err != nil {
resp.Diagnostics.Append(handleApplicationNotFoundError(ctx, err, &resp.State)...)
return
}
if response == nil {
return
}
d.trace("read application", map[string]interface{}{"resource": appName, "response": response})

data.ApplicationName = types.StringValue(appName)
data.ModelName = types.StringValue(modelName)

d.trace("Found", applicationDataSourceModelForLogging(ctx, &data))
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

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

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

func applicationDataSourceModelForLogging(_ context.Context, app *applicationDataSourceModel) map[string]interface{} {
value := map[string]interface{}{
"application-name": app.ApplicationName.ValueString(),
"model": app.ModelName.ValueString(),
}
return value
}
104 changes: 104 additions & 0 deletions internal/provider/data_source_application_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright 2024 Canonical Ltd.
// Licensed under the Apache License, Version 2.0, see LICENCE file for details.

package provider

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

func TestAcc_DataSourceApplicationLXD_Edge(t *testing.T) {
if testingCloud != LXDCloudTesting {
t.Skip(t.Name() + " only runs with LXD")
}
modelName := acctest.RandomWithPrefix("tf-datasource-application-test-model")
applicationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: frameworkProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccDataSourceApplicationLXD(modelName, applicationName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.juju_application.this", "model", modelName),
resource.TestCheckResourceAttr("data.juju_application.this", "name", applicationName),
),
},
},
})
}

func TestAcc_DataSourceApplicationK8s_Edge(t *testing.T) {
if testingCloud != MicroK8sTesting {
t.Skip(t.Name() + " only runs with MicroK8s")
}
modelName := acctest.RandomWithPrefix("tf-datasource-application-test-model")
applicationName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: frameworkProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccDataSourceApplicationK8s(modelName, applicationName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.juju_application.this", "model", modelName),
resource.TestCheckResourceAttr("data.juju_application.this", "name", applicationName),
),
},
},
})
}

func testAccDataSourceApplicationLXD(modelName, applicationName string) string {
return fmt.Sprintf(`
resource "juju_model" "model" {
name = %q
}

resource "juju_application" "this" {
name = %q
model = juju_model.model.name
trust = true

charm {
name = "ubuntu"
channel = "latest/stable"
}
}

data "juju_application" "this" {
model = juju_model.model.name
name = juju_application.this.name

}`, modelName, applicationName)
}

func testAccDataSourceApplicationK8s(modelName, applicationName string) string {
return fmt.Sprintf(`
resource "juju_model" "model" {
name = %q
}

resource "juju_application" "this" {
name = %q
model = juju_model.model.name
trust = true

charm {
name = "zinc-k8s"
channel = "latest/stable"
}
}

data "juju_application" "this" {
model = juju_model.model.name
name = juju_application.this.name

}`, modelName, applicationName)
}
9 changes: 5 additions & 4 deletions internal/provider/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ import (
//
// @module=juju.resource-application
const (
LogDataSourceMachine = "datasource-machine"
LogDataSourceModel = "datasource-model"
LogDataSourceOffer = "datasource-offer"
LogDataSourceSecret = "datasource-secret"
LogDataSourceApplication = "datasource-application"
LogDataSourceMachine = "datasource-machine"
LogDataSourceModel = "datasource-model"
LogDataSourceOffer = "datasource-offer"
LogDataSourceSecret = "datasource-secret"

LogResourceApplication = "resource-application"
LogResourceAccessModel = "resource-access-model"
Expand Down
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ func (p *jujuProvider) Resources(_ context.Context) []func() resource.Resource {
// the Metadata method. All data sources must have unique names.
func (p *jujuProvider) DataSources(_ context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
func() datasource.DataSource { return NewApplicationDataSource() },
func() datasource.DataSource { return NewMachineDataSource() },
func() datasource.DataSource { return NewModelDataSource() },
func() datasource.DataSource { return NewOfferDataSource() },
Expand Down
1 change: 1 addition & 0 deletions templates/index.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Today this provider allows you to manage the following via resources:

and refer to the following via data sources:

* Applications
* Machines
* Models
* Offers
Expand Down