Skip to content

Commit

Permalink
Add code for multiproject GCE Client creation
Browse files Browse the repository at this point in the history
- Still uses default gce.conf to get most of the info
- If project info not specified will use default config values, which
  corresponds to cluster-project
- Overrides TokenURL and TokenBody and Network info if project is
  specified
- Imports new package github.com/go-ini/ini to properly work with
  gce.conf which is in ini file format
  • Loading branch information
panslava committed Nov 26, 2024
1 parent fc39048 commit 9fbc1be
Show file tree
Hide file tree
Showing 24 changed files with 4,002 additions and 11 deletions.
36 changes: 25 additions & 11 deletions cmd/glbc/app/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,30 +78,44 @@ func NewKubeConfig(logger klog.Logger) (*rest.Config, error) {
return config, nil
}

// GCEConfString returns the default GCE cluster config file content as a string.
func GCEConfString(logger klog.Logger) (string, error) {
if flags.F.ConfigFilePath == "" {
return "", fmt.Errorf("config file path is not specified")
}

logger.Info("Reading config from the specified path", "path", flags.F.ConfigFilePath)
configBytes, err := os.ReadFile(flags.F.ConfigFilePath)
if err != nil {
return "", fmt.Errorf("failed to read config file: %v", err)
}

configString := string(configBytes)
logger.V(4).Info("Cloudprovider config file", "config", configString)

return configString, nil
}

// NewGCEClient returns a client to the GCE environment. This will block until
// a valid configuration file can be read.
func NewGCEClient(logger klog.Logger) *gce.Cloud {
var configReader func() io.Reader
if flags.F.ConfigFilePath != "" {
logger.Info("Reading config from the specified path", "path", flags.F.ConfigFilePath)
config, err := os.Open(flags.F.ConfigFilePath)
if err != nil {
klog.Fatalf("%v", err)
}
defer config.Close()

allConfig, err := io.ReadAll(config)
allConfig, err := GCEConfString(logger)
if err != nil {
klog.Fatalf("Error while reading config (%q): %v", flags.F.ConfigFilePath, err)
klog.Fatalf("Error getting default cluster GCE config: %v", err)
}
logger.V(4).Info("Cloudprovider config file", "config", string(allConfig))

configReader = generateConfigReaderFunc(allConfig)
configReader = generateConfigReaderFunc([]byte(allConfig))
} else {
logger.V(2).Info("No cloudprovider config file provided, using default values")
configReader = func() io.Reader { return nil }
}

return GCEClientForConfigReader(configReader, logger)
}

