Initial commit.

This commit is contained in:
2021-09-26 10:30:51 +05:00
commit 1b1c218bb6
13 changed files with 838 additions and 0 deletions

127
internal/tasks/base.go Normal file
View 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
View 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
View 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()
}
}