ClusterManager: Implements support of remote clusters.

After this change users will be able to:
* Create cluster and add/remove servers to it
* Install apps on remote cluster
* Move already installed apps between clusters
* Apps running on server being removed will auto-migrate
  to another server from that same cluster

This is achieved by:
* Installing and running minimal version of dodo on remote cluster
* Ingress-nginx is installed automatically on new clusters
* Next to nginx we run VPN client in the same pod, so that
  default cluster can establish secure communication with it
* Multiple reverse proxies are configured to get to the
  remote cluster service from ingress installed on default cluster.

Next steps:
* Support remote clusters in dodo apps (prototype ready)
* Clean up old cluster when moving app to the new one. Currently
  old cluster keeps running app pods even though no ingress can
  reach it anymore.

Change-Id: Iffc908c93416d4126a8e1c2832eae7b659cb8044
diff --git a/core/dns-api/store.go b/core/dns-api/store.go
index 6d0a7c1..8dfd914 100644
--- a/core/dns-api/store.go
+++ b/core/dns-api/store.go
@@ -2,11 +2,16 @@
 
 import (
 	"fmt"
+	"io"
+	"os"
 )
 
 type RecordStore interface {
+	Log() error
 	Add(entry, txt string) error
+	AddARecord(entry, ip string) error
 	Delete(entry, txt string) error
+	DeleteARecord(entry, ip string) error
 }
 
 type fsRecordStore struct {
@@ -16,21 +21,32 @@
 	db       string
 }
 
+func (s *fsRecordStore) Log() error {
+	r, err := s.fs.Reader(s.db)
+	if err != nil {
+		return err
+	}
+	defer r.Close()
+	_, err = io.Copy(os.Stdout, r)
+	return err
+}
+
 func (s *fsRecordStore) read() (*RecordsFile, error) {
 	r, err := s.fs.Reader(s.db)
 	if err != nil {
 		return nil, err
 	}
 	defer r.Close()
-	return NewRecordsFile(r)
+	return NewRecordsFile(io.TeeReader(r, os.Stdout))
 }
+
 func (s *fsRecordStore) write(z *RecordsFile) error {
 	w, err := s.fs.Writer(s.db)
 	if err != nil {
 		return err
 	}
 	defer w.Close()
-	return z.Write(w)
+	return z.Write(io.MultiWriter(w, os.Stdout))
 }
 
 func (s *fsRecordStore) Add(entry, txt string) error {
@@ -46,6 +62,19 @@
 	return s.write(z)
 }
 
+func (s *fsRecordStore) AddARecord(entry, ip string) error {
+	z, err := s.read()
+	if err != nil {
+		return err
+	}
+	fqdn := fmt.Sprintf("%s.%s.", entry, s.zone)
+	z.CreateARecord(fqdn, ip)
+	// for _, ip := range s.publicIP {
+	// 	z.CreateARecord(fqdn, ip)
+	// }
+	return s.write(z)
+}
+
 func (s *fsRecordStore) Delete(entry, txt string) error {
 	z, err := s.read()
 	if err != nil {
@@ -56,3 +85,16 @@
 	// z.DeleteRecordsFor(fqdn)
 	return s.write(z)
 }
+
+func (s *fsRecordStore) DeleteARecord(entry, ip string) error {
+	z, err := s.read()
+	if err != nil {
+		return err
+	}
+	fqdn := fmt.Sprintf("%s.%s.", entry, s.zone)
+	if err := z.DeleteARecord(fqdn, ip); err != nil {
+		return err
+	}
+	// z.DeleteRecordsFor(fqdn)
+	return s.write(z)
+}