func GCEClientForConfigReader(configReader func() io.Reader, logger klog.Logger) *gce.Cloud {
// Creating the cloud interface involves resolving the metadata server to get
// an oauth token. If this fails, the token provider assumes it's not on GCE.
// No errors are thrown. So we need to keep retrying till it works because
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.22.8
require (
github.com/GoogleCloudPlatform/gke-networking-api v0.1.2-0.20240909212819-4b1bab7c69ea
github.com/GoogleCloudPlatform/k8s-cloud-provider v1.32.0
github.com/go-ini/ini v1.67.0
github.com/go-logr/logr v1.4.2
github.com/golang/protobuf v1.5.4
github.com/google/go-cmp v0.6.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCv
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
Expand Down
126 changes: 126 additions & 0 deletions pkg/multiproject/gce/gce.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package gce

import (
"bytes"
"encoding/json"
"fmt"
"io"
"strings"

"github.com/go-ini/ini"
cloudgce "k8s.io/cloud-provider-gcp/providers/gce"
"k8s.io/ingress-gce/cmd/glbc/app"
v1 "k8s.io/ingress-gce/pkg/apis/clusterslice/v1"
"k8s.io/klog/v2"
)

func init() {
// Disable pretty printing for INI files, to match default format of gce.conf.
ini.PrettyFormat = false
ini.PrettyEqual = true
ini.PrettySection = true
}

// NewGCEForClusterSlice returns a new GCE client for the given project.
// If clusterSlice is nil, it returns the default cloud associated with the cluster's project.
// It modifies the default configuration when a clusterSlice is provided.
func NewGCEForClusterSlice(defaultConfigContent string, clusterSlice *v1.ClusterSlice, logger klog.Logger) (*cloudgce.Cloud, error) {
modifiedConfigContent, err := generateConfigForClusterSlice(defaultConfigContent, clusterSlice)
if err != nil {
return nil, fmt.Errorf("failed to modify config content: %v", err)
}

// Return a new GCE client using the modified configuration content
return app.GCEClientForConfigReader(
func() io.Reader { return strings.NewReader(modifiedConfigContent) },
logger,
), nil
}

func generateConfigForClusterSlice(defaultConfigContent string, clusterSlice *v1.ClusterSlice) (string, error) {
if clusterSlice == nil {
return defaultConfigContent, nil
}

// Load the config content into an INI file
cfg, err := ini.Load([]byte(defaultConfigContent))
if err != nil {
return "", fmt.Errorf("failed to parse default config content: %w", err)
}

globalSection := cfg.Section("global")
if globalSection == nil {
return "", fmt.Errorf("global section not found in config")
}

// Update ProjectID
projectIDKey := "project-id"
globalSection.Key(projectIDKey).SetValue(clusterSlice.Spec.ProjectID)

// Update TokenURL
tokenURLKey := "token-url"
tokenURL := globalSection.Key(tokenURLKey).String()
projectNumberInt := clusterSlice.Spec.ProjectNumber
projectNumberStr := fmt.Sprintf("%d", projectNumberInt)
newTokenURL := replaceProjectNumberInTokenURL(tokenURL, projectNumberStr)
globalSection.Key(tokenURLKey).SetValue(newTokenURL)

// Update TokenBody
tokenBodyKey := "token-body"
tokenBody := globalSection.Key(tokenBodyKey).String()
newTokenBody, err := updateTokenProjectNumber(tokenBody, int(projectNumberInt))
if err != nil {
return "", fmt.Errorf("failed to update TokenBody: %v", err)
}
globalSection.Key(tokenBodyKey).SetValue(newTokenBody)

// Update NetworkName and SubnetworkName
if clusterSlice.Spec.NetworkConfig != nil {
networkNameKey := "network-name"
globalSection.Key(networkNameKey).SetValue(clusterSlice.Spec.NetworkConfig.Network)

subnetworkNameKey := "subnetwork-name"
globalSection.Key(subnetworkNameKey).SetValue(clusterSlice.Spec.NetworkConfig.DefaultSubnetwork)
}

// Write the modified config content to a string with custom options
var modifiedConfigContent bytes.Buffer
_, err = cfg.WriteTo(&modifiedConfigContent)
if err != nil {
return "", fmt.Errorf("failed to write modified config content: %v", err)
}

return modifiedConfigContent.String(), nil
}

// replaceProjectNumberInTokenURL replaces the project number in the token URL.
func replaceProjectNumberInTokenURL(tokenURL string, projectNumber string) string {
parts := strings.Split(tokenURL, "/")
for i, part := range parts {
if part == "projects" && i+1 < len(parts) {
parts[i+1] = projectNumber
break
}
}
return strings.Join(parts, "/")
}

func updateTokenProjectNumber(tokenBody string, projectNumber int) (string, error) {
var bodyMap map[string]interface{}

// Unmarshal the JSON string into a map
if err := json.Unmarshal([]byte(tokenBody), &bodyMap); err != nil {
return "", fmt.Errorf("error unmarshaling TokenBody: %v", err)
}

// Update the "projectNumber" field with the new value
bodyMap["projectNumber"] = projectNumber

// Marshal the map back into a JSON string
newTokenBodyBytes, err := json.Marshal(bodyMap)
if err != nil {
return "", fmt.Errorf("error marshaling TokenBody: %v", err)
}

return string(newTokenBodyBytes), nil
}
Loading

0 comments on commit 9fbc1be

Please sign in to comment.