Skip to content

Commit

Permalink
Remove zone support
Browse files Browse the repository at this point in the history
Signed-off-by: Pierre-Emmanuel Jacquier <[email protected]>
  • Loading branch information
pierre-emmanuelJ committed Nov 14, 2024
1 parent 7b303c4 commit 48a1f4a
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 61 deletions.
5 changes: 0 additions & 5 deletions docs/examples/cloud-controller-manager.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,6 @@ spec:
- --leader-elect=true
- --allow-untagged-cloud
env:
- name: EXOSCALE_ZONE
valueFrom:
secretKeyRef:
key: api-zone
name: exoscale-credentials
- name: EXOSCALE_API_KEY
valueFrom:
secretKeyRef:
Expand Down
8 changes: 1 addition & 7 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,6 @@ with API credentials. This can be achieved:

The following environment variables are available to configure Exoscale CCM:

* `EXOSCALE_ZONE` [**required**]: the Exoscale zone which the cluster/CCM
runs in; e.g. `ch-gva-2`

* `EXOSCALE_API_KEY` / `EXOSCALE_API_SECRET` [**required**]: actual Exoscale API
credentials

Expand All @@ -78,13 +75,11 @@ the CCM *Deployment*.

First, start by exporting the Exoscale API credentials (we recommend that you
create dedicated API credentials using the [Exoscale IAM][exo-iam] service) to
provide to the CCM in your shell, as well as the zone in which the cluster is
located:
provide to the CCM in your shell:

```Shell
export EXOSCALE_API_KEY="EXOxxxxxxxxxxxxxxxxxxxxxxxx"
export EXOSCALE_API_SECRET="xxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export EXOSCALE_ZONE="ch-gva-2"
```

