From 6d8459166d0f63333b539ba4499eab37ace2004b Mon Sep 17 00:00:00 2001 From: Sandeep Rajan Date: Thu, 18 Jan 2018 15:28:02 -0500 Subject: [PATCH 1/8] kube-dns configmap translate --- cmd/kubeadm/app/phases/addons/dns/BUILD | 3 + cmd/kubeadm/app/phases/addons/dns/dns.go | 68 ++++++++- cmd/kubeadm/app/phases/addons/dns/dns_test.go | 142 +++++++++++++++++- .../app/phases/addons/dns/manifests.go | 17 ++- 4 files changed, 220 insertions(+), 10 deletions(-) diff --git a/cmd/kubeadm/app/phases/addons/dns/BUILD b/cmd/kubeadm/app/phases/addons/dns/BUILD index ef8c19b2b8dc9..6065970f27528 100644 --- a/cmd/kubeadm/app/phases/addons/dns/BUILD +++ b/cmd/kubeadm/app/phases/addons/dns/BUILD @@ -19,7 +19,9 @@ go_test( "//cmd/kubeadm/app/util:go_default_library", "//pkg/apis/core:go_default_library", "//pkg/util/version:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", "//vendor/k8s.io/client-go/testing:go_default_library", @@ -41,6 +43,7 @@ go_library( "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/apiclient:go_default_library", "//pkg/api/legacyscheme:go_default_library", + "//pkg/kubelet/types:go_default_library", "//pkg/util/version:go_default_library", "//vendor/k8s.io/api/apps/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/cmd/kubeadm/app/phases/addons/dns/dns.go b/cmd/kubeadm/app/phases/addons/dns/dns.go index 4f1db3d045e66..84a8219e44a51 100644 --- a/cmd/kubeadm/app/phases/addons/dns/dns.go +++ b/cmd/kubeadm/app/phases/addons/dns/dns.go @@ -18,7 +18,11 @@ package dns import ( "fmt" + "runtime" + "strings" + + "encoding/json" apps "k8s.io/api/apps/v1" "k8s.io/api/core/v1" @@ -33,12 +37,15 @@ import ( kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" "k8s.io/kubernetes/pkg/api/legacyscheme" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/util/version" ) const ( // KubeDNSServiceAccountName describes the name of the ServiceAccount for the kube-dns addon - KubeDNSServiceAccountName = "kube-dns" + KubeDNSServiceAccountName = "kube-dns" + kubeDNSStubDomain = "stubDomains" + kubeDNSUpstreamNameservers = "upstreamNameservers" ) // EnsureDNSAddon creates the kube-dns or CoreDNS addon @@ -139,11 +146,27 @@ func coreDNSAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interfac if err != nil { return fmt.Errorf("error when parsing CoreDNS deployment template: %v", err) } + kubeDNSConfigMap, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(kubeadmconstants.KubeDNS, metav1.GetOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + + stubDomain, err := translateStubDomainOfKubeDNSToProxyCoreDNS(kubeDNSStubDomain, kubeDNSConfigMap) + if err != nil { + return err + } + + upstreamNameserver, err := translateUpstreamNameServerOfKubeDNSToUpstreamProxyCoreDNS(kubeDNSUpstreamNameservers, kubeDNSConfigMap) + if err != nil { + return err + } // Get the config file for CoreDNS - coreDNSConfigMapBytes, err := kubeadmutil.ParseTemplate(CoreDNSConfigMap, struct{ DNSDomain, ServiceCIDR string }{ - ServiceCIDR: cfg.Networking.ServiceSubnet, - DNSDomain: cfg.Networking.DNSDomain, + coreDNSConfigMapBytes, err := kubeadmutil.ParseTemplate(CoreDNSConfigMap, struct{ DNSDomain, ServiceCIDR, UpstreamNameserver, StubDomain string }{ + ServiceCIDR: cfg.Networking.ServiceSubnet, + DNSDomain: cfg.Networking.DNSDomain, + UpstreamNameserver: upstreamNameserver, + StubDomain: stubDomain, }) if err != nil { return fmt.Errorf("error when parsing CoreDNS configMap template: %v", err) @@ -244,3 +267,40 @@ func createDNSService(dnsService *v1.Service, serviceBytes []byte, client client } return nil } + +// translateStubDomainOfKubeDNSToProxyCoreDNS translates StubDomain Data in kube-dns ConfigMap +// in the form of Proxy for the CoreDNS Corefile. +func translateStubDomainOfKubeDNSToProxyCoreDNS(dataField string, kubeDNSConfigMap *v1.ConfigMap) (string, error) { + if proxy, ok := kubeDNSConfigMap.Data[dataField]; ok { + stubDomainData := make(map[string][]string) + proxyStanzaList := coreDNSProxyStanzaPrefix + + err := json.Unmarshal([]byte(proxy), &stubDomainData) + if err != nil { + return "", fmt.Errorf("failed to parse JSON from 'kube-dns ConfigMap: %v", err) + } + + for domain, proxyIP := range stubDomainData { + proxyStanzaList = proxyStanzaList + domain + coreDNSProxyDefaultPort + coreDNSCorefileDefaultData + strings.Join(proxyIP, " ") + coreDNSProxyStanzaSuffix + } + return proxyStanzaList, nil + } + return "", nil +} + +// translateUpstreamNameServerOfKubeDNSToUpstreamProxyCoreDNS translates UpstreamNameServer Data in kube-dns ConfigMap +// in the form of Proxy for the CoreDNS Corefile. +func translateUpstreamNameServerOfKubeDNSToUpstreamProxyCoreDNS(dataField string, kubeDNSConfigMap *v1.ConfigMap) (string, error) { + if upstreamValues, ok := kubeDNSConfigMap.Data[dataField]; ok { + var upstreamProxyIP []string + + err := json.Unmarshal([]byte(upstreamValues), &upstreamProxyIP) + if err != nil { + return "", fmt.Errorf("failed to parse JSON from 'kube-dns ConfigMap: %v", err) + } + + coreDNSProxyStanzaList := strings.Join(upstreamProxyIP, " ") + return coreDNSProxyStanzaList, nil + } + return kubetypes.ResolvConfDefault, nil +} diff --git a/cmd/kubeadm/app/phases/addons/dns/dns_test.go b/cmd/kubeadm/app/phases/addons/dns/dns_test.go index c144dc49e0454..91a19a36bd748 100644 --- a/cmd/kubeadm/app/phases/addons/dns/dns_test.go +++ b/cmd/kubeadm/app/phases/addons/dns/dns_test.go @@ -17,9 +17,12 @@ limitations under the License. package dns import ( + "strings" "testing" + "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" clientsetfake "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" @@ -127,9 +130,11 @@ func TestCompileManifests(t *testing.T) { }, { manifest: CoreDNSConfigMap, - data: struct{ DNSDomain, ServiceCIDR string }{ - DNSDomain: "foo", - ServiceCIDR: "foo", + data: struct{ DNSDomain, ServiceCIDR, UpstreamNameserver, StubDomain string }{ + DNSDomain: "foo", + ServiceCIDR: "foo", + UpstreamNameserver: "foo", + StubDomain: "foo", }, expected: true, }, @@ -175,3 +180,134 @@ func TestGetDNSIP(t *testing.T) { } } } + +func TestTranslateStubDomainKubeDNSToCoreDNS(t *testing.T) { + testCases := []struct { + configMap *v1.ConfigMap + expect string + }{ + { + configMap: &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kube-dns", + Namespace: "kube-system", + }, + Data: map[string]string{ + "stubDomains": `{"foo.com" : ["1.2.3.4:5300","3.3.3.3"], "my.cluster.local" : ["2.3.4.5"]}`, + "upstreamNameservers": `["8.8.8.8", "8.8.4.4"]`, + }, + }, + + expect: ` + foo.com:53 { + errors + cache 30 + proxy . 1.2.3.4:5300 3.3.3.3 + } + my.cluster.local:53 { + errors + cache 30 + proxy . 2.3.4.5 + } + `, + }, + { + configMap: &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kubedns", + Namespace: "kube-system", + }, + }, + + expect: "", + }, + { + configMap: &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kube-dns", + Namespace: "kube-system", + }, + Data: map[string]string{ + "stubDomains": `{"foo.com" : ["1.2.3.4:5300"], "my.cluster.local" : ["2.3.4.5"]}`, + "upstreamNameservers": `["8.8.8.8", "8.8.4.4"]`, + }, + }, + + expect: ` + foo.com:53 { + errors + cache 30 + proxy . 1.2.3.4:5300 + } + my.cluster.local:53 { + errors + cache 30 + proxy . 2.3.4.5 + } + `, + }, + } + for _, testCase := range testCases { + out, err := translateStubDomainOfKubeDNSToProxyCoreDNS(kubeDNSStubDomain, testCase.configMap) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if !strings.Contains(out, testCase.expect) { + t.Errorf("expected to find %q in output: %q", testCase.expect, out) + } + } +} + +func TestTranslateUpstreamKubeDNSToCoreDNS(t *testing.T) { + testCases := []struct { + configMap *v1.ConfigMap + expect string + }{ + { + configMap: &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kube-dns", + Namespace: "kube-system", + }, + }, + + expect: "/etc/resolv.conf", + }, + { + configMap: &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kubedns", + Namespace: "kube-system", + }, + Data: map[string]string{ + "stubDomains": ` {"foo.com" : ["1.2.3.4:5300"], "my.cluster.local" : ["2.3.4.5"]}`, + "upstreamNameservers": `["8.8.8.8", "8.8.4.4", "4.4.4.4"]`, + }, + }, + + expect: "8.8.8.8 8.8.4.4 4.4.4.4", + }, + { + configMap: &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kubedns", + Namespace: "kube-system", + }, + Data: map[string]string{ + "upstreamNameservers": `["8.8.8.8", "8.8.4.4"]`, + }, + }, + + expect: "8.8.8.8 8.8.4.4", + }, + } + for _, testCase := range testCases { + out, err := translateUpstreamNameServerOfKubeDNSToUpstreamProxyCoreDNS(kubeDNSUpstreamNameservers, testCase.configMap) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if !strings.Contains(out, testCase.expect) { + t.Errorf("expected to find %q in output: %q", testCase.expect, out) + } + } +} diff --git a/cmd/kubeadm/app/phases/addons/dns/manifests.go b/cmd/kubeadm/app/phases/addons/dns/manifests.go index b3d1ee8f07d75..69a493d05de07 100644 --- a/cmd/kubeadm/app/phases/addons/dns/manifests.go +++ b/cmd/kubeadm/app/phases/addons/dns/manifests.go @@ -309,13 +309,13 @@ data: health kubernetes {{ .DNSDomain }} {{ .ServiceCIDR }} { pods insecure - upstream /etc/resolv.conf + upstream {{ .UpstreamNameserver }} fallthrough in-addr.arpa ip6.arpa } prometheus :9153 - proxy . /etc/resolv.conf + proxy . {{ .UpstreamNameserver }} cache 30 - } + }{{ .StubDomain }} ` // CoreDNSClusterRole is the CoreDNS ClusterRole manifest CoreDNSClusterRole = ` @@ -358,4 +358,15 @@ metadata: name: coredns namespace: kube-system ` + // coreDNSProxyDefaultPort, coreDNSCorefileDefaultData, coreDNSProxyStanzaSuffix is used in the translation of configMap of kube-dns to CoreDNS. + coreDNSProxyStanzaPrefix = ` + ` + coreDNSProxyDefaultPort = `:53` + coreDNSCorefileDefaultData = ` { + errors + cache 30 + proxy . ` + coreDNSProxyStanzaSuffix = ` + } + ` ) From d2e83a2b07e488bb4eb95c991d7cb392147b8975 Mon Sep 17 00:00:00 2001 From: Sandeep Rajan Date: Fri, 9 Feb 2018 15:26:14 -0500 Subject: [PATCH 2/8] add federations translation --- cmd/kubeadm/app/phases/addons/dns/dns.go | 35 ++++++++-- cmd/kubeadm/app/phases/addons/dns/dns_test.go | 64 ++++++++++++++++++- .../app/phases/addons/dns/manifests.go | 15 +++-- 3 files changed, 101 insertions(+), 13 deletions(-) diff --git a/cmd/kubeadm/app/phases/addons/dns/dns.go b/cmd/kubeadm/app/phases/addons/dns/dns.go index 84a8219e44a51..d07cfb0868aeb 100644 --- a/cmd/kubeadm/app/phases/addons/dns/dns.go +++ b/cmd/kubeadm/app/phases/addons/dns/dns.go @@ -46,6 +46,7 @@ const ( KubeDNSServiceAccountName = "kube-dns" kubeDNSStubDomain = "stubDomains" kubeDNSUpstreamNameservers = "upstreamNameservers" + kubeDNSFederation = "federations" ) // EnsureDNSAddon creates the kube-dns or CoreDNS addon @@ -160,12 +161,17 @@ func coreDNSAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interfac if err != nil { return err } + coreDNSDomain := cfg.Networking.DNSDomain + federations, err := translateFederationsofKubeDNSToCoreDNS(kubeDNSFederation, coreDNSDomain, kubeDNSConfigMap) + if err != nil { + return err + } // Get the config file for CoreDNS - coreDNSConfigMapBytes, err := kubeadmutil.ParseTemplate(CoreDNSConfigMap, struct{ DNSDomain, ServiceCIDR, UpstreamNameserver, StubDomain string }{ - ServiceCIDR: cfg.Networking.ServiceSubnet, - DNSDomain: cfg.Networking.DNSDomain, + coreDNSConfigMapBytes, err := kubeadmutil.ParseTemplate(CoreDNSConfigMap, struct{ DNSDomain, UpstreamNameserver, Federation, StubDomain string }{ + DNSDomain: coreDNSDomain, UpstreamNameserver: upstreamNameserver, + Federation: federations, StubDomain: stubDomain, }) if err != nil { @@ -273,7 +279,7 @@ func createDNSService(dnsService *v1.Service, serviceBytes []byte, client client func translateStubDomainOfKubeDNSToProxyCoreDNS(dataField string, kubeDNSConfigMap *v1.ConfigMap) (string, error) { if proxy, ok := kubeDNSConfigMap.Data[dataField]; ok { stubDomainData := make(map[string][]string) - proxyStanzaList := coreDNSProxyStanzaPrefix + proxyStanzaList := coreDNSStanzaPrefix err := json.Unmarshal([]byte(proxy), &stubDomainData) if err != nil { @@ -304,3 +310,24 @@ func translateUpstreamNameServerOfKubeDNSToUpstreamProxyCoreDNS(dataField string } return kubetypes.ResolvConfDefault, nil } + +// translateFederationofKubeDNSToCoreDNS translates Federations Data in kube-dns ConfigMap +// to Federation for CoreDNS Corefile. +func translateFederationsofKubeDNSToCoreDNS(dataField, coreDNSDomain string, kubeDNSConfigMap *v1.ConfigMap) (string, error) { + if federation, ok := kubeDNSConfigMap.Data[dataField]; ok { + federationData := make(map[string]string) + + err := json.Unmarshal([]byte(federation), &federationData) + if err != nil { + return "", fmt.Errorf("failed to parse JSON from kube-dns ConfigMap: %v", err) + } + federationStanzaList := coreDNSStanzaPrefix + coreDNSFederation + coreDNSDomain + coreDNSFederationStanzaPrefix + var federationStanzaContents string + for name, domain := range federationData { + federationStanzaContents = federationStanzaContents + coreDNSFederationStanzaMid + name + " " + domain + } + federationStanzaList = federationStanzaList + federationStanzaContents + coreDNSFederationStanzaSuffix + return federationStanzaList, nil + } + return "", nil +} diff --git a/cmd/kubeadm/app/phases/addons/dns/dns_test.go b/cmd/kubeadm/app/phases/addons/dns/dns_test.go index 91a19a36bd748..7b9ca5f119b29 100644 --- a/cmd/kubeadm/app/phases/addons/dns/dns_test.go +++ b/cmd/kubeadm/app/phases/addons/dns/dns_test.go @@ -130,9 +130,9 @@ func TestCompileManifests(t *testing.T) { }, { manifest: CoreDNSConfigMap, - data: struct{ DNSDomain, ServiceCIDR, UpstreamNameserver, StubDomain string }{ + data: struct{ DNSDomain, Federation, UpstreamNameserver, StubDomain string }{ DNSDomain: "foo", - ServiceCIDR: "foo", + Federation: "foo", UpstreamNameserver: "foo", StubDomain: "foo", }, @@ -311,3 +311,63 @@ func TestTranslateUpstreamKubeDNSToCoreDNS(t *testing.T) { } } } + +func TestTranslateFederationKubeDNSToCoreDNS(t *testing.T) { + testCases := []struct { + configMap *v1.ConfigMap + expect string + }{ + { + configMap: &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kube-dns", + Namespace: "kube-system", + }, + Data: map[string]string{ + "federations": `{"foo" : "foo.feddomain.com", "bar" : "bar.feddomain.com"}`, + "stubDomains": `{"foo.com" : ["1.2.3.4:5300","3.3.3.3"], "my.cluster.local" : ["2.3.4.5"]}`, + "upstreamNameservers": `["8.8.8.8", "8.8.4.4"]`, + }, + }, + + expect: ` + federation cluster.local { + foo foo.feddomain.com + bar bar.feddomain.com + }`, + }, + { + configMap: &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kubedns", + Namespace: "kube-system", + }, + }, + + expect: "", + }, + { + configMap: &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kube-dns", + Namespace: "kube-system", + }, + Data: map[string]string{ + "stubDomains": `{"foo.com" : ["1.2.3.4:5300"], "my.cluster.local" : ["2.3.4.5"]}`, + "upstreamNameservers": `["8.8.8.8", "8.8.4.4"]`, + }, + }, + + expect: "", + }, + } + for _, testCase := range testCases { + out, err := translateFederationsofKubeDNSToCoreDNS(kubeDNSFederation, "cluster.local", testCase.configMap) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if !strings.Contains(out, testCase.expect) { + t.Errorf("expected to find %q in output: %q", testCase.expect, out) + } + } +} diff --git a/cmd/kubeadm/app/phases/addons/dns/manifests.go b/cmd/kubeadm/app/phases/addons/dns/manifests.go index 69a493d05de07..f3a06db5bbc4c 100644 --- a/cmd/kubeadm/app/phases/addons/dns/manifests.go +++ b/cmd/kubeadm/app/phases/addons/dns/manifests.go @@ -307,11 +307,11 @@ data: .:53 { errors health - kubernetes {{ .DNSDomain }} {{ .ServiceCIDR }} { + kubernetes {{ .DNSDomain }} in-addr.arpa ip6.arpa { pods insecure upstream {{ .UpstreamNameserver }} fallthrough in-addr.arpa ip6.arpa - } + }{{ .Federation }} prometheus :9153 proxy . {{ .UpstreamNameserver }} cache 30 @@ -359,14 +359,15 @@ metadata: namespace: kube-system ` // coreDNSProxyDefaultPort, coreDNSCorefileDefaultData, coreDNSProxyStanzaSuffix is used in the translation of configMap of kube-dns to CoreDNS. - coreDNSProxyStanzaPrefix = ` - ` + coreDNSStanzaPrefix = "\n " coreDNSProxyDefaultPort = `:53` coreDNSCorefileDefaultData = ` { errors cache 30 proxy . ` - coreDNSProxyStanzaSuffix = ` - } - ` + coreDNSProxyStanzaSuffix = "\n }\n " + coreDNSFederation = "federation " + coreDNSFederationStanzaPrefix = " {" + coreDNSFederationStanzaMid = "\n " + coreDNSFederationStanzaSuffix = "\n }" ) From 73378fe6eb7715ebdc209c790e4aedae28f05c93 Mon Sep 17 00:00:00 2001 From: Sandeep Rajan Date: Fri, 9 Feb 2018 16:23:25 -0500 Subject: [PATCH 3/8] improve tests --- cmd/kubeadm/app/phases/addons/dns/dns_test.go | 68 +++++++++++++++---- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/cmd/kubeadm/app/phases/addons/dns/dns_test.go b/cmd/kubeadm/app/phases/addons/dns/dns_test.go index 7b9ca5f119b29..db84e594fd3ca 100644 --- a/cmd/kubeadm/app/phases/addons/dns/dns_test.go +++ b/cmd/kubeadm/app/phases/addons/dns/dns_test.go @@ -184,7 +184,8 @@ func TestGetDNSIP(t *testing.T) { func TestTranslateStubDomainKubeDNSToCoreDNS(t *testing.T) { testCases := []struct { configMap *v1.ConfigMap - expect string + expectOne string + expectTwo string }{ { configMap: &v1.ConfigMap{ @@ -198,7 +199,7 @@ func TestTranslateStubDomainKubeDNSToCoreDNS(t *testing.T) { }, }, - expect: ` + expectOne: ` foo.com:53 { errors cache 30 @@ -209,6 +210,18 @@ func TestTranslateStubDomainKubeDNSToCoreDNS(t *testing.T) { cache 30 proxy . 2.3.4.5 } + `, + expectTwo: ` + my.cluster.local:53 { + errors + cache 30 + proxy . 2.3.4.5 + } + foo.com:53 { + errors + cache 30 + proxy . 1.2.3.4:5300 3.3.3.3 + } `, }, { @@ -219,7 +232,7 @@ func TestTranslateStubDomainKubeDNSToCoreDNS(t *testing.T) { }, }, - expect: "", + expectOne: "", }, { configMap: &v1.ConfigMap{ @@ -233,7 +246,7 @@ func TestTranslateStubDomainKubeDNSToCoreDNS(t *testing.T) { }, }, - expect: ` + expectOne: ` foo.com:53 { errors cache 30 @@ -245,6 +258,31 @@ func TestTranslateStubDomainKubeDNSToCoreDNS(t *testing.T) { proxy . 2.3.4.5 } `, + expectTwo: ` + my.cluster.local:53 { + errors + cache 30 + proxy . 2.3.4.5 + } + foo.com:53 { + errors + cache 30 + proxy . 1.2.3.4:5300 + } + `, + }, + { + configMap: &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kube-dns", + Namespace: "kube-system", + }, + Data: map[string]string{ + "upstreamNameservers": `["8.8.8.8", "8.8.4.4"]`, + }, + }, + + expectOne: "", }, } for _, testCase := range testCases { @@ -252,8 +290,8 @@ func TestTranslateStubDomainKubeDNSToCoreDNS(t *testing.T) { if err != nil { t.Errorf("unexpected error: %v", err) } - if !strings.Contains(out, testCase.expect) { - t.Errorf("expected to find %q in output: %q", testCase.expect, out) + if !strings.Contains(out, testCase.expectOne) && !strings.Contains(out, testCase.expectTwo) { + t.Errorf("expected to find %q or %q in output: %q", testCase.expectOne, testCase.expectTwo, out) } } } @@ -315,7 +353,8 @@ func TestTranslateUpstreamKubeDNSToCoreDNS(t *testing.T) { func TestTranslateFederationKubeDNSToCoreDNS(t *testing.T) { testCases := []struct { configMap *v1.ConfigMap - expect string + expectOne string + expectTwo string }{ { configMap: &v1.ConfigMap{ @@ -330,10 +369,15 @@ func TestTranslateFederationKubeDNSToCoreDNS(t *testing.T) { }, }, - expect: ` + expectOne: ` federation cluster.local { foo foo.feddomain.com bar bar.feddomain.com + }`, + expectTwo: ` + federation cluster.local { + bar bar.feddomain.com + foo foo.feddomain.com }`, }, { @@ -344,7 +388,7 @@ func TestTranslateFederationKubeDNSToCoreDNS(t *testing.T) { }, }, - expect: "", + expectOne: "", }, { configMap: &v1.ConfigMap{ @@ -358,7 +402,7 @@ func TestTranslateFederationKubeDNSToCoreDNS(t *testing.T) { }, }, - expect: "", + expectOne: "", }, } for _, testCase := range testCases { @@ -366,8 +410,8 @@ func TestTranslateFederationKubeDNSToCoreDNS(t *testing.T) { if err != nil { t.Errorf("unexpected error: %v", err) } - if !strings.Contains(out, testCase.expect) { - t.Errorf("expected to find %q in output: %q", testCase.expect, out) + if !strings.Contains(out, testCase.expectOne) && !strings.Contains(out, testCase.expectTwo) { + t.Errorf("expected to find %q or %q in output: %q", testCase.expectOne, testCase.expectTwo, out) } } } From 8107f155717631ab4bdb3809b2d19cade0690401 Mon Sep 17 00:00:00 2001 From: Sandeep Rajan Date: Mon, 12 Feb 2018 19:05:07 -0500 Subject: [PATCH 4/8] use caddy for translation --- cmd/kubeadm/app/phases/addons/dns/BUILD | 2 +- cmd/kubeadm/app/phases/addons/dns/dns.go | 71 ++++++++++++++++--- .../app/phases/addons/dns/manifests.go | 12 ---- 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/cmd/kubeadm/app/phases/addons/dns/BUILD b/cmd/kubeadm/app/phases/addons/dns/BUILD index 6065970f27528..6127279ee5a72 100644 --- a/cmd/kubeadm/app/phases/addons/dns/BUILD +++ b/cmd/kubeadm/app/phases/addons/dns/BUILD @@ -43,8 +43,8 @@ go_library( "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/apiclient:go_default_library", "//pkg/api/legacyscheme:go_default_library", - "//pkg/kubelet/types:go_default_library", "//pkg/util/version:go_default_library", + "//vendor/github.com/mholt/caddy/caddyfile:go_default_library", "//vendor/k8s.io/api/apps/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/rbac/v1:go_default_library", diff --git a/cmd/kubeadm/app/phases/addons/dns/dns.go b/cmd/kubeadm/app/phases/addons/dns/dns.go index d07cfb0868aeb..744aed84bffcd 100644 --- a/cmd/kubeadm/app/phases/addons/dns/dns.go +++ b/cmd/kubeadm/app/phases/addons/dns/dns.go @@ -17,12 +17,12 @@ limitations under the License. package dns import ( + "encoding/json" "fmt" - "runtime" "strings" - "encoding/json" + "github.com/mholt/caddy/caddyfile" apps "k8s.io/api/apps/v1" "k8s.io/api/core/v1" @@ -37,7 +37,6 @@ import ( kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" "k8s.io/kubernetes/pkg/api/legacyscheme" - kubetypes "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/util/version" ) @@ -47,6 +46,7 @@ const ( kubeDNSStubDomain = "stubDomains" kubeDNSUpstreamNameservers = "upstreamNameservers" kubeDNSFederation = "federations" + coreDNSStanzaFormat = "\n " ) // EnsureDNSAddon creates the kube-dns or CoreDNS addon @@ -147,6 +147,8 @@ func coreDNSAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interfac if err != nil { return fmt.Errorf("error when parsing CoreDNS deployment template: %v", err) } + + // Get the kube-dns ConfigMap for translation to equivalent CoreDNS Config. kubeDNSConfigMap, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(kubeadmconstants.KubeDNS, metav1.GetOptions{}) if err != nil && !apierrors.IsNotFound(err) { return err @@ -279,16 +281,39 @@ func createDNSService(dnsService *v1.Service, serviceBytes []byte, client client func translateStubDomainOfKubeDNSToProxyCoreDNS(dataField string, kubeDNSConfigMap *v1.ConfigMap) (string, error) { if proxy, ok := kubeDNSConfigMap.Data[dataField]; ok { stubDomainData := make(map[string][]string) - proxyStanzaList := coreDNSStanzaPrefix + var proxyStanzaList string err := json.Unmarshal([]byte(proxy), &stubDomainData) if err != nil { return "", fmt.Errorf("failed to parse JSON from 'kube-dns ConfigMap: %v", err) } + var proxyStanza []interface{} for domain, proxyIP := range stubDomainData { - proxyStanzaList = proxyStanzaList + domain + coreDNSProxyDefaultPort + coreDNSCorefileDefaultData + strings.Join(proxyIP, " ") + coreDNSProxyStanzaSuffix + strings.Join(proxyIP, " ") + pStanza := map[string]interface{}{} + pStanza["keys"] = []string{domain + ":53"} + pStanza["body"] = [][]string{ + {"errors"}, + {"cache", "30"}, + {"proxy", ".", strings.Join(proxyIP, " ")}, + } + + proxyStanza = append(proxyStanza, pStanza) + } + stanzasBytes, err := json.Marshal(proxyStanza) + if err != nil { + return "", err } + + outputJSON, err := caddyfile.FromJSON(stanzasBytes) + if err != nil { + return "", err + } + // This is required to format the Corefile, otherwise errors due to bad yaml format. + output := strings.NewReplacer("\n\t", "\n ", "\"", "", "\n}", "\n }", "\n\n", "\n ").Replace(string(outputJSON)) + proxyStanzaList = coreDNSStanzaFormat + output + coreDNSStanzaFormat + return proxyStanzaList, nil } return "", nil @@ -308,26 +333,50 @@ func translateUpstreamNameServerOfKubeDNSToUpstreamProxyCoreDNS(dataField string coreDNSProxyStanzaList := strings.Join(upstreamProxyIP, " ") return coreDNSProxyStanzaList, nil } - return kubetypes.ResolvConfDefault, nil + return "/etc/resolv.conf", nil } -// translateFederationofKubeDNSToCoreDNS translates Federations Data in kube-dns ConfigMap +// translateFederationsofKubeDNSToCoreDNS translates Federations Data in kube-dns ConfigMap // to Federation for CoreDNS Corefile. func translateFederationsofKubeDNSToCoreDNS(dataField, coreDNSDomain string, kubeDNSConfigMap *v1.ConfigMap) (string, error) { if federation, ok := kubeDNSConfigMap.Data[dataField]; ok { + var ( + federationStanza []interface{} + fData []string + ) federationData := make(map[string]string) err := json.Unmarshal([]byte(federation), &federationData) if err != nil { return "", fmt.Errorf("failed to parse JSON from kube-dns ConfigMap: %v", err) } - federationStanzaList := coreDNSStanzaPrefix + coreDNSFederation + coreDNSDomain + coreDNSFederationStanzaPrefix - var federationStanzaContents string + for name, domain := range federationData { - federationStanzaContents = federationStanzaContents + coreDNSFederationStanzaMid + name + " " + domain + fData = append(fData, name+" "+domain) } - federationStanzaList = federationStanzaList + federationStanzaContents + coreDNSFederationStanzaSuffix + fStanza := map[string]interface{}{} + fStanza["body"] = [][]string{ + {strings.Join(fData, "\n ")}, + } + federationStanza = append(federationStanza, fStanza) + fStanza["keys"] = []string{"federation " + coreDNSDomain} + + stanzasBytes, err := json.Marshal(federationStanza) + if err != nil { + return "", err + } + + outputJSON, err := caddyfile.FromJSON(stanzasBytes) + if err != nil { + return "", err + } + + // This is required to format the Corefile, otherwise errors due to bad yaml format. + output := strings.NewReplacer("\n\t", "\n ", "\"", "", "\n}", "\n }").Replace(string(outputJSON)) + federationStanzaList := coreDNSStanzaFormat + output + return federationStanzaList, nil + } return "", nil } diff --git a/cmd/kubeadm/app/phases/addons/dns/manifests.go b/cmd/kubeadm/app/phases/addons/dns/manifests.go index f3a06db5bbc4c..4d696f28c7c81 100644 --- a/cmd/kubeadm/app/phases/addons/dns/manifests.go +++ b/cmd/kubeadm/app/phases/addons/dns/manifests.go @@ -358,16 +358,4 @@ metadata: name: coredns namespace: kube-system ` - // coreDNSProxyDefaultPort, coreDNSCorefileDefaultData, coreDNSProxyStanzaSuffix is used in the translation of configMap of kube-dns to CoreDNS. - coreDNSStanzaPrefix = "\n " - coreDNSProxyDefaultPort = `:53` - coreDNSCorefileDefaultData = ` { - errors - cache 30 - proxy . ` - coreDNSProxyStanzaSuffix = "\n }\n " - coreDNSFederation = "federation " - coreDNSFederationStanzaPrefix = " {" - coreDNSFederationStanzaMid = "\n " - coreDNSFederationStanzaSuffix = "\n }" ) From f1428e9effb5108780cb9c4b066c875eec995579 Mon Sep 17 00:00:00 2001 From: Sandeep Rajan Date: Wed, 14 Feb 2018 11:23:08 -0500 Subject: [PATCH 5/8] vendor caddy --- Godeps/Godeps.json | 5 + Godeps/LICENSES | 209 ++++++++ vendor/BUILD | 1 + vendor/github.com/mholt/caddy/LICENSE.txt | 201 +++++++ vendor/github.com/mholt/caddy/caddyfile/BUILD | 27 + .../mholt/caddy/caddyfile/dispenser.go | 260 +++++++++ .../github.com/mholt/caddy/caddyfile/json.go | 198 +++++++ .../github.com/mholt/caddy/caddyfile/lexer.go | 150 ++++++ .../github.com/mholt/caddy/caddyfile/parse.go | 497 ++++++++++++++++++ 9 files changed, 1548 insertions(+) create mode 100644 vendor/github.com/mholt/caddy/LICENSE.txt create mode 100644 vendor/github.com/mholt/caddy/caddyfile/BUILD create mode 100644 vendor/github.com/mholt/caddy/caddyfile/dispenser.go create mode 100644 vendor/github.com/mholt/caddy/caddyfile/json.go create mode 100644 vendor/github.com/mholt/caddy/caddyfile/lexer.go create mode 100644 vendor/github.com/mholt/caddy/caddyfile/parse.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 0cfd0cb41a52a..47fe4e5de073a 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -2092,6 +2092,11 @@ "ImportPath": "github.com/matttproud/golang_protobuf_extensions/pbutil", "Rev": "fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a" }, + { + "ImportPath": "github.com/mholt/caddy/caddyfile", + "Comment": "v0.10.10-57-g2de4950", + "Rev": "2de495001514ed50eac2e09f474c32d417100c5c" + }, { "ImportPath": "github.com/miekg/dns", "Rev": "5d001d020961ae1c184f9f8152fdc73810481677" diff --git a/Godeps/LICENSES b/Godeps/LICENSES index 6a6224ec110b9..9e43fac7227e9 100644 --- a/Godeps/LICENSES +++ b/Godeps/LICENSES @@ -72089,6 +72089,215 @@ THE SOFTWARE. ================================================================================ +================================================================================ += vendor/github.com/mholt/caddy/caddyfile licensed under: = + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. + += vendor/github.com/mholt/caddy/LICENSE.txt e3fc50a88d0a364313df4b21ef20c29e +================================================================================ + + ================================================================================ = vendor/github.com/Microsoft/go-winio licensed under: = diff --git a/vendor/BUILD b/vendor/BUILD index 3eb12bb641565..3271831dfc908 100644 --- a/vendor/BUILD +++ b/vendor/BUILD @@ -288,6 +288,7 @@ filegroup( "//vendor/github.com/mailru/easyjson/jlexer:all-srcs", "//vendor/github.com/mailru/easyjson/jwriter:all-srcs", "//vendor/github.com/matttproud/golang_protobuf_extensions/pbutil:all-srcs", + "//vendor/github.com/mholt/caddy/caddyfile:all-srcs", "//vendor/github.com/miekg/dns:all-srcs", "//vendor/github.com/mindprince/gonvml:all-srcs", "//vendor/github.com/mistifyio/go-zfs:all-srcs", diff --git a/vendor/github.com/mholt/caddy/LICENSE.txt b/vendor/github.com/mholt/caddy/LICENSE.txt new file mode 100644 index 0000000000000..8dada3edaf50d --- /dev/null +++ b/vendor/github.com/mholt/caddy/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/vendor/github.com/mholt/caddy/caddyfile/BUILD b/vendor/github.com/mholt/caddy/caddyfile/BUILD new file mode 100644 index 0000000000000..b291c638b3c60 --- /dev/null +++ b/vendor/github.com/mholt/caddy/caddyfile/BUILD @@ -0,0 +1,27 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "dispenser.go", + "json.go", + "lexer.go", + "parse.go", + ], + importpath = "github.com/mholt/caddy/caddyfile", + visibility = ["//visibility:public"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/vendor/github.com/mholt/caddy/caddyfile/dispenser.go b/vendor/github.com/mholt/caddy/caddyfile/dispenser.go new file mode 100644 index 0000000000000..c7b3f4c120897 --- /dev/null +++ b/vendor/github.com/mholt/caddy/caddyfile/dispenser.go @@ -0,0 +1,260 @@ +// Copyright 2015 Light Code Labs, LLC +// +// 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 caddyfile + +import ( + "errors" + "fmt" + "io" + "strings" +) + +// Dispenser is a type that dispenses tokens, similarly to a lexer, +// except that it can do so with some notion of structure and has +// some really convenient methods. +type Dispenser struct { + filename string + tokens []Token + cursor int + nesting int +} + +// NewDispenser returns a Dispenser, ready to use for parsing the given input. +func NewDispenser(filename string, input io.Reader) Dispenser { + tokens, _ := allTokens(input) // ignoring error because nothing to do with it + return Dispenser{ + filename: filename, + tokens: tokens, + cursor: -1, + } +} + +// NewDispenserTokens returns a Dispenser filled with the given tokens. +func NewDispenserTokens(filename string, tokens []Token) Dispenser { + return Dispenser{ + filename: filename, + tokens: tokens, + cursor: -1, + } +} + +// Next loads the next token. Returns true if a token +// was loaded; false otherwise. If false, all tokens +// have been consumed. +func (d *Dispenser) Next() bool { + if d.cursor < len(d.tokens)-1 { + d.cursor++ + return true + } + return false +} + +// NextArg loads the next token if it is on the same +// line. Returns true if a token was loaded; false +// otherwise. If false, all tokens on the line have +// been consumed. It handles imported tokens correctly. +func (d *Dispenser) NextArg() bool { + if d.cursor < 0 { + d.cursor++ + return true + } + if d.cursor >= len(d.tokens) { + return false + } + if d.cursor < len(d.tokens)-1 && + d.tokens[d.cursor].File == d.tokens[d.cursor+1].File && + d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) == d.tokens[d.cursor+1].Line { + d.cursor++ + return true + } + return false +} + +// NextLine loads the next token only if it is not on the same +// line as the current token, and returns true if a token was +// loaded; false otherwise. If false, there is not another token +// or it is on the same line. It handles imported tokens correctly. +func (d *Dispenser) NextLine() bool { + if d.cursor < 0 { + d.cursor++ + return true + } + if d.cursor >= len(d.tokens) { + return false + } + if d.cursor < len(d.tokens)-1 && + (d.tokens[d.cursor].File != d.tokens[d.cursor+1].File || + d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) < d.tokens[d.cursor+1].Line) { + d.cursor++ + return true + } + return false +} + +// NextBlock can be used as the condition of a for loop +// to load the next token as long as it opens a block or +// is already in a block. It returns true if a token was +// loaded, or false when the block's closing curly brace +// was loaded and thus the block ended. Nested blocks are +// not supported. +func (d *Dispenser) NextBlock() bool { + if d.nesting > 0 { + d.Next() + if d.Val() == "}" { + d.nesting-- + return false + } + return true + } + if !d.NextArg() { // block must open on same line + return false + } + if d.Val() != "{" { + d.cursor-- // roll back if not opening brace + return false + } + d.Next() + if d.Val() == "}" { + // Open and then closed right away + return false + } + d.nesting++ + return true +} + +// Val gets the text of the current token. If there is no token +// loaded, it returns empty string. +func (d *Dispenser) Val() string { + if d.cursor < 0 || d.cursor >= len(d.tokens) { + return "" + } + return d.tokens[d.cursor].Text +} + +// Line gets the line number of the current token. If there is no token +// loaded, it returns 0. +func (d *Dispenser) Line() int { + if d.cursor < 0 || d.cursor >= len(d.tokens) { + return 0 + } + return d.tokens[d.cursor].Line +} + +// File gets the filename of the current token. If there is no token loaded, +// it returns the filename originally given when parsing started. +func (d *Dispenser) File() string { + if d.cursor < 0 || d.cursor >= len(d.tokens) { + return d.filename + } + if tokenFilename := d.tokens[d.cursor].File; tokenFilename != "" { + return tokenFilename + } + return d.filename +} + +// Args is a convenience function that loads the next arguments +// (tokens on the same line) into an arbitrary number of strings +// pointed to in targets. If there are fewer tokens available +// than string pointers, the remaining strings will not be changed +// and false will be returned. If there were enough tokens available +// to fill the arguments, then true will be returned. +func (d *Dispenser) Args(targets ...*string) bool { + enough := true + for i := 0; i < len(targets); i++ { + if !d.NextArg() { + enough = false + break + } + *targets[i] = d.Val() + } + return enough +} + +// RemainingArgs loads any more arguments (tokens on the same line) +// into a slice and returns them. Open curly brace tokens also indicate +// the end of arguments, and the curly brace is not included in +// the return value nor is it loaded. +func (d *Dispenser) RemainingArgs() []string { + var args []string + + for d.NextArg() { + if d.Val() == "{" { + d.cursor-- + break + } + args = append(args, d.Val()) + } + + return args +} + +// ArgErr returns an argument error, meaning that another +// argument was expected but not found. In other words, +// a line break or open curly brace was encountered instead of +// an argument. +func (d *Dispenser) ArgErr() error { + if d.Val() == "{" { + return d.Err("Unexpected token '{', expecting argument") + } + return d.Errf("Wrong argument count or unexpected line ending after '%s'", d.Val()) +} + +// SyntaxErr creates a generic syntax error which explains what was +// found and what was expected. +func (d *Dispenser) SyntaxErr(expected string) error { + msg := fmt.Sprintf("%s:%d - Syntax error: Unexpected token '%s', expecting '%s'", d.File(), d.Line(), d.Val(), expected) + return errors.New(msg) +} + +// EOFErr returns an error indicating that the dispenser reached +// the end of the input when searching for the next token. +func (d *Dispenser) EOFErr() error { + return d.Errf("Unexpected EOF") +} + +// Err generates a custom parse-time error with a message of msg. +func (d *Dispenser) Err(msg string) error { + msg = fmt.Sprintf("%s:%d - Error during parsing: %s", d.File(), d.Line(), msg) + return errors.New(msg) +} + +// Errf is like Err, but for formatted error messages +func (d *Dispenser) Errf(format string, args ...interface{}) error { + return d.Err(fmt.Sprintf(format, args...)) +} + +// numLineBreaks counts how many line breaks are in the token +// value given by the token index tknIdx. It returns 0 if the +// token does not exist or there are no line breaks. +func (d *Dispenser) numLineBreaks(tknIdx int) int { + if tknIdx < 0 || tknIdx >= len(d.tokens) { + return 0 + } + return strings.Count(d.tokens[tknIdx].Text, "\n") +} + +// isNewLine determines whether the current token is on a different +// line (higher line number) than the previous token. It handles imported +// tokens correctly. If there isn't a previous token, it returns true. +func (d *Dispenser) isNewLine() bool { + if d.cursor < 1 { + return true + } + if d.cursor > len(d.tokens)-1 { + return false + } + return d.tokens[d.cursor-1].File != d.tokens[d.cursor].File || + d.tokens[d.cursor-1].Line+d.numLineBreaks(d.cursor-1) < d.tokens[d.cursor].Line +} diff --git a/vendor/github.com/mholt/caddy/caddyfile/json.go b/vendor/github.com/mholt/caddy/caddyfile/json.go new file mode 100644 index 0000000000000..0d37e8e9892ce --- /dev/null +++ b/vendor/github.com/mholt/caddy/caddyfile/json.go @@ -0,0 +1,198 @@ +// Copyright 2015 Light Code Labs, LLC +// +// 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 caddyfile + +import ( + "bytes" + "encoding/json" + "fmt" + "sort" + "strconv" + "strings" +) + +const filename = "Caddyfile" + +// ToJSON converts caddyfile to its JSON representation. +func ToJSON(caddyfile []byte) ([]byte, error) { + var j EncodedCaddyfile + + serverBlocks, err := Parse(filename, bytes.NewReader(caddyfile), nil) + if err != nil { + return nil, err + } + + for _, sb := range serverBlocks { + block := EncodedServerBlock{ + Keys: sb.Keys, + Body: [][]interface{}{}, + } + + // Extract directives deterministically by sorting them + var directives = make([]string, len(sb.Tokens)) + for dir := range sb.Tokens { + directives = append(directives, dir) + } + sort.Strings(directives) + + // Convert each directive's tokens into our JSON structure + for _, dir := range directives { + disp := NewDispenserTokens(filename, sb.Tokens[dir]) + for disp.Next() { + block.Body = append(block.Body, constructLine(&disp)) + } + } + + // tack this block onto the end of the list + j = append(j, block) + } + + result, err := json.Marshal(j) + if err != nil { + return nil, err + } + + return result, nil +} + +// constructLine transforms tokens into a JSON-encodable structure; +// but only one line at a time, to be used at the top-level of +// a server block only (where the first token on each line is a +// directive) - not to be used at any other nesting level. +func constructLine(d *Dispenser) []interface{} { + var args []interface{} + + args = append(args, d.Val()) + + for d.NextArg() { + if d.Val() == "{" { + args = append(args, constructBlock(d)) + continue + } + args = append(args, d.Val()) + } + + return args +} + +// constructBlock recursively processes tokens into a +// JSON-encodable structure. To be used in a directive's +// block. Goes to end of block. +func constructBlock(d *Dispenser) [][]interface{} { + block := [][]interface{}{} + + for d.Next() { + if d.Val() == "}" { + break + } + block = append(block, constructLine(d)) + } + + return block +} + +// FromJSON converts JSON-encoded jsonBytes to Caddyfile text +func FromJSON(jsonBytes []byte) ([]byte, error) { + var j EncodedCaddyfile + var result string + + err := json.Unmarshal(jsonBytes, &j) + if err != nil { + return nil, err + } + + for sbPos, sb := range j { + if sbPos > 0 { + result += "\n\n" + } + for i, key := range sb.Keys { + if i > 0 { + result += ", " + } + //result += standardizeScheme(key) + result += key + } + result += jsonToText(sb.Body, 1) + } + + return []byte(result), nil +} + +// jsonToText recursively transforms a scope of JSON into plain +// Caddyfile text. +func jsonToText(scope interface{}, depth int) string { + var result string + + switch val := scope.(type) { + case string: + if strings.ContainsAny(val, "\" \n\t\r") { + result += `"` + strings.Replace(val, "\"", "\\\"", -1) + `"` + } else { + result += val + } + case int: + result += strconv.Itoa(val) + case float64: + result += fmt.Sprintf("%v", val) + case bool: + result += fmt.Sprintf("%t", val) + case [][]interface{}: + result += " {\n" + for _, arg := range val { + result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n" + } + result += strings.Repeat("\t", depth-1) + "}" + case []interface{}: + for i, v := range val { + if block, ok := v.([]interface{}); ok { + result += "{\n" + for _, arg := range block { + result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n" + } + result += strings.Repeat("\t", depth-1) + "}" + continue + } + result += jsonToText(v, depth) + if i < len(val)-1 { + result += " " + } + } + } + + return result +} + +// TODO: Will this function come in handy somewhere else? +/* +// standardizeScheme turns an address like host:https into https://host, +// or "host:" into "host". +func standardizeScheme(addr string) string { + if hostname, port, err := net.SplitHostPort(addr); err == nil { + if port == "http" || port == "https" { + addr = port + "://" + hostname + } + } + return strings.TrimSuffix(addr, ":") +} +*/ + +// EncodedCaddyfile encapsulates a slice of EncodedServerBlocks. +type EncodedCaddyfile []EncodedServerBlock + +// EncodedServerBlock represents a server block ripe for encoding. +type EncodedServerBlock struct { + Keys []string `json:"keys"` + Body [][]interface{} `json:"body"` +} diff --git a/vendor/github.com/mholt/caddy/caddyfile/lexer.go b/vendor/github.com/mholt/caddy/caddyfile/lexer.go new file mode 100644 index 0000000000000..2b38627be93c2 --- /dev/null +++ b/vendor/github.com/mholt/caddy/caddyfile/lexer.go @@ -0,0 +1,150 @@ +// Copyright 2015 Light Code Labs, LLC +// +// 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 caddyfile + +import ( + "bufio" + "io" + "unicode" +) + +type ( + // lexer is a utility which can get values, token by + // token, from a Reader. A token is a word, and tokens + // are separated by whitespace. A word can be enclosed + // in quotes if it contains whitespace. + lexer struct { + reader *bufio.Reader + token Token + line int + } + + // Token represents a single parsable unit. + Token struct { + File string + Line int + Text string + } +) + +// load prepares the lexer to scan an input for tokens. +// It discards any leading byte order mark. +func (l *lexer) load(input io.Reader) error { + l.reader = bufio.NewReader(input) + l.line = 1 + + // discard byte order mark, if present + firstCh, _, err := l.reader.ReadRune() + if err != nil { + return err + } + if firstCh != 0xFEFF { + err := l.reader.UnreadRune() + if err != nil { + return err + } + } + + return nil +} + +// next loads the next token into the lexer. +// A token is delimited by whitespace, unless +// the token starts with a quotes character (") +// in which case the token goes until the closing +// quotes (the enclosing quotes are not included). +// Inside quoted strings, quotes may be escaped +// with a preceding \ character. No other chars +// may be escaped. The rest of the line is skipped +// if a "#" character is read in. Returns true if +// a token was loaded; false otherwise. +func (l *lexer) next() bool { + var val []rune + var comment, quoted, escaped bool + + makeToken := func() bool { + l.token.Text = string(val) + return true + } + + for { + ch, _, err := l.reader.ReadRune() + if err != nil { + if len(val) > 0 { + return makeToken() + } + if err == io.EOF { + return false + } + panic(err) + } + + if quoted { + if !escaped { + if ch == '\\' { + escaped = true + continue + } else if ch == '"' { + quoted = false + return makeToken() + } + } + if ch == '\n' { + l.line++ + } + if escaped { + // only escape quotes + if ch != '"' { + val = append(val, '\\') + } + } + val = append(val, ch) + escaped = false + continue + } + + if unicode.IsSpace(ch) { + if ch == '\r' { + continue + } + if ch == '\n' { + l.line++ + comment = false + } + if len(val) > 0 { + return makeToken() + } + continue + } + + if ch == '#' { + comment = true + } + + if comment { + continue + } + + if len(val) == 0 { + l.token = Token{Line: l.line} + if ch == '"' { + quoted = true + continue + } + } + + val = append(val, ch) + } +} diff --git a/vendor/github.com/mholt/caddy/caddyfile/parse.go b/vendor/github.com/mholt/caddy/caddyfile/parse.go new file mode 100644 index 0000000000000..142a87f931e2c --- /dev/null +++ b/vendor/github.com/mholt/caddy/caddyfile/parse.go @@ -0,0 +1,497 @@ +// Copyright 2015 Light Code Labs, LLC +// +// 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 caddyfile + +import ( + "io" + "log" + "os" + "path/filepath" + "strings" +) + +// Parse parses the input just enough to group tokens, in +// order, by server block. No further parsing is performed. +// Server blocks are returned in the order in which they appear. +// Directives that do not appear in validDirectives will cause +// an error. If you do not want to check for valid directives, +// pass in nil instead. +func Parse(filename string, input io.Reader, validDirectives []string) ([]ServerBlock, error) { + p := parser{Dispenser: NewDispenser(filename, input), validDirectives: validDirectives} + return p.parseAll() +} + +// allTokens lexes the entire input, but does not parse it. +// It returns all the tokens from the input, unstructured +// and in order. +func allTokens(input io.Reader) ([]Token, error) { + l := new(lexer) + err := l.load(input) + if err != nil { + return nil, err + } + var tokens []Token + for l.next() { + tokens = append(tokens, l.token) + } + return tokens, nil +} + +type parser struct { + Dispenser + block ServerBlock // current server block being parsed + validDirectives []string // a directive must be valid or it's an error + eof bool // if we encounter a valid EOF in a hard place + definedSnippets map[string][]Token +} + +func (p *parser) parseAll() ([]ServerBlock, error) { + var blocks []ServerBlock + + for p.Next() { + err := p.parseOne() + if err != nil { + return blocks, err + } + if len(p.block.Keys) > 0 { + blocks = append(blocks, p.block) + } + } + + return blocks, nil +} + +func (p *parser) parseOne() error { + p.block = ServerBlock{Tokens: make(map[string][]Token)} + + return p.begin() +} + +func (p *parser) begin() error { + if len(p.tokens) == 0 { + return nil + } + + err := p.addresses() + + if err != nil { + return err + } + + if p.eof { + // this happens if the Caddyfile consists of only + // a line of addresses and nothing else + return nil + } + + if ok, name := p.isSnippet(); ok { + if p.definedSnippets == nil { + p.definedSnippets = map[string][]Token{} + } + if _, found := p.definedSnippets[name]; found { + return p.Errf("redeclaration of previously declared snippet %s", name) + } + // consume all tokens til matched close brace + tokens, err := p.snippetTokens() + if err != nil { + return err + } + p.definedSnippets[name] = tokens + // empty block keys so we don't save this block as a real server. + p.block.Keys = nil + return nil + } + + return p.blockContents() +} + +func (p *parser) addresses() error { + var expectingAnother bool + + for { + tkn := replaceEnvVars(p.Val()) + + // special case: import directive replaces tokens during parse-time + if tkn == "import" && p.isNewLine() { + err := p.doImport() + if err != nil { + return err + } + continue + } + + // Open brace definitely indicates end of addresses + if tkn == "{" { + if expectingAnother { + return p.Errf("Expected another address but had '%s' - check for extra comma", tkn) + } + break + } + + if tkn != "" { // empty token possible if user typed "" + // Trailing comma indicates another address will follow, which + // may possibly be on the next line + if tkn[len(tkn)-1] == ',' { + tkn = tkn[:len(tkn)-1] + expectingAnother = true + } else { + expectingAnother = false // but we may still see another one on this line + } + + p.block.Keys = append(p.block.Keys, tkn) + } + + // Advance token and possibly break out of loop or return error + hasNext := p.Next() + if expectingAnother && !hasNext { + return p.EOFErr() + } + if !hasNext { + p.eof = true + break // EOF + } + if !expectingAnother && p.isNewLine() { + break + } + } + + return nil +} + +func (p *parser) blockContents() error { + errOpenCurlyBrace := p.openCurlyBrace() + if errOpenCurlyBrace != nil { + // single-server configs don't need curly braces + p.cursor-- + } + + err := p.directives() + if err != nil { + return err + } + + // Only look for close curly brace if there was an opening + if errOpenCurlyBrace == nil { + err = p.closeCurlyBrace() + if err != nil { + return err + } + } + + return nil +} + +// directives parses through all the lines for directives +// and it expects the next token to be the first +// directive. It goes until EOF or closing curly brace +// which ends the server block. +func (p *parser) directives() error { + for p.Next() { + // end of server block + if p.Val() == "}" { + break + } + + // special case: import directive replaces tokens during parse-time + if p.Val() == "import" { + err := p.doImport() + if err != nil { + return err + } + p.cursor-- // cursor is advanced when we continue, so roll back one more + continue + } + + // normal case: parse a directive on this line + if err := p.directive(); err != nil { + return err + } + } + return nil +} + +// doImport swaps out the import directive and its argument +// (a total of 2 tokens) with the tokens in the specified file +// or globbing pattern. When the function returns, the cursor +// is on the token before where the import directive was. In +// other words, call Next() to access the first token that was +// imported. +func (p *parser) doImport() error { + // syntax checks + if !p.NextArg() { + return p.ArgErr() + } + importPattern := replaceEnvVars(p.Val()) + if importPattern == "" { + return p.Err("Import requires a non-empty filepath") + } + if p.NextArg() { + return p.Err("Import takes only one argument (glob pattern or file)") + } + // splice out the import directive and its argument (2 tokens total) + tokensBefore := p.tokens[:p.cursor-1] + tokensAfter := p.tokens[p.cursor+1:] + var importedTokens []Token + + // first check snippets. That is a simple, non-recursive replacement + if p.definedSnippets != nil && p.definedSnippets[importPattern] != nil { + importedTokens = p.definedSnippets[importPattern] + } else { + // make path relative to Caddyfile rather than current working directory (issue #867) + // and then use glob to get list of matching filenames + absFile, err := filepath.Abs(p.Dispenser.filename) + if err != nil { + return p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.filename, err) + } + + var matches []string + var globPattern string + if !filepath.IsAbs(importPattern) { + globPattern = filepath.Join(filepath.Dir(absFile), importPattern) + } else { + globPattern = importPattern + } + matches, err = filepath.Glob(globPattern) + + if err != nil { + return p.Errf("Failed to use import pattern %s: %v", importPattern, err) + } + if len(matches) == 0 { + if strings.Contains(globPattern, "*") { + log.Printf("[WARNING] No files matching import pattern: %s", importPattern) + } else { + return p.Errf("File to import not found: %s", importPattern) + } + } + + // collect all the imported tokens + + for _, importFile := range matches { + newTokens, err := p.doSingleImport(importFile) + if err != nil { + return err + } + + var importLine int + for i, token := range newTokens { + if token.Text == "import" { + importLine = token.Line + continue + } + if token.Line == importLine { + var abs string + if filepath.IsAbs(token.Text) { + abs = token.Text + } else if !filepath.IsAbs(importFile) { + abs = filepath.Join(filepath.Dir(absFile), token.Text) + } else { + abs = filepath.Join(filepath.Dir(importFile), token.Text) + } + newTokens[i] = Token{ + Text: abs, + Line: token.Line, + File: token.File, + } + } + } + + importedTokens = append(importedTokens, newTokens...) + } + } + + // splice the imported tokens in the place of the import statement + // and rewind cursor so Next() will land on first imported token + p.tokens = append(tokensBefore, append(importedTokens, tokensAfter...)...) + p.cursor-- + + return nil +} + +// doSingleImport lexes the individual file at importFile and returns +// its tokens or an error, if any. +func (p *parser) doSingleImport(importFile string) ([]Token, error) { + file, err := os.Open(importFile) + if err != nil { + return nil, p.Errf("Could not import %s: %v", importFile, err) + } + defer file.Close() + + if info, err := file.Stat(); err != nil { + return nil, p.Errf("Could not import %s: %v", importFile, err) + } else if info.IsDir() { + return nil, p.Errf("Could not import %s: is a directory", importFile) + } + + importedTokens, err := allTokens(file) + if err != nil { + return nil, p.Errf("Could not read tokens while importing %s: %v", importFile, err) + } + + // Tack the file path onto these tokens so errors show the imported file's name + // (we use full, absolute path to avoid bugs: issue #1892) + filename, err := filepath.Abs(importFile) + if err != nil { + return nil, p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.filename, err) + } + for i := 0; i < len(importedTokens); i++ { + importedTokens[i].File = filename + } + + return importedTokens, nil +} + +// directive collects tokens until the directive's scope +// closes (either end of line or end of curly brace block). +// It expects the currently-loaded token to be a directive +// (or } that ends a server block). The collected tokens +// are loaded into the current server block for later use +// by directive setup functions. +func (p *parser) directive() error { + dir := p.Val() + nesting := 0 + + // TODO: More helpful error message ("did you mean..." or "maybe you need to install its server type") + if !p.validDirective(dir) { + return p.Errf("Unknown directive '%s'", dir) + } + + // The directive itself is appended as a relevant token + p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor]) + + for p.Next() { + if p.Val() == "{" { + nesting++ + } else if p.isNewLine() && nesting == 0 { + p.cursor-- // read too far + break + } else if p.Val() == "}" && nesting > 0 { + nesting-- + } else if p.Val() == "}" && nesting == 0 { + return p.Err("Unexpected '}' because no matching opening brace") + } + p.tokens[p.cursor].Text = replaceEnvVars(p.tokens[p.cursor].Text) + p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor]) + } + + if nesting > 0 { + return p.EOFErr() + } + return nil +} + +// openCurlyBrace expects the current token to be an +// opening curly brace. This acts like an assertion +// because it returns an error if the token is not +// a opening curly brace. It does NOT advance the token. +func (p *parser) openCurlyBrace() error { + if p.Val() != "{" { + return p.SyntaxErr("{") + } + return nil +} + +// closeCurlyBrace expects the current token to be +// a closing curly brace. This acts like an assertion +// because it returns an error if the token is not +// a closing curly brace. It does NOT advance the token. +func (p *parser) closeCurlyBrace() error { + if p.Val() != "}" { + return p.SyntaxErr("}") + } + return nil +} + +// validDirective returns true if dir is in p.validDirectives. +func (p *parser) validDirective(dir string) bool { + if p.validDirectives == nil { + return true + } + for _, d := range p.validDirectives { + if d == dir { + return true + } + } + return false +} + +// replaceEnvVars replaces environment variables that appear in the token +// and understands both the $UNIX and %WINDOWS% syntaxes. +func replaceEnvVars(s string) string { + s = replaceEnvReferences(s, "{%", "%}") + s = replaceEnvReferences(s, "{$", "}") + return s +} + +// replaceEnvReferences performs the actual replacement of env variables +// in s, given the placeholder start and placeholder end strings. +func replaceEnvReferences(s, refStart, refEnd string) string { + index := strings.Index(s, refStart) + for index != -1 { + endIndex := strings.Index(s, refEnd) + if endIndex != -1 { + ref := s[index : endIndex+len(refEnd)] + s = strings.Replace(s, ref, os.Getenv(ref[len(refStart):len(ref)-len(refEnd)]), -1) + } else { + return s + } + index = strings.Index(s, refStart) + } + return s +} + +// ServerBlock associates any number of keys (usually addresses +// of some sort) with tokens (grouped by directive name). +type ServerBlock struct { + Keys []string + Tokens map[string][]Token +} + +func (p *parser) isSnippet() (bool, string) { + keys := p.block.Keys + // A snippet block is a single key with parens. Nothing else qualifies. + if len(keys) == 1 && strings.HasPrefix(keys[0], "(") && strings.HasSuffix(keys[0], ")") { + return true, strings.TrimSuffix(keys[0][1:], ")") + } + return false, "" +} + +// read and store everything in a block for later replay. +func (p *parser) snippetTokens() ([]Token, error) { + // TODO: disallow imports in snippets for simplicity at import time + // snippet must have curlies. + err := p.openCurlyBrace() + if err != nil { + return nil, err + } + count := 1 + tokens := []Token{} + for p.Next() { + if p.Val() == "}" { + count-- + if count == 0 { + break + } + } + if p.Val() == "{" { + count++ + } + tokens = append(tokens, p.tokens[p.cursor]) + } + // make sure we're matched up + if count != 0 { + return nil, p.SyntaxErr("}") + } + return tokens, nil +} From 993e5413ce55d50e67e3f169671c5cec5458e5c2 Mon Sep 17 00:00:00 2001 From: Sandeep Rajan Date: Thu, 22 Feb 2018 10:40:24 -0500 Subject: [PATCH 6/8] update version and manifest --- cmd/kubeadm/app/phases/addons/dns/manifests.go | 2 +- cmd/kubeadm/app/phases/addons/dns/versions.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/kubeadm/app/phases/addons/dns/manifests.go b/cmd/kubeadm/app/phases/addons/dns/manifests.go index 4d696f28c7c81..26c8878afa63a 100644 --- a/cmd/kubeadm/app/phases/addons/dns/manifests.go +++ b/cmd/kubeadm/app/phases/addons/dns/manifests.go @@ -309,7 +309,7 @@ data: health kubernetes {{ .DNSDomain }} in-addr.arpa ip6.arpa { pods insecure - upstream {{ .UpstreamNameserver }} + upstream fallthrough in-addr.arpa ip6.arpa }{{ .Federation }} prometheus :9153 diff --git a/cmd/kubeadm/app/phases/addons/dns/versions.go b/cmd/kubeadm/app/phases/addons/dns/versions.go index 4ab029cd4f50f..9a278dd090cf9 100644 --- a/cmd/kubeadm/app/phases/addons/dns/versions.go +++ b/cmd/kubeadm/app/phases/addons/dns/versions.go @@ -23,13 +23,13 @@ import ( const ( kubeDNSv190AndAboveVersion = "1.14.8" - coreDNSVersion = "1.0.4" + coreDNSVersion = "1.0.6" ) // GetDNSVersion returns the right kube-dns version for a specific k8s version func GetDNSVersion(kubeVersion *version.Version, dns string) string { // v1.9.0+ uses kube-dns 1.14.8 - // v1.9.0+ uses CoreDNS 1.0.4 if feature gate "CoreDNS" is enabled. + // v1.9.0+ uses CoreDNS 1.0.6 if feature gate "CoreDNS" is enabled. // In the future when the version is bumped at HEAD; add conditional logic to return the right versions // Also, the version might be bumped for different k8s releases on the same branch From f038d994e0c7cf3f52053b8d3664dbd6c0958c29 Mon Sep 17 00:00:00 2001 From: Sandeep Rajan Date: Thu, 22 Feb 2018 11:03:58 -0500 Subject: [PATCH 7/8] bump coredns feature gates to beta --- cmd/kubeadm/app/features/features.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kubeadm/app/features/features.go b/cmd/kubeadm/app/features/features.go index 9dae9c659f7c0..f9f5ee281bb2d 100644 --- a/cmd/kubeadm/app/features/features.go +++ b/cmd/kubeadm/app/features/features.go @@ -54,7 +54,7 @@ var InitFeatureGates = FeatureList{ StoreCertsInSecrets: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}}, // We don't want to advertise this feature gate exists in v1.9 to avoid confusion as it is not yet working HighAvailability: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190, HiddenInHelpText: true}, - CoreDNS: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, + CoreDNS: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Beta}, MinimumVersion: v190}, DynamicKubeletConfig: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}, MinimumVersion: v190}, Auditing: {FeatureSpec: utilfeature.FeatureSpec{Default: false, PreRelease: utilfeature.Alpha}}, } From fbdeff5d4b31d9aec69bf2bd1d79c4cd6817d2cd Mon Sep 17 00:00:00 2001 From: Sandeep Rajan Date: Tue, 27 Feb 2018 19:33:15 -0500 Subject: [PATCH 8/8] Code Cleanup --- cmd/kubeadm/app/phases/addons/dns/dns.go | 48 +++++------ cmd/kubeadm/app/phases/addons/dns/dns_test.go | 80 +++++++++---------- .../app/phases/addons/dns/manifests.go | 2 +- 3 files changed, 63 insertions(+), 67 deletions(-) diff --git a/cmd/kubeadm/app/phases/addons/dns/dns.go b/cmd/kubeadm/app/phases/addons/dns/dns.go index 744aed84bffcd..f7fc2cba5aa1b 100644 --- a/cmd/kubeadm/app/phases/addons/dns/dns.go +++ b/cmd/kubeadm/app/phases/addons/dns/dns.go @@ -46,7 +46,6 @@ const ( kubeDNSStubDomain = "stubDomains" kubeDNSUpstreamNameservers = "upstreamNameservers" kubeDNSFederation = "federations" - coreDNSStanzaFormat = "\n " ) // EnsureDNSAddon creates the kube-dns or CoreDNS addon @@ -281,24 +280,20 @@ func createDNSService(dnsService *v1.Service, serviceBytes []byte, client client func translateStubDomainOfKubeDNSToProxyCoreDNS(dataField string, kubeDNSConfigMap *v1.ConfigMap) (string, error) { if proxy, ok := kubeDNSConfigMap.Data[dataField]; ok { stubDomainData := make(map[string][]string) - var proxyStanzaList string - err := json.Unmarshal([]byte(proxy), &stubDomainData) if err != nil { return "", fmt.Errorf("failed to parse JSON from 'kube-dns ConfigMap: %v", err) } - var proxyStanza []interface{} + var proxyStanza []interface{} for domain, proxyIP := range stubDomainData { - strings.Join(proxyIP, " ") pStanza := map[string]interface{}{} pStanza["keys"] = []string{domain + ":53"} pStanza["body"] = [][]string{ {"errors"}, {"cache", "30"}, - {"proxy", ".", strings.Join(proxyIP, " ")}, + append([]string{"proxy", "."}, proxyIP...), } - proxyStanza = append(proxyStanza, pStanza) } stanzasBytes, err := json.Marshal(proxyStanza) @@ -306,15 +301,12 @@ func translateStubDomainOfKubeDNSToProxyCoreDNS(dataField string, kubeDNSConfigM return "", err } - outputJSON, err := caddyfile.FromJSON(stanzasBytes) + corefileStanza, err := caddyfile.FromJSON(stanzasBytes) if err != nil { return "", err } - // This is required to format the Corefile, otherwise errors due to bad yaml format. - output := strings.NewReplacer("\n\t", "\n ", "\"", "", "\n}", "\n }", "\n\n", "\n ").Replace(string(outputJSON)) - proxyStanzaList = coreDNSStanzaFormat + output + coreDNSStanzaFormat - return proxyStanzaList, nil + return prepCorefileFormat(string(corefileStanza), 4), nil } return "", nil } @@ -342,7 +334,7 @@ func translateFederationsofKubeDNSToCoreDNS(dataField, coreDNSDomain string, kub if federation, ok := kubeDNSConfigMap.Data[dataField]; ok { var ( federationStanza []interface{} - fData []string + body [][]string ) federationData := make(map[string]string) @@ -350,33 +342,37 @@ func translateFederationsofKubeDNSToCoreDNS(dataField, coreDNSDomain string, kub if err != nil { return "", fmt.Errorf("failed to parse JSON from kube-dns ConfigMap: %v", err) } + fStanza := map[string]interface{}{} for name, domain := range federationData { - fData = append(fData, name+" "+domain) - } - fStanza := map[string]interface{}{} - fStanza["body"] = [][]string{ - {strings.Join(fData, "\n ")}, + body = append(body, []string{name, domain}) } federationStanza = append(federationStanza, fStanza) fStanza["keys"] = []string{"federation " + coreDNSDomain} - + fStanza["body"] = body stanzasBytes, err := json.Marshal(federationStanza) if err != nil { return "", err } - outputJSON, err := caddyfile.FromJSON(stanzasBytes) + corefileStanza, err := caddyfile.FromJSON(stanzasBytes) if err != nil { return "", err } - // This is required to format the Corefile, otherwise errors due to bad yaml format. - output := strings.NewReplacer("\n\t", "\n ", "\"", "", "\n}", "\n }").Replace(string(outputJSON)) - federationStanzaList := coreDNSStanzaFormat + output - - return federationStanzaList, nil - + return prepCorefileFormat(string(corefileStanza), 8), nil } return "", nil } + +// prepCorefileFormat indents the output of the Corefile caddytext and replaces tabs with spaces +// to neatly format the configmap, making it readable. +func prepCorefileFormat(s string, indentation int) string { + r := []string{} + for _, line := range strings.Split(s, "\n") { + indented := strings.Repeat(" ", indentation) + line + r = append(r, indented) + } + corefile := strings.Join(r, "\n") + return "\n" + strings.Replace(corefile, "\t", " ", -1) +} diff --git a/cmd/kubeadm/app/phases/addons/dns/dns_test.go b/cmd/kubeadm/app/phases/addons/dns/dns_test.go index db84e594fd3ca..2233bf182d01f 100644 --- a/cmd/kubeadm/app/phases/addons/dns/dns_test.go +++ b/cmd/kubeadm/app/phases/addons/dns/dns_test.go @@ -201,28 +201,28 @@ func TestTranslateStubDomainKubeDNSToCoreDNS(t *testing.T) { expectOne: ` foo.com:53 { - errors - cache 30 - proxy . 1.2.3.4:5300 3.3.3.3 + errors + cache 30 + proxy . 1.2.3.4:5300 3.3.3.3 } + my.cluster.local:53 { - errors - cache 30 - proxy . 2.3.4.5 - } - `, + errors + cache 30 + proxy . 2.3.4.5 + }`, expectTwo: ` my.cluster.local:53 { - errors - cache 30 - proxy . 2.3.4.5 + errors + cache 30 + proxy . 2.3.4.5 } + foo.com:53 { - errors - cache 30 - proxy . 1.2.3.4:5300 3.3.3.3 - } - `, + errors + cache 30 + proxy . 1.2.3.4:5300 3.3.3.3 + }`, }, { configMap: &v1.ConfigMap{ @@ -248,28 +248,28 @@ func TestTranslateStubDomainKubeDNSToCoreDNS(t *testing.T) { expectOne: ` foo.com:53 { - errors - cache 30 - proxy . 1.2.3.4:5300 + errors + cache 30 + proxy . 1.2.3.4:5300 } + my.cluster.local:53 { - errors - cache 30 - proxy . 2.3.4.5 - } - `, + errors + cache 30 + proxy . 2.3.4.5 + }`, expectTwo: ` my.cluster.local:53 { - errors - cache 30 - proxy . 2.3.4.5 + errors + cache 30 + proxy . 2.3.4.5 } + foo.com:53 { - errors - cache 30 - proxy . 1.2.3.4:5300 - } - `, + errors + cache 30 + proxy . 1.2.3.4:5300 + }`, }, { configMap: &v1.ConfigMap{ @@ -370,15 +370,15 @@ func TestTranslateFederationKubeDNSToCoreDNS(t *testing.T) { }, expectOne: ` - federation cluster.local { - foo foo.feddomain.com - bar bar.feddomain.com - }`, + federation cluster.local { + foo foo.feddomain.com + bar bar.feddomain.com + }`, expectTwo: ` - federation cluster.local { - bar bar.feddomain.com - foo foo.feddomain.com - }`, + federation cluster.local { + bar bar.feddomain.com + foo foo.feddomain.com + }`, }, { configMap: &v1.ConfigMap{ diff --git a/cmd/kubeadm/app/phases/addons/dns/manifests.go b/cmd/kubeadm/app/phases/addons/dns/manifests.go index 26c8878afa63a..3289e27f6051d 100644 --- a/cmd/kubeadm/app/phases/addons/dns/manifests.go +++ b/cmd/kubeadm/app/phases/addons/dns/manifests.go @@ -309,7 +309,7 @@ data: health kubernetes {{ .DNSDomain }} in-addr.arpa ip6.arpa { pods insecure - upstream + upstream fallthrough in-addr.arpa ip6.arpa }{{ .Federation }} prometheus :9153