|
| 1 | +package builder |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "fmt" |
| 6 | + "sync" |
| 7 | + "time" |
| 8 | + |
| 9 | + "github.com/sirupsen/logrus" |
| 10 | +) |
| 11 | + |
| 12 | +type BuildResult struct { |
| 13 | + EndAt time.Time |
| 14 | + Err error |
| 15 | +} |
| 16 | + |
| 17 | +type EvalResult struct { |
| 18 | + EndAt time.Time |
| 19 | + OutPath string |
| 20 | + DrvPath string |
| 21 | + MachineId string |
| 22 | + Err error |
| 23 | +} |
| 24 | + |
| 25 | +type EvalFunc func(ctx context.Context, flakeUrl string, hostname string) (drvPath string, outPath string, machineId string, err error) |
| 26 | +type BuildFunc func(ctx context.Context, drvPath string) error |
| 27 | + |
| 28 | +type Builder struct { |
| 29 | + hostname string |
| 30 | + mu sync.Mutex |
| 31 | + evalFunc EvalFunc |
| 32 | + buildFunc BuildFunc |
| 33 | + evalTimeout time.Duration |
| 34 | + buildTimeout time.Duration |
| 35 | + IsEvaluating bool |
| 36 | + IsBuilding bool |
| 37 | + evalCancelFunc context.CancelFunc |
| 38 | + buildCancelFunc context.CancelFunc |
| 39 | + buildCtx context.Context |
| 40 | + generation *Generation |
| 41 | + previousGeneration *Generation |
| 42 | + EvaluationDone chan Generation |
| 43 | + BuildDone chan Generation |
| 44 | +} |
| 45 | + |
| 46 | +func New(hostname string, evalTimeout time.Duration, evalFunc EvalFunc, buildTimeout time.Duration, buildFunc BuildFunc) *Builder { |
| 47 | + return &Builder{ |
| 48 | + hostname: hostname, |
| 49 | + evalFunc: evalFunc, |
| 50 | + evalTimeout: evalTimeout, |
| 51 | + buildFunc: buildFunc, |
| 52 | + buildTimeout: buildTimeout, |
| 53 | + BuildDone: make(chan Generation), |
| 54 | + } |
| 55 | +} |
| 56 | + |
| 57 | +type Status int64 |
| 58 | + |
| 59 | +const ( |
| 60 | + Init Status = iota |
| 61 | + Evaluating |
| 62 | + EvaluationSucceeded |
| 63 | + EvaluationFailed |
| 64 | + Building |
| 65 | + BuildSucceeded |
| 66 | + BuildFailed |
| 67 | +) |
| 68 | + |
| 69 | +func StatusToString(status Status) string { |
| 70 | + switch status { |
| 71 | + case Init: |
| 72 | + return "init" |
| 73 | + case Evaluating: |
| 74 | + return "evaluating" |
| 75 | + case EvaluationSucceeded: |
| 76 | + return "evaluation-succeeded" |
| 77 | + case EvaluationFailed: |
| 78 | + return "evaluation-failed" |
| 79 | + case Building: |
| 80 | + return "building" |
| 81 | + case BuildSucceeded: |
| 82 | + return "build-succeeded" |
| 83 | + case BuildFailed: |
| 84 | + return "build-failed" |
| 85 | + } |
| 86 | + return "" |
| 87 | +} |
| 88 | + |
| 89 | +func StatusFromString(status string) Status { |
| 90 | + switch status { |
| 91 | + case "init": |
| 92 | + return Init |
| 93 | + case "evaluating": |
| 94 | + return Evaluating |
| 95 | + case "evaluation-succeeded": |
| 96 | + return EvaluationSucceeded |
| 97 | + case "evaluation-failed": |
| 98 | + return EvaluationFailed |
| 99 | + case "building": |
| 100 | + return Building |
| 101 | + case "build-succeeded": |
| 102 | + return BuildSucceeded |
| 103 | + case "build-failed": |
| 104 | + return BuildFailed |
| 105 | + } |
| 106 | + return Init |
| 107 | +} |
| 108 | + |
| 109 | +// We consider each created genration is legit to be deployed: hard |
| 110 | +// reset is ensured at RepositoryStatus creation. |
| 111 | +type Generation struct { |
| 112 | + UUID string `json:"uuid"` |
| 113 | + FlakeUrl string `json:"flake-url"` |
| 114 | + Hostname string `json:"hostname"` |
| 115 | + |
| 116 | + Status Status `json:"status"` |
| 117 | + |
| 118 | + SelectedRemoteUrl string `json:"remote-url"` |
| 119 | + SelectedRemoteName string `json:"remote-name"` |
| 120 | + SelectedBranchName string `json:"branch-name"` |
| 121 | + SelectedCommitId string `json:"commit-id"` |
| 122 | + SelectedCommitMsg string `json:"commit-msg"` |
| 123 | + SelectedBranchIsTesting bool `json:"branch-is-testing"` |
| 124 | + |
| 125 | + MainCommitId string `json:"main-commit-id"` |
| 126 | + MainRemoteName string `json:"main-remote-name"` |
| 127 | + MainBranchName string `json:"main-branch-name"` |
| 128 | + |
| 129 | + Evaluated bool `json:"evaluated"` |
| 130 | + EvalStartedAt time.Time `json:"eval-started-at"` |
| 131 | + EvalEndedAt time.Time `json:"eval-ended-at"` |
| 132 | + EvalErr error `json:"-"` |
| 133 | + OutPath string `json:"outpath"` |
| 134 | + DrvPath string `json:"drvpath"` |
| 135 | + MachineId string `json:"machine-id"` |
| 136 | + |
| 137 | + Built bool `json:"built"` |
| 138 | + BuildStartedAt time.Time `json:"build-started-at"` |
| 139 | + BuildEndedAt time.Time `json:"build-ended-at"` |
| 140 | + BuildErr error `json:"-"` |
| 141 | +} |
| 142 | + |
| 143 | +func (b *Builder) GetGeneration() Generation { |
| 144 | + b.mu.Lock() |
| 145 | + defer b.mu.Unlock() |
| 146 | + return *b.generation |
| 147 | +} |
| 148 | + |
| 149 | +func (b *Builder) GetPreviousGeneration() Generation { |
| 150 | + b.mu.Lock() |
| 151 | + defer b.mu.Unlock() |
| 152 | + return *b.previousGeneration |
| 153 | +} |
| 154 | + |
| 155 | +type State struct { |
| 156 | + IsBuilding bool |
| 157 | + IsEvaluating bool |
| 158 | +} |
| 159 | + |
| 160 | +func (b *Builder) State() State { |
| 161 | + b.mu.Lock() |
| 162 | + defer b.mu.Unlock() |
| 163 | + return State{ |
| 164 | + IsBuilding: b.IsBuilding, |
| 165 | + IsEvaluating: b.IsEvaluating, |
| 166 | + } |
| 167 | +} |
| 168 | + |
| 169 | +func (b *Builder) Stop() { |
| 170 | + b.mu.Lock() |
| 171 | + if b.IsEvaluating { |
| 172 | + b.evalCancelFunc() |
| 173 | + b.mu.Unlock() |
| 174 | + <-b.EvaluationDone |
| 175 | + } else { |
| 176 | + b.mu.Unlock() |
| 177 | + } |
| 178 | + b.mu.Lock() |
| 179 | + if b.IsBuilding { |
| 180 | + b.buildCancelFunc() |
| 181 | + b.mu.Unlock() |
| 182 | + <-b.BuildDone |
| 183 | + } else { |
| 184 | + b.mu.Unlock() |
| 185 | + } |
| 186 | +} |
| 187 | + |
| 188 | +// Eval evaluates a generation. It cancels current any generation |
| 189 | +// evaluation or build. |
| 190 | +func (b *Builder) Eval(flakeUrl string) { |
| 191 | + var ctx context.Context |
| 192 | + // This is to prempt the builder since we don't nede to allow |
| 193 | + // several evaluation in parallel |
| 194 | + b.Stop() |
| 195 | + b.mu.Lock() |
| 196 | + b.IsEvaluating = true |
| 197 | + b.previousGeneration = b.generation |
| 198 | + g := Generation{ |
| 199 | + FlakeUrl: flakeUrl, |
| 200 | + Hostname: b.hostname, |
| 201 | + } |
| 202 | + b.generation = &g |
| 203 | + ctx, b.evalCancelFunc = context.WithCancel(context.Background()) |
| 204 | + b.mu.Unlock() |
| 205 | + |
| 206 | + b.EvaluationDone = make(chan Generation) |
| 207 | + go func() { |
| 208 | + ctx, cancel := context.WithTimeout(ctx, b.evalTimeout) |
| 209 | + defer cancel() |
| 210 | + drvPath, outPath, machineId, err := b.evalFunc(ctx, flakeUrl, b.hostname) |
| 211 | + logrus.Infof("builder: evaluation done with machineId=%d drvPath=%s", machineId, drvPath) |
| 212 | + b.mu.Lock() |
| 213 | + defer b.mu.Unlock() |
| 214 | + b.generation.EvalErr = err |
| 215 | + b.generation.DrvPath = drvPath |
| 216 | + b.generation.OutPath = outPath |
| 217 | + b.generation.MachineId = machineId |
| 218 | + b.generation.Evaluated = true |
| 219 | + b.IsEvaluating = false |
| 220 | + b.EvaluationDone <- *b.generation |
| 221 | + }() |
| 222 | +} |
| 223 | + |
| 224 | +// Build builds a generation. |
| 225 | +func (b *Builder) Build() error { |
| 226 | + var ctx context.Context |
| 227 | + b.mu.Lock() |
| 228 | + defer b.mu.Unlock() |
| 229 | + |
| 230 | + if b.generation == nil || !b.generation.Evaluated { |
| 231 | + return fmt.Errorf("The generation is not evaluated") |
| 232 | + } |
| 233 | + if b.IsBuilding { |
| 234 | + return fmt.Errorf("The builder is already building") |
| 235 | + } |
| 236 | + if b.generation.Built { |
| 237 | + return fmt.Errorf("The generation is already built") |
| 238 | + } |
| 239 | + b.IsBuilding = true |
| 240 | + ctx, b.buildCancelFunc = context.WithCancel(context.Background()) |
| 241 | + |
| 242 | + go func() { |
| 243 | + ctx, cancel := context.WithTimeout(ctx, b.buildTimeout) |
| 244 | + defer cancel() |
| 245 | + err := b.buildFunc(ctx, b.generation.DrvPath) |
| 246 | + b.mu.Lock() |
| 247 | + defer b.mu.Unlock() |
| 248 | + b.generation.BuildErr = err |
| 249 | + if b.generation.BuildErr == nil { |
| 250 | + b.generation.Built = true |
| 251 | + } |
| 252 | + b.IsBuilding = false |
| 253 | + b.BuildDone <- *b.generation |
| 254 | + }() |
| 255 | + return nil |
| 256 | +} |
0 commit comments