Skip to content

Commit

Permalink
Merge pull request #541 from kian99/CSS-9768-implement-jaas-validators
Browse files Browse the repository at this point in the history
feat: add jaas validators
  • Loading branch information
hmlanigan authored Aug 12, 2024
2 parents 5de70c1 + e1d0097 commit bf6a31c
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 0 deletions.
38 changes: 38 additions & 0 deletions internal/juju/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ type Client struct {
SSHKeys sshKeysClient
Users usersClient
Secrets secretsClient

isJAAS func() bool
}

// IsJAAS returns a boolean to indicate whether the controller configured is a JAAS controller.
// JAAS controllers offer additional functionality for permission management.
func (c Client) IsJAAS() bool {
return c.isJAAS()
}

type jujuModel struct {
Expand Down Expand Up @@ -82,6 +90,12 @@ func NewClient(ctx context.Context, config ControllerConfiguration) (*Client, er
modelUUIDcache: make(map[string]jujuModel),
subCtx: tflog.NewSubsystem(ctx, LogJujuClient),
}
// Client ID and secret are only set when connecting to JAAS. Use this as a fallback
// value if connecting to the controller fails.
defaultJAASCheck := false
if config.ClientID != "" && config.ClientSecret != "" {
defaultJAASCheck = true
}

return &Client{
Applications: *newApplicationClient(sc),
Expand All @@ -93,9 +107,33 @@ func NewClient(ctx context.Context, config ControllerConfiguration) (*Client, er
SSHKeys: *newSSHKeysClient(sc),
Users: *newUsersClient(sc),
Secrets: *newSecretsClient(sc),
isJAAS: func() bool { return sc.IsJAAS(defaultJAASCheck) },
}, nil
}

var checkJAASOnce sync.Once
var isJAAS bool

// IsJAAS checks if the controller is a JAAS controller.
// It does this by checking whether it offers the "JIMM" facade which
// will only ever be offered by JAAS. The method accepts a default value
// and doesn't return an error because callers are not expected to fail if
// they can't determine whether they are connecting to JAAS.
//
// IsJAAS uses a synchronisation object to only perform the check once and return the same result.
func (sc *sharedClient) IsJAAS(defaultVal bool) bool {
checkJAASOnce.Do(func() {
conn, err := sc.GetConnection(nil)
if err != nil {
isJAAS = defaultVal
return
}
defer conn.Close()
isJAAS = conn.BestFacadeVersion("JIMM") != 0
})
return isJAAS
}

// GetConnection returns a juju connection for use creating juju
// api clients given the provided model name.
func (sc *sharedClient) GetConnection(modelName *string) (api.Connection, error) {
Expand Down
64 changes: 64 additions & 0 deletions internal/provider/validator_avoid_jaas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2024 Canonical Ltd.
// Licensed under the Apache License, Version 2.0, see LICENCE file for details.

package provider

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"

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

var _ datasource.ConfigValidator = &AvoidJAASValidator{}
var _ resource.ConfigValidator = &AvoidJAASValidator{}

// AvoidJAASValidator enforces that the resource is not used with JAAS.
// Useful to direct users to more capable resources.
type AvoidJAASValidator struct {
Client *juju.Client
PreferredObject string
}

// Description returns a plain text description of the validator's behavior, suitable for a practitioner to understand its impact.
func (v AvoidJAASValidator) Description(ctx context.Context) string {
return v.MarkdownDescription(ctx)
}

// MarkdownDescription returns a markdown formatted description of the validator's behavior, suitable for a practitioner to understand its impact.
func (v AvoidJAASValidator) MarkdownDescription(_ context.Context) string {
return "Enforces that this resource should not be used with JAAS"
}

// ValidateResource performs the validation on the data source.
func (v AvoidJAASValidator) ValidateDataSource(ctx context.Context, req datasource.ValidateConfigRequest, resp *datasource.ValidateConfigResponse) {
resp.Diagnostics = v.validate()
}

// ValidateResource performs the validation on the resource.
func (v AvoidJAASValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
resp.Diagnostics = v.validate()
}

// validate runs the main validation logic of the validator, reading configuration data out of `config` and returning with diagnostics.
func (v AvoidJAASValidator) validate() diag.Diagnostics {
var diags diag.Diagnostics

// Return without error if a nil client is detected.
// This is possible since validation is called at various points throughout resource creation.
if v.Client != nil && v.Client.IsJAAS() {
hint := ""
if v.PreferredObject != "" {
hint = "Try the " + v.PreferredObject + " resource instead."
}
diags.AddError("Invalid use of resource with JAAS.",
"This resource is not supported with JAAS. "+
hint+
"JAAS offers additional enterprise features through the use of dedicated resources. "+
"See https://jaas.ai/ for more details.")
}
return diags
}
56 changes: 56 additions & 0 deletions internal/provider/validator_require_jaas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2024 Canonical Ltd.
// Licensed under the Apache License, Version 2.0, see LICENCE file for details.

package provider

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"

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

var _ datasource.ConfigValidator = &RequiresJAASValidator{}
var _ resource.ConfigValidator = &RequiresJAASValidator{}

// RequiresJAASValidator enforces that the resource can only be used with JAAS.
type RequiresJAASValidator struct {
Client *juju.Client
}

// Description returns a plain text description of the validator's behavior, suitable for a practitioner to understand its impact.
func (v RequiresJAASValidator) Description(ctx context.Context) string {
return v.MarkdownDescription(ctx)
}

// MarkdownDescription returns a markdown formatted description of the validator's behavior, suitable for a practitioner to understand its impact.
func (v RequiresJAASValidator) MarkdownDescription(_ context.Context) string {
return "Enforces that this resource can only be used with JAAS"
}

// ValidateResource performs the validation on the data source.
func (v RequiresJAASValidator) ValidateDataSource(ctx context.Context, req datasource.ValidateConfigRequest, resp *datasource.ValidateConfigResponse) {
resp.Diagnostics = v.validate()
}

// ValidateResource performs the validation on the resource.
func (v RequiresJAASValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
resp.Diagnostics = v.validate()
}

// validate runs the main validation logic of the validator, reading configuration data out of `config` and returning with diagnostics.
func (v RequiresJAASValidator) validate() diag.Diagnostics {
var diags diag.Diagnostics

// Return without error if a nil client is detected.
// This is possible since validation is called at various points throughout resource creation.
if v.Client != nil && !v.Client.IsJAAS() {
diags.AddError("Attempted use of resource without JAAS.",
"This resource can only be used with JAAS, which offers additional enterprise features - see https://jaas.ai/ for more details.")
}

return diags
}

0 comments on commit bf6a31c

Please sign in to comment.