mirror of https://github.com/kubernetes/kops.git
Merge pull request #1750 from robinpercy/cli-cloud-labels
WIP: Exposing cloud labels as a CLI option
This commit is contained in:
commit
08419fcae8
|
@ -18,6 +18,7 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -76,6 +77,9 @@ type CreateClusterOptions struct {
|
|||
// Enable/Disable Bastion Host complete setup
|
||||
Bastion bool
|
||||
|
||||
// Specify tags for AWS instance groups
|
||||
CloudLabels string
|
||||
|
||||
// Egress configuration - FOR TESTING ONLY
|
||||
Egress string
|
||||
}
|
||||
|
@ -174,6 +178,9 @@ func NewCmdCreateCluster(f *util.Factory, out io.Writer) *cobra.Command {
|
|||
// Bastion
|
||||
cmd.Flags().BoolVar(&options.Bastion, "bastion", options.Bastion, "Pass the --bastion flag to enable a bastion instance group. Only applies to private topology.")
|
||||
|
||||
// Allow custom tags from the CLI
|
||||
cmd.Flags().StringVar(&options.CloudLabels, "cloud-labels", options.CloudLabels, "A list of KV pairs used to tag all instance groups in AWS (eg \"Owner=John Doe,Team=Some Team\").")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -297,6 +304,11 @@ func RunCreateCluster(f *util.Factory, out io.Writer, c *CreateClusterOptions) e
|
|||
var masters []*api.InstanceGroup
|
||||
var nodes []*api.InstanceGroup
|
||||
var instanceGroups []*api.InstanceGroup
|
||||
cloudLabels, err := parseCloudLabels(c.CloudLabels)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing global cloud labels: %v", err)
|
||||
}
|
||||
cluster.Spec.CloudLabels = cloudLabels
|
||||
|
||||
// Build the master subnets
|
||||
// The master zones is the default set of zones unless explicitly set
|
||||
|
@ -774,3 +786,30 @@ func trimCommonPrefix(names []string) []string {
|
|||
|
||||
return names
|
||||
}
|
||||
|
||||
// parseCloudLabels takes a CSV list of key=value records and parses them into a map. Nested '='s are supported via
|
||||
// quoted strings (eg `foo="bar=baz"` parses to map[string]string{"foo":"bar=baz"}. Nested commas are not supported.
|
||||
func parseCloudLabels(s string) (map[string]string, error) {
|
||||
|
||||
// Replace commas with newlines to allow a single pass with csv.Reader.
|
||||
// We can't use csv.Reader for the initial split because it would see each key=value record as a single field
|
||||
// and significantly complicates using quoted fields as keys or values.
|
||||
records := strings.Replace(s, ",", "\n", -1)
|
||||
|
||||
// Let the CSV library do the heavy-lifting in handling nested ='s
|
||||
r := csv.NewReader(strings.NewReader(records))
|
||||
r.Comma = '='
|
||||
r.FieldsPerRecord = 2
|
||||
r.LazyQuotes = false
|
||||
r.TrimLeadingSpace = true
|
||||
kvPairs, err := r.ReadAll()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("One or more key=value pairs are malformed:\n%s\n:%v", records, err)
|
||||
}
|
||||
|
||||
m := make(map[string]string, len(kvPairs))
|
||||
for _, pair := range kvPairs {
|
||||
m[pair[0]] = pair[1]
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseCloudLabels(t *testing.T) {
|
||||
expect := map[string]string{"foo": "bar", "fib": "baz"}
|
||||
checkParse(t, "", map[string]string{}, false)
|
||||
checkParse(t, "foo=bar,fib=baz", expect, false)
|
||||
checkParse(t, `foo=bar,"fib"="baz"`, expect, false)
|
||||
checkParse(t, `"fo\""o"=bar,"fi\b"="baz"`,
|
||||
map[string]string{`fo\"o`: "bar", `fi\b`: "baz"}, false)
|
||||
checkParse(t, `fo"o=bar,fib=baz`, expect, true)
|
||||
checkParse(t, `fo,o=bar,fib=baz`, expect, true)
|
||||
}
|
||||
|
||||
func checkParse(t *testing.T, s string, expect map[string]string, shouldErr bool) {
|
||||
m, err := parseCloudLabels(s)
|
||||
if err != nil {
|
||||
if shouldErr {
|
||||
return
|
||||
} else {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range expect {
|
||||
if m[k] != v {
|
||||
t.Errorf("Expected: %v, Got: %v", expect, m)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ kops create cluster \
|
|||
--master-security-groups sg-12345678,i-abcd1234 \
|
||||
--topology private \
|
||||
--networking weave \
|
||||
--cloud-labels "Team=Dev,Owner=John Doe" \
|
||||
--image 293135079892/k8s-1.4-debian-jessie-amd64-hvm-ebs-2016-11-16 \
|
||||
${NAME}
|
||||
```
|
||||
|
|
|
@ -41,6 +41,14 @@ Examples:
|
|||
|
||||
`--dns-zone=example.com` to use the hosted zone with a name of example.com
|
||||
|
||||
## cloud-labels
|
||||
|
||||
`cloud-labels` specifies tags for instance groups in AWS. The supported format is a CSV list of key=value pairs.
|
||||
Keys and values must not contain embedded commas but they may contain equals signs ('=') as long as the field is
|
||||
quoted:
|
||||
* `--cloud-labels "Project=\"Name=Foo Customer=Acme\",Owner=Jane Doe"` will be parsed as {Project:"Name=Foo Customer=Acme",
|
||||
Owner: "Jane Doe"}
|
||||
|
||||
## UpdatePolicy
|
||||
|
||||
Cluster.Spec.UpdatePolicy
|
||||
|
|
|
@ -19,6 +19,7 @@ kops create cluster
|
|||
--bastion Pass the --bastion flag to enable a bastion instance group. Only applies to private topology.
|
||||
--channel string Channel for default versions and configuration to use (default "stable")
|
||||
--cloud string Cloud provider to use - gce, aws
|
||||
--cloud-labels string A list of KV pairs used to tag all instance groups in AWS (eg "Owner=John Doe,Team=Some Team").
|
||||
--dns string DNS hosted zone to use: public|private. Default is 'public'. (default "Public")
|
||||
--dns-zone string DNS hosted zone to use (defaults to longest matching zone)
|
||||
--image string Image to use
|
||||
|
|
|
@ -237,6 +237,9 @@ type ClusterSpec struct {
|
|||
|
||||
// API field controls how the API is exposed outside the cluster
|
||||
API *AccessSpec `json:"api,omitempty"`
|
||||
|
||||
// Tags for AWS instance groups
|
||||
CloudLabels map[string]string `json:"cloudLabels,omitempty"`
|
||||
}
|
||||
|
||||
type AccessSpec struct {
|
||||
|
|
|
@ -235,6 +235,9 @@ type ClusterSpec struct {
|
|||
|
||||
// API field controls how the API is exposed outside the cluster
|
||||
API *AccessSpec `json:"api,omitempty"`
|
||||
|
||||
// Tags for AWS instance groups
|
||||
CloudLabels map[string]string `json:"cloudLabels,omitempty"`
|
||||
}
|
||||
|
||||
type AccessSpec struct {
|
||||
|
|
|
@ -436,6 +436,7 @@ func autoConvert_v1alpha1_ClusterSpec_To_kops_ClusterSpec(in *ClusterSpec, out *
|
|||
} else {
|
||||
out.API = nil
|
||||
}
|
||||
out.CloudLabels = in.CloudLabels
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -573,6 +574,7 @@ func autoConvert_kops_ClusterSpec_To_v1alpha1_ClusterSpec(in *kops.ClusterSpec,
|
|||
} else {
|
||||
out.API = nil
|
||||
}
|
||||
out.CloudLabels = in.CloudLabels
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -155,6 +155,9 @@ type ClusterSpec struct {
|
|||
|
||||
// API field controls how the API is exposed outside the cluster
|
||||
API *AccessSpec `json:"api,omitempty"`
|
||||
|
||||
// Tags for AWS resources
|
||||
CloudLabels map[string]string `json:"cloudLabels,omitempty"`
|
||||
}
|
||||
|
||||
type AccessSpec struct {
|
||||
|
|
|
@ -472,6 +472,7 @@ func autoConvert_v1alpha2_ClusterSpec_To_kops_ClusterSpec(in *ClusterSpec, out *
|
|||
} else {
|
||||
out.API = nil
|
||||
}
|
||||
out.CloudLabels = in.CloudLabels
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -623,6 +624,7 @@ func autoConvert_kops_ClusterSpec_To_v1alpha2_ClusterSpec(in *kops.ClusterSpec,
|
|||
} else {
|
||||
out.API = nil
|
||||
}
|
||||
out.CloudLabels = in.CloudLabels
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -134,6 +134,11 @@ func (m *KopsModelContext) NodeInstanceGroups() []*kops.InstanceGroup {
|
|||
func (m *KopsModelContext) CloudTagsForInstanceGroup(ig *kops.InstanceGroup) (map[string]string, error) {
|
||||
labels := make(map[string]string)
|
||||
|
||||
// Apply any user-specified global labels first so they can be overridden by IG-specific labels
|
||||
for k, v := range m.Cluster.Spec.CloudLabels {
|
||||
labels[k] = v
|
||||
}
|
||||
|
||||
// Apply any user-specified labels
|
||||
for k, v := range ig.Spec.CloudLabels {
|
||||
labels[k] = v
|
||||
|
|
|
@ -10,6 +10,10 @@ spec:
|
|||
loadBalancer:
|
||||
type: Public
|
||||
channel: stable
|
||||
cloudLabels:
|
||||
Owner: John Doe
|
||||
dn: 'cn=John Doe: dc=example dc=com'
|
||||
foo/bar: fib+baz
|
||||
cloudProvider: aws
|
||||
configBase: memfs://tests/private.example.com
|
||||
etcdClusters:
|
||||
|
|
|
@ -8,6 +8,10 @@ spec:
|
|||
loadBalancer:
|
||||
type: Public
|
||||
channel: stable
|
||||
cloudLabels:
|
||||
Owner: John Doe
|
||||
dn: 'cn=John Doe: dc=example dc=com'
|
||||
foo/bar: fib+baz
|
||||
cloudProvider: aws
|
||||
configBase: memfs://tests/private.example.com
|
||||
etcdClusters:
|
||||
|
|
|
@ -12,3 +12,4 @@ MasterSecurityGroups:
|
|||
- sg-exampleid3
|
||||
- sg-exampleid4
|
||||
KubernetesVersion: v1.4.8
|
||||
cloudLabels: "Owner=John Doe,dn=\"cn=John Doe: dc=example dc=com\", foo/bar=fib+baz"
|
||||
|
|
Loading…
Reference in New Issue