Skip to content

Commit

Permalink
feat: add jaas validators
Browse files Browse the repository at this point in the history
  • Loading branch information
kian99 committed Aug 6, 2024
1 parent 5de70c1 commit 9e6ef73
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 0 deletions.
37 changes: 37 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,32 @@ 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
}
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
66 changes: 66 additions & 0 deletions internal/provider/validator_avoid_jaas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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/provider"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/juju/terraform-provider-juju/internal/juju"
)

var _ datasource.ConfigValidator = &AvoidJAASValidator{}
var _ provider.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
}

func (v AvoidJAASValidator) Description(ctx context.Context) string {
return v.MarkdownDescription(ctx)
}

func (v AvoidJAASValidator) MarkdownDescription(_ context.Context) string {
return "Enforces that this resource should not be used with JAAS"
}

func (v AvoidJAASValidator) ValidateDataSource(ctx context.Context, req datasource.ValidateConfigRequest, resp *datasource.ValidateConfigResponse) {
resp.Diagnostics = v.Validate(ctx, req.Config)
}

func (v AvoidJAASValidator) ValidateProvider(ctx context.Context, req provider.ValidateConfigRequest, resp *provider.ValidateConfigResponse) {
resp.Diagnostics = v.Validate(ctx, req.Config)
}

func (v AvoidJAASValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
resp.Diagnostics = v.Validate(ctx, req.Config)
}

func (v AvoidJAASValidator) Validate(ctx context.Context, config tfsdk.Config) diag.Diagnostics {

var diags diag.Diagnostics

if v.Client != nil {
if v.Client.IsJAAS() {
hint := ""
if v.PreferredObject != "" {
hint = "Try the " + v.PreferredObject + " resource instead."
}
diags.AddWarning("Invalid use of resource with JAAS.",
"It is not supported to use this resource with a JAAS setup. "+
hint+
"JAAS offers additional enterprise features through the use of dedicated resources. "+
"See the provider documentation for more details.")
}
}
return diags
}
58 changes: 58 additions & 0 deletions internal/provider/validator_require_jaas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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/provider"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/juju/terraform-provider-juju/internal/juju"
)

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

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

func (v RequiresJAASValidator) Description(ctx context.Context) string {
return v.MarkdownDescription(ctx)
}

func (v RequiresJAASValidator) MarkdownDescription(_ context.Context) string {
return "Enforces that this resource can only be used with JAAS"
}

func (v RequiresJAASValidator) ValidateDataSource(ctx context.Context, req datasource.ValidateConfigRequest, resp *datasource.ValidateConfigResponse) {
resp.Diagnostics = v.Validate(ctx, req.Config)
}

func (v RequiresJAASValidator) ValidateProvider(ctx context.Context, req provider.ValidateConfigRequest, resp *provider.ValidateConfigResponse) {
resp.Diagnostics = v.Validate(ctx, req.Config)
}

func (v RequiresJAASValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
resp.Diagnostics = v.Validate(ctx, req.Config)
}

func (v RequiresJAASValidator) Validate(ctx context.Context, config tfsdk.Config) diag.Diagnostics {

var diags diag.Diagnostics

if v.Client != nil {
if v.Client.IsJAAS() {
diags.AddError("Attempted use of resource without JAAS.",
"This resource can only be used with a JAAS setup offering additional enterprise features - see https://jaas.ai/ for more details.")
}
}

return diags
}

0 comments on commit 9e6ef73

Please sign in to comment.