Skip to content

Commit d8f0022

Browse files
authored
Merge pull request #149 from nicolehanjing/nicoleh-tags
tags: initial implementation of tags
2 parents c069a21 + fb56cd9 commit d8f0022

File tree

7 files changed

+597
-32
lines changed

7 files changed

+597
-32
lines changed

Diff for: go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ require (
1818
k8s.io/kubelet v0.20.0
1919
k8s.io/legacy-cloud-providers v0.20.0
2020
k8s.io/utils v0.0.0-20201110183641-67b214c5f920
21+
sigs.k8s.io/yaml v1.2.0
2122
)

Diff for: pkg/providers/v2/cloud.go

+87-12
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ limitations under the License.
1919
package v2
2020

2121
import (
22+
"errors"
2223
"fmt"
2324
"io"
25+
"io/ioutil"
2426
"regexp"
2527

2628
"github.com/aws/aws-sdk-go/aws"
@@ -30,12 +32,26 @@ import (
3032
"github.com/aws/aws-sdk-go/aws/session"
3133
"github.com/aws/aws-sdk-go/service/ec2"
3234

35+
"k8s.io/apimachinery/pkg/util/validation/field"
3336
cloudprovider "k8s.io/cloud-provider"
37+
awsconfigv1alpha1 "k8s.io/cloud-provider-aws/pkg/apis/config/v1alpha1"
38+
"k8s.io/klog/v2"
39+
"sigs.k8s.io/yaml"
3440
)
3541

3642
func init() {
3743
cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
38-
return newCloud()
44+
cfg, err := readAWSCloudConfig(config)
45+
if err != nil {
46+
return nil, fmt.Errorf("failed to read AWS cloud provider config file: %v", err)
47+
}
48+
49+
errs := validateAWSCloudConfig(cfg)
50+
if len(errs) > 0 {
51+
return nil, fmt.Errorf("failed to validate AWS cloud config: %v", errs.ToAggregate())
52+
}
53+
54+
return newCloud(cfg)
3955
})
4056
}
4157

@@ -53,6 +69,14 @@ type cloud struct {
5369
region string
5470
ec2 EC2
5571
metadata EC2Metadata
72+
tags awsTagging
73+
}
74+
75+
// EC2 is an interface defining only the methods we call from the AWS EC2 SDK.
76+
type EC2 interface {
77+
DescribeInstances(request *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error)
78+
79+
CreateTags(*ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error)
5680
}
5781

5882
// EC2Metadata is an abstraction over the AWS metadata service.
@@ -61,6 +85,48 @@ type EC2Metadata interface {
6185
GetMetadata(path string) (string, error)
6286
}
6387

88+
func readAWSCloudConfig(config io.Reader) (*awsconfigv1alpha1.AWSCloudConfig, error) {
89+
if config == nil {
90+
return nil, errors.New("no AWS cloud provider config file given")
91+
}
92+
93+
// read the config file
94+
data, err := ioutil.ReadAll(config)
95+
if err != nil {
96+
return nil, fmt.Errorf("unable to read cloud configuration from %q [%v]", config, err)
97+
}
98+
99+
var cfg awsconfigv1alpha1.AWSCloudConfig
100+
err = yaml.Unmarshal(data, &cfg)
101+
if err != nil {
102+
// we got an error where the decode wasn't related to a missing type
103+
return nil, err
104+
}
105+
106+
return &cfg, nil
107+
}
108+
109+
// validateAWSCloudConfig validates AWSCloudConfig
110+
// clusterName is required
111+
func validateAWSCloudConfig(config *awsconfigv1alpha1.AWSCloudConfig) field.ErrorList {
112+
allErrs := field.ErrorList{}
113+
114+
if config.Kind != "AWSCloudConfig" {
115+
allErrs = append(allErrs, field.Invalid(field.NewPath("kind"), "invalid kind for cloud config: %q", config.Kind))
116+
}
117+
118+
if config.APIVersion != awsconfigv1alpha1.SchemeGroupVersion.String() {
119+
allErrs = append(allErrs, field.Invalid(field.NewPath("apiVersion"), "invalid apiVersion for cloud config: %q", config.APIVersion))
120+
}
121+
122+
fieldPath := field.NewPath("config")
123+
if len(config.Config.ClusterName) == 0 {
124+
allErrs = append(allErrs, field.Required(fieldPath.Child("clusterName"), "cluster name cannot be empty"))
125+
}
126+
127+
return allErrs
128+
}
129+
64130
func getAvailabilityZone(metadata EC2Metadata) (string, error) {
65131
return metadata.GetMetadata("placement/availability-zone")
66132
}
@@ -82,7 +148,7 @@ func azToRegion(az string) (string, error) {
82148
}
83149

