github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/commands/plugin_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"runtime"
    12  	"text/template"
    13  	"time"
    14  
    15  	"github.com/juju/cmd"
    16  	"github.com/juju/cmd/cmdtesting"
    17  	gitjujutesting "github.com/juju/testing"
    18  	jc "github.com/juju/testing/checkers"
    19  	gc "gopkg.in/check.v1"
    20  
    21  	"github.com/juju/juju/jujuclient"
    22  	"github.com/juju/juju/jujuclient/jujuclienttesting"
    23  	"github.com/juju/juju/testing"
    24  )
    25  
    26  type PluginSuite struct {
    27  	testing.FakeJujuXDGDataHomeSuite
    28  	oldPath string
    29  }
    30  
    31  var _ = gc.Suite(&PluginSuite{})
    32  
    33  func (suite *PluginSuite) SetUpTest(c *gc.C) {
    34  	//TODO(bogdanteleaga): Fix bash tests
    35  	if runtime.GOOS == "windows" {
    36  		c.Skip("bug 1403084: tests use bash scrips, will be rewritten for windows")
    37  	}
    38  	suite.FakeJujuXDGDataHomeSuite.SetUpTest(c)
    39  	suite.oldPath = os.Getenv("PATH")
    40  	os.Setenv("PATH", "/bin:"+gitjujutesting.HomePath())
    41  	jujuclienttesting.SetupMinimalFileStore(c)
    42  }
    43  
    44  func (suite *PluginSuite) TearDownTest(c *gc.C) {
    45  	os.Setenv("PATH", suite.oldPath)
    46  	suite.FakeJujuXDGDataHomeSuite.TearDownTest(c)
    47  }
    48  
    49  func (*PluginSuite) TestFindPlugins(c *gc.C) {
    50  	plugins := findPlugins()
    51  	c.Assert(plugins, gc.DeepEquals, []string{})
    52  }
    53  
    54  func (suite *PluginSuite) TestFindPluginsOrder(c *gc.C) {
    55  	suite.makeWorkingPlugin("foo", 0744)
    56  	suite.makeWorkingPlugin("bar", 0654)
    57  	suite.makeWorkingPlugin("baz", 0645)
    58  	plugins := findPlugins()
    59  	c.Assert(plugins, gc.DeepEquals, []string{"juju-bar", "juju-baz", "juju-foo"})
    60  }
    61  
    62  func (suite *PluginSuite) TestFindPluginsBadNames(c *gc.C) {
    63  	suite.makePlugin("juju-1foo", "", 0755)
    64  	suite.makePlugin("juju--foo", "", 0755)
    65  	suite.makePlugin("ajuju-foo", "", 0755)
    66  	plugins := findPlugins()
    67  	c.Assert(plugins, gc.DeepEquals, []string{})
    68  }
    69  
    70  func (suite *PluginSuite) TestFindPluginsIgnoreNotExec(c *gc.C) {
    71  	suite.makeWorkingPlugin("foo", 0644)
    72  	suite.makeWorkingPlugin("bar", 0666)
    73  	plugins := findPlugins()
    74  	c.Assert(plugins, gc.DeepEquals, []string{})
    75  }
    76  
    77  func (suite *PluginSuite) TestRunPluginExising(c *gc.C) {
    78  	suite.makeWorkingPlugin("foo", 0755)
    79  	ctx := cmdtesting.Context(c)
    80  	err := RunPlugin(ctx, "foo", []string{"some params"})
    81  	c.Assert(err, jc.ErrorIsNil)
    82  	c.Assert(cmdtesting.Stdout(ctx), gc.Equals, "foo some params\n")
    83  	c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "")
    84  }
    85  
    86  func (suite *PluginSuite) TestRunPluginWithFailing(c *gc.C) {
    87  	suite.makeFailingPlugin("foo", 2)
    88  	ctx := cmdtesting.Context(c)
    89  	err := RunPlugin(ctx, "foo", []string{"some params"})
    90  	c.Assert(err, gc.ErrorMatches, "subprocess encountered error code 2")
    91  	c.Assert(err, jc.Satisfies, cmd.IsRcPassthroughError)
    92  	c.Assert(cmdtesting.Stdout(ctx), gc.Equals, "failing\n")
    93  	c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "")
    94  }
    95  
    96  func (suite *PluginSuite) TestGatherDescriptionsInParallel(c *gc.C) {
    97  	// Make plugins that will deadlock if we don't start them in parallel.
    98  	// Each plugin depends on another one being started before they will
    99  	// complete. They make a full loop, so no sequential ordering will ever
   100  	// succeed.
   101  	suite.makeFullPlugin(PluginParams{Name: "foo", Creates: "foo", DependsOn: "bar"})
   102  	suite.makeFullPlugin(PluginParams{Name: "bar", Creates: "bar", DependsOn: "baz"})
   103  	suite.makeFullPlugin(PluginParams{Name: "baz", Creates: "baz", DependsOn: "error"})
   104  	suite.makeFullPlugin(PluginParams{Name: "error", ExitStatus: 1, Creates: "error", DependsOn: "foo"})
   105  
   106  	// If the code was wrong, GetPluginDescriptions would deadlock,
   107  	// so timeout after a short while
   108  	resultChan := make(chan []PluginDescription)
   109  	go func() {
   110  		resultChan <- GetPluginDescriptions()
   111  	}()
   112  	// 10 seconds is arbitrary but should always be generously long. Test
   113  	// actually only takes about 15ms in practice. But 10s allows for system hiccups, etc.
   114  	waitTime := 10 * time.Second
   115  	var results []PluginDescription
   116  	select {
   117  	case results = <-resultChan:
   118  		break
   119  	case <-time.After(waitTime):
   120  		c.Fatalf("took longer than %fs to complete.", waitTime.Seconds())
   121  	}
   122  
   123  	c.Assert(results, gc.HasLen, 4)
   124  	c.Assert(results[0].name, gc.Equals, "bar")
   125  	c.Assert(results[0].description, gc.Equals, "bar description")
   126  	c.Assert(results[1].name, gc.Equals, "baz")
   127  	c.Assert(results[1].description, gc.Equals, "baz description")
   128  	c.Assert(results[2].name, gc.Equals, "error")
   129  	c.Assert(results[2].description, gc.Equals, "error occurred running 'juju-error --description'")
   130  	c.Assert(results[3].name, gc.Equals, "foo")
   131  	c.Assert(results[3].description, gc.Equals, "foo description")
   132  }
   133  
   134  func (suite *PluginSuite) TestHelpPluginName(c *gc.C) {
   135  	suite.makeFullPlugin(PluginParams{Name: "foo"})
   136  	output := badrun(c, 0, "help", "foo")
   137  	expectedHelp := `foo longer help
   138  
   139  something useful
   140  `
   141  	c.Assert(output, gc.Matches, expectedHelp)
   142  }
   143  
   144  func (suite *PluginSuite) TestHelpPluginNameNotAPlugin(c *gc.C) {
   145  	output := badrun(c, 0, "help", "foo")
   146  	expectedHelp := "ERROR unknown command or topic for foo\n"
   147  	c.Assert(output, gc.Matches, expectedHelp)
   148  }
   149  
   150  func (suite *PluginSuite) TestHelpAsArg(c *gc.C) {
   151  	suite.makeFullPlugin(PluginParams{Name: "foo"})
   152  	output := badrun(c, 0, "foo", "--help")
   153  	expectedHelp := `foo longer help
   154  
   155  something useful
   156  `
   157  	c.Assert(output, gc.Matches, expectedHelp)
   158  }
   159  
   160  func (suite *PluginSuite) TestDebugAsArg(c *gc.C) {
   161  	suite.makeFullPlugin(PluginParams{Name: "foo"})
   162  	output := badrun(c, 0, "foo", "--debug")
   163  	expectedDebug := "some debug\n"
   164  	c.Assert(output, gc.Matches, expectedDebug)
   165  }
   166  
   167  func (suite *PluginSuite) setupClientStore(c *gc.C) {
   168  	// Plugins are run as either controller or model commands,
   169  	// and so require a current controller or account and model.
   170  	store := jujuclient.NewFileClientStore()
   171  	err := store.AddController("myctrl", jujuclient.ControllerDetails{
   172  		ControllerUUID: testing.ControllerTag.Id(),
   173  		CACert:         "fake",
   174  	})
   175  	c.Assert(err, jc.ErrorIsNil)
   176  	err = store.SetCurrentController("myctrl")
   177  	c.Assert(err, jc.ErrorIsNil)
   178  	err = store.UpdateAccount("myctrl", jujuclient.AccountDetails{
   179  		User:     "admin",
   180  		Password: "hunter2",
   181  	})
   182  	c.Assert(err, jc.ErrorIsNil)
   183  }
   184  
   185  func (suite *PluginSuite) TestJujuModelEnvVars(c *gc.C) {
   186  	suite.setupClientStore(c)
   187  	suite.makeFullPlugin(PluginParams{Name: "foo"})
   188  	output := badrun(c, 0, "foo", "-m", "mymodel", "-p", "pluginarg")
   189  	expectedDebug := "foo -m mymodel -p pluginarg\nmodel is:  mymodel\n"
   190  	c.Assert(output, gc.Matches, expectedDebug)
   191  }
   192  
   193  func (suite *PluginSuite) TestJujuControllerEnvVars(c *gc.C) {
   194  	suite.setupClientStore(c)
   195  	suite.makeFullPlugin(PluginParams{Name: "foo"})
   196  	output := badrun(c, 0, "foo", "-c", "myctrl", "-p", "pluginarg")
   197  	expectedDebug := "foo -c myctrl -p pluginarg\ncontroller is:  myctrl\n"
   198  	c.Assert(output, gc.Matches, expectedDebug)
   199  }
   200  
   201  func (suite *PluginSuite) makePlugin(fullName, script string, perm os.FileMode) {
   202  	filename := gitjujutesting.HomePath(fullName)
   203  	content := fmt.Sprintf("#!/bin/bash --norc\n%s", script)
   204  	ioutil.WriteFile(filename, []byte(content), perm)
   205  }
   206  
   207  func (suite *PluginSuite) makeWorkingPlugin(name string, perm os.FileMode) {
   208  	script := fmt.Sprintf("echo %s $*", name)
   209  	suite.makePlugin(JujuPluginPrefix+name, script, perm)
   210  }
   211  
   212  func (suite *PluginSuite) makeFailingPlugin(name string, exitStatus int) {
   213  	script := fmt.Sprintf("echo failing\nexit %d", exitStatus)
   214  	suite.makePlugin(JujuPluginPrefix+name, script, 0755)
   215  }
   216  
   217  type PluginParams struct {
   218  	Name       string
   219  	ExitStatus int
   220  	Creates    string
   221  	DependsOn  string
   222  }
   223  
   224  const pluginTemplate = `#!/bin/bash --norc
   225  
   226  if [ "$1" = "--description" ]; then
   227    if [ -n "{{.Creates}}" ]; then
   228      touch "{{.Creates}}"
   229    fi
   230    if [ -n "{{.DependsOn}}" ]; then
   231      # Sleep 10ms while waiting to allow other stuff to do work
   232      while [ ! -e "{{.DependsOn}}" ]; do sleep 0.010; done
   233    fi
   234    echo "{{.Name}} description"
   235    exit {{.ExitStatus}}
   236  fi
   237  
   238  if [ "$1" = "--help" ]; then
   239    echo "{{.Name}} longer help"
   240    echo ""
   241    echo "something useful"
   242    exit {{.ExitStatus}}
   243  fi
   244  
   245  if [ "$1" = "--debug" ]; then
   246    echo "some debug"
   247    exit {{.ExitStatus}}
   248  fi
   249  
   250  echo {{.Name}} $*
   251  if [ -n "$JUJU_MODEL" ]; then
   252    echo "model is: " $JUJU_MODEL
   253  fi
   254  if [ -n "$JUJU_CONTROLLER" ]; then
   255    echo "controller is: " $JUJU_CONTROLLER
   256  fi
   257  exit {{.ExitStatus}}
   258  `
   259  
   260  func (suite *PluginSuite) makeFullPlugin(params PluginParams) {
   261  	// Create a new template and parse the plugin into it.
   262  	t := template.Must(template.New("plugin").Parse(pluginTemplate))
   263  	content := &bytes.Buffer{}
   264  	filename := gitjujutesting.HomePath("juju-" + params.Name)
   265  	// Create the files in the temp dirs, so we don't pollute the working space
   266  	if params.Creates != "" {
   267  		params.Creates = gitjujutesting.HomePath(params.Creates)
   268  	}
   269  	if params.DependsOn != "" {
   270  		params.DependsOn = gitjujutesting.HomePath(params.DependsOn)
   271  	}
   272  	t.Execute(content, params)
   273  	ioutil.WriteFile(filename, content.Bytes(), 0755)
   274  }