Skip to content

Commit

Permalink
PDI-1511: Add pingone_branding_settings (Resource) Export (#44)
Browse files Browse the repository at this point in the history
* PDI-1511: Add pingone_branding_settings (Resource) Export

* Allow export env id specification. Validate env id. Do not source env id from API response data.

* Provide a 404 specific error message when export env id does not exist.
  • Loading branch information
erikostien-pingidentity authored Feb 26, 2024
1 parent ac9588c commit 87d48f6
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 52 deletions.
27 changes: 25 additions & 2 deletions cmd/platform/custom_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ type MultiService struct {

type ExportFormat string

type PingOneRegion string

// Verify that the custom type satisfies the pflag.Value interface
var (
_ pflag.Value = (*MultiService)(nil)
_ pflag.Value = (*ExportFormat)(nil)
_ pflag.Value = (*PingOneRegion)(nil)
)

// Implement pflag.Value interface for custom type in cobra service parameter
Expand All @@ -34,7 +37,7 @@ func (s *MultiService) Set(service string) error {
}
*s.services = append(*s.services, service)
default:
return fmt.Errorf("unrecognized service %q", service)
return fmt.Errorf("unrecognized service %q. Must be one of: %q", service, serviceEnumPlatform)
}
return nil
}
Expand All @@ -54,7 +57,7 @@ func (s *ExportFormat) Set(format string) error {
case connector.ENUMEXPORTFORMAT_HCL:
*s = ExportFormat(format)
default:
return fmt.Errorf("unrecognized export format %q", format)
return fmt.Errorf("unrecognized export format %q. Must be one of: %q", format, connector.ENUMEXPORTFORMAT_HCL)
}
return nil
}
Expand All @@ -66,3 +69,23 @@ func (s *ExportFormat) Type() string {
func (s *ExportFormat) String() string {
return string(*s)
}

// Implement pflag.Value interface for custom type in cobra pingone-region parameter

func (s *PingOneRegion) Set(region string) error {
switch region {
case connector.ENUMREGION_AP, connector.ENUMREGION_CA, connector.ENUMREGION_EU, connector.ENUMREGION_NA:
*s = PingOneRegion(region)
default:
return fmt.Errorf("unrecognized PingOne Region: %q. Must be one of: %q, %q, %q, %q", region, connector.ENUMREGION_AP, connector.ENUMREGION_CA, connector.ENUMREGION_EU, connector.ENUMREGION_NA)
}
return nil
}

func (s *PingOneRegion) Type() string {
return "string"
}

func (s *PingOneRegion) String() string {
return string(*s)
}
33 changes: 20 additions & 13 deletions cmd/platform/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import (
)

const (
pingoneExportEnvironmentIdParamName = "pingone-export-environment-id"
pingoneExportEnvironmentIdParamConfigKey = "pingone.export-environment-id"

pingoneWorkerEnvironmentIdParamName = "pingone-worker-environment-id"
pingoneWorkerEnvironmentIdParamConfigKey = "pingone.worker-environment-id"

Expand All @@ -35,6 +38,7 @@ var (
serviceEnumPlatform,
},
}
pingoneRegion PingOneRegion
outputDir string
overwriteExport bool
apiClient *sdk.Client
Expand Down Expand Up @@ -83,12 +87,20 @@ func NewExportCommand() *cobra.Command {
outputDir = pwd
}

// 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(pingoneWorkerEnvironmentIdParamConfigKey)
}

// Using the --service parameter(s) provided by user, build list of connectors to export
exportableConnectors := []connector.Exportable{}
for _, service := range *multiService.services {
switch service {
case serviceEnumPlatform:
exportableConnectors = append(exportableConnectors, pingone_platform.Connector(cmd.Context(), apiClient, viper.GetString(pingoneWorkerEnvironmentIdParamConfigKey)))
exportableConnectors = append(exportableConnectors, pingone_platform.Connector(cmd.Context(), apiClient, exportEnvID))
// default:
// This unrecognized service condition is handled by cobra with the custom type MultiService
}
Expand Down Expand Up @@ -127,9 +139,10 @@ 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(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().String(pingoneRegionParamName, "", "The region code of the service (NA, EU, AP, CA).\nAlso configurable via environment variable PINGCTL_PINGONE_REGION")
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))

