Skip to content

Commit

Permalink
documenting a bunch of things
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisMcKenzie committed Nov 4, 2015
1 parent 8a27e4e commit c877deb
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 144 deletions.
6 changes: 2 additions & 4 deletions installer/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ package installer

import "io"

// Installer is an interface that allows different methods of writing
// the given io.Reader to disk.
type Installer interface {
// Install Defines a Method that takes a destination path
// and a io.Reader and untars and gzip decodes a tarball and
// places the files inside on the FS with `dest` as their root
// It returns the number of files written and an error
Install(dest string, r io.Reader) (int, error)
}
4 changes: 4 additions & 0 deletions installer/tar.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import (

var ErrNilReader = errors.New("Install: must have a non-nil Reader")

// TarInstaller Defines an install Method that takes a destination path
// and a io.Reader and untars and gzip decodes a tarball and
// places the files inside on the FS with `dest` as their root
// It returns the number of files written and an error
type TarInstaller struct{}

func (i TarInstaller) Install(dest string, fr io.Reader) (count int, err error) {
Expand Down
2 changes: 2 additions & 0 deletions lock/consul.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"github.com/hashicorp/consul/api"
)

// ConsulLocker is a Locker that will use consul as the coordinator for
// establish a lock amongst multiple machines
type ConsulLocker struct {
semaphore *api.Semaphore
}
Expand Down
8 changes: 8 additions & 0 deletions lock/locker.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
package lock

// Locker is an interface that allows you to block execution of
// another process across a set of machines.
type Locker interface {
// Acquire takes a shutdownCh and return a lock chan and error
//
// the lock chan can be used to block the process until the lock
// has been acquired and the chan receives.
Acquire(<-chan struct{}) (<-chan struct{}, error)
// Release will release the lock allowing for other processes to
// acquire.
Release() error
}
9 changes: 4 additions & 5 deletions package.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@

mkdir -p packaging/output

set -x
GOARCH=amd64 GOOS=linux go build -o packaging/root/usr/local/bin/dropship main.go
GOARCH=$1 GOOS=$2 go build -o packaging/root/usr/local/bin/dropship main.go

fpm -s dir -t deb -n dropship -v "$1" -p packaging/output/dropship.deb \
fpm -s dir -t deb -n dropship -v "$1" -p packaging/output/dropship.$2-$1.deb \
--deb-priority optional --category admin \
--force \
--after-install packaging/scripts/postinstall.deb \
Expand All @@ -14,8 +13,8 @@ fpm -s dir -t deb -n dropship -v "$1" -p packaging/output/dropship.deb \
--url https://github.com/chrismckenzie/dropship \
--description "Dropship automatically keeps you software up to date" \
-m "Chris McKenzie <[email protected]>" \
-a amd64 \
-a $1 \
--config-files /etc/dropship.d/dropship.hcl \
packaging/root/=/

cp packaging/output/dropship.deb .
cp packaging/output/dropship.$2-$1.deb .
138 changes: 138 additions & 0 deletions service/dispatcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package service

import (
"errors"
"log"
"os/exec"
"strings"
"time"

"github.com/ChrisMcKenzie/dropship/installer"
"github.com/ChrisMcKenzie/dropship/lock"
"github.com/ChrisMcKenzie/dropship/updater"
"github.com/hashicorp/consul/api"
"github.com/spf13/viper"
)

// Dispatcher is responsible for managing a given services state and
// sending work to the Runner pool
type Dispatcher struct {
config Config
ticker *time.Ticker
task *Runner
hash string
shutdownCh <-chan struct{}
}

func NewDispatcher(cfg Config, t *Runner, shutdownCh <-chan struct{}) (*Dispatcher, error) {
w := Dispatcher{
config: cfg,
task: t,
shutdownCh: shutdownCh,
}

dur, err := time.ParseDuration(cfg.CheckInterval)
if err != nil {
return nil, err
}
w.ticker = time.NewTicker(dur)

go w.start()

return &w, nil
}

func (w *Dispatcher) start() {
for {
select {
case <-w.ticker.C:
w.task.Do(w)
case _, ok := <-w.shutdownCh:
if !ok {
log.Printf("Shutting down dispatcher for %s", w.config.Name)
w.ticker.Stop()
return
}
}
}
}

func (w *Dispatcher) Work() {
log.Printf("[INF]: Starting Update check for %s...", w.config.Name)
user := viper.GetString("rackspaceUser")
key := viper.GetString("rackspaceKey")
region := viper.GetString("rackspaceRegion")

u := updater.NewRackspaceUpdater(user, key, region)
opts := &updater.Options{w.config.Artifact.Bucket, w.config.Artifact.Path}

isOutOfDate, err := u.IsOutdated(w.hash, opts)
if err != nil {
log.Printf("[ERR]: Unable to check updates for %s %v", w.config.Name, err)
return
}

if isOutOfDate {
if w.config.Sequential {
log.Printf("[INF]: Acquiring lock for %s", w.config.Name)
l, err := lock.NewConsulLocker("dropship/services/"+w.config.Name, api.DefaultConfig())
if err != nil {
log.Printf("[ERR]: Unable to retreive update lock. %v", err)
return
}
_, err = l.Acquire(w.shutdownCh)
if err != nil {
log.Printf("[ERR]: Unable to retreive update lock. %v", err)
return
}
defer l.Release()
}

log.Printf("[INF]: Installing update for %s...", w.config.Name)
fr, meta, err := u.Download(opts)
if err != nil {
log.Printf("[ERR]: Unable to download update for %s %v", w.config.Name, err)
return
}

i, err := getInstaller(meta.ContentType)
if err != nil {
log.Printf("[ERR]: %s for %s", w.config.Name, err)
return
}

filesWritten, err := i.Install(w.config.Artifact.Destination, fr)
if err != nil {
log.Printf("[ERR]: Unable to install update for %s %s", w.config.Name, err)
}

if w.config.PostCommand != "" {
res, err := executeCommand(w.config.PostCommand)
if err != nil {
log.Printf("[ERR]: Unable to execute postComment. %v", err)
}
log.Printf("[INF]: postCommand executed successfully. %v", res)
}

log.Printf("[INF]: Update for %s installed successfully. [hash: %s] [files written: %d]", w.config.Name, meta.Hash, filesWritten)
w.hash = meta.Hash
} else {
log.Printf("[INF]: %s is up to date", w.config.Name)
}
}

func executeCommand(c string) (string, error) {
cmd := strings.Fields(c)
out, err := exec.Command(cmd[0], cmd[1:]...).Output()
return string(out), err
}

func getInstaller(contentType string) (installer.Installer, error) {
switch contentType {
case "application/x-gzip":
var installer installer.TarInstaller
return installer, nil
}

return nil, errors.New("Unable to determine installation method from file type")
}
3 changes: 3 additions & 0 deletions service/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package service

import "sync"

// Worker is a data type that can perform work.
type Worker interface {
Work()
}

// Runner is a type of worker pool that takes its work over a channel
// this allows for a dispatcher to actually signal the work.
type Runner struct {
work chan Worker
wg sync.WaitGroup
Expand Down
135 changes: 0 additions & 135 deletions service/service.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,5 @@
package service

import (
"errors"
"log"
"os/exec"
"strings"
"time"

"github.com/ChrisMcKenzie/dropship/installer"
"github.com/ChrisMcKenzie/dropship/lock"
"github.com/ChrisMcKenzie/dropship/updater"
"github.com/hashicorp/consul/api"
"github.com/spf13/viper"
)

type Artifact struct {
Type string `hcl:",key"`
Bucket string `hcl:"bucket"`
Expand All @@ -28,124 +14,3 @@ type Config struct {
Sequential bool `hcl:"sequentialUpdates"`
Artifact Artifact `hcl:"artifact,expand"`
}

type Dispatcher struct {
config Config
ticker *time.Ticker
task *Runner
hash string
shutdownCh <-chan struct{}
}

func NewDispatcher(cfg Config, t *Runner, shutdownCh <-chan struct{}) (*Dispatcher, error) {
w := Dispatcher{
config: cfg,
task: t,
shutdownCh: shutdownCh,
}

dur, err := time.ParseDuration(cfg.CheckInterval)
if err != nil {
return nil, err
}
w.ticker = time.NewTicker(dur)

go w.start()

return &w, nil
}

func (w *Dispatcher) start() {
for {
select {
case <-w.ticker.C:
w.task.Do(w)
case _, ok := <-w.shutdownCh:
if !ok {
log.Printf("Shutting down dispatcher for %s", w.config.Name)
w.ticker.Stop()
return
}
}
}
}

func (w *Dispatcher) Work() {
log.Printf("[INF]: Starting Update check for %s...", w.config.Name)
user := viper.GetString("rackspaceUser")
key := viper.GetString("rackspaceKey")
region := viper.GetString("rackspaceRegion")

u := updater.NewRackspaceUpdater(user, key, region)
opts := &updater.Options{w.config.Artifact.Bucket, w.config.Artifact.Path}

isOutOfDate, err := u.IsOutdated(w.hash, opts)
if err != nil {
log.Printf("[ERR]: Unable to check updates for %s %v", w.config.Name, err)
return
}

if isOutOfDate {
if w.config.Sequential {
log.Printf("[INF]: Acquiring lock for %s", w.config.Name)
l, err := lock.NewConsulLocker("dropship/services/"+w.config.Name, api.DefaultConfig())
if err != nil {
log.Printf("[ERR]: Unable to retreive update lock. %v", err)
return
}
_, err = l.Acquire(w.shutdownCh)
if err != nil {
log.Printf("[ERR]: Unable to retreive update lock. %v", err)
return
}
defer l.Release()
}

log.Printf("[INF]: Installing update for %s...", w.config.Name)
fr, meta, err := u.Download(opts)
if err != nil {
log.Printf("[ERR]: Unable to download update for %s %v", w.config.Name, err)
return
}

i, err := getInstaller(meta.ContentType)
if err != nil {
log.Printf("[ERR]: %s for %s", w.config.Name, err)
return
}

filesWritten, err := i.Install(w.config.Artifact.Destination, fr)
if err != nil {
log.Printf("[ERR]: Unable to install update for %s %s", w.config.Name, err)
}

if w.config.PostCommand != "" {
res, err := executeCommand(w.config.PostCommand)
if err != nil {
log.Printf("[ERR]: Unable to execute postComment. %v", err)
}
log.Printf("[INF]: postCommand executed successfully. %v", res)
}

log.Printf("[INF]: Update for %s installed successfully. [hash: %s] [files written: %d]", w.config.Name, meta.Hash, filesWritten)
w.hash = meta.Hash
} else {
log.Printf("[INF]: %s is up to date", w.config.Name)
}
}

func executeCommand(c string) (string, error) {
cmd := strings.Fields(c)
out, err := exec.Command(cmd[0], cmd[1:]...).Output()
return string(out), err
}

func getInstaller(contentType string) (installer.Installer, error) {
switch contentType {
case "application/x-gzip":
var installer installer.TarInstaller
return installer, nil
}

return nil, errors.New("Unable to determine installation method from file type")
}
2 changes: 2 additions & 0 deletions updater/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package updater

import "io"

// Updater is an interface that defines methods for checking a files
// freshness and downloading an updated version if needed.
type Updater interface {
IsOutdated(hash string, opts *Options) (bool, error)
Download(*Options) (io.ReadCloser, MetaData, error)
Expand Down

0 comments on commit c877deb

Please sign in to comment.