Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

digitalocean: support reading access token from file #4147

Merged
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
14 changes: 14 additions & 0 deletions cluster-autoscaler/cloudprovider/digitalocean/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ offering which can be enabled/disable dynamically for an existing cluster.

# Configuration

## Cloud config file

The (JSON) configuration file of the DigitalOcean cloud provider supports the
following values:

- `cluster_id`: the ID of the cluster (a UUID)
- `token`: the DigitalOcean access token literally defined
- `token_file`: a file path containing the DigitalOcean access token
- `url`: the DigitalOcean URL (optional; defaults to `https://api.digitalocean.com/`)

Exactly one of `token` or `token_file` must be provided.

## Behavior

Parameters of the autoscaler (such as whether it is on or off, and the
minimum/maximum values) are configured through the public DOKS API and
subsequently reflected by the node pool objects. The cloud provider periodically
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package digitalocean

import (
"bytes"
"context"
"encoding/json"
"errors"
Expand All @@ -26,7 +27,7 @@ import (

"github.com/digitalocean/godo"
"golang.org/x/oauth2"
klog "k8s.io/klog/v2"
"k8s.io/klog/v2"
)

var (
Expand Down Expand Up @@ -62,6 +63,10 @@ type Config struct {
// DigitalOcean Cluster Autoscaler is running.
Token string `json:"token"`

// TokenFile references the token from the given file. It cannot be specified
// together with Token.
TokenFile string `json:"token_file"`

// URL points to DigitalOcean API. If empty, defaults to
// https://api.digitalocean.com/
URL string `json:"url"`
Expand All @@ -80,13 +85,28 @@ func newManager(configReader io.Reader) (*Manager, error) {
}
}

if cfg.Token == "" {
if cfg.Token == "" && cfg.TokenFile == "" {
return nil, errors.New("access token is not provided")
}
if cfg.Token != "" && cfg.TokenFile != "" {
return nil, errors.New("access token literal and access token file must not be provided together")
}
if cfg.ClusterID == "" {
return nil, errors.New("cluster ID is not provided")
}

if cfg.TokenFile != "" {
tokenData, err := ioutil.ReadFile(cfg.TokenFile)
if err != nil {
return nil, fmt.Errorf("failed to read token file: %s", err)
}
tokenData = bytes.TrimSpace(tokenData)
if len(tokenData) == 0 {
return nil, fmt.Errorf("token file %q is empty", cfg.TokenFile)
}
cfg.Token = string(tokenData)
}

tokenSource := oauth2.StaticTokenSource(&oauth2.Token{
AccessToken: cfg.Token,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,22 @@ import (

"github.com/digitalocean/godo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewManager(t *testing.T) {
t.Run("success", func(t *testing.T) {
t.Run("success with literal token", func(t *testing.T) {
cfg := `{"cluster_id": "123456", "token": "123-123-123", "url": "https://api.digitalocean.com/v2", "version": "dev"}`

manager, err := newManager(bytes.NewBufferString(cfg))
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, manager.clusterID, "123456", "cluster ID does not match")
})
t.Run("success with token file", func(t *testing.T) {
cfg := `{"cluster_id": "123456", "token_file": "testdata/correct_token", "url": "https://api.digitalocean.com/v2", "version": "dev"}`

manager, err := newManager(bytes.NewBufferString(cfg))
require.NoError(t, err)
assert.Equal(t, manager.clusterID, "123456", "cluster ID does not match")
})

Expand All @@ -41,7 +49,31 @@ func TestNewManager(t *testing.T) {
_, err := newManager(bytes.NewBufferString(cfg))
assert.EqualError(t, err, errors.New("access token is not provided").Error())
})
t.Run("literal and token file", func(t *testing.T) {
cfg := `{"cluster_id": "123456", "token": "123-123-123", "token_file": "tokendata/correct_token", "url": "https://api.digitalocean.com/v2", "version": "dev"}`

_, err := newManager(bytes.NewBufferString(cfg))
assert.EqualError(t, err, errors.New("access token literal and access token file must not be provided together").Error())
})
t.Run("missing token file", func(t *testing.T) {
cfg := `{"cluster_id": "123456", "token_file": "testdata/missing_token", "url": "https://api.digitalocean.com/v2", "version": "dev"}`

_, err := newManager(bytes.NewBufferString(cfg))
require.NotNil(t, err)
assert.Contains(t, err.Error(), "failed to read token file")
})
t.Run("empty token file", func(t *testing.T) {
cfg := `{"cluster_id": "123456", "token_file": "testdata/empty_token", "url": "https://api.digitalocean.com/v2", "version": "dev"}`

_, err := newManager(bytes.NewBufferString(cfg))
assert.EqualError(t, err, errors.New(`token file "testdata/empty_token" is empty`).Error())
})
t.Run("all whitespace token file", func(t *testing.T) {
cfg := `{"cluster_id": "123456", "token_file": "testdata/whitespace_token", "url": "https://api.digitalocean.com/v2", "version": "dev"}`

_, err := newManager(bytes.NewBufferString(cfg))
assert.EqualError(t, err, errors.New(`token file "testdata/whitespace_token" is empty`).Error())
})
t.Run("empty cluster ID", func(t *testing.T) {
cfg := `{"cluster_id": "", "token": "123-123-123", "url": "https://api.digitalocean.com/v2", "version": "dev"}`

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
123-123-123
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@