Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added more configuration capabilities #14

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions cmd/pagerduty-slack-sync/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ func main() {
stop := make(chan os.Signal, 1) // Buffer size of 1
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)

config, err := sync.NewConfigFromEnv()
config, err := getConfig()

if err != nil {
logrus.Errorf("could not parse config, error: %v", err)
os.Exit(-1)
return
}

logrus.Infof("starting, going to sync %d schedules", len(config.Schedules))
logrus.Infof("starting, going to sync %d schedules every %d seconds.", len(config.Schedules), config.RunIntervalInSeconds)

timer := time.NewTicker(time.Second * time.Duration(config.RunIntervalInSeconds))

Expand All @@ -43,3 +44,14 @@ func main() {
}
}
}

func getConfig() (*sync.Config, error) {
configYamlFilePath := os.Getenv("CONFIG_YAML_FILE_PATH")
if configYamlFilePath != "" {
logrus.Infof("Getting configuration from yaml config %s", configYamlFilePath)
return sync.NewConfigFromYaml(configYamlFilePath)
} else {
logrus.Infof("Getting configuration from environment variables")
return sync.NewConfigFromEnv()
}
}
48 changes: 29 additions & 19 deletions internal/sync/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package sync
import (
"fmt"
"os"
"strconv"
"strings"
"time"
)
Expand All @@ -14,7 +13,15 @@ const (
slackTokenKey = "SLACK_TOKEN"
runInterval = "RUN_INTERVAL_SECONDS"
pdScheduleLookaheadKey = "PAGERDUTY_SCHEDULE_LOOKAHEAD"
runIntervalDefault = 60
currentOnCallFormat = "USER_GROUP_CURRENT_ON_CALL_FORMAT"
currentOnCallEnabled = "USER_GROUP_CURRENT_ON_CALL_ENABLED"
allOnCallFormat = "USER_GROUP_ALL_ON_CALL_FORMAT"
allOnCallEnabled = "USER_GROUP_ALL_ON_CALL_ENABLED"

pagerDutyLookAheadDefault = time.Hour * 24 * 100
runIntervalDefault = 60
currentOnCallFormatDefault = "current-oncall-%s"
allOnCallFormatDefault = "all-oncall-%ss"
)

// Config is used to configure application
Expand All @@ -26,6 +33,8 @@ type Config struct {
SlackToken string
RunIntervalInSeconds int
PagerdutyScheduleLookahead time.Duration
CurrentOnCallEnabled bool
AllOnCallEnabled bool
}

