Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deploy: container history #578

Merged
merged 22 commits into from
Mar 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ed9496e
updated function description to match present functionality
seifghazi Feb 9, 2019
25ff9c6
added container bucket, successfully stores commit and date
seifghazi Feb 15, 2019
1dad3c7
database updates with container metadata on project deployment
seifghazi Feb 22, 2019
52098af
inline comment for clarity
seifghazi Feb 22, 2019
678635f
commented out test
seifghazi Feb 22, 2019
ce07f2c
Merge branch 'master' into deploy/293-quick-rollback
seifghazi Feb 24, 2019
d39d72a
remove logger
seifghazi Feb 26, 2019
54ecbf3
fixed conflicts
seifghazi Feb 26, 2019
243cb0c
merge branch
seifghazi Feb 26, 2019
e8b1af5
update logger
seifghazi Feb 26, 2019
1ea1d40
added error checks, metadata struct to store deployment data
seifghazi Mar 6, 2019
b91c1d3
add nested bucket functionality
seifghazi Mar 6, 2019
d0be09c
minor refactor, added nested buckets for each project
seifghazi Mar 6, 2019
8109bac
added tests covering bkt functionality
seifghazi Mar 29, 2019
9088cf1
updated insert query to resolve deadlock issue
seifghazi Mar 29, 2019
a80a910
Merge branch 'master' into deploy/293-quick-rollback
seifghazi Mar 29, 2019
2f5234f
more context with errors, added some error checks with bkt creation, …
seifghazi Mar 30, 2019
bcf4d3d
split function signature onto two lines
seifghazi Mar 30, 2019
15f71e6
more context to erros
seifghazi Mar 30, 2019
29acfbb
added counterfeiter
seifghazi Mar 30, 2019
758d804
added some comments for future reference
seifghazi Mar 30, 2019
6008ccd
reorder comment to fix lint error
seifghazi Mar 31, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions daemon/inertiad/build/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,7 @@ func (b *Builder) dockerBuild(d Config, cli *docker.Client,
return func() error { return b.run(ctx, cli, d.Name, containerResp.ID, out) }, nil
}

// run starts project and tracks all active project containers and pipes an error
// to the returned channel if any container exits or errors.
// run starts project and tracks all active project containers
func (b *Builder) run(ctx context.Context, client *docker.Client, name, id string, out io.Writer) error {
reportProjectStartup(name, out)
return client.ContainerStart(ctx, id, types.ContainerStartOptions{})
Expand Down
6 changes: 6 additions & 0 deletions daemon/inertiad/daemon/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,11 @@ func (s *Server) upHandler(w http.ResponseWriter, r *http.Request) {
return
}

// Update container management history following a successful build and deployment
err = s.deployment.UpdateContainerHistory(s.docker)
if err != nil {
stream.Error(res.ErrInternalServer("failed to update container history following build", err))
}

stream.Success(res.Msg("Project startup initiated!", http.StatusCreated))
}
73 changes: 70 additions & 3 deletions daemon/inertiad/project/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import (
"fmt"
"io/ioutil"
"os"
"time"

"github.com/ubclaunchpad/inertia/daemon/inertiad/crypto"
bolt "go.etcd.io/bbolt"
)

var (
// database buckets
envVariableBucket = []byte("envVariables")
envVariableBucket = []byte("envVariables")
deployedProjectsBucket = []byte("deployedProjects")
)

// DeploymentDataManager stores persistent deployment configuration
Expand Down Expand Up @@ -50,6 +52,14 @@ func NewDataManager(dbPath string, keyPath string) (*DeploymentDataManager, erro
}
if err = db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists(envVariableBucket)
if err != nil {
return fmt.Errorf("failed to created env variable bucket: %s", err.Error())
}

