From c76d71b12e2f0126bb9bfe0b440775566e32f5d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20Crespo?= Date: Wed, 1 May 2024 17:00:11 +0000 Subject: [PATCH] Implement updateContextDecisionJob --- hack/ccp/internal/controller/iterator.go | 6 + hack/ccp/internal/controller/jobs.go | 167 +++++++++++++++--- hack/ccp/internal/controller/package.go | 44 +++-- hack/ccp/internal/controller/package_test.go | 27 +-- hack/ccp/internal/store/mysql.go | 66 ++++++- hack/ccp/internal/store/sqlcmysql/db.go | 110 +++++++----- .../ccp/internal/store/sqlcmysql/query.sql.go | 83 +++++++++ hack/ccp/internal/store/store.go | 16 ++ hack/ccp/internal/workflow/config.go | 4 +- hack/ccp/sqlc/mysql/query.sql | 9 + 10 files changed, 440 insertions(+), 92 deletions(-) diff --git a/hack/ccp/internal/controller/iterator.go b/hack/ccp/internal/controller/iterator.go index a3681fdbd..f133d8cdb 100644 --- a/hack/ccp/internal/controller/iterator.go +++ b/hack/ccp/internal/controller/iterator.go @@ -37,6 +37,12 @@ type chain struct { choices any // TODO: see `generated_choices` in `chain.py`. } +func (c *chain) update(kvs map[string]string) { + for k, v := range kvs { + c.pCtx.Set(k, string(v)) + } +} + // iterator carries a package through all its workflow. type iterator struct { logger logr.Logger diff --git a/hack/ccp/internal/controller/jobs.go b/hack/ccp/internal/controller/jobs.go index 3c3ba9e71..ff1f41572 100644 --- a/hack/ccp/internal/controller/jobs.go +++ b/hack/ccp/internal/controller/jobs.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "maps" "time" "github.com/go-logr/logr" @@ -317,22 +318,22 @@ var _ jobRunner = (*updateContextDecisionJob)(nil) // TODO: this should be defined in the workflow, not hardcoded here. // // nolint: unused -var updateContextDecisionJobChoiceMapping = map[string]string{ +var updateContextDecisionJobChoiceMapping = map[uuid.UUID]uuid.UUID{ // Decision point "Assign UUIDs to directories?". - "8882bad4-561c-4126-89c9-f7f0c083d5d7": "bd899573-694e-4d33-8c9b-df0af802437d", - "e10a31c3-56df-4986-af7e-2794ddfe8686": "bd899573-694e-4d33-8c9b-df0af802437d", - "d6f6f5db-4cc2-4652-9283-9ec6a6d181e5": "bd899573-694e-4d33-8c9b-df0af802437d", - "1563f22f-f5f7-4dfe-a926-6ab50d408832": "bd899573-694e-4d33-8c9b-df0af802437d", + uuid.MustParse("8882bad4-561c-4126-89c9-f7f0c083d5d7"): uuid.MustParse("bd899573-694e-4d33-8c9b-df0af802437d"), + uuid.MustParse("e10a31c3-56df-4986-af7e-2794ddfe8686"): uuid.MustParse("bd899573-694e-4d33-8c9b-df0af802437d"), + uuid.MustParse("d6f6f5db-4cc2-4652-9283-9ec6a6d181e5"): uuid.MustParse("bd899573-694e-4d33-8c9b-df0af802437d"), + uuid.MustParse("1563f22f-f5f7-4dfe-a926-6ab50d408832"): uuid.MustParse("bd899573-694e-4d33-8c9b-df0af802437d"), // Decision "Yes" (for "Assign UUIDs to directories?"). - "7e4cf404-e62d-4dc2-8d81-6141e390f66f": "2dc3f487-e4b0-4e07-a4b3-6216ed24ca14", - "2732a043-b197-4cbc-81ab-4e2bee9b74d3": "2dc3f487-e4b0-4e07-a4b3-6216ed24ca14", - "aa793efa-1b62-498c-8f92-cab187a99a2a": "2dc3f487-e4b0-4e07-a4b3-6216ed24ca14", - "efd98ddb-80a6-4206-80bf-81bf00f84416": "2dc3f487-e4b0-4e07-a4b3-6216ed24ca14", + uuid.MustParse("7e4cf404-e62d-4dc2-8d81-6141e390f66f"): uuid.MustParse("2dc3f487-e4b0-4e07-a4b3-6216ed24ca14"), + uuid.MustParse("2732a043-b197-4cbc-81ab-4e2bee9b74d3"): uuid.MustParse("2dc3f487-e4b0-4e07-a4b3-6216ed24ca14"), + uuid.MustParse("aa793efa-1b62-498c-8f92-cab187a99a2a"): uuid.MustParse("2dc3f487-e4b0-4e07-a4b3-6216ed24ca14"), + uuid.MustParse("efd98ddb-80a6-4206-80bf-81bf00f84416"): uuid.MustParse("2dc3f487-e4b0-4e07-a4b3-6216ed24ca14"), // Decision "No" (for "Assign UUIDs to directories?"). - "0053c670-3e61-4a3e-a188-3a2dd1eda426": "891f60d0-1ba8-48d3-b39e-dd0934635d29", - "8e93e523-86bb-47e1-a03a-4b33e13f8c5e": "891f60d0-1ba8-48d3-b39e-dd0934635d29", - "6dfbeff8-c6b1-435b-833a-ed764229d413": "891f60d0-1ba8-48d3-b39e-dd0934635d29", - "dc0ee6b6-ed5f-42a3-bc8f-c9c7ead03ed1": "891f60d0-1ba8-48d3-b39e-dd0934635d29", + uuid.MustParse("0053c670-3e61-4a3e-a188-3a2dd1eda426"): uuid.MustParse("891f60d0-1ba8-48d3-b39e-dd0934635d29"), + uuid.MustParse("8e93e523-86bb-47e1-a03a-4b33e13f8c5e"): uuid.MustParse("891f60d0-1ba8-48d3-b39e-dd0934635d29"), + uuid.MustParse("6dfbeff8-c6b1-435b-833a-ed764229d413"): uuid.MustParse("891f60d0-1ba8-48d3-b39e-dd0934635d29"), + uuid.MustParse("dc0ee6b6-ed5f-42a3-bc8f-c9c7ead03ed1"): uuid.MustParse("891f60d0-1ba8-48d3-b39e-dd0934635d29"), } func newUpdateContextDecisionJob(j *job) (*updateContextDecisionJob, error) { @@ -347,7 +348,7 @@ func newUpdateContextDecisionJob(j *job) (*updateContextDecisionJob, error) { }, nil } -func (l *updateContextDecisionJob) exec(ctx context.Context) (_ uuid.UUID, err error) { +func (l *updateContextDecisionJob) exec(ctx context.Context) (linkID uuid.UUID, err error) { defer func() { if err != nil { err = fmt.Errorf("nextChainDecisionJob: %v", err) @@ -355,6 +356,12 @@ func (l *updateContextDecisionJob) exec(ctx context.Context) (_ uuid.UUID, err e } if e := l.j.markComplete(ctx); e != nil { err = e + return + } + if id := l.j.wl.ExitCodes[0].LinkID; id == nil || *id == uuid.Nil { + err = fmt.Errorf("nextChainDecisionJob: linkID undefined") + } else { + linkID = *id } }() @@ -365,19 +372,133 @@ func (l *updateContextDecisionJob) exec(ctx context.Context) (_ uuid.UUID, err e return uuid.Nil, fmt.Errorf("save: %v", err) } - panic("not implemented") + // Load new context from the database (DashboardSettings). + // TODO: split this out? Workflow items with no replacements configured + // seems like a different case. + if len(l.config.Replacements) == 0 { + if dict, err := l.loadDatabaseContext(ctx); err != nil { + return uuid.Nil, fmt.Errorf("load dict from db: %v", err) + } else if dict != nil { + l.j.chain.update(dict) + return uuid.Nil, nil + } + } - // TODO: - // - Load settings from DashboardSetting to update context. - // - Or load preconfigured choice to update context. - // - Or mark as awaiting. + // Load new context from processing configuration. + if dict, err := l.loadPreconfiguredContext(); err != nil { + return uuid.Nil, fmt.Errorf("load context with preconfigured choice: %v", err) + } else if dict != nil { + l.j.chain.update(dict) + return uuid.Nil, nil + } - id := l.j.wl.ExitCodes[0].LinkID - if id == nil || *id == uuid.Nil { - return uuid.Nil, errors.New("ops") + // Build decision point and await resolution. + opts := make([]option, len(l.config.Replacements)) + for i, item := range l.config.Replacements { + opts[i] = option(item.Description.String()) } - return *id, nil + return l.await(ctx, opts) +} + +// loadDatabaseContext loads the context dictionary from the database. +func (l *updateContextDecisionJob) loadDatabaseContext(ctx context.Context) (map[string]string, error) { + ln, ok := l.j.wf.Links[l.j.wl.FallbackLinkID] + if !ok { + return nil, nil + } + cfg, ok := ln.Config.(workflow.LinkStandardTaskConfig) + if !ok { + return nil, nil + } + if cfg.Execute == "" { + return nil, nil + } + + ret, err := l.j.pkg.store.ReadDict(ctx, cfg.Execute) + if err != nil { + return nil, err + } + + return l.formatChoices(ret), nil +} + +// loadPreconfiguredContext loads the context dictionary from the workflow. +func (l *updateContextDecisionJob) loadPreconfiguredContext() (map[string]string, error) { + var normalizedChoice uuid.UUID + if v, ok := updateContextDecisionJobChoiceMapping[l.j.wl.ID]; ok { + normalizedChoice = v + } else { + normalizedChoice = l.j.wl.ID + } + + choices, err := l.j.pkg.parseProcessingConfig() + if err != nil { + return nil, err + } + + for _, choice := range choices { + if choice.AppliesTo != normalizedChoice.String() { + continue + } + desiredChoice, err := uuid.Parse(choice.GoToChain) + if err != nil { + return nil, err + } + if v, ok := updateContextDecisionJobChoiceMapping[desiredChoice]; ok { + desiredChoice = v + } + ln, ok := l.j.wf.Links[normalizedChoice] + if !ok { + return nil, nil // fmt.Errorf("desired choice not found: %s", desiredChoice) + } + config, ok := ln.Config.(workflow.LinkMicroServiceChoiceReplacementDic) + if !ok { + return nil, fmt.Errorf("desired choice doesn't have the expected type: %s", desiredChoice) + } + for _, replacement := range config.Replacements { + if replacement.ID == desiredChoice.String() { + choices := maps.Clone(replacement.Items) + return l.formatChoices(choices), nil + } + } + } + + return nil, nil +} + +func (l *updateContextDecisionJob) formatChoices(choices map[string]string) map[string]string { + for k, v := range choices { + delete(choices, k) + choices[fmt.Sprintf("%%%s%%", k)] = v + } + + return choices +} + +func (l *updateContextDecisionJob) await(ctx context.Context, opts []option) (_ uuid.UUID, err error) { + defer func() { + if err != nil { + err = fmt.Errorf("await: %v", err) + return + } + }() + + if err := l.j.markAwaitingDecision(ctx); err != nil { + return uuid.Nil, err + } + + decision, err := l.j.pkg.AwaitDecision(ctx, opts) + if err != nil { + return uuid.Nil, err + } + + // TODO: decision here should be an integer. + // https://github.com/artefactual/archivematica/blob/2dd5a2366bf0529c193a19a5546087ed9a0b5534/src/MCPServer/lib/server/jobs/decisions.py#L286-L298 + + panic("not implemented") + + return decision.uuid(), nil } // directoryClientScriptJob. diff --git a/hack/ccp/internal/controller/package.go b/hack/ccp/internal/controller/package.go index 9e0e40b92..c3651d181 100644 --- a/hack/ccp/internal/controller/package.go +++ b/hack/ccp/internal/controller/package.go @@ -109,6 +109,25 @@ func (p *Package) String() string { return p.Name() } +// parseProcessingConfig returns a list of preconfigured choices. A missing +// configuration file is a non-error, i.e. returns an empty slice of choices. +func (p *Package) parseProcessingConfig() ([]workflow.Choice, error) { + f, err := os.Open(filepath.Join(p.path, "processingMCP.xml")) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, err + } + + choices, err := workflow.ParseConfig(f) + if err != nil { + return nil, fmt.Errorf("parse: %v", err) + } + + return choices, nil +} + // PreconfiguredChoice looks up a pre-configured choice in the processing // configuration file that is part of the package. func (p *Package) PreconfiguredChoice(linkID uuid.UUID) (uuid.UUID, error) { @@ -118,19 +137,11 @@ func (p *Package) PreconfiguredChoice(linkID uuid.UUID) (uuid.UUID, error) { return chainID, nil } - f, err := os.Open(filepath.Join(p.path, "processingMCP.xml")) - if err != nil { - if os.IsNotExist(err) { - return uuid.Nil, nil - } - return uuid.Nil, err - } - - // TODO: this could be cached if the file isn't going to change, but - // Archivematica is not doing any caching. - choices, err := workflow.ParseConfig(f) + choices, err := p.parseProcessingConfig() if err != nil { return uuid.Nil, err + } else if len(choices) == 0 { + return uuid.Nil, nil } var chainID uuid.UUID @@ -194,7 +205,7 @@ func (p *Package) Files(ctx context.Context, filterFilenameEnd, filterSubdir str if err != nil { return nil, err } - ret := make([]replacementMapping, len(files)) + ret := make([]replacementMapping, 0, len(files)) seen := make(map[string]struct{}, len(files)) for _, f := range files { @@ -218,6 +229,9 @@ func (p *Package) Files(ctx context.Context, filterFilenameEnd, filterSubdir str if err != nil { return err } + if d.IsDir() { + return nil + } fname := d.Name() if filterFilenameEnd != "" && !strings.HasPrefix(fname, filterFilenameEnd) { return nil @@ -227,7 +241,7 @@ func (p *Package) Files(ctx context.Context, filterFilenameEnd, filterSubdir str } ret = append(ret, map[string]replacement{ "%relativeLocation": replacement(path), - "%fileUUID%": replacement(""), + "%fileUUID%": replacement("None"), "%fileGrpUse%": replacement(""), }) return nil @@ -546,8 +560,8 @@ func fileReplacements(pkg *Package, f *store.File) replacementMapping { ext := filepath.Ext(f.CurrentLocation) extWithDot := "." + ext name := filepath.Base(strings.TrimSuffix(f.CurrentLocation, ext)) - absolutePath := strings.ReplaceAll(f.CurrentLocation, "%SIPDirectory%", pkg.Path()) - absolutePath = strings.ReplaceAll(absolutePath, "%transferDirectory%", pkg.Path()) + absolutePath := strings.ReplaceAll(f.CurrentLocation, "%SIPDirectory%", joinPath(pkg.Path(), "")) + absolutePath = strings.ReplaceAll(absolutePath, "%transferDirectory%", joinPath(pkg.Path(), "")) maps.Copy(mapping, map[string]replacement{ "%fileUUID%": replacement(f.ID.String()), diff --git a/hack/ccp/internal/controller/package_test.go b/hack/ccp/internal/controller/package_test.go index a1bb87d93..1d37acbca 100644 --- a/hack/ccp/internal/controller/package_test.go +++ b/hack/ccp/internal/controller/package_test.go @@ -8,17 +8,22 @@ import ( ) func TestReplacements(t *testing.T) { - pCtx := &packageContext{OrderedMap: orderedmap.NewOrderedMap[string, string]()} - pCtx.Set("%path%", "/mnt/disk") - pCtx.Set("%name%", `Dr. Evelyn "The Innovator" O'Neill: The Complete Digital Archives`) + t.Parallel() - rm := replacementMapping(map[string]replacement{ - "%uuid%": "91354225-f28b-433c-8280-cf6a5edea2ff", - "%job%": `cool \\stuff`, - }).update(pCtx) + t.Run("Updates itself with a given packageContext", func(t *testing.T) { + t.Parallel() + pCtx := &packageContext{OrderedMap: orderedmap.NewOrderedMap[string, string]()} + pCtx.Set("%path%", "/mnt/disk") + pCtx.Set("%name%", `Dr. Evelyn "The Innovator" O'Neill: The Complete Digital Archives`) - assert.Equal(t, - rm.replaceValues(`%name% with path="%path%" and uuid="%uuid%" did: %job%`), - `Dr. Evelyn \"The Innovator\" O'Neill: The Complete Digital Archives with path="/mnt/disk" and uuid="91354225-f28b-433c-8280-cf6a5edea2ff" did: cool \\\\\\\\stuff`, - ) + rm := replacementMapping(map[string]replacement{ + "%uuid%": "91354225-f28b-433c-8280-cf6a5edea2ff", + "%job%": `cool \\stuff`, + }).update(pCtx) + + assert.Equal(t, + rm.replaceValues(`%name% with path="%path%" and uuid="%uuid%" did: %job%`), + `Dr. Evelyn \"The Innovator\" O'Neill: The Complete Digital Archives with path="/mnt/disk" and uuid="91354225-f28b-433c-8280-cf6a5edea2ff" did: cool \\\\\\\\stuff`, + ) + }) } diff --git a/hack/ccp/internal/store/mysql.go b/hack/ccp/internal/store/mysql.go index aa1b386dc..2932d916c 100644 --- a/hack/ccp/internal/store/mysql.go +++ b/hack/ccp/internal/store/mysql.go @@ -507,7 +507,71 @@ func (s *mysqlStoreImpl) Files(ctx context.Context, id uuid.UUID, packageType en break } else { ret = append(ret, files...) - offset++ + offset += uint(len(files)) + } + } + + return ret, nil +} + +func (s *mysqlStoreImpl) ReadPipelineID(ctx context.Context) (_ uuid.UUID, err error) { + defer wrap(&err, "ReadPipelineID()") + + ret, err := s.queries.ReadDashboardSetting(ctx, "dashboard_uuid") + if err == sql.ErrNoRows { + return uuid.Nil, ErrNotFound + } + if err != nil { + return uuid.Nil, err + } + + id, err := uuid.Parse(ret.Value) + if err != nil { + return uuid.Nil, err + } + + return id, err +} + +func (s *mysqlStoreImpl) ReadDict(ctx context.Context, name string) (_ map[string]string, err error) { + defer wrap(&err, "ReadDict(%s)", name) + + rows, err := s.queries.ReadDashboardSettingsWithScope(ctx, name) + if err != nil { + return nil, err + } + ln := len(rows) + if ln == 0 { + return nil, ErrNotFound + } + + ret := make(map[string]string, ln) + for _, row := range rows { + ret[row.Name] = ret[row.Value] + } + + return ret, nil +} + +func (s *mysqlStoreImpl) ReadStorageServiceConfig(ctx context.Context) (ret StorageServiceConfig, err error) { + defer wrap(&err, "ReadStorageServiceConfig()") + + rows, err := s.queries.ReadDashboardSettingsWithNameLike(ctx, "storage_service_%") + if err != nil { + return ret, err + } + if len(rows) == 0 { + return ret, ErrNotFound + } + + for _, row := range rows { + switch row.Name { + case "storage_service_url": + ret.URL = row.Value + case "storage_service_user": + ret.Username = row.Value + case "storage_service_apikey": + ret.APIKey = row.Value } } diff --git a/hack/ccp/internal/store/sqlcmysql/db.go b/hack/ccp/internal/store/sqlcmysql/db.go index cabe4d164..5f4a481b8 100644 --- a/hack/ccp/internal/store/sqlcmysql/db.go +++ b/hack/ccp/internal/store/sqlcmysql/db.go @@ -54,6 +54,15 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.getLockStmt, err = db.PrepareContext(ctx, getLock); err != nil { return nil, fmt.Errorf("error preparing query GetLock: %w", err) } + if q.readDashboardSettingStmt, err = db.PrepareContext(ctx, readDashboardSetting); err != nil { + return nil, fmt.Errorf("error preparing query ReadDashboardSetting: %w", err) + } + if q.readDashboardSettingsWithNameLikeStmt, err = db.PrepareContext(ctx, readDashboardSettingsWithNameLike); err != nil { + return nil, fmt.Errorf("error preparing query ReadDashboardSettingsWithNameLike: %w", err) + } + if q.readDashboardSettingsWithScopeStmt, err = db.PrepareContext(ctx, readDashboardSettingsWithScope); err != nil { + return nil, fmt.Errorf("error preparing query ReadDashboardSettingsWithScope: %w", err) + } if q.readTransferLocationStmt, err = db.PrepareContext(ctx, readTransferLocation); err != nil { return nil, fmt.Errorf("error preparing query ReadTransferLocation: %w", err) } @@ -133,6 +142,21 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing getLockStmt: %w", cerr) } } + if q.readDashboardSettingStmt != nil { + if cerr := q.readDashboardSettingStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing readDashboardSettingStmt: %w", cerr) + } + } + if q.readDashboardSettingsWithNameLikeStmt != nil { + if cerr := q.readDashboardSettingsWithNameLikeStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing readDashboardSettingsWithNameLikeStmt: %w", cerr) + } + } + if q.readDashboardSettingsWithScopeStmt != nil { + if cerr := q.readDashboardSettingsWithScopeStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing readDashboardSettingsWithScopeStmt: %w", cerr) + } + } if q.readTransferLocationStmt != nil { if cerr := q.readTransferLocationStmt.Close(); cerr != nil { err = fmt.Errorf("error closing readTransferLocationStmt: %w", cerr) @@ -210,49 +234,55 @@ func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, ar } type Queries struct { - db DBTX - tx *sql.Tx - cleanUpActiveJobsStmt *sql.Stmt - cleanUpActiveSIPsStmt *sql.Stmt - cleanUpActiveTasksStmt *sql.Stmt - cleanUpActiveTransfersStmt *sql.Stmt - cleanUpAwaitingJobsStmt *sql.Stmt - cleanUpTasksWithAwaitingJobsStmt *sql.Stmt - createJobStmt *sql.Stmt - createTransferStmt *sql.Stmt - createUnitVarStmt *sql.Stmt - getLockStmt *sql.Stmt - readTransferLocationStmt *sql.Stmt - readTransferWithLocationStmt *sql.Stmt - readUnitVarStmt *sql.Stmt - readUnitVarsStmt *sql.Stmt - releaseLockStmt *sql.Stmt - updateJobStatusStmt *sql.Stmt - updateTransferLocationStmt *sql.Stmt - updateUnitVarStmt *sql.Stmt + db DBTX + tx *sql.Tx + cleanUpActiveJobsStmt *sql.Stmt + cleanUpActiveSIPsStmt *sql.Stmt + cleanUpActiveTasksStmt *sql.Stmt + cleanUpActiveTransfersStmt *sql.Stmt + cleanUpAwaitingJobsStmt *sql.Stmt + cleanUpTasksWithAwaitingJobsStmt *sql.Stmt + createJobStmt *sql.Stmt + createTransferStmt *sql.Stmt + createUnitVarStmt *sql.Stmt + getLockStmt *sql.Stmt + readDashboardSettingStmt *sql.Stmt + readDashboardSettingsWithNameLikeStmt *sql.Stmt + readDashboardSettingsWithScopeStmt *sql.Stmt + readTransferLocationStmt *sql.Stmt + readTransferWithLocationStmt *sql.Stmt + readUnitVarStmt *sql.Stmt + readUnitVarsStmt *sql.Stmt + releaseLockStmt *sql.Stmt + updateJobStatusStmt *sql.Stmt + updateTransferLocationStmt *sql.Stmt + updateUnitVarStmt *sql.Stmt } func (q *Queries) WithTx(tx *sql.Tx) *Queries { return &Queries{ - db: tx, - tx: tx, - cleanUpActiveJobsStmt: q.cleanUpActiveJobsStmt, - cleanUpActiveSIPsStmt: q.cleanUpActiveSIPsStmt, - cleanUpActiveTasksStmt: q.cleanUpActiveTasksStmt, - cleanUpActiveTransfersStmt: q.cleanUpActiveTransfersStmt, - cleanUpAwaitingJobsStmt: q.cleanUpAwaitingJobsStmt, - cleanUpTasksWithAwaitingJobsStmt: q.cleanUpTasksWithAwaitingJobsStmt, - createJobStmt: q.createJobStmt, - createTransferStmt: q.createTransferStmt, - createUnitVarStmt: q.createUnitVarStmt, - getLockStmt: q.getLockStmt, - readTransferLocationStmt: q.readTransferLocationStmt, - readTransferWithLocationStmt: q.readTransferWithLocationStmt, - readUnitVarStmt: q.readUnitVarStmt, - readUnitVarsStmt: q.readUnitVarsStmt, - releaseLockStmt: q.releaseLockStmt, - updateJobStatusStmt: q.updateJobStatusStmt, - updateTransferLocationStmt: q.updateTransferLocationStmt, - updateUnitVarStmt: q.updateUnitVarStmt, + db: tx, + tx: tx, + cleanUpActiveJobsStmt: q.cleanUpActiveJobsStmt, + cleanUpActiveSIPsStmt: q.cleanUpActiveSIPsStmt, + cleanUpActiveTasksStmt: q.cleanUpActiveTasksStmt, + cleanUpActiveTransfersStmt: q.cleanUpActiveTransfersStmt, + cleanUpAwaitingJobsStmt: q.cleanUpAwaitingJobsStmt, + cleanUpTasksWithAwaitingJobsStmt: q.cleanUpTasksWithAwaitingJobsStmt, + createJobStmt: q.createJobStmt, + createTransferStmt: q.createTransferStmt, + createUnitVarStmt: q.createUnitVarStmt, + getLockStmt: q.getLockStmt, + readDashboardSettingStmt: q.readDashboardSettingStmt, + readDashboardSettingsWithNameLikeStmt: q.readDashboardSettingsWithNameLikeStmt, + readDashboardSettingsWithScopeStmt: q.readDashboardSettingsWithScopeStmt, + readTransferLocationStmt: q.readTransferLocationStmt, + readTransferWithLocationStmt: q.readTransferWithLocationStmt, + readUnitVarStmt: q.readUnitVarStmt, + readUnitVarsStmt: q.readUnitVarsStmt, + releaseLockStmt: q.releaseLockStmt, + updateJobStatusStmt: q.updateJobStatusStmt, + updateTransferLocationStmt: q.updateTransferLocationStmt, + updateUnitVarStmt: q.updateUnitVarStmt, } } diff --git a/hack/ccp/internal/store/sqlcmysql/query.sql.go b/hack/ccp/internal/store/sqlcmysql/query.sql.go index a55501f40..256bd814d 100644 --- a/hack/ccp/internal/store/sqlcmysql/query.sql.go +++ b/hack/ccp/internal/store/sqlcmysql/query.sql.go @@ -162,6 +162,89 @@ func (q *Queries) GetLock(ctx context.Context) (interface{}, error) { return coalesce, err } +const readDashboardSetting = `-- name: ReadDashboardSetting :one +SELECT name, value, scope FROM DashboardSettings WHERE name = ? +` + +type ReadDashboardSettingRow struct { + Name string + Value string + Scope string +} + +func (q *Queries) ReadDashboardSetting(ctx context.Context, name string) (*ReadDashboardSettingRow, error) { + row := q.queryRow(ctx, q.readDashboardSettingStmt, readDashboardSetting, name) + var i ReadDashboardSettingRow + err := row.Scan(&i.Name, &i.Value, &i.Scope) + return &i, err +} + +const readDashboardSettingsWithNameLike = `-- name: ReadDashboardSettingsWithNameLike :many +SELECT name, value, scope FROM DashboardSettings WHERE name LIKE ? +` + +type ReadDashboardSettingsWithNameLikeRow struct { + Name string + Value string + Scope string +} + +func (q *Queries) ReadDashboardSettingsWithNameLike(ctx context.Context, name string) ([]*ReadDashboardSettingsWithNameLikeRow, error) { + rows, err := q.query(ctx, q.readDashboardSettingsWithNameLikeStmt, readDashboardSettingsWithNameLike, name) + if err != nil { + return nil, err + } + defer rows.Close() + items := []*ReadDashboardSettingsWithNameLikeRow{} + for rows.Next() { + var i ReadDashboardSettingsWithNameLikeRow + if err := rows.Scan(&i.Name, &i.Value, &i.Scope); err != nil { + return nil, err + } + items = append(items, &i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const readDashboardSettingsWithScope = `-- name: ReadDashboardSettingsWithScope :many +SELECT name, value, scope FROM DashboardSettings WHERE scope = ? +` + +type ReadDashboardSettingsWithScopeRow struct { + Name string + Value string + Scope string +} + +func (q *Queries) ReadDashboardSettingsWithScope(ctx context.Context, scope string) ([]*ReadDashboardSettingsWithScopeRow, error) { + rows, err := q.query(ctx, q.readDashboardSettingsWithScopeStmt, readDashboardSettingsWithScope, scope) + if err != nil { + return nil, err + } + defer rows.Close() + items := []*ReadDashboardSettingsWithScopeRow{} + for rows.Next() { + var i ReadDashboardSettingsWithScopeRow + if err := rows.Scan(&i.Name, &i.Value, &i.Scope); err != nil { + return nil, err + } + items = append(items, &i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const readTransferLocation = `-- name: ReadTransferLocation :one SELECT transferUUID, currentLocation FROM Transfers WHERE transferUUID = ? ` diff --git a/hack/ccp/internal/store/store.go b/hack/ccp/internal/store/store.go index 273b2a23f..ad9aae2de 100644 --- a/hack/ccp/internal/store/store.go +++ b/hack/ccp/internal/store/store.go @@ -61,6 +61,16 @@ type Store interface { // interface; rangefunc did work but it's not supported by linters yet. Files(ctx context.Context, id uuid.UUID, packageType enums.PackageType, filterFilenameEnd, filterSubdir, replacementPath string) ([]File, error) + // ReadPipelineID reads the identifier of this pipeline. + ReadPipelineID(ctx context.Context) (uuid.UUID, error) + + // ReadDict reads a dictionary given its name. + ReadDict(ctx context.Context, name string) (map[string]string, error) + + // ReadStorageServiceConfig reads the connection attributes of the + // Archivematica Storage Service associated to this pipeline. + ReadStorageServiceConfig(ctx context.Context) (StorageServiceConfig, error) + Running() bool Close() error } @@ -100,3 +110,9 @@ type File struct { OriginalLocation string `db:"originalLocation"` FileGrpUse string `db:"fileGrpUse"` } + +type StorageServiceConfig struct { + URL string + Username string + APIKey string +} diff --git a/hack/ccp/internal/workflow/config.go b/hack/ccp/internal/workflow/config.go index 0289688c6..089a8f71d 100644 --- a/hack/ccp/internal/workflow/config.go +++ b/hack/ccp/internal/workflow/config.go @@ -302,14 +302,14 @@ type Choice struct { } func (c Choice) LinkID() uuid.UUID { - if id, err := uuid.Parse(c.AppliesTo); err != nil { + if id, err := uuid.Parse(c.AppliesTo); err == nil { return id } return uuid.Nil } func (c Choice) ChainID() uuid.UUID { - if id, err := uuid.Parse(c.GoToChain); err != nil { + if id, err := uuid.Parse(c.GoToChain); err == nil { return id } return uuid.Nil diff --git a/hack/ccp/sqlc/mysql/query.sql b/hack/ccp/sqlc/mysql/query.sql index 87c064de5..acb20c284 100644 --- a/hack/ccp/sqlc/mysql/query.sql +++ b/hack/ccp/sqlc/mysql/query.sql @@ -72,3 +72,12 @@ SELECT COALESCE(GET_LOCK('lock', 0), 0); -- name: ReleaseLock :one SELECT RELEASE_LOCK('lock'); + +-- name: ReadDashboardSettingsWithScope :many +SELECT name, value, scope FROM DashboardSettings WHERE scope = ?; + +-- name: ReadDashboardSettingsWithNameLike :many +SELECT name, value, scope FROM DashboardSettings WHERE name LIKE ?; + +-- name: ReadDashboardSetting :one +SELECT name, value, scope FROM DashboardSettings WHERE name = ?;