/* Copyright 2016 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. */ /****************************************************************************** Template Functions are what map functions in the models, to internal logic in kops. This is the point where we connect static YAML configuration to dynamic runtime values in memory. When defining a new function: - Build the new function here - Define the new function in AddTo() dest["MyNewFunction"] = MyNewFunction // <-- Function Pointer ******************************************************************************/ package cloudup import ( "encoding/base64" "encoding/binary" "fmt" "github.com/golang/glog" api "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/model" "k8s.io/kops/util/pkg/vfs" "k8s.io/kubernetes/pkg/util/sets" "math/big" "net" "strings" "text/template" ) type TemplateFunctions struct { cluster *api.Cluster instanceGroups []*api.InstanceGroup tags sets.String region string modelContext *model.KopsModelContext } func (tf *TemplateFunctions) WellKnownServiceIP(id int) (net.IP, error) { _, cidr, err := net.ParseCIDR(tf.cluster.Spec.ServiceClusterIPRange) if err != nil { return nil, fmt.Errorf("error parsing ServiceClusterIPRange %q: %v", tf.cluster.Spec.ServiceClusterIPRange, err) } ip4 := cidr.IP.To4() if ip4 != nil { n := binary.BigEndian.Uint32(ip4) n += uint32(id) serviceIP := make(net.IP, len(ip4)) binary.BigEndian.PutUint32(serviceIP, n) return serviceIP, nil } ip6 := cidr.IP.To16() if ip6 != nil { baseIPInt := big.NewInt(0) baseIPInt.SetBytes(ip6) serviceIPInt := big.NewInt(0) serviceIPInt.Add(big.NewInt(int64(id)), baseIPInt) serviceIP := make(net.IP, len(ip6)) serviceIPBytes := serviceIPInt.Bytes() for i := range serviceIPBytes { serviceIP[len(serviceIP)-len(serviceIPBytes)+i] = serviceIPBytes[i] } return serviceIP, nil } return nil, fmt.Errorf("Unexpected IP address type for ServiceClusterIPRange: %s", tf.cluster.Spec.ServiceClusterIPRange) } // This will define the available functions we can use in our YAML models // If we are trying to get a new function implemented it MUST // be defined here. func (tf *TemplateFunctions) AddTo(dest template.FuncMap) { dest["SharedVPC"] = tf.SharedVPC // Remember that we may be on a different arch from the target. Hard-code for now. dest["Arch"] = func() string { return "amd64" } // Network topology definitions dest["IsTopologyPublic"] = tf.IsTopologyPublic dest["IsTopologyPrivate"] = tf.IsTopologyPrivate dest["IsTopologyPrivateMasters"] = tf.IsTopologyPrivateMasters dest["GetELBName32"] = tf.modelContext.GetELBName32 dest["WellKnownServiceIP"] = tf.WellKnownServiceIP dest["Base64Encode"] = func(s string) string { return base64.StdEncoding.EncodeToString([]byte(s)) } dest["replace"] = func(s, find, replace string) string { return strings.Replace(s, find, replace, -1) } dest["join"] = func(a []string, sep string) string { return strings.Join(a, sep) } dest["ClusterName"] = tf.modelContext.ClusterName dest["HasTag"] = tf.HasTag dest["Image"] = tf.Image dest["WithDefaultBool"] = func(v *bool, defaultValue bool) bool { if v != nil { return *v } return defaultValue } dest["GetInstanceGroup"] = tf.GetInstanceGroup dest["CloudTags"] = tf.modelContext.CloudTagsForInstanceGroup dest["KubeDNS"] = func() *api.KubeDNSConfig { return tf.cluster.Spec.KubeDNS } dest["DnsControllerArgv"] = tf.DnsControllerArgv } // SharedVPC is a simple helper function which makes the templates for a shared VPC clearer func (tf *TemplateFunctions) SharedVPC() bool { return tf.cluster.SharedVPC() } // These are the network topology functions. They are boolean logic for checking which type of // topology this cluster is set to be deployed with. func (tf *TemplateFunctions) IsTopologyPrivate() bool { return tf.cluster.IsTopologyPrivate() } func (tf *TemplateFunctions) IsTopologyPublic() bool { return tf.cluster.IsTopologyPublic() } func (tf *TemplateFunctions) IsTopologyPrivateMasters() bool { return tf.cluster.IsTopologyPrivateMasters() } // Image returns the docker image name for the specified component func (tf *TemplateFunctions) Image(component string) (string, error) { if component == "kube-dns" { // TODO: Once we are shipping different versions, start to use them return "gcr.io/google_containers/kubedns-amd64:1.3", nil } if !isBaseURL(tf.cluster.Spec.KubernetesVersion) { return "gcr.io/google_containers/" + component + ":" + "v" + tf.cluster.Spec.KubernetesVersion, nil } baseURL := tf.cluster.Spec.KubernetesVersion baseURL = strings.TrimSuffix(baseURL, "/") tagURL := baseURL + "/bin/linux/amd64/" + component + ".docker_tag" glog.V(2).Infof("Downloading docker tag for %s from: %s", component, tagURL) b, err := vfs.Context.ReadFile(tagURL) if err != nil { return "", fmt.Errorf("error reading tag file %q: %v", tagURL, err) } tag := strings.TrimSpace(string(b)) glog.V(2).Infof("Found tag %q for %q", tag, component) return "gcr.io/google_containers/" + component + ":" + tag, nil } // HasTag returns true if the specified tag is set func (tf *TemplateFunctions) HasTag(tag string) bool { _, found := tf.tags[tag] return found } // GetInstanceGroup returns the instance group with the specified name func (tf *TemplateFunctions) GetInstanceGroup(name string) (*api.InstanceGroup, error) { for _, ig := range tf.instanceGroups { if ig.ObjectMeta.Name == name { return ig, nil } } return nil, fmt.Errorf("InstanceGroup %q not found", name) } func (tf *TemplateFunctions) DnsControllerArgv() ([]string, error) { var argv []string argv = append(argv, "/usr/bin/dns-controller") argv = append(argv, "--watch-ingress=false") argv = append(argv, "--dns=aws-route53") zone := tf.cluster.Spec.DNSZone if zone != "" { if strings.Contains(zone, ".") { // match by name argv = append(argv, "--zone="+zone) } else { // match by id argv = append(argv, "--zone=*/"+zone) } } // permit wildcard updates argv = append(argv, "--zone=*/*") // Verbose, but not crazy logging argv = append(argv, "-v=2") return argv, nil }