Initial commit.
This commit is contained in:
127
internal/tasks/base.go
Normal file
127
internal/tasks/base.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/robfig/cron"
|
||||
g "github.com/xanzy/go-gitlab"
|
||||
"go.dev.pztrn.name/periodicator/internal/gitlab"
|
||||
)
|
||||
|
||||
// BaseTask is a base task structure.
|
||||
type BaseTask struct {
|
||||
client *gitlab.Client
|
||||
|
||||
projectID int
|
||||
title string
|
||||
body string
|
||||
tags []string
|
||||
|
||||
executionStartTimestamp time.Time
|
||||
cron string
|
||||
dueIn time.Duration
|
||||
}
|
||||
|
||||
func (b *BaseTask) checkIfOpenedTaskExists(issues []*g.Issue) bool {
|
||||
b.log("Checking if opened task already exists...")
|
||||
|
||||
var foundAndNotClosed bool
|
||||
|
||||
for _, issue := range issues {
|
||||
if issue.Title == b.title && issue.ClosedAt == nil {
|
||||
foundAndNotClosed = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return foundAndNotClosed
|
||||
}
|
||||
|
||||
func (b *BaseTask) log(message string) {
|
||||
log.Println("Task '" + b.title + "': " + message)
|
||||
}
|
||||
|
||||
// Run executes task procedure.
|
||||
func (b *BaseTask) Run() {
|
||||
// Get similar tasks.
|
||||
issues, err := b.client.GetIssuesByTitle(b.projectID, b.title)
|
||||
if err != nil {
|
||||
b.log("Error while getting issues from Gitlab: " + err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Check if we have opened task. We should not create another task until already
|
||||
// created task is closed.
|
||||
|
||||
if b.checkIfOpenedTaskExists(issues) {
|
||||
b.log("Found already existing task that isn't closed, won't create new task")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
b.log("No still opened tasks found, checking if we should create new one...")
|
||||
|
||||
// Get latest task creation timestamp from Gitlab.
|
||||
lastTaskCreationTS := b.executionStartTimestamp
|
||||
|
||||
for _, issue := range issues {
|
||||
if issue.ClosedAt != nil && issue.CreatedAt.After(lastTaskCreationTS) {
|
||||
lastTaskCreationTS = *issue.CreatedAt
|
||||
}
|
||||
}
|
||||
|
||||
b.log("Last task creation timestamp: " + lastTaskCreationTS.String())
|
||||
|
||||
// Set up cron job parser.
|
||||
cp := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
|
||||
|
||||
schedule, err := cp.Parse(b.cron)
|
||||
if err != nil {
|
||||
b.log("Failed to parse cron string: " + err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Figure out next task creation and deadline timestamps.
|
||||
nextCreationTS := schedule.Next(lastTaskCreationTS)
|
||||
|
||||
// Check if task should be created and create if so.
|
||||
if nextCreationTS.Before(time.Now()) {
|
||||
// Deadlines should be calculated until first one will appear AFTER today.
|
||||
nextDeadlineTSAssumed := nextCreationTS.Add(b.dueIn)
|
||||
|
||||
var nextDeadlineTS g.ISOTime
|
||||
|
||||
for {
|
||||
if nextDeadlineTSAssumed.After(time.Now()) {
|
||||
nextDeadlineTS = g.ISOTime(nextDeadlineTSAssumed)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
nextDeadlineTSAssumed = nextDeadlineTSAssumed.Add(b.dueIn)
|
||||
}
|
||||
|
||||
b.log("Found no opened tasks and task should be created, doing so. Task deadline: " + nextDeadlineTS.String())
|
||||
|
||||
// nolint:exhaustivestruct
|
||||
err := b.client.CreateIssue(b.projectID, &g.CreateIssueOptions{
|
||||
Title: &b.title,
|
||||
Description: &b.body,
|
||||
Labels: b.tags,
|
||||
DueDate: &nextDeadlineTS,
|
||||
})
|
||||
if err != nil {
|
||||
b.log("Failed to create task: " + err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
} else {
|
||||
b.log("Next task creation in future (" + nextCreationTS.String() + "), skipping for now")
|
||||
|
||||
return
|
||||
}
|
||||
}
|
43
internal/tasks/config.go
Normal file
43
internal/tasks/config.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package tasks
|
||||
|
||||
import "time"
|
||||
|
||||
// Config is a task's configuration as should be defined in configuration file.
|
||||
// nolint:tagliatelle
|
||||
type Config struct {
|
||||
ProjectID int `yaml:"project_id"`
|
||||
Title string `yaml:"title"`
|
||||
Body string `yaml:"body"`
|
||||
Tags []string `yaml:"tags"`
|
||||
ExecutionStart TaskStartTime `yaml:"execution_start"`
|
||||
Cron string `yaml:"cron"`
|
||||
DueIn time.Duration `yaml:"due_in"`
|
||||
}
|
||||
|
||||
// TaskStartTime holds task's start time for next creation timestamp calculation.
|
||||
type TaskStartTime struct {
|
||||
ts time.Time
|
||||
}
|
||||
|
||||
func (tts *TaskStartTime) GetTimestamp() time.Time {
|
||||
return tts.ts
|
||||
}
|
||||
|
||||
func (tts *TaskStartTime) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var timeData string
|
||||
|
||||
if err := unmarshal(&timeData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t, err := time.Parse("2006-01-02 15:04:05", timeData)
|
||||
if err != nil {
|
||||
// ToDo: fix it!
|
||||
// nolint:wrapcheck
|
||||
return err
|
||||
}
|
||||
|
||||
tts.ts = t
|
||||
|
||||
return nil
|
||||
}
|
23
internal/tasks/tasks.go
Normal file
23
internal/tasks/tasks.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"go.dev.pztrn.name/periodicator/internal/gitlab"
|
||||
)
|
||||
|
||||
// Process processes passed tasks.
|
||||
func Process(client *gitlab.Client, tasks []Config) {
|
||||
for _, task := range tasks {
|
||||
t := &BaseTask{
|
||||
client: client,
|
||||
projectID: task.ProjectID,
|
||||
title: task.Title,
|
||||
body: task.Body,
|
||||
tags: task.Tags,
|
||||
executionStartTimestamp: task.ExecutionStart.GetTimestamp(),
|
||||
cron: task.Cron,
|
||||
dueIn: task.DueIn,
|
||||
}
|
||||
|
||||
t.Run()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user