Manager: More work on Shaman support
This introduces some more conceptual changes to Shaman. The most important one is that there is no longer a "checkout ID", but a "checkout path". The Shaman client can request any subpath of the checkout directory, so that it can handle things like project- or scene-specific prefixes.
This commit is contained in:
parent
2b0d154a07
commit
b2288e7f28
@ -76,3 +76,4 @@ Note that list is **not** in any specific order.
|
||||
|
||||
- [ ] Notification system to push "job done" messages to. Ideally would be in a form/shape that allows sending a message to Rocket.Chat, Matrix, Telegram, Discord, email, webbrowser, push URL-encoded/JSON/XML to some URL, stuff like that. Idea by Dan McLaughlin.
|
||||
- [ ] Notification client inside Blender itself, so that you get a message when your job is done.
|
||||
- [ ] Separate the OpenAPI definition of Shaman from the rest of Flamenco Manager. That way a part of BAT can also use the code generator. It also is the first step towards running Shaman as a standalone service.
|
||||
|
@ -34,6 +34,7 @@ import (
|
||||
"git.blender.org/flamenco/internal/own_url"
|
||||
"git.blender.org/flamenco/internal/upnp_ssdp"
|
||||
"git.blender.org/flamenco/pkg/api"
|
||||
"git.blender.org/flamenco/pkg/shaman"
|
||||
)
|
||||
|
||||
var cliArgs struct {
|
||||
@ -137,7 +138,8 @@ func buildFlamencoAPI(configService *config.Service, persist *persistence.DB) ap
|
||||
}
|
||||
logStorage := task_logs.NewStorage(configService.Get().TaskLogsPath)
|
||||
taskStateMachine := task_state_machine.NewStateMachine(persist)
|
||||
flamenco := api_impl.NewFlamenco(compiler, persist, logStorage, configService, taskStateMachine)
|
||||
shamanServer := shaman.NewServer(configService.Get().Shaman, nil)
|
||||
flamenco := api_impl.NewFlamenco(compiler, persist, logStorage, configService, taskStateMachine, shamanServer)
|
||||
return flamenco
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"git.blender.org/flamenco/internal/manager/persistence"
|
||||
"git.blender.org/flamenco/internal/manager/task_state_machine"
|
||||
"git.blender.org/flamenco/pkg/api"
|
||||
"git.blender.org/flamenco/pkg/shaman"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
@ -80,16 +81,19 @@ type ConfigService interface {
|
||||
}
|
||||
|
||||
type Shaman interface {
|
||||
// IsEnabled returns whether this Shaman service is enabled or not.
|
||||
IsEnabled() bool
|
||||
|
||||
// Checkout creates a directory, and symlinks the required files into it. The
|
||||
// files must all have been uploaded to Shaman before calling this.
|
||||
Checkout(ctx context.Context, checkoutID string, checkout api.ShamanCheckout) error
|
||||
Checkout(ctx context.Context, checkout api.ShamanCheckout) error
|
||||
|
||||
// Requirements checks a Shaman Requirements file, and returns the subset
|
||||
// containing the unknown files.
|
||||
Requirements(ctx context.Context, requirements api.ShamanRequirementsRequest) (api.ShamanRequirementsResponse, error)
|
||||
|
||||
// Check the status of a file on the Shaman server.
|
||||
FileStoreCheck(ctx context.Context, checksum string, filesize int64) (api.ShamanFileStatus, error)
|
||||
FileStoreCheck(ctx context.Context, checksum string, filesize int64) api.ShamanFileStatus
|
||||
|
||||
// Store a new file on the Shaman server. Note that the Shaman server can
|
||||
// return early when another client finishes uploading the exact same file, to
|
||||
@ -97,6 +101,8 @@ type Shaman interface {
|
||||
FileStore(ctx context.Context, file io.ReadCloser, checksum string, filesize int64, canDefer bool, originalFilename string) error
|
||||
}
|
||||
|
||||
var _ Shaman = (*shaman.Server)(nil)
|
||||
|
||||
// NewFlamenco creates a new Flamenco service.
|
||||
func NewFlamenco(
|
||||
jc JobCompiler,
|
||||
@ -104,6 +110,7 @@ func NewFlamenco(
|
||||
ls LogStorage,
|
||||
cs ConfigService,
|
||||
sm TaskStateMachine,
|
||||
sha Shaman,
|
||||
) *Flamenco {
|
||||
return &Flamenco{
|
||||
jobCompiler: jc,
|
||||
@ -111,6 +118,7 @@ func NewFlamenco(
|
||||
logStorage: ls,
|
||||
config: cs,
|
||||
stateMachine: sm,
|
||||
shaman: sha,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -412,17 +412,17 @@ func (m *MockShaman) EXPECT() *MockShamanMockRecorder {
|
||||
}
|
||||
|
||||
// Checkout mocks base method.
|
||||
func (m *MockShaman) Checkout(arg0 context.Context, arg1 string, arg2 api.ShamanCheckout) error {
|
||||
func (m *MockShaman) Checkout(arg0 context.Context, arg1 api.ShamanCheckout) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Checkout", arg0, arg1, arg2)
|
||||
ret := m.ctrl.Call(m, "Checkout", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Checkout indicates an expected call of Checkout.
|
||||
func (mr *MockShamanMockRecorder) Checkout(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
func (mr *MockShamanMockRecorder) Checkout(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Checkout", reflect.TypeOf((*MockShaman)(nil).Checkout), arg0, arg1, arg2)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Checkout", reflect.TypeOf((*MockShaman)(nil).Checkout), arg0, arg1)
|
||||
}
|
||||
|
||||
// FileStore mocks base method.
|
||||
@ -440,12 +440,11 @@ func (mr *MockShamanMockRecorder) FileStore(arg0, arg1, arg2, arg3, arg4, arg5 i
|
||||
}
|
||||
|
||||
// FileStoreCheck mocks base method.
|
||||
func (m *MockShaman) FileStoreCheck(arg0 context.Context, arg1 string, arg2 int64) (api.ShamanFileStatus, error) {
|
||||
func (m *MockShaman) FileStoreCheck(arg0 context.Context, arg1 string, arg2 int64) api.ShamanFileStatus {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FileStoreCheck", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(api.ShamanFileStatus)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
return ret0
|
||||
}
|
||||
|
||||
// FileStoreCheck indicates an expected call of FileStoreCheck.
|
||||
@ -454,6 +453,20 @@ func (mr *MockShamanMockRecorder) FileStoreCheck(arg0, arg1, arg2 interface{}) *
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FileStoreCheck", reflect.TypeOf((*MockShaman)(nil).FileStoreCheck), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// IsEnabled mocks base method.
|
||||
func (m *MockShaman) IsEnabled() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IsEnabled")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// IsEnabled indicates an expected call of IsEnabled.
|
||||
func (mr *MockShamanMockRecorder) IsEnabled() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsEnabled", reflect.TypeOf((*MockShaman)(nil).IsEnabled))
|
||||
}
|
||||
|
||||
// Requirements mocks base method.
|
||||
func (m *MockShaman) Requirements(arg0 context.Context, arg1 api.ShamanRequirementsRequest) (api.ShamanRequirementsResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -11,12 +11,18 @@ import (
|
||||
"git.blender.org/flamenco/pkg/shaman/fileserver"
|
||||
)
|
||||
|
||||
func (f *Flamenco) isShamanEnabled() bool {
|
||||
return f.shaman.IsEnabled()
|
||||
}
|
||||
|
||||
// Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.
|
||||
// (POST /shaman/checkout/create/{checkoutID})
|
||||
func (f *Flamenco) ShamanCheckout(e echo.Context, checkoutID string) error {
|
||||
logger := requestLogger(e).With().
|
||||
Str("checkoutID", checkoutID).
|
||||
Logger()
|
||||
func (f *Flamenco) ShamanCheckout(e echo.Context) error {
|
||||
logger := requestLogger(e)
|
||||
if !f.isShamanEnabled() {
|
||||
logger.Error().Msg("shaman server not active, unable to serve request")
|
||||
return sendAPIError(e, http.StatusServiceUnavailable, "shaman server not active")
|
||||
}
|
||||
|
||||
var reqBody api.ShamanCheckoutJSONBody
|
||||
err := e.Bind(&reqBody)
|
||||
@ -25,7 +31,7 @@ func (f *Flamenco) ShamanCheckout(e echo.Context, checkoutID string) error {
|
||||
return sendAPIError(e, http.StatusBadRequest, "invalid format")
|
||||
}
|
||||
|
||||
err = f.shaman.Checkout(e.Request().Context(), checkoutID, api.ShamanCheckout(reqBody))
|
||||
err = f.shaman.Checkout(e.Request().Context(), api.ShamanCheckout(reqBody))
|
||||
if err != nil {
|
||||
// TODO: return 409 when checkout already exists.
|
||||
logger.Warn().Err(err).Msg("Shaman: creating checkout")
|
||||
@ -39,6 +45,10 @@ func (f *Flamenco) ShamanCheckout(e echo.Context, checkoutID string) error {
|
||||
// (POST /shaman/checkout/requirements)
|
||||
func (f *Flamenco) ShamanCheckoutRequirements(e echo.Context) error {
|
||||
logger := requestLogger(e)
|
||||
if !f.isShamanEnabled() {
|
||||
logger.Error().Msg("shaman server not active, unable to serve request")
|
||||
return sendAPIError(e, http.StatusServiceUnavailable, "shaman server not active")
|
||||
}
|
||||
|
||||
var reqBody api.ShamanCheckoutRequirementsJSONBody
|
||||
err := e.Bind(&reqBody)
|
||||
@ -62,13 +72,14 @@ func (f *Flamenco) ShamanFileStoreCheck(e echo.Context, checksum string, filesiz
|
||||
logger := requestLogger(e).With().
|
||||
Str("checksum", checksum).Int("filesize", filesize).
|
||||
Logger()
|
||||
|
||||
status, err := f.shaman.FileStoreCheck(e.Request().Context(), checksum, int64(filesize))
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("Shaman: checking stored file")
|
||||
return sendAPIError(e, http.StatusInternalServerError, "unexpected error: %v", err)
|
||||
if !f.isShamanEnabled() {
|
||||
logger.Error().Msg("shaman server not active, unable to serve request")
|
||||
return sendAPIError(e, http.StatusServiceUnavailable, "shaman server not active")
|
||||
}
|
||||
|
||||
logger.Debug().Msg("shaman: checking file")
|
||||
status := f.shaman.FileStoreCheck(e.Request().Context(), checksum, int64(filesize))
|
||||
|
||||
// TODO: actually switch over the actual statuses, see the TODO in the Shaman interface.
|
||||
switch status {
|
||||
case api.ShamanFileStatusStored:
|
||||
@ -94,6 +105,13 @@ func (f *Flamenco) ShamanFileStore(e echo.Context, checksum string, filesize int
|
||||
|
||||
logCtx := requestLogger(e).With().
|
||||
Str("checksum", checksum).Int("filesize", filesize)
|
||||
|
||||
if !f.isShamanEnabled() {
|
||||
logger := logCtx.Logger()
|
||||
logger.Error().Msg("shaman server not active, unable to serve request")
|
||||
return sendAPIError(e, http.StatusServiceUnavailable, "shaman server not active")
|
||||
}
|
||||
|
||||
if params.XShamanCanDeferUpload != nil {
|
||||
canDefer = *params.XShamanCanDeferUpload
|
||||
logCtx = logCtx.Bool("canDefer", canDefer)
|
||||
|
@ -25,6 +25,7 @@ type mockedFlamenco struct {
|
||||
logStorage *mocks.MockLogStorage
|
||||
config *mocks.MockConfigService
|
||||
stateMachine *mocks.MockTaskStateMachine
|
||||
shaman *mocks.MockShaman
|
||||
}
|
||||
|
||||
func newMockedFlamenco(mockCtrl *gomock.Controller) mockedFlamenco {
|
||||
@ -33,7 +34,8 @@ func newMockedFlamenco(mockCtrl *gomock.Controller) mockedFlamenco {
|
||||
ls := mocks.NewMockLogStorage(mockCtrl)
|
||||
cs := mocks.NewMockConfigService(mockCtrl)
|
||||
sm := mocks.NewMockTaskStateMachine(mockCtrl)
|
||||
f := NewFlamenco(jc, ps, ls, cs, sm)
|
||||
sha := mocks.NewMockShaman(mockCtrl)
|
||||
f := NewFlamenco(jc, ps, ls, cs, sm, sha)
|
||||
|
||||
return mockedFlamenco{
|
||||
flamenco: f,
|
||||
|
@ -1,5 +1,11 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
shaman_config "git.blender.org/flamenco/pkg/shaman/config"
|
||||
)
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// The default configuration, use DefaultConfig() to obtain a copy.
|
||||
@ -14,6 +20,17 @@ var defaultConfig = Conf{
|
||||
TaskLogsPath: "./task-logs",
|
||||
SSDPDiscovery: true,
|
||||
|
||||
Shaman: shaman_config.Config{
|
||||
Enabled: true,
|
||||
FileStorePath: "./shaman-file-storage/file-store",
|
||||
CheckoutPath: "./shaman-file-storage/checkout",
|
||||
GarbageCollect: shaman_config.GarbageCollect{
|
||||
Period: 24 * time.Hour,
|
||||
MaxAge: 31 * 24 * time.Hour,
|
||||
ExtraCheckoutDirs: []string{},
|
||||
},
|
||||
},
|
||||
|
||||
// ActiveTaskTimeoutInterval: 10 * time.Minute,
|
||||
// ActiveWorkerTimeoutInterval: 1 * time.Minute,
|
||||
|
||||
@ -33,16 +50,6 @@ var defaultConfig = Conf{
|
||||
// },
|
||||
// },
|
||||
|
||||
// Shaman: ShamanConfig{
|
||||
// Enabled: true,
|
||||
// FileStorePath: defaultShamanFilestorePath,
|
||||
// GarbageCollect: ShamanGarbageCollect{
|
||||
// Period: 24 * time.Hour,
|
||||
// MaxAge: 31 * 24 * time.Hour,
|
||||
// ExtraCheckoutDirs: []string{},
|
||||
// },
|
||||
// },
|
||||
|
||||
// JWT: jwtauth.Config{
|
||||
// DownloadKeysInterval: 1 * time.Hour,
|
||||
// },
|
||||
|
@ -12,10 +12,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.blender.org/flamenco/internal/appinfo"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"git.blender.org/flamenco/internal/appinfo"
|
||||
shaman_config "git.blender.org/flamenco/pkg/shaman/config"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -25,9 +27,6 @@ const (
|
||||
|
||||
// // relative to the Flamenco Server Base URL:
|
||||
// jwtPublicKeysRelativeURL = "api/flamenco/jwt/public-keys"
|
||||
|
||||
defaultShamanFilestorePath = "/shared/flamenco/file-store"
|
||||
defaultJobStorage = "/shared/flamenco/jobs"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -97,7 +96,7 @@ type Base struct {
|
||||
// TestTasks TestTasks `yaml:"test_tasks"`
|
||||
|
||||
// Shaman configuration settings.
|
||||
// Shaman ShamanConfig `yaml:"shaman"`
|
||||
Shaman shaman_config.Config `yaml:"shaman"`
|
||||
|
||||
// Authentication settings.
|
||||
// JWT jwtauth.Config `yaml:"user_authentication"`
|
||||
@ -109,12 +108,6 @@ type Base struct {
|
||||
// Websetup *WebsetupConf `yaml:"websetup,omitempty"`
|
||||
}
|
||||
|
||||
type ShamanConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
FileStorePath string `yaml:"fileStorePath"`
|
||||
GarbageCollect ShamanGarbageCollect `yaml:"garbageCollect"`
|
||||
}
|
||||
|
||||
// GarbageCollect contains the config options for the GC.
|
||||
type ShamanGarbageCollect struct {
|
||||
// How frequently garbage collection is performed on the file store:
|
||||
|
@ -197,10 +197,10 @@ func (mr *MockFlamencoClientMockRecorder) ShamanCheckoutRequirementsWithResponse
|
||||
}
|
||||
|
||||
// ShamanCheckoutWithBodyWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) ShamanCheckoutWithBodyWithResponse(arg0 context.Context, arg1, arg2 string, arg3 io.Reader, arg4 ...api.RequestEditorFn) (*api.ShamanCheckoutResponse, error) {
|
||||
func (m *MockFlamencoClient) ShamanCheckoutWithBodyWithResponse(arg0 context.Context, arg1 string, arg2 io.Reader, arg3 ...api.RequestEditorFn) (*api.ShamanCheckoutResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1, arg2, arg3}
|
||||
for _, a := range arg4 {
|
||||
varargs := []interface{}{arg0, arg1, arg2}
|
||||
for _, a := range arg3 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "ShamanCheckoutWithBodyWithResponse", varargs...)
|
||||
@ -210,17 +210,17 @@ func (m *MockFlamencoClient) ShamanCheckoutWithBodyWithResponse(arg0 context.Con
|
||||
}
|
||||
|
||||
// ShamanCheckoutWithBodyWithResponse indicates an expected call of ShamanCheckoutWithBodyWithResponse.
|
||||
func (mr *MockFlamencoClientMockRecorder) ShamanCheckoutWithBodyWithResponse(arg0, arg1, arg2, arg3 interface{}, arg4 ...interface{}) *gomock.Call {
|
||||
func (mr *MockFlamencoClientMockRecorder) ShamanCheckoutWithBodyWithResponse(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1, arg2, arg3}, arg4...)
|
||||
varargs := append([]interface{}{arg0, arg1, arg2}, arg3...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShamanCheckoutWithBodyWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).ShamanCheckoutWithBodyWithResponse), varargs...)
|
||||
}
|
||||
|
||||
// ShamanCheckoutWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) ShamanCheckoutWithResponse(arg0 context.Context, arg1 string, arg2 api.ShamanCheckoutJSONRequestBody, arg3 ...api.RequestEditorFn) (*api.ShamanCheckoutResponse, error) {
|
||||
func (m *MockFlamencoClient) ShamanCheckoutWithResponse(arg0 context.Context, arg1 api.ShamanCheckoutJSONRequestBody, arg2 ...api.RequestEditorFn) (*api.ShamanCheckoutResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1, arg2}
|
||||
for _, a := range arg3 {
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "ShamanCheckoutWithResponse", varargs...)
|
||||
@ -230,9 +230,9 @@ func (m *MockFlamencoClient) ShamanCheckoutWithResponse(arg0 context.Context, ar
|
||||
}
|
||||
|
||||
// ShamanCheckoutWithResponse indicates an expected call of ShamanCheckoutWithResponse.
|
||||
func (mr *MockFlamencoClientMockRecorder) ShamanCheckoutWithResponse(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call {
|
||||
func (mr *MockFlamencoClientMockRecorder) ShamanCheckoutWithResponse(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1, arg2}, arg3...)
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShamanCheckoutWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).ShamanCheckoutWithResponse), varargs...)
|
||||
}
|
||||
|
||||
|
@ -286,17 +286,12 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/shaman/checkout/create/{checkoutID}:
|
||||
/shaman/checkout/create:
|
||||
summary: Symlink a set of files into the checkout area.
|
||||
post:
|
||||
operationId: shamanCheckout
|
||||
summary: Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.
|
||||
tags: [shaman]
|
||||
parameters:
|
||||
- name: checkoutID
|
||||
in: path
|
||||
required: true
|
||||
schema: {type: string}
|
||||
requestBody:
|
||||
description: Set of files to check out.
|
||||
required: true
|
||||
@ -749,7 +744,14 @@ components:
|
||||
"files":
|
||||
type: array
|
||||
items: {$ref: "#/components/schemas/ShamanFileSpecWithPath"}
|
||||
required: [files]
|
||||
"checkoutPath":
|
||||
type: string
|
||||
description: >
|
||||
Path where the Manager should create this checkout, It is relative
|
||||
to the Shaman checkout path as configured on the Manager. In older
|
||||
versions of the Shaman this was just the "checkout ID", but in this
|
||||
version it can be a path like `project-slug/scene-name/unique-ID`.
|
||||
required: [files, checkoutPath]
|
||||
example:
|
||||
files:
|
||||
- sha: 35b0491c27b0333d1fb45fc0789a12ca06b1d640d2569780b807de504d7029e0
|
||||
|
@ -134,9 +134,9 @@ type ClientInterface interface {
|
||||
TaskUpdate(ctx context.Context, taskId string, body TaskUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// ShamanCheckout request with any body
|
||||
ShamanCheckoutWithBody(ctx context.Context, checkoutID string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
ShamanCheckoutWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
ShamanCheckout(ctx context.Context, checkoutID string, body ShamanCheckoutJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
ShamanCheckout(ctx context.Context, body ShamanCheckoutJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// ShamanCheckoutRequirements request with any body
|
||||
ShamanCheckoutRequirementsWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
@ -342,8 +342,8 @@ func (c *Client) TaskUpdate(ctx context.Context, taskId string, body TaskUpdateJ
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) ShamanCheckoutWithBody(ctx context.Context, checkoutID string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewShamanCheckoutRequestWithBody(c.Server, checkoutID, contentType, body)
|
||||
func (c *Client) ShamanCheckoutWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewShamanCheckoutRequestWithBody(c.Server, contentType, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -354,8 +354,8 @@ func (c *Client) ShamanCheckoutWithBody(ctx context.Context, checkoutID string,
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) ShamanCheckout(ctx context.Context, checkoutID string, body ShamanCheckoutJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewShamanCheckoutRequest(c.Server, checkoutID, body)
|
||||
func (c *Client) ShamanCheckout(ctx context.Context, body ShamanCheckoutJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewShamanCheckoutRequest(c.Server, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -791,33 +791,26 @@ func NewTaskUpdateRequestWithBody(server string, taskId string, contentType stri
|
||||
}
|
||||
|
||||
// NewShamanCheckoutRequest calls the generic ShamanCheckout builder with application/json body
|
||||
func NewShamanCheckoutRequest(server string, checkoutID string, body ShamanCheckoutJSONRequestBody) (*http.Request, error) {
|
||||
func NewShamanCheckoutRequest(server string, body ShamanCheckoutJSONRequestBody) (*http.Request, error) {
|
||||
var bodyReader io.Reader
|
||||
buf, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bodyReader = bytes.NewReader(buf)
|
||||
return NewShamanCheckoutRequestWithBody(server, checkoutID, "application/json", bodyReader)
|
||||
return NewShamanCheckoutRequestWithBody(server, "application/json", bodyReader)
|
||||
}
|
||||
|
||||
// NewShamanCheckoutRequestWithBody generates requests for ShamanCheckout with any type of body
|
||||
func NewShamanCheckoutRequestWithBody(server string, checkoutID string, contentType string, body io.Reader) (*http.Request, error) {
|
||||
func NewShamanCheckoutRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
var pathParam0 string
|
||||
|
||||
pathParam0, err = runtime.StyleParamWithLocation("simple", false, "checkoutID", runtime.ParamLocationPath, checkoutID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/shaman/checkout/create/%s", pathParam0)
|
||||
operationPath := fmt.Sprintf("/shaman/checkout/create")
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
@ -1070,9 +1063,9 @@ type ClientWithResponsesInterface interface {
|
||||
TaskUpdateWithResponse(ctx context.Context, taskId string, body TaskUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*TaskUpdateResponse, error)
|
||||
|
||||
// ShamanCheckout request with any body
|
||||
ShamanCheckoutWithBodyWithResponse(ctx context.Context, checkoutID string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ShamanCheckoutResponse, error)
|
||||
ShamanCheckoutWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ShamanCheckoutResponse, error)
|
||||
|
||||
ShamanCheckoutWithResponse(ctx context.Context, checkoutID string, body ShamanCheckoutJSONRequestBody, reqEditors ...RequestEditorFn) (*ShamanCheckoutResponse, error)
|
||||
ShamanCheckoutWithResponse(ctx context.Context, body ShamanCheckoutJSONRequestBody, reqEditors ...RequestEditorFn) (*ShamanCheckoutResponse, error)
|
||||
|
||||
// ShamanCheckoutRequirements request with any body
|
||||
ShamanCheckoutRequirementsWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ShamanCheckoutRequirementsResponse, error)
|
||||
@ -1568,16 +1561,16 @@ func (c *ClientWithResponses) TaskUpdateWithResponse(ctx context.Context, taskId
|
||||
}
|
||||
|
||||
// ShamanCheckoutWithBodyWithResponse request with arbitrary body returning *ShamanCheckoutResponse
|
||||
func (c *ClientWithResponses) ShamanCheckoutWithBodyWithResponse(ctx context.Context, checkoutID string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ShamanCheckoutResponse, error) {
|
||||
rsp, err := c.ShamanCheckoutWithBody(ctx, checkoutID, contentType, body, reqEditors...)
|
||||
func (c *ClientWithResponses) ShamanCheckoutWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ShamanCheckoutResponse, error) {
|
||||
rsp, err := c.ShamanCheckoutWithBody(ctx, contentType, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseShamanCheckoutResponse(rsp)
|
||||
}
|
||||
|
||||
func (c *ClientWithResponses) ShamanCheckoutWithResponse(ctx context.Context, checkoutID string, body ShamanCheckoutJSONRequestBody, reqEditors ...RequestEditorFn) (*ShamanCheckoutResponse, error) {
|
||||
rsp, err := c.ShamanCheckout(ctx, checkoutID, body, reqEditors...)
|
||||
func (c *ClientWithResponses) ShamanCheckoutWithResponse(ctx context.Context, body ShamanCheckoutJSONRequestBody, reqEditors ...RequestEditorFn) (*ShamanCheckoutResponse, error) {
|
||||
rsp, err := c.ShamanCheckout(ctx, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -47,8 +47,8 @@ type ServerInterface interface {
|
||||
// (POST /api/worker/task/{task_id})
|
||||
TaskUpdate(ctx echo.Context, taskId string) error
|
||||
// Create a directory, and symlink the required files into it. The files must all have been uploaded to Shaman before calling this endpoint.
|
||||
// (POST /shaman/checkout/create/{checkoutID})
|
||||
ShamanCheckout(ctx echo.Context, checkoutID string) error
|
||||
// (POST /shaman/checkout/create)
|
||||
ShamanCheckout(ctx echo.Context) error
|
||||
// Checks a Shaman Requirements file, and reports which files are unknown.
|
||||
// (POST /shaman/checkout/requirements)
|
||||
ShamanCheckoutRequirements(ctx echo.Context) error
|
||||
@ -193,16 +193,9 @@ func (w *ServerInterfaceWrapper) TaskUpdate(ctx echo.Context) error {
|
||||
// ShamanCheckout converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) ShamanCheckout(ctx echo.Context) error {
|
||||
var err error
|
||||
// ------------- Path parameter "checkoutID" -------------
|
||||
var checkoutID string
|
||||
|
||||
err = runtime.BindStyledParameterWithLocation("simple", false, "checkoutID", runtime.ParamLocationPath, ctx.Param("checkoutID"), &checkoutID)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter checkoutID: %s", err))
|
||||
}
|
||||
|
||||
// Invoke the callback with all the unmarshalled arguments
|
||||
err = w.Handler.ShamanCheckout(ctx, checkoutID)
|
||||
err = w.Handler.ShamanCheckout(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -337,7 +330,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
||||
router.POST(baseURL+"/api/worker/state-changed", wrapper.WorkerStateChanged)
|
||||
router.POST(baseURL+"/api/worker/task", wrapper.ScheduleTask)
|
||||
router.POST(baseURL+"/api/worker/task/:task_id", wrapper.TaskUpdate)
|
||||
router.POST(baseURL+"/shaman/checkout/create/:checkoutID", wrapper.ShamanCheckout)
|
||||
router.POST(baseURL+"/shaman/checkout/create", wrapper.ShamanCheckout)
|
||||
router.POST(baseURL+"/shaman/checkout/requirements", wrapper.ShamanCheckoutRequirements)
|
||||
router.OPTIONS(baseURL+"/shaman/files/:checksum/:filesize", wrapper.ShamanFileStoreCheck)
|
||||
router.POST(baseURL+"/shaman/files/:checksum/:filesize", wrapper.ShamanFileStore)
|
||||
|
@ -18,90 +18,92 @@ import (
|
||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"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",
|
||||
"4NAxUwAA",
|
||||
"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",
|
||||
"AP//RfyB4BlUAAA=",
|
||||
}
|
||||
|
||||
// GetSwagger returns the content of the embedded swagger specification file
|
||||
|
@ -244,7 +244,9 @@ type SecurityError struct {
|
||||
|
||||
// Set of files with their SHA256 checksum, size in bytes, and desired location in the checkout directory.
|
||||
type ShamanCheckout struct {
|
||||
Files []ShamanFileSpecWithPath `json:"files"`
|
||||
// Path where the Manager should create this checkout, It is relative to the Shaman checkout path as configured on the Manager. In older versions of the Shaman this was just the "checkout ID", but in this version it can be a path like `project-slug/scene-name/unique-ID`.
|
||||
CheckoutPath string `json:"checkoutPath"`
|
||||
Files []ShamanFileSpecWithPath `json:"files"`
|
||||
}
|
||||
|
||||
// Specification of a file in the Shaman storage.
|
||||
|
@ -16,12 +16,13 @@ var (
|
||||
ErrMissingFiles = errors.New("unknown files requested in checkout")
|
||||
)
|
||||
|
||||
func (m *Manager) Checkout(ctx context.Context, checkoutID string, checkout api.ShamanCheckout) error {
|
||||
logger := *zerolog.Ctx(ctx)
|
||||
func (m *Manager) Checkout(ctx context.Context, checkout api.ShamanCheckout) error {
|
||||
logger := (*zerolog.Ctx(ctx)).With().
|
||||
Str("checkoutPath", checkout.CheckoutPath).Logger()
|
||||
logger.Debug().Msg("shaman: user requested checkout creation")
|
||||
|
||||
// Actually create the checkout.
|
||||
resolvedCheckoutInfo, err := m.PrepareCheckout(checkoutID)
|
||||
resolvedCheckoutInfo, err := m.PrepareCheckout(checkout.CheckoutPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -30,7 +31,10 @@ func (m *Manager) Checkout(ctx context.Context, checkoutID string, checkout api.
|
||||
var checkoutOK bool
|
||||
defer func() {
|
||||
if !checkoutOK {
|
||||
m.EraseCheckout(checkoutID)
|
||||
err := m.EraseCheckout(checkout.CheckoutPath)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("shaman: error erasing checkout directory")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -24,8 +24,8 @@ package checkout
|
||||
|
||||
import "regexp"
|
||||
|
||||
var validCheckoutRegexp = regexp.MustCompile("^[a-zA-Z0-9_]+$")
|
||||
var validCheckoutRegexp = regexp.MustCompile("^[a-zA-Z0-9_ /]+$")
|
||||
|
||||
func isValidCheckoutID(checkoutID string) bool {
|
||||
func isValidCheckoutPath(checkoutID string) bool {
|
||||
return validCheckoutRegexp.MatchString(checkoutID)
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ import (
|
||||
// Manager creates checkouts and provides info about missing files.
|
||||
type Manager struct {
|
||||
checkoutBasePath string
|
||||
fileStore filestore.Storage
|
||||
fileStore *filestore.Store
|
||||
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
@ -49,8 +49,8 @@ type Manager struct {
|
||||
type ResolvedCheckoutInfo struct {
|
||||
// The absolute path on our filesystem.
|
||||
absolutePath string
|
||||
// The path relative to the Manager.checkoutBasePath. This is what is
|
||||
// sent back to the client.
|
||||
// The path relative to the Manager.checkoutBasePath. This is what was
|
||||
// received from the client.
|
||||
RelativePath string
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ var (
|
||||
)
|
||||
|
||||
// NewManager creates and returns a new Checkout Manager.
|
||||
func NewManager(conf config.Config, fileStore filestore.Storage) *Manager {
|
||||
func NewManager(conf config.Config, fileStore *filestore.Store) *Manager {
|
||||
logger := log.With().Str("checkoutDir", conf.CheckoutPath).Logger()
|
||||
logger.Info().Msg("opening checkout directory")
|
||||
|
||||
@ -79,36 +79,28 @@ func (m *Manager) Close() {
|
||||
m.wg.Wait()
|
||||
}
|
||||
|
||||
func (m *Manager) pathForCheckoutID(checkoutID string) (ResolvedCheckoutInfo, error) {
|
||||
if !isValidCheckoutID(checkoutID) {
|
||||
func (m *Manager) pathForCheckout(requestedCheckoutPath string) (ResolvedCheckoutInfo, error) {
|
||||
if !isValidCheckoutPath(requestedCheckoutPath) {
|
||||
return ResolvedCheckoutInfo{}, ErrInvalidCheckoutID
|
||||
}
|
||||
|
||||
// When changing the number of path components the checkout ID is turned into,
|
||||
// be sure to also update the EraseCheckout() function for this.
|
||||
|
||||
// We're expecting ObjectIDs as checkoutIDs, which means most variation
|
||||
// is in the last characters.
|
||||
lastBitIndex := len(checkoutID) - 2
|
||||
relativePath := path.Join(checkoutID[lastBitIndex:], checkoutID)
|
||||
|
||||
return ResolvedCheckoutInfo{
|
||||
absolutePath: path.Join(m.checkoutBasePath, relativePath),
|
||||
RelativePath: relativePath,
|
||||
absolutePath: filepath.Join(m.checkoutBasePath, requestedCheckoutPath),
|
||||
RelativePath: requestedCheckoutPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PrepareCheckout creates the root directory for a specific checkout.
|
||||
// Returns the path relative to the checkout root directory.
|
||||
func (m *Manager) PrepareCheckout(checkoutID string) (ResolvedCheckoutInfo, error) {
|
||||
checkoutPaths, err := m.pathForCheckoutID(checkoutID)
|
||||
func (m *Manager) PrepareCheckout(checkoutPath string) (ResolvedCheckoutInfo, error) {
|
||||
checkoutPaths, err := m.pathForCheckout(checkoutPath)
|
||||
if err != nil {
|
||||
return ResolvedCheckoutInfo{}, err
|
||||
}
|
||||
|
||||
logger := log.With().
|
||||
Str("checkoutPath", checkoutPaths.absolutePath).
|
||||
Str("checkoutID", checkoutID).
|
||||
Str("absolutePath", checkoutPaths.absolutePath).
|
||||
Str("checkoutPath", checkoutPath).
|
||||
Logger()
|
||||
|
||||
if stat, err := os.Stat(checkoutPaths.absolutePath); !os.IsNotExist(err) {
|
||||
@ -136,7 +128,7 @@ func (m *Manager) PrepareCheckout(checkoutID string) (ResolvedCheckoutInfo, erro
|
||||
|
||||
// EraseCheckout removes the checkout directory structure identified by the ID.
|
||||
func (m *Manager) EraseCheckout(checkoutID string) error {
|
||||
checkoutPaths, err := m.pathForCheckoutID(checkoutID)
|
||||
checkoutPaths, err := m.pathForCheckout(checkoutID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ type receiverChannel chan struct{}
|
||||
|
||||
// FileServer deals with receiving and serving of uploaded files.
|
||||
type FileServer struct {
|
||||
fileStore filestore.Storage
|
||||
fileStore *filestore.Store
|
||||
|
||||
receiverMutex sync.Mutex
|
||||
receiverChannels map[string]receiverChannel
|
||||
@ -44,7 +44,7 @@ type FileServer struct {
|
||||
}
|
||||
|
||||
// New creates a new File Server and starts a monitoring goroutine.
|
||||
func New(fileStore filestore.Storage) *FileServer {
|
||||
func New(fileStore *filestore.Store) *FileServer {
|
||||
ctx, ctxCancel := context.WithCancel(context.Background())
|
||||
|
||||
fs := &FileServer{
|
||||
|
@ -65,20 +65,25 @@ func (fs *FileServer) ReceiveFile(
|
||||
defer bodyReader.Close()
|
||||
|
||||
localPath, status := fs.fileStore.ResolveFile(checksum, filesize, filestore.ResolveEverything)
|
||||
logger = logger.With().Str("path", localPath).Logger()
|
||||
logger = logger.With().
|
||||
Str("path", localPath).
|
||||
Str("checksum", checksum).
|
||||
Int64("filesize", filesize).
|
||||
Str("status", status.String()).
|
||||
Logger()
|
||||
|
||||
switch status {
|
||||
case filestore.StatusStored:
|
||||
logger.Info().Msg("uploaded file already exists")
|
||||
logger.Info().Msg("shaman: uploaded file already exists")
|
||||
return ErrFileAlreadyExists
|
||||
case filestore.StatusUploading:
|
||||
if canDefer {
|
||||
logger.Info().Msg("someone is uploading this file and client can defer")
|
||||
logger.Info().Msg("shaman: someone is uploading this file and client can defer")
|
||||
return ErrFileAlreadyExists
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info().Msg("receiving file")
|
||||
logger.Info().Msg("shaman: receiving file")
|
||||
|
||||
streamTo, err := fs.fileStore.OpenForUpload(checksum, filesize)
|
||||
if err != nil {
|
||||
|
@ -25,36 +25,8 @@ package filestore
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Storage is the interface for Shaman file stores.
|
||||
type Storage interface {
|
||||
// ResolveFile checks the status of the file in the store and returns the actual path.
|
||||
ResolveFile(checksum string, filesize int64, storedOnly StoredOnly) (string, FileStatus)
|
||||
|
||||
// OpenForUpload returns a file pointer suitable to stream an uploaded file to.
|
||||
OpenForUpload(checksum string, filesize int64) (*os.File, error)
|
||||
|
||||
// BasePath returns the directory path of the storage.
|
||||
// This is the directory containing the 'stored' and 'uploading' directories.
|
||||
BasePath() string
|
||||
|
||||
// StoragePath returns the directory path of the 'stored' storage bin.
|
||||
StoragePath() string
|
||||
|
||||
// MoveToStored moves a file from 'uploading' storage to the actual 'stored' storage.
|
||||
MoveToStored(checksum string, filesize int64, uploadedFilePath string) error
|
||||
|
||||
// RemoveUploadedFile removes a file from the 'uploading' storage.
|
||||
// This is intended to clean up files for which upload was aborted for some reason.
|
||||
RemoveUploadedFile(filePath string)
|
||||
|
||||
// RemoveStoredFile removes a file from the 'stored' storage bin.
|
||||
// This is intended to garbage collect old, unused files.
|
||||
RemoveStoredFile(filePath string) error
|
||||
}
|
||||
|
||||
// FileStatus represents the status of a file in the store.
|
||||
type FileStatus int
|
||||
|
||||
|
@ -41,7 +41,7 @@ type Server struct {
|
||||
config config.Config
|
||||
|
||||
auther jwtauth.Authenticator
|
||||
fileStore filestore.Storage
|
||||
fileStore *filestore.Store
|
||||
fileServer *fileserver.FileServer
|
||||
checkoutMan *checkout.Manager
|
||||
|
||||
@ -52,7 +52,12 @@ type Server struct {
|
||||
// NewServer creates a new Shaman server.
|
||||
func NewServer(conf config.Config, auther jwtauth.Authenticator) *Server {
|
||||
if !conf.Enabled {
|
||||
log.Info().Msg("Shaman server is disabled")
|
||||
log.Info().Msg("shaman server is disabled")
|
||||
return nil
|
||||
}
|
||||
|
||||
if conf.CheckoutPath == "" {
|
||||
log.Error().Interface("config", conf).Msg("shaman: no checkout path configured, unable to start")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -101,10 +106,14 @@ func (s *Server) Close() {
|
||||
s.wg.Wait()
|
||||
}
|
||||
|
||||
func (s *Server) IsEnabled() bool {
|
||||
return s != nil && s.config.Enabled
|
||||
}
|
||||
|
||||
// Checkout creates a directory, and symlinks the required files into it. The
|
||||
// files must all have been uploaded to Shaman before calling this.
|
||||
func (s *Server) Checkout(ctx context.Context, checkoutID string, checkout api.ShamanCheckout) error {
|
||||
return s.checkoutMan.Checkout(ctx, checkoutID, checkout)
|
||||
func (s *Server) Checkout(ctx context.Context, checkout api.ShamanCheckout) error {
|
||||
return s.checkoutMan.Checkout(ctx, checkout)
|
||||
}
|
||||
|
||||
// Requirements checks a Shaman Requirements file, and returns the subset
|
||||
|
Loading…
x
Reference in New Issue
Block a user