// Schedule models a PagerDuty schedule that will be synced with Slack
Expand All @@ -47,21 +56,21 @@ func NewConfigFromEnv() (*Config, error) {
config := &Config{
PagerDutyToken: os.Getenv(pagerDutyTokenKey),
SlackToken: os.Getenv(slackTokenKey),
RunIntervalInSeconds: runIntervalDefault,
RunIntervalInSeconds: GetEnvInt(runInterval, runIntervalDefault),
CurrentOnCallEnabled: GetEnvBool(currentOnCallEnabled, true),
AllOnCallEnabled: GetEnvBool(allOnCallEnabled, true),
}

runInterval := os.Getenv(runInterval)
v, err := strconv.Atoi(runInterval)
if err == nil {
config.RunIntervalInSeconds = v
}

pagerdutyScheduleLookahead, err := getPagerdutyScheduleLookahead()
pagerdutyScheduleLookahead, err := GetPagerdutyScheduleLookahead()
if err != nil {
return nil, err
}

config.PagerdutyScheduleLookahead = pagerdutyScheduleLookahead

currentOnCallNameFormat := GetEnvStr(currentOnCallFormat, currentOnCallFormatDefault)
allOnCallNameFormat := GetEnvStr(allOnCallFormat, allOnCallFormatDefault)

for _, key := range os.Environ() {
if strings.HasPrefix(key, scheduleKeyPrefix) {
value := strings.Split(key, "=")[1]
Expand All @@ -70,7 +79,7 @@ func NewConfigFromEnv() (*Config, error) {
return nil, fmt.Errorf("expecting schedule value to be a comma separated scheduleId,name but got %s", value)
}

config.Schedules = appendSchedule(config.Schedules, scheduleValues[0], scheduleValues[1])
config.Schedules = appendSchedule(config, scheduleValues[0], scheduleValues[1], currentOnCallNameFormat, allOnCallNameFormat)
}
}

Expand All @@ -81,13 +90,15 @@ func NewConfigFromEnv() (*Config, error) {
return config, nil
}

func appendSchedule(schedules []Schedule, scheduleID, teamName string) []Schedule {
currentGroupName := fmt.Sprintf("current-oncall-%s", teamName)
allGroupName := fmt.Sprintf("all-oncall-%ss", teamName)
newScheduleList := make([]Schedule, len(schedules))
func appendSchedule(config *Config, scheduleID, teamName string, currentOnCallNameFormat string, allOnCallNameFormat string) []Schedule {

currentGroupName := fmt.Sprintf(currentOnCallNameFormat, teamName)
allGroupName := fmt.Sprintf(allOnCallNameFormat, teamName)

newScheduleList := make([]Schedule, len(config.Schedules))
updated := false

for i, s := range schedules {
for i, s := range config.Schedules {
if s.CurrentOnCallGroupName != currentGroupName {
newScheduleList[i] = s

Expand All @@ -114,12 +125,11 @@ func appendSchedule(schedules []Schedule, scheduleID, teamName string) []Schedul
return newScheduleList
}

func getPagerdutyScheduleLookahead() (time.Duration, error) {
result := time.Hour * 24 * 100
func GetPagerdutyScheduleLookahead() (time.Duration, error) {

pdScheduleLookahead, ok := os.LookupEnv(pdScheduleLookaheadKey)
if !ok {
return result, nil
return pagerDutyLookAheadDefault, nil
}

v, err := time.ParseDuration(pdScheduleLookahead)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,33 @@ func Test_NewConfigFromEnv_SingleScheduleDefined(t *testing.T) {
config.Schedules))
}

func Test_NewConfigFromEnv_FormatsChanged(t *testing.T) {
defer SetEnv("SCHEDULE_PLATFORM", "1234,platform-engineer")()
defer SetEnv("PAGERDUTY_TOKEN", "token1")()
defer SetEnv("SLACK_TOKEN", "secretToken1")()
defer SetEnv("RUN_INTERVAL_SECONDS", "10")()
defer SetEnv("USER_GROUP_CURRENT_ON_CALL_FORMAT", "pd-%s")()
defer SetEnv("USER_GROUP_ALL_ON_CALL_FORMAT", "all-%ss")()

config, err := NewConfigFromEnv()

assert.NoError(t, err)
assert.Equal(t, "token1", config.PagerDutyToken)
assert.Equal(t, "secretToken1", config.SlackToken)
assert.Equal(t, 10, config.RunIntervalInSeconds)
assert.Equal(t, time.Hour*24*100, config.PagerdutyScheduleLookahead)
assert.Equal(t, 1, len(config.Schedules))
assert.Equal(t, "all-platform-engineers", config.Schedules[0].AllOnCallGroupName)
assert.Equal(t, "pd-platform-engineer", config.Schedules[0].CurrentOnCallGroupName)

assert.True(t, assert.ObjectsAreEqualValues([]Schedule{{
ScheduleIDs: []string{"1234"},
AllOnCallGroupName: "all-platform-engineers",
CurrentOnCallGroupName: "pd-platform-engineer",
}},
config.Schedules))
}

func Test_NewConfigFromEnv_SingleScheduleDefinedWithDefaultRunInterval(t *testing.T) {
defer SetEnv("SCHEDULE_PLATFORM", "1234,platform-engineer")()
defer SetEnv("PAGERDUTY_TOKEN", "token1")()
Expand Down
40 changes: 40 additions & 0 deletions internal/sync/config_from_yaml_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package sync

import (
"github.com/stretchr/testify/assert"
"testing"
"time"
)

const (
filePath = "example.yaml"
)

func Test_NewConfigFromYaml_SingleScheduleDefined(t *testing.T) {
config, err := NewConfigFromYaml(filePath)

assert.NoError(t, err)
assert.Equal(t, "token1", config.PagerDutyToken)
assert.Equal(t, "secretToken1", config.SlackToken)
assert.Equal(t, 600, config.RunIntervalInSeconds)
assert.Equal(t, time.Hour*24, config.PagerdutyScheduleLookahead)
assert.Equal(t, 2, len(config.Schedules))
assert.Equal(t, true, config.CurrentOnCallEnabled)
assert.Equal(t, false, config.AllOnCallEnabled)

firstSchedule := config.Schedules[0]
assert.Equal(t, "all-oncall-platform-engineers", firstSchedule.AllOnCallGroupName)
assert.Equal(t, "current-oncall-platform-engineer", firstSchedule.CurrentOnCallGroupName)

assert.True(t, assert.ObjectsAreEqualValues([]Schedule{
{
ScheduleIDs: []string{"1234"},
AllOnCallGroupName: "all-oncall-platform-engineers",
CurrentOnCallGroupName: "current-oncall-platform-engineer",
}, {
ScheduleIDs: []string{"123", "12345"},
AllOnCallGroupName: "all-company-wide",
CurrentOnCallGroupName: "pd-company-wide",
}},
config.Schedules))
}
98 changes: 98 additions & 0 deletions internal/sync/config_yaml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package sync

import (
"fmt"
"gopkg.in/yaml.v3"
"log"
"os"
"time"
)

type ConfigYaml struct {
RunInterval int `yaml:"run-interval"`
PagerDutyLookAheadDuration time.Duration `yaml:"pager-duty-look-ahead-duration"`
PagerDutyToken string `yaml:"pager-duty-token"`
SlackToken string `yaml:"slack-token"`
CurrentOnCallEnabled bool `yaml:"current-on-call-enabled"`
CurrentOnCallNameFormat string `yaml:"current-on-call-name-format"`
AllOnCallEnabled bool `yaml:"all-on-call-enabled"`
AllOnCallNameFormat string `yaml:"all-on-call-name-format"`
UserGroups []UserGroup `yaml:"user-groups"`
}

type UserGroup struct {
Name string `yaml:"name"`
ScheduleIDs []string `yaml:"schedules"`
CurrentOnCallEnabled bool `yaml:"current-on-call-enabled"`
CurrentOnCallNameFormat string `yaml:"current-on-call-name-format"`
AllOnCallEnabled bool `yaml:"all-on-call-enabled"`
AllOnCallNameFormat string `yaml:"all-on-call-name-format"`
}

func NewConfigFromYaml(filePath string) (*Config, error) {
fmt.Printf("Reading YAML file %s\n", filePath)
data, err := os.ReadFile(filePath)
if err != nil {
log.Fatalf("Error reading YAML file: %v", err)
}

var configYaml ConfigYaml
err = yaml.Unmarshal(data, &configYaml)
if err != nil {
log.Fatalf("Error unmarshaling YAML: %v", err)
}

if len(configYaml.UserGroups) == 0 {
return nil, fmt.Errorf("expecting at least one user-groups defined in YAML file")
}

defaultCurrentOnCallNameFormat := getOrDefault(configYaml.CurrentOnCallNameFormat, currentOnCallFormatDefault)
defaultAllOnCallNameFormat := getOrDefault(configYaml.AllOnCallNameFormat, allOnCallFormatDefault)

scheduleList := make([]Schedule, len(configYaml.UserGroups))

fmt.Printf("Creating %d user-groups.\n", len(scheduleList))
for i, s := range configYaml.UserGroups {
fmt.Printf("Registering user-group %s.\n", s.Name)

currentGroupName := fmt.Sprintf(getOrDefault(s.CurrentOnCallNameFormat, defaultCurrentOnCallNameFormat), s.Name)
allGroupName := fmt.Sprintf(getOrDefault(s.AllOnCallNameFormat, defaultAllOnCallNameFormat), s.Name)

fmt.Printf("Registering schedules(%q) for user-groups(%s, %s).\n", s.ScheduleIDs, currentGroupName, allGroupName)
scheduleList[i] = Schedule{
ScheduleIDs: s.ScheduleIDs,
CurrentOnCallGroupName: currentGroupName,
AllOnCallGroupName: allGroupName,
}

}

runIntervalInSeconds := configYaml.RunInterval
if runIntervalInSeconds == 0 {
runIntervalInSeconds = runIntervalDefault
}

pagerdutyScheduleLookahead := configYaml.PagerDutyLookAheadDuration
if pagerdutyScheduleLookahead == 0 {
pagerdutyScheduleLookahead = pagerDutyLookAheadDefault
}

config := &Config{
Schedules: scheduleList,
PagerDutyToken: configYaml.PagerDutyToken,
SlackToken: configYaml.SlackToken,
PagerdutyScheduleLookahead: pagerdutyScheduleLookahead,
RunIntervalInSeconds: runIntervalInSeconds,
CurrentOnCallEnabled: configYaml.CurrentOnCallEnabled,
AllOnCallEnabled: configYaml.AllOnCallEnabled,
}

return config, nil
}

func getOrDefault(format string, formatDefault string) string {
if format == "" {
return formatDefault
}
return format
}
40 changes: 40 additions & 0 deletions internal/sync/defaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package sync

import (
"os"
"strconv"
)

func GetEnvStr(key string, d string) string {
v := os.Getenv(key)
if v == "" {
return d
}
return v
}

func GetEnvInt(key string, d int) int {
s, ok := os.LookupEnv(key)
if !ok {
return d
}

v, err := strconv.Atoi(s)
if err != nil {
return d
}
return v
}

func GetEnvBool(key string, d bool) bool {
s, ok := os.LookupEnv(key)
if !ok {
return d
}

v, err := strconv.ParseBool(s)
if err != nil {
return d
}
return v
}
18 changes: 18 additions & 0 deletions internal/sync/example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
run-interval: 600
pager-duty-look-ahead-duration: 24h
pager-duty-token: "token1"
slack-token: "secretToken1"
current-on-call-enabled: true
all-on-call-enabled: false
user-groups:
- user-group:
name: platform-engineer
schedules:
- 1234
- user-group:
name: company-wide
current-on-call-name-format: "pd-%s"
all-on-call-name-format: "all-%s"
schedules:
- 123
- 12345
Loading