Flamenco/cmd/shaman-checkout-id-setter/main.go

178 lines
5.1 KiB
Go
Raw Permalink Normal View History

package main
// SPDX-License-Identifier: GPL-3.0-or-later
import (
"bufio"
"context"
"errors"
"flag"
"io/fs"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/mattn/go-colorable"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"projects.blender.org/studio/flamenco/internal/appinfo"
"projects.blender.org/studio/flamenco/internal/manager/config"
"projects.blender.org/studio/flamenco/internal/manager/persistence"
"projects.blender.org/studio/flamenco/pkg/api"
)
func main() {
output := zerolog.ConsoleWriter{Out: colorable.NewColorableStdout(), TimeFormat: time.RFC3339}
log.Logger = log.Output(output)
log.Info().
Str("version", appinfo.ApplicationVersion).
Str("git", appinfo.ApplicationGitHash).
Str("releaseCycle", appinfo.ReleaseCycle).
Str("os", runtime.GOOS).
Str("arch", runtime.GOARCH).
Msgf("starting %v shaman-checkout-id-setter", appinfo.ApplicationName)
log.Warn().Msg("Use with care, and at your own risk.")
log.Warn().Msg("This is an experimental program, and may ruin your entire Flamenco database.")
log.Warn().Msg("Press Enter to continue.")
_, _ = bufio.NewReader(os.Stdin).ReadBytes('\n')
parseCliArgs()
// Load configuration.
configService := config.NewService()
err := configService.Load()
if err != nil && !errors.Is(err, fs.ErrNotExist) {
log.Error().Err(err).Msg("loading configuration")
}
// Reject working on a brand new installation.
isFirstRun, err := configService.IsFirstRun()
switch {
case err != nil:
log.Fatal().Err(err).Msg("unable to determine whether this is the first run of Flamenco or not")
case isFirstRun:
log.Fatal().Msg("this should be run on an already-used database")
}
// Find the {jobs} variable.
vars := configService.ResolveVariables(config.VariableAudienceWorkers, config.VariablePlatform(runtime.GOOS))
jobsPath := vars["jobs"].Value
if jobsPath == "" {
log.Fatal().Msg("unable to resolve 'jobs' variable")
}
// Connect to the database.
ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer ctxCancel()
persist := openDB(*configService)
defer persist.Close()
// Get all jobs from the database.
jobs, err := persist.QueryJobs(ctx, api.JobsQuery{})
if err != nil {
log.Fatal().Err(err).Msg("unable to fetch jobs")
}
log.Info().Int("numJobs", len(jobs)).Msg("processing all jobs")
numJobsUpdated := 0
for _, job := range jobs {
logger := log.With().Uint("id", job.ID).Str("uuid", job.UUID).Logger()
if job.Storage.ShamanCheckoutID != "" {
logger.Info().Str("checkoutID", job.Storage.ShamanCheckoutID).Msg("job already has a Shaman checkout ID")
continue
}
logger.Trace().Msg("processing job")
// Find the 'blendfile' setting.
blendfile, ok := job.Settings["blendfile"].(string)
if !ok {
logger.Info().Msg("skipping job, it has no `blendfile` setting")
continue
}
// See if it starts with `{jobs}`, otherwise it's not submitted via Shaman.
relpath, found := strings.CutPrefix(blendfile, "{jobs}"+string(os.PathSeparator))
if !found {
logger.Info().Str("blendfile", blendfile).Msg("skipping job, its blendfile setting doesn't start with `{jobs}/`")
continue
}
// See if there is a `pack-info.txt` file next to the blend file. This is
// another indication that we have the right directory.
packInfoPath := filepath.Join(jobsPath, filepath.Dir(relpath), "pack-info.txt")
_, err := os.Stat(packInfoPath)
switch {
case errors.Is(err, os.ErrNotExist):
logger.Warn().Str("packInfo", packInfoPath).Msg("skipping job, pack-info.txt not found where expected")
continue
case err != nil:
logger.Fatal().Str("packInfo", packInfoPath).Msg("error accessing pack-info.txt")
}
// Extract the checkout ID from the blend file path.
checkoutID := filepath.Dir(relpath)
logger = logger.With().Str("checkoutID", checkoutID).Logger()
// Store it on the job.
logger.Debug().Msg("updating job")
job.Storage.ShamanCheckoutID = checkoutID
if err := persist.SaveJobStorageInfo(ctx, job); err != nil {
logger.Error().Err(err).Msg("error saving job to the database")
continue
}
numJobsUpdated++
}
log.Info().Msgf("done, updated %d of %d jobs", numJobsUpdated, len(jobs))
}
func parseCliArgs() {
var quiet, debug, trace bool
flag.BoolVar(&quiet, "quiet", false, "Only log warning-level and worse.")
flag.BoolVar(&debug, "debug", false, "Enable debug-level logging.")
flag.BoolVar(&trace, "trace", false, "Enable trace-level logging.")
flag.Parse()
var logLevel zerolog.Level
switch {
case trace:
logLevel = zerolog.TraceLevel
case debug:
logLevel = zerolog.DebugLevel
case quiet:
logLevel = zerolog.WarnLevel
default:
logLevel = zerolog.InfoLevel
}
zerolog.SetGlobalLevel(logLevel)
}
// openDB opens the database or dies.
func openDB(configService config.Service) *persistence.DB {
dsn := configService.Get().DatabaseDSN
if dsn == "" {
log.Fatal().Msg("configure the database in flamenco-manager.yaml")
}
dbCtx, dbCtxCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer dbCtxCancel()
persist, err := persistence.OpenDB(dbCtx, dsn)
if err != nil {
log.Fatal().
Err(err).
Str("dsn", dsn).
Msg("error opening database")
}
return persist
}