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

[FEATURE] Introduces homebrew task #67

Merged
merged 13 commits into from
Jun 20, 2018
2 changes: 2 additions & 0 deletions dev.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
up:
- go: 1.10.1
- golang_dep
- homebrew:
- curl
- custom:
name: Install shellcheck
met?: test -e /usr/local/Cellar/shellcheck
Expand Down
14 changes: 8 additions & 6 deletions pkg/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,30 +62,32 @@ func (e *Env) Environ() (vars []string) {
return vars
}

func (e *Env) getAndSplitPath() []string {
// GetPathParts gets paths in PATH environment variable
func (e *Env) GetPathParts() []string {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: do we need this to be public in the end?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not really...

return strings.Split(e.env["PATH"], ":")
}

func (e *Env) joinAndSetPath(elems ...string) {
// SetPathParts set PATH environment variable
func (e *Env) SetPathParts(elems ...string) {
e.env["PATH"] = strings.Join(elems, ":")
}

// PrependToPath inserts a new path at the beginning of the PATH variable
func (e *Env) PrependToPath(path string) {
elems := e.getAndSplitPath()
elems := e.GetPathParts()
elems = append([]string{path}, elems...)
e.joinAndSetPath(elems...)
e.SetPathParts(elems...)
}

// RemoveFromPath removes all path entries matching a substring
func (e *Env) RemoveFromPath(substring string) {
newElems := []string{}
for _, elem := range e.getAndSplitPath() {
for _, elem := range e.GetPathParts() {
if !strings.Contains(elem, substring) {
newElems = append(newElems, elem)
}
}
e.joinAndSetPath(newElems...)
e.SetPathParts(newElems...)
}

// Changed returns all changes made on the variables
Expand Down
72 changes: 72 additions & 0 deletions pkg/helpers/homebrew.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package helpers

import (
"path/filepath"
"strings"

"github.com/devbuddy/devbuddy/pkg/utils"
)

type caskroom struct {
prefix string
}

type cellar struct {
prefix string
}

// Homebrew represent an homebrew installation
type Homebrew struct {
caskroom *caskroom
cellar *cellar
}

// NewHomebrew is returning a new Cellar
func NewHomebrew() *Homebrew {
prefix := "/usr/local"

return &Homebrew{
cellar: &cellar{prefix: prefix},
caskroom: &caskroom{prefix: prefix},
}
}

// NewHomebrewWithPrefix is returning a new Cellar at prefix
func NewHomebrewWithPrefix(prefix string) *Homebrew {
return &Homebrew{
cellar: &cellar{prefix: prefix},
caskroom: &caskroom{prefix: prefix},
}
}

// IsInstalled returns true if `pkg` is installed in cellar or in caskroom
func (h *Homebrew) IsInstalled(formula string) (installed bool) {
path := buildFormulaPath(formula)

return h.cellar.IsInstalled(path) || h.caskroom.IsInstalled(path)
}

func buildFormulaPath(filename string) string {
Copy link
Member

Choose a reason for hiding this comment

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

The logic doesn't seem so obvious, I feel like we should explain what happens here.

results := strings.Split(filename, "/")
formula := results[len(results)-1]
return strings.TrimSuffix(formula, filepath.Ext(formula))
}

// IsInstalled returns true if formulua was installed in Caskrook
func (c *caskroom) IsInstalled(formula string) bool {
Copy link
Member

Choose a reason for hiding this comment

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

Those methods (also on cellar) looks like they should be private.

path := "/opt/homebrew-cask/Caskroom"

if !utils.PathExists(path) {
path = filepath.Join(c.prefix, "Caskroom")
}

return utils.PathExists(filepath.Join(path, formula))
}

// IsInstalled returns true if formulua was installed in cellar
func (c *cellar) IsInstalled(formula string) bool {
path := filepath.Join(c.prefix, "Cellar")

path = filepath.Join(path, formula)
return utils.PathExists(path)
Copy link
Member

Choose a reason for hiding this comment

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

Nit: return utils.PathExists(filepath.Join(h.prefix, "Cellar", pkg))

}
28 changes: 28 additions & 0 deletions pkg/helpers/homebrew_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package helpers

import (
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
)

func TestIsInCellar(t *testing.T) {
prefix, err := ioutil.TempDir("/tmp", "dad-brew")
Copy link
Member

Choose a reason for hiding this comment

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

  • is there a reason to specify the tmpdir? ioutil.TempDir("", ...)
  • we should clean this folder up after the test.

But all that is annoying, let's use Filet (it's already used in this project):
https://github.com/Flaque/filet#creating-temporary-directories

require.NoError(t, err, "ioutil.TempDir() failed")

cellarPath := filepath.Join(prefix, "Cellar")

caskroomPath := filepath.Join(prefix, "Caskroom")

os.MkdirAll(filepath.Join(cellarPath, "curl", "1.2.3"), os.ModePerm)
os.MkdirAll(filepath.Join(caskroomPath, "emacs", "26.1"), os.ModePerm)

h := NewHomebrewWithPrefix(prefix)

require.Truef(t, h.IsInstalled("curl"), "Curl is properly installed in Cellar %s", cellarPath)
Copy link
Member

Choose a reason for hiding this comment

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

I missed those require functions, nice!

require.Falsef(t, h.IsInstalled("vim"), "vim is missing from Homebrew %s", prefix)
require.True(t, h.IsInstalled("emacs"), "Emacs is properly installed in caskroom %s", caskroomPath)
}
76 changes: 76 additions & 0 deletions pkg/tasks/homebrew.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package tasks

import (
"fmt"
"strings"

"github.com/devbuddy/devbuddy/pkg/helpers"
)

func init() {
allTasks["homebrew"] = newHomebrew
}

type Homebrew struct {
formulas []string
}

func newHomebrew(config *taskConfig) (Task, error) {
task := &Homebrew{}

for _, value := range config.payload.([]interface{}) {
if v, ok := value.(string); ok {
task.formulas = append(task.formulas, v)
} else {
return nil, fmt.Errorf("invalid homebrew formulas")
}
}

if len(task.formulas) == 0 {
return nil, fmt.Errorf("no homebrew formulas specified")
}

return task, nil
}

func (h *Homebrew) name() string {
return "Homebrew"
}

func (h *Homebrew) header() string {
return strings.Join(h.formulas, ", ")
}

func (h *Homebrew) actions(ctx *context) (actions []taskAction) {
for _, f := range h.formulas {
actions = append(actions, &brewInstall{formula: f})
}
return
}

type brewInstall struct {
formula string
success bool
}

func (b *brewInstall) description() string {
return fmt.Sprintf("installing %s", b.formula)
}

func (b *brewInstall) needed(ctx *context) (bool, error) {
return !b.success, nil
}

func (b *brewInstall) run(ctx *context) error {
brew := helpers.NewHomebrew()

if !brew.IsInstalled(b.formula) {
Copy link
Member

Choose a reason for hiding this comment

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

This condition should be in the needed method, so the TaskRunner knows whether this action should be run or not.

err := command(ctx, "brew", "install", b.formula).AddOutputFilter("already satisfied").Run()
Copy link
Member

Choose a reason for hiding this comment

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

Does this AddOutputFilter make sense with brew?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah not really cause anyway we should just display those informations


if err != nil {
return fmt.Errorf("Homebrew failed: %s", err)
}
}
b.success = true
return nil
}
16 changes: 16 additions & 0 deletions pkg/tasks/homebrew_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package tasks

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestHomebrew(t *testing.T) {
task := ensureLoadTestTask(t, `
homebrew:
- file1
- file2
`)
require.Equal(t, task.(*Homebrew).formulas, []string{"file1", "file2"})
}