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

Add a converge command #66

Closed
wants to merge 12 commits into from
13 changes: 10 additions & 3 deletions command/command_tree.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package command

import (
"github.com/PacketFire/immigrant/command/converge"
"github.com/PacketFire/immigrant/command/version"

"github.com/PacketFire/immigrant/pkg/config"
"github.com/mitchellh/cli"
)

const (
cliVersion string = "0.0.1"
)

func init() {
Register("version", func(ui cli.Ui) (cli.Command, error) { return version.New(ui, cliVersion), nil })
// Build takes a config as an argument and attempts to construct a cli registry,
// injecting the config into each method.
func Build(conf config.Config) Registry {
r := make(Registry)
r.Register("version", func(ui cli.Ui) (cli.Command, error) { return version.New(conf, ui, cliVersion), nil })
r.Register("converge", func(ui cli.Ui) (cli.Command, error) { return converge.New(conf, ui), nil })

return r
}
38 changes: 38 additions & 0 deletions command/converge/converge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package converge

import (
"github.com/PacketFire/immigrant/pkg/config"
"github.com/mitchellh/cli"
)

// New initializes the converge command.
func New(conf config.Config, ui cli.Ui) *Cmd {
return &Cmd{UI: ui, config: conf}
}

// Cmd represents a command within mitchellh/cli and stores all the config
// necessary to execute the version command.
type Cmd struct {
UI cli.Ui
config config.Config
}

// Run executes the converge command. An integer representing the success of
//the execution is returned. This is currently a stub.
func (c *Cmd) Run(_ []string) int {

c.UI.Output("STUB")

return 0
}

// Synopsis returns a short help string for the converge command.
func (c *Cmd) Synopsis() string {
return "Converges local revision tree onto the configured store"
}

// Help returns an empty string as no additional information is needed to
// execute this command.
func (c *Cmd) Help() string {
return ""
}
15 changes: 15 additions & 0 deletions command/converge/converge_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package converge

import (
"github.com/PacketFire/immigrant/pkg/config"
"github.com/mitchellh/cli"
"strings"
"testing"
)

func TestHelpHasTabs(t *testing.T) {
t.Parallel()
if strings.ContainsRune(New(config.Config{}, cli.NewMockUi()).Help(), '\t') {
t.Fatal("help has tabs")
}
}
22 changes: 9 additions & 13 deletions command/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,24 @@ import (
// Factory is a function that returns a new instance of a CLI-sub command.
type Factory func(cli.Ui) (cli.Command, error)

// Register adds a new CLI sub-command to the registry.
func Register(name string, fn Factory) {
if registry == nil {
registry = make(map[string]Factory)
}
// Registry maps to a map of Factory types with a string key.
type Registry map[string]Factory

if registry[name] != nil {
// Register adds a new CLI sub-command to the registry.
func (r Registry) Register(name string, fn Factory) {
if _, prs := r[name]; prs != false {
panic(fmt.Errorf("Command %q is already registered", name))
}
registry[name] = fn

r[name] = fn
}

// Map returns a realized mapping of available CLI commands in a format that
// the CLI class can consume. This should be called after all registration is
// complete.
func Map(ui cli.Ui) map[string]cli.CommandFactory {
func (r Registry) Map(ui cli.Ui) map[string]cli.CommandFactory {
m := make(map[string]cli.CommandFactory)
for name, fn := range registry {
for name, fn := range r {
thisFn := fn
m[name] = func() (cli.Command, error) {
return thisFn(ui)
Expand All @@ -38,10 +38,6 @@ func Map(ui cli.Ui) map[string]cli.CommandFactory {
return m
}

// registry has an entry for each available CLI sub-command, indexed by sub
// command name. This should be populated at package init() time via Register().
var registry map[string]Factory

// MakeShutdownCh returns a channel that can be used for shutdown notifications
// for commands. This channel will send a message for every interrupt or SIGTERM
// received.
Expand Down
8 changes: 5 additions & 3 deletions command/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@ package version

import (
"fmt"
"github.com/PacketFire/immigrant/pkg/config"

"github.com/mitchellh/cli"
)

// New initializes the version command.
func New(ui cli.Ui, version string) *Cmd {
return &Cmd{UI: ui, version: version}
func New(conf config.Config, ui cli.Ui, version string) *Cmd {
return &Cmd{UI: ui, config: conf, version: version}
}

// Cmd represents a command within mitchellh/cli and stores all the context
// necessary to execute the version command.
type Cmd struct {
UI cli.Ui
config config.Config
version string
}

// Run executes the version command, printing a version string for immigrant.'
// Run executes the version command, printing a version string for immigrant.
// the return value should always be 0 as this command should never fail.
func (c *Cmd) Run(_ []string) int {
c.UI.Output(fmt.Sprintf("immigrant %s", c.version))
Expand Down
3 changes: 2 additions & 1 deletion command/version/version_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package version

import (
"github.com/PacketFire/immigrant/pkg/config"
"github.com/mitchellh/cli"
"strings"
"testing"
)

func TestHelpHasTabs(t *testing.T) {
t.Parallel()
if strings.ContainsRune(New(cli.NewMockUi(), "").Help(), '\t') {
if strings.ContainsRune(New(config.Config{}, cli.NewMockUi(), "").Help(), '\t') {
t.Fatal("help has tabs")
}
}
10 changes: 0 additions & 10 deletions glide.yaml

This file was deleted.

3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"

"github.com/PacketFire/immigrant/command"
"github.com/PacketFire/immigrant/pkg/config"
"github.com/mitchellh/cli"
)

Expand All @@ -26,7 +27,7 @@ func main() {
}

ui := &cli.BasicUi{Writer: os.Stdout, ErrorWriter: os.Stderr}
cmds := command.Map(ui)
cmds := command.Build(config.Config{}).Map(ui)
var names []string
for c := range cmds {
names = append(names, c)
Expand Down
37 changes: 29 additions & 8 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,32 @@ import (
"path/filepath"
)

// ErrNoTypeSpecified is triggered when a driver type lookup occurs with no
// sprecified type field.
type ErrNoTypeSpecified struct{}

func (e *ErrNoTypeSpecified) Error() string {
return "driver type not specified"
}

// Config represents a KV mapping of string parameters to be passed to the
// database.
type Config map[string]string

// DriverType returns the string representation of the driver to load.
func (c Config) DriverType() (string, error) {
if v, prs := c["type"]; prs == true {
return v, nil
}

return "", &ErrNoTypeSpecified{}
}

// ParseConfig takes a path to the config directory and attempts to parse the
// config.yml file in that directory. On success a map[string]string is
// returned. On failure a map[string]string and an error is returned.
func ParseConfig(path string) (map[string]string, error) {
c := make(map[string]string)
// config.yml file in that directory. On success a Config is
// returned. On failure a Config and an error is returned.
func ParseConfig(path string) (Config, error) {
c := make(Config)

cp, err := configFileName(path)
if err != nil {
Expand Down Expand Up @@ -52,10 +73,10 @@ func configFileName(path string) (string, error) {
}

// unmarshalYamlConfig takes the raw yaml []byte and unmarshals it to a
// map[string]string. On success a map[string]string and an error are returned.
// on failure, a map[string]string and error is returned.
func unmarshalYamlConfig(yml []byte) (map[string]string, error) {
c := make(map[string]string)
// Config. On success a Config and an error are returned.
// on failure, a Config and error is returned.
func unmarshalYamlConfig(yml []byte) (Config, error) {
c := make(Config)
if err := yaml.Unmarshal(yml, c); err != nil {
return c, errors.New("Unable to unmarshal config file")
}
Expand Down
25 changes: 25 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

const (
testDir string = "test/"
errFmt string = "expected %v got %v"
)

func TestParseConfig(t *testing.T) {
Expand All @@ -18,3 +19,27 @@ func TestParseConfig(t *testing.T) {
t.Errorf("Config unmarshal failing.")
}
}

func TestInvokingDriverTypeShould(t *testing.T) {
t.Run("return a string representation of the type and nil when type key is defined.", func(t *testing.T) {
expectedType := "mock"
c := Config{
"type": expectedType,
}

receivedType, _ := c.DriverType()
if receivedType != expectedType {
t.Errorf(errFmt, expectedType, receivedType)
}
})

t.Run("return an error when type key is undefined.", func(t *testing.T) {
c := Config{}

_, e := c.DriverType()
if e == nil {
t.Errorf(errFmt, "error", e)
}
})

}
14 changes: 9 additions & 5 deletions pkg/core/driver.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package core

import (
"github.com/PacketFire/immigrant/pkg/config"
)

// Driver defines the necessary methods to interface a database with
// immigrant. State tracking, Migrations and Rollbacks should mostly be
// defined and implemented by the driver.
type Driver interface {
Init(map[string]string) error // Setup connection and state tracking.
Migrate(Revision) error // Execute a migration against the target database.
Rollback(Revision) error // Execute a rollback against the target database.
State() *Revision // State returns the current revision of the database.
Close() // Close a connection to the target database.
Init(config.Config) error // Setup connection and state tracking.
Migrate(Revision) error // Execute a migration against the target database.
Rollback(Revision) error // Execute a rollback against the target database.
State() *Revision // State returns the current revision of the database.
Close() // Close a connection to the target database.
}
34 changes: 34 additions & 0 deletions pkg/drivers/drivers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package drivers

import (
"fmt"
"github.com/PacketFire/immigrant/pkg/config"
"github.com/PacketFire/immigrant/pkg/core"
"github.com/PacketFire/immigrant/pkg/drivers/mock"
)

// ErrUnknownDriverType is returned when an unspecified driver type is
// designated.
type ErrUnknownDriverType struct {
t string
}

func (e *ErrUnknownDriverType) Error() string {
return fmt.Sprintf("unknown driver type %v", e.t)
}

// GenerateDriverFromConfig takes a config and attempts to return a
// corresponding driver as derived from the config.
func GenerateDriverFromConfig(c config.Config) (core.Driver, error) {
dt, e := c.DriverType()
if e != nil {
return nil, e
}

switch dt {
case "mock":
return &mock.Driver{}, nil
}

return nil, &ErrUnknownDriverType{dt}
}
24 changes: 24 additions & 0 deletions pkg/drivers/drivers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package drivers

import (
"reflect"
"testing"

"github.com/PacketFire/immigrant/pkg/config"
)

const errFmt string = "expected %v got %v"

func TestPassingAConfigWithExplicitTypeToGenerateDriverFromConfigMethodShould(t *testing.T) {
t.Run("return a mock driver when type is mock.", func(t *testing.T) {
c := config.Config{
"type": "mock",
}

d, _ := GenerateDriverFromConfig(c)
dType := reflect.TypeOf(d).String()
if dType != "*mock.Driver" {
t.Errorf(errFmt, "*mock.Driver", dType)
}
})
}
3 changes: 2 additions & 1 deletion pkg/drivers/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mock
import (
"errors"

"github.com/PacketFire/immigrant/pkg/config"
"github.com/PacketFire/immigrant/pkg/core"
)

Expand All @@ -15,7 +16,7 @@ type Driver struct {
// Init mocks the requirements for Init on the
// github.com/PacketFire/immigrant/pkg/core.Driver interface. This method
// always returns a successful call.
func (dri *Driver) Init(config map[string]string) error {
func (dri *Driver) Init(config config.Config) error {
return nil
}

Expand Down
Loading