_, err = tx.CreateBucketIfNotExists(deployedProjectsBucket)
seifghazi marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return fmt.Errorf("failed to created deployed projects bucket: %s", err.Error())
}
return err
}); err != nil {
return nil, fmt.Errorf("failed to instantiate database: %s", err.Error())
Expand All @@ -63,8 +73,7 @@ func NewDataManager(dbPath string, keyPath string) (*DeploymentDataManager, erro

// AddEnvVariable adds a new environment variable that will be applied
// to all project containers
func (c *DeploymentDataManager) AddEnvVariable(name, value string,
encrypt bool) error {
func (c *DeploymentDataManager) AddEnvVariable(name, value string, encrypt bool) error {
if len(name) == 0 || len(value) == 0 {
return errors.New("invalid env configuration")
}
Expand Down Expand Up @@ -138,6 +147,64 @@ func (c *DeploymentDataManager) GetEnvVariables(decrypt bool) ([]string, error)
return envs, err
}

// AddProjectBuildData stores and tracks metadata from successful builds
// TODO: Change name, error check, only insert project mdata inside private helper 'update build'
func (c *DeploymentDataManager) AddProjectBuildData(projectName string, mdata DeploymentMetadata) error {
bobheadxi marked this conversation as resolved.
Show resolved Hide resolved
// encode metadata so it can be stored as byte array
encodedMdata, err := json.Marshal(mdata)
if err != nil {
return fmt.Errorf("failure encrypting metadata: %s", err.Error())
}
err = c.db.Update(func(tx *bolt.Tx) error {
depProjectsBkt := tx.Bucket(deployedProjectsBucket)
// if bkt with project name doesnt exist create new bkt, otherwise update existing bucket
if projectBkt := depProjectsBkt.Bucket([]byte(projectName)); projectBkt == nil {
projectBkt, err := depProjectsBkt.CreateBucket([]byte(projectName))
if err != nil {
return fmt.Errorf("failure creating project bkt: %s", err.Error())
}

if err := projectBkt.Put([]byte(time.Now().String()), encodedMdata); err != nil {
return fmt.Errorf("failure inserting project metadata: %s", err.Error())
}
}
return nil
})
return c.UpdateProjectBuildData(projectName, mdata)
}

// UpdateProjectBuildData updates existing project bkt with recent build's metadata
func (c *DeploymentDataManager) UpdateProjectBuildData(projectName string,
mdata DeploymentMetadata) error {
// encode metadata so it can be stored as byte array
encodedMdata, err := json.Marshal(mdata)
if err != nil {
return fmt.Errorf("failure encrypting metadata: %s", err.Error())
}
return c.db.Update(func(tx *bolt.Tx) error {
depProjectBkt := tx.Bucket(deployedProjectsBucket)
projectBkt := depProjectBkt.Bucket([]byte(projectName))

if err := projectBkt.Put([]byte(time.Now().String()), encodedMdata); err != nil {
return fmt.Errorf("failure updating db with project metadata: %s", err.Error())
}
return nil
})

}

// GetNumOfDeployedProjects returns number of projects currently deployed
func (c *DeploymentDataManager) GetNumOfDeployedProjects(projectName string) (int, error) {
var numBkts int
err := c.db.View(func(tx *bolt.Tx) error {
depProjectBkt := tx.Bucket(deployedProjectsBucket)
bktStats := depProjectBkt.Stats()
numBkts = bktStats.BucketN
return nil
})
return numBkts, err
}

func (c *DeploymentDataManager) destroy() error {
return c.db.Update(func(tx *bolt.Tx) error {
if err := tx.DeleteBucket(envVariableBucket); err != nil {
Expand Down
43 changes: 43 additions & 0 deletions daemon/inertiad/project/data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,49 @@ func TestDataManager_EnvVariableOperations(t *testing.T) {
}
}

func TestDataManager_ProjectBuildDataOperations(t *testing.T) {
type args struct {
projectName string
metadata DeploymentMetadata
numProjects int
}
tests := []struct {
name string
args args
wantErr bool
}{
{"valid project build", args{"projectB", DeploymentMetadata{"hash", "ID", "status", "time"}, 2}, false},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might want to add some potential error cases here - what if the project name is empty? etc.

}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := "./test_config"
err := os.Mkdir(dir, os.ModePerm)
assert.Nil(t, err)
defer os.RemoveAll(dir)

// Instantiate
c, err := NewDataManager(path.Join(dir, "deployment.db"), path.Join(dir, "key"))
assert.Nil(t, err)

// Add
err = c.AddProjectBuildData(tt.args.projectName, tt.args.metadata)
assert.Equal(t, tt.wantErr, (err != nil))
bobheadxi marked this conversation as resolved.
Show resolved Hide resolved

// Adding using same project name should only update existing bucket
err = c.AddProjectBuildData(tt.args.projectName, tt.args.metadata)
numBkts, err := c.GetNumOfDeployedProjects(tt.args.projectName)
assert.Nil(t, err)
assert.Equal(t, tt.args.numProjects, numBkts)

// Adding using diff project name should create new bucket
err = c.AddProjectBuildData(tt.args.projectName+"_new", tt.args.metadata)
numBkts, err = c.GetNumOfDeployedProjects(tt.args.projectName)
assert.Nil(t, err)
assert.Equal(t, tt.args.numProjects+1, numBkts)
})
}
}

func TestDataManager_destroy(t *testing.T) {
dir := "./test_config"
err := os.Mkdir(dir, os.ModePerm)
Expand Down
64 changes: 64 additions & 0 deletions daemon/inertiad/project/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ type Deployer interface {
GetBranch() string
CompareRemotes(string) error

UpdateContainerHistory(cli *docker.Client) error

GetDataManager() (*DeploymentDataManager, bool)

Watch(*docker.Client) (<-chan string, <-chan error)
Expand Down Expand Up @@ -70,6 +72,15 @@ type DeploymentConfig struct {
PemFilePath string
}

// DeploymentMetadata is used to store metadata relevant
// to the most recent deployment
type DeploymentMetadata struct {
Hash string
ContainerID string
ContainerStatus string
StartedAt string
}

// NewDeployment creates a new deployment
func NewDeployment(
projectDirectory string,
Expand Down Expand Up @@ -314,6 +325,59 @@ func (d *Deployment) CompareRemotes(remoteURL string) error {
return nil
}

// UpdateContainerHistory will update container bucket with recent build's
// metadata
func (d *Deployment) UpdateContainerHistory(cli *docker.Client) error {

// Get project hash
head, err := d.repo.Head()
if err != nil {
return fmt.Errorf("failed fetching repo head when updating container history: %s", err.Error())
}
// Retrieve container for recently deployed project
ctx := context.Background()
var recentlyBuiltContainer types.Container
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{})
if err != nil {
return fmt.Errorf("failure fetching list of containers: %s", err.Error())
}
for _, container := range containers {
if container.Names[0] == d.project {
bobheadxi marked this conversation as resolved.
Show resolved Hide resolved
recentlyBuiltContainer = container
}
}

// Get container metadata
var containerID string
if len(recentlyBuiltContainer.ID) > 0 {
containerID = recentlyBuiltContainer.ID
}
containerJSON, err := cli.ContainerInspect(ctx, containerID)
if err != nil {
return fmt.Errorf("failure fetching container metadata: %s", err.Error())
}
containerState := containerJSON.ContainerJSONBase.State // similar to running "docker inspect {container}"
var containerStatus, containerStartedAtTime string
if containerState != nil {
containerStatus = containerState.Status
containerStartedAtTime = containerState.StartedAt
}

metadata := DeploymentMetadata{
Hash: head.Hash().String(),
ContainerID: containerID,
ContainerStatus: containerStatus,
StartedAt: containerStartedAtTime}

// Update db with newly built container metadata
err = d.dataManager.AddProjectBuildData(d.project, metadata)
if err != nil {
return fmt.Errorf("failure adding build metadata: %s", err.Error())
}

return nil
}

// GetDataManager returns the class managing deployment data
func (d *Deployment) GetDataManager() (manager *DeploymentDataManager, found bool) {
if d.dataManager == nil {
Expand Down
73 changes: 73 additions & 0 deletions daemon/inertiad/project/mocks/deployer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Empty file added make
Empty file.
Empty file modified test/keys/id_rsa
100755 → 100644
Empty file.