diff --git a/README.md b/README.md index 42acd48..7b638d6 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,19 @@ passing a comma-delimited list, e.g.: LOCKSMITHCTL_ENDPOINT=, +### Discovering endpoints + +DNS SRV records can be used to discover the endpoints. You will need to setup [etcd DNS Discovery](https://coreos.com/etcd/docs/latest/v2/clustering.html#dns-discovery) on your domain first. The `-discovery-srv=` option triggers DNS SRV discovery, as long as no `-endpoint` option is specified. E.g.: + + -discovery-srv=mydomain.com + +This would trigger DNS discovery on the following SRV records: + +``` +_etcd-client-ssl._tcp.mydomain.com +_etcd-client._tcp.mydomain.com +``` + ### Listing the Holders ``` diff --git a/locksmithctl/discovery.go b/locksmithctl/discovery.go new file mode 100644 index 0000000..3bf6e69 --- /dev/null +++ b/locksmithctl/discovery.go @@ -0,0 +1,57 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "net" + "net/url" +) + +const ( + etcdClientService = "etcd-client" +) + +// discoverEndpoints looks up the client endpoints for a domain. +func discoverEndpoints(domain string) ([]string, error) { + var urls []*url.URL + + updateURLs := func(service, scheme string) error { + _, addrs, err := net.LookupSRV(service, "tcp", domain) + if err != nil { + return err + } + for _, srv := range addrs { + urls = append(urls, &url.URL{ + Scheme: scheme, + Host: net.JoinHostPort(srv.Target, fmt.Sprintf("%d", srv.Port)), + }) + } + return nil + } + + errHTTPS := updateURLs(etcdClientService+"-ssl", "https") + errHTTP := updateURLs(etcdClientService, "http") + + if errHTTPS != nil && errHTTP != nil { + return nil, fmt.Errorf("dns lookup errors: %s and %s", errHTTPS, errHTTP) + } + + endpoints := make([]string, len(urls)) + for i := range urls { + endpoints[i] = urls[i].String() + } + return endpoints, nil +} diff --git a/locksmithctl/locksmithctl.go b/locksmithctl/locksmithctl.go index 265eaa3..6a69ed5 100644 --- a/locksmithctl/locksmithctl.go +++ b/locksmithctl/locksmithctl.go @@ -47,6 +47,7 @@ var ( globalFlags = struct { Debug bool + DiscoverySrv string Endpoints endpoints EtcdKeyFile string EtcdCertFile string @@ -86,7 +87,8 @@ func init() { out.Init(os.Stdout, 0, 8, 1, '\t', 0) globalFlagSet.BoolVar(&globalFlags.Debug, "debug", false, "Print out debug information to stderr.") - globalFlagSet.Var(&globalFlags.Endpoints, "endpoint", "etcd endpoint for locksmith. Specify multiple times to use multiple endpoints.") + globalFlagSet.StringVar(&globalFlags.DiscoverySrv, "discovery-srv", "", "DNS domain used to discover the etcd endpoints.") + globalFlagSet.Var(&globalFlags.Endpoints, "endpoint", "etcd endpoint for locksmith. Specify multiple times to use multiple endpoints. Overrides discovery-srv option.") globalFlagSet.StringVar(&globalFlags.EtcdKeyFile, "etcd-keyfile", "", "etcd key file authentication") globalFlagSet.StringVar(&globalFlags.EtcdCertFile, "etcd-certfile", "", "etcd cert file authentication") globalFlagSet.StringVar(&globalFlags.EtcdCAFile, "etcd-cafile", "", "etcd CA file authentication") @@ -132,6 +134,18 @@ func main() { globalFlagSet.Parse(os.Args[1:]) var args = globalFlagSet.Args() + if len(globalFlags.Endpoints) == 0 && globalFlags.DiscoverySrv != "" { + endpoints, err := discoverEndpoints(globalFlags.DiscoverySrv) + if err == nil { + for _, ep := range endpoints { + globalFlagSet.Set("endpoint", ep) + } + } else { + fmt.Fprintln(os.Stderr, "DNS SRV discovery failed:", err) + } + } + + // still no endpoints defined - use defaults if len(globalFlags.Endpoints) == 0 { globalFlags.Endpoints = defaultEndpoints }