Commit 0a5c757b authored by Victor Lopez's avatar Victor Lopez

Merge branch 'pc/refactor-time' into 'master'

Refactor time!

See merge request gl-infra/oncall-robot-assistant!4
parents 8278bff3 a4c73332
Pipeline #13602781 passed with stage
in 53 seconds
image: golang:1.9
before_script:
- export GOPATH=/builds/go
- export REPONAME=$(basename $CI_PROJECT_DIR)
- export GROUPNAME=$(basename $(dirname $CI_PROJECT_DIR))
- export PATH=$PATH:$GOPATH/bin
- mkdir -p $GOPATH/src/gitlab.com/$GROUPNAME
- cd $GOPATH/src/gitlab.com/$GROUPNAME
- ln -s $CI_PROJECT_DIR
- cd $CI_PROJECT_NAME
- pwd
- go get -u github.com/golang/dep/cmd/dep
- dep ensure
build:
script:
- go test ./... -v -cover
- go build
artifacts:
paths:
- gitlab-patcher
expire_in: 1 week
package main
import (
"testing"
)
func TestReadConfig(t *testing.T) {
filename := "invalid-cfg.json"
_, err := readConfig(filename)
if err == nil {
t.Fatalf("could not read configuration file %s", filename)
}
want := "stat invalid-cfg.json: no such file or directory"
got := err.Error()
if got != want {
t.Fatalf("wrong configuration error, got %s; expected %s", got, want)
}
}
......@@ -3,28 +3,30 @@ package main
import (
"flag"
"fmt"
"github.com/xanzy/go-gitlab"
"log"
"time"
"github.com/xanzy/go-gitlab"
"gitlab.com/gl-infra/oncall-robot-assistant/oncall"
)
func main() {
cfgFile := flag.String("config", "./.settings.json", "the configuration file")
flag.Parse()
config, err := readConfig(*cfgFile)
config, err := oncall.ReadConfig(*cfgFile)
if err != nil {
log.Fatalln(err)
}
// get current primary
primaryNow, err := config.getOncallPerson("primary", "current")
primaryNow, err := oncall.GetOncallPerson(config, "primary", "current")
if err != nil {
log.Fatalln(err)
}
// get current secondary
secondaryNow, err := config.getOncallPerson("secondary", "current")
secondaryNow, err := oncall.GetOncallPerson(config, "secondary", "current")
if err != nil {
log.Fatalln(err)
}
......@@ -33,13 +35,13 @@ func main() {
fmt.Printf("sec oncall now: %s\n", secondaryNow)
// get next primary
primaryNext, err := config.getOncallPerson("primary", "next")
primaryNext, err := oncall.GetOncallPerson(config, "primary", "next")
if err != nil {
log.Fatalln(err)
}
// get next secondary
secondaryNext, err := config.getOncallPerson("secondary", "next")
secondaryNext, err := oncall.GetOncallPerson(config, "secondary", "next")
if err != nil {
log.Fatalln(err)
}
......
package main
package oncall
import (
"encoding/json"
"errors"
"fmt"
"github.com/PagerDuty/go-pagerduty"
"io"
"io/ioutil"
"os"
"strconv"
"time"
"gopkg.in/yaml.v2"
)
// PagerDutyConfig keeps the configuration for the pagerduty service
type PagerDutyConfig struct {
Token string `yaml:"token"`
Schedules []string `yaml:"schedules"`
PrimarySchedule string `yaml:"primary"`
SecondarySchedule string `yaml:"secondary"`
}
// RealConfig the real deal
type RealConfig struct {
PagerDuty PagerDutyConfig `yaml:"pagerduty"`
GitLabToken string `yaml:"gitlab-token"`
}
// Config : map the json fields on the config file
type Config struct {
GitLabToken string `json:"gitlab_token"`
......@@ -22,9 +36,23 @@ type Config struct {
OncallEndTime string `json:"oncall_end_time"`
}
// Parses the yaml configuration from a reader
func ParseConfig(r io.Reader) (RealConfig, error) {
configBytes, err := ioutil.ReadAll(r)
config := RealConfig{}
if err != nil {
return config, fmt.Errorf("could not read configuration: %s", err)
}
err = yaml.Unmarshal(configBytes, &config)
if err != nil {
return config, fmt.Errorf("could not parse configuration yaml: %s", err)
}
return config, nil
}
// readConfig will attempt to read the different configuration
// parameters from a JSON formatted file.
func readConfig(f string) (*Config, error) {
func ReadConfig(f string) (*Config, error) {
if _, err := os.Stat(f); os.IsNotExist(err) {
return nil, errors.New(err.Error())
}
......@@ -36,60 +64,3 @@ func readConfig(f string) (*Config, error) {
json.Unmarshal(content, &cfg)
return &cfg, nil
}
// getOncallDates return formatted dates for the current or future oncall shift
func (c Config) getOncallDates(offset int) (start string, end string) {
// get current time
t := time.Now()
// get month
year := strconv.Itoa(t.Year())
var month string
if t.Month() < 10 {
month = "0" + strconv.Itoa(int(t.Month()))
} else {
month = strconv.Itoa(int(t.Month()))
}
// get day (considering offset too)
var day string
if (t.Day() + offset) < 10 {
day = "0" + strconv.Itoa(t.Day()+offset)
} else {
day = strconv.Itoa(t.Day() + offset)
}
// return formatted start/end dates
start = fmt.Sprintf("%s-%s-%sT%sZ", year, month, day, c.OncallStartTime)
end = fmt.Sprintf("%s-%s-%sT%sZ", year, month, day, c.OncallEndTime)
return start, end
}
// getOncall
func (c Config) getOncallPerson(shift string, period string) (string, error) {
// if we ask for current oncall period, there is no offset to apply.
// otherwise offset = 1 because we will run this the day before
// the rotation changes.
var offset int
if period == "current" {
offset = 0
} else {
offset = 5
}
// switch oncall shift type
switch shift {
case "primary":
shift = c.OncallPriID
case "secondary":
shift = c.OncallSecID
}
var options pagerduty.ListOnCallUsersOptions
options.Since, options.Until = c.getOncallDates(offset)
client := pagerduty.NewClient(c.PDToken)
oncall, err := client.ListOnCallUsers(shift, options)
if err != nil {
return "", err
}
return oncall[0].Name, nil
}
package oncall
import (
"fmt"
"os"
"reflect"
"strings"
"testing"
)
func TestReadConfig(t *testing.T) {
filename := "invalid-cfg.json"
_, err := ReadConfig(filename)
if err == nil {
t.Fatalf("could not read configuration file %s", filename)
}
want := "stat invalid-cfg.json: no such file or directory"
got := err.Error()
if got != want {
t.Fatalf("wrong configuration error, got %s; expected %s", got, want)
}
}
func TestParseConfigWrong(t *testing.T) {
_, err := ParseConfig(strings.NewReader("invalid yaml"))
expected := "could not parse configuration yaml: yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `invalid...` into oncall.RealConfig"
if fmt.Sprintf("%s", err) != expected {
t.Fatalf("unexpected error, got %s", err)
}
}
func TestParseConfigCorrectly(t *testing.T) {
f, err := os.Open("../test-fixtures/settings.yaml")
defer f.Close()
if err != nil {
t.Fatalf("failed to open the configuration file: %s", err)
}
c, err := ParseConfig(f)
if err != nil {
t.Fatalf("failed to parse the configuration: %s", err)
}
assertEquals(t, "gitlab token", "ZZXX", c.GitLabToken)
assertEquals(t, "pagerduty token", "XXYY", c.PagerDuty.Token)
assertEquals(t, "schedules", []string{"PrimaryID", "SecondaryID"}, c.PagerDuty.Schedules)
}
func assertEquals(t *testing.T, name string, expected, actual interface{}) {
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("%s is not as expected: %+v; got %+v", name, expected, actual)
}
}
package oncall
import (
"fmt"
"time"
pagerduty "github.com/PagerDuty/go-pagerduty"
)
// PagerDuty helper
type PagerDuty struct {
token string
}
// NewPagerDuty returns a new PagerDuty object
func NewPagerDuty(token string) PagerDuty {
return PagerDuty{
token: token,
}
}
// ListOncallPeople returns a list of the people that is going to be oncall for the next X days
func (pd PagerDuty) ListOncallPeople(ScheduleID string, days int) ([]pagerduty.OnCall, error) {
client := pagerduty.NewClient(pd.token)
resp, err := client.ListOnCalls(pagerduty.ListOnCallOptions{
ScheduleIDs: []string{ScheduleID},
Since: fmt.Sprintf("%sT04:00:00Z", time.Now().Add(-24*time.Duration(days)*time.Hour).Format("2006-01-02")),
Until: fmt.Sprintf("%sT16:00:00Z", time.Now().Add(24*time.Duration(days)*time.Hour).Format("2006-01-02")),
})
if err != nil {
return nil, fmt.Errorf("could not list oncall people for the next week: %s", err)
}
return resp.OnCalls, nil
}
// GetOncallPerson returns the person who happens to be on call
func GetOncallPerson(c *Config, shift string, period string) (string, error) {
// if we ask for current oncall period, there is no offset to apply.
// otherwise offset = 1 because we will run this the day before
// the rotation changes.
var offset int
if period == "current" {
offset = 0
} else {
offset = 5
}
// switch oncall shift type
switch shift {
case "primary":
shift = c.OncallPriID
case "secondary":
shift = c.OncallSecID
}
var options pagerduty.ListOnCallUsersOptions
options.Since, options.Until = getOncallDates(c, offset)
client := pagerduty.NewClient(c.PDToken)
oncall, err := client.ListOnCallUsers(shift, options)
if err != nil {
return "", err
}
return oncall[0].Name, nil
}
// getOncallDates returns formatted dates as string for the current or future oncall shift
func getOncallDates(c *Config, offset int) (start string, end string) {
start = fmt.Sprintf("%sT%sZ", time.Now().Add(24*time.Duration(offset)*time.Hour).Format("2006-01-02"), c.OncallStartTime)
end = fmt.Sprintf("%sT%sZ", time.Now().Add(24*time.Duration(offset)*time.Hour).Format("2006-01-02"), c.OncallEndTime)
return
}
package oncall
import (
"fmt"
"os"
"testing"
"time"
)
func TestOncallDates(t *testing.T) {
c := &Config{
OncallStartTime: "04:00:00",
OncallEndTime: "16:00:00",
}
tt := []struct {
offset int
start string
end string
}{
{
0,
fmt.Sprintf("%sT%sZ", time.Now().Format("2006-01-02"), c.OncallStartTime),
fmt.Sprintf("%sT%sZ", time.Now().Format("2006-01-02"), c.OncallEndTime),
},
{
1,
fmt.Sprintf("%sT%sZ", time.Now().Add(24*time.Hour).Format("2006-01-02"), c.OncallStartTime),
fmt.Sprintf("%sT%sZ", time.Now().Add(24*time.Hour).Format("2006-01-02"), c.OncallEndTime),
},
{
7,
fmt.Sprintf("%sT%sZ", time.Now().Add(24*7*time.Hour).Format("2006-01-02"), c.OncallStartTime),
fmt.Sprintf("%sT%sZ", time.Now().Add(24*7*time.Hour).Format("2006-01-02"), c.OncallEndTime),
},
}
for _, tc := range tt {
t.Run(fmt.Sprintf("With %d offset", tc.offset), func(t *testing.T) {
start, end := getOncallDates(c, tc.offset)
if tc.start != start {
t.Fatalf("incorrect start, expected %s; got %s", tc.start, start)
}
if tc.end != end {
t.Fatalf("incorrect end, expected %s; got %s", tc.end, end)
}
})
}
}
func TestPagerDutyStuff(t *testing.T) {
pd := NewPagerDuty(os.Getenv("PD_API_KEY"))
people, err := pd.ListOncallPeople(os.Getenv("PD_PRIMARY_SCHEDULE"), 7)
if err != nil {
t.Fatalf("could not list the oncall people: %s", err)
}
for _, o := range people {
fmt.Printf("On Call from %s to %s for %s: %s\n", o.Start, o.End, o.Schedule.Summary, o.User.Summary)
}
}
---
pagerduty:
token: XXYY
schedules:
- PrimaryID
- SecondaryID
gitlab-token: ZZXX
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment