diff --git a/go.mod b/go.mod index 5ebc5aba..48e88967 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( github.com/Masterminds/semver/v3 v3.1.1 github.com/gin-gonic/gin v1.7.4 + github.com/go-vela/mock v0.10.0 github.com/go-vela/pkg-executor v0.10.0 github.com/go-vela/pkg-queue v0.10.0 github.com/go-vela/pkg-runtime v0.10.0 diff --git a/internal/build/doc.go b/internal/build/doc.go new file mode 100644 index 00000000..c2c5c609 --- /dev/null +++ b/internal/build/doc.go @@ -0,0 +1,11 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +// Package build provides the ability for Vela to +// manipulate and manage a build from a pipeline. +// +// Usage: +// +// import "github.com/go-vela/worker/internal/build" +package build diff --git a/internal/build/snapshot.go b/internal/build/snapshot.go new file mode 100644 index 00000000..77adb1e8 --- /dev/null +++ b/internal/build/snapshot.go @@ -0,0 +1,51 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package build + +import ( + "strings" + "time" + + "github.com/go-vela/sdk-go/vela" + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// Snapshot creates a moment in time record of the build +// and attempts to upload it to the server. +func Snapshot(b *library.Build, c *vela.Client, e error, l *logrus.Entry, r *library.Repo) { + // check if the build is not in a canceled status + if !strings.EqualFold(b.GetStatus(), constants.StatusCanceled) { + // check if the error provided is empty + if e != nil { + // populate build fields with error based values + b.SetError(e.Error()) + b.SetStatus(constants.StatusError) + b.SetFinished(time.Now().UTC().Unix()) + } + } + + // check if the logger provided is empty + if l == nil { + // create new logger + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#NewEntry + l = logrus.NewEntry(logrus.StandardLogger()) + } + + // check if the Vela client provided is empty + if c != nil { + l.Debug("uploading build snapshot") + + // send API call to update the build + // + // https://pkg.go.dev/github.com/go-vela/sdk-go/vela?tab=doc#BuildService.Update + _, _, err := c.Build.Update(r.GetOrg(), r.GetName(), b) + if err != nil { + l.Errorf("unable to upload build snapshot: %v", err) + } + } +} diff --git a/internal/build/snapshot_test.go b/internal/build/snapshot_test.go new file mode 100644 index 00000000..7747b6e0 --- /dev/null +++ b/internal/build/snapshot_test.go @@ -0,0 +1,105 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package build + +import ( + "errors" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/go-vela/mock/server" + "github.com/go-vela/sdk-go/vela" + "github.com/go-vela/types/library" +) + +func TestBuild_Snapshot(t *testing.T) { + // setup types + b := &library.Build{ + ID: vela.Int64(1), + Number: vela.Int(1), + Parent: vela.Int(1), + Event: vela.String("push"), + Status: vela.String("success"), + Error: vela.String(""), + Enqueued: vela.Int64(1563474077), + Created: vela.Int64(1563474076), + Started: vela.Int64(1563474077), + Finished: vela.Int64(0), + Deploy: vela.String(""), + Clone: vela.String("https://github.com/github/octocat.git"), + Source: vela.String("https://github.com/github/octocat/abcdefghi123456789"), + Title: vela.String("push received from https://github.com/github/octocat"), + Message: vela.String("First commit..."), + Commit: vela.String("48afb5bdc41ad69bf22588491333f7cf71135163"), + Sender: vela.String("OctoKitty"), + Author: vela.String("OctoKitty"), + Branch: vela.String("master"), + Ref: vela.String("refs/heads/master"), + BaseRef: vela.String(""), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + r := &library.Repo{ + ID: vela.Int64(1), + Org: vela.String("github"), + Name: vela.String("octocat"), + FullName: vela.String("github/octocat"), + Link: vela.String("https://github.com/github/octocat"), + Clone: vela.String("https://github.com/github/octocat.git"), + Branch: vela.String("master"), + Timeout: vela.Int64(60), + Visibility: vela.String("public"), + Private: vela.Bool(false), + Trusted: vela.Bool(false), + Active: vela.Bool(true), + AllowPull: vela.Bool(false), + AllowPush: vela.Bool(true), + AllowDeploy: vela.Bool(false), + AllowTag: vela.Bool(false), + } + + gin.SetMode(gin.TestMode) + + s := httptest.NewServer(server.FakeHandler()) + + _client, err := vela.NewClient(s.URL, "", nil) + if err != nil { + t.Errorf("unable to create Vela API client: %v", err) + } + + tests := []struct { + build *library.Build + client *vela.Client + err error + repo *library.Repo + }{ + { + build: b, + client: _client, + err: errors.New("unable to create network"), + repo: r, + }, + { + build: nil, + client: _client, + err: errors.New("unable to create network"), + repo: r, + }, + { + build: nil, + client: nil, + err: nil, + repo: nil, + }, + } + + // run test + for _, test := range tests { + Snapshot(test.build, test.client, test.err, nil, test.repo) + } +} diff --git a/internal/build/upload.go b/internal/build/upload.go new file mode 100644 index 00000000..4ab8e6f5 --- /dev/null +++ b/internal/build/upload.go @@ -0,0 +1,79 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package build + +import ( + "strings" + "time" + + "github.com/go-vela/sdk-go/vela" + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" + "github.com/sirupsen/logrus" +) + +// Upload tracks the final state of the build +// and attempts to upload it to the server. +func Upload(b *library.Build, c *vela.Client, e error, l *logrus.Entry, r *library.Repo) { + // handle the build based off the status provided + switch b.GetStatus() { + // build is in a canceled state + case constants.StatusCanceled: + fallthrough + // build is in a error state + case constants.StatusError: + fallthrough + // build is in a failure state + case constants.StatusFailure: + // if the build is in a canceled, error + // or failure state we DO NOT want to + // update the state to be success + break + // build is in a pending state + case constants.StatusPending: + // if the build is in a pending state + // then something must have gone + // drastically wrong because this + // SHOULD NOT happen + b.SetStatus(constants.StatusKilled) + default: + // update the build with a success state + b.SetStatus(constants.StatusSuccess) + } + + // check if the build is not in a canceled status + if !strings.EqualFold(b.GetStatus(), constants.StatusCanceled) { + // check if the error provided is empty + if e != nil { + // update the build with error based values + b.SetError(e.Error()) + b.SetStatus(constants.StatusError) + } + } + + // update the build with the finished timestamp + b.SetFinished(time.Now().UTC().Unix()) + + // check if the logger provided is empty + if l == nil { + // create new logger + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#NewEntry + l = logrus.NewEntry(logrus.StandardLogger()) + } + + // check if the Vela client provided is empty + if c != nil { + l.Debug("uploading final build state") + + // send API call to update the build + // + // https://pkg.go.dev/github.com/go-vela/sdk-go/vela?tab=doc#BuildService.Update + _, _, err := c.Build.Update(r.GetOrg(), r.GetName(), b) + if err != nil { + l.Errorf("unable to upload final build state: %v", err) + } + } +} diff --git a/internal/build/upload_test.go b/internal/build/upload_test.go new file mode 100644 index 00000000..b29f90fc --- /dev/null +++ b/internal/build/upload_test.go @@ -0,0 +1,132 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package build + +import ( + "errors" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/go-vela/mock/server" + "github.com/go-vela/sdk-go/vela" + "github.com/go-vela/types/library" +) + +func TestBuild_Upload(t *testing.T) { + // setup types + _build := &library.Build{ + ID: vela.Int64(1), + Number: vela.Int(1), + Parent: vela.Int(1), + Event: vela.String("push"), + Status: vela.String("success"), + Error: vela.String(""), + Enqueued: vela.Int64(1563474077), + Created: vela.Int64(1563474076), + Started: vela.Int64(1563474077), + Finished: vela.Int64(0), + Deploy: vela.String(""), + Clone: vela.String("https://github.com/github/octocat.git"), + Source: vela.String("https://github.com/github/octocat/abcdefghi123456789"), + Title: vela.String("push received from https://github.com/github/octocat"), + Message: vela.String("First commit..."), + Commit: vela.String("48afb5bdc41ad69bf22588491333f7cf71135163"), + Sender: vela.String("OctoKitty"), + Author: vela.String("OctoKitty"), + Branch: vela.String("master"), + Ref: vela.String("refs/heads/master"), + BaseRef: vela.String(""), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + _canceled := *_build + _canceled.SetStatus("canceled") + + _error := *_build + _error.SetStatus("error") + + _pending := *_build + _pending.SetStatus("pending") + + _repo := &library.Repo{ + ID: vela.Int64(1), + Org: vela.String("github"), + Name: vela.String("octocat"), + FullName: vela.String("github/octocat"), + Link: vela.String("https://github.com/github/octocat"), + Clone: vela.String("https://github.com/github/octocat.git"), + Branch: vela.String("master"), + Timeout: vela.Int64(60), + Visibility: vela.String("public"), + Private: vela.Bool(false), + Trusted: vela.Bool(false), + Active: vela.Bool(true), + AllowPull: vela.Bool(false), + AllowPush: vela.Bool(true), + AllowDeploy: vela.Bool(false), + AllowTag: vela.Bool(false), + } + + gin.SetMode(gin.TestMode) + + s := httptest.NewServer(server.FakeHandler()) + + _client, err := vela.NewClient(s.URL, "", nil) + if err != nil { + t.Errorf("unable to create Vela API client: %v", err) + } + + tests := []struct { + build *library.Build + client *vela.Client + err error + repo *library.Repo + }{ + { + build: _build, + client: _client, + err: errors.New("unable to create network"), + repo: _repo, + }, + { + build: &_canceled, + client: _client, + err: errors.New("unable to create network"), + repo: _repo, + }, + { + build: &_error, + client: _client, + err: errors.New("unable to create network"), + repo: _repo, + }, + { + build: &_pending, + client: _client, + err: errors.New("unable to create network"), + repo: _repo, + }, + { + build: nil, + client: _client, + err: errors.New("unable to create network"), + repo: _repo, + }, + { + build: nil, + client: nil, + err: nil, + repo: nil, + }, + } + + // run test + for _, test := range tests { + Upload(test.build, test.client, test.err, nil, test.repo) + } +} diff --git a/internal/doc.go b/internal/doc.go new file mode 100644 index 00000000..a8093234 --- /dev/null +++ b/internal/doc.go @@ -0,0 +1,16 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +// Package internal provides a collection of internal +// packages used for the Vela executor systems. +// +// More information: +// +// * https://golang.org/doc/go1.4#internalpackages +// * https://docs.google.com/document/d/1e8kOo3r51b2BWtTs_1uADIA5djfXhPT36s6eHVRIvaU/edit +// +// Usage: +// +// import "github.com/go-vela/worker/internal" +package internal diff --git a/internal/internal.go b/internal/internal.go new file mode 100644 index 00000000..a0e07f05 --- /dev/null +++ b/internal/internal.go @@ -0,0 +1,10 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package internal + +// For more information: +// +// * https://golang.org/doc/go1.4#internalpackages +// * https://docs.google.com/document/d/1e8kOo3r51b2BWtTs_1uADIA5djfXhPT36s6eHVRIvaU/edit diff --git a/internal/service/doc.go b/internal/service/doc.go new file mode 100644 index 00000000..7a2f0920 --- /dev/null +++ b/internal/service/doc.go @@ -0,0 +1,11 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +// Package service provides the ability for Vela to +// manipulate and manage a service from a pipeline. +// +// Usage: +// +// import "github.com/go-vela/worker/internal/service" +package service diff --git a/internal/service/environment.go b/internal/service/environment.go new file mode 100644 index 00000000..401a4b83 --- /dev/null +++ b/internal/service/environment.go @@ -0,0 +1,85 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package service + +import ( + "fmt" + + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" +) + +// Environment attempts to update the environment variables +// for the container based off the library resources. +// +// nolint: lll // ignore long line length due to parameters +func Environment(c *pipeline.Container, b *library.Build, r *library.Repo, s *library.Service, version string) error { + // check if container or container environment are empty + if c == nil || c.Environment == nil { + return fmt.Errorf("empty container provided for environment") + } + + // check if the build provided is empty + if b != nil { + // check if the channel exists in the environment + channel, ok := c.Environment["VELA_CHANNEL"] + if !ok { + // set default for channel + channel = constants.DefaultRoute + } + + // check if the workspace exists in the environment + workspace, ok := c.Environment["VELA_WORKSPACE"] + if !ok { + // set default for workspace + workspace = constants.WorkspaceDefault + } + + // update environment variables + c.Environment["VELA_DISTRIBUTION"] = b.GetDistribution() + c.Environment["VELA_HOST"] = b.GetHost() + c.Environment["VELA_RUNTIME"] = b.GetRuntime() + c.Environment["VELA_VERSION"] = version + + // populate environment variables from build library + // + // https://pkg.go.dev/github.com/go-vela/types/pipeline#Container.MergeEnv + // -> + // https://pkg.go.dev/github.com/go-vela/types/library#Build.Environment + err := c.MergeEnv(b.Environment(workspace, channel)) + if err != nil { + return err + } + } + + // check if the repo provided is empty + if r != nil { + // populate environment variables from repo library + // + // https://pkg.go.dev/github.com/go-vela/types/pipeline#Container.MergeEnv + // -> + // https://pkg.go.dev/github.com/go-vela/types/library#Repo.Environment + err := c.MergeEnv(r.Environment()) + if err != nil { + return err + } + } + + // check if the service provided is empty + if s != nil { + // populate environment variables from service library + // + // https://pkg.go.dev/github.com/go-vela/types/pipeline#Container.MergeEnv + // -> + // https://pkg.go.dev/github.com/go-vela/types/library#Service.Environment + err := c.MergeEnv(s.Environment()) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/service/environment_test.go b/internal/service/environment_test.go new file mode 100644 index 00000000..d4c89457 --- /dev/null +++ b/internal/service/environment_test.go @@ -0,0 +1,134 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package service + +import ( + "testing" + + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" + "github.com/go-vela/types/raw" +) + +func TestService_Environment(t *testing.T) { + // setup types + b := new(library.Build) + b.SetID(1) + b.SetRepoID(1) + b.SetNumber(1) + b.SetParent(1) + b.SetEvent("push") + b.SetStatus("running") + b.SetError("") + b.SetEnqueued(1563474077) + b.SetCreated(1563474076) + b.SetStarted(1563474078) + b.SetFinished(1563474079) + b.SetDeploy("") + b.SetDeployPayload(raw.StringSliceMap{"foo": "test1"}) + b.SetClone("https://github.com/github/octocat.git") + b.SetSource("https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163") + b.SetTitle("push received from https://github.com/github/octocat") + b.SetMessage("First commit...") + b.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") + b.SetSender("OctoKitty") + b.SetAuthor("OctoKitty") + b.SetEmail("OctoKitty@github.com") + b.SetLink("https://example.company.com/github/octocat/1") + b.SetBranch("master") + b.SetRef("refs/heads/master") + b.SetBaseRef("") + b.SetHeadRef("changes") + b.SetHost("example.company.com") + b.SetRuntime("docker") + b.SetDistribution("linux") + + c := &pipeline.Container{ + ID: "service_github_octocat_1_postgres", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "postgres:12-alpine", + Name: "postgres", + Number: 1, + Ports: []string{"5432:5432"}, + Pull: "not_present", + } + + r := new(library.Repo) + r.SetID(1) + r.SetOrg("github") + r.SetName("octocat") + r.SetFullName("github/octocat") + r.SetLink("https://github.com/github/octocat") + r.SetClone("https://github.com/github/octocat.git") + r.SetBranch("master") + r.SetTimeout(30) + r.SetVisibility("public") + r.SetPrivate(false) + r.SetTrusted(false) + r.SetActive(true) + r.SetAllowPull(false) + r.SetAllowPush(true) + r.SetAllowDeploy(false) + r.SetAllowTag(false) + r.SetAllowComment(false) + + s := new(library.Service) + s.SetID(1) + s.SetBuildID(1) + s.SetRepoID(1) + s.SetNumber(1) + s.SetName("postgres") + s.SetImage("postgres:12-alpine") + s.SetStatus("running") + s.SetExitCode(0) + s.SetCreated(1563474076) + s.SetStarted(1563474078) + s.SetFinished(1563474079) + s.SetHost("example.company.com") + s.SetRuntime("docker") + s.SetDistribution("linux") + + // setup tests + tests := []struct { + failure bool + build *library.Build + container *pipeline.Container + repo *library.Repo + service *library.Service + }{ + { + failure: false, + build: b, + container: c, + repo: r, + service: s, + }, + { + failure: true, + build: nil, + container: nil, + repo: nil, + service: nil, + }, + } + + // run tests + for _, test := range tests { + err := Environment(test.container, test.build, test.repo, test.service, "v0.0.0") + + if test.failure { + if err == nil { + t.Errorf("Environment should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("Environment returned err: %v", err) + } + } +} diff --git a/internal/service/load.go b/internal/service/load.go new file mode 100644 index 00000000..7c49e367 --- /dev/null +++ b/internal/service/load.go @@ -0,0 +1,59 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package service + +import ( + "fmt" + "sync" + + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" +) + +// Load attempts to capture the library service +// representing the container from the map. +func Load(c *pipeline.Container, m *sync.Map) (*library.Service, error) { + // check if the container provided is empty + if c == nil { + return nil, fmt.Errorf("empty container provided") + } + + // load the container ID as the service key from the map + result, ok := m.Load(c.ID) + if !ok { + return nil, fmt.Errorf("unable to load service %s", c.ID) + } + + // cast the value from the service key to the expected type + s, ok := result.(*library.Service) + if !ok { + return nil, fmt.Errorf("unable to cast value for service %s", c.ID) + } + + return s, nil +} + +// LoadLogs attempts to capture the library service logs +// representing the container from the map. +func LoadLogs(c *pipeline.Container, m *sync.Map) (*library.Log, error) { + // check if the container provided is empty + if c == nil { + return nil, fmt.Errorf("empty container provided") + } + + // load the container ID as the service log key from the map + result, ok := m.Load(c.ID) + if !ok { + return nil, fmt.Errorf("unable to load logs for service %s", c.ID) + } + + // cast the value from the service log key to the expected type + l, ok := result.(*library.Log) + if !ok { + return nil, fmt.Errorf("unable to cast value to logs for service %s", c.ID) + } + + return l, nil +} diff --git a/internal/service/load_test.go b/internal/service/load_test.go new file mode 100644 index 00000000..01d6cb63 --- /dev/null +++ b/internal/service/load_test.go @@ -0,0 +1,162 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package service + +import ( + "reflect" + "sync" + "testing" + + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" +) + +func TestService_Load(t *testing.T) { + // setup types + c := &pipeline.Container{ + ID: "service_github_octocat_1_postgres", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "postgres:12-alpine", + Name: "postgres", + Number: 1, + Ports: []string{"5432:5432"}, + Pull: "not_present", + } + + goodMap := new(sync.Map) + goodMap.Store(c.ID, new(library.Service)) + + badMap := new(sync.Map) + badMap.Store(c.ID, c) + + // setup tests + tests := []struct { + failure bool + container *pipeline.Container + _map *sync.Map + want *library.Service + }{ + { + failure: false, + container: c, + want: new(library.Service), + _map: goodMap, + }, + { + failure: true, + container: c, + want: nil, + _map: badMap, + }, + { + failure: true, + container: new(pipeline.Container), + want: nil, + _map: new(sync.Map), + }, + { + failure: true, + container: nil, + want: nil, + _map: nil, + }, + } + + // run tests + for _, test := range tests { + got, err := Load(test.container, test._map) + + if test.failure { + if err == nil { + t.Errorf("Load should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("Load returned err: %v", err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Load is %v, want %v", got, test.want) + } + } +} + +func TestStep_LoadLogs(t *testing.T) { + // setup types + c := &pipeline.Container{ + ID: "service_github_octocat_1_postgres", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "postgres:12-alpine", + Name: "postgres", + Number: 1, + Ports: []string{"5432:5432"}, + Pull: "not_present", + } + + goodMap := new(sync.Map) + goodMap.Store(c.ID, new(library.Log)) + + badMap := new(sync.Map) + badMap.Store(c.ID, c) + + // setup tests + tests := []struct { + failure bool + container *pipeline.Container + _map *sync.Map + want *library.Log + }{ + { + failure: false, + container: c, + want: new(library.Log), + _map: goodMap, + }, + { + failure: true, + container: c, + want: nil, + _map: badMap, + }, + { + failure: true, + container: new(pipeline.Container), + want: nil, + _map: new(sync.Map), + }, + { + failure: true, + container: nil, + want: nil, + _map: nil, + }, + } + + // run tests + for _, test := range tests { + got, err := LoadLogs(test.container, test._map) + + if test.failure { + if err == nil { + t.Errorf("LoadLogs should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("LoadLogs returned err: %v", err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("LoadLogs is %v, want %v", got, test.want) + } + } +} diff --git a/internal/service/snapshot.go b/internal/service/snapshot.go new file mode 100644 index 00000000..5421ef02 --- /dev/null +++ b/internal/service/snapshot.go @@ -0,0 +1,66 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package service + +import ( + "strings" + "time" + + "github.com/go-vela/sdk-go/vela" + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" + "github.com/sirupsen/logrus" +) + +// Snapshot creates a moment in time record of the +// service and attempts to upload it to the server. +// +// nolint: lll // ignore long line length due to parameters +func Snapshot(ctn *pipeline.Container, b *library.Build, c *vela.Client, l *logrus.Entry, r *library.Repo, s *library.Service) { + // check if the build is not in a canceled status + if !strings.EqualFold(s.GetStatus(), constants.StatusCanceled) { + // check if the container is running in headless mode + if !ctn.Detach { + // update the service fields to indicate a success + s.SetStatus(constants.StatusSuccess) + s.SetFinished(time.Now().UTC().Unix()) + } + + // check if the container has an unsuccessful exit code + if ctn.ExitCode != 0 { + // check if container failures should be ignored + if !ctn.Ruleset.Continue { + // set build status to failure + b.SetStatus(constants.StatusFailure) + } + + // update the service fields to indicate a failure + s.SetExitCode(ctn.ExitCode) + s.SetStatus(constants.StatusFailure) + } + } + + // check if the logger provided is empty + if l == nil { + // create new logger + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#NewEntry + l = logrus.NewEntry(logrus.StandardLogger()) + } + + // check if the Vela client provided is empty + if c != nil { + l.Debug("uploading service snapshot") + + // send API call to update the service + // + // https://pkg.go.dev/github.com/go-vela/sdk-go/vela?tab=doc#SvcService.Update + _, _, err := c.Svc.Update(r.GetOrg(), r.GetName(), b.GetNumber(), s) + if err != nil { + l.Errorf("unable to upload service snapshot: %v", err) + } + } +} diff --git a/internal/service/snapshot_test.go b/internal/service/snapshot_test.go new file mode 100644 index 00000000..85d8ee48 --- /dev/null +++ b/internal/service/snapshot_test.go @@ -0,0 +1,142 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package service + +import ( + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/go-vela/mock/server" + "github.com/go-vela/sdk-go/vela" + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" +) + +func TestService_Snapshot(t *testing.T) { + // setup types + _build := &library.Build{ + ID: vela.Int64(1), + Number: vela.Int(1), + Parent: vela.Int(1), + Event: vela.String("push"), + Status: vela.String("success"), + Error: vela.String(""), + Enqueued: vela.Int64(1563474077), + Created: vela.Int64(1563474076), + Started: vela.Int64(1563474077), + Finished: vela.Int64(0), + Deploy: vela.String(""), + Clone: vela.String("https://github.com/github/octocat.git"), + Source: vela.String("https://github.com/github/octocat/abcdefghi123456789"), + Title: vela.String("push received from https://github.com/github/octocat"), + Message: vela.String("First commit..."), + Commit: vela.String("48afb5bdc41ad69bf22588491333f7cf71135163"), + Sender: vela.String("OctoKitty"), + Author: vela.String("OctoKitty"), + Branch: vela.String("master"), + Ref: vela.String("refs/heads/master"), + BaseRef: vela.String(""), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + _container := &pipeline.Container{ + ID: "service_github_octocat_1_postgres", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "postgres:12-alpine", + Name: "postgres", + Number: 1, + Ports: []string{"5432:5432"}, + Pull: "not_present", + } + + _exitCode := &pipeline.Container{ + ID: "service_github_octocat_1_postgres", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + ExitCode: 137, + Image: "postgres:12-alpine", + Name: "postgres", + Number: 1, + Ports: []string{"5432:5432"}, + Pull: "not_present", + } + + _repo := &library.Repo{ + ID: vela.Int64(1), + Org: vela.String("github"), + Name: vela.String("octocat"), + FullName: vela.String("github/octocat"), + Link: vela.String("https://github.com/github/octocat"), + Clone: vela.String("https://github.com/github/octocat.git"), + Branch: vela.String("master"), + Timeout: vela.Int64(60), + Visibility: vela.String("public"), + Private: vela.Bool(false), + Trusted: vela.Bool(false), + Active: vela.Bool(true), + AllowPull: vela.Bool(false), + AllowPush: vela.Bool(true), + AllowDeploy: vela.Bool(false), + AllowTag: vela.Bool(false), + } + + _service := &library.Service{ + ID: vela.Int64(1), + BuildID: vela.Int64(1), + RepoID: vela.Int64(1), + Number: vela.Int(1), + Name: vela.String("postgres"), + Image: vela.String("postgres:12-alpine"), + Status: vela.String("running"), + ExitCode: vela.Int(0), + Created: vela.Int64(1563474076), + Started: vela.Int64(0), + Finished: vela.Int64(1563474079), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + gin.SetMode(gin.TestMode) + + s := httptest.NewServer(server.FakeHandler()) + + _client, err := vela.NewClient(s.URL, "", nil) + if err != nil { + t.Errorf("unable to create Vela API client: %v", err) + } + + tests := []struct { + build *library.Build + client *vela.Client + container *pipeline.Container + repo *library.Repo + service *library.Service + }{ + { + build: _build, + client: _client, + container: _container, + repo: _repo, + service: _service, + }, + { + build: _build, + client: _client, + container: _exitCode, + repo: _repo, + service: nil, + }, + } + + // run test + for _, test := range tests { + Snapshot(test.container, test.build, test.client, nil, test.repo, test.service) + } +} diff --git a/internal/service/upload.go b/internal/service/upload.go new file mode 100644 index 00000000..96ac7b2e --- /dev/null +++ b/internal/service/upload.go @@ -0,0 +1,93 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package service + +import ( + "time" + + "github.com/go-vela/sdk-go/vela" + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" + "github.com/sirupsen/logrus" +) + +// Upload tracks the final state of the service +// and attempts to upload it to the server. +// +// nolint: lll // ignore long line length due to parameters +func Upload(ctn *pipeline.Container, b *library.Build, c *vela.Client, l *logrus.Entry, r *library.Repo, s *library.Service) { + // handle the service based off the status provided + switch s.GetStatus() { + // service is in a canceled state + case constants.StatusCanceled: + fallthrough + // service is in a error state + case constants.StatusError: + fallthrough + // service is in a failure state + case constants.StatusFailure: + // if the service is in a canceled, error + // or failure state we DO NOT want to + // update the state to be success + break + // service is in a pending state + case constants.StatusPending: + // if the service is in a pending state + // then something must have gone + // drastically wrong because this + // SHOULD NOT happen + // + // TODO: consider making this a constant + // + // nolint: gomnd // ignore magic number 137 + s.SetExitCode(137) + s.SetFinished(time.Now().UTC().Unix()) + s.SetStatus(constants.StatusKilled) + + // check if the service was not started + if s.GetStarted() == 0 { + // set the started time to the finished time + s.SetStarted(s.GetFinished()) + } + default: + // update the service with a success state + s.SetStatus(constants.StatusSuccess) + } + + // check if the service finished + if s.GetFinished() == 0 { + // update the service with the finished timestamp + s.SetFinished(time.Now().UTC().Unix()) + + // check the container for an unsuccessful exit code + if ctn.ExitCode != 0 { + // update the service fields to indicate a failure + s.SetExitCode(ctn.ExitCode) + s.SetStatus(constants.StatusFailure) + } + } + + // check if the logger provided is empty + if l == nil { + // create new logger + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#NewEntry + l = logrus.NewEntry(logrus.StandardLogger()) + } + + // check if the Vela client provided is empty + if c != nil { + l.Debug("uploading service snapshot") + + // send API call to update the service + // + // https://pkg.go.dev/github.com/go-vela/sdk-go/vela?tab=doc#SvcService.Update + _, _, err := c.Svc.Update(r.GetOrg(), r.GetName(), b.GetNumber(), s) + if err != nil { + l.Errorf("unable to upload service snapshot: %v", err) + } + } +} diff --git a/internal/service/upload_test.go b/internal/service/upload_test.go new file mode 100644 index 00000000..4b88c19e --- /dev/null +++ b/internal/service/upload_test.go @@ -0,0 +1,170 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package service + +import ( + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/go-vela/mock/server" + "github.com/go-vela/sdk-go/vela" + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" +) + +func TestService_Upload(t *testing.T) { + // setup types + _build := &library.Build{ + ID: vela.Int64(1), + Number: vela.Int(1), + Parent: vela.Int(1), + Event: vela.String("push"), + Status: vela.String("success"), + Error: vela.String(""), + Enqueued: vela.Int64(1563474077), + Created: vela.Int64(1563474076), + Started: vela.Int64(1563474077), + Finished: vela.Int64(0), + Deploy: vela.String(""), + Clone: vela.String("https://github.com/github/octocat.git"), + Source: vela.String("https://github.com/github/octocat/abcdefghi123456789"), + Title: vela.String("push received from https://github.com/github/octocat"), + Message: vela.String("First commit..."), + Commit: vela.String("48afb5bdc41ad69bf22588491333f7cf71135163"), + Sender: vela.String("OctoKitty"), + Author: vela.String("OctoKitty"), + Branch: vela.String("master"), + Ref: vela.String("refs/heads/master"), + BaseRef: vela.String(""), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + _container := &pipeline.Container{ + ID: "step_github_octocat_1_init", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + } + + _exitCode := &pipeline.Container{ + ID: "step_github_octocat_1_init", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + ExitCode: 137, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + } + + _repo := &library.Repo{ + ID: vela.Int64(1), + Org: vela.String("github"), + Name: vela.String("octocat"), + FullName: vela.String("github/octocat"), + Link: vela.String("https://github.com/github/octocat"), + Clone: vela.String("https://github.com/github/octocat.git"), + Branch: vela.String("master"), + Timeout: vela.Int64(60), + Visibility: vela.String("public"), + Private: vela.Bool(false), + Trusted: vela.Bool(false), + Active: vela.Bool(true), + AllowPull: vela.Bool(false), + AllowPush: vela.Bool(true), + AllowDeploy: vela.Bool(false), + AllowTag: vela.Bool(false), + } + + _service := &library.Service{ + ID: vela.Int64(1), + BuildID: vela.Int64(1), + RepoID: vela.Int64(1), + Number: vela.Int(1), + Name: vela.String("postgres"), + Image: vela.String("postgres:12-alpine"), + Status: vela.String("running"), + ExitCode: vela.Int(0), + Created: vela.Int64(1563474076), + Started: vela.Int64(0), + Finished: vela.Int64(1563474079), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + _canceled := *_service + _canceled.SetStatus("canceled") + + _error := *_service + _error.SetStatus("error") + + _pending := *_service + _pending.SetStatus("pending") + + gin.SetMode(gin.TestMode) + + s := httptest.NewServer(server.FakeHandler()) + + _client, err := vela.NewClient(s.URL, "", nil) + if err != nil { + t.Errorf("unable to create Vela API client: %v", err) + } + + tests := []struct { + build *library.Build + client *vela.Client + container *pipeline.Container + repo *library.Repo + service *library.Service + }{ + { + build: _build, + client: _client, + container: _container, + repo: _repo, + service: _service, + }, + { + build: _build, + client: _client, + container: _container, + repo: _repo, + service: &_canceled, + }, + { + build: _build, + client: _client, + container: _container, + repo: _repo, + service: &_error, + }, + { + build: _build, + client: _client, + container: _container, + repo: _repo, + service: &_pending, + }, + { + build: _build, + client: _client, + container: _exitCode, + repo: _repo, + service: nil, + }, + } + + // run test + for _, test := range tests { + Upload(test.container, test.build, test.client, nil, test.repo, test.service) + } +} diff --git a/internal/step/doc.go b/internal/step/doc.go new file mode 100644 index 00000000..e763b81d --- /dev/null +++ b/internal/step/doc.go @@ -0,0 +1,11 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +// Package step provides the ability for Vela to +// manipulate and manage a step from a pipeline. +// +// Usage: +// +// import "github.com/go-vela/worker/internal/step" +package step diff --git a/internal/step/environment.go b/internal/step/environment.go new file mode 100644 index 00000000..36821c4a --- /dev/null +++ b/internal/step/environment.go @@ -0,0 +1,85 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "fmt" + + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" +) + +// Environment attempts to update the environment variables +// for the container based off the library resources. +// +// nolint: lll // ignore long line length due to parameters +func Environment(c *pipeline.Container, b *library.Build, r *library.Repo, s *library.Step, version string) error { + // check if container or container environment are empty + if c == nil || c.Environment == nil { + return fmt.Errorf("empty container provided for environment") + } + + // check if the build provided is empty + if b != nil { + // check if the channel exists in the environment + channel, ok := c.Environment["VELA_CHANNEL"] + if !ok { + // set default for channel + channel = constants.DefaultRoute + } + + // check if the workspace exists in the environment + workspace, ok := c.Environment["VELA_WORKSPACE"] + if !ok { + // set default for workspace + workspace = constants.WorkspaceDefault + } + + // update environment variables + c.Environment["VELA_DISTRIBUTION"] = b.GetDistribution() + c.Environment["VELA_HOST"] = b.GetHost() + c.Environment["VELA_RUNTIME"] = b.GetRuntime() + c.Environment["VELA_VERSION"] = version + + // populate environment variables from build library + // + // https://pkg.go.dev/github.com/go-vela/types/pipeline#Container.MergeEnv + // -> + // https://pkg.go.dev/github.com/go-vela/types/library#Build.Environment + err := c.MergeEnv(b.Environment(workspace, channel)) + if err != nil { + return err + } + } + + // check if the repo provided is empty + if r != nil { + // populate environment variables from build library + // + // https://pkg.go.dev/github.com/go-vela/types/pipeline#Container.MergeEnv + // -> + // https://pkg.go.dev/github.com/go-vela/types/library#Repo.Environment + err := c.MergeEnv(r.Environment()) + if err != nil { + return err + } + } + + // check if the step provided is empty + if s != nil { + // populate environment variables from step library + // + // https://pkg.go.dev/github.com/go-vela/types/pipeline#Container.MergeEnv + // -> + // https://pkg.go.dev/github.com/go-vela/types/library#Service.Environment + err := c.MergeEnv(s.Environment()) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/step/environment_test.go b/internal/step/environment_test.go new file mode 100644 index 00000000..d8441374 --- /dev/null +++ b/internal/step/environment_test.go @@ -0,0 +1,133 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "testing" + + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" + "github.com/go-vela/types/raw" +) + +func TestStep_Environment(t *testing.T) { + // setup types + b := new(library.Build) + b.SetID(1) + b.SetRepoID(1) + b.SetNumber(1) + b.SetParent(1) + b.SetEvent("push") + b.SetStatus("running") + b.SetError("") + b.SetEnqueued(1563474077) + b.SetCreated(1563474076) + b.SetStarted(1563474078) + b.SetFinished(1563474079) + b.SetDeploy("") + b.SetDeployPayload(raw.StringSliceMap{"foo": "test1"}) + b.SetClone("https://github.com/github/octocat.git") + b.SetSource("https://github.com/github/octocat/48afb5bdc41ad69bf22588491333f7cf71135163") + b.SetTitle("push received from https://github.com/github/octocat") + b.SetMessage("First commit...") + b.SetCommit("48afb5bdc41ad69bf22588491333f7cf71135163") + b.SetSender("OctoKitty") + b.SetAuthor("OctoKitty") + b.SetEmail("OctoKitty@github.com") + b.SetLink("https://example.company.com/github/octocat/1") + b.SetBranch("master") + b.SetRef("refs/heads/master") + b.SetBaseRef("") + b.SetHeadRef("changes") + b.SetHost("example.company.com") + b.SetRuntime("docker") + b.SetDistribution("linux") + + c := &pipeline.Container{ + ID: "step_github_octocat_1_init", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + } + + r := new(library.Repo) + r.SetID(1) + r.SetOrg("github") + r.SetName("octocat") + r.SetFullName("github/octocat") + r.SetLink("https://github.com/github/octocat") + r.SetClone("https://github.com/github/octocat.git") + r.SetBranch("master") + r.SetTimeout(30) + r.SetVisibility("public") + r.SetPrivate(false) + r.SetTrusted(false) + r.SetActive(true) + r.SetAllowPull(false) + r.SetAllowPush(true) + r.SetAllowDeploy(false) + r.SetAllowTag(false) + r.SetAllowComment(false) + + s := new(library.Step) + s.SetID(1) + s.SetBuildID(1) + s.SetRepoID(1) + s.SetNumber(1) + s.SetName("clone") + s.SetImage("target/vela-git:v0.3.0") + s.SetStatus("running") + s.SetExitCode(0) + s.SetCreated(1563474076) + s.SetStarted(1563474078) + s.SetFinished(1563474079) + s.SetHost("example.company.com") + s.SetRuntime("docker") + s.SetDistribution("linux") + + // setup tests + tests := []struct { + failure bool + build *library.Build + container *pipeline.Container + repo *library.Repo + step *library.Step + }{ + { + failure: false, + build: b, + container: c, + repo: r, + step: s, + }, + { + failure: true, + build: nil, + container: nil, + repo: nil, + step: nil, + }, + } + + // run tests + for _, test := range tests { + err := Environment(test.container, test.build, test.repo, test.step, "v0.0.0") + + if test.failure { + if err == nil { + t.Errorf("Environment should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("Environment returned err: %v", err) + } + } +} diff --git a/internal/step/load.go b/internal/step/load.go new file mode 100644 index 00000000..52b8a08f --- /dev/null +++ b/internal/step/load.go @@ -0,0 +1,85 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "fmt" + "sync" + + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" +) + +// Load attempts to capture the library step +// representing the container from the map. +func Load(c *pipeline.Container, m *sync.Map) (*library.Step, error) { + // check if the container provided is empty + if c == nil { + return nil, fmt.Errorf("empty container provided") + } + + // load the container ID as the step key from the map + result, ok := m.Load(c.ID) + if !ok { + return nil, fmt.Errorf("unable to load step %s", c.ID) + } + + // cast the value from the step key to the expected type + s, ok := result.(*library.Step) + if !ok { + return nil, fmt.Errorf("unable to cast value for step %s", c.ID) + } + + return s, nil +} + +// LoadInit attempts to capture the container representing +// the init process from the pipeline. +func LoadInit(p *pipeline.Build) (*pipeline.Container, error) { + // check if the pipeline provided is empty + if p == nil { + return nil, fmt.Errorf("empty pipeline provided") + } + + // create new container for the init step + c := new(pipeline.Container) + + // check if there are steps in the pipeline + if len(p.Steps) > 0 { + // update the container for the init process + c = p.Steps[0] + } + + // check if there are stages in the pipeline + if len(p.Stages) > 0 { + // update the container for the init process + c = p.Stages[0].Steps[0] + } + + return c, nil +} + +// LoadLogs attempts to capture the library step logs +// representing the container from the map. +func LoadLogs(c *pipeline.Container, m *sync.Map) (*library.Log, error) { + // check if the container provided is empty + if c == nil { + return nil, fmt.Errorf("empty container provided") + } + + // load the container ID as the step log key from the map + result, ok := m.Load(c.ID) + if !ok { + return nil, fmt.Errorf("unable to load logs for step %s", c.ID) + } + + // cast the value from the step log key to the expected type + l, ok := result.(*library.Log) + if !ok { + return nil, fmt.Errorf("unable to cast value to logs for step %s", c.ID) + } + + return l, nil +} diff --git a/internal/step/load_test.go b/internal/step/load_test.go new file mode 100644 index 00000000..74bb1e7c --- /dev/null +++ b/internal/step/load_test.go @@ -0,0 +1,255 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "reflect" + "sync" + "testing" + + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" +) + +func TestStep_Load(t *testing.T) { + // setup types + c := &pipeline.Container{ + ID: "step_github_octocat_1_init", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + } + + goodMap := new(sync.Map) + goodMap.Store(c.ID, new(library.Step)) + + badMap := new(sync.Map) + badMap.Store(c.ID, c) + + // setup tests + tests := []struct { + failure bool + container *pipeline.Container + _map *sync.Map + want *library.Step + }{ + { + failure: false, + container: c, + want: new(library.Step), + _map: goodMap, + }, + { + failure: true, + container: c, + want: nil, + _map: badMap, + }, + { + failure: true, + container: new(pipeline.Container), + want: nil, + _map: new(sync.Map), + }, + { + failure: true, + container: nil, + want: nil, + _map: nil, + }, + } + + // run tests + for _, test := range tests { + got, err := Load(test.container, test._map) + + if test.failure { + if err == nil { + t.Errorf("Load should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("Load returned err: %v", err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Load is %v, want %v", got, test.want) + } + } +} + +func TestStep_LoadInit(t *testing.T) { + // setup tests + tests := []struct { + failure bool + pipeline *pipeline.Build + want *pipeline.Container + }{ + { + failure: false, + pipeline: &pipeline.Build{ + Version: "1", + ID: "github_octocat_1", + Stages: pipeline.StageSlice{ + { + Name: "init", + Steps: pipeline.ContainerSlice{ + { + ID: "github_octocat_1_init_init", + Directory: "/vela/src/github.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + }, + }, + }, + }, + }, + want: &pipeline.Container{ + ID: "github_octocat_1_init_init", + Directory: "/vela/src/github.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + }, + }, + { + failure: false, + pipeline: &pipeline.Build{ + Version: "1", + ID: "github_octocat_1", + Steps: pipeline.ContainerSlice{ + { + ID: "step_github_octocat_1_init", + Directory: "/vela/src/github.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + }, + }, + }, + want: &pipeline.Container{ + ID: "step_github_octocat_1_init", + Directory: "/vela/src/github.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + }, + }, + { + failure: true, + pipeline: nil, + want: nil, + }, + } + + // run tests + for _, test := range tests { + got, err := LoadInit(test.pipeline) + + if test.failure { + if err == nil { + t.Errorf("LoadInit should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("LoadInit returned err: %v", err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("LoadInit is %v, want %v", got, test.want) + } + } +} + +func TestStep_LoadLogs(t *testing.T) { + // setup types + c := &pipeline.Container{ + ID: "step_github_octocat_1_init", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + } + + goodMap := new(sync.Map) + goodMap.Store(c.ID, new(library.Log)) + + badMap := new(sync.Map) + badMap.Store(c.ID, c) + + // setup tests + tests := []struct { + failure bool + container *pipeline.Container + _map *sync.Map + want *library.Log + }{ + { + failure: false, + container: c, + want: new(library.Log), + _map: goodMap, + }, + { + failure: true, + container: c, + want: nil, + _map: badMap, + }, + { + failure: true, + container: new(pipeline.Container), + want: nil, + _map: new(sync.Map), + }, + { + failure: true, + container: nil, + want: nil, + _map: nil, + }, + } + + // run tests + for _, test := range tests { + got, err := LoadLogs(test.container, test._map) + + if test.failure { + if err == nil { + t.Errorf("LoadLogs should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("LoadLogs returned err: %v", err) + } + + if !reflect.DeepEqual(got, test.want) { + t.Errorf("LoadLogs is %v, want %v", got, test.want) + } + } +} diff --git a/internal/step/skip.go b/internal/step/skip.go new file mode 100644 index 00000000..cc7a8fac --- /dev/null +++ b/internal/step/skip.go @@ -0,0 +1,50 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "strings" + + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" +) + +// Skip creates the ruledata from the build and repository +// information and returns true if the data does not match +// the ruleset for the given container. +func Skip(c *pipeline.Container, b *library.Build, r *library.Repo) bool { + // check if the container provided is empty + if c == nil { + return true + } + + // create ruledata from build and repository information + // + // https://pkg.go.dev/github.com/go-vela/types/pipeline#RuleData + ruledata := &pipeline.RuleData{ + Branch: b.GetBranch(), + Event: b.GetEvent(), + Repo: r.GetFullName(), + Status: b.GetStatus(), + } + + // check if the build event is tag + if strings.EqualFold(b.GetEvent(), constants.EventTag) { + // add tag information to ruledata with refs/tags prefix removed + ruledata.Tag = strings.TrimPrefix(b.GetRef(), "refs/tags/") + } + + // check if the build event is deployment + if strings.EqualFold(b.GetEvent(), constants.EventDeploy) { + // add deployment target information to ruledata + ruledata.Target = b.GetDeploy() + } + + // return the inverse of container execute + // + // https://pkg.go.dev/github.com/go-vela/types/pipeline#Container.Execute + return !c.Execute(ruledata) +} diff --git a/internal/step/skip_test.go b/internal/step/skip_test.go new file mode 100644 index 00000000..c1c8fc3c --- /dev/null +++ b/internal/step/skip_test.go @@ -0,0 +1,200 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "testing" + + "github.com/go-vela/sdk-go/vela" + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" +) + +func TestStep_Skip(t *testing.T) { + // setup types + _build := &library.Build{ + ID: vela.Int64(1), + Number: vela.Int(1), + Parent: vela.Int(1), + Event: vela.String("push"), + Status: vela.String("success"), + Error: vela.String(""), + Enqueued: vela.Int64(1563474077), + Created: vela.Int64(1563474076), + Started: vela.Int64(1563474077), + Finished: vela.Int64(0), + Deploy: vela.String(""), + Clone: vela.String("https://github.com/github/octocat.git"), + Source: vela.String("https://github.com/github/octocat/abcdefghi123456789"), + Title: vela.String("push received from https://github.com/github/octocat"), + Message: vela.String("First commit..."), + Commit: vela.String("48afb5bdc41ad69bf22588491333f7cf71135163"), + Sender: vela.String("OctoKitty"), + Author: vela.String("OctoKitty"), + Branch: vela.String("master"), + Ref: vela.String("refs/heads/master"), + BaseRef: vela.String(""), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + _comment := &library.Build{ + ID: vela.Int64(1), + Number: vela.Int(1), + Parent: vela.Int(1), + Event: vela.String("comment"), + Status: vela.String("success"), + Error: vela.String(""), + Enqueued: vela.Int64(1563474077), + Created: vela.Int64(1563474076), + Started: vela.Int64(1563474077), + Finished: vela.Int64(0), + Deploy: vela.String(""), + Clone: vela.String("https://github.com/github/octocat.git"), + Source: vela.String("https://github.com/github/octocat/abcdefghi123456789"), + Title: vela.String("push received from https://github.com/github/octocat"), + Message: vela.String("First commit..."), + Commit: vela.String("48afb5bdc41ad69bf22588491333f7cf71135163"), + Sender: vela.String("OctoKitty"), + Author: vela.String("OctoKitty"), + Branch: vela.String("master"), + Ref: vela.String("refs/heads/master"), + BaseRef: vela.String(""), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + _deploy := &library.Build{ + ID: vela.Int64(1), + Number: vela.Int(1), + Parent: vela.Int(1), + Event: vela.String("deployment"), + Status: vela.String("success"), + Error: vela.String(""), + Enqueued: vela.Int64(1563474077), + Created: vela.Int64(1563474076), + Started: vela.Int64(1563474077), + Finished: vela.Int64(0), + Deploy: vela.String(""), + Clone: vela.String("https://github.com/github/octocat.git"), + Source: vela.String("https://github.com/github/octocat/abcdefghi123456789"), + Title: vela.String("push received from https://github.com/github/octocat"), + Message: vela.String("First commit..."), + Commit: vela.String("48afb5bdc41ad69bf22588491333f7cf71135163"), + Sender: vela.String("OctoKitty"), + Author: vela.String("OctoKitty"), + Branch: vela.String("master"), + Ref: vela.String("refs/heads/master"), + BaseRef: vela.String(""), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + _tag := &library.Build{ + ID: vela.Int64(1), + Number: vela.Int(1), + Parent: vela.Int(1), + Event: vela.String("tag"), + Status: vela.String("success"), + Error: vela.String(""), + Enqueued: vela.Int64(1563474077), + Created: vela.Int64(1563474076), + Started: vela.Int64(1563474077), + Finished: vela.Int64(0), + Deploy: vela.String(""), + Clone: vela.String("https://github.com/github/octocat.git"), + Source: vela.String("https://github.com/github/octocat/abcdefghi123456789"), + Title: vela.String("push received from https://github.com/github/octocat"), + Message: vela.String("First commit..."), + Commit: vela.String("48afb5bdc41ad69bf22588491333f7cf71135163"), + Sender: vela.String("OctoKitty"), + Author: vela.String("OctoKitty"), + Branch: vela.String("master"), + Ref: vela.String("refs/heads/master"), + BaseRef: vela.String(""), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + _container := &pipeline.Container{ + ID: "step_github_octocat_1_init", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + } + + _repo := &library.Repo{ + ID: vela.Int64(1), + Org: vela.String("github"), + Name: vela.String("octocat"), + FullName: vela.String("github/octocat"), + Link: vela.String("https://github.com/github/octocat"), + Clone: vela.String("https://github.com/github/octocat.git"), + Branch: vela.String("master"), + Timeout: vela.Int64(60), + Visibility: vela.String("public"), + Private: vela.Bool(false), + Trusted: vela.Bool(false), + Active: vela.Bool(true), + AllowPull: vela.Bool(false), + AllowPush: vela.Bool(true), + AllowDeploy: vela.Bool(false), + AllowTag: vela.Bool(false), + } + + tests := []struct { + build *library.Build + container *pipeline.Container + repo *library.Repo + want bool + }{ + { + build: _build, + container: _container, + repo: _repo, + want: false, + }, + { + build: _comment, + container: _container, + repo: _repo, + want: false, + }, + { + build: _deploy, + container: _container, + repo: _repo, + want: false, + }, + { + build: _tag, + container: _container, + repo: _repo, + want: false, + }, + { + build: nil, + container: nil, + repo: nil, + want: true, + }, + } + + // run test + for _, test := range tests { + got := Skip(test.container, test.build, test.repo) + + if got != test.want { + t.Errorf("Skip is %v, want %v", got, test.want) + } + } +} diff --git a/internal/step/snapshot.go b/internal/step/snapshot.go new file mode 100644 index 00000000..d1a155cc --- /dev/null +++ b/internal/step/snapshot.go @@ -0,0 +1,119 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "strings" + "time" + + "github.com/go-vela/sdk-go/vela" + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" + "github.com/sirupsen/logrus" +) + +// Snapshot creates a moment in time record of the +// step and attempts to upload it to the server. +// +// nolint: lll // ignore long line length due to parameters +func Snapshot(ctn *pipeline.Container, b *library.Build, c *vela.Client, l *logrus.Entry, r *library.Repo, s *library.Step) { + // check if the build is not in a canceled status + if !strings.EqualFold(s.GetStatus(), constants.StatusCanceled) { + // check if the container is running in headless mode + if !ctn.Detach { + // update the step fields to indicate a success + s.SetStatus(constants.StatusSuccess) + s.SetFinished(time.Now().UTC().Unix()) + } + + // check if the container has an unsuccessful exit code + if ctn.ExitCode != 0 { + // check if container failures should be ignored + if !ctn.Ruleset.Continue { + // set build status to failure + b.SetStatus(constants.StatusFailure) + } + + // update the step fields to indicate a failure + s.SetExitCode(ctn.ExitCode) + s.SetStatus(constants.StatusFailure) + } + } + + // check if the logger provided is empty + if l == nil { + // create new logger + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#NewEntry + l = logrus.NewEntry(logrus.StandardLogger()) + } + + // check if the Vela client provided is empty + if c != nil { + l.Debug("uploading step snapshot") + + // send API call to update the step + // + // https://pkg.go.dev/github.com/go-vela/sdk-go/vela?tab=doc#StepService.Update + _, _, err := c.Step.Update(r.GetOrg(), r.GetName(), b.GetNumber(), s) + if err != nil { + l.Errorf("unable to upload step snapshot: %v", err) + } + } +} + +// SnapshotInit creates a moment in time record of the +// init step and attempts to upload it to the server. +// +// nolint: lll // ignore long line length due to parameters +func SnapshotInit(ctn *pipeline.Container, b *library.Build, c *vela.Client, l *logrus.Entry, r *library.Repo, s *library.Step, lg *library.Log) { + // check if the build is not in a canceled status + if !strings.EqualFold(s.GetStatus(), constants.StatusCanceled) { + // check if the container has an unsuccessful exit code + if ctn.ExitCode != 0 { + // check if container failures should be ignored + if !ctn.Ruleset.Continue { + // set build status to failure + b.SetStatus(constants.StatusFailure) + } + + // update the step fields to indicate a failure + s.SetExitCode(ctn.ExitCode) + s.SetStatus(constants.StatusFailure) + } + } + + // check if the logger provided is empty + if l == nil { + // create new logger + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#NewEntry + l = logrus.NewEntry(logrus.StandardLogger()) + } + + // check if the Vela client provided is empty + if c != nil { + l.Debug("uploading step snapshot") + + // send API call to update the step + // + // https://pkg.go.dev/github.com/go-vela/sdk-go/vela?tab=doc#StepService.Update + _, _, err := c.Step.Update(r.GetOrg(), r.GetName(), b.GetNumber(), s) + if err != nil { + l.Errorf("unable to upload step snapshot: %v", err) + } + + l.Debug("uploading step logs") + + // send API call to update the logs for the step + // + // https://pkg.go.dev/github.com/go-vela/sdk-go/vela?tab=doc#LogService.UpdateStep + _, _, err = c.Log.UpdateStep(r.GetOrg(), r.GetName(), b.GetNumber(), s.GetNumber(), lg) + if err != nil { + l.Errorf("unable to upload step logs: %v", err) + } + } +} diff --git a/internal/step/snapshot_test.go b/internal/step/snapshot_test.go new file mode 100644 index 00000000..cf3073aa --- /dev/null +++ b/internal/step/snapshot_test.go @@ -0,0 +1,267 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/go-vela/mock/server" + "github.com/go-vela/sdk-go/vela" + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" +) + +func TestStep_Snapshot(t *testing.T) { + // setup types + _build := &library.Build{ + ID: vela.Int64(1), + Number: vela.Int(1), + Parent: vela.Int(1), + Event: vela.String("push"), + Status: vela.String("success"), + Error: vela.String(""), + Enqueued: vela.Int64(1563474077), + Created: vela.Int64(1563474076), + Started: vela.Int64(1563474077), + Finished: vela.Int64(0), + Deploy: vela.String(""), + Clone: vela.String("https://github.com/github/octocat.git"), + Source: vela.String("https://github.com/github/octocat/abcdefghi123456789"), + Title: vela.String("push received from https://github.com/github/octocat"), + Message: vela.String("First commit..."), + Commit: vela.String("48afb5bdc41ad69bf22588491333f7cf71135163"), + Sender: vela.String("OctoKitty"), + Author: vela.String("OctoKitty"), + Branch: vela.String("master"), + Ref: vela.String("refs/heads/master"), + BaseRef: vela.String(""), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + _container := &pipeline.Container{ + ID: "step_github_octocat_1_init", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + } + + _exitCode := &pipeline.Container{ + ID: "step_github_octocat_1_init", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + ExitCode: 137, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + } + + _repo := &library.Repo{ + ID: vela.Int64(1), + Org: vela.String("github"), + Name: vela.String("octocat"), + FullName: vela.String("github/octocat"), + Link: vela.String("https://github.com/github/octocat"), + Clone: vela.String("https://github.com/github/octocat.git"), + Branch: vela.String("master"), + Timeout: vela.Int64(60), + Visibility: vela.String("public"), + Private: vela.Bool(false), + Trusted: vela.Bool(false), + Active: vela.Bool(true), + AllowPull: vela.Bool(false), + AllowPush: vela.Bool(true), + AllowDeploy: vela.Bool(false), + AllowTag: vela.Bool(false), + } + + _step := &library.Step{ + ID: vela.Int64(1), + BuildID: vela.Int64(1), + RepoID: vela.Int64(1), + Number: vela.Int(1), + Name: vela.String("clone"), + Image: vela.String("target/vela-git:v0.3.0"), + Status: vela.String("running"), + ExitCode: vela.Int(0), + Created: vela.Int64(1563474076), + Started: vela.Int64(0), + Finished: vela.Int64(1563474079), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + gin.SetMode(gin.TestMode) + + s := httptest.NewServer(server.FakeHandler()) + + _client, err := vela.NewClient(s.URL, "", nil) + if err != nil { + t.Errorf("unable to create Vela API client: %v", err) + } + + tests := []struct { + build *library.Build + client *vela.Client + container *pipeline.Container + repo *library.Repo + step *library.Step + }{ + { + build: _build, + client: _client, + container: _container, + repo: _repo, + step: _step, + }, + { + build: _build, + client: _client, + container: _exitCode, + repo: _repo, + step: nil, + }, + } + + // run test + for _, test := range tests { + Snapshot(test.container, test.build, test.client, nil, test.repo, test.step) + } +} + +func TestStep_SnapshotInit(t *testing.T) { + // setup types + _build := &library.Build{ + ID: vela.Int64(1), + Number: vela.Int(1), + Parent: vela.Int(1), + Event: vela.String("push"), + Status: vela.String("success"), + Error: vela.String(""), + Enqueued: vela.Int64(1563474077), + Created: vela.Int64(1563474076), + Started: vela.Int64(1563474077), + Finished: vela.Int64(0), + Deploy: vela.String(""), + Clone: vela.String("https://github.com/github/octocat.git"), + Source: vela.String("https://github.com/github/octocat/abcdefghi123456789"), + Title: vela.String("push received from https://github.com/github/octocat"), + Message: vela.String("First commit..."), + Commit: vela.String("48afb5bdc41ad69bf22588491333f7cf71135163"), + Sender: vela.String("OctoKitty"), + Author: vela.String("OctoKitty"), + Branch: vela.String("master"), + Ref: vela.String("refs/heads/master"), + BaseRef: vela.String(""), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + _container := &pipeline.Container{ + ID: "step_github_octocat_1_init", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + } + + _exitCode := &pipeline.Container{ + ID: "step_github_octocat_1_init", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + ExitCode: 137, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + } + + _repo := &library.Repo{ + ID: vela.Int64(1), + Org: vela.String("github"), + Name: vela.String("octocat"), + FullName: vela.String("github/octocat"), + Link: vela.String("https://github.com/github/octocat"), + Clone: vela.String("https://github.com/github/octocat.git"), + Branch: vela.String("master"), + Timeout: vela.Int64(60), + Visibility: vela.String("public"), + Private: vela.Bool(false), + Trusted: vela.Bool(false), + Active: vela.Bool(true), + AllowPull: vela.Bool(false), + AllowPush: vela.Bool(true), + AllowDeploy: vela.Bool(false), + AllowTag: vela.Bool(false), + } + + _step := &library.Step{ + ID: vela.Int64(1), + BuildID: vela.Int64(1), + RepoID: vela.Int64(1), + Number: vela.Int(1), + Name: vela.String("clone"), + Image: vela.String("target/vela-git:v0.3.0"), + Status: vela.String("running"), + ExitCode: vela.Int(0), + Created: vela.Int64(1563474076), + Started: vela.Int64(0), + Finished: vela.Int64(1563474079), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + gin.SetMode(gin.TestMode) + + s := httptest.NewServer(server.FakeHandler()) + + _client, err := vela.NewClient(s.URL, "", nil) + if err != nil { + t.Errorf("unable to create Vela API client: %v", err) + } + + tests := []struct { + build *library.Build + client *vela.Client + container *pipeline.Container + log *library.Log + repo *library.Repo + step *library.Step + }{ + { + build: _build, + client: _client, + container: _container, + log: new(library.Log), + repo: _repo, + step: _step, + }, + { + build: _build, + client: _client, + container: _exitCode, + log: new(library.Log), + repo: _repo, + step: nil, + }, + } + + // run test + for _, test := range tests { + SnapshotInit(test.container, test.build, test.client, nil, test.repo, test.step, test.log) + } +} diff --git a/internal/step/upload.go b/internal/step/upload.go new file mode 100644 index 00000000..f0c29897 --- /dev/null +++ b/internal/step/upload.go @@ -0,0 +1,93 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "time" + + "github.com/go-vela/sdk-go/vela" + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" + "github.com/sirupsen/logrus" +) + +// Upload tracks the final state of the step +// and attempts to upload it to the server. +// +// nolint: lll // ignore long line length due to parameters +func Upload(ctn *pipeline.Container, b *library.Build, c *vela.Client, l *logrus.Entry, r *library.Repo, s *library.Step) { + // handle the step based off the status provided + switch s.GetStatus() { + // step is in a canceled state + case constants.StatusCanceled: + fallthrough + // step is in a error state + case constants.StatusError: + fallthrough + // step is in a failure state + case constants.StatusFailure: + // if the step is in a canceled, error + // or failure state we DO NOT want to + // update the state to be success + break + // step is in a pending state + case constants.StatusPending: + // if the step is in a pending state + // then something must have gone + // drastically wrong because this + // SHOULD NOT happen + // + // TODO: consider making this a constant + // + // nolint: gomnd // ignore magic number 137 + s.SetExitCode(137) + s.SetFinished(time.Now().UTC().Unix()) + s.SetStatus(constants.StatusKilled) + + // check if the step was not started + if s.GetStarted() == 0 { + // set the started time to the finished time + s.SetStarted(s.GetFinished()) + } + default: + // update the step with a success state + s.SetStatus(constants.StatusSuccess) + } + + // check if the step finished + if s.GetFinished() == 0 { + // update the step with the finished timestamp + s.SetFinished(time.Now().UTC().Unix()) + + // check the container for an unsuccessful exit code + if ctn.ExitCode != 0 { + // update the step fields to indicate a failure + s.SetExitCode(ctn.ExitCode) + s.SetStatus(constants.StatusFailure) + } + } + + // check if the logger provided is empty + if l == nil { + // create new logger + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#NewEntry + l = logrus.NewEntry(logrus.StandardLogger()) + } + + // check if the Vela client provided is empty + if c != nil { + l.Debug("uploading final step state") + + // send API call to update the step + // + // https://pkg.go.dev/github.com/go-vela/sdk-go/vela?tab=doc#StepService.Update + _, _, err := c.Step.Update(r.GetOrg(), r.GetName(), b.GetNumber(), s) + if err != nil { + l.Errorf("unable to upload final step state: %v", err) + } + } +} diff --git a/internal/step/upload_test.go b/internal/step/upload_test.go new file mode 100644 index 00000000..957c92c7 --- /dev/null +++ b/internal/step/upload_test.go @@ -0,0 +1,170 @@ +// Copyright (c) 2021 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package step + +import ( + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/go-vela/mock/server" + "github.com/go-vela/sdk-go/vela" + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" +) + +func TestStep_Upload(t *testing.T) { + // setup types + _build := &library.Build{ + ID: vela.Int64(1), + Number: vela.Int(1), + Parent: vela.Int(1), + Event: vela.String("push"), + Status: vela.String("success"), + Error: vela.String(""), + Enqueued: vela.Int64(1563474077), + Created: vela.Int64(1563474076), + Started: vela.Int64(1563474077), + Finished: vela.Int64(0), + Deploy: vela.String(""), + Clone: vela.String("https://github.com/github/octocat.git"), + Source: vela.String("https://github.com/github/octocat/abcdefghi123456789"), + Title: vela.String("push received from https://github.com/github/octocat"), + Message: vela.String("First commit..."), + Commit: vela.String("48afb5bdc41ad69bf22588491333f7cf71135163"), + Sender: vela.String("OctoKitty"), + Author: vela.String("OctoKitty"), + Branch: vela.String("master"), + Ref: vela.String("refs/heads/master"), + BaseRef: vela.String(""), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + _container := &pipeline.Container{ + ID: "step_github_octocat_1_init", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + } + + _exitCode := &pipeline.Container{ + ID: "step_github_octocat_1_init", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + ExitCode: 137, + Image: "#init", + Name: "init", + Number: 1, + Pull: "always", + } + + _repo := &library.Repo{ + ID: vela.Int64(1), + Org: vela.String("github"), + Name: vela.String("octocat"), + FullName: vela.String("github/octocat"), + Link: vela.String("https://github.com/github/octocat"), + Clone: vela.String("https://github.com/github/octocat.git"), + Branch: vela.String("master"), + Timeout: vela.Int64(60), + Visibility: vela.String("public"), + Private: vela.Bool(false), + Trusted: vela.Bool(false), + Active: vela.Bool(true), + AllowPull: vela.Bool(false), + AllowPush: vela.Bool(true), + AllowDeploy: vela.Bool(false), + AllowTag: vela.Bool(false), + } + + _step := &library.Step{ + ID: vela.Int64(1), + BuildID: vela.Int64(1), + RepoID: vela.Int64(1), + Number: vela.Int(1), + Name: vela.String("clone"), + Image: vela.String("target/vela-git:v0.3.0"), + Status: vela.String("running"), + ExitCode: vela.Int(0), + Created: vela.Int64(1563474076), + Started: vela.Int64(0), + Finished: vela.Int64(1563474079), + Host: vela.String("example.company.com"), + Runtime: vela.String("docker"), + Distribution: vela.String("linux"), + } + + _canceled := *_step + _canceled.SetStatus("canceled") + + _error := *_step + _error.SetStatus("error") + + _pending := *_step + _pending.SetStatus("pending") + + gin.SetMode(gin.TestMode) + + s := httptest.NewServer(server.FakeHandler()) + + _client, err := vela.NewClient(s.URL, "", nil) + if err != nil { + t.Errorf("unable to create Vela API client: %v", err) + } + + tests := []struct { + build *library.Build + client *vela.Client + container *pipeline.Container + repo *library.Repo + step *library.Step + }{ + { + build: _build, + client: _client, + container: _container, + repo: _repo, + step: _step, + }, + { + build: _build, + client: _client, + container: _container, + repo: _repo, + step: &_canceled, + }, + { + build: _build, + client: _client, + container: _container, + repo: _repo, + step: &_error, + }, + { + build: _build, + client: _client, + container: _container, + repo: _repo, + step: &_pending, + }, + { + build: _build, + client: _client, + container: _exitCode, + repo: _repo, + step: nil, + }, + } + + // run test + for _, test := range tests { + Upload(test.container, test.build, test.client, nil, test.repo, test.step) + } +}