Skip to content

Commit

Permalink
PDI-1516: Add pingone_custom_domain (Resource) Export (#48)
Browse files Browse the repository at this point in the history
* PDI-1516: Add pingone_custom_domain (Resource) Export

- Fix the bug where the export environment ID was not being bound to Viper
configuration properly.
- Remove unneeded parameters from initApiClient()
- Updated --pingone-export-environment-id help message to include
the available environment variable.
- Added the ImportBlock Sanitize() function to make sure the "to"
field is lowercase, without special characters, and spaces are replaced
with underscores.
- Remove "Resource" from creation methods for each exportable
resource following effective go conventions.
- Do not create import.tf file for empty exported resources.
- Add Debug logging to unit tests.
- Add error output if the export environment ID is empty.
- Make log variables, create a common test helper package and move log printing to it, and don't sanitize resource type in import blocks
  • Loading branch information
erikostien-pingidentity authored Feb 27, 2024
1 parent dc6ddb4 commit 86430aa
Show file tree
Hide file tree
Showing 19 changed files with 159 additions and 30 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/code-analysis-lint-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ jobs:
go-version-file: 'go.mod'
cache: true
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v4
with:
# Require: The version of golangci-lint to use.
# When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version.
Expand Down Expand Up @@ -127,6 +127,8 @@ jobs:
PINGCTL_PINGONE_WORKER_CLIENT_SECRET: ${{ secrets.PINGCTL_PINGONE_WORKER_CLIENT_SECRET }}
PINGCTL_PINGONE_REGION: ${{ secrets.PINGCTL_PINGONE_REGION }}
PINGCTL_PINGONE_WORKER_ENVIRONMENT_ID: ${{ secrets.PINGCTL_PINGONE_WORKER_ENVIRONMENT_ID }}
PINGCTL_LOG_LEVEL: ${{ vars.PINGCTL_LOG_LEVEL }}
PINGCTL_LOG_PATH: ${{ vars.PINGCTL_LOG_PATH }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
Expand Down
3 changes: 3 additions & 0 deletions cmd/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/pingidentity/pingctl/cmd"
"github.com/pingidentity/pingctl/internal/testutils"
)

// Test Auth Login Command Executes without issue
Expand All @@ -22,6 +23,7 @@ func TestAuthLoginCmd_Execute(t *testing.T) {
// Execute the command
err := rootCmd.Execute()
if err != nil {
testutils.PrintLogs(t)
t.Fatalf("Err: %q, Captured StdOut: %q", err, stdout.String())
}
}
Expand All @@ -41,6 +43,7 @@ func TestAuthLogoutCmd_Execute(t *testing.T) {
// Execute the command
err := rootCmd.Execute()
if err != nil {
testutils.PrintLogs(t)
t.Fatalf("Err: %q, Captured StdOut: %q", err, stdout.String())
}
}
2 changes: 2 additions & 0 deletions cmd/feedback_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/pingidentity/pingctl/cmd"
"github.com/pingidentity/pingctl/internal/testutils"
)

