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

Swap out internal client for new external client - SAML Groups #123

Open
wants to merge 9 commits into
base: master
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
3 changes: 3 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ Below arguments for the provider can also be set as environment variables.
If specified, auth token takes priority over username/password.
* `insecure_skip_verify` or `SPLUNK_INSECURE_SKIP_VERIFY` - (Optional) Insecure skip verification flag (Defaults to `true`)
* `timeout` or `SPLUNK_TIMEOUT` - (Optional) Timeout when making calls to Splunk server. (Defaults to `60 seconds`)
* `use_client_default` - Determines the default behavior for resources that implement use_client. Permitted values are legacy and external.
Currently defaults to legacy, but will default to external in a future version.
The legacy client is being replaced by a standalone Splunk client with improved error and drift handling. The legacy client will be deprecated in a future version.

(NOTE: Auth token can only be used with certain type of Splunk deployments.
Read more on authentication with tokens here: https://docs.splunk.com/Documentation/Splunk/latest/Security/Setupauthenticationwithtokens)
2 changes: 2 additions & 0 deletions docs/resources/admin_saml_groups.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ For latest resource argument reference: https://docs.splunk.com/Documentation/Sp
This resource block supports the following arguments:
* `name` - (Required) The name of the external group.
* `roles` - (Required) List of internal roles assigned to the group.
* `use_client` - (Optional) Set to explicitly specify which client to use for this resource. Leave unset to use the provider's default. Permitted non-empty values are legacy and external.
The legacy client is being replaced by a standalone Splunk client with improved error and drift handling. The legacy client will be deprecated in a future version.

## Attribute Reference
In addition to all arguments above, This resource block exports the following arguments:
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
module github.com/splunk/terraform-provider-splunk

go 1.17
go 1.18

require (
github.com/google/go-querystring v1.0.0
github.com/hashicorp/terraform-plugin-sdk v1.15.0
github.com/splunk/go-splunk-client v0.0.1
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/splunk/go-splunk-client v0.0.1 h1:f4VW1vKqpTUCd13fYQ5/vdohZliolHAEIKfcVOgajdw=
github.com/splunk/go-splunk-client v0.0.1/go.mod h1:PBd+U0yLaO2WQTV2uOkvSKRA9tIQA8f7gikMIPCXEDw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
Expand Down Expand Up @@ -248,10 +250,8 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
anushjay marked this conversation as resolved.
Show resolved Hide resolved
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
Expand Down
83 changes: 83 additions & 0 deletions internal/sync/client_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2022 Splunk, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sync

import (
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/splunk/go-splunk-client/pkg/client"
)

// clientID implements the Sync interface for client.ID.
type clientID struct {
id *client.ID
titleField string
}

// NewClientID returns a Sync that maps a client.ID to a Terraform
// resource ID.
func NewClientID(id *client.ID, titleField string) Sync {
return clientID{
id: id,
titleField: titleField,
}
}

// SyncResource synchronizes schema.ResourceData from the locally stored client.ID.
func (id clientID) SyncResource(d *schema.ResourceData) error {
if idURL, err := id.id.URL(); err == nil {
d.SetId(idURL)
}

if id.id.Title != "" {
if err := d.Set(id.titleField, id.id.Title); err != nil {
return err
}
}

return nil
}

// SyncObject synchronizes the locally stored client.ID from schema.ResourceData.
func (id clientID) SyncObject(d *schema.ResourceData) error {
// Title is set by titleField, but may get overridden if d.Id() is parseable as a client.ID
id.id.Title = d.Get(id.titleField).(string)

// an unparseable ID URL should be ignored, so it can instead be determined at read-time.
// this is a likely scenario when moving from the legacy client to the external client.
// if parseable, fully replaces the stored client.ID value, including the Title which was
// synchronized from id.titleField above.
if parsedId, err := client.ParseID(d.Id()); err == nil {
*id.id = parsedId
}

return nil
}

// GetObject returns the locally stored client.ID.
func (id clientID) GetObject() interface{} {
return id.id
}

// mustParseID returns a pointer to a new client.ID by parsing the given URL. It panics if client.ParseID()
// returns an error. This function is present to simplify testing where we don't expect URL parsing errors
// to occur.
func mustParseID(url string) *client.ID {
id, err := client.ParseID(url)
if err != nil {
panic(err)
}

return &id
}
109 changes: 109 additions & 0 deletions internal/sync/client_id_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright 2022 Splunk, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sync

import (
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/splunk/go-splunk-client/pkg/client"
)

func Test_clientIdResourceDataHandler(t *testing.T) {
tests := syncResourceTestCases{
{
name: "empty",
sync: NewClientID(&client.ID{}, "name"),
schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
},
},
checkFunc: composeResourceCheckFunc(
checkResourceIdEquals(""),
checkResourceKeyEquals("name", ""),
),
},
{
name: "set",
sync: NewClientID(mustParseID("https://localhost:8089/services/authentication/users/testuser"), "name"),
schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
},
},
checkFunc: composeResourceCheckFunc(
checkResourceIdEquals("https://localhost:8089/services/authentication/users/testuser"),
checkResourceKeyEquals("name", "testuser"),
),
},
}