84150
// newCloud creates a new instance of AWSCloud.
85-
func newCloud() (cloudprovider.Interface, error) {
151+
func newCloud(cfg *awsconfigv1alpha1.AWSCloudConfig) (cloudprovider.Interface, error) {
86152
sess, err := session.NewSession(&aws.Config{})
87153
if err != nil {
88154
return nil, fmt.Errorf("unable to initialize AWS session: %v", err)
@@ -112,11 +178,6 @@ func newCloud() (cloudprovider.Interface, error) {
112178
return nil, err
113179
}
114180

115-
instances, err := newInstances(az, creds)
116-
if err != nil {
117-
return nil, err
118-
}
119-
120181
ec2Sess, err := session.NewSession(&aws.Config{
121182
Region: aws.String(region),
122183
Credentials: creds,
@@ -130,15 +191,29 @@ func newCloud() (cloudprovider.Interface, error) {
130191
return nil, fmt.Errorf("error creating AWS ec2 client: %q", err)
131192
}
132193

133-
awsCloud := &cloud{
194+
var tags awsTagging
195+
if cfg.Config.ClusterName != "" {
196+
tags, err = newAWSTags(cfg.Config.ClusterName)
197+
if err != nil {
198+
return nil, err
199+
}
200+
} else {
201+
klog.Warning("misconfigured cluster: no clusterName")
202+
}
203+
204+
instances, err := newInstances(az, creds, tags)
205+
if err != nil {
206+
return nil, err
207+
}
208+
209+
return &cloud{
134210
creds: creds,
135211
instances: instances,
136212
region: region,
137213
metadata: metadataClient,
138214
ec2: ec2Service,
139-
}
140-
141-
return awsCloud, nil
215+
tags: tags,
216+
}, nil
142217
}
143218

144219
// Initialize passes a Kubernetes clientBuilder interface to the cloud provider
@@ -177,7 +252,7 @@ func (c *cloud) Routes() (cloudprovider.Routes, bool) {
177252

178253
// HasClusterID returns true if the cluster has a clusterID
179254
func (c *cloud) HasClusterID() bool {
180-
return false
255+
return len(c.tags.clusterName()) > 0
181256
}
182257

183258
// InstancesV2 is an implementation for instances and should only be implemented by external cloud providers.

Diff for: pkg/providers/v2/cloud_test.go

+176
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,14 @@ limitations under the License.
1919
package v2
2020

2121
import (
22+
"bytes"
23+
"reflect"
2224
"testing"
2325

2426
"github.com/stretchr/testify/assert"
27+
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/cloud-provider-aws/pkg/apis/config/v1alpha1"
2530
)
2631

2732
func TestAzToRegion(t *testing.T) {
@@ -44,3 +49,174 @@ func TestAzToRegion(t *testing.T) {
4449
assert.Equal(t, testCase.region, ret)
4550
}
4651
}
52+
53+
func TestReadAWSCloudConfig(t *testing.T) {
54+
testcases := []struct {
55+
name string
56+
configData string
57+
config *v1alpha1.AWSCloudConfig
58+
expectErr bool
59+
}{
60+
{
61+
name: "config with valid cluster name",
62+
configData: `---
63+
kind: AWSCloudConfig
64+
apiVersion: config.aws.io/v1alpha1
65+
config:
66+
clusterName: test
67+
`,
68+
config: &v1alpha1.AWSCloudConfig{
69+
TypeMeta: metav1.TypeMeta{
70+
Kind: "AWSCloudConfig",
71+
APIVersion: "config.aws.io/v1alpha1",
72+
},
73+
Config: v1alpha1.AWSConfig{
74+
ClusterName: "test",
75+
},
76+
},
77+
},
78+
{
79+
name: "config with empty cluster name",
80+
configData: `---
81+
kind: AWSCloudConfig
82+
apiVersion: config.aws.io/v1alpha1
83+
config:
84+
clusterName: ""
85+
`,
86+
config: &v1alpha1.AWSCloudConfig{
87+
TypeMeta: metav1.TypeMeta{
88+
Kind: "AWSCloudConfig",
89+
APIVersion: "config.aws.io/v1alpha1",
90+
},
91+
Config: v1alpha1.AWSConfig{
92+
ClusterName: "",
93+
},
94+
},
95+
},
96+
{
97+
name: "config with only kind and apiVersion",
98+
configData: `---
99+
kind: AWSCloudConfig
100+
apiVersion: config.aws.io/v1alpha1
101+
`,
102+
config: &v1alpha1.AWSCloudConfig{
103+
TypeMeta: metav1.TypeMeta{
104+
Kind: "AWSCloudConfig",
105+
APIVersion: "config.aws.io/v1alpha1",
106+
},
107+
Config: v1alpha1.AWSConfig{
108+
ClusterName: "",
109+
},
110+
},
111+
},
112+
{
113+
name: "config with wrong Kind",
114+
configData: `---
115+
kind: WrongCloudConfig
116+
apiVersion: config.aws.io/v1alpha1
117+
config:
118+
clusterName: test
119+
`,
120+
config: nil,
121+
expectErr: true,
122+
},
123+
{
124+
name: "config with wrong apiversion",
125+
configData: `---
126+
kind: AWSCloudConfig
127+
apiVersion: wrong.aws.io/v1alpha1
128+
config:
129+
clusterName: test
130+
`,
131+
config: nil,
132+
expectErr: true,
133+
},
134+
}
135+
136+
for _, testcase := range testcases {
137+
t.Run(testcase.name, func(t *testing.T) {
138+
buffer := bytes.NewBufferString(testcase.configData)
139+
cloudConfig, err := readAWSCloudConfig(buffer)
140+
if err != nil && !testcase.expectErr {
141+
t.Fatal(err)
142+
}
143+
144+
if err == nil && testcase.expectErr {
145+
t.Error("expected error but got none")
146+
}
147+
148+
if !reflect.DeepEqual(cloudConfig, testcase.config) {
149+
t.Logf("actual cloud config: %#v", cloudConfig)
150+
t.Logf("expected cloud config: %#v", testcase.config)
151+
t.Error("AWS cloud config did not match")
152+
}
153+
})
154+
}
155+
}
156+
157+
func TestValidateAWSCloudConfig(t *testing.T) {
158+
testcases := []struct {
159+
name string
160+
config *v1alpha1.AWSCloudConfig
161+
expectErr bool
162+
}{
163+
{
164+
name: "valid config",
165+
config: &v1alpha1.AWSCloudConfig{
166+
TypeMeta: metav1.TypeMeta{
167+
Kind: "AWSCloudConfig",
168+
APIVersion: "config.aws.io/v1alpha1",
169+
},
170+
Config: v1alpha1.AWSConfig{
171+
ClusterName: "test",
172+
},
173+
},
174+
},
175+
{
176+
name: "empty cluster name",
177+
config: &v1alpha1.AWSCloudConfig{
178+
TypeMeta: metav1.TypeMeta{
179+
Kind: "AWSCloudConfig",
180+
APIVersion: "config.aws.io/v1alpha1",
181+
},
182+
Config: v1alpha1.AWSConfig{
183+
ClusterName: "",
184+
},
185+
},
186+
expectErr: true,
187+
},
188+
{
189+
name: "empty config",
190+
config: &v1alpha1.AWSCloudConfig{
191+
TypeMeta: metav1.TypeMeta{
192+
Kind: "AWSCloudConfig",
193+
APIVersion: "config.aws.io/v1alpha1",
194+
},
195+
Config: v1alpha1.AWSConfig{},
196+
},
197+
expectErr: true,
198+
},
199+
{
200+
name: "invalid config",
201+
config: &v1alpha1.AWSCloudConfig{
202+
TypeMeta: metav1.TypeMeta{
203+
Kind: "AWSCloudConfig",
204+
APIVersion: "config.aws.io/v1alpha1",
205+
},
206+
},
207+
expectErr: true,
208+
},
209+
}
210+
211+
for _, testcase := range testcases {
212+
t.Run(testcase.name, func(t *testing.T) {
213+
errs := validateAWSCloudConfig(testcase.config)
214+
215+
if testcase.expectErr && len(errs) == 0 {
216+
t.Errorf("expected error but got none")
217+
} else if !testcase.expectErr && len(errs) > 0 {
218+
t.Errorf("expected no error but received errors: %v", errs.ToAggregate())
219+
}
220+
})
221+
}
222+
}

0 commit comments

Comments
 (0)