Skip to content

Commit

Permalink
Merge pull request #146 from carolynvs/manifest-schema
Browse files Browse the repository at this point in the history
Add porter schema root command
  • Loading branch information
carolynvs-msft authored Mar 1, 2019
2 parents e61b9d3 + 106ccfe commit f0b5619
Show file tree
Hide file tree
Showing 23 changed files with 555 additions and 85 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ We use [Hugo](gohugo.io) to build our documentation site, and it is hosted on [N
1. Run `make docs-preview` to start Hugo. It will watch the file system for changes.
1. Open <http://localhost:1313> to preview the site.

If anyone is interested in contributing changes to our makefile to improve the authoring exerience, such
If anyone is interested in contributing changes to our makefile to improve the authoring experience, such
as doing this with Docker so that you don't need Hugo installed, it would be a welcome contribution! ❤️

[good-first-issue]: https://github.com/deislabs/porter/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+label%3Abacklog+
Expand Down
1 change: 1 addition & 0 deletions cmd/porter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func buildRootCommand() *cobra.Command {
cmd.PersistentFlags().BoolVar(&p.Debug, "debug", false, "Enable debug logging")

cmd.AddCommand(buildVersionCommand(p))
cmd.AddCommand(buildSchemaCommand(p))
cmd.AddCommand(buildCreateCommand(p))
cmd.AddCommand(buildRunCommand(p))
cmd.AddCommand(buildBuildCommand(p))
Expand Down
2 changes: 1 addition & 1 deletion cmd/porter/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func buildRunCommand(p *porter.Porter) *cobra.Command {
if opts.rawAction == "" {
opts.rawAction = os.Getenv(config.EnvACTION)
if p.Debug {
fmt.Fprintf(p.Out, "DEBUG: defaulting action to %s (%s)\n", config.EnvACTION, opts.rawAction)
fmt.Fprintf(p.Err, "DEBUG: defaulting action to %s (%s)\n", config.EnvACTION, opts.rawAction)
}
}

Expand Down
17 changes: 17 additions & 0 deletions cmd/porter/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import (
"github.com/deislabs/porter/pkg/porter"
"github.com/spf13/cobra"
)

func buildSchemaCommand(p *porter.Porter) *cobra.Command {
cmd := &cobra.Command{
Use: "schema",
Short: "Print the JSON schema for the Porter manifest",
RunE: func(cmd *cobra.Command, args []string) error {
return p.PrintManifestSchema()
},
}
return cmd
}
27 changes: 22 additions & 5 deletions pkg/config/actions.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
package config

import "fmt"
import (
"fmt"
"strings"
)

type Action string

const (
ActionInstall Action = "install"
ActionUpgrade Action = "upgrade"
ActionUninstall Action = "uninstall"
ActionInstall Action = "install"
ActionUpgrade Action = "upgrade"
ActionUninstall Action = "uninstall"
ErrInvalidAction string = "invalid action"
)

// IsSupportedAction determines if the value is an action supported by Porter.
func IsSupportedAction(value string) bool {
_, err := ParseAction(value)
return err == nil
}

// ParseAction converts a string into an Action, or returns an error message.
func ParseAction(value string) (Action, error) {
action := Action(value)
switch action {
case ActionInstall, ActionUpgrade, ActionUninstall:
return action, nil
default:
return "", fmt.Errorf("invalid action %q", value)
return "", fmt.Errorf("%s %q", ErrInvalidAction, value)
}
}

// IsInvalidActionError determines if an error is the error returned by ParseAction when
// a value isn't a valid action.
func IsInvalidActionError(err error) bool {
return strings.HasPrefix(err.Error(), ErrInvalidAction)
}
22 changes: 22 additions & 0 deletions pkg/config/actions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package config

import "testing"

func TestIsSupportedAction(t *testing.T) {
testcases := map[string]bool{
"install": true,
"upgrade": true,
"uninstall": true,
"status": false,
"INSTALL": false,
}

for action, wantSupported := range testcases {
t.Run(action, func(t *testing.T) {
gotSupported := IsSupportedAction(action)
if wantSupported != gotSupported {
t.Fatalf("IsSupportedAction(%q) failed, want %t, got %t", action, wantSupported, gotSupported)
}
})
}
}
14 changes: 9 additions & 5 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,21 @@ type Config struct {
Manifest *Manifest

porterHome string
templates *packr.Box
}

// New Config initializes a default porter configuration.
func New() *Config {
return &Config{
Context: context.New(),
Context: context.New(),
templates: NewTemplatesBox(),
}
}

func NewTemplatesBox() *packr.Box {
return packr.New("github.com/deislabs/porter/pkg/config/templates", "./templates")
}

// GetHomeDir determines the path to the porter home directory.
func (c *Config) GetHomeDir() (string, error) {
if c.porterHome != "" {
Expand Down Expand Up @@ -99,14 +105,12 @@ func (c *Config) GetPorterRuntimePath() (string, error) {

// GetPorterConfigTemplate returns a porter.yaml template file for use in new bundles
func (c *Config) GetPorterConfigTemplate() ([]byte, error) {
t := packr.New("templates", "./templates")
return t.Find(Name)
return c.templates.Find(Name)
}

// GetRunScriptTemplate returns a run.sh template for use in new bundles
func (c *Config) GetRunScriptTemplate() ([]byte, error) {
t := packr.New("templates", "./templates")
return t.Find(filepath.Base(RunScript))
return c.templates.Find(filepath.Base(RunScript))
}

// GetBundleManifest gets the path to another bundle's manifest.
Expand Down
13 changes: 6 additions & 7 deletions pkg/config/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ type TestConfig struct {

// NewTestConfig initializes a configuration suitable for testing, with the output buffered, and an in-memory file system.
func NewTestConfig(t *testing.T) *TestConfig {
cxt := context.NewTestContext(t)
c := &TestConfig{
Config: &Config{
Context: cxt.Context,
},
TestContext: cxt,
tc := context.NewTestContext(t)
cfg := New()
cfg.Context = tc.Context
return &TestConfig{
Config: cfg,
TestContext: tc,
}
return c
}

// InitializePorterHome initializes the test filesystem with the supporting files in the PORTER_HOME directory.
Expand Down
2 changes: 2 additions & 0 deletions pkg/exec/exec.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:generate packr2

package exec

import (
Expand Down
12 changes: 12 additions & 0 deletions pkg/mixin/exec/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package exec

import (
"testing"

"github.com/deislabs/porter/pkg/test"
)

// sad hack: not sure how to make a common test main for all my subpackages
func TestMain(m *testing.M) {
test.TestMainWithMockedCommandHandlers(m)
}
16 changes: 16 additions & 0 deletions pkg/mixin/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package mixin

// Metadata about a mixin
type Metadata struct {
// Mixin Name
Name string
// Mixin Directory
Dir string
// Path to the client executable
ClientPath string
// Version
// Repository or Source (where did it come from)
// Author
// Is it up to date
// etc
}
72 changes: 72 additions & 0 deletions pkg/mixin/provider/filesystem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package mixinprovider

import (
"bytes"
"io/ioutil"
"path/filepath"

"github.com/deislabs/porter/pkg/config"
"github.com/deislabs/porter/pkg/context"
"github.com/deislabs/porter/pkg/mixin"
"github.com/pkg/errors"
)

func NewFileSystem(config *config.Config) *FileSystem {
return &FileSystem{
Config: config,
}
}

type FileSystem struct {
*config.Config
}

func (p *FileSystem) GetMixins() ([]mixin.Metadata, error) {
mixinsDir, err := p.GetMixinsDir()
if err != nil {
return nil, err
}

files, err := p.FileSystem.ReadDir(mixinsDir)
if err != nil {
return nil, errors.Wrapf(err, "could not list the contents of the mixins directory %q", mixinsDir)
}

mixins := make([]mixin.Metadata, 0, len(files))
for _, file := range files {
if !file.IsDir() {
continue
}

mixinDir := filepath.Join(mixinsDir, file.Name())
mixins = append(mixins, mixin.Metadata{
Name: file.Name(),
ClientPath: filepath.Join(mixinDir, file.Name()),
Dir: mixinDir,
})
}

return mixins, nil
}

func (p *FileSystem) GetMixinSchema(m mixin.Metadata) (string, error) {
r := mixin.NewRunner(m.Name, m.Dir, false)
r.Command = "schema"

// Copy the existing context and tweak to pipe the output differently
mixinSchema := &bytes.Buffer{}
var mixinContext context.Context
mixinContext = *p.Context
mixinContext.Out = mixinSchema
if !p.Debug {
mixinContext.Err = ioutil.Discard
}
r.Context = &mixinContext

err := r.Run()
if err != nil {
return "", err
}

return mixinSchema.String(), nil
}
83 changes: 83 additions & 0 deletions pkg/mixin/provider/filesystem_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package mixinprovider

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

"github.com/deislabs/porter/pkg/config"
"github.com/deislabs/porter/pkg/mixin"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestFileSystem_GetMixins(t *testing.T) {
// Do this in a temp directory so that we can control which mixins show up in the list
os.Setenv(config.EnvHOME, os.TempDir())
defer os.Unsetenv(config.EnvHOME)

c := config.NewTestConfig(t)
c.FileSystem = &afero.Afero{Fs: afero.NewOsFs()} // Hit the real file system for this test

mixinsDir, err := c.GetMixinsDir()
require.Nil(t, err)

// Just copy in the exec and helm mixins
srcMixinsDir := filepath.Join(c.TestContext.FindBinDir(), "mixins")
c.CopyDirectory(filepath.Join(srcMixinsDir, "helm"), mixinsDir, true)
c.CopyDirectory(filepath.Join(srcMixinsDir, "exec"), mixinsDir, true)

p := NewFileSystem(c.Config)
mixins, err := p.GetMixins()

require.Nil(t, err)
require.Len(t, mixins, 2)
assert.Equal(t, mixins[0].Name, "exec")
assert.Equal(t, mixins[1].Name, "helm")

dir, err := os.Stat(mixins[0].Dir)
require.NoError(t, err)
assert.True(t, dir.IsDir())
assert.Equal(t, dir.Name(), "exec")

binary, err := os.Stat(mixins[0].ClientPath)
require.NoError(t, err)
assert.True(t, binary.Mode().IsRegular())
assert.Equal(t, binary.Name(), "exec")
}

func TestFileSystem_GetMixinSchema(t *testing.T) {
c := config.NewTestConfig(t)
// Hit the real file system for this test
c.FileSystem = &afero.Afero{Fs: afero.NewOsFs()}
c.NewCommand = exec.Command

// bin is my home now
binDir := c.TestContext.FindBinDir()
os.Setenv(config.EnvHOME, binDir)
defer os.Unsetenv(config.EnvHOME)

p := NewFileSystem(c.Config)
mixins, err := p.GetMixins()
require.NoError(t, err)

var e *mixin.Metadata
for _, m := range mixins {
if m.Name == "exec" {
e = &m
break
}
}
require.NotNil(t, e)

gotSchema, err := p.GetMixinSchema(*e)
require.NoError(t, err)

wantSchema, err := ioutil.ReadFile("../../exec/testdata/schema.json")
require.NoError(t, err)

assert.Equal(t, string(wantSchema), string(gotSchema))
}
10 changes: 5 additions & 5 deletions pkg/mixin/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ func (r *Runner) Validate() error {

func (r *Runner) Run() error {
if r.Debug {
fmt.Fprintf(r.Out, "DEBUG mixin: %s\n", r.Mixin)
fmt.Fprintf(r.Out, "DEBUG mixinDir: %s\n", r.mixinDir)
fmt.Fprintf(r.Out, "DEBUG file: %s\n", r.File)
fmt.Fprintf(r.Out, "DEBUG stdin:\n%s\n", r.Step)
fmt.Fprintf(r.Err, "DEBUG mixin: %s\n", r.Mixin)
fmt.Fprintf(r.Err, "DEBUG mixinDir: %s\n", r.mixinDir)
fmt.Fprintf(r.Err, "DEBUG file: %s\n", r.File)
fmt.Fprintf(r.Err, "DEBUG stdin:\n%s\n", r.Step)
}

mixinPath := r.getMixinPath()
Expand Down Expand Up @@ -84,7 +84,7 @@ func (r *Runner) Run() error {

prettyCmd := fmt.Sprintf("%s %s", cmd.Path, strings.Join(cmd.Args, " "))
if r.Debug {
fmt.Fprintln(r.Out, prettyCmd)
fmt.Fprintln(r.Err, prettyCmd)
}

err := cmd.Start()
Expand Down
Loading

0 comments on commit f0b5619

Please sign in to comment.