updated with changes to support automated triggering of post-deploymet provisioning
diff --git a/automation/tracker.go b/automation/tracker.go
new file mode 100644
index 0000000..9f38b23
--- /dev/null
+++ b/automation/tracker.go
@@ -0,0 +1,137 @@
+package main
+
+import (
+ "encoding/json"
+ "github.com/fzzy/radix/redis"
+ "log"
+ "net/url"
+ "os"
+)
+
+type ProvisionState int8
+
+const (
+ Unprovisioned ProvisionState = iota
+ ProvisionError
+ Provisioning
+ Provisioned
+)
+
+func (s *ProvisionState) String() string {
+ switch *s {
+ case Unprovisioned:
+ return "UNPROVISIONED"
+ case ProvisionError:
+ return "PROVISIONERROR"
+ case Provisioning:
+ return "PROVISIONING"
+ case Provisioned:
+ return "PROVISIONED"
+ default:
+ return "UNKNOWN"
+ }
+}
+
+// TrackerRecord state kept for each node to be provisioned
+type TrackerRecord struct {
+ State ProvisionState
+
+ // Timeestamp maintains the time the node started provisioning, eventually will be used to time out
+ // provisinion states
+ Timestamp int64
+}
+
+// Tracker used to track if a node has been post deployed provisioned
+type Tracker interface {
+ Get(key string) (*TrackerRecord, error)
+ Set(key string, record *TrackerRecord) error
+ Clear(key string) error
+}
+
+// RedisTracker redis implementation of the tracker interface
+type RedisTracker struct {
+ client *redis.Client
+}
+
+func (t *RedisTracker) Get(key string) (*TrackerRecord, error) {
+ reply := t.client.Cmd("get", key)
+ if reply.Err != nil {
+ return nil, reply.Err
+ }
+ if reply.Type == redis.NilReply {
+ var record TrackerRecord
+ record.State = Unprovisioned
+ return &record, nil
+ }
+
+ value, err := reply.Str()
+ if err != nil {
+ return nil, err
+ }
+ var record TrackerRecord
+ err = json.Unmarshal([]byte(value), &record)
+ if err != nil {
+ return nil, err
+ }
+ return &record, nil
+}
+
+func (t *RedisTracker) Set(key string, record *TrackerRecord) error {
+ reply := t.client.Cmd("set", key, true)
+ return reply.Err
+}
+
+func (t *RedisTracker) Clear(key string) error {
+ reply := t.client.Cmd("del", key)
+ return reply.Err
+}
+
+// MemoryTracker in memory implementation of the tracker interface
+type MemoryTracker struct {
+ data map[string]TrackerRecord
+}
+
+func (m *MemoryTracker) Get(key string) (*TrackerRecord, error) {
+ if value, ok := m.data[key]; ok {
+ return &value, nil
+ }
+ var record TrackerRecord
+ record.State = Unprovisioned
+ return &record, nil
+}
+
+func (m *MemoryTracker) Set(key string, record *TrackerRecord) error {
+ m.data[key] = *record
+ return nil
+}
+
+func (m *MemoryTracker) Clear(key string) error {
+ delete(m.data, key)
+ return nil
+}
+
+// NetTracker constructs an implemetation of the Tracker interface. Which implementation selected
+// depends on the environment. If a link to a redis instance is defined then this will
+// be used, else an in memory version will be used.
+func NewTracker() Tracker {
+ // Check the environment to see if we are linked to a redis DB
+ if os.Getenv("AUTODB_ENV_REDIS_VERSION") != "" {
+ tracker := new(RedisTracker)
+ if spec := os.Getenv("AUTODB_PORT"); spec != "" {
+ port, err := url.Parse(spec)
+ checkError(err, "[error] unable to lookup to redis database : %s", err)
+ tracker.client, err = redis.Dial(port.Scheme, port.Host)
+ checkError(err, "[error] unable to connect to redis database : '%s' : %s", port, err)
+ log.Println("[info] Using REDIS to track provisioning status of nodes")
+ return tracker
+ } else {
+ log.Fatalf("[error] looks like we are configured for REDIS, but no PORT defined in environment")
+ }
+ }
+
+ // Else fallback to an in memory tracker
+ tracker := new(MemoryTracker)
+ tracker.data = make(map[string]TrackerRecord)
+ log.Println("[info] Using memory based structures to track provisioning status of nodes")
+ return tracker
+}