tests.test(t)
}

func Test_clientIdObjectValueHandler(t *testing.T) {
tests := syncObjectTestCases{
{
name: "empty",
sync: NewClientID(&client.ID{}, "name"),
schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
},
},
checkFunc: checkGetObjectEquality(&client.ID{}),
},
{
name: "invalid resource Id",
prepFunc: withId("invalid"),
sync: NewClientID(&client.ID{}, "name"),
schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
},
},
// clientId.SyncObject doesn't actually return any errors, as invalid URLs
// are assumed to be due to migration from the legacy client.
checkFunc: checkGetObjectEquality(&client.ID{}),
},
{
name: "valid resource name",
sync: NewClientID(&client.ID{}, "name"),
schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
},
},
schemaValues: map[string]interface{}{
"name": "valid_name",
},
checkFunc: checkGetObjectEquality(&client.ID{Title: "valid_name"}),
},
{
name: "valid resource Id",
prepFunc: withId("https://localhost:8089/services/authentication/users/testuser"),
sync: NewClientID(&client.ID{}, "name"),
schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
},
},
checkFunc: checkGetObjectEquality(mustParseID("https://localhost:8089/services/authentication/users/testuser")),
},
}

tests.test(t)
}
74 changes: 74 additions & 0 deletions internal/sync/direct_field.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2022 Splunk, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sync

import (
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

// directField implements the Sync interface for simple types that are synchronized directly
// between the resource and object.
type directField[T any] struct {
key string
value *T
}

// NewDirectField returns a Sync that directly associates a value with a resource field.
//
// This direct association is generally not sufficient for lists, sets, or maps,
// as the resource value is stored with stored elements as interface{}, not
// the concrete types the referenced value likely contains.
func NewDirectField[T any](key string, value *T) Sync {
return directField[T]{
key: key,
value: value,
}
}

// SyncResource synchronizes schema.ResourceData from the locally stored value.
func (field directField[T]) SyncResource(d *schema.ResourceData) error {
return d.Set(field.key, *field.value)
}

// SyncObject synchronizes the locally stored value from schema.ResourceData.
func (field directField[T]) SyncObject(d *schema.ResourceData) error {
resourceValueI := d.Get(field.key)
// nil interface = unknown key
if resourceValueI == nil {
return fmt.Errorf("resource: likely unknown key %q", field.key)
}

resourceValueT, ok := resourceValueI.(T)
if !ok {
return fmt.Errorf("resource: unable to assign resource type %T to object type %T", resourceValueI, *new(T))
}

*field.value = resourceValueT

return nil
}

// GetObject returns the locally stored value.
func (field directField[T]) GetObject() interface{} {
return field.value
}

// pointerToValueOf returns a new pointer to a copy of the given value. It is used to ease testing
// without needing an existing variable to reference.
func pointerToValueOf[T any](value T) *T {
return &value
}
Loading