Next, run the following command from the same shell:
Expand All @@ -111,7 +106,6 @@ of the environment variables mentioned above):
``` yaml
# Global (API) configuration
global:
zone: "<EXOSCALE_ZONE>"
apiKey: "<EXOSCALE_API_KEY>"
apiSecret: "<EXOSCALE_API_SECRET>"
apiCredentialsFile: "<EXOSCALE_API_CREDENTIALS_FILE>"
Expand Down
1 change: 0 additions & 1 deletion docs/scripts/generate-secret.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,4 @@ type: Opaque
data:
api-key: '$(printf "%s" "$EXOSCALE_API_KEY" | base64)'
api-secret: '$(printf "%s" "$EXOSCALE_API_SECRET" | base64)'
api-zone: '$(printf "%s" "$EXOSCALE_ZONE" | base64)'
EOF
82 changes: 65 additions & 17 deletions exoscale/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,33 @@ type exoscaleAPICredentials struct {
type refreshableExoscaleClient struct {
exo exoscaleClient
apiCredentials exoscaleAPICredentials
apiEndpoint string
apiEndpoint v3.Endpoint

*sync.RWMutex
}

func newRefreshableExoscaleClient(ctx context.Context, config *globalConfig) (*refreshableExoscaleClient, error) {
type switchZone func(ctx context.Context, client *v3.Client, zone v3.ZoneName) (*v3.Client, error)

var switchZoneCallback switchZone = func(ctx context.Context, client *v3.Client, zone v3.ZoneName) (*v3.Client, error) {
if zone == "" {
return client, nil
}

zoneEndpoint, err := client.GetZoneAPIEndpoint(ctx, zone)
if err != nil {
return nil, err
}

return client.WithEndpoint(zoneEndpoint), nil
}

func newRefreshableExoscaleClient(ctx context.Context, config *globalConfig, zone v3.ZoneName, zoneCallback switchZone) (*refreshableExoscaleClient, error) {
c := &refreshableExoscaleClient{
RWMutex: &sync.RWMutex{},
}

if config.APIEndpoint != "" {
c.apiEndpoint = config.APIEndpoint
c.apiEndpoint = v3.Endpoint(config.APIEndpoint)
}

if config.APIKey != "" && config.APISecret != "" { //nolint:gocritic
Expand All @@ -60,25 +75,36 @@ func newRefreshableExoscaleClient(ctx context.Context, config *globalConfig) (*r
APISecret: config.APISecret,
}

//TODO add chain credentials with env...etc
creds := credentials.NewStaticCredentials(c.apiCredentials.APIKey, c.apiCredentials.APISecret)
exo, err := v3.NewClient(creds, v3.ClientOptWithUserAgent(
var opts []v3.ClientOpt
if c.apiEndpoint != "" {
opts = append(opts, v3.ClientOptWithEndpoint(c.apiEndpoint))
}

opts = append(opts, v3.ClientOptWithUserAgent(
fmt.Sprintf("Exoscale-K8s-Cloud-Controller/%s", versionString),
))

//TODO add chain credentials with env...etc
creds := credentials.NewStaticCredentials(
c.apiCredentials.APIKey,
c.apiCredentials.APISecret,
)
exo, err := v3.NewClient(creds, opts...)
if err != nil {
return nil, err
}

if c.apiEndpoint != "" {
exo = exo.WithEndpoint(v3.Endpoint(c.apiEndpoint))
exo, err = zoneCallback(ctx, exo, zone)
if err != nil {
return nil, err
}

c.exo = exo
} else if config.APICredentialsFile != "" {
infof("reading (watching) Exoscale API credentials from file %q", config.APICredentialsFile)

c.refreshCredentialsFromFile(config.APICredentialsFile)
go c.watchCredentialsFile(ctx, config.APICredentialsFile)
c.refreshCredentialsFromFile(ctx, config.APICredentialsFile, zone, zoneCallback)
go c.watchCredentialsFile(ctx, config.APICredentialsFile, zone, zoneCallback)
} else {
return nil, errors.New("incomplete or missing Exoscale API credentials")
}
Expand All @@ -97,7 +123,12 @@ func (c *refreshableExoscaleClient) Wait(ctx context.Context, op *v3.Operation,
)
}

func (c *refreshableExoscaleClient) watchCredentialsFile(ctx context.Context, path string) {
func (c *refreshableExoscaleClient) watchCredentialsFile(
ctx context.Context,
path string,
zone v3.ZoneName,
zoneCallback switchZone,
) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
fatalf("failed to watch credentials file %q: %v", path, err)
Expand All @@ -120,7 +151,7 @@ func (c *refreshableExoscaleClient) watchCredentialsFile(ctx context.Context, pa
if event.Name == path &&
(event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create) {
infof("refreshing API credentials from file %q", path)
c.refreshCredentialsFromFile(path)
c.refreshCredentialsFromFile(ctx, path, zone, zoneCallback)
}

case err, ok := <-watcher.Errors:
Expand All @@ -138,7 +169,12 @@ func (c *refreshableExoscaleClient) watchCredentialsFile(ctx context.Context, pa
}
}

func (c *refreshableExoscaleClient) refreshCredentialsFromFile(path string) {
func (c *refreshableExoscaleClient) refreshCredentialsFromFile(
ctx context.Context,
path string,
zone v3.ZoneName,
zoneCallback switchZone,
) {
f, err := os.Open(path)
if err != nil {
fatalf("failed to read credentials file %q: %v", path, err)
Expand All @@ -151,18 +187,30 @@ func (c *refreshableExoscaleClient) refreshCredentialsFromFile(path string) {
return
}

var opts []v3.ClientOpt
if c.apiEndpoint != "" {
opts = append(opts, v3.ClientOptWithEndpoint(c.apiEndpoint))
}

opts = append(opts, v3.ClientOptWithUserAgent(
fmt.Sprintf("Exoscale-K8s-Cloud-Controller/%s", versionString),
))

//TODO add chain credentials with env...etc
creds := credentials.NewStaticCredentials(apiCredentials.APIKey, apiCredentials.APISecret)
client, err := v3.NewClient(creds)
client, err := v3.NewClient(creds, opts...)
if err != nil {
infof("failed to initialize Exoscale client: %v", err)
return
}

c.Lock()
if c.apiEndpoint != "" {
client = client.WithEndpoint(v3.Endpoint(c.apiEndpoint))
client, err = zoneCallback(ctx, client, zone)
if err != nil {
errorf("failed to switch client zone: %v", err)
return
}

c.Lock()
c.exo = client
c.apiCredentials = apiCredentials
c.Unlock()
Expand Down
14 changes: 10 additions & 4 deletions exoscale/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ import (
"path"
"sync"
"time"

v3 "github.com/exoscale/egoscale/v3"
)

var testZoneCallback switchZone = func(ctx context.Context, client *v3.Client, zone v3.ZoneName) (*v3.Client, error) {
return client, nil
}

func (ts *exoscaleCCMTestSuite) Test_newRefreshableExoscaleClient_no_config() {
_, err := newRefreshableExoscaleClient(context.Background(), &testConfig_empty.Global)
_, err := newRefreshableExoscaleClient(context.Background(), &testConfig_empty.Global, v3.ZoneNameCHGva2, testZoneCallback)
ts.Require().Error(err)
}

Expand All @@ -23,7 +29,7 @@ func (ts *exoscaleCCMTestSuite) Test_newRefreshableExoscaleClient_credentials()
},
}

actual, err := newRefreshableExoscaleClient(context.Background(), &testConfig_typical.Global)
actual, err := newRefreshableExoscaleClient(context.Background(), &testConfig_typical.Global, v3.ZoneNameCHGva2, testZoneCallback)
ts.Require().NoError(err)
ts.Require().Equal(expected.apiCredentials, actual.apiCredentials)
ts.Require().NotNil(actual.exo)
Expand All @@ -48,7 +54,7 @@ func (ts *exoscaleCCMTestSuite) Test_refreshableExoscaleClient_refreshCredential
ts.Require().NoError(os.WriteFile(testAPICredentialsFile, jsonAPICredentials, 0o600))

client := &refreshableExoscaleClient{RWMutex: &sync.RWMutex{}}
client.refreshCredentialsFromFile(testAPICredentialsFile)
client.refreshCredentialsFromFile(context.Background(), testAPICredentialsFile, v3.ZoneNameCHGva2, testZoneCallback)

client.RLock()
defer client.RUnlock()
Expand Down Expand Up @@ -76,7 +82,7 @@ func (ts *exoscaleCCMTestSuite) Test_refreshableExoscaleClient_watchCredentialsF
defer cancel()

client := &refreshableExoscaleClient{RWMutex: &sync.RWMutex{}}
go client.watchCredentialsFile(ctx, testAPICredentialsFile)
go client.watchCredentialsFile(ctx, testAPICredentialsFile, v3.ZoneNameCHGva2, testZoneCallback)

time.Sleep(1 * time.Second)
ts.Require().NoError(os.WriteFile(testAPICredentialsFile, jsonAPICredentials, 0o600))
Expand Down
28 changes: 22 additions & 6 deletions exoscale/exoscale.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package exoscale

import (
"context"
"errors"
"fmt"
"io"
"os"
"strings"
"time"

v3 "github.com/exoscale/egoscale/v3"
"github.com/exoscale/egoscale/v3/metadata"
"k8s.io/client-go/kubernetes"
cloudprovider "k8s.io/cloud-provider"
"k8s.io/klog/v2"
Expand Down Expand Up @@ -59,11 +61,20 @@ func newExoscaleCloud(config *cloudConfig) (cloudprovider.Interface, error) {
cfg: config,
}

provider.zone = config.Global.Zone
if provider.zone == "" {
return nil, errors.New("zone not specified, please set the 'zone' in the cloud configuration file or the EXOSCALE_ZONE environment variable")
}
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute))
defer cancel()

zone, err := metadata.FromCdRom(metadata.AvailabilityZone)
if err != nil {
klog.Warningf("error to get exoscale node metadata from CD-ROM: %v", err)
klog.Info("fallback on server metadata")
zone, err = metadata.Get(ctx, metadata.AvailabilityZone)
if err != nil {
klog.Errorf("error to get exoscale node metadata from server: %v", err)
return nil, fmt.Errorf("get metadata: %w", err)
}
}
provider.zone = zone
provider.instances = newInstances(provider, &config.Instances)
provider.loadBalancer = newLoadBalancer(provider, &config.LoadBalancer)
provider.zones = newZones(provider)
Expand All @@ -82,7 +93,12 @@ func (p *cloudProvider) Initialize(clientBuilder cloudprovider.ControllerClientB
p.ctx = ctx
p.stop = cancel

client, err := newRefreshableExoscaleClient(p.ctx, &p.cfg.Global)
client, err := newRefreshableExoscaleClient(
p.ctx,
&p.cfg.Global,
v3.ZoneName(p.zone),
switchZoneCallback,
)
if err != nil {
fatalf("could not create Exoscale client: %v", err)
}
Expand Down
4 changes: 0 additions & 4 deletions exoscale/exoscale_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ type cloudConfig struct {
}

type globalConfig struct {
Zone string
APIKey string `yaml:"apiKey"`
APISecret string `yaml:"apiSecret"`
APICredentialsFile string `yaml:"apiCredentialsFile"`
Expand All @@ -33,9 +32,6 @@ func readExoscaleConfig(config io.Reader) (cloudConfig, error) {
}

// From environment
if value, exists := os.LookupEnv("EXOSCALE_ZONE"); exists {
cfg.Global.Zone = value
}
if value, exists := os.LookupEnv("EXOSCALE_API_KEY"); exists {
cfg.Global.APIKey = value
}
Expand Down
Loading

0 comments on commit 48a1f4a

Please sign in to comment.