Skip to content

Commit 05e2011

Browse files
timoreimannTimo Reimann
authored and
Timo Reimann
committed
digitalocean: support reading access token from file
This makes it possible to securely store the access token in a file and load it into the cloud provider from there. Document DigitalOcean's cloud config format while we are here.
1 parent 7d7df8c commit 05e2011

File tree

6 files changed

+73
-4
lines changed

6 files changed

+73
-4
lines changed

cluster-autoscaler/cloudprovider/digitalocean/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ offering which can be enabled/disable dynamically for an existing cluster.
66

77
# Configuration
88

9+
## Cloud config file
10+
11+
The (JSON) configuration file of the DigitalOcean cloud provider supports the
12+
following values:
13+
14+
- `cluster_id`: the ID of the cluster (a UUID)
15+
- `token`: the DigitalOcean access token literally defined
16+
- `token_file`: a file path containing the DigitalOcean access token
17+
- `url`: the DigitalOcean URL (optional; defaults to `https://api.digitalocean.com/`)
18+
19+
Exactly one of `token` or `token_file` must be provided.
20+
21+
## Behavior
22+
923
Parameters of the autoscaler (such as whether it is on or off, and the
1024
minimum/maximum values) are configured through the public DOKS API and
1125
subsequently reflected by the node pool objects. The cloud provider periodically

cluster-autoscaler/cloudprovider/digitalocean/digitalocean_manager.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package digitalocean
1818

1919
import (
20+
"bytes"
2021
"context"
2122
"encoding/json"
2223
"errors"
@@ -26,7 +27,7 @@ import (
2627

2728
"github.com/digitalocean/godo"
2829
"golang.org/x/oauth2"
29-
klog "k8s.io/klog/v2"
30+
"k8s.io/klog/v2"
3031
)
3132

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

66+
// TokenFile references the token from the given file. It cannot be specified
67+
// together with Token.
68+
TokenFile string `json:"token_file"`
69+
6570
// URL points to DigitalOcean API. If empty, defaults to
6671
// https://api.digitalocean.com/
6772
URL string `json:"url"`
@@ -80,13 +85,28 @@ func newManager(configReader io.Reader) (*Manager, error) {
8085
}
8186
}
8287

83-
if cfg.Token == "" {
88+
if cfg.Token == "" && cfg.TokenFile == "" {
8489
return nil, errors.New("access token is not provided")
8590
}
91+
if cfg.Token != "" && cfg.TokenFile != "" {
92+
return nil, errors.New("access token literal and access token file must not be provided together")
93+
}
8694
if cfg.ClusterID == "" {
8795
return nil, errors.New("cluster ID is not provided")
8896
}
8997

98+
if cfg.TokenFile != "" {
99+
tokenData, err := ioutil.ReadFile(cfg.TokenFile)
100+
if err != nil {
101+
return nil, fmt.Errorf("failed to read token file: %s", err)
102+
}
103+
tokenData = bytes.TrimSpace(tokenData)
104+
if len(tokenData) == 0 {
105+
return nil, fmt.Errorf("token file %q is empty", cfg.TokenFile)
106+
}
107+
cfg.Token = string(tokenData)
108+
}
109+
90110
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{
91111
AccessToken: cfg.Token,
92112
})

cluster-autoscaler/cloudprovider/digitalocean/digitalocean_manager_test.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,22 @@ import (
2424

2525
"github.com/digitalocean/godo"
2626
"github.com/stretchr/testify/assert"
27+
"github.com/stretchr/testify/require"
2728
)
2829

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

3334
manager, err := newManager(bytes.NewBufferString(cfg))
34-
assert.NoError(t, err)
35+
require.NoError(t, err)
36+
assert.Equal(t, manager.clusterID, "123456", "cluster ID does not match")
37+
})
38+
t.Run("success with token file", func(t *testing.T) {
39+
cfg := `{"cluster_id": "123456", "token_file": "testdata/correct_token", "url": "https://api.digitalocean.com/v2", "version": "dev"}`
40+
41+
manager, err := newManager(bytes.NewBufferString(cfg))
42+
require.NoError(t, err)
3543
assert.Equal(t, manager.clusterID, "123456", "cluster ID does not match")
3644
})
3745

@@ -41,7 +49,31 @@ func TestNewManager(t *testing.T) {
4149
_, err := newManager(bytes.NewBufferString(cfg))
4250
assert.EqualError(t, err, errors.New("access token is not provided").Error())
4351
})
52+
t.Run("literal and token file", func(t *testing.T) {
53+
cfg := `{"cluster_id": "123456", "token": "123-123-123", "token_file": "tokendata/correct_token", "url": "https://api.digitalocean.com/v2", "version": "dev"}`
54+
55+
_, err := newManager(bytes.NewBufferString(cfg))
56+
assert.EqualError(t, err, errors.New("access token literal and access token file must not be provided together").Error())
57+
})
58+
t.Run("missing token file", func(t *testing.T) {
59+
cfg := `{"cluster_id": "123456", "token_file": "testdata/missing_token", "url": "https://api.digitalocean.com/v2", "version": "dev"}`
4460

61+
_, err := newManager(bytes.NewBufferString(cfg))
62+
require.NotNil(t, err)
63+
assert.Contains(t, err.Error(), "failed to read token file")
64+
})
65+
t.Run("empty token file", func(t *testing.T) {
66+
cfg := `{"cluster_id": "123456", "token_file": "testdata/empty_token", "url": "https://api.digitalocean.com/v2", "version": "dev"}`
67+
68+
_, err := newManager(bytes.NewBufferString(cfg))
69+
assert.EqualError(t, err, errors.New(`token file "testdata/empty_token" is empty`).Error())
70+
})
71+
t.Run("all whitespace token file", func(t *testing.T) {
72+
cfg := `{"cluster_id": "123456", "token_file": "testdata/whitespace_token", "url": "https://api.digitalocean.com/v2", "version": "dev"}`
73+
74+
_, err := newManager(bytes.NewBufferString(cfg))
75+
assert.EqualError(t, err, errors.New(`token file "testdata/whitespace_token" is empty`).Error())
76+
})
4577
t.Run("empty cluster ID", func(t *testing.T) {
4678
cfg := `{"cluster_id": "", "token": "123-123-123", "url": "https://api.digitalocean.com/v2", "version": "dev"}`
4779

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
123-123-123

cluster-autoscaler/cloudprovider/digitalocean/testdata/empty_token

Whitespace-only changes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+

0 commit comments

Comments
 (0)