Skip to content

Commit

Permalink
Scheduled tasks commands
Browse files Browse the repository at this point in the history
  • Loading branch information
ipmb committed Dec 29, 2020
1 parent 0e336f9 commit 39ff458
Show file tree
Hide file tree
Showing 2 changed files with 246 additions and 0 deletions.
96 changes: 96 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import (
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/aws/aws-sdk-go/service/eventbridge"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/google/uuid"
"github.com/lincolnloop/apppack/auth"
"github.com/logrusorgru/aurora"
)
Expand Down Expand Up @@ -576,6 +578,100 @@ func (a *App) ScaleProcess(processType string, minProcessCount int, maxProcessCo
return nil
}

type ScheduledTask struct {
Schedule string `json:"schedule"`
Command string `json:"command"`
}

// ScheduledTasks lists scheduled tasks for the app
func (a *App) ScheduledTasks() ([]*ScheduledTask, error) {
ssmSvc := ssm.New(a.Session)
parameterName := fmt.Sprintf("/paaws/apps/%s/scheduled-tasks", a.Name)
parameterOutput, err := ssmSvc.GetParameter(&ssm.GetParameterInput{
Name: &parameterName,
})
var tasks []*ScheduledTask
if err != nil {
tasks = []*ScheduledTask{}
} else {
if err = json.Unmarshal([]byte(*parameterOutput.Parameter.Value), &tasks); err != nil {
return nil, err
}
}
return tasks, nil
}

// CreateScheduledTask adds a scheduled task for the app
func (a *App) CreateScheduledTask(schedule string, command string) ([]*ScheduledTask, error) {
if err := a.ValidateCronString(schedule); err != nil {
return nil, err
}
ssmSvc := ssm.New(a.Session)
tasks, err := a.ScheduledTasks()
if err != nil {
return nil, err
}
tasks = append(tasks, &ScheduledTask{
Schedule: schedule,
Command: command,
})
tasksBytes, err := json.Marshal(tasks)
if err != nil {
return nil, err
}
parameterName := fmt.Sprintf("/paaws/apps/%s/scheduled-tasks", a.Name)
_, err = ssmSvc.PutParameter(&ssm.PutParameterInput{
Name: &parameterName,
Value: aws.String(fmt.Sprintf("%s", tasksBytes)),
Overwrite: aws.Bool(true),
Type: aws.String("String"),
})
return tasks, nil
}

// DeleteScheduledTask deletes the scheduled task at the given index
func (a *App) DeleteScheduledTask(idx int) (*ScheduledTask, error) {
ssmSvc := ssm.New(a.Session)
tasks, err := a.ScheduledTasks()
if err != nil {
return nil, err
}
if idx > len(tasks) || idx < 0 {
return nil, fmt.Errorf("invalid index for task to delete")
}
taskToDelete := tasks[idx]
tasks = append(tasks[:idx], tasks[idx+1:]...)
tasksBytes, err := json.Marshal(tasks)
if err != nil {
return nil, err
}
parameterName := fmt.Sprintf("/paaws/apps/%s/scheduled-tasks", a.Name)
_, err = ssmSvc.PutParameter(&ssm.PutParameterInput{
Name: &parameterName,
Value: aws.String(fmt.Sprintf("%s", tasksBytes)),
Overwrite: aws.Bool(true),
Type: aws.String("String"),
})
return taskToDelete, nil
}

// ValidateCronString validates a cron schedule rule
func (a *App) ValidateCronString(rule string) error {
eventSvc := eventbridge.New(a.Session)
ruleName := fmt.Sprintf("apppack-validate-%s", uuid.New().String())
_, err := eventSvc.PutRule(&eventbridge.PutRuleInput{
Name: &ruleName,
ScheduleExpression: aws.String(fmt.Sprintf("cron(%s)", rule)),
State: aws.String("DISABLED"),
})
if err == nil {
eventSvc.DeleteRule(&eventbridge.DeleteRuleInput{
Name: &ruleName,
})
}
return err
}

