/
create_pipeline.go
203 lines (173 loc) · 5.82 KB
/
create_pipeline.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
package pipeline
import (
"errors"
"fmt"
"strconv"
"strings"
"unicode"
"github.com/gaia-pipeline/gaia/security"
"github.com/gaia-pipeline/gaia"
"github.com/gaia-pipeline/gaia/services"
)
const (
// Percent of pipeline creation progress after git clone
pipelineCloneStatus = 25
// Percent of pipeline creation progress after compile process done
pipelineCompileStatus = 50
// Percent of pipeline creation progress after validation
pipelineValidateStatus = 75
// Completed percent progress
pipelineCompleteStatus = 100
// Split char to separate path from pipeline and name
pipelinePathSplitChar = "/"
)
var (
// errPathLength is a validation error during pipeline name input
errPathLength = errors.New("name of pipeline is empty or one of the path elements length exceeds 50 characters")
// errPipelineNameInUse is thrown when a pipelines name is already in use
errPipelineNameInUse = errors.New("pipeline name is already in use")
// errPipelineNameInvalid is thrown when the pipeline name contains invalid characters
errPipelineNameInvalid = errors.New("must match [A-z][0-9][-][_][ ]")
)
// CreatePipeline is the main function which executes step by step the creation
// of a plugin.
// After each step, the status is written to store and can be retrieved via API.
func (s *GaiaPipelineService) CreatePipeline(p *gaia.CreatePipeline) {
gitToken := p.GitHubToken
p.GitHubToken = ""
storeService, _ := services.StorageService()
// Define build process for the given type
bP := newBuildPipeline(p.Pipeline.Type)
if bP == nil {
// Pipeline type is not supported
p.StatusType = gaia.CreatePipelineFailed
p.Output = fmt.Sprintf("create pipeline failed. Pipeline type is not supported %s is not supported", p.Pipeline.Type)
_ = storeService.CreatePipelinePut(p)
return
}
// Setup environment before cloning repo and command
err := bP.PrepareEnvironment(p)
if err != nil {
p.StatusType = gaia.CreatePipelineFailed
p.Output = fmt.Sprintf("cannot prepare build: %s", err.Error())
_ = storeService.CreatePipelinePut(p)
return
}
// Clone git repo
err = gitCloneRepo(p.Pipeline.Repo)
if err != nil {
p.StatusType = gaia.CreatePipelineFailed
p.Output = fmt.Sprintf("cannot prepare build: %s", err.Error())
_ = storeService.CreatePipelinePut(p)
return
}
// Update status of our pipeline build
p.Status = pipelineCloneStatus
err = storeService.CreatePipelinePut(p)
if err != nil {
p.StatusType = gaia.CreatePipelineFailed
gaia.Cfg.Logger.Error("cannot put create pipeline into store", "error", err.Error())
return
}
// Run compile process
err = bP.ExecuteBuild(p)
if err != nil {
p.StatusType = gaia.CreatePipelineFailed
_ = storeService.CreatePipelinePut(p)
return
}
// Update status of our pipeline build
p.Status = pipelineCompileStatus
err = storeService.CreatePipelinePut(p)
if err != nil {
p.StatusType = gaia.CreatePipelineFailed
gaia.Cfg.Logger.Error("cannot put create pipeline into store", "error", err.Error())
return
}
// Run update if needed
err = updatePipeline(&p.Pipeline)
if err != nil {
p.StatusType = gaia.CreatePipelineFailed
p.Output = fmt.Sprintf("cannot update pipeline: %s", err.Error())
_ = storeService.CreatePipelinePut(p)
return
}
// Try to get pipeline jobs to check if this pipeline is valid.
if err = s.deps.Scheduler.SetPipelineJobs(&p.Pipeline); err != nil {
p.StatusType = gaia.CreatePipelineFailed
p.Output = fmt.Sprintf("cannot validate pipeline: %s", err.Error())
_ = storeService.CreatePipelinePut(p)
return
}
// Update status of our pipeline build
p.Status = pipelineValidateStatus
err = storeService.CreatePipelinePut(p)
if err != nil {
gaia.Cfg.Logger.Error("cannot put create pipeline into store", "error", err.Error())
return
}
p.Pipeline.TriggerToken = security.GenerateRandomUUIDV5()
// Save the generated pipeline data
err = bP.SavePipeline(&p.Pipeline)
if err != nil {
p.StatusType = gaia.CreatePipelineFailed
p.Output = fmt.Sprintf("failed to save the created pipeline: %s", err.Error())
_ = storeService.CreatePipelinePut(p)
return
}
// Copy compiled binary to plugins folder which is the final step
err = bP.CopyBinary(p)
if err != nil {
p.StatusType = gaia.CreatePipelineFailed
p.Output = fmt.Sprintf("cannot copy compiled binary: %s", err.Error())
_ = storeService.CreatePipelinePut(p)
return
}
// Set create pipeline status to complete
p.Status = pipelineCompleteStatus
p.StatusType = gaia.CreatePipelineSuccess
err = storeService.CreatePipelinePut(p)
if err != nil {
p.StatusType = gaia.CreatePipelineFailed
gaia.Cfg.Logger.Error("cannot put create pipeline into store", "error", err.Error())
return
}
if !gaia.Cfg.Poll && len(gitToken) > 0 {
// if there is a githubtoken provided, that means that a webhook was requested to be added.
id := strconv.Itoa(p.Pipeline.ID)
err = createGithubWebhook(gitToken, p.Pipeline.Repo, id, nil)
if err != nil {
gaia.Cfg.Logger.Error("error while creating webhook for repository", "error", err.Error())
return
}
}
}
// ValidatePipelineName validates a given pipeline name and
// returns the correct error back.
func ValidatePipelineName(pName string) error {
valid := func(r rune) bool {
return unicode.IsDigit(r) || unicode.IsLetter(r) || unicode.IsSpace(r) || r == '-' || r == '_'
}
// Note, this is faster than regex.
for _, c := range pName {
if !valid(c) {
return errPipelineNameInvalid
}
}
// The name could contain a path. Split it up.
path := strings.Split(pName, pipelinePathSplitChar)
// Iterate all objects.
for _, s := range path {
// Length should be correct.
if len(s) < 1 || len(s) > 50 {
return errPathLength
}
// Check if pipeline name is already in use.
for _, activePipeline := range GlobalActivePipelines.GetAll() {
if strings.EqualFold(s, activePipeline.Name) {
return errPipelineNameInUse
}
}
}
return nil
}