Skip to content

Commit

Permalink
Merge pull request #4036 from stevekuznetsov/skuznets/expose-cli-output
Browse files Browse the repository at this point in the history
HOSTEDCP-1542: cmd: add an option to render into a file, use it in e2e
  • Loading branch information
openshift-merge-bot[bot] authored Jun 4, 2024
2 parents cd2d529 + eef4a0a commit e66add8
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 107 deletions.
66 changes: 40 additions & 26 deletions cmd/cluster/aws/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,20 +125,7 @@ func applyPlatformSpecificsValues(ctx context.Context, exampleOptions *apifixtur
}
}
if infra == nil {
opt := awsinfra.CreateInfraOptions{
Region: opts.AWSPlatform.Region,
InfraID: opts.InfraID,
AWSCredentialsOpts: opts.AWSPlatform.AWSCredentialsOpts,
Name: opts.Name,
BaseDomain: opts.BaseDomain,
BaseDomainPrefix: opts.BaseDomainPrefix,
AdditionalTags: opts.AWSPlatform.AdditionalTags,
Zones: opts.AWSPlatform.Zones,
EnableProxy: opts.AWSPlatform.EnableProxy,
SSHKeyFile: opts.SSHKeyFile,
SingleNATGateway: opts.AWSPlatform.SingleNATGateway,
CredentialsSecretData: secretData,
}
opt := CreateInfraOptions(opts)
infra, err = opt.CreateInfra(ctx, opts.Log)
if err != nil {
return fmt.Errorf("failed to create infra: %w", err)
Expand All @@ -156,18 +143,7 @@ func applyPlatformSpecificsValues(ctx context.Context, exampleOptions *apifixtur
return fmt.Errorf("failed to load infra json: %w", err)
}
} else {
opt := awsinfra.CreateIAMOptions{
Region: opts.AWSPlatform.Region,
AWSCredentialsOpts: opts.AWSPlatform.AWSCredentialsOpts,
InfraID: infra.InfraID,
IssuerURL: opts.AWSPlatform.IssuerURL,
AdditionalTags: opts.AWSPlatform.AdditionalTags,
PrivateZoneID: infra.PrivateZoneID,
PublicZoneID: infra.PublicZoneID,
LocalZoneID: infra.LocalZoneID,
KMSKeyARN: opts.AWSPlatform.EtcdKMSKeyARN,
CredentialsSecretData: secretData,
}
opt := CreateIAMOptions(opts, infra)
iamInfo, err = opt.CreateIAM(ctx, client)
if err != nil {
return fmt.Errorf("failed to create iam: %w", err)
Expand Down Expand Up @@ -236,6 +212,44 @@ func applyPlatformSpecificsValues(ctx context.Context, exampleOptions *apifixtur
return nil
}

func CreateInfraOptions(opts *core.CreateOptions) awsinfra.CreateInfraOptions {
return awsinfra.CreateInfraOptions{
Region: opts.AWSPlatform.Region,
InfraID: opts.InfraID,
AWSCredentialsOpts: opts.AWSPlatform.AWSCredentialsOpts,
Name: opts.Name,
BaseDomain: opts.BaseDomain,
BaseDomainPrefix: opts.BaseDomainPrefix,
AdditionalTags: opts.AWSPlatform.AdditionalTags,
Zones: opts.AWSPlatform.Zones,
EnableProxy: opts.AWSPlatform.EnableProxy,
SSHKeyFile: opts.SSHKeyFile,
SingleNATGateway: opts.AWSPlatform.SingleNATGateway,
}
}

func CreateIAMOptions(opts *core.CreateOptions, infra *awsinfra.CreateInfraOutput) awsinfra.CreateIAMOptions {
return awsinfra.CreateIAMOptions{
Region: opts.AWSPlatform.Region,
AWSCredentialsOpts: opts.AWSPlatform.AWSCredentialsOpts,
InfraID: infra.InfraID,
IssuerURL: opts.AWSPlatform.IssuerURL,
AdditionalTags: opts.AWSPlatform.AdditionalTags,
PrivateZoneID: infra.PrivateZoneID,
PublicZoneID: infra.PublicZoneID,
LocalZoneID: infra.LocalZoneID,
KMSKeyARN: opts.AWSPlatform.EtcdKMSKeyARN,
}
}

// IsRequiredOption returns a cobra style error message when the flag value is empty
func IsRequiredOption(flag string, value string) error {
if len(value) == 0 {
return fmt.Errorf("required flag(s) \"%s\" not set", flag)
}
return nil
}

// ValidateCreateCredentialInfo validates if the credentials secret name is empty that the aws-creds and pull-secret flags are
// not empty; validates if the credentials secret is not empty, that it can be retrieved
func ValidateCreateCredentialInfo(opts awsutil.AWSCredentialsOptions, credentialSecretName, namespace, pullSecretFile string) error {
Expand Down
39 changes: 24 additions & 15 deletions cmd/cluster/azure/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,24 +84,12 @@ func applyPlatformSpecificsValues(ctx context.Context, exampleOptions *apifixtur
return fmt.Errorf("failed to deserialize infra json file: %w", err)
}
} else {
rhcosImage, err := lookupRHCOSImage(ctx, opts.Arch, opts.ReleaseImage, opts.PullSecretFile)
infraOpts, err := CreateInfraOptions(ctx, opts)
if err != nil {
return fmt.Errorf("failed to retrieve RHCOS image: %w", err)
return err
}

infra, err = (&azureinfra.CreateInfraOptions{
Name: opts.Name,
Location: opts.AzurePlatform.Location,
InfraID: opts.InfraID,
CredentialsFile: opts.AzurePlatform.CredentialsFile,
BaseDomain: opts.BaseDomain,
RHCOSImage: rhcosImage,
VnetID: opts.AzurePlatform.VnetID,
ResourceGroupName: opts.AzurePlatform.ResourceGroupName,
NetworkSecurityGroupID: opts.AzurePlatform.NetworkSecurityGroupID,
ResourceGroupTags: opts.AzurePlatform.ResourceGroupTags,
SubnetID: opts.AzurePlatform.SubnetID,
}).Run(ctx, opts.Log)
infra, err = infraOpts.Run(ctx, opts.Log)
if err != nil {
return fmt.Errorf("failed to create infra: %w", err)
}
Expand Down Expand Up @@ -156,6 +144,27 @@ func applyPlatformSpecificsValues(ctx context.Context, exampleOptions *apifixtur
return nil
}

func CreateInfraOptions(ctx context.Context, opts *core.CreateOptions) (azureinfra.CreateInfraOptions, error) {
rhcosImage, err := lookupRHCOSImage(ctx, opts.Arch, opts.ReleaseImage, opts.PullSecretFile)
if err != nil {
return azureinfra.CreateInfraOptions{}, fmt.Errorf("failed to retrieve RHCOS image: %w", err)
}

return azureinfra.CreateInfraOptions{
Name: opts.Name,
Location: opts.AzurePlatform.Location,
InfraID: opts.InfraID,
CredentialsFile: opts.AzurePlatform.CredentialsFile,
BaseDomain: opts.BaseDomain,
RHCOSImage: rhcosImage,
VnetID: opts.AzurePlatform.VnetID,
ResourceGroupName: opts.AzurePlatform.ResourceGroupName,
NetworkSecurityGroupID: opts.AzurePlatform.NetworkSecurityGroupID,
ResourceGroupTags: opts.AzurePlatform.ResourceGroupTags,
SubnetID: opts.AzurePlatform.SubnetID,
}, nil
}

// lookupRHCOSImage looks up a release image and extracts the RHCOS VHD image based on the nodepool arch
func lookupRHCOSImage(ctx context.Context, arch string, image string, pullSecretFile string) (string, error) {
rhcosImage := ""
Expand Down
3 changes: 2 additions & 1 deletion cmd/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ func NewCreateCommands() *cobra.Command {
cmd.PersistentFlags().StringVar(&opts.ReleaseStream, "release-stream", opts.ReleaseStream, "The OCP release stream for the cluster (e.g. 4.15.0-0.nightly), this flag is ignored if release-image is set")
cmd.PersistentFlags().StringVar(&opts.PullSecretFile, "pull-secret", opts.PullSecretFile, "File path to a pull secret.")
cmd.PersistentFlags().StringVar(&opts.ControlPlaneAvailabilityPolicy, "control-plane-availability-policy", opts.ControlPlaneAvailabilityPolicy, "Availability policy for hosted cluster components. Supported options: SingleReplica, HighlyAvailable")
cmd.PersistentFlags().BoolVar(&opts.Render, "render", opts.Render, "Render output as YAML to stdout instead of applying")
cmd.PersistentFlags().BoolVar(&opts.Render, "render", opts.Render, "Render output as YAML instead of applying")
cmd.PersistentFlags().StringVar(&opts.RenderInto, "render-into", opts.RenderInto, "Render output as YAML into this file instead of applying. If unset, YAML will be output to stdout.")
cmd.PersistentFlags().StringVar(&opts.ControlPlaneOperatorImage, "control-plane-operator-image", opts.ControlPlaneOperatorImage, "Override the default image used to deploy the control plane operator")
cmd.PersistentFlags().StringVar(&opts.SSHKeyFile, "ssh-key", opts.SSHKeyFile, "Path to an SSH key file")
cmd.PersistentFlags().StringVar(&opts.AdditionalTrustBundle, "additional-trust-bundle", opts.AdditionalTrustBundle, "Path to a file with user CA bundle")
Expand Down
26 changes: 21 additions & 5 deletions cmd/cluster/core/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type CreateOptions struct {
ReleaseImage string
ReleaseStream string
Render bool
RenderInto string
SSHKeyFile string
ServiceCIDR []string
ClusterCIDR []string
Expand Down Expand Up @@ -476,7 +477,7 @@ func GetAPIServerAddressByNode(ctx context.Context, l logr.Logger) (string, erro
}

func Validate(ctx context.Context, opts *CreateOptions) error {
if !opts.Render {
if !opts.Render && opts.RenderInto != "" {
client, err := util.GetClient()
if err != nil {
return err
Expand All @@ -499,7 +500,7 @@ func Validate(ctx context.Context, opts *CreateOptions) error {

// Validate if mgmt cluster and NodePool CPU arches don't match, a multi-arch release image or stream was used
// Exception for ppc64le arch since management cluster would be in x86 and node pools are going to be in ppc64le arch
if !opts.AWSPlatform.MultiArch && !opts.Render && opts.Arch != hyperv1.ArchitecturePPC64LE {
if !opts.AWSPlatform.MultiArch && (!opts.Render || opts.RenderInto != "") && opts.Arch != hyperv1.ArchitecturePPC64LE {
mgmtClusterCPUArch, err := hyperutil.GetMgmtClusterCPUArch(ctx)
if err != nil {
return err
Expand Down Expand Up @@ -539,13 +540,28 @@ func CreateCluster(ctx context.Context, opts *CreateOptions, platformSpecificApp
}

// In render mode, print the objects and return early
if opts.Render {
if opts.Render || opts.RenderInto != "" {
output := os.Stdout
if opts.RenderInto != "" {
var err error
output, err = os.Create(opts.RenderInto)
if err != nil {
return fmt.Errorf("failed to create file for rendering output: %w", err)
}
defer func() {
if err := output.Close(); err != nil {
fmt.Printf("failed to close file for rendering output: %v\n", err)
}
}()
}
for _, object := range exampleOptions.Resources().AsObjects() {
err := hyperapi.YamlSerializer.Encode(object, os.Stdout)
err := hyperapi.YamlSerializer.Encode(object, output)
if err != nil {
return fmt.Errorf("failed to encode objects: %w", err)
}
fmt.Println("---")
if _, err := fmt.Fprintln(output, "---"); err != nil {
return fmt.Errorf("failed to write object separator: %w", err)
}
}
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/cluster/kubevirt/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func ApplyPlatformSpecificsValues(ctx context.Context, exampleOptions *apifixtur
if opts.KubevirtPlatform.ServicePublishingStrategy != NodePortServicePublishingStrategy && opts.KubevirtPlatform.APIServerAddress != "" {
return fmt.Errorf("external-api-server-address is supported only for NodePort service publishing strategy, service publishing strategy %s is used", opts.KubevirtPlatform.ServicePublishingStrategy)
}
if opts.KubevirtPlatform.APIServerAddress == "" && opts.KubevirtPlatform.ServicePublishingStrategy == NodePortServicePublishingStrategy && !opts.Render {
if opts.KubevirtPlatform.APIServerAddress == "" && opts.KubevirtPlatform.ServicePublishingStrategy == NodePortServicePublishingStrategy && (!opts.Render || opts.RenderInto != "") {
if opts.KubevirtPlatform.APIServerAddress, err = core.GetAPIServerAddressByNode(ctx, opts.Log); err != nil {
return err
}
Expand Down
58 changes: 31 additions & 27 deletions cmd/cluster/powervs/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,33 +120,8 @@ func applyPlatformSpecificsValues(ctx context.Context, exampleOptions *apifixtur
}

if infra == nil {
opt := &powervsinfra.CreateInfraOptions{
Name: opts.Name,
Namespace: opts.Namespace,
BaseDomain: opts.BaseDomain,
ResourceGroup: opts.PowerVSPlatform.ResourceGroup,
InfraID: opts.InfraID,
OutputFile: opts.InfrastructureJSON,
Region: opts.PowerVSPlatform.Region,
Zone: opts.PowerVSPlatform.Zone,
CloudInstanceID: opts.PowerVSPlatform.CloudInstanceID,
CloudConnection: opts.PowerVSPlatform.CloudConnection,
VPCRegion: opts.PowerVSPlatform.VPCRegion,
VPC: opts.PowerVSPlatform.VPC,
Debug: opts.PowerVSPlatform.Debug,
RecreateSecrets: opts.PowerVSPlatform.RecreateSecrets,
PER: opts.PowerVSPlatform.PER,
TransitGatewayLocation: opts.PowerVSPlatform.TransitGatewayLocation,
TransitGateway: opts.PowerVSPlatform.TransitGateway,
}
infra = &powervsinfra.Infra{
ID: opts.InfraID,
BaseDomain: opts.BaseDomain,
ResourceGroup: opts.PowerVSPlatform.ResourceGroup,
Region: opts.PowerVSPlatform.Region,
Zone: opts.PowerVSPlatform.Zone,
VPCRegion: opts.PowerVSPlatform.VPCRegion,
}
var opt *powervsinfra.CreateInfraOptions
opt, infra = CreateInfraOptions(opts)
err = infra.SetupInfra(ctx, opt)
if err != nil {
return fmt.Errorf("failed to create infra: %w", err)
Expand Down Expand Up @@ -188,3 +163,32 @@ func applyPlatformSpecificsValues(ctx context.Context, exampleOptions *apifixtur

return nil
}

func CreateInfraOptions(opts *core.CreateOptions) (*powervsinfra.CreateInfraOptions, *powervsinfra.Infra) {
return &powervsinfra.CreateInfraOptions{
Name: opts.Name,
Namespace: opts.Namespace,
BaseDomain: opts.BaseDomain,
ResourceGroup: opts.PowerVSPlatform.ResourceGroup,
InfraID: opts.InfraID,
OutputFile: opts.InfrastructureJSON,
Region: opts.PowerVSPlatform.Region,
Zone: opts.PowerVSPlatform.Zone,
CloudInstanceID: opts.PowerVSPlatform.CloudInstanceID,
CloudConnection: opts.PowerVSPlatform.CloudConnection,
VPCRegion: opts.PowerVSPlatform.VPCRegion,
VPC: opts.PowerVSPlatform.VPC,
Debug: opts.PowerVSPlatform.Debug,
RecreateSecrets: opts.PowerVSPlatform.RecreateSecrets,
PER: opts.PowerVSPlatform.PER,
TransitGatewayLocation: opts.PowerVSPlatform.TransitGatewayLocation,
TransitGateway: opts.PowerVSPlatform.TransitGateway,
}, &powervsinfra.Infra{
ID: opts.InfraID,
BaseDomain: opts.BaseDomain,
ResourceGroup: opts.PowerVSPlatform.ResourceGroup,
Region: opts.PowerVSPlatform.Region,
Zone: opts.PowerVSPlatform.Zone,
VPCRegion: opts.PowerVSPlatform.VPCRegion,
}
}
5 changes: 5 additions & 0 deletions cmd/infra/aws/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ func (o *CreateInfraOptions) Run(ctx context.Context, l logr.Logger) error {
if err != nil {
return err
}
return o.Output(result)
}

func (o *CreateInfraOptions) Output(result *CreateInfraOutput) error {
// Write out stateful information
out := os.Stdout
if len(o.OutputFile) > 0 {
var err error
Expand Down
4 changes: 4 additions & 0 deletions cmd/infra/aws/create_iam.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ func (o *CreateIAMOptions) Run(ctx context.Context, client crclient.Client) erro
if err != nil {
return err
}
return o.Output(results)
}

func (o *CreateIAMOptions) Output(results *CreateIAMOutput) error {
// Write out stateful information
out := os.Stdout
if len(o.OutputFile) > 0 {
Expand Down
38 changes: 21 additions & 17 deletions cmd/infra/powervs/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,23 +276,7 @@ func (options *CreateInfraOptions) Run(ctx context.Context) error {
}

defer func() {
out := os.Stdout
if len(options.OutputFile) > 0 {
var err error
out, err = os.Create(options.OutputFile)
if err != nil {
log(options.InfraID).Error(err, "cannot create output file")
}
defer out.Close()
}
outputBytes, err := json.MarshalIndent(infra, "", " ")
if err != nil {
log(options.InfraID).WithName(options.InfraID).Error(err, "failed to serialize output infra")
}
_, err = out.Write(outputBytes)
if err != nil {
log(options.InfraID).Error(err, "failed to write output infra json")
}
options.Output(infra)
}()

if err := infra.SetupInfra(ctx, options); err != nil {
Expand All @@ -302,6 +286,26 @@ func (options *CreateInfraOptions) Run(ctx context.Context) error {
return nil
}

func (options *CreateInfraOptions) Output(infra *Infra) {
out := os.Stdout
if len(options.OutputFile) > 0 {
var err error
out, err = os.Create(options.OutputFile)
if err != nil {
log(options.InfraID).Error(err, "cannot create output file")
}
defer out.Close()
}
outputBytes, err := json.MarshalIndent(infra, "", " ")
if err != nil {
log(options.InfraID).WithName(options.InfraID).Error(err, "failed to serialize output infra")
}
_, err = out.Write(outputBytes)
if err != nil {
log(options.InfraID).Error(err, "failed to write output infra json")
}
}

// checkUnsupportedPowerVSZone omitting powervs zones that does not support hypershift infra creation flow
func checkUnsupportedPowerVSZone(zone string) error {
for i := 0; i < len(unsupportedPowerVSZones); i++ {
Expand Down
3 changes: 2 additions & 1 deletion product-cli/cmd/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ func NewCreateCommands() *cobra.Command {
cmd.PersistentFlags().StringVar(&opts.NetworkType, "network-type", opts.NetworkType, "Enum specifying the cluster SDN provider. Supports either Calico, OVNKubernetes, OpenShiftSDN or Other.")
cmd.PersistentFlags().StringVar(&opts.PullSecretFile, "pull-secret", opts.PullSecretFile, "Filepath to a pull secret.")
cmd.PersistentFlags().StringVar(&opts.ReleaseImage, "release-image", opts.ReleaseImage, "The OCP release image for the HostedCluster.")
cmd.PersistentFlags().BoolVar(&opts.Render, "render", opts.Render, "Renders the HostedCluster manifest output as YAML to stdout instead of automatically applying the manifests to the management cluster.")
cmd.PersistentFlags().BoolVar(&opts.Render, "render", opts.Render, "Render output as YAML instead of applying")
cmd.PersistentFlags().StringVar(&opts.RenderInto, "render-into", opts.RenderInto, "Render output as YAML into this file instead of applying. If unset, YAML will be output to stdout.")
cmd.PersistentFlags().StringArrayVar(&opts.ServiceCIDR, "service-cidr", opts.ServiceCIDR, "The CIDR of the service network. Can be specified multiple times.")
cmd.PersistentFlags().BoolVar(&opts.DefaultDual, "default-dual", opts.DefaultDual, "Defines the Service and Cluster CIDRs as dual-stack default values. This flag is ignored if service-cidr or cluster-cidr are set. Cannot be defined with service-cidr or cluster-cidr flag.")
cmd.PersistentFlags().StringVar(&opts.SSHKeyFile, "ssh-key", opts.SSHKeyFile, "Filepath to an SSH key file.")
Expand Down
Loading

0 comments on commit e66add8

Please sign in to comment.