github.com/technosophos/deis@v1.7.1-0.20150915173815-f9005256004b/deisctl/config/etcd/etcd.go (about)

     1  package etcd
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"net/http"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/coreos/fleet/pkg"
    11  	"github.com/coreos/fleet/ssh"
    12  	etcdlib "github.com/coreos/go-etcd/etcd"
    13  	"github.com/deis/deis/deisctl/backend/fleet"
    14  	"github.com/deis/deis/deisctl/config/model"
    15  )
    16  
    17  // ConfigBackend is an etcd-based implementation of the config.Backend interface
    18  type ConfigBackend struct {
    19  	etcdlib *etcdlib.Client
    20  }
    21  
    22  func getTunnelFlag() string {
    23  	tun := fleet.Flags.Tunnel
    24  	if tun != "" && !strings.Contains(tun, ":") {
    25  		tun += ":22"
    26  	}
    27  	return tun
    28  }
    29  
    30  func getChecker() *ssh.HostKeyChecker {
    31  	if !fleet.Flags.StrictHostKeyChecking {
    32  		return nil
    33  	}
    34  	keyFile := ssh.NewHostKeyFile(fleet.Flags.KnownHostsFile)
    35  	return ssh.NewHostKeyChecker(keyFile)
    36  }
    37  
    38  // Get a value by key from etcd
    39  func (cb *ConfigBackend) Get(key string) (string, error) {
    40  	sort, recursive := true, false
    41  	resp, err := cb.etcdlib.Get(key, sort, recursive)
    42  	if err != nil {
    43  		return "", err
    44  	}
    45  	return resp.Node.Value, nil
    46  }
    47  
    48  // GetWithDefault gets a value by key from etcd and return a default value if
    49  // not found
    50  func (cb *ConfigBackend) GetWithDefault(key string, defaultValue string) (string, error) {
    51  	sort, recursive := true, false
    52  	resp, err := cb.etcdlib.Get(key, sort, recursive)
    53  	if err != nil {
    54  		etcdErr, ok := err.(*etcdlib.EtcdError)
    55  		if ok && etcdErr.ErrorCode == 100 {
    56  			return defaultValue, nil
    57  		}
    58  		return "", err
    59  	}
    60  	return resp.Node.Value, nil
    61  }
    62  
    63  func singleNodeToConfigNode(node *etcdlib.Node) *model.ConfigNode {
    64  	key := model.ConfigNode{
    65  		Key:        node.Key,
    66  		Expiration: node.Expiration,
    67  	}
    68  
    69  	if node.Dir != true && node.Key != "" {
    70  		key.Value = node.Value
    71  	}
    72  
    73  	return &key
    74  }
    75  
    76  func traverseNode(node *etcdlib.Node) []*model.ConfigNode {
    77  	var serviceKeys []*model.ConfigNode
    78  
    79  	if len(node.Nodes) > 0 {
    80  		for _, nodeChild := range node.Nodes {
    81  			serviceKeys = append(serviceKeys, traverseNode(nodeChild)...)
    82  		}
    83  	} else {
    84  		key := singleNodeToConfigNode(node)
    85  		if key.Key != "" {
    86  			serviceKeys = append(serviceKeys, key)
    87  		}
    88  	}
    89  
    90  	return serviceKeys
    91  }
    92  
    93  // GetRecursive returns a slice of all key/value pairs "under" a specified key
    94  // in etcd
    95  func (cb *ConfigBackend) GetRecursive(key string) ([]*model.ConfigNode, error) {
    96  	resp, err := cb.etcdlib.Get(key, true, true)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	nodes := traverseNode(resp.Node)
   102  	return nodes, nil
   103  }
   104  
   105  // Delete a key/value pair by key from etcd
   106  func (cb *ConfigBackend) Delete(key string) error {
   107  	_, err := cb.etcdlib.Delete(key, false)
   108  	return err
   109  }
   110  
   111  // Set a value for the specified key in etcd
   112  func (cb *ConfigBackend) Set(key string, value string) (string, error) {
   113  	resp, err := cb.etcdlib.Set(key, value, 0) // don't use TTLs
   114  	if err != nil {
   115  		return "", err
   116  	}
   117  	return resp.Node.Value, nil
   118  }
   119  
   120  // SetWithTTL sets a value for the specified key in etcd-- with a time to live
   121  func (cb *ConfigBackend) SetWithTTL(key string, value string, ttl uint64) (string, error) {
   122  	resp, err := cb.etcdlib.Update(key, value, ttl)
   123  	if err != nil {
   124  		return "", err
   125  	}
   126  	return resp.Node.Value, nil
   127  }
   128  
   129  // NewConfigBackend returns this etcd-based implementation of the config.Backend
   130  // interface
   131  func NewConfigBackend() (*ConfigBackend, error) {
   132  	var dial func(string, string) (net.Conn, error)
   133  	sshTimeout := time.Duration(fleet.Flags.SSHTimeout*1000) * time.Millisecond
   134  	tun := getTunnelFlag()
   135  	if tun != "" {
   136  		sshClient, err := ssh.NewSSHClient("core", tun, getChecker(), false, sshTimeout)
   137  		if err != nil {
   138  			return nil, fmt.Errorf("failed initializing SSH client: %v", err)
   139  		}
   140  
   141  		dial = func(network, addr string) (net.Conn, error) {
   142  			tcpaddr, err := net.ResolveTCPAddr(network, addr)
   143  			if err != nil {
   144  				return nil, err
   145  			}
   146  			return sshClient.DialTCP(network, nil, tcpaddr)
   147  		}
   148  	}
   149  
   150  	tlsConfig, err := pkg.ReadTLSConfigFiles(fleet.Flags.EtcdCAFile,
   151  		fleet.Flags.EtcdCertFile, fleet.Flags.EtcdKeyFile)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	trans := http.Transport{
   157  		Dial:            dial,
   158  		TLSClientConfig: tlsConfig,
   159  	}
   160  
   161  	timeout := time.Duration(fleet.Flags.RequestTimeout*1000) * time.Millisecond
   162  	machines := []string{fleet.Flags.Endpoint}
   163  
   164  	c := etcdlib.NewClient(machines)
   165  	c.SetDialTimeout(timeout)
   166  
   167  	// use custom transport with SSH tunnel capability
   168  	c.SetTransport(&trans)
   169  
   170  	return &ConfigBackend{etcdlib: c}, nil
   171  }