cmd.MarkFlagsRequiredTogether(pingoneWorkerEnvironmentIdParamName, pingoneWorkerClientIdParamName, pingoneWorkerClientSecretParamName, pingoneRegionParamName)

Expand Down Expand Up @@ -170,19 +183,13 @@ func initApiClient(ctx context.Context, cmd *cobra.Command) (*sdk.Client, error)
clientID := viper.GetString(pingoneWorkerClientIdParamConfigKey)
clientSecret := viper.GetString(pingoneWorkerClientSecretParamConfigKey)
environmentID := viper.GetString(pingoneWorkerEnvironmentIdParamConfigKey)
region := viper.GetString(pingoneRegionParamConfigKey)

var region string
switch viper.GetString(pingoneRegionParamConfigKey) {
case "NA":
region = "NorthAmerica"
case "EU":
region = "Europe"
case "AP":
region = "AsiaPacific"
case "CA":
region = "Canada"
switch region {
case connector.ENUMREGION_AP, connector.ENUMREGION_CA, connector.ENUMREGION_EU, connector.ENUMREGION_NA:
l.Debug().Msgf("PingOne region %q validated.", region)
default:
return nil, fmt.Errorf("provided pingone region code not recognized: %s", viper.GetString(pingoneRegionParamConfigKey))
return nil, fmt.Errorf("unrecognized PingOne Region: %q. Must be one of: %q, %q, %q, %q", region, connector.ENUMREGION_AP, connector.ENUMREGION_CA, connector.ENUMREGION_EU, connector.ENUMREGION_NA)
}

apiConfig := &sdk.Config{
Expand Down
5 changes: 5 additions & 0 deletions internal/connector/exportable.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import (

const (
ENUMEXPORTFORMAT_HCL = "HCL"

ENUMREGION_AP = "AsiaPacific"
ENUMREGION_CA = "Canada"
ENUMREGION_EU = "Europe"
ENUMREGION_NA = "NorthAmerica"
)

// Embed import block template needed for export generation
Expand Down
6 changes: 3 additions & 3 deletions internal/connector/exportable_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ type ImportBlock struct {
}

type SDKClientInfo struct {
Context context.Context
ApiClient *sdk.Client
EnvironmentID string
Context context.Context
ApiClient *sdk.Client
ExportEnvironmentID string
}

// A connector that allows exporting configuration
Expand Down
28 changes: 24 additions & 4 deletions internal/connector/pingone_platform/pingone_platform_connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,38 @@ type PingonePlatformConnector struct {
}

// Utility method for creating a PingonePlatformConnector
func Connector(ctx context.Context, apiClient *sdk.Client, environmentID string) *PingonePlatformConnector {
func Connector(ctx context.Context, apiClient *sdk.Client, exportEnvironmentID string) *PingonePlatformConnector {
return &PingonePlatformConnector{
clientInfo: connector.SDKClientInfo{
Context: ctx,
ApiClient: apiClient,
EnvironmentID: environmentID,
Context: ctx,
ApiClient: apiClient,
ExportEnvironmentID: exportEnvironmentID,
},
}
}

func (c *PingonePlatformConnector) Export(format, outputDir string, overwriteExport bool) error {
l := logger.Get()

l.Debug().Msgf("Validating export environment ID...")

environment, response, err := c.clientInfo.ApiClient.ManagementAPIClient.EnvironmentsApi.ReadOneEnvironment(c.clientInfo.Context, c.clientInfo.ExportEnvironmentID).Execute()
defer response.Body.Close()
if err != nil {
l.Error().Err(err).Msgf("ReadOneEnvironment Response Code: %s\nResponse Body: %s", response.Status, response.Body)
return err
}

if environment == nil {
l.Error().Msgf("Returned ReadOneEnvironment() environment is nil.")
l.Error().Msgf("ReadOneEnvironment Response Code: %s\nResponse Body: %s", response.Status, response.Body)
if response.StatusCode == 404 {
return fmt.Errorf("failed to fetch environment. the provided environment id %q does not exist", c.clientInfo.ExportEnvironmentID)
} else {
return fmt.Errorf("failed to fetch environment %q via ReadOneEnvironment()", c.clientInfo.ExportEnvironmentID)
}
}

l.Debug().Msgf("Exporting all PingOne Platform Resources...")

hclImportBlockTemplate, err := template.New("HCLImportBlock").Parse(connector.HCLImportBlockTemplate)
Expand All @@ -54,6 +73,7 @@ func (c *PingonePlatformConnector) Export(format, outputDir string, overwriteExp
resources.AgreementLocalizationResource(&c.clientInfo),
resources.AgreementLocalizationEnableResource(&c.clientInfo),
resources.AgreementLocalizationRevisionResource(&c.clientInfo),
resources.BrandingSettingsResource(&c.clientInfo),
}

for _, exportableResource := range exportableResources {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (r *PingoneAgreementResource) ExportAll() (*[]connector.ImportBlock, error)

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

entityArray, response, err := r.clientInfo.ApiClient.ManagementAPIClient.AgreementsResourcesApi.ReadAllAgreements(r.clientInfo.Context, r.clientInfo.EnvironmentID).Execute()
entityArray, response, err := r.clientInfo.ApiClient.ManagementAPIClient.AgreementsResourcesApi.ReadAllAgreements(r.clientInfo.Context, r.clientInfo.ExportEnvironmentID).Execute()
defer response.Body.Close()
if err != nil {
l.Error().Err(err).Msgf("ReadAllAgreements Response Code: %s\nResponse Body: %s", response.Status, response.Body)
Expand All @@ -54,18 +54,12 @@ func (r *PingoneAgreementResource) ExportAll() (*[]connector.ImportBlock, error)
for _, agreement := range embedded.GetAgreements() {
agreementId, agreementIdOk := agreement.GetIdOk()
agreementName, agreementNameOk := agreement.GetNameOk()
agreementEnvironment, agreementEnvironmentOk := agreement.GetEnvironmentOk()
var agreementEnvironmentId *string
var agreementEnvironmentIdOk = false
if agreementEnvironmentOk {
agreementEnvironmentId, agreementEnvironmentIdOk = agreementEnvironment.GetIdOk()
}

if agreementIdOk && agreementNameOk && agreementEnvironmentOk && agreementEnvironmentIdOk {
if agreementIdOk && agreementNameOk {
importBlocks = append(importBlocks, connector.ImportBlock{
ResourceType: r.ResourceType(),
ResourceName: *agreementName,
ResourceID: fmt.Sprintf("%s/%s", *agreementEnvironmentId, *agreementId),
ResourceID: fmt.Sprintf("%s/%s", r.clientInfo.ExportEnvironmentID, *agreementId),
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (r *PingoneAgreementLocalizationResource) ExportAll() (*[]connector.ImportB

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

agreementEntityArray, response, err := r.clientInfo.ApiClient.ManagementAPIClient.AgreementsResourcesApi.ReadAllAgreements(r.clientInfo.Context, r.clientInfo.EnvironmentID).Execute()
agreementEntityArray, response, err := r.clientInfo.ApiClient.ManagementAPIClient.AgreementsResourcesApi.ReadAllAgreements(r.clientInfo.Context, r.clientInfo.ExportEnvironmentID).Execute()
defer response.Body.Close()
if err != nil {
l.Error().Err(err).Msgf("ReadAllAgreements Response Code: %s\nResponse Body: %s", response.Status, response.Body)
Expand All @@ -54,15 +54,9 @@ func (r *PingoneAgreementLocalizationResource) ExportAll() (*[]connector.ImportB
for _, agreement := range agreementEmbedded.GetAgreements() {
agreementId, agreementIdOk := agreement.GetIdOk()
agreementName, agreementNameOk := agreement.GetNameOk()
agreementEnvironment, agreementEnvironmentOk := agreement.GetEnvironmentOk()
var agreementEnvironmentId *string
var agreementEnvironmentIdOk = false
if agreementEnvironmentOk {
agreementEnvironmentId, agreementEnvironmentIdOk = agreementEnvironment.GetIdOk()
}

if agreementIdOk && agreementNameOk && agreementEnvironmentOk && agreementEnvironmentIdOk {
agreementLanguageEntityArray, response, err := r.clientInfo.ApiClient.ManagementAPIClient.AgreementLanguagesResourcesApi.ReadAllAgreementLanguages(r.clientInfo.Context, r.clientInfo.EnvironmentID, *agreement.Id).Execute()
if agreementIdOk && agreementNameOk {
agreementLanguageEntityArray, response, err := r.clientInfo.ApiClient.ManagementAPIClient.AgreementLanguagesResourcesApi.ReadAllAgreementLanguages(r.clientInfo.Context, r.clientInfo.ExportEnvironmentID, *agreement.Id).Execute()
defer response.Body.Close()
if err != nil {
l.Error().Err(err).Msgf("ReadAllAgreementLanguages Response Code: %s\nResponse Body: %s", response.Status, response.Body)
Expand Down Expand Up @@ -93,7 +87,7 @@ func (r *PingoneAgreementLocalizationResource) ExportAll() (*[]connector.ImportB
importBlocks = append(importBlocks, connector.ImportBlock{
ResourceType: r.ResourceType(),
ResourceName: fmt.Sprintf("%s_%s", *agreementName, *agreementLanguageLocale),
ResourceID: fmt.Sprintf("%s/%s/%s", *agreementEnvironmentId, *agreementId, *agreementLanguageId),
ResourceID: fmt.Sprintf("%s/%s/%s", r.clientInfo.ExportEnvironmentID, *agreementId, *agreementLanguageId),
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (r *PingoneAgreementLocalizationRevisionResource) ExportAll() (*[]connector

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

agreementEntityArray, response, err := r.clientInfo.ApiClient.ManagementAPIClient.AgreementsResourcesApi.ReadAllAgreements(r.clientInfo.Context, r.clientInfo.EnvironmentID).Execute()
agreementEntityArray, response, err := r.clientInfo.ApiClient.ManagementAPIClient.AgreementsResourcesApi.ReadAllAgreements(r.clientInfo.Context, r.clientInfo.ExportEnvironmentID).Execute()
defer response.Body.Close()
if err != nil {
l.Error().Err(err).Msgf("ReadAllAgreements Response Code: %s\nResponse Body: %s", response.Status, response.Body)
Expand All @@ -54,15 +54,9 @@ func (r *PingoneAgreementLocalizationRevisionResource) ExportAll() (*[]connector
for _, agreement := range agreementEmbedded.GetAgreements() {
agreementId, agreementIdOk := agreement.GetIdOk()
agreementName, agreementNameOk := agreement.GetNameOk()
agreementEnvironment, agreementEnvironmentOk := agreement.GetEnvironmentOk()
var agreementEnvironmentId *string
var agreementEnvironmentIdOk = false
if agreementEnvironmentOk {
agreementEnvironmentId, agreementEnvironmentIdOk = agreementEnvironment.GetIdOk()
}

if agreementIdOk && agreementNameOk && agreementEnvironmentOk && agreementEnvironmentIdOk {
agreementLanguageEntityArray, response, err := r.clientInfo.ApiClient.ManagementAPIClient.AgreementLanguagesResourcesApi.ReadAllAgreementLanguages(r.clientInfo.Context, r.clientInfo.EnvironmentID, *agreement.Id).Execute()
if agreementIdOk && agreementNameOk {
agreementLanguageEntityArray, response, err := r.clientInfo.ApiClient.ManagementAPIClient.AgreementLanguagesResourcesApi.ReadAllAgreementLanguages(r.clientInfo.Context, r.clientInfo.ExportEnvironmentID, *agreement.Id).Execute()
defer response.Body.Close()
if err != nil {
l.Error().Err(err).Msgf("ReadAllAgreementLanguages Response Code: %s\nResponse Body: %s", response.Status, response.Body)
Expand Down Expand Up @@ -90,7 +84,7 @@ func (r *PingoneAgreementLocalizationRevisionResource) ExportAll() (*[]connector
agreementLanguageId, agreementLanguageIdOk := agreementLanguage.GetIdOk()

if agreementLanguageLocaleOk && agreementLanguageIdOk {
agreementLanguageRevisionEntityArray, response, err := r.clientInfo.ApiClient.ManagementAPIClient.AgreementRevisionsResourcesApi.ReadAllAgreementLanguageRevisions(r.clientInfo.Context, r.clientInfo.EnvironmentID, *agreementId, *agreementLanguageId).Execute()
agreementLanguageRevisionEntityArray, response, err := r.clientInfo.ApiClient.ManagementAPIClient.AgreementRevisionsResourcesApi.ReadAllAgreementLanguageRevisions(r.clientInfo.Context, r.clientInfo.ExportEnvironmentID, *agreementId, *agreementLanguageId).Execute()
defer response.Body.Close()
if err != nil {
l.Error().Err(err).Msgf("ReadAllAgreementLanguageRevisions Response Code: %s\nResponse Body: %s", response.Status, response.Body)
Expand All @@ -117,7 +111,7 @@ func (r *PingoneAgreementLocalizationRevisionResource) ExportAll() (*[]connector
importBlocks = append(importBlocks, connector.ImportBlock{
ResourceType: r.ResourceType(),
ResourceName: fmt.Sprintf("%s_%s_%d", *agreementName, *agreementLanguageLocale, (revisionIndex + 1)),
ResourceID: fmt.Sprintf("%s/%s/%s/%s", *agreementEnvironmentId, *agreementId, *agreementLanguageId, *revisionId),
ResourceID: fmt.Sprintf("%s/%s/%s/%s", r.clientInfo.ExportEnvironmentID, *agreementId, *agreementLanguageId, *revisionId),
})
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package resources

import (
"github.com/pingidentity/pingctl/internal/connector"
"github.com/pingidentity/pingctl/internal/logger"
)

// Verify that the resource satisfies the exportable resource interface
var (
_ connector.ExportableResource = &PingoneBrandingSettingsResource{}
)

type PingoneBrandingSettingsResource struct {
clientInfo *connector.SDKClientInfo
}

// Utility method for creating a PingoneAgreementLocalizationRevisionResource
func BrandingSettingsResource(clientInfo *connector.SDKClientInfo) *PingoneBrandingSettingsResource {
return &PingoneBrandingSettingsResource{
clientInfo: clientInfo,
}
}

func (r *PingoneBrandingSettingsResource) ExportAll() (*[]connector.ImportBlock, error) {
l := logger.Get()

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

importBlocks := []connector.ImportBlock{}

importBlocks = append(importBlocks, connector.ImportBlock{
ResourceType: r.ResourceType(),
ResourceName: "branding",
ResourceID: r.clientInfo.ExportEnvironmentID,
})

return &importBlocks, nil
}

func (r *PingoneBrandingSettingsResource) ResourceType() string {
return "pingone_branding_settings"
}

0 comments on commit 87d48f6

Please sign in to comment.