Skip to content

Commit

Permalink
TravisCI Bee (#234)
Browse files Browse the repository at this point in the history
  • Loading branch information
CalmBit authored and muesli committed May 4, 2019
1 parent 1726fd7 commit a25f8d3
Show file tree
Hide file tree
Showing 6 changed files with 424 additions and 0 deletions.
Binary file added assets/bees/travisbee.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
233 changes: 233 additions & 0 deletions bees/travisbee/travisbee.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/*
* Copyright (C) 2019 CalmBit
* 2014-2019 Christian Muehlhaeuser
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* CalmBit <[email protected]>
* Christian Muehlhaeuser <[email protected]>
*/

// Package travisbee is a bee for monitoring and reacting to the status of
// TravisCI builds.
package travisbee

import (
"context"
"time"

"github.com/muesli/beehive/bees"
"github.com/shuheiktgw/go-travis"
)

// TravisBee is a bee for monitoring and reacting to the status of
// TravisCI builds.
type TravisBee struct {
bees.Bee

eventChan chan bees.Event
client *travis.Client
apiToken string
builds map[uint]BuildTracker
}

// BuildTracker is a marker struct to denote "tracked builds" that TravisBee
// cares about.
type BuildTracker struct {
id uint
state string
lastTime time.Time
}

// Run executes the Bee's event loop.
func (mod *TravisBee) Run(eventChan chan bees.Event) {

mod.eventChan = eventChan
mod.client = travis.NewClient(travis.ApiOrgUrl, mod.apiToken)
mod.builds = make(map[uint]BuildTracker)

since := time.Now()
timeout := time.Duration(time.Second * 10)
for {
select {
case <-mod.SigChan:
return
case <-time.After(timeout):
mod.getBuilds(since)

}
since = time.Now()
timeout = time.Duration(time.Minute)
}
}

func (mod *TravisBee) getBuilds(since time.Time) {
var opt = travis.BuildsOption{Limit: 10, Offset: 0, SortBy: "started_at:desc"}
builds, _, err := mod.client.Builds.List(context.Background(), &opt)
if err != nil {
mod.LogFatal("Error getting builds from travis-ci: ", err)
}

for i, currentBuild := range builds {
if b, ok := mod.builds[currentBuild.Id]; ok {
b.lastTime = time.Now()
if b.state != currentBuild.State {
mod.handleStateChange(&b, currentBuild)
b.state = currentBuild.State
}
mod.builds[currentBuild.Id] = b
} else {
mod.LogDebugf("[%d] %d - %s / %s", i, currentBuild.Id, currentBuild.StartedAt, currentBuild.State)

// If a build was just barely created (i.e. hasn't moved into any other
// stage yet) then we can't even get a started_at time - instead, let's
// just make it a new build, figuring it's obviously something we havent
// seen before.
if currentBuild.State == "created" {
mod.handleNewBuild(currentBuild, since)
} else {
t, err := time.Parse(time.RFC3339, currentBuild.StartedAt)
if err != nil {
mod.LogErrorf("Error parsing time %s - %v", currentBuild.StartedAt, err)
}
if t.After(since) {
mod.handleNewBuild(currentBuild, since)
}
}
}
}
}

func (mod *TravisBee) handleNewBuild(build *travis.Build, since time.Time) {
mod.builds[build.Id] = BuildTracker{id: build.Id, state: build.State, lastTime: time.Now()}
mod.LogDebugf("Tracking build %d - state %s", build.Id, build.State)

ev := bees.Event{
Bee: mod.Name(),
Name: "build_started",
Options: []bees.Placeholder{
{
Name: "id",
Type: "uint",
Value: build.Id,
},
{
Name: "state",
Type: "string",
Value: build.State,
},
{
Name: "repo_slug",
Type: "string",
Value: build.Repository.Slug,
},
{
Name: "duration",
Type: "uint",
Value: build.Duration,
},
},
}

mod.eventChan <- ev
}

func (mod *TravisBee) handleStateChange(bt *BuildTracker, build *travis.Build) {
mod.LogDebugf("State changed! (was %s, is now %s)", bt.state, build.State)

ev := bees.Event{
Bee: mod.Name(),
Name: "build_status_change",
Options: []bees.Placeholder{
{
Name: "id",
Type: "uint",
Value: build.Id,
},
{
Name: "state",
Type: "string",
Value: build.State,
},
{
Name: "last_state",
Type: "string",
Value: bt.state,
},
{
Name: "repo_slug",
Type: "string",
Value: build.Repository.Slug,
},
{
Name: "duration",
Type: "uint",
Value: build.Duration,
},
},
}

mod.eventChan <- ev

if build.State == "canceled" || build.State == "passed" ||
build.State == "failed" || build.State == "errored" {
mod.handleBuildFinish(bt, build)
}
}

func (mod *TravisBee) handleBuildFinish(bt *BuildTracker, build *travis.Build) {
mod.LogDebugf("Build %d has finished with state %s", build.Id, build.State)
ev := bees.Event{
Bee: mod.Name(),
Name: "build_finished",
Options: []bees.Placeholder{
{
Name: "id",
Type: "uint",
Value: build.Id,
},
{
Name: "state",
Type: "string",
Value: build.State,
},
{
Name: "previous_state",
Type: "string",
Value: build.PreviousState,
},
{
Name: "repo_slug",
Type: "string",
Value: build.Repository.Slug,
},
{
Name: "duration",
Type: "uint",
Value: build.Duration,
},
},
}
mod.eventChan <- ev
mod.LogDebugf("Now untracking build %d", bt.id)
delete(mod.builds, bt.id)
}

// ReloadOptions parses the config options and initializes the Bee.
func (mod *TravisBee) ReloadOptions(options bees.BeeOptions) {
mod.SetOptions(options)

options.Bind("api_key", &mod.apiToken)
}
Loading

0 comments on commit a25f8d3

Please sign in to comment.