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 porter install command #229

Merged
merged 3 commits into from
Mar 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
58 changes: 58 additions & 0 deletions cmd/porter/bundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package main

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

func buildBundleCommands(p *porter.Porter) *cobra.Command {
cmd := &cobra.Command{
Use: "bundle",
Short: "bundle commands",
}

cmd.AddCommand(buildBundleInstallCommand(p))

return cmd
}

func buildBundleInstallCommand(p *porter.Porter) *cobra.Command {
opts := porter.InstallOptions{}
cmd := &cobra.Command{
Use: "install",
Short: "Install a bundle",
Example: ` porter install
porter install --insecure
porter install --file myapp/bundle.json
porter install --name MyAppInDev
porter install --param-file base-values.txt --param-file dev-values.txt --param test-mode=true --param header-color=blue
porter install --cred azure --cred kubernetes
`,
PreRunE: func(cmd *cobra.Command, args []string) error {
return opts.Prepare()
},
RunE: func(cmd *cobra.Command, args []string) error {
return p.InstallBundle(opts)
},
}

f := cmd.Flags()
f.BoolVar(&opts.Insecure, "insecure", false,
"Allow installing untrusted bundles")
f.StringVarP(&opts.File, "file", "f", "bundle.json",
"Path to the CNAB definition to install")
f.StringVar(&opts.Name, "name", "",
"Name of the claim, defaults to the name of the bundle")
f.StringSliceVar(&opts.ParamFiles, "param-file", nil,
"Path to a parameters definition file for the bundle, each line in the form of NAME=VALUE. May be specified multiple times.")
f.StringSliceVar(&opts.RawParams, "param", nil,
"Define an individual parameter in the form NAME=VALUE. Overrides parameters set with the same name using --param-file. May be specified multiple times.")
f.StringSliceVarP(&opts.CredentialSets, "cred", "c", nil,
"Credential to use when installing the bundle. May be either a named set of credentials or a filepath, and specified multiple times.")

return cmd
}

func buildInstallCommand(p *porter.Porter) *cobra.Command {
return buildBundleInstallCommand(p)
}
38 changes: 38 additions & 0 deletions cmd/porter/bundle_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"strings"
"testing"

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

func TestValidateInstallCommand(t *testing.T) {
testcases := []struct {
name string
args string
wantError string
}{
{"no args", "install", ""},
{"invalid param", "install --param A:B", "invalid parameter (A:B), must be in name=value format"},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
p := buildRootCommand()
osargs := strings.Split(tc.args, " ")
cmd, args, err := p.Find(osargs)
require.NoError(t, err)

err = cmd.ParseFlags(args)
require.NoError(t, err)

err = cmd.PreRunE(cmd, args)
if tc.wantError == "" {
require.NoError(t, err)
} else {
require.EqualError(t, err, tc.wantError)
}
})
}
}
2 changes: 2 additions & 0 deletions cmd/porter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ func buildRootCommand() *cobra.Command {
cmd.AddCommand(buildCreateCommand(p))
cmd.AddCommand(buildRunCommand(p))
cmd.AddCommand(buildBuildCommand(p))
cmd.AddCommand(buildBundleCommands(p))
cmd.AddCommand(buildInstallCommand(p))
cmd.AddCommand(buildListCommands(p))

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

import (
"strings"
"testing"

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

func TestCommandWiring(t *testing.T) {
testcases := []string{
"build",
"create",
"install",
"run",
"schema",
"bundle install",
"list mixins",
"version",
}

for _, tc := range testcases {
t.Run(tc, func(t *testing.T) {
osargs := strings.Split(tc, " ")

rootCmd := buildRootCommand()
_, _, err := rootCmd.Find(osargs)
assert.NoError(t, err)
})
}
}
48 changes: 48 additions & 0 deletions pkg/parameters/parameters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

This file has been modified from the original source
https://github.com/kubernetes-incubator/service-catalog/blob/129d98e6f6e3c65d16d47a26fcf3357f0c3478de/cmd/svcat/parameters/parameters.go
to use just one of the original functions that I (Carolyn Van Slyck) wrote for Service Catalog.
*/

package parameters

import (
"fmt"
"strings"
)

// ParseVariableAssignments converts a string array of variable assignments
// into a map of keys and values
// Example:
// [a=b c=abc1232=== d=banana d=pineapple] becomes map[a:b c:abc1232=== d:[pineapple]]
func ParseVariableAssignments(params []string) (map[string]string, error) {
variables := make(map[string]string)
for _, p := range params {

parts := strings.SplitN(p, "=", 2)
if len(parts) < 2 {
return nil, fmt.Errorf("invalid parameter (%s), must be in name=value format", p)
}

variable := strings.TrimSpace(parts[0])
if variable == "" {
return nil, fmt.Errorf("invalid parameter (%s), variable name is required", p)
}
value := strings.TrimSpace(parts[1])

variables[variable] = value
}

return variables, nil
}
61 changes: 61 additions & 0 deletions pkg/parameters/parameters_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

This file has been modified from the original source
https://github.com/kubernetes-incubator/service-catalog/blob/129d98e6f6e3c65d16d47a26fcf3357f0c3478de/cmd/svcat/parameters/parameters_test.go
to use just one of the original functions that I (Carolyn Van Slyck) wrote for Service Catalog.
*/

package parameters

import (
"reflect"
"testing"
)

func TestParseVariableAssignments(t *testing.T) {
testcases := []struct {
Name, Raw, Variable, Value string
}{
{"simple", "a=b", "a", "b"},
{"multiple equal signs", "c=abc1232===", "c", "abc1232==="},
{"empty value", "d=", "d", ""},
{"extra whitespace", " a = b ", "a", "b"},
}

for _, tc := range testcases {
t.Run(tc.Name, func(t *testing.T) {

params := []string{tc.Raw}

got, err := ParseVariableAssignments(params)
if err != nil {
t.Fatal(err)
}

want := make(map[string]string)
want[tc.Variable] = tc.Value
if !reflect.DeepEqual(want, got) {
t.Fatalf("%s\nexpected:\n\t%v\ngot:\n\t%v\n", tc.Raw, want, got)
}
})
}
}

func TestParseVariableAssignments_MissingVariableName(t *testing.T) {
params := []string{"=b"}

_, err := ParseVariableAssignments(params)
if err == nil {
t.Fatal("should have failed due to a missing variable name")
}
}
51 changes: 51 additions & 0 deletions pkg/porter/install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package porter

import (
"fmt"

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

type InstallOptions struct {
// Name of the claim.
Name string

// File path to the CNAB bundle.
File string

// Insecure bundle installation allowed.
Insecure bool

// RawParams is the unparsed list of NAME=VALUE parameters set on the command line.
RawParams []string

// Params is the parsed set of parameters from RawParams.
Params map[string]string

// ParamFiles is a list of file paths containing lines of NAME=VALUE parameter definitions.
ParamFiles []string

// CredentialSets is a list of credentialset names to make available to the bundle.
CredentialSets []string
}

func (o *InstallOptions) Prepare() error {
return o.parseParams()
}

func (o *InstallOptions) parseParams() error {
p, err := parameters.ParseVariableAssignments(o.RawParams)
if err == nil {
o.Params = p
}
return err
}

func (p *Porter) InstallBundle(opts InstallOptions) error {
err := p.Config.LoadManifest()
if err != nil {
return err
}
fmt.Fprintf(p.Out, "installing %s...\n", p.Manifest.Name)
return nil
}
19 changes: 19 additions & 0 deletions pkg/porter/install_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package porter

import (
"testing"

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

func TestInstallOptions_Prepare(t *testing.T) {
opts := InstallOptions{
RawParams: []string{"A=1", "B=2"},
}

err := opts.Prepare()
require.NoError(t, err)

assert.Len(t, opts.Params, 2)
}