/
status.go
146 lines (128 loc) · 3.98 KB
/
status.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
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/go-kit/kit/log"
"github.com/pkg/errors"
)
type StatusReport []StatusItem
type StatusItem struct {
Version float64 `json:"version"`
TimestampUTC string `json:"timestampUTC"`
Status Status `json:"status"`
}
type Type string
const (
StatusTransitioning Type = "transitioning"
StatusError Type = "error"
StatusSuccess Type = "success"
)
type Status struct {
Operation string `json:"operation"`
Status Type `json:"status"`
FormattedMessage FormattedMessage `json:"formattedMessage"`
}
type FormattedMessage struct {
Lang string `json:"lang"`
Message string `json:"message"`
}
func NewStatus(t Type, operation, message string) StatusReport {
return []StatusItem{
{
Version: 1.0,
TimestampUTC: time.Now().UTC().Format(time.RFC3339),
Status: Status{
Operation: operation,
Status: t,
FormattedMessage: FormattedMessage{
Lang: "en",
Message: message},
},
},
}
}
func (r StatusReport) marshal() ([]byte, error) {
return json.MarshalIndent(r, "", "\t")
}
// Save persists the status message to the specified status folder using the
// sequence number. The operation consists of writing to a temporary file in the
// same folder and moving it to the final destination for atomicity.
func (r StatusReport) Save(statusFolder string, seqNum int) error {
fn := fmt.Sprintf("%d.status", seqNum)
path := filepath.Join(statusFolder, fn)
tmpFile, err := ioutil.TempFile(statusFolder, fn)
if err != nil {
return fmt.Errorf("status: failed to create temporary file: %v", err)
}
tmpFile.Close()
b, err := r.marshal()
if err != nil {
return fmt.Errorf("status: failed to marshal into json: %v", err)
}
if err := ioutil.WriteFile(tmpFile.Name(), b, 0644); err != nil {
return fmt.Errorf("status: failed to path=%s error=%v", tmpFile.Name(), err)
}
if err := os.Rename(tmpFile.Name(), path); err != nil {
return fmt.Errorf("status: failed to move to path=%s error=%v", path, err)
}
return nil
}
// reportStatus saves operation status to the status file for the extension
// handler with the optional given message, if the given cmd requires reporting
// status.
//
// If an error occurs reporting the status, it will be logged and returned.
func reportStatus(ctx *log.Context, hEnv HandlerEnvironment, seqNum int, t Type, c cmd, msg string) error {
if !c.shouldReportStatus {
ctx.Log("status", "not reported for operation (by design)")
return nil
}
s := NewStatus(t, c.name, statusMsg(c, t, msg))
if err := s.Save(hEnv.HandlerEnvironment.StatusFolder, seqNum); err != nil {
ctx.Log("event", "failed to save handler status", "error", err)
return errors.Wrap(err, "failed to save handler status")
}
return nil
}
// readStatus loads current status file in StatusReport
func readStatus(ctx *log.Context, hEnv HandlerEnvironment, seqNum int) (Type, error) {
fileName := fmt.Sprintf("%d.status", seqNum)
path := filepath.Join(hEnv.HandlerEnvironment.StatusFolder, fileName)
buffer, err := ioutil.ReadFile(path)
if err != nil {
return "", fmt.Errorf("Error reading status file %s: %v", path, err)
}
var statusReport StatusReport
if err := json.Unmarshal(buffer, &statusReport); err != nil {
return "", fmt.Errorf("error parsing json: %v", err)
}
if len(statusReport) != 1 {
return "", fmt.Errorf("wrong statusReport count. expected:1, got:%d", len(statusReport))
}
return statusReport[0].Status.Status, nil
}
// statusMsg creates the reported status message based on the provided operation
// type and the given message string.
//
// A message will be generated for empty string. For error status, pass the
// error message.
func statusMsg(c cmd, t Type, msg string) string {
s := c.name
switch t {
case StatusSuccess:
s += " succeeded"
case StatusTransitioning:
s += " in progress"
case StatusError:
s += " failed"
}
if msg != "" {
// append the original
s += ": " + msg
}
return s
}