wip
This commit is contained in:
parent
4be9a5a982
commit
bb446505a8
9 changed files with 526 additions and 2 deletions
|
@ -108,9 +108,119 @@ type Cmd struct {
|
||||||
3. Воркеры начинают выполнять вершины графа, пересылая друг другу выходные директории джобов.
|
3. Воркеры начинают выполнять вершины графа, пересылая друг другу выходные директории джобов.
|
||||||
4. Результаты работы джобов скачиваются на клиента.
|
4. Результаты работы джобов скачиваются на клиента.
|
||||||
|
|
||||||
## Протокол: Клиент <-> Координатор
|
## Протоколы
|
||||||
|
|
||||||
## Протокол: Координатор <-> Воркер
|
Общение между компонентами будет происходить поверх HTTP и json. В реальной системе мы бы
|
||||||
|
взяли более продвинутый протокол и более эффективный формат сериализации, но в этой учебной
|
||||||
|
задаче нам важнее уменьшить сложность системы.
|
||||||
|
|
||||||
|
### Протокол: Клиент <-> Координатор
|
||||||
|
|
||||||
|
При общении клиента и кординатора, клиент всегда выступает инициатором запроса.
|
||||||
|
|
||||||
|
* `POST /build` - стартует новый билд. Клиент посылает в Body запроса json c описанием сборки. Сервер
|
||||||
|
стримит в body ответа json сообщения описывающие прогресс сборки (тут правильнее было бы использовать
|
||||||
|
websocket, но нас устраивает более простое решение).
|
||||||
|
|
||||||
|
* `POST /source/{sha1}` - загружает файл с исходным кодом на координатор. Клиент посылает
|
||||||
|
содержимое файла в body запроса. `{sha1}` - равен `{sha1}` хешу от содержимого файла.
|
||||||
|
|
||||||
|
### Протокол: Координатор <-> Воркер
|
||||||
|
|
||||||
|
При общении воркера и координатора, воркер всегда выступает инициатором запроса.
|
||||||
|
|
||||||
|
* `GET /source/{sha1}` - скачивает файл с исходным кодом с координатора.
|
||||||
|
|
||||||
|
* `POST /heartbeat` - синхронизирует состояние воркера и координатора. Воркер посылает
|
||||||
|
в теле запроса json описывающий изменение в своём состоянии. Коордитора отвечает json-ом со
|
||||||
|
списком задач, которые должен выполнить воркер.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package proto
|
||||||
|
|
||||||
|
// CompleteJob описывает результат работы джоба.
|
||||||
|
type CompletedJob struct {
|
||||||
|
ID graph.ID
|
||||||
|
|
||||||
|
Stdout, Stderr []byte
|
||||||
|
|
||||||
|
// Error описывает сообщение об ошибке, из-за которого джоб не удалось выполнить.
|
||||||
|
//
|
||||||
|
// Если Error == nil, значит джоб завершился успешно.
|
||||||
|
Error *string
|
||||||
|
}
|
||||||
|
|
||||||
|
type HeartbeatRequest struct {
|
||||||
|
// WorkerID задаёт персистентный идентификатор данного воркера.
|
||||||
|
//
|
||||||
|
// WorkerID так же выступает в качестве endpoint-а, к которому можно подключиться по HTTP.
|
||||||
|
//
|
||||||
|
// В наших тестов, идентификатор будет иметь вид "localhost:%d".
|
||||||
|
WorkerID string
|
||||||
|
|
||||||
|
// ProcessID задаёт эфемерный идентификатор текущего процесса воркера.
|
||||||
|
//
|
||||||
|
// Координатор запоминает ProcessID для каждого воркера.
|
||||||
|
//
|
||||||
|
// Измение ProcessID значит, что воркер перезапустился.
|
||||||
|
ProcessID string
|
||||||
|
|
||||||
|
// RunningJobs перечисляет список джобов, которые выполняются на этом воркере
|
||||||
|
// в данный момент.
|
||||||
|
RunningJobs []graph.ID
|
||||||
|
|
||||||
|
DownloadingSources []graph.ID
|
||||||
|
|
||||||
|
DownloadingArtifacts []graph.ID
|
||||||
|
|
||||||
|
// FreeSlots сообщаяет, сколько еще процессов можно запустить на этом воркере.
|
||||||
|
FreeSlots int
|
||||||
|
|
||||||
|
// CompletedJobs сообщает координатору, какие джобы завершили исполнение на этом воркере
|
||||||
|
// на этой итерации цикла.
|
||||||
|
CompletedJobs []CompletedJob
|
||||||
|
|
||||||
|
// AddedArtifacts говорит, какие артефакты появились в кеше на этой итерации цикла.
|
||||||
|
AddedArtifacts []graph.ID
|
||||||
|
|
||||||
|
// AddedSourceFiles говорит, какие файлы появились в кеше на этой итерации цикла.
|
||||||
|
AddedSourceFiles []graph.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// JobSpec описывает джоб, который нужно запустить.
|
||||||
|
type JobSpec struct {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtifactSpec описывает артефакт, который нужно скачать с другого воркера.
|
||||||
|
type ArtifactSpec struct {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// SourceFileSpec описывает файл с исходным кодом, который нужно скачать с координатора.
|
||||||
|
type SourceFileSpec struct {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type HeartbeatResponse struct {
|
||||||
|
JobsToRun map[graph.ID]JobSpec
|
||||||
|
|
||||||
|
ArtifactsToDownload map[graph.ID]ArtifactSpec
|
||||||
|
|
||||||
|
ArtifactsToRemove []graph.ID
|
||||||
|
|
||||||
|
SourceFilesToDownload map[graph.ID]SourceFileSpec
|
||||||
|
|
||||||
|
SourceFilesToRemove []graph.ID
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Протокол: Воркер <-> Воркер
|
||||||
|
|
||||||
|
Общение между воркерам происходит тогда, когда системе нужно передать артефакты сборки с
|
||||||
|
одного воркера на другой.
|
||||||
|
|
||||||
|
* `GET /artifact/{sha1}` - возвращает директорию с выходными данными джоба в формате `tar`.
|
||||||
|
|
||||||
## Кеширование
|
## Кеширование
|
||||||
|
|
||||||
|
|
40
distbuild/pkg/artifact/cache.go
Normal file
40
distbuild/pkg/artifact/cache.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package artifact
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gitlab.com/slon/shad-go/distbuild/pkg/build"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotFound = errors.New("file not found")
|
||||||
|
ErrWriteLocked = errors.New("file is locked for write")
|
||||||
|
ErrReadLocked = errors.New("file is locked for read")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cache struct{}
|
||||||
|
|
||||||
|
func NewCache(root string) (*Cache, error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Range(artifactFn func(file build.ID) error) error {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Remove(artifact build.ID) error {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Create(artifact build.ID) (path string, abort, commit func(), err error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Get(file build.ID) (path string, unlock func(), err error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(c *Cache) http.Handler {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
74
distbuild/pkg/build/graph.go
Normal file
74
distbuild/pkg/build/graph.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package build
|
||||||
|
|
||||||
|
import "crypto/sha1"
|
||||||
|
|
||||||
|
type ID [sha1.Size]byte
|
||||||
|
|
||||||
|
// Job описывает одну вершину графа сборки.
|
||||||
|
type Job struct {
|
||||||
|
// ID задаёт уникальный идентификатор джоба.
|
||||||
|
//
|
||||||
|
// ID вычисляется как хеш от всех входных файлов, команд запуска и хешей зависимых джобов.
|
||||||
|
//
|
||||||
|
// Выход джоба целиком опеределяется его ID. Это важное свойство позволяет кешировать
|
||||||
|
// результаты сборки.
|
||||||
|
ID ID
|
||||||
|
|
||||||
|
// Name задаёт человекочитаемое имя джоба.
|
||||||
|
//
|
||||||
|
// Например:
|
||||||
|
// build gitlab.com/slon/disbuild/pkg/b
|
||||||
|
// vet gitlab.com/slon/disbuild/pkg/a
|
||||||
|
// test gitlab.com/slon/disbuild/pkg/test
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Inputs задаёт список файлов из директории с исходным кодом,
|
||||||
|
// которые нужны для работы этого джоба.
|
||||||
|
//
|
||||||
|
// В типичном случае, тут будут перечислены все .go файлы одного пакета.
|
||||||
|
Inputs []string
|
||||||
|
|
||||||
|
// Deps задаёт список джобов, выходы которых нужны для работы этого джоба.
|
||||||
|
Deps []ID
|
||||||
|
|
||||||
|
// Cmds описывает список команд, которые нужно выполнить в рамках этого джоба.
|
||||||
|
Cmds []Cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cmd описывает одну команду сборки.
|
||||||
|
//
|
||||||
|
// Есть несколько видов команд. Все виды команд описываются одной структурой.
|
||||||
|
// Реальный тип определяется тем, какие поля структуры заполнены.
|
||||||
|
//
|
||||||
|
// exec - выполняет произвольную команду
|
||||||
|
// cat - записывает строку в файл
|
||||||
|
//
|
||||||
|
// Все строки в описании команды могут содержать в себе на переменные. Перед выполнением
|
||||||
|
// реальной команды, переменные заменяются на их реальные значения.
|
||||||
|
//
|
||||||
|
// {{OUTPUT_DIR}} - абсолютный путь до выходной директории джоба.
|
||||||
|
// {{SOURCE_DIR}} - абсолютный путь до директории с исходными файлами.
|
||||||
|
// {{DEP:f374b81d81f641c8c3d5d5468081ef83b2c7dae9}} - абсолютный путь до директории,
|
||||||
|
// содержащей выход джоба с id f374b81d81f641c8c3d5d5468081ef83b2c7dae9.
|
||||||
|
type Cmd struct {
|
||||||
|
// Exec описывает команду, которую нужно выполнить.
|
||||||
|
Exec []string
|
||||||
|
|
||||||
|
// Environ описывает переменные окружения, которые необходимы для работы команды из Exec.
|
||||||
|
Environ []string
|
||||||
|
|
||||||
|
// WorkingDirectory задаёт рабочую директорию для команды из Exec.
|
||||||
|
WorkingDirectory string
|
||||||
|
|
||||||
|
// CatTemplate задаёт шаблон строки, которую нужно записать в файл.
|
||||||
|
CatTemplate string
|
||||||
|
|
||||||
|
// CatOutput задаёт выходной файл для команды типа cat.
|
||||||
|
CatOutput string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Graph struct {
|
||||||
|
SourceFiles map[ID]string
|
||||||
|
|
||||||
|
Jobs []Job
|
||||||
|
}
|
107
distbuild/pkg/client/build.go
Normal file
107
distbuild/pkg/client/build.go
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gitlab.com/slon/shad-go/distbuild/pkg/build"
|
||||||
|
"gitlab.com/slon/shad-go/distbuild/pkg/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
CoordinatorEndpoint string
|
||||||
|
|
||||||
|
SourceDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
type BuildListener interface {
|
||||||
|
OnJobStdout(jobID build.ID, stdout []byte) error
|
||||||
|
OnJobStderr(jobID build.ID, stdout []byte) error
|
||||||
|
|
||||||
|
OnJobFinished(jobID build.ID) error
|
||||||
|
OnJobFailed(jobID build.ID, code int, error string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) uploadSources(ctx context.Context, src proto.MissingSources) error {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Build(ctx context.Context, graph build.Graph, lsn BuildListener) error {
|
||||||
|
graphJS, err := json.Marshal(graph)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", c.CoordinatorEndpoint+"/build", bytes.NewBuffer(graphJS))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
|
rsp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("build failed: %w", err)
|
||||||
|
}
|
||||||
|
defer rsp.Body.Close()
|
||||||
|
|
||||||
|
if rsp.StatusCode != 200 {
|
||||||
|
errorMsg, _ := ioutil.ReadAll(rsp.Body)
|
||||||
|
return fmt.Errorf("build failed: %s", errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := json.NewDecoder(rsp.Body)
|
||||||
|
|
||||||
|
var missing proto.MissingSources
|
||||||
|
if err := d.Decode(&missing); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.uploadSources(ctx, missing); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
var update proto.StatusUpdate
|
||||||
|
if err := d.Decode(&update); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case update.BuildFailed != nil:
|
||||||
|
return fmt.Errorf("build failed: %s", update.BuildFailed.Error)
|
||||||
|
|
||||||
|
case update.JobFinished != nil:
|
||||||
|
jf := update.JobFinished
|
||||||
|
|
||||||
|
if jf.Stdout != nil {
|
||||||
|
if err := lsn.OnJobStdout(jf.ID, jf.Stdout); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if jf.Stderr != nil {
|
||||||
|
if err := lsn.OnJobStderr(jf.ID, jf.Stderr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if jf.Error != nil {
|
||||||
|
if err := lsn.OnJobFailed(jf.ID, jf.ExitCode, *jf.Error); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := lsn.OnJobFinished(jf.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("build failed: unexpected status update")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
distbuild/pkg/dist/build.go
vendored
Normal file
4
distbuild/pkg/dist/build.go
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
package dist
|
||||||
|
|
||||||
|
type Build struct {
|
||||||
|
}
|
42
distbuild/pkg/filecache/filecache.go
Normal file
42
distbuild/pkg/filecache/filecache.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package filecache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gitlab.com/slon/shad-go/distbuild/pkg/build"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotFound = errors.New("file not found")
|
||||||
|
ErrWriteLocked = errors.New("file is locked for write")
|
||||||
|
ErrReadLocked = errors.New("file is locked for read")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cache struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(rootDir string) (*Cache, error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Range(fileFn func(file build.ID) error) error {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Remove(file build.ID) error {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Write(file build.ID) (w io.WriteCloser, abort func(), err error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Get(file build.ID) (path string, unlock func(), err error) {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(c *Cache) http.Handler {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
18
distbuild/pkg/proto/build.go
Normal file
18
distbuild/pkg/proto/build.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.com/slon/shad-go/distbuild/pkg/build"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MissingSources struct {
|
||||||
|
MissingFiles []build.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusUpdate struct {
|
||||||
|
JobFinished *FinishedJob
|
||||||
|
BuildFailed *BuildFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
type BuildFailed struct {
|
||||||
|
Error string
|
||||||
|
}
|
83
distbuild/pkg/proto/heartbeat.go
Normal file
83
distbuild/pkg/proto/heartbeat.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.com/slon/shad-go/distbuild/pkg/build"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CompleteJob описывает результат работы джоба.
|
||||||
|
type FinishedJob struct {
|
||||||
|
ID build.ID
|
||||||
|
|
||||||
|
Stdout, Stderr []byte
|
||||||
|
|
||||||
|
ExitCode int
|
||||||
|
|
||||||
|
// Error описывает сообщение об ошибке, из-за которого джоб не удалось выполнить.
|
||||||
|
//
|
||||||
|
// Если Error == nil, значит джоб завершился успешно.
|
||||||
|
Error *string
|
||||||
|
}
|
||||||
|
|
||||||
|
type HeartbeatRequest struct {
|
||||||
|
// WorkerID задаёт персистентный идентификатор данного воркера.
|
||||||
|
//
|
||||||
|
// WorkerID так же выступает в качестве endpoint-а, к которому можно подключиться по HTTP.
|
||||||
|
//
|
||||||
|
// В наших тестов, идентификатор будет иметь вид "localhost:%d".
|
||||||
|
WorkerID string
|
||||||
|
|
||||||
|
// ProcessID задаёт эфемерный идентификатор текущего процесса воркера.
|
||||||
|
//
|
||||||
|
// Координатор запоминает ProcessID для каждого воркера.
|
||||||
|
//
|
||||||
|
// Измение ProcessID значит, что воркер перезапустился.
|
||||||
|
ProcessID string
|
||||||
|
|
||||||
|
// RunningJobs перечисляет список джобов, которые выполняются на этом воркере
|
||||||
|
// в данный момент.
|
||||||
|
RunningJobs []build.ID
|
||||||
|
|
||||||
|
DownloadingSources []build.ID
|
||||||
|
|
||||||
|
DownloadingArtifacts []build.ID
|
||||||
|
|
||||||
|
// FreeSlots сообщаяет, сколько еще процессов можно запустить на этом воркере.
|
||||||
|
FreeSlots int
|
||||||
|
|
||||||
|
// FinishedJob сообщает координатору, какие джобы завершили исполнение на этом воркере
|
||||||
|
// на этой итерации цикла.
|
||||||
|
FinishedJob []FinishedJob
|
||||||
|
|
||||||
|
// AddedArtifacts говорит, какие артефакты появились в кеше на этой итерации цикла.
|
||||||
|
AddedArtifacts []build.ID
|
||||||
|
|
||||||
|
// AddedSourceFiles говорит, какие файлы появились в кеше на этой итерации цикла.
|
||||||
|
AddedSourceFiles []build.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// JobSpec описывает джоб, который нужно запустить.
|
||||||
|
type JobSpec struct {
|
||||||
|
SourceFiles map[build.ID]string
|
||||||
|
|
||||||
|
Job build.Job
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtifactSpec описывает артефакт, который нужно скачать с другого воркера.
|
||||||
|
type ArtifactSpec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// SourceFileSpec описывает файл с исходным кодом, который нужно скачать с координатора.
|
||||||
|
type SourceFileSpec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type HeartbeatResponse struct {
|
||||||
|
JobsToRun map[build.ID]JobSpec
|
||||||
|
|
||||||
|
ArtifactsToDownload map[build.ID]ArtifactSpec
|
||||||
|
|
||||||
|
ArtifactsToRemove []build.ID
|
||||||
|
|
||||||
|
SourceFilesToDownload map[build.ID]SourceFileSpec
|
||||||
|
|
||||||
|
SourceFilesToRemove []build.ID
|
||||||
|
}
|
46
distbuild/pkg/worker/worker.go
Normal file
46
distbuild/pkg/worker/worker.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package worker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"gitlab.com/slon/shad-go/distbuild/pkg/artifact"
|
||||||
|
"gitlab.com/slon/shad-go/distbuild/pkg/build"
|
||||||
|
"gitlab.com/slon/shad-go/distbuild/pkg/filecache"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Worker struct {
|
||||||
|
CoordinatorEndpoint string
|
||||||
|
|
||||||
|
SourceFiles *filecache.Cache
|
||||||
|
Artifacts *artifact.Cache
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
newArtifacts []build.ID
|
||||||
|
newSources []build.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Worker) recover() error {
|
||||||
|
err := w.SourceFiles.Range(func(file build.ID) error {
|
||||||
|
w.newSources = append(w.newSources, file)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.Artifacts.Range(func(file build.ID) error {
|
||||||
|
w.newArtifacts = append(w.newArtifacts, file)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Worker) Run(ctx context.Context) error {
|
||||||
|
if err := w.recover(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue