/
logs.go
149 lines (131 loc) · 4.29 KB
/
logs.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
package controller
import (
"context"
"errors"
"fmt"
"math"
"strings"
"time"
"github.com/railwayapp/cli/entity"
)
const (
GQL_SOFT_ERROR = "Error fetching build logs"
)
func (c *Controller) GetActiveDeploymentLogs(ctx context.Context, numLines int32) error {
deployment, err := c.GetActiveDeployment(ctx)
if err != nil {
return err
}
return c.logsForState(ctx, &entity.DeploymentLogsRequest{
DeploymentID: deployment.ID,
ProjectID: deployment.ProjectID,
NumLines: numLines,
})
}
func (c *Controller) GetActiveBuildLogs(ctx context.Context, numLines int32) error {
projectConfig, err := c.GetProjectConfigs(ctx)
if err != nil {
return err
}
deployment, err := c.gtwy.GetLatestDeploymentForEnvironment(ctx, projectConfig.Project, projectConfig.Environment)
if err != nil {
return err
}
return c.logsForState(ctx, &entity.DeploymentLogsRequest{
DeploymentID: deployment.ID,
ProjectID: projectConfig.Project,
NumLines: numLines,
})
}
/* Logs for state will get logs for a current state (Either building or not building state)
It does this by capturing the initial state of the deploy, and looping while it stays in that state
The loop captures the previous deploy as well as the current and does log diffing on the unified state
When the state transitions from building to not building, the loop breaks
*/
func (c *Controller) logsForState(ctx context.Context, req *entity.DeploymentLogsRequest) error {
// Stream on building -> Building until !Building then break
// Stream on not building -> !Building until Failed then break
deploy, err := c.gtwy.GetDeploymentByID(ctx, &entity.DeploymentByIDRequest{
DeploymentID: req.DeploymentID,
ProjectID: req.ProjectID,
GQL: c.getQuery(ctx, ""),
})
if err != nil {
return err
}
// Print Logs w/ Limit
logLines := strings.Split(logsForState(ctx, deploy.Status, deploy), "\n")
offset := 0.0
if req.NumLines != 0 {
// If a limit is set, walk it back n steps (with a min of zero so no panics)
offset = math.Max(float64(len(logLines))-float64(req.NumLines)-1, 0.0)
}
// GQL may return partial errors for build logs if not ready
// The response won't fail but will be a partial error. Check this.
err = errFromGQL(ctx, logLines)
if err != nil {
return err
}
// Output Initial Logs
currLogs := strings.Join(logLines[int(offset):], "\n")
if len(currLogs) > 0 {
fmt.Println(currLogs)
}
if deploy.Status == entity.STATUS_FAILED {
return errors.New("Build Failed! Please see output for more information")
}
prevDeploy := deploy
logState := deploy.Status
deltaState := hasTransitioned(nil, deploy)
for !deltaState && req.NumLines == 0 {
time.Sleep(2 * time.Second)
currDeploy, err := c.gtwy.GetDeploymentByID(ctx, &entity.DeploymentByIDRequest{
DeploymentID: req.DeploymentID,
ProjectID: req.ProjectID,
GQL: c.getQuery(ctx, logState),
})
if err != nil {
return err
}
// Current Logs fetched from server
currLogs := strings.Split(logsForState(ctx, logState, currDeploy), "\n")
// Previous logs fetched from prevDeploy variable
prevLogs := strings.Split(logsForState(ctx, logState, prevDeploy), "\n")
// Diff logs using the line numbers as references
logDiff := currLogs[len(prevLogs)-1 : len(currLogs)-1]
// If no changes we continue
if len(logDiff) == 0 {
continue
}
// Output logs
fmt.Println(strings.Join(logDiff, "\n"))
// Set out walk pointer forward using the newest logs
deltaState = hasTransitioned(prevDeploy, currDeploy)
prevDeploy = currDeploy
}
return nil
}
func hasTransitioned(prev *entity.Deployment, curr *entity.Deployment) bool {
return prev != nil && curr != nil && prev.Status != curr.Status
}
func (c *Controller) getQuery(ctx context.Context, status string) entity.DeploymentGQL {
return entity.DeploymentGQL{
BuildLogs: status == entity.STATUS_BUILDING || status == "",
DeployLogs: status != entity.STATUS_BUILDING || status == "",
Status: true,
}
}
func logsForState(ctx context.Context, status string, deploy *entity.Deployment) string {
if status == entity.STATUS_BUILDING {
return deploy.BuildLogs
}
return deploy.DeployLogs
}
func errFromGQL(ctx context.Context, logLines []string) error {
for _, l := range logLines {
if strings.Contains(l, GQL_SOFT_ERROR) {
return errors.New(GQL_SOFT_ERROR)
}
}
return nil
}