Skip to content
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
2 changes: 2 additions & 0 deletions cmd/infra/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package infra
import (
"github.com/openshift/hypershift/cmd/infra/aws"
"github.com/openshift/hypershift/cmd/infra/azure"
"github.com/openshift/hypershift/cmd/infra/gcp"
"github.com/openshift/hypershift/cmd/infra/powervs"

"github.com/spf13/cobra"
Expand All @@ -17,6 +18,7 @@ func NewCreateCommand() *cobra.Command {

cmd.AddCommand(aws.NewCreateCommand())
cmd.AddCommand(azure.NewCreateCommand())
cmd.AddCommand(gcp.NewCreateCommand())
cmd.AddCommand(powervs.NewCreateCommand())

return cmd
Expand Down
2 changes: 2 additions & 0 deletions cmd/infra/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package infra
import (
"github.com/openshift/hypershift/cmd/infra/aws"
"github.com/openshift/hypershift/cmd/infra/azure"
"github.com/openshift/hypershift/cmd/infra/gcp"
"github.com/openshift/hypershift/cmd/infra/powervs"

"github.com/spf13/cobra"
Expand All @@ -17,6 +18,7 @@ func NewDestroyCommand() *cobra.Command {

cmd.AddCommand(aws.NewDestroyCommand())
cmd.AddCommand(azure.NewDestroyCommand())
cmd.AddCommand(gcp.NewDestroyCommand())
cmd.AddCommand(powervs.NewDestroyCommand())

return cmd
Expand Down
11 changes: 11 additions & 0 deletions cmd/infra/gcp/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package gcp

import "time"

const (
// defaultOperationTimeout is the timeout for waiting on GCP long-running operations.
defaultOperationTimeout = 5 * time.Minute

// defaultPollingInterval is the interval between polling attempts for operation status.
defaultPollingInterval = 2 * time.Second
)
183 changes: 183 additions & 0 deletions cmd/infra/gcp/create_infra.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package gcp

import (
"context"
"encoding/json"
"fmt"
"os"

"github.com/openshift/hypershift/cmd/log"

"github.com/go-logr/logr"
"github.com/spf13/cobra"
)

const (
DefaultSubnetCIDR = "10.0.0.0/24"
)

// CreateInfraOptions contains options for creating GCP infrastructure
type CreateInfraOptions struct {
// Required flags
ProjectID string
Region string
InfraID string

// Optional flags
VPCCidr string
OutputFile string
}

// CreateInfraOutput contains the output from infrastructure creation
type CreateInfraOutput struct {
Region string `json:"region"`
ProjectID string `json:"projectId"`
InfraID string `json:"infraId"`
NetworkName string `json:"networkName"`
NetworkSelfLink string `json:"networkSelfLink"`
SubnetName string `json:"subnetName"`
SubnetSelfLink string `json:"subnetSelfLink"`
SubnetCIDR string `json:"subnetCidr"`
RouterName string `json:"routerName"`
NATName string `json:"natName"`
}

// NewCreateCommand creates a new cobra command for creating GCP infrastructure
func NewCreateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "gcp",
Short: "Creates GCP infrastructure resources for a cluster",
SilenceUsage: true,
}

opts := CreateInfraOptions{
VPCCidr: DefaultSubnetCIDR,
}

cmd.Flags().StringVar(&opts.ProjectID, "project-id", opts.ProjectID, "GCP Project ID (required)")
cmd.Flags().StringVar(&opts.Region, "region", opts.Region, "GCP region where infrastructure will be created")
cmd.Flags().StringVar(&opts.InfraID, "infra-id", opts.InfraID, "Infrastructure ID for resource naming (required)")
cmd.Flags().StringVar(&opts.VPCCidr, "vpc-cidr", opts.VPCCidr, "CIDR block for the subnet")
cmd.Flags().StringVar(&opts.OutputFile, "output-file", opts.OutputFile, "Path to file that will contain output information from infra resources (optional)")

_ = cmd.MarkFlagRequired("project-id")
_ = cmd.MarkFlagRequired("region")
_ = cmd.MarkFlagRequired("infra-id")

logger := log.Log
cmd.PreRunE = func(cmd *cobra.Command, args []string) error {
return opts.Validate()
}
cmd.RunE = func(cmd *cobra.Command, args []string) error {
if err := opts.Run(cmd.Context(), logger); err != nil {
logger.Error(err, "Failed to create GCP infrastructure")
return err
}
logger.Info("Successfully created GCP infrastructure")
return nil
}

return cmd
}

// Validate validates the create infrastructure options
func (o *CreateInfraOptions) Validate() error {
if o.ProjectID == "" {
return fmt.Errorf("--project-id is required")
}
if o.InfraID == "" {
return fmt.Errorf("--infra-id is required")
}
if o.Region == "" {
return fmt.Errorf("--region is required")
}
return nil
}

// Run executes the infrastructure creation
func (o *CreateInfraOptions) Run(ctx context.Context, logger logr.Logger) error {
result, err := o.CreateInfra(ctx, logger)
if err != nil {
return err
}
return o.Output(result)
}

// Output writes the infrastructure output to stdout or a file
func (o *CreateInfraOptions) Output(result *CreateInfraOutput) error {
out := os.Stdout
if len(o.OutputFile) > 0 {
var err error
out, err = os.Create(o.OutputFile)
if err != nil {
return fmt.Errorf("cannot create output file: %w", err)
}
defer func(out *os.File) {
_ = out.Close()
}(out)
}
outputBytes, err := json.MarshalIndent(result, "", " ")
if err != nil {
return fmt.Errorf("failed to serialize result: %w", err)
}
_, err = out.Write(outputBytes)
if err != nil {
return fmt.Errorf("failed to write result: %w", err)
}
return nil
}

// CreateInfra creates the GCP infrastructure resources
func (o *CreateInfraOptions) CreateInfra(ctx context.Context, logger logr.Logger) (*CreateInfraOutput, error) {
logger.Info("Creating GCP infrastructure", "projectID", o.ProjectID, "region", o.Region, "infraID", o.InfraID)

// Ensure a sensible default for programmatic callers that don't go through NewCreateCommand
if o.VPCCidr == "" {
o.VPCCidr = DefaultSubnetCIDR
}

// Initialize network manager
networkManager, err := NewNetworkManager(ctx, o.ProjectID, o.InfraID, o.Region, logger)
if err != nil {
return nil, fmt.Errorf("failed to initialize network manager: %w", err)
}

result := &CreateInfraOutput{
Region: o.Region,
ProjectID: o.ProjectID,
InfraID: o.InfraID,
}

// Create VPC network
network, err := networkManager.CreateNetwork(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create VPC network: %w", err)
}
result.NetworkName = network.Name
result.NetworkSelfLink = network.SelfLink

// Create subnet
subnet, err := networkManager.CreateSubnet(ctx, network.SelfLink, o.VPCCidr)
if err != nil {
return nil, fmt.Errorf("failed to create subnet: %w", err)
}
result.SubnetName = subnet.Name
result.SubnetSelfLink = subnet.SelfLink
result.SubnetCIDR = subnet.IpCidrRange

// Create Cloud Router
router, err := networkManager.CreateRouter(ctx, network.SelfLink)
if err != nil {
return nil, fmt.Errorf("failed to create Cloud Router: %w", err)
}
result.RouterName = router.Name

// Create Cloud NAT
natName, err := networkManager.CreateNAT(ctx, router.Name, subnet.SelfLink)
if err != nil {
return nil, fmt.Errorf("failed to create Cloud NAT: %w", err)
}
result.NATName = natName

return result, nil
}
Loading