forked from docker-archive/deploykit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathplugin.go
128 lines (110 loc) · 3.22 KB
/
plugin.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package main
import (
"encoding/json"
"fmt"
"path/filepath"
"time"
log "github.com/Sirupsen/logrus"
"github.com/docker/infrakit/pkg/spi/instance"
"github.com/spf13/afero"
"math/rand"
)
// This example uses local files as a representation of an instance. When we
// create an instance, we write a file in a directory. The content of the file is simply
// the message in the provision spec, so we can verify correctness of the content easily.
// When we destroy an instance, we remove the file.
// DescribeInstances simply would list the files with the matching
// tags.
// Spec is just whatever that can be unmarshalled into a generic JSON map
type Spec map[string]interface{}
func init() {
rand.Seed(time.Now().UTC().UnixNano())
}
// fileInstance represents a single file instance on disk.
type fileInstance struct {
instance.Description
Spec instance.Spec
}
type plugin struct {
Dir string
fs afero.Fs
}
// NewFileInstancePlugin returns an instance plugin backed by disk files.
func NewFileInstancePlugin(dir string) instance.Plugin {
log.Debugln("file instance plugin. dir=", dir)
return &plugin{
Dir: dir,
fs: afero.NewOsFs(),
}
}
// Validate performs local validation on a provision request.
func (p *plugin) Validate(req json.RawMessage) error {
log.Debugln("validate", string(req))
spec := Spec{}
if err := json.Unmarshal(req, &spec); err != nil {
return err
}
log.Debugln("Validated:", spec)
return nil
}
// Provision creates a new instance based on the spec.
func (p *plugin) Provision(spec instance.Spec) (*instance.ID, error) {
// simply writes a file
// use timestamp as instance id
id := instance.ID(fmt.Sprintf("instance-%d", rand.Int63()))
buff, err := json.MarshalIndent(fileInstance{
Description: instance.Description{
Tags: spec.Tags,
ID: id,
LogicalID: spec.LogicalID,
},
Spec: spec,
}, " ", " ")
log.Debugln("provision", id, "data=", string(buff), "err=", err)
if err != nil {
return nil, err
}
return &id, afero.WriteFile(p.fs, filepath.Join(p.Dir, string(id)), buff, 0644)
}
// Destroy terminates an existing instance.
func (p *plugin) Destroy(instance instance.ID) error {
fp := filepath.Join(p.Dir, string(instance))
log.Debugln("destroy", fp)
return p.fs.Remove(fp)
}
// DescribeInstances returns descriptions of all instances matching all of the provided tags.
// TODO - need to define the fitlering of tags => AND or OR of matches?
func (p *plugin) DescribeInstances(tags map[string]string) ([]instance.Description, error) {
log.Debugln("describe-instances", tags)
entries, err := afero.ReadDir(p.fs, p.Dir)
if err != nil {
return nil, err
}
result := []instance.Description{}
scan:
for _, entry := range entries {
fp := filepath.Join(p.Dir, entry.Name())
file, err := p.fs.Open(fp)
if err != nil {
log.Warningln("error opening", fp)
continue scan
}
inst := fileInstance{}
err = json.NewDecoder(file).Decode(&inst)
if err != nil {
log.Warning("cannot decode", entry.Name())
continue scan
}
if len(tags) == 0 {
result = append(result, inst.Description)
} else {
for k, v := range tags {
if inst.Tags[k] != v {
continue scan // we implement AND
}
}
result = append(result, inst.Description)
}
}
return result, nil
}