// Init will pull in app settings from DyanmoDB and provide helper
func Init(name string) (*App, error) {
sess, err := auth.AwsSession(name)
Expand Down
150 changes: 150 additions & 0 deletions cmd/scheduledTasks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
Copyright © 2020 NAME HERE <EMAIL ADDRESS>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd

import (
"fmt"
"strconv"
"strings"

"github.com/AlecAivazis/survey/v2"
"github.com/lincolnloop/apppack/app"
"github.com/logrusorgru/aurora"
"github.com/spf13/cobra"
)

func printTasks(tasks []*app.ScheduledTask) {
if len(tasks) == 0 {
fmt.Printf("%s", aurora.Yellow(fmt.Sprintf("no scheduled tasks defined")))
return
} else {
fmt.Printf("%s\n", aurora.Faint("Min\tHr\tDayMon\tMon\tDayWk\tYr"))
}
for _, task := range tasks {
fmt.Printf("%s\t%s\n", aurora.Faint(strings.Join(strings.Split(task.Schedule, " "), "\t")), task.Command)
}
}

// scheduledTasksCmd represents the scheduledTasks command
var scheduledTasksCmd = &cobra.Command{
Use: "scheduled-tasks",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
startSpinner()
a, err := app.Init(AppName)
checkErr(err)
tasks, err := a.ScheduledTasks()
Spinner.Stop()
checkErr(err)
printTasks(tasks)
},
}

var schedule string

// scheduledTasksCreateCmd represents the create command
var scheduledTasksCreateCmd = &cobra.Command{
Use: "create --schedule \"...\" \"COMMAND\"",
Args: cobra.ExactArgs(1),
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
if len(strings.Split(schedule, " ")) != 6 {
checkErr(fmt.Errorf("schedule string should contain 6 space separated values\nhttps://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html#cron-expressions"))
}
command := strings.Join(args, " ")
startSpinner()
a, err := app.Init(AppName)
checkErr(err)
tasks, err := a.CreateScheduledTask(schedule, command)
Spinner.Stop()
checkErr(err)
printSuccess("task created")
printTasks(tasks)
},
}

// scheduledTasksCreateCmd represents the create command
var scheduledTasksDeleteCmd = &cobra.Command{
Use: "delete [INDEX]",
Args: cobra.MaximumNArgs(1),
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
startSpinner()
a, err := app.Init(AppName)
checkErr(err)
var idx int
var task *app.ScheduledTask
if len(args) > 0 {
idx, err = strconv.Atoi(args[0])
checkErr(err)
idx--
} else {
tasks, err := a.ScheduledTasks()
checkErr(err)
taskList := []string{}
for _, t := range tasks {
taskList = append(taskList, fmt.Sprintf("%s %s", t.Schedule, t.Command))
}
questions := []*survey.Question{{
Name: "task",
Prompt: &survey.Select{
Message: "Scheduled task to delete:",
Options: taskList,
FilterMessage: "",
},
}}
answers := make(map[string]int)
Spinner.Stop()
if err := survey.Ask(questions, &answers); err != nil {
checkErr(err)
}
idx = answers["task"]
}
task, err = a.DeleteScheduledTask(idx)
checkErr(err)
printSuccess("scheduled task deleted:")
fmt.Printf(" %s %s\n", aurora.Faint(task.Schedule), task.Command)
},
}

func init() {
rootCmd.AddCommand(scheduledTasksCmd)
scheduledTasksCmd.PersistentFlags().StringVarP(&AppName, "app-name", "a", "", "App name (required)")
scheduledTasksCmd.MarkPersistentFlagRequired("app-name")

scheduledTasksCmd.AddCommand(scheduledTasksCreateCmd)
scheduledTasksCreateCmd.Flags().StringVarP(&schedule, "schedule", "s", "", "Cron-like schedule. See https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html#cron-expressions")

scheduledTasksCmd.AddCommand(scheduledTasksDeleteCmd)
}

0 comments on commit 39ff458

Please sign in to comment.