From bbcb75598983fbba69ddb4798e2d7f3ad87696dd Mon Sep 17 00:00:00 2001 From: Dinushi Dhananjani Date: Thu, 2 Jul 2026 20:45:06 +0530 Subject: [PATCH 1/3] feat: extend init command with interactive wizard via survey --- go.mod | 7 +++ go.sum | 46 +++++++++++++++++ internal/cli/init.go | 120 +++++++++++++++++++++++++++++++++++-------- 3 files changed, 152 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 04af3c6..2fdf742 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,16 @@ go 1.24 require gopkg.in/yaml.v3 v3.0.1 require ( + github.com/AlecAivazis/survey/v2 v2.3.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/mattn/go-colorable v0.1.2 // indirect + github.com/mattn/go-isatty v0.0.8 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.9 // indirect go.etcd.io/bbolt v1.4.3 // indirect golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/text v0.4.0 // indirect ) diff --git a/go.sum b/go.sum index 783edee..37f1661 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,63 @@ +github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= +github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/cli/init.go b/internal/cli/init.go index dbf5bfc..aac0c87 100644 --- a/internal/cli/init.go +++ b/internal/cli/init.go @@ -1,49 +1,127 @@ package cli import ( - "bufio" "fmt" - "os" - "strings" - - "github.com/spf13/cobra" "github.com/burnless/burnless/internal/config" + + "github.com/AlecAivazis/survey/v2" + "github.com/spf13/cobra" ) // NewInitCmd creates the "burnless init" command. func NewInitCmd() *cobra.Command { return &cobra.Command{ - Use: "init", - Short: "Create a new sre.yaml for your service", - RunE: runInit, + Use: "init", + Short: "Create a new sre.yaml for your service", + SilenceUsage: true, + RunE: runInit, } } func runInit(cmd *cobra.Command, args []string) error { - reader := bufio.NewReader(os.Stdin) + fmt.Println() + fmt.Println(" Let's create your sre.yaml") + fmt.Println() + + // Step 1: service + team + var serviceName string + if err := survey.AskOne(&survey.Input{ + Message: "Service name:", + Help: "The name of the service this config applies to", + }, &serviceName, survey.WithValidator(survey.Required)); err != nil { + return err + } - serviceName := prompt(reader, "Service name") - team := prompt(reader, "Team") + var team string + if err := survey.AskOne(&survey.Input{ + Message: "Team:", + Help: "The team responsible for this service", + }, &team, survey.WithValidator(survey.Required)); err != nil { + return err + } + + // Step 2: SLO target + var sloTarget float64 + var sloTargetStr string + if err := survey.AskOne(&survey.Input{ + Message: "SLO availability target %:", + Default: "99.9", + Help: "e.g. 99.9 means 99.9% of requests must succeed", + }, &sloTargetStr, survey.WithValidator(survey.Required)); err != nil { + return err + } + if _, err := fmt.Sscanf(sloTargetStr, "%f", &sloTarget); err != nil { + return fmt.Errorf("invalid SLO target %q: enter a number like 99.9", sloTargetStr) + } + if sloTarget <= 0 || sloTarget > 100 { + return fmt.Errorf("SLO target must be between 0 and 100, got %.2f", sloTarget) + } + // Step 3: SLO window + var sloWindow string + if err := survey.AskOne(&survey.Select{ + Message: "SLO window:", + Options: []string{"7d", "30d", "90d"}, + Default: "30d", + Help: "The rolling time window for your SLO measurement", + }, &sloWindow); err != nil { + return err + } + + // Step 4: Prometheus metric + var metric string + if err := survey.AskOne(&survey.Input{ + Message: "Prometheus metric name:", + Default: "http_requests_total", + Help: "The metric used to calculate your SLO", + }, &metric, survey.WithValidator(survey.Required)); err != nil { + return err + } + + // Step 5: Slack channel + var slackChannel string + if err := survey.AskOne(&survey.Input{ + Message: "Slack channel for alerts:", + Default: "#sre-incidents", + Help: "Where burnless will send remediation notifications", + }, &slackChannel); err != nil { + return err + } + + // Step 6: on-call provider + var oncallProvider string + if err := survey.AskOne(&survey.Select{ + Message: "On-call provider:", + Options: []string{"pagerduty", "opsgenie", "none"}, + Default: "pagerduty", + Help: "The tool used for on-call routing", + }, &oncallProvider); err != nil { + return err + } + + // Generate and write sre.yaml cfg := config.GenerateDefault(serviceName, team) + cfg.SLOs[0].Target = sloTarget + cfg.SLOs[0].Window = sloWindow + cfg.SLOs[0].Indicator.Metric = metric + cfg.OnCall.NotifySlack = slackChannel + if oncallProvider != "none" { + cfg.OnCall.Provider = oncallProvider + } - // validate before writing — fail fast if something is wrong if err := cfg.Validate(); err != nil { return fmt.Errorf("generated config is invalid: %w", err) } - if err := config.WriteToFile(cfg, "sre.yaml"); err != nil { return fmt.Errorf("writing sre.yaml: %w", err) } - fmt.Println("✓ Created sre.yaml") + fmt.Println() + fmt.Printf(" Created sre.yaml for %s\n", serviceName) + fmt.Println(" Next steps:") + fmt.Println(" burnless validate -- check your config") + fmt.Println(" burnless diff -- preview what apply will create") + fmt.Println() return nil } - -// prompt prints a question and reads a single line of user input. -func prompt(reader *bufio.Reader, question string) string { - fmt.Printf("? %s: ", question) - answer, _ := reader.ReadString('\n') - return strings.TrimSpace(answer) -} From 1fde1ba4ecbda98dbb653a9e746ef35680771fc9 Mon Sep 17 00:00:00 2001 From: Dinushi Dhananjani Date: Fri, 3 Jul 2026 09:59:27 +0530 Subject: [PATCH 2/3] feat: implement burnless status command (closes #4) --- cmd/burnless/main.go | 1 + internal/cli/status.go | 96 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 internal/cli/status.go diff --git a/cmd/burnless/main.go b/cmd/burnless/main.go index 830542e..37efb27 100644 --- a/cmd/burnless/main.go +++ b/cmd/burnless/main.go @@ -30,6 +30,7 @@ func main() { rootCmd.AddCommand(cli.NewValidateCmd()) rootCmd.AddCommand(cli.NewToilCmd()) rootCmd.AddCommand(cli.NewDiffCmd()) + rootCmd.AddCommand(cli.NewStatusCmd()) if err := rootCmd.Execute(); err != nil { os.Exit(1) diff --git a/internal/cli/status.go b/internal/cli/status.go new file mode 100644 index 0000000..59bf948 --- /dev/null +++ b/internal/cli/status.go @@ -0,0 +1,96 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/burnless/burnless/internal/config" + + "github.com/spf13/cobra" +) + +// NewStatusCmd creates the "burnless status" command. +func NewStatusCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "status [file]", + Short: "Show SLO configuration and error budget status", + SilenceUsage: true, + Args: cobra.MaximumNArgs(1), + RunE: runStatus, + } + return cmd +} + +func runStatus(cmd *cobra.Command, args []string) error { + path := "sre.yaml" + if len(args) == 1 { + path = args[0] + } + + cfg, err := config.Load(path) + if err != nil { + return err + } + + divider := " " + strings.Repeat("-", 53) + + fmt.Println() + fmt.Printf(" Service: %s\n", cfg.Service) + fmt.Printf(" Team: %s\n", cfg.Team) + + // SLOs + fmt.Println() + fmt.Println(" SLOs configured:") + fmt.Println(divider) + for _, slo := range cfg.SLOs { + line := fmt.Sprintf(" %-20s target %.1f%% window %s", slo.Name, slo.Target, slo.Window) + if slo.ThresholdMS > 0 { + line += fmt.Sprintf(" threshold %dms", slo.ThresholdMS) + } + fmt.Println(line) + } + + // Error budget alerts + fmt.Println() + fmt.Println(" Error budget alerts:") + fmt.Println(divider) + for _, alert := range cfg.ErrorBudget.BurnRateAlerts { + remediate := "" + if alert.Remediate != "" { + remediate = fmt.Sprintf(" -> %s", alert.Remediate) + } + fmt.Printf(" %-10s %.1fx burn rate window %-4s%s\n", + alert.Severity, alert.Rate, alert.Window, remediate) + } + + // Runbooks + if len(cfg.Runbooks) > 0 { + fmt.Println() + fmt.Println(" Runbooks:") + fmt.Println(divider) + for name, rb := range cfg.Runbooks { + fmt.Printf(" %-14s mode: %-10s %d step(s)\n", + name, rb.Mode, len(rb.Steps)) + } + } + + // On-call + fmt.Println() + if cfg.OnCall.Provider != "" { + fmt.Printf(" On-call: %s (escalation: %d min)\n", + cfg.OnCall.Provider, cfg.OnCall.EscalationMinutes) + } + + // Dashboard + if cfg.Dashboards.Provider != "" { + fmt.Printf(" Dashboard: %s (auto-generate: %v)\n", + cfg.Dashboards.Provider, cfg.Dashboards.AutoGenerate) + } + + fmt.Println() + fmt.Println(" Run 'burnless apply' to deploy this config.") + fmt.Println(" Use '--live' flag with Prometheus URL for real-time burn rate (coming soon).") + fmt.Println() + + return nil +} From 7cb86bdcd0920519d74c1caded4355eefbe1b47e Mon Sep 17 00:00:00 2001 From: Dinushi Dhananjani Date: Fri, 3 Jul 2026 17:31:34 +0530 Subject: [PATCH 3/3] docs: getting started guide (closes #23) --- docs/getting-started.md | 254 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 235 insertions(+), 19 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 1a88a7f..9810e52 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,39 +1,255 @@ # Getting Started with Burnless -## Prerequisites -- Go 1.24+ -- A running Prometheus instance -- kubectl (optional, for Kubernetes) +Burnless is an open-source SRE platform that brings reliability engineering under version control. Instead of SLOs living in a Datadog dashboard and runbooks rotting in Confluence, everything lives in a single `sre.yaml` file — reviewed via pull requests, validated in CI, and eventually enforced automatically. -## Install +**Phase 1 (available now):** Track the manual toil your team absorbs every week, convert it to dollar cost, and produce reports that give managers the data to justify automation investment. +**Phase 2 (in development):** A burn rate agent watches Prometheus continuously and triggers remediation runbooks automatically — scaling replicas, rolling back deploys — before a human alert even fires. + +--- + +## 1. Install + +**Homebrew (macOS/Linux):** +```bash +brew install --cask Custos-com/tap/burnless +``` + +**curl one-liner:** +```bash +curl -fsSL https://raw.githubusercontent.com/Custos-com/Burnless/main/install.sh | sh +``` + +**Go install:** +```bash +go install github.com/Custos-com/Burnless/cmd/burnless@latest +``` + +Verify your install: ```bash -git clone https://github.com/Custos-com/Burnless.git -cd burnless -make build +burnless version +burnless --help ``` -## Create your first sre.yaml +--- + +## 2. Create your first sre.yaml + +Run the interactive wizard: +```bash +burnless init +``` + +You'll be prompted for six things: +``` + Let's create your sre.yaml + +? Service name: payments-api +? Team: platform-engineering +? SLO availability target %: 99.9 +? SLO window: 30d +? Prometheus metric name: http_requests_total +? Slack channel for alerts: #sre-incidents +? On-call provider: pagerduty +``` + +This generates a complete `sre.yaml`. Here's what each section means: ```yaml -service: my-api +service: payments-api team: platform-engineering + +# SLOs — what "good" looks like for this service. +# 99.9% availability over 30 days = ~43 minutes of allowed downtime per month. slos: - name: availability - target: 99.9% + target: 99.9 window: 30d + indicator: + metric: http_requests_total + good_filter: status!~"5.." + +# Error budget alerts — when to fire and what to do. +# 14.4x burn rate = you will exhaust your monthly error budget in 2 days. Critical. +# 6x burn rate = you will exhaust it in 5 days. Warning. error_budget: burn_rate_alerts: - - rate: 14.4x - severity: critical - notify: - slack: "#incidents" + - severity: critical + rate: 14.4 + window: 1h + remediate: scale-up + - severity: warning + rate: 6 + window: 6h + remediate: restart-pods + +# Runbooks — what to do when an alert fires. +# auto: agent executes immediately. semi-auto: agent asks for Slack approval first. +runbooks: + scale-up: + mode: auto + steps: + - kubectl scale deploy/payments-api --replicas=+2 + restart-pods: + mode: semi-auto + steps: + - kubectl rollout restart deploy/payments-api + +oncall: + provider: pagerduty + escalation_minutes: 10 + notify_slack: '#sre-incidents' + +dashboards: + provider: grafana + auto_generate: true +``` + +--- + +## 3. Validate it + +```bash +burnless validate +``` + +Success: +``` +✓ sre.yaml is valid + service: payments-api + 1 SLO(s) defined + 2 burn rate alert(s) configured + 2 runbook(s) defined +``` + +To see what a failure looks like, set `target: 150` in your sre.yaml and run validate again: +``` +✗ sre.yaml is invalid + slo[0] target must be between 0 and 100, got 150.00 ``` -## Run Burnless +Fix it and validate again. **Use this in CI** — `burnless validate` exits 1 on failure, so it blocks merges with broken configs: +```yaml +# .github/workflows/ci.yml +- run: burnless validate +``` + +--- + +## 4. Preview what apply will do ```bash -burnless validate # check your sre.yaml is valid -burnless status # see current SLO status -burnless agent # start the agent daemon +burnless diff ``` + +``` +Would create: + SLO: availability target=99.9% window=30d + Alert: critical @ 14.4x burn rate -> scale-up + Alert: warning @ 6.0x burn rate -> restart-pods + Runbook: scale-up (auto, 1 step) + Runbook: restart-pods (semi-auto, 1 step) + +Nothing is deployed yet. Run 'burnless apply' to deploy. +``` + +`burnless diff` with no arguments previews what `apply` would create. Nothing is deployed — it is a dry run. + +--- + +## 5. Check your config summary + +```bash +burnless status +``` + +``` + Service: payments-api + Team: platform-engineering + + SLOs configured: + ----------------------------------------------------- + availability target 99.9% window 30d + + Error budget alerts: + ----------------------------------------------------- + critical 14.4x burn rate window 1h -> scale-up + warning 6.0x burn rate window 6h -> restart-pods + + Runbooks: + ----------------------------------------------------- + scale-up mode: auto 1 step(s) + restart-pods mode: semi-auto 1 step(s) + + On-call: pagerduty (escalation: 10 min) + Dashboard: grafana (auto-generate: true) + + Run 'burnless apply' to deploy this config. +``` + +--- + +## 6. Track your first toil event + +Toil is manual, repetitive work that interrupts your week and could be automated. Log it as it happens: + +```bash +burnless toil log --service payments-api --task manual-rollback --duration 45m --automatable +burnless toil log --service payments-api --task restart-pods --duration 20m --automatable +burnless toil log --service payments-api --task update-config --duration 30m +``` + +Flags: +- `--service` — which service caused the toil +- `--task` — short name for the task +- `--duration` — how long it took (e.g. `45m`, `2h`) +- `--automatable` — flag if this could be automated (used for priority scoring) + +--- + +## 7. Generate your first report + +```bash +burnless toil report --month 2026-06 +``` + +``` +Toil Report — June 2026 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +Total toil hours: 4h 35m +Estimated cost: $110.58 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +Top tasks by automation priority: + + HIGH manual-rollback 3x 2h 15m $54.37 + HIGH restart-pods 2x 40m $16.10 + MED update-config 1x 30m $12.10 +``` + +**What "estimated cost" means:** Burnless uses a $180,000/year fully-loaded SRE salary as the basis. Each hour of toil = ~$90. This gives your manager a concrete number, not just hours. + +**What HIGH/MED/LOW means:** Tasks flagged `--automatable` score higher. Tasks that recur frequently score higher. Use this table in your next 1:1 — it makes the case for automation investment without needing to build a spreadsheet. + +--- + +## 8. Export for your manager + +```bash +burnless toil export --output june-toil.csv +``` + +Opens cleanly in Excel or Google Sheets. Share it directly with your manager or attach it to your quarterly review. + +--- + +## What's next + +- **Add `sre.yaml` to your Git repo** — treat it like infrastructure code, review changes via PRs +- **Add `burnless validate` to your CI/CD pipeline** — catch broken configs before they merge +- **Coming soon: `burnless apply`** — deploy your SLO config to Prometheus and Grafana automatically +- **Coming soon: burn rate agent** — watches Prometheus every 60 seconds and triggers your runbooks before a human alert fires + +**Links:** +- [GitHub](https://github.com/Custos-com/Burnless) +- [Open an issue](https://github.com/Custos-com/Burnless/issues) +- [Contributing guide](../CONTRIBUTING.md)