harness-drone/store/build/build.go
2019-08-26 12:26:09 -07:00

582 lines
13 KiB
Go

// Copyright 2019 Drone IO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package build
import (
"context"
"github.com/drone/drone/core"
"github.com/drone/drone/store/shared/db"
)
// New returns a new Buildcore.
func New(db *db.DB) core.BuildStore {
return &buildStore{db}
}
type buildStore struct {
db *db.DB
}
// Find returns a build from the datacore.
func (s *buildStore) Find(ctx context.Context, id int64) (*core.Build, error) {
out := &core.Build{ID: id}
err := s.db.View(func(queryer db.Queryer, binder db.Binder) error {
params := toParams(out)
query, args, err := binder.BindNamed(queryKey, params)
if err != nil {
return err
}
row := queryer.QueryRow(query, args...)
return scanRow(row, out)
})
return out, err
}
// FindNumber returns a build from the datastore by build number.
func (s *buildStore) FindNumber(ctx context.Context, repo, number int64) (*core.Build, error) {
out := &core.Build{Number: number, RepoID: repo}
err := s.db.View(func(queryer db.Queryer, binder db.Binder) error {
params := toParams(out)
query, args, err := binder.BindNamed(queryNumber, params)
if err != nil {
return err
}
row := queryer.QueryRow(query, args...)
return scanRow(row, out)
})
return out, err
}
// FindLast returns the last build from the datastore by ref.
func (s *buildStore) FindRef(ctx context.Context, repo int64, ref string) (*core.Build, error) {
out := &core.Build{RepoID: repo, Ref: ref}
err := s.db.View(func(queryer db.Queryer, binder db.Binder) error {
params := toParams(out)
query, args, err := binder.BindNamed(queryRowRef, params)
if err != nil {
return err
}
row := queryer.QueryRow(query, args...)
return scanRow(row, out)
})
return out, err
}
// List returns a list of builds from the datastore by repository id.
func (s *buildStore) List(ctx context.Context, repo int64, limit, offset int) ([]*core.Build, error) {
var out []*core.Build
err := s.db.View(func(queryer db.Queryer, binder db.Binder) error {
params := map[string]interface{}{
"build_repo_id": repo,
"limit": limit,
"offset": offset,
}
stmt, args, err := binder.BindNamed(queryRepo, params)
if err != nil {
return err
}
rows, err := queryer.Query(stmt, args...)
if err != nil {
return err
}
out, err = scanRows(rows)
return err
})
return out, err
}
// ListRef returns a list of builds from the datastore by ref.
func (s *buildStore) ListRef(ctx context.Context, repo int64, ref string, limit, offset int) ([]*core.Build, error) {
var out []*core.Build
err := s.db.View(func(queryer db.Queryer, binder db.Binder) error {
params := map[string]interface{}{
"build_repo_id": repo,
"build_ref": ref,
"limit": limit,
"offset": offset,
}
stmt, args, err := binder.BindNamed(queryRef, params)
if err != nil {
return err
}
rows, err := queryer.Query(stmt, args...)
if err != nil {
return err
}
out, err = scanRows(rows)
return err
})
return out, err
}
// Pending returns a list of pending builds from the datastore by repository id.
func (s *buildStore) Pending(ctx context.Context) ([]*core.Build, error) {
var out []*core.Build
err := s.db.View(func(queryer db.Queryer, binder db.Binder) error {
rows, err := queryer.Query(queryPending)
if err != nil {
return err
}
out, err = scanRows(rows)
return err
})
return out, err
}
// Running returns a list of running builds from the datastore by repository id.
func (s *buildStore) Running(ctx context.Context) ([]*core.Build, error) {
var out []*core.Build
err := s.db.View(func(queryer db.Queryer, binder db.Binder) error {
rows, err := queryer.Query(queryRunning)
if err != nil {
return err
}
out, err = scanRows(rows)
return err
})
return out, err
}
// Create persists a build to the datacore.
func (s *buildStore) Create(ctx context.Context, build *core.Build, stages []*core.Stage) error {
if s.db.Driver() == db.Postgres {
return s.createPostgres(ctx, build, stages)
}
return s.create(ctx, build, stages)
}
func (s *buildStore) create(ctx context.Context, build *core.Build, stages []*core.Stage) error {
build.Version = 1
return s.db.Update(func(execer db.Execer, binder db.Binder) error {
params := toParams(build)
stmt, args, err := binder.BindNamed(stmtInsert, params)
if err != nil {
return err
}
res, err := execer.Exec(stmt, args...)
if err != nil {
return err
}
build.ID, err = res.LastInsertId()
if err != nil {
return err
}
for _, stage := range stages {
stage.Version = 1
stage.BuildID = build.ID
params := toStageParams(stage)
stmt, args, err := binder.BindNamed(stmtStageInsert, params)
if err != nil {
return err
}
res, err := execer.Exec(stmt, args...)
if err != nil {
return err
}
stage.ID, err = res.LastInsertId()
}
return err
})
}
func (s *buildStore) createPostgres(ctx context.Context, build *core.Build, stages []*core.Stage) error {
build.Version = 1
return s.db.Update(func(execer db.Execer, binder db.Binder) error {
params := toParams(build)
stmt, args, err := binder.BindNamed(stmtInsertPg, params)
if err != nil {
return err
}
err = execer.QueryRow(stmt, args...).Scan(&build.ID)
if err != nil {
return err
}
for _, stage := range stages {
stage.Version = 1
stage.BuildID = build.ID
params := toStageParams(stage)
stmt, args, err := binder.BindNamed(stmtStageInsertPg, params)
if err != nil {
return err
}
err = execer.QueryRow(stmt, args...).Scan(&stage.ID)
if err != nil {
return err
}
}
return err
})
}
// Update updates a build in the datacore.
func (s *buildStore) Update(ctx context.Context, build *core.Build) error {
versionNew := build.Version + 1
versionOld := build.Version
err := s.db.Lock(func(execer db.Execer, binder db.Binder) error {
params := toParams(build)
params["build_version_old"] = versionOld
params["build_version_new"] = versionNew
stmt, args, err := binder.BindNamed(stmtUpdate, params)
if err != nil {
return err
}
res, err := execer.Exec(stmt, args...)
if err != nil {
return err
}
effected, err := res.RowsAffected()
if err != nil {
return err
}
if effected == 0 {
return db.ErrOptimisticLock
}
return nil
})
if err == nil {
build.Version = versionNew
}
return err
}
// Delete deletes a build from the datacore.
func (s *buildStore) Delete(ctx context.Context, build *core.Build) error {
return s.db.Lock(func(execer db.Execer, binder db.Binder) error {
params := toParams(build)
stmt, args, err := binder.BindNamed(stmtDelete, params)
if err != nil {
return err
}
_, err = execer.Exec(stmt, args...)
return err
})
}
// Purge deletes builds from the database where the build number is less than n.
func (s *buildStore) Purge(ctx context.Context, repo, number int64) error {
build := &core.Build{
RepoID: repo,
Number: number,
}
return s.db.Lock(func(execer db.Execer, binder db.Binder) error {
params := toParams(build)
stmt, args, err := binder.BindNamed(stmtPurge, params)
if err != nil {
return err
}
_, err = execer.Exec(stmt, args...)
return err
})
}
// Count returns a count of builds.
func (s *buildStore) Count(ctx context.Context) (i int64, err error) {
err = s.db.View(func(queryer db.Queryer, binder db.Binder) error {
return queryer.QueryRow(queryCount).Scan(&i)
})
return
}
const queryCount = `
SELECT COUNT(*)
FROM builds
`
const queryBase = `
SELECT
build_id
,build_repo_id
,build_trigger
,build_number
,build_parent
,build_status
,build_error
,build_event
,build_action
,build_link
,build_timestamp
,build_title
,build_message
,build_before
,build_after
,build_ref
,build_source_repo
,build_source
,build_target
,build_author
,build_author_name
,build_author_email
,build_author_avatar
,build_sender
,build_params
,build_cron
,build_deploy
,build_deploy_id
,build_started
,build_finished
,build_created
,build_updated
,build_version
`
const queryKey = queryBase + `
FROM builds
WHERE build_id = :build_id
`
const queryNumber = queryBase + `
FROM builds
WHERE build_repo_id = :build_repo_id
AND build_number = :build_number
`
const queryRef = queryBase + `
FROM builds
WHERE build_repo_id = :build_repo_id
AND build_ref = :build_ref
ORDER BY build_id DESC
LIMIT :limit OFFSET :offset
`
const queryRowRef = queryBase + `
FROM builds
WHERE build_repo_id = :build_repo_id
AND build_ref = :build_ref
ORDER BY build_id DESC
LIMIT 1
`
const queryRepo = queryBase + `
FROM builds
WHERE build_repo_id = :build_repo_id
ORDER BY build_id DESC
LIMIT :limit OFFSET :offset
`
const queryPending = queryBase + `
FROM builds
WHERE EXISTS (
SELECT stage_id
FROM stages
WHERE stages.stage_build_id = builds.build_id
AND stages.stage_status = 'pending'
)
ORDER BY build_id ASC
`
const queryRunning = queryBase + `
FROM builds
WHERE EXISTS (
SELECT stage_id
FROM stages
WHERE stages.stage_build_id = builds.build_id
AND stages.stage_status = 'running'
)
ORDER BY build_id ASC
`
// const queryRunningOLD = queryBase + `
// FROM builds
// WHERE build_status = 'running'
// ORDER BY build_id ASC
// `
const queryAll = queryBase + `
FROM builds
WHERE build_id > :build_id
LIMIT :limit OFFSET :offset
`
const stmtUpdate = `
UPDATE builds SET
build_parent = :build_parent
,build_status = :build_status
,build_error = :build_error
,build_event = :build_event
,build_action = :build_action
,build_link = :build_link
,build_timestamp = :build_timestamp
,build_title = :build_title
,build_message = :build_message
,build_before = :build_before
,build_after = :build_after
,build_ref = :build_ref
,build_source_repo = :build_source_repo
,build_source = :build_source
,build_target = :build_target
,build_author = :build_author
,build_author_name = :build_author_name
,build_author_email = :build_author_email
,build_author_avatar = :build_author_avatar
,build_sender = :build_sender
,build_params = :build_params
,build_cron = :build_cron
,build_deploy = :build_deploy
,build_started = :build_started
,build_finished = :build_finished
,build_updated = :build_updated
,build_version = :build_version_new
WHERE build_id = :build_id
AND build_version = :build_version_old
`
const stmtInsert = `
INSERT INTO builds (
build_repo_id
,build_trigger
,build_number
,build_parent
,build_status
,build_error
,build_event
,build_action
,build_link
,build_timestamp
,build_title
,build_message
,build_before
,build_after
,build_ref
,build_source_repo
,build_source
,build_target
,build_author
,build_author_name
,build_author_email
,build_author_avatar
,build_sender
,build_params
,build_cron
,build_deploy
,build_deploy_id
,build_started
,build_finished
,build_created
,build_updated
,build_version
) VALUES (
:build_repo_id
,:build_trigger
,:build_number
,:build_parent
,:build_status
,:build_error
,:build_event
,:build_action
,:build_link
,:build_timestamp
,:build_title
,:build_message
,:build_before
,:build_after
,:build_ref
,:build_source_repo
,:build_source
,:build_target
,:build_author
,:build_author_name
,:build_author_email
,:build_author_avatar
,:build_sender
,:build_params
,:build_cron
,:build_deploy
,:build_deploy_id
,:build_started
,:build_finished
,:build_created
,:build_updated
,:build_version
)
`
const stmtInsertPg = stmtInsert + `
RETURNING build_id
`
const stmtStageInsert = `
INSERT INTO stages (
stage_repo_id
,stage_build_id
,stage_number
,stage_name
,stage_kind
,stage_type
,stage_status
,stage_error
,stage_errignore
,stage_exit_code
,stage_limit
,stage_os
,stage_arch
,stage_variant
,stage_kernel
,stage_machine
,stage_started
,stage_stopped
,stage_created
,stage_updated
,stage_version
,stage_on_success
,stage_on_failure
,stage_depends_on
,stage_labels
) VALUES (
:stage_repo_id
,:stage_build_id
,:stage_number
,:stage_name
,:stage_kind
,:stage_type
,:stage_status
,:stage_error
,:stage_errignore
,:stage_exit_code
,:stage_limit
,:stage_os
,:stage_arch
,:stage_variant
,:stage_kernel
,:stage_machine
,:stage_started
,:stage_stopped
,:stage_created
,:stage_updated
,:stage_version
,:stage_on_success
,:stage_on_failure
,:stage_depends_on
,:stage_labels
)
`
const stmtStageInsertPg = stmtStageInsert + `
RETURNING stage_id
`
const stmtDelete = `
DELETE FROM builds
WHERE build_id = :build_id
`
const stmtPurge = `
DELETE FROM builds
WHERE build_repo_id = :build_repo_id
AND build_number < :build_number
`