-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathapi.go
241 lines (232 loc) · 9.19 KB
/
api.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
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
package patrol
import (
"encoding/json"
)
const (
API_TOGGLE_STATE_ENABLE = iota + 1
API_TOGGLE_STATE_DISABLE
API_TOGGLE_STATE_RESTART
API_TOGGLE_STATE_RUNONCE_ENABLE // enable runonce
API_TOGGLE_STATE_RUNONCE_DISABLE // disable runonce
API_TOGGLE_STATE_ENABLE_RUNONCE_ENABLE // enable AND enable runonce
API_TOGGLE_STATE_ENABLE_RUNONCE_DISABLE // enable AND disable runonce
)
const (
LISTEN_HTTP_PORT_DEFAULT = 8421
LISTEN_UDP_PORT_DEFAULT = 1248
)
// Requests by Default are STATELESS - If no values are set then nothing is modified!
// The reason we're stateless by default is so that our UDP endpoint can make requests as if it were a HTTP GET Request
// UDP has the downside that if an error occurs a response will not be sent in return
type API_Request struct {
// Unique Identifier
ID string `json:"id,omitempty"`
// Group: `app` or `service`
Group string `json:"group,omitempty"`
// Ping?
// Only supported by either APP_KEEPALIVE_HTTP or APP_KEEPALIVE_UDP
// If APP_KEEPALIVE_HTTP is used, the HTTP endpoint MUST be used
// If APP_KEEPALIVE_UDP is used, the UDP endpoint MUST be used
Ping bool `json:"ping,omitempty"`
// App Process ID
// Ping MUST be true if we wish to send a PID
PID uint32 `json:"pid,omitempty"`
// Toggle State
//
// API_TOGGLE_STATE_ENABLE = 1
// API_TOGGLE_STATE_DISABLE = 2
// API_TOGGLE_STATE_RESTART = 3
// API_TOGGLE_STATE_RUNONCE_ENABLE = 4
// API_TOGGLE_STATE_RUNONCE_DISABLE = 5
// API_TOGGLE_STATE_ENABLE_RUNONCE_ENABLE = 6
// API_TOGGLE_STATE_ENABLE_RUNONCE_DISABLE = 7
//
// API_TOGGLE_STATE_ENABLE: Enable App or Service
// API_TOGGLE_STATE_DISABLE: Disable App or Service
// API_TOGGLE_STATE_RESTART: Restart App or Service, Enable App or Service if Disabled
// API_TOGGLE_STATE_RUNONCE_ENABLE: Enable RunOnce for App or Service
// API_TOGGLE_STATE_RUNONCE_DISABLE: Disable RunOnce for App or Service
// API_TOGGLE_STATE_ENABLE_RUNONCE_ENABLE: Enable App or Service and Enable RunOnce
// API_TOGGLE_STATE_ENABLE_RUNONCE_DISABLE: Enable App or Service and Disable RunOnce
Toggle uint8 `json:"toggle,omitempty"`
// Return History?
History bool `json:"history,omitempty"`
// KeyValue
KeyValue map[string]interface{} `json:"keyvalue,omitempty"`
// If KeyValueReplace is true, previous KeyValue will be replaced with KeyValue
KeyValueReplace bool `json:"keyvalue-replace,omitempty"`
// Secret is required to access the /api GET and POST endpoints
Secret string `json:"secret,omitempty"`
// CAS IS OPTIONAL
// if CAS is NOT set: we will ignore it and we will override all of our values and state!!!
// if CAS IS SET: we will only override values if our CAS is correct!
// HOWEVER, we will ALWAYS update our PING/LastSeen value REGARDLESS OF CAS!!!
// updating `Ping, LastSeen, or PID` will cause our CAS to be incremented!!!
CAS uint64 `json:"cas,omitempty"`
}
func (self *API_Request) IsValid() bool {
if self == nil {
return false
}
return true
}
// An API Response references our STATE at the time of Request
// If any values change or CAS is incremented, they will STILL reference the premodification state!
//
// When using UDP, we won't be able to respond with all of our data, we're going to have to limit our response size
// We're going to limit our response to: `id, group, pid, started, lastseen, disabled, restart, run-once, shutdown`
// We'll have to ignore `history, keyvalue, and errors`, if they're needed the HTTP endpoint should be used instead
type API_Response struct {
// Unique Identifier
ID string `json:"id,omitempty"`
// Instance ID - UUIDv4 - Only exists IF we're running!
InstanceID string `json:"instance-id,omitempty"`
// Group: `app` or `service`
Group string `json:"group,omitempty"`
// Display Name
Name string `json:"name,omitempty"`
// App Process ID
PID uint32 `json:"pid,omitempty"`
// Timestamp App or Service started at
Started *Timestamp `json:"started,omitempty"`
// Timestamp App or Service was last seen
LastSeen *Timestamp `json:"lastseen,omitempty"`
// Is our App or Service Disabled?
Disabled bool `json:"disabled,omitempty"`
// Is our App or Service in a Restart state?
Restart bool `json:"restart,omitempty"`
// Is our App or Service set to RunOnce?
RunOnce bool `json:"run-once,omitempty"`
// Is Patrol in a Shutdown state?
Shutdown bool `json:"shutdown,omitempty"`
// History of previous App or Service states at the time of close()
History []*History `json:"history,omitempty"`
// Current state's KeyValue
KeyValue map[string]interface{} `json:"keyvalue,omitempty"`
// Does this App or Service require a Secret to modify?
Secret bool `json:"secret,omitempty"`
// Did any Errors occur?
Errors []string `json:"errors,omitempty"`
// like all of our other values, CAS is a snapshot of our PREVIOUS state
// we are NEVER going to return our current CAS after modifying our current state or values
// the reason for this is that if a modification request is successful, we know our CAS is CAS + 1
// if we were to take a snapshot, update our object, then get our CAS ---
// we could never actually verify what our current state or values are!!!
// the reason for this has to do with triggers, we NEVER KNOW when we're going to unlock and/or execute triggers!!!
// there are going to be very many scenarios where an API request is made and our CAS is updated more than once!!!
// we're never in a scenario where we take a snapshot, update, and get our CAS WITHOUT UNLOCKING!!!
// if we want to make a clean CAS, we should do a REQUEST without modifying anything(no ping), then do a secondary request without incrementing CAS!
CAS uint64 `json:"cas,omitempty"`
// CASInvalid is the only exception to data that references our previous snapshot
// We need to know if our CAS was successful or not!
// I prefer to have this as invalid and not valid as most requests without a CAS will be valid!
CASInvalid bool `json:"cas-invalid,omitempty"`
// this is for unmarshal only
patrol *Patrol
}
// I'm not sure how else to override/fix our timestamp when using a custom Timestamp Parse in Unmarshal
// we're going to clone our struct except for []History
// we will temporarily use json.RawMessage and then we will create a History object for each result
// this really isn't that aesthetic but it works well!
type api_response struct {
ID string `json:"id,omitempty"`
InstanceID string `json:"instance-id,omitempty"`
Group string `json:"group,omitempty"`
Name string `json:"name,omitempty"`
PID uint32 `json:"pid,omitempty"`
Started *Timestamp `json:"started,omitempty"`
LastSeen *Timestamp `json:"lastseen,omitempty"`
Disabled bool `json:"disabled,omitempty"`
Restart bool `json:"restart,omitempty"`
RunOnce bool `json:"run-once,omitempty"`
Shutdown bool `json:"shutdown,omitempty"`
History []json.RawMessage `json:"history,omitempty"`
KeyValue map[string]interface{} `json:"keyvalue,omitempty"`
Secret bool `json:"secret,omitempty"`
Errors []string `json:"errors,omitempty"`
CAS uint64 `json:"cas,omitempty"`
CASInvalid bool `json:"cas-invalid,omitempty"`
}
func (self *API_Response) IsValid() bool {
if self == nil {
return false
}
return true
}
func (self *API_Response) UnmarshalJSON(
data []byte,
) error {
result := &api_response{
Started: self.NewAPITimestamp(),
LastSeen: self.NewAPITimestamp(),
}
if err := json.Unmarshal(data, result); err != nil {
return err
}
if result == nil {
return nil
}
// unmarshal history
if l := len(result.History); l > 0 {
self.History = make([]*History, 0, l)
for i := 0; i < l; i++ {
h := self.NewAPIHistory()
if err := json.Unmarshal(result.History[i], h); err != nil {
return err
}
self.History = append(self.History, h)
}
}
// fix response
self.ID = result.ID
self.InstanceID = result.InstanceID
self.Group = result.Group
self.Name = result.Name
self.PID = result.PID
self.Started = result.Started
self.LastSeen = result.LastSeen
self.Disabled = result.Disabled
self.Restart = result.Restart
self.RunOnce = result.RunOnce
self.Shutdown = result.Shutdown
self.KeyValue = result.KeyValue
self.Secret = result.Secret
self.Errors = result.Errors
self.CAS = result.CAS
self.CASInvalid = result.CASInvalid
return nil
}
func (self *API_Response) NewAPITimestamp() *Timestamp {
if !self.patrol.IsValid() {
return &Timestamp{}
}
return self.patrol.NewAPITimestamp()
}
func (self *API_Response) NewAPIHistory() *History {
if !self.patrol.IsValid() {
return &History{
Started: &Timestamp{},
LastSeen: &Timestamp{},
Stopped: &Timestamp{},
}
}
return self.patrol.NewAPIHistory()
}
// use this when our response needs a reference to patrol for custom timestamp unmarshaling
func (self *Patrol) NewAPIResponse() *API_Response {
return &API_Response{
patrol: self,
}
}
func (self *Patrol) NewAPIHistory() *History {
return &History{
Started: self.NewAPITimestamp(),
LastSeen: self.NewAPITimestamp(),
Stopped: self.NewAPITimestamp(),
}
}
func (self *Patrol) NewAPITimestamp() *Timestamp {
return &Timestamp{
TimestampFormat: self.config.Timestamp,
}
}