// Test Feedback Command Executes without issue
Expand All @@ -22,6 +23,7 @@ func TestFeedbackCmd_Execute(t *testing.T) {
// Execute the root command
err := rootCmd.Execute()
if err != nil {
testutils.PrintLogs(t)
t.Fatalf("Err: %q, Captured StdOut: %q", err, stdout.String())
}
}
22 changes: 15 additions & 7 deletions cmd/platform/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ var (

exportConfigurationParamMapping = map[string]string{
pingoneWorkerEnvironmentIdParamName: pingoneWorkerEnvironmentIdParamConfigKey,
pingoneExportEnvironmentIdParamName: pingoneExportEnvironmentIdParamConfigKey,
pingoneWorkerClientIdParamName: pingoneWorkerClientIdParamConfigKey,
pingoneWorkerClientSecretParamName: pingoneWorkerClientSecretParamConfigKey,
pingoneRegionParamName: pingoneRegionParamConfigKey,
Expand All @@ -62,7 +63,7 @@ func NewExportCommand() *cobra.Command {

l.Debug().Msgf("Export Subcommand Called.")

apiClient, err := initApiClient(cmd.Context(), cmd)
apiClient, err := initApiClient(cmd.Context())
if err != nil {
output.Format(cmd, output.CommandOutput{
Message: "Unable to initialize PingOne SDK client",
Expand All @@ -88,11 +89,18 @@ func NewExportCommand() *cobra.Command {
}

// Find the env ID to export. Default to worker env id if not provided by user.
var exportEnvID string
if viper.IsSet(pingoneExportEnvironmentIdParamConfigKey) {
exportEnvID = viper.GetString(pingoneExportEnvironmentIdParamConfigKey)
} else {
exportEnvID := viper.GetString(pingoneExportEnvironmentIdParamConfigKey)
if exportEnvID == "" {
exportEnvID = viper.GetString(pingoneWorkerEnvironmentIdParamConfigKey)

// if the exportEnvID is still empty, this is a problem. Return error.
if exportEnvID == "" {
output.Format(cmd, output.CommandOutput{
Message: "Failed to determine export environment ID",
Result: output.ENUMCOMMANDOUTPUTRESULT_FAILURE,
})
return fmt.Errorf("failed to determine export environment ID")
}
}

// Using the --service parameter(s) provided by user, build list of connectors to export
Expand Down Expand Up @@ -139,7 +147,7 @@ func NewExportCommand() *cobra.Command {

// Add flags that are bound to configuration file keys
cmd.Flags().String(pingoneWorkerEnvironmentIdParamName, "", "The ID of the PingOne environment that contains the worker token client used to authenticate.\nAlso configurable via environment variable PINGCTL_PINGONE_WORKER_ENVIRONMENT_ID")
cmd.Flags().String(pingoneExportEnvironmentIdParamName, "", "The ID of the PingOne environment to export. (Default: The PingOne worker environment ID)")
cmd.Flags().String(pingoneExportEnvironmentIdParamName, "", "The ID of the PingOne environment to export. (Default: The PingOne worker environment ID)\nAlso configurable via environment variable PINGCTL_PINGONE_EXPORT_ENVIRONMENT_ID")
cmd.Flags().String(pingoneWorkerClientIdParamName, "", "The ID of the worker app (also the client ID) used to authenticate.\nAlso configurable via environment variable PINGCTL_PINGONE_WORKER_CLIENT_ID")
cmd.Flags().String(pingoneWorkerClientSecretParamName, "", "The client secret of the worker app used to authenticate.\nAlso configurable via environment variable PINGCTL_PINGONE_WORKER_CLIENT_SECRET")
cmd.Flags().Var(&pingoneRegion, pingoneRegionParamName, fmt.Sprintf("The region of the service. Allowed: %q, %q, %q, %q\nAlso configurable via environment variable PINGCTL_PINGONE_REGION", connector.ENUMREGION_AP, connector.ENUMREGION_CA, connector.ENUMREGION_EU, connector.ENUMREGION_NA))
Expand All @@ -164,7 +172,7 @@ func init() {
l.Debug().Msgf("Initializing Export Subcommand...")
}

func initApiClient(ctx context.Context, cmd *cobra.Command) (*sdk.Client, error) {
func initApiClient(ctx context.Context) (*sdk.Client, error) {
l := logger.Get()

if apiClient != nil {
Expand Down
6 changes: 4 additions & 2 deletions cmd/platform/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

"github.com/pingidentity/pingctl/cmd"
"github.com/pingidentity/pingctl/internal/testutils"
)

// Test Platform Export Command Executes without issue
Expand All @@ -18,11 +19,12 @@ func TestPlatformExportCmd_Execute(t *testing.T) {
rootCmd.SetOut(&stdout)
rootCmd.SetErr(&stdout)

rootCmd.SetArgs([]string{"platform", "export", "--output-directory", os.Getenv("TMPDIR"), "--overwrite"})
rootCmd.SetArgs([]string{"platform", "export", "--output-directory", os.TempDir(), "--overwrite"})

// Execute the command
err := rootCmd.Execute()
if err != nil {
t.Fatalf("Export Command failed. Make sure to have PingOne env variables set if test is failing.\nErr: %q, Captured StdOut: %q", err, stdout.String())
testutils.PrintLogs(t)
t.Fatalf("Export Command failed. Make sure to have PingOne env variables set if test is failing.\nErr: %q, Captured StdOut: %s", err, stdout.String())
}
}
4 changes: 4 additions & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/pingidentity/pingctl/cmd"
"github.com/pingidentity/pingctl/internal/testutils"
)

// Test Root Command Executes without issue
Expand All @@ -20,6 +21,7 @@ func TestRootCmd_Execute(t *testing.T) {
// Execute the root command
err := rootCmd.Execute()
if err != nil {
testutils.PrintLogs(t)
t.Fatalf("Err: %q, Captured StdOut: %q", err, stdout.String())
}
}
Expand All @@ -37,6 +39,7 @@ func TestRootCmd_JSONOutput(t *testing.T) {
// Execute the root command
err := rootCmd.Execute()
if err != nil {
testutils.PrintLogs(t)
t.Fatal(err)
}

Expand All @@ -54,6 +57,7 @@ func TestRootCmd_JSONOutput(t *testing.T) {
// Execute the root command
err = rootCmd.Execute()
if err != nil {
testutils.PrintLogs(t)
t.Fatal(err)
}

Expand Down
11 changes: 11 additions & 0 deletions internal/connector/exportable_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package connector

import (
"context"
"regexp"
"strings"

sdk "github.com/patrickcping/pingone-go-sdk-v2/pingone"
)
Expand All @@ -23,3 +25,12 @@ type ExportableResource interface {
ExportAll() (*[]ImportBlock, error)
ResourceType() string
}

func (b *ImportBlock) Sanitize() {
// Replace spaces with underscores
b.ResourceName = strings.ReplaceAll(b.ResourceName, " ", "_")
// Remove all non-Alphanumeric characters/non-underscores
b.ResourceName = regexp.MustCompile(`[^a-zA-Z0-9_]+`).ReplaceAllString(b.ResourceName, "")
// Make everything lowercase
b.ResourceName = strings.ToLower(b.ResourceName)
}
28 changes: 19 additions & 9 deletions internal/connector/pingone_platform/pingone_platform_connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,16 @@ func (c *PingonePlatformConnector) Export(format, outputDir string, overwriteExp
}

exportableResources := []connector.ExportableResource{
resources.AgreementResource(&c.clientInfo),
resources.AgreementEnableResource(&c.clientInfo),
resources.AgreementLocalizationResource(&c.clientInfo),
resources.AgreementLocalizationEnableResource(&c.clientInfo),
resources.AgreementLocalizationRevisionResource(&c.clientInfo),
resources.BrandingSettingsResource(&c.clientInfo),
resources.BrandingThemeResource(&c.clientInfo),
resources.BrandingThemeDefaultResource(&c.clientInfo),
resources.CertificateResource(&c.clientInfo),
resources.Agreement(&c.clientInfo),
resources.AgreementEnable(&c.clientInfo),
resources.AgreementLocalization(&c.clientInfo),
resources.AgreementLocalizationEnable(&c.clientInfo),
resources.AgreementLocalizationRevision(&c.clientInfo),
resources.BrandingSettings(&c.clientInfo),
resources.BrandingTheme(&c.clientInfo),
resources.BrandingThemeDefault(&c.clientInfo),
resources.Certificate(&c.clientInfo),
resources.CustomDomain(&c.clientInfo),
}

for _, exportableResource := range exportableResources {
Expand All @@ -85,6 +86,12 @@ func (c *PingonePlatformConnector) Export(format, outputDir string, overwriteExp
return fmt.Errorf("failed to export resource %s. err: %s", exportableResource.ResourceType(), err.Error())
}

if len(*importBlocks) == 0 {
// No resources exported. Avoid creating an empty import.tf file
l.Debug().Msgf("Nothing exported for resource %s. Skipping import file generation...", exportableResource.ResourceType())
continue
}

l.Debug().Msgf("Generating import file for %s resource...", exportableResource.ResourceType())

outputFileName := fmt.Sprintf("%s.tf", exportableResource.ResourceType())
Expand All @@ -105,6 +112,9 @@ func (c *PingonePlatformConnector) Export(format, outputDir string, overwriteExp
defer outputFile.Close()

for _, importBlock := range *importBlocks {
// Sanitize import block "to". Make lowercase, remove special chars, convert space to underscore
importBlock.Sanitize()

switch format {
case connector.ENUMEXPORTFORMAT_HCL:
err := hclImportBlockTemplate.Execute(outputFile, importBlock)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type PingoneAgreementResource struct {
}

// Utility method for creating a PingoneAgreementResource
func AgreementResource(clientInfo *connector.SDKClientInfo) *PingoneAgreementResource {
func Agreement(clientInfo *connector.SDKClientInfo) *PingoneAgreementResource {
return &PingoneAgreementResource{
clientInfo: clientInfo,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type PingoneAgreementEnableResource struct {
}

// Utility method for creating a PingoneAgreementEnableResource
func AgreementEnableResource(clientInfo *connector.SDKClientInfo) *PingoneAgreementEnableResource {
func AgreementEnable(clientInfo *connector.SDKClientInfo) *PingoneAgreementEnableResource {
return &PingoneAgreementEnableResource{
clientInfo: clientInfo,
}
Expand All @@ -28,7 +28,7 @@ func (r *PingoneAgreementEnableResource) ExportAll() (*[]connector.ImportBlock,

l.Debug().Msgf("Fetching all pingone_agreement_enable resources...")

agreementImportBlocks, err := AgreementResource(r.clientInfo).ExportAll()
agreementImportBlocks, err := Agreement(r.clientInfo).ExportAll()
if err != nil {
return nil, err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type PingoneAgreementLocalizationResource struct {
}

// Utility method for creating a PingoneAgreementLocalizationResource
func AgreementLocalizationResource(clientInfo *connector.SDKClientInfo) *PingoneAgreementLocalizationResource {
func AgreementLocalization(clientInfo *connector.SDKClientInfo) *PingoneAgreementLocalizationResource {
return &PingoneAgreementLocalizationResource{
clientInfo: clientInfo,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type PingoneAgreementLocalizationEnableResource struct {
}

// Utility method for creating a PingoneAgreementLocalizationEnableResource
func AgreementLocalizationEnableResource(clientInfo *connector.SDKClientInfo) *PingoneAgreementLocalizationEnableResource {
func AgreementLocalizationEnable(clientInfo *connector.SDKClientInfo) *PingoneAgreementLocalizationEnableResource {
return &PingoneAgreementLocalizationEnableResource{
clientInfo: clientInfo,
}
Expand All @@ -28,7 +28,7 @@ func (r *PingoneAgreementLocalizationEnableResource) ExportAll() (*[]connector.I

l.Debug().Msgf("Fetching all pingone_agreement_localization_enable resources...")

localizationImportBlocks, err := AgreementLocalizationResource(r.clientInfo).ExportAll()
localizationImportBlocks, err := AgreementLocalization(r.clientInfo).ExportAll()
if err != nil {
return nil, err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type PingoneAgreementLocalizationRevisionResource struct {
}

// Utility method for creating a PingoneAgreementLocalizationRevisionResource
func AgreementLocalizationRevisionResource(clientInfo *connector.SDKClientInfo) *PingoneAgreementLocalizationRevisionResource {
func AgreementLocalizationRevision(clientInfo *connector.SDKClientInfo) *PingoneAgreementLocalizationRevisionResource {
return &PingoneAgreementLocalizationRevisionResource{
clientInfo: clientInfo,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type PingoneBrandingSettingsResource struct {
}

// Utility method for creating a PingoneBrandingSettingsResource
func BrandingSettingsResource(clientInfo *connector.SDKClientInfo) *PingoneBrandingSettingsResource {
func BrandingSettings(clientInfo *connector.SDKClientInfo) *PingoneBrandingSettingsResource {
return &PingoneBrandingSettingsResource{
clientInfo: clientInfo,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type PingoneBrandingThemeResource struct {
}

// Utility method for creating a PingoneBrandingThemeResource
func BrandingThemeResource(clientInfo *connector.SDKClientInfo) *PingoneBrandingThemeResource {
func BrandingTheme(clientInfo *connector.SDKClientInfo) *PingoneBrandingThemeResource {
return &PingoneBrandingThemeResource{
clientInfo: clientInfo,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type PingoneBrandingThemeDefaultResource struct {
}

// Utility method for creating a PingoneBrandingThemeDefaultResource
func BrandingThemeDefaultResource(clientInfo *connector.SDKClientInfo) *PingoneBrandingThemeDefaultResource {
func BrandingThemeDefault(clientInfo *connector.SDKClientInfo) *PingoneBrandingThemeDefaultResource {
return &PingoneBrandingThemeDefaultResource{
clientInfo: clientInfo,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type PingoneCertificateResource struct {
}

// Utility method for creating a PingoneCertificateResource
func CertificateResource(clientInfo *connector.SDKClientInfo) *PingoneCertificateResource {
func Certificate(clientInfo *connector.SDKClientInfo) *PingoneCertificateResource {
return &PingoneCertificateResource{
clientInfo: clientInfo,
}
Expand Down
Loading

0 comments on commit 86430aa

Please sign in to comment.