예제 #1
0
// GetBuildersComments returns the comments for each of the given builders.
func GetBuildersComments(builders []string) (map[string][]*BuilderComment, error) {
	if len(builders) == 0 {
		return map[string][]*BuilderComment{}, nil
	}
	buildersInterface := make([]interface{}, 0, len(builders))
	for _, b := range builders {
		buildersInterface = append(buildersInterface, b)
	}
	tmpl := util.RepeatJoin("?", ",", len(buildersInterface))
	c := []*BuilderComment{}
	stmt := fmt.Sprintf("SELECT * FROM %s WHERE builder IN (%s);", TABLE_BUILDER_COMMENTS, tmpl)
	if err := DB.Select(&c, stmt, buildersInterface...); err != nil {
		if err == sql.ErrNoRows {
			// None of these builders have comments.
			return map[string][]*BuilderComment{}, nil
		}
		return nil, fmt.Errorf("Unable to retrieve comment for builders: %v", err)
	}
	rv := map[string][]*BuilderComment{}
	for _, comment := range c {
		if _, ok := rv[comment.Builder]; !ok {
			rv[comment.Builder] = []*BuilderComment{}
		}
		rv[comment.Builder] = append(rv[comment.Builder], comment)
	}
	return rv, nil
}
예제 #2
0
// GetCommitsComments returns the comments on each of the given commits.
func GetCommitsComments(commits []string) (map[string][]*CommitComment, error) {
	if len(commits) == 0 {
		return map[string][]*CommitComment{}, nil
	}
	commitsInterface := make([]interface{}, 0, len(commits))
	for _, c := range commits {
		commitsInterface = append(commitsInterface, c)
	}
	tmpl := util.RepeatJoin("?", ",", len(commitsInterface))
	c := []*CommitComment{}
	if err := DB.Select(&c, fmt.Sprintf("SELECT * FROM %s WHERE commit IN (%s);", TABLE_COMMIT_COMMENTS, tmpl), commitsInterface...); err != nil {
		if err == sql.ErrNoRows {
			// None of these commits have comments.
			return map[string][]*CommitComment{}, nil
		}
		return nil, fmt.Errorf("Unable to retrieve comments for commits: %v", err)
	}
	rv := map[string][]*CommitComment{}
	for _, comment := range c {
		if _, ok := rv[comment.Commit]; ok {
			rv[comment.Commit] = append(rv[comment.Commit], comment)
		} else {
			rv[comment.Commit] = []*CommitComment{comment}
		}
	}
	return rv, nil
}
예제 #3
0
// GetBuildIDsForCommits retrieves IDs for all builds which first included each
// of the given commits.
func GetBuildIDsForCommits(commits []string) (map[string][]int, error) {
	res := []struct {
		Revision string `db:"revision"`
		BuildId  int    `db:"buildId"`
	}{}
	commitsInterface := make([]interface{}, 0, len(commits))
	for _, c := range commits {
		commitsInterface = append(commitsInterface, c)
	}
	tmpl := util.RepeatJoin("?", ",", len(commitsInterface))
	if err := DB.Select(&res, fmt.Sprintf("SELECT revision, buildId FROM %s WHERE revision IN (%s);", TABLE_BUILD_REVISIONS, tmpl), commitsInterface...); err != nil {
		if err == sql.ErrNoRows {
			// No builds include these commits.
			return map[string][]int{}, nil
		}
		return nil, fmt.Errorf("Unable to retrieve builds for commits: %v", err)
	}
	rv := map[string][]int{}
	for _, r := range res {
		if v, ok := rv[r.Revision]; !ok || v == nil {
			rv[r.Revision] = []int{}
		}
		rv[r.Revision] = append(rv[r.Revision], r.BuildId)
	}
	return rv, nil
}
예제 #4
0
// GetActiveAlerts retrieves all active alerts.
func GetActiveAlerts() ([]*Alert, error) {
	// Get the Alerts.
	rv := []*Alert{}
	if err := DB.Select(&rv, fmt.Sprintf("SELECT id,name,category,triggered,snoozedUntil,dismissedAt,message,nag,autoDismiss,lastFired FROM %s WHERE active = 1;", TABLE_ALERTS)); err != nil {
		return nil, fmt.Errorf("Could not retrieve active alerts: %v", err)
	}

	if len(rv) == 0 {
		return []*Alert{}, nil
	}

	interfaceIds := make([]interface{}, 0, len(rv))
	alertsById := map[int64]*Alert{}
	for _, a := range rv {
		interfaceIds = append(interfaceIds, a.Id)
		alertsById[a.Id] = a
	}
	inputTmpl := util.RepeatJoin("?", ",", len(interfaceIds))

	// Get the Comments.
	comments := []*commentFromDB{}
	if err := DB.Select(&comments, fmt.Sprintf("SELECT * FROM %s WHERE alertId IN (%s);", TABLE_COMMENTS, inputTmpl), interfaceIds...); err != nil {
		return nil, fmt.Errorf("Could not retrieve comments for active alerts: %v", err)
	}
	for _, c := range comments {
		alertsById[c.AlertId].Comments = append(alertsById[c.AlertId].Comments, c.toComment())
	}

	// Get the Actions.
	actions := []actionFromDB{}
	if err := DB.Select(&actions, fmt.Sprintf("SELECT * FROM %s WHERE alertId IN (%s);", TABLE_ACTIONS, inputTmpl), interfaceIds...); err != nil {
		return nil, fmt.Errorf("Could not retrieve actions for active alerts: %v", err)
	}
	for _, a := range actions {
		action, err := a.toAction()
		if err != nil {
			return nil, fmt.Errorf("Could not retrieve actions for active alerts: Failed to parse Action: %v", err)
		}
		alertsById[a.AlertId].Actions = append(alertsById[a.AlertId].Actions, action)
	}

	return rv, nil
}
예제 #5
0
// replaceCommentsIntoDB inserts, updates, or deletes the given BuildComments
// into the database. Returns a map whose keys are pointers to any
// newly-inserted BuildComments and whose values are the IDs which should be
// assigned to those comments. This allows us to postpone assigning any IDs
// until the transaction has completed successfully.
func replaceCommentsIntoDB(tx *sqlx.Tx, comments []*BuildComment, newBuildID int) (map[*BuildComment]int, error) {
	// First, determine which comments need to be inserted, updated, and deleted.
	commentsFromDB := []*BuildComment{}
	stmt := fmt.Sprintf("SELECT * FROM %s WHERE buildId = ?;", TABLE_BUILD_COMMENTS)
	if err := tx.Select(&commentsFromDB, stmt, newBuildID); err != nil {
		return nil, fmt.Errorf("Could not retrieve build comments from database: %v", err)
	}
	oldComments := make(map[int]bool, len(commentsFromDB))
	for _, c := range commentsFromDB {
		oldComments[int(c.Id)] = true
	}
	newComments := make(map[int]*BuildComment, len(comments))
	for _, c := range comments {
		newComments[c.Id] = c
	}
	update := make([]*BuildComment, 0, len(comments))
	insert := make([]*BuildComment, 0, len(comments))
	remove := make([]int, 0, len(comments))
	for _, c := range comments {
		if _, ok := oldComments[c.Id]; ok {
			update = append(update, c)
			delete(oldComments, c.Id)
		} else {
			insert = append(insert, c)
		}
	}
	for id, _ := range oldComments {
		remove = append(remove, id)
	}

	rv := map[*BuildComment]int{}

	// Delete any no-longer-existing comments.
	if len(remove) > 0 {
		idTmpl := util.RepeatJoin("?", ",", len(remove))
		removeIds := make([]interface{}, 0, len(remove))
		for _, id := range remove {
			removeIds = append(removeIds, id)
		}
		stmt := fmt.Sprintf("DELETE FROM %s WHERE id IN (%s)", TABLE_BUILD_COMMENTS, idTmpl)
		if _, err := tx.Exec(stmt, removeIds...); err != nil {
			return nil, fmt.Errorf("Could not remove old build comments.")
		}
	}

	// Insert any new comments.
	for _, c := range insert {
		stmt := fmt.Sprintf("INSERT INTO %s (buildId,user,timestamp,message) VALUES (?,?,?,?);", TABLE_BUILD_COMMENTS)
		res, err := tx.Exec(stmt, newBuildID, c.User, c.Timestamp, c.Message)
		if err != nil {
			return nil, fmt.Errorf("Unable to push build comments into database: %v", err)
		}
		id, err := res.LastInsertId()
		if err != nil {
			return nil, fmt.Errorf("Unable to get ID for inserted build comment: %v", err)
		}
		rv[c] = int(id)
	}

	// Update any already-existing comments.
	for _, c := range update {
		stmt := fmt.Sprintf("UPDATE %s SET buildId=?, user=?, timestamp=?, message=? WHERE id = ?", TABLE_BUILD_COMMENTS)
		if _, err := tx.Exec(stmt, c.BuildId, c.User, c.Timestamp, c.Message, c.Id); err != nil {
			return nil, fmt.Errorf("Failed to update build comments: %v", err)
		}
	}

	return rv, nil
}
예제 #6
0
// replaceCommitsIntoDB inserts, updates, or deletes the commits for the
// build into the database.
func replaceCommitsIntoDB(tx *sqlx.Tx, commits []string, buildID int) error {
	// First, determine which commits need to be inserted, updated, and deleted.
	commitsFromDB := []*commitFromDB{}
	stmt := fmt.Sprintf("SELECT * FROM %s WHERE buildId = ?;", TABLE_BUILD_REVISIONS)
	if err := DB.Select(&commitsFromDB, stmt, buildID); err != nil {
		return fmt.Errorf("Could not retrieve commits from database: %v", err)
	}
	oldCommits := make(map[string]*commitFromDB, len(commitsFromDB))
	for _, c := range commitsFromDB {
		oldCommits[c.Revision] = c
	}
	newCommits := make(map[string]bool, len(commits))
	for _, c := range commits {
		newCommits[c] = true
	}
	update := make([]*commitFromDB, 0, len(commits))
	insert := make([]*commitFromDB, 0, len(commits))
	remove := make([]int, 0, len(commits))
	for _, c := range commits {
		if old, ok := oldCommits[c]; ok {
			update = append(update, old)
			delete(oldCommits, old.Revision)
		} else {
			insert = append(insert, &commitFromDB{
				BuildID:  buildID,
				Revision: c,
			})
		}
	}
	for _, c := range oldCommits {
		remove = append(remove, c.Id)
	}

	// Delete any no-longer-existing commits.
	if len(remove) > 0 {
		idTmpl := util.RepeatJoin("?", ",", len(remove))
		removeIds := make([]interface{}, 0, len(remove))
		for _, id := range remove {
			removeIds = append(removeIds, id)
		}
		stmt := fmt.Sprintf("DELETE FROM %s WHERE id IN (%s)", TABLE_BUILD_REVISIONS, idTmpl)
		if _, err := tx.Exec(stmt, removeIds...); err != nil {
			return fmt.Errorf("Could not remove old build revisions.")
		}
	}

	// Insert any new commits.
	for _, c := range insert {
		c.BuildID = buildID
		stmt := fmt.Sprintf("INSERT INTO %s (buildId,revision) VALUES (?,?);", TABLE_BUILD_REVISIONS)
		if _, err := tx.Exec(stmt, c.BuildID, c.Revision); err != nil {
			return fmt.Errorf("Unable to push revisions into database: %v", err)
		}
	}

	// Update any already-existing commits.
	for _, c := range update {
		stmt := fmt.Sprintf("UPDATE %s SET buildId=?, revision=? WHERE id = ?", TABLE_BUILD_REVISIONS)
		if _, err := tx.Exec(stmt, c.BuildID, c.Revision, c.Id); err != nil {
			return fmt.Errorf("Failed to update build revisions: %v", err)
		}
	}

	return nil
}
예제 #7
0
// replaceStepsIntoDB inserts, updates, or deletes the given BuildSteps into
// the database. Returns a map whose keys are pointers to any newly-inserted
// BuildSteps and whose values are the IDs which should be assigned to those
// steps. This allows us to postpone assigning any IDs until the transaction
// has completed successfully.
func replaceStepsIntoDB(tx *sqlx.Tx, steps []*BuildStep, newBuildID int) (map[*BuildStep]int, error) {
	// First, determine which steps need to be inserted, updated, and deleted.
	stepsFromDB := []*buildStepFromDB{}
	stmt := fmt.Sprintf("SELECT * FROM %s WHERE buildId = ?;", TABLE_BUILD_STEPS)
	if err := tx.Select(&stepsFromDB, stmt, newBuildID); err != nil {
		return nil, fmt.Errorf("Could not retrieve build steps from database: %v", err)
	}
	oldSteps := make(map[int]bool, len(stepsFromDB))
	for _, s := range stepsFromDB {
		oldSteps[int(s.Id)] = true
	}
	newSteps := make(map[int]*BuildStep, len(steps))
	for _, s := range steps {
		newSteps[s.Id] = s
	}
	update := make([]*BuildStep, 0, len(steps))
	insert := make([]*BuildStep, 0, len(steps))
	remove := make([]int, 0, len(steps))
	for _, s := range steps {
		if _, ok := oldSteps[s.Id]; ok {
			update = append(update, s)
			delete(oldSteps, s.Id)
		} else {
			insert = append(insert, s)
		}
	}
	for id, _ := range oldSteps {
		remove = append(remove, id)
	}

	rv := map[*BuildStep]int{}

	// Delete any no-longer-existing steps.
	if len(remove) > 0 {
		idTmpl := util.RepeatJoin("?", ",", len(remove))
		removeIds := make([]interface{}, 0, len(remove))
		for _, id := range remove {
			removeIds = append(removeIds, id)
		}
		stmt := fmt.Sprintf("DELETE FROM %s WHERE id IN (%s)", TABLE_BUILD_STEPS, idTmpl)
		if _, err := tx.Exec(stmt, removeIds...); err != nil {
			return nil, fmt.Errorf("Could not remove old build steps.")
		}
	}

	// Insert any new steps.
	for _, s := range insert {
		stmt := fmt.Sprintf("INSERT INTO %s (buildId,name,results,number,started,finished) VALUES (?,?,?,?,?,?);", TABLE_BUILD_STEPS)
		res, err := tx.Exec(stmt, newBuildID, s.Name, s.Results, s.Number, s.Started, s.Finished)
		if err != nil {
			return nil, fmt.Errorf("Unable to push buildsteps into database: %v", err)
		}
		id, err := res.LastInsertId()
		if err != nil {
			return nil, fmt.Errorf("Unable to get ID for inserted buildstep: %v", err)
		}
		rv[s] = int(id)
	}

	// Update any already-existing steps.
	for _, s := range update {
		stmt := fmt.Sprintf("UPDATE %s SET buildId=?, name=?, results=?, number=?, started=?, finished=? WHERE id = ?", TABLE_BUILD_STEPS)
		if _, err := tx.Exec(stmt, s.BuildID, s.Name, s.Results, s.Number, s.Started, s.Finished, s.Id); err != nil {
			return nil, fmt.Errorf("Failed to update build steps: %v", err)
		}
	}

	return rv, nil
}
예제 #8
0
// GetBuildsFromDB retrieves the given builds from the database.
func GetBuildsFromDB(ids []int) (map[int]*Build, error) {
	if len(ids) == 0 {
		return map[int]*Build{}, nil
	}
	interfaceIds := make([]interface{}, 0, len(ids))
	for _, id := range ids {
		interfaceIds = append(interfaceIds, id)
	}
	inputTmpl := util.RepeatJoin("?", ",", len(interfaceIds))

	var wg sync.WaitGroup

	// Get builds
	var buildsById map[int]*Build
	var buildsErr error
	wg.Add(1)
	go func() {
		defer wg.Done()
		b := []*buildFromDB{}
		if err := DB.Select(&b, fmt.Sprintf("SELECT * FROM %s WHERE id IN (%s);", TABLE_BUILDS, inputTmpl), interfaceIds...); err != nil {
			buildsErr = fmt.Errorf("Could not retrieve builds: %v", err)
			return
		}
		buildsById = map[int]*Build{}
		for _, buildFromDB := range b {
			build := buildFromDB.toBuild()
			// Build properties.
			var properties [][]interface{}
			if build.PropertiesStr != "" {
				if err := json.Unmarshal([]byte(build.PropertiesStr), &properties); err != nil {
					buildsErr = fmt.Errorf("Unable to parse build properties: %v", err)
				}
			}
			build.Properties = properties

			// Start and end times.
			build.Times = []float64{build.Started, build.Finished}
			buildsById[build.Id] = build
		}
	}()

	// Build steps.
	stepsFromDB := []*buildStepFromDB{}
	var stepsErr error
	wg.Add(1)
	go func() {
		defer wg.Done()
		if err := DB.Select(&stepsFromDB, fmt.Sprintf("SELECT * FROM %s WHERE buildId IN (%s);", TABLE_BUILD_STEPS, inputTmpl), interfaceIds...); err != nil {
			stepsErr = fmt.Errorf("Could not retrieve build steps from database: %v", err)
			return
		}
	}()

	// Commits for each build.
	commitsFromDB := []*commitFromDB{}
	var commitsErr error
	wg.Add(1)
	go func() {
		defer wg.Done()
		if err := DB.Select(&commitsFromDB, fmt.Sprintf("SELECT * FROM %s WHERE buildId IN (%s);", TABLE_BUILD_REVISIONS, inputTmpl), interfaceIds...); err != nil {
			commitsErr = fmt.Errorf("Could not retrieve revisions from database: %v", err)
			return
		}
	}()

	// Comments on each build.
	commentsFromDB := []*BuildComment{}
	var commentsErr error
	wg.Add(1)
	go func() {
		defer wg.Done()
		if err := DB.Select(&commentsFromDB, fmt.Sprintf("SELECT * FROM %s WHERE buildId IN (%s);", TABLE_BUILD_COMMENTS, inputTmpl), interfaceIds...); err != nil {
			commentsErr = fmt.Errorf("Could not retrieve comments from database: %v", err)
			return
		}
	}()

	wg.Wait()

	// Return error if applicable.
	if buildsErr != nil {
		return nil, buildsErr
	}
	if stepsErr != nil {
		return nil, stepsErr
	}
	if commitsErr != nil {
		return nil, commitsErr
	}
	if commentsErr != nil {
		return nil, commentsErr
	}

	// Associate steps with builds.
	for _, stepFromDB := range stepsFromDB {
		s := stepFromDB.toBuildStep()
		s.Times = []float64{s.Started, s.Finished}
		s.ResultsRaw = []interface{}{float64(s.Results), []interface{}{}}
		build, ok := buildsById[s.BuildID]
		if !ok {
			return nil, fmt.Errorf("Failed to retrieve builds; got a build step with no associated build.")
		}
		if build.Steps == nil {
			build.Steps = []*BuildStep{}
		}
		build.Steps = append(build.Steps, s)
	}

	// Associate commits with builds.
	for _, c := range commitsFromDB {
		build, ok := buildsById[c.BuildID]
		if !ok {
			return nil, fmt.Errorf("Failed to retrieve builds; got a commit with no associated build.")
		}
		if build.Commits == nil {
			build.Commits = []string{}
		}
		build.Commits = append(build.Commits, c.Revision)
	}

	// Associate comments with builds.
	for _, c := range commentsFromDB {
		build, ok := buildsById[c.BuildId]
		if !ok {
			return nil, fmt.Errorf("Failed to retrieve builds; got a comment with no associated build.")
		}
		if build.Comments == nil {
			build.Comments = []*BuildComment{}
		}
		build.Comments = append(build.Comments, c)
	}
	return buildsById, nil
}
예제 #9
0
// replaceIntoDB inserts or updates the Alert in the database.
func (a *Alert) replaceIntoDB() (rv error) {
	tx, err := DB.Beginx()
	if err != nil {
		return fmt.Errorf("Unable to push Alert into database - Could not begin transaction: %v", err)
	}
	defer func() { rv = database.CommitOrRollback(tx, rv) }()

	// Insert the alert itself.
	active := 0
	if a.DismissedAt == 0 {
		active = 1
	}
	res, err := tx.Exec(fmt.Sprintf("REPLACE INTO %s (id,active,name,triggered,category,message,nag,snoozedUntil,dismissedAt,autoDismiss,lastFired) VALUES (?,?,?,?,?,?,?,?,?,?,?);", TABLE_ALERTS), a.Id, active, a.Name, a.Triggered, a.Category, a.Message, a.Nag, a.SnoozedUntil, a.DismissedAt, a.AutoDismiss, a.LastFired)
	if err != nil {
		return fmt.Errorf("Failed to push alert into database: %v", err)
	}
	id, err := res.LastInsertId()
	if err != nil {
		return fmt.Errorf("Failed to push alert into database; LastInsertId failed: %v", err)
	}
	a.Id = id

	// Comments.

	// First, delete existing comments so we don't have leftovers hanging around from before.
	if _, err := tx.Exec(fmt.Sprintf("DELETE FROM %s WHERE alertId = ?;", TABLE_COMMENTS), a.Id); err != nil {
		return fmt.Errorf("Failed to delete comments from database: %v", err)
	}
	// Actually insert the comments.
	if len(a.Comments) > 0 {
		commentFields := 4
		commentTmpl := util.RepeatJoin("?", ",", commentFields)
		commentsTmpl := util.RepeatJoin(fmt.Sprintf("(%s)", commentTmpl), ",", len(a.Comments))
		flattenedComments := make([]interface{}, 0, commentFields*len(a.Comments))
		for _, c := range a.Comments {
			flattenedComments = append(flattenedComments, a.Id, c.User, c.Time, c.Message)
		}
		if _, err := tx.Exec(fmt.Sprintf("INSERT INTO %s (alertId,user,time,message) VALUES %s;", TABLE_COMMENTS, commentsTmpl), flattenedComments...); err != nil {
			return fmt.Errorf("Unable to push comments into database: %v", err)
		}
	}

	// Actions.

	// First, delete existing actions so we don't have leftovers hanging around from before.
	if _, err := tx.Exec(fmt.Sprintf("DELETE FROM %s WHERE alertId = ?;", TABLE_ACTIONS), a.Id); err != nil {
		return fmt.Errorf("Failed to delete actions from database: %v", err)
	}
	// Actually insert the actions.
	if len(a.Actions) > 0 {
		actionFields := 2
		actionTmpl := util.RepeatJoin("?", ",", actionFields)
		actionsTmpl := util.RepeatJoin(fmt.Sprintf("(%s)", actionTmpl), ",", len(a.Actions))
		flattenedActions := make([]interface{}, 0, actionFields*len(a.Actions))
		for _, action := range a.Actions {
			flattenedActions = append(flattenedActions, a.Id, action.String())
		}
		if _, err := tx.Exec(fmt.Sprintf("INSERT INTO %s (alertId,action) VALUES %s;", TABLE_ACTIONS, actionsTmpl), flattenedActions...); err != nil {
			return fmt.Errorf("Unable to push actions into database: %v", err)
		}
	}

	// the transaction is committed during the deferred function.
	return nil
}