forked from surminus/viaduct
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresource.go
More file actions
206 lines (172 loc) · 6.07 KB
/
Copy pathresource.go
File metadata and controls
206 lines (172 loc) · 6.07 KB
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package viaduct
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
"time"
)
// errAbandoned is returned when a resource is still running once its timeout
// has passed. The operation carries on, so the run treats it as a reason to
// stop rather than as an ordinary failure.
var errAbandoned = errors.New("timed out")
// ResourceKind is the kind of resource, such as "File" or "Package".
type ResourceKind string
// Resource holds details about a particular resource during a Viaduct run.
type Resource struct {
// ResourceID is the resources generated ID.
ResourceID
// ResourceKind is what the resource kind is, such as "File" or "Package".
ResourceKind
// Status denotes the current status of the resource.
Status
// Attributes are the resource type attributes.
Attributes ResourceAttributes
// DependsOn is a list of resource dependencies.
DependsOn []ResourceID `json:"DependsOn,omitempty"`
// GlobalLock will mean the resource will not run at the same time
// as other resources that have this set to true.
GlobalLock bool
// LockKey narrows the lock to a single domain, so the resource only
// waits for other resources using the same key. Empty means the
// resource is serialised against every other lock holder.
LockKey string `json:"LockKey,omitempty"`
// Timeout overrides how long this resource is given to run. Zero uses
// the manifest setting, and a negative duration means no timeout.
Timeout time.Duration `json:"Timeout,omitempty"`
// Error contains any errors raised during a run.
Error `json:"Error"`
}
type Error struct {
Err error `json:"-"`
Message string `json:"Message"`
}
// ResourceParams are a set of options that each resource can set
// depending on their logic
type ResourceParams struct {
// GlobalLock can be set to ensure that the resource uses a global
// lock during operations
GlobalLock bool
// LockKey names the domain the lock applies to, such as PackageLock or
// PasswdLock. The resource then only waits for other resources using the
// same key, rather than for every lock holder in the run. Setting a key
// implies a lock.
LockKey string
}
// NewResourceParams creates a new ResourceParams.
func NewResourceParams() *ResourceParams {
return &ResourceParams{}
}
// NewResourceParamsWithLock creates a new ResourceParams, but with
// a global lock applied. The resource is serialised against every other lock
// holder in the run: prefer NewResourceParamsWithLockKey when you know which
// domain the resource contends on.
func NewResourceParamsWithLock() *ResourceParams {
return &ResourceParams{GlobalLock: true}
}
// NewResourceParamsWithLockKey creates a new ResourceParams with a lock that
// only applies to the given domain, such as PackageLock or PasswdLock.
func NewResourceParamsWithLockKey(key string) *ResourceParams {
return &ResourceParams{GlobalLock: true, LockKey: key}
}
// ResourceAttributes implement different resource types, such as File or
// Directory. As long as this interface is implemented, then custom resources
// can be directly integrated.
type ResourceAttributes interface {
// Description returns a short human-readable identifier for this
// resource instance, such as a file path or command.
Description() string
// OperationName is a simple identifier for the operation type, such as
// Create, Delete, Update or Run.
OperationName() string
// Params are a set of parameters that determine how a resource should
// interact with Viaduct.
Params() *ResourceParams
// PreflightChecks are used to check that the resource parameters have been
// correctly set, and to ensure that default parameters are assigned.
PreflightChecks(log *Logger) error
// Run performs the resource operation.
Run(log *Logger) error
}
// ResourceID is an id of a resource.
type ResourceID string
func newResource(deps []*Resource) (*Resource, error) {
var dependsOn []ResourceID
for _, d := range deps {
if d.ResourceID == "" {
return &Resource{}, fmt.Errorf("dependency is not a valid resource: %s", attrJSON(d))
}
dependsOn = append(dependsOn, d.ResourceID)
}
return &Resource{
DependsOn: dependsOn,
Status: Pending,
}, nil
}
func (r *Resource) init(a ResourceAttributes) error {
if err := r.setKind(a); err != nil {
return err
}
return nil
}
func (r *Resource) setKind(a ResourceAttributes) error {
t := reflect.TypeOf(a)
if t.Kind() != reflect.Pointer {
return fmt.Errorf("cannot determine resource type")
}
k := t.Elem().Name()
r.ResourceKind = ResourceKind(k)
return nil
}
func (r *Resource) setID() error {
if r.ResourceKind == "" {
return fmt.Errorf("resource kind has not been set")
}
j, err := json.Marshal(r)
if err != nil {
return err
}
h := sha1.New()
h.Write(j)
sha := hex.EncodeToString(h.Sum(nil))
idstr := strings.Join([]string{"id", sha[0:8]}, "-")
r.ResourceID = ResourceID(strings.Join([]string{string(r.ResourceKind), idstr}, "_"))
return nil
}
func (r *Resource) preflight() error {
log := NewLogger(string(r.ResourceKind), "Preflight")
return r.Attributes.PreflightChecks(log)
}
// run performs the resource operation, giving up on it if it takes longer than
// the timeout. A timeout of zero or less lets it run for as long as it takes.
//
// A resource operation cannot be cancelled, since Run takes no context, so an
// abandoned resource carries on in the background: giving up means the run
// stops waiting for it and reports it as failed, not that whatever it was
// doing has stopped.
func (r *Resource) run(timeout time.Duration) (*Logger, error) {
log := NewLogger(string(r.ResourceKind), r.Attributes.OperationName())
if timeout <= 0 {
return log, r.Attributes.Run(log)
}
done := make(chan error, 1)
go func() {
done <- r.Attributes.Run(log)
}()
select {
case err := <-done:
return log, err
case <-time.After(timeout):
return log, fmt.Errorf(
"%w after %s: the operation was abandoned and may still be running. Raise the limit with WithTimeout, SetResourceTimeout or --resource-timeout",
errAbandoned,
timeout,
)
}
}
func (r *Resource) Failed() bool {
return r.Status == Failed || r.Status == DependencyFailed
}