Skip to content

Commit

Permalink
Add abstractions to fetch client resource data in tf cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
sergiught committed Aug 8, 2023
1 parent 810de08 commit 9574462
Show file tree
Hide file tree
Showing 4 changed files with 298 additions and 0 deletions.
36 changes: 36 additions & 0 deletions internal/cli/terraform.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package cli

import (
"context"
"os"
"path"

"github.com/spf13/cobra"

"github.com/auth0/auth0-cli/internal/auth0"
)

var tfFlags = terraformFlags{
Expand All @@ -27,6 +30,15 @@ type (
}
)

func (i *terraformInputs) parseResourceFetchers(api *auth0.API) []resourceDataFetcher {
// Hard coding this for now until we add support for the `--resources` flag.
return []resourceDataFetcher{
&clientResourceFetcher{
api: api,
},
}
}

func terraformCmd(cli *cli) *cobra.Command {
cmd := &cobra.Command{
Use: "terraform",
Expand Down Expand Up @@ -63,6 +75,15 @@ func generateTerraformCmd(cli *cli) *cobra.Command {

func generateTerraformCmdRun(cli *cli, inputs *terraformInputs) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
data, err := fetchImportData(cmd.Context(), inputs.parseResourceFetchers(cli.api)...)
if err != nil {
return err
}

// Just temporarily. Remove this once import file generation is in place.
cli.renderer.JSONResult(data)
cli.renderer.Newline()

if err := generateTerraformConfigFiles(inputs); err != nil {
return err
}
Expand All @@ -79,6 +100,21 @@ func generateTerraformCmdRun(cli *cli, inputs *terraformInputs) func(cmd *cobra.
}
}

func fetchImportData(ctx context.Context, fetchers ...resourceDataFetcher) (importDataList, error) {
var importData importDataList

for _, fetcher := range fetchers {
data, err := fetcher.FetchData(ctx)
if err != nil {
return nil, err
}

importData = append(importData, data...)
}

return importData, nil
}

func generateTerraformConfigFiles(inputs *terraformInputs) error {
const readWritePermission = 0755
if err := os.MkdirAll(inputs.OutputDIR, readWritePermission); err != nil {
Expand Down
79 changes: 79 additions & 0 deletions internal/cli/terraform_fetcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package cli

import (
"context"
"regexp"

"github.com/auth0/go-auth0/management"

"github.com/auth0/auth0-cli/internal/auth0"
)

type (
importDataList []importDataItem

importDataItem struct {
ResourceName string
ImportID string
}

resourceDataFetcher interface {
FetchData(ctx context.Context) (importDataList, error)
}

clientResourceFetcher struct {
api *auth0.API
}
)

func (f *clientResourceFetcher) FetchData(ctx context.Context) (importDataList, error) {
var data importDataList

var page int
for {
clients, err := f.api.Client.List(
ctx,
management.Page(page),
management.Parameter("is_global", "false"),
management.IncludeFields("client_id", "name"),
)
if err != nil {
return nil, err
}

for _, client := range clients.Clients {
data = append(data, importDataItem{
ResourceName: "auth0_client." + sanitizeResourceName(client.GetName()),
ImportID: client.GetClientID(),
})
}

if !clients.HasNext() {
break
}

page++
}

return data, nil
}

// sanitizeResourceName will return a valid terraform resource name.
//
// A name must start with a letter or underscore and may
// contain only letters, digits, underscores, and dashes.
func sanitizeResourceName(name string) string {
// Regular expression pattern to remove invalid characters.
namePattern := "[^a-zA-Z0-9_-]+"
re := regexp.MustCompile(namePattern)

sanitizedName := re.ReplaceAllString(name, "")

// Regular expression pattern to remove leading digits or dashes.
namePattern = "^[0-9-]+"
re = regexp.MustCompile(namePattern)

sanitizedName = re.ReplaceAllString(sanitizedName, "")

return sanitizedName
}
142 changes: 142 additions & 0 deletions internal/cli/terraform_fetcher_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package cli

import (
"context"
"fmt"
"testing"

"github.com/auth0/go-auth0/management"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"

"github.com/auth0/auth0-cli/internal/auth0"
"github.com/auth0/auth0-cli/internal/auth0/mock"
)

func TestSanitizeResourceName(t *testing.T) {
testCases := []struct {
input string
expected string
}{
// Test cases with valid names
{"ValidName123", "ValidName123"},
{"_Another_Valid-Name", "_Another_Valid-Name"},
{"name_with_123", "name_with_123"},
{"_start_with_underscore", "_start_with_underscore"},

// Test cases with invalid names to be sanitized
{"Invalid@Name", "InvalidName"},
{"Invalid Name", "InvalidName"},
{"123StartWithNumber", "StartWithNumber"},
{"-StartWithDash", "StartWithDash"},
{"", ""},
}

for _, testCase := range testCases {
t.Run(testCase.input, func(t *testing.T) {
sanitized := sanitizeResourceName(testCase.input)
assert.Equal(t, testCase.expected, sanitized)
})
}
}

func TestClientResourceFetcher_FetchData(t *testing.T) {
t.Run("it successfully retrieves client data", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

clientAPI := mock.NewMockClientAPI(ctrl)
clientAPI.EXPECT().
List(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
Return(
&management.ClientList{
List: management.List{
Start: 0,
Limit: 2,
Total: 4,
},
Clients: []*management.Client{
{
ClientID: auth0.String("clientID_1"),
Name: auth0.String("My Test Client 1"),
},
{
ClientID: auth0.String("clientID_2"),
Name: auth0.String("My Test Client 2"),
},
},
},
nil,
)
clientAPI.EXPECT().
List(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
Return(
&management.ClientList{
List: management.List{
Start: 2,
Limit: 4,
Total: 4,
},
Clients: []*management.Client{
{
ClientID: auth0.String("clientID_3"),
Name: auth0.String("My Test Client 3"),
},
{
ClientID: auth0.String("clientID_4"),
Name: auth0.String("My Test Client 4"),
},
},
},
nil,
)

fetcher := clientResourceFetcher{
api: &auth0.API{
Client: clientAPI,
},
}

expectedData := importDataList{
{
ResourceName: "auth0_client.MyTestClient1",
ImportID: "clientID_1",
},
{
ResourceName: "auth0_client.MyTestClient2",
ImportID: "clientID_2",
},
{
ResourceName: "auth0_client.MyTestClient3",
ImportID: "clientID_3",
},
{
ResourceName: "auth0_client.MyTestClient4",
ImportID: "clientID_4",
},
}

data, err := fetcher.FetchData(context.Background())
assert.NoError(t, err)
assert.Equal(t, expectedData, data)
})

t.Run("it returns an error if api call fails", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

clientAPI := mock.NewMockClientAPI(ctrl)
clientAPI.EXPECT().
List(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
Return(nil, fmt.Errorf("failed to list clients"))

fetcher := clientResourceFetcher{
api: &auth0.API{
Client: clientAPI,
},
}

_, err := fetcher.FetchData(context.Background())
assert.EqualError(t, err, "failed to list clients")
})
}
41 changes: 41 additions & 0 deletions internal/cli/terraform_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cli

import (
"context"
"errors"
"os"
"path"
"testing"
Expand All @@ -9,6 +11,45 @@ import (
"github.com/stretchr/testify/require"
)

type mockFetcher struct {
mockData importDataList
mockErr error
}

func (m *mockFetcher) FetchData(context.Context) (importDataList, error) {
return m.mockData, m.mockErr
}

func TestFetchImportData(t *testing.T) {
t.Run("it can successfully fetch import data for multiple resources", func(t *testing.T) {
mockData1 := importDataList{{ResourceName: "Resource1", ImportID: "123"}}
mockData2 := importDataList{{ResourceName: "Resource2", ImportID: "456"}}
mockFetchers := []resourceDataFetcher{
&mockFetcher{mockData: mockData1},
&mockFetcher{mockData: mockData2},
}

expectedData := importDataList{
{ResourceName: "Resource1", ImportID: "123"},
{ResourceName: "Resource2", ImportID: "456"},
}

data, err := fetchImportData(context.Background(), mockFetchers...)
assert.NoError(t, err)
assert.Equal(t, expectedData, data)
})

t.Run("it returns an error when a data fetcher fails", func(t *testing.T) {
expectedErr := errors.New("failed to list clients")
mockFetchers := []resourceDataFetcher{
&mockFetcher{mockErr: expectedErr},
}

_, err := fetchImportData(context.Background(), mockFetchers...)
assert.EqualError(t, err, "failed to list clients")
})
}

func TestGenerateTerraformConfigFiles(t *testing.T) {
testInputs := terraformInputs{
OutputDIR: "./terraform/dev",
Expand Down

0 comments on commit 9574462

Please sign in to comment.