/
pyo
/
updater
Обзор
Документация
Войти
/
pyo
/
updater
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
develop
pkg/builder/builder.go
284 строки
7 KB
ilovepitsa
restore from another repo
16 июн 2025, 18:36
16 июн 2025, 18:36
91b9a34
Код
Авторство
О чём код?
package builder import ( "archive/tar" "archive/zip" "bytes" "context" "encoding/json" "fmt" "io" "os" "path/filepath" "time" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/mount" "github.com/docker/docker/client" "github.com/spf13/afero" "gitverse.ru/pyo/updater/pkg/config" "gitverse.ru/pyo/updater/pkg/models" "go.uber.org/zap" ) type OperationRepo interface { GetNewOperation(ctx context.Context) ([]models.BuildOperation, error) UpdateOperationStatus(ctx context.Context, operationID int, status int) error } type DockerBuilder struct { cfg *config.Config Repo OperationRepo dockerClient *client.Client fs afero.Afero render *Templater Logger *zap.Logger } func NewDockerBuilder( cfg *config.Config, repo OperationRepo, dockerClient *client.Client, logger *zap.Logger, fs afero.Afero, render *Templater, ) *DockerBuilder { return &DockerBuilder{ cfg: cfg, Repo: repo, dockerClient: dockerClient, Logger: logger, render: render, fs: fs, } } func (b *DockerBuilder) Run(ctx context.Context) error { ticker := time.NewTicker(b.cfg.BuildInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return nil case <-ticker.C: } ops, err := b.Repo.GetNewOperation(ctx) if err != nil { b.Logger.Error("Cant get new operations", zap.Error(err)) continue } err = b.BuildAll(ctx, ops) if err != nil { b.Logger.Error("Cant build all", zap.Error(err)) } ticker.Reset(b.cfg.BuildInterval) } } func (b *DockerBuilder) BuildAll(ctx context.Context, operations []models.BuildOperation) error { if len(operations) == 0 { return nil } b.Logger.Error("Build all") for _, op := range operations { b.Repo.UpdateOperationStatus(ctx, op.Id, int(models.BuildStatusProcessing)) err := b.BuildImage(ctx, op) b.Logger.Error("Build end") if err != nil { b.Logger.Error("Cant build image", zap.Error(err)) b.Repo.UpdateOperationStatus(ctx, op.Id, int(models.BuildStatusError)) continue } id, err := b.RunImage(ctx, op) if err != nil { b.Repo.UpdateOperationStatus(ctx, op.Id, int(models.BuildStatusError)) b.Logger.Error("Cant run image", zap.Error(err)) continue } status, err := b.WaitContainer(ctx, id) if err != nil { b.Repo.UpdateOperationStatus(ctx, op.Id, int(models.BuildStatusError)) b.Logger.Error("build ended with error", zap.String("name", op.ComponentName), zap.String("version", op.Version), zap.Error(err)) continue } b.Logger.Error("build ended", zap.String("name", op.ComponentName), zap.String("version", op.Version), zap.Int64("status", status)) b.dockerClient.ContainerRemove(ctx, id, container.RemoveOptions{Force: true}) b.Repo.UpdateOperationStatus(ctx, op.Id, int(models.BuildStatusDone)) err = b.fs.Remove(op.ComponentPath) if err != nil { b.Logger.Error("Cant remove component", zap.Error(err)) } } return nil } func (b *DockerBuilder) WaitContainer(ctx context.Context, containerID string) (int64, error) { statCh, errCh := b.dockerClient.ContainerWait(ctx, containerID, container.WaitConditionNotRunning) select { case err := <-errCh: return -1, fmt.Errorf("error waiting for container: %w", err) case result := <-statCh: b.Logger.Error("container ended", zap.String("containerID", containerID), zap.Int64("status", result.StatusCode)) if result.Error != nil { return -1, fmt.Errorf("container error: %v", result.Error.Message) } return result.StatusCode, nil } } func (b *DockerBuilder) BuildImage(ctx context.Context, operation models.BuildOperation) error { buf := bytes.NewBuffer(nil) tw := tar.NewWriter(buf) err := b.SetupDockerFile(ctx, tw, operation) if err != nil { return err } tw.Close() resp, err := b.dockerClient.ImageBuild( ctx, buf, types.ImageBuildOptions{ Tags: []string{operation.ComponentName + "-" + operation.Version}, }) if err != nil { return err } buf1 := bytes.NewBuffer(nil) io.Copy(buf1, resp.Body) resp.Body.Close() return HandleDockerError(buf1) } func HandleDockerError(ans *bytes.Buffer) error { decoder := json.NewDecoder(ans) var buildError error fmt.Println(ans.String()) for { var message struct { Stream string `json:"stream"` Error string `json:"error"` ErrorDetail struct { Message string `json:"message"` } `json:"errorDetail"` Aux struct { ID string `json:"ID"` } `json:"aux"` } if err := decoder.Decode(&message); err != nil { if err == io.EOF { break } return fmt.Errorf("failed to parse build output: %w", err) } // Check for error messages if message.Error != "" { buildError = fmt.Errorf("build failed: %s", message.Error) } else if message.ErrorDetail.Message != "" { buildError = fmt.Errorf("build failed: %s", message.ErrorDetail.Message) } // Optional: Print progress messages if message.Stream != "" { fmt.Print(message.Stream) } } if buildError != nil { return buildError } return nil } func (b *DockerBuilder) RunImage(ctx context.Context, operation models.BuildOperation) (string, error) { err := b.fs.MkdirAll(filepath.Join(b.cfg.BuildedDir, operation.ComponentName, operation.Version), 0755) if err != nil { return "", err } err = b.fs.Fs.MkdirAll(filepath.Join(b.cfg.BuildedDir, operation.ComponentName, operation.Version), os.FileMode(b.cfg.FileMode)) if err != nil { return "", err } resp, err := b.dockerClient.ContainerCreate(ctx, &container.Config{ Image: operation.ComponentName + "-" + operation.Version, }, &container.HostConfig{ Mounts: []mount.Mount{ { Type: mount.TypeVolume, Source: "updater_updater_data", Target: filepath.Join("/builded"), Consistency: mount.ConsistencyFull, }, }, }, nil, nil, "builder"+operation.ComponentName+operation.Version) if err != nil { return "", err } return resp.ID, b.dockerClient.ContainerStart(ctx, resp.ID, container.StartOptions{}) } func (b *DockerBuilder) SetupDockerFile(ctx context.Context, dst *tar.Writer, operation models.BuildOperation) error { arch, err := zip.OpenReader(operation.ComponentPath) if err != nil { return err } defer arch.Close() for _, file := range arch.File { if file.FileInfo().IsDir() { continue } zipf, err := file.Open() if err != nil { return err } err = func() error { defer zipf.Close() header := tar.Header{ Name: file.Name, Mode: 0755, Size: file.FileInfo().Size(), } err = dst.WriteHeader(&header) if err != nil { return err } if _, err := io.Copy(dst, zipf); err != nil { return err } return nil }() if err != nil { return err } } rendered := bytes.NewBuffer(nil) err = b.render.Execute(rendered, b.cfg.TmplName, models.DockerFileArgs{ Name: operation.ComponentName, ComponentPath: operation.ComponentPath, Version: operation.Version, BaseImage: b.cfg.BaseImage, PackageManager: b.cfg.PackageManager, DevDepend: operation.ComponentDevDepend, Tools: operation.ComponentBuildOptions.Tools, CrossCompileTools: operation.ComponentBuildOptions.CrossCompileTools, Platforms: operation.ComponentBuildOptions.Platforms, }) if err != nil { return err } dst.WriteHeader(&tar.Header{ Name: "Dockerfile", Mode: 0755, Size: int64(rendered.Len()), }) _, err = dst.Write(rendered.Bytes()) fmt.Println(rendered.String()) return err }