Skip to content
Closed
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
439 changes: 439 additions & 0 deletions integration/instance.go

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions integration/lang/simple.mcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# minimal example to make integrationtest pass
$root = getenv("MGMT_TEST_ROOT")

file "${root}/pass" {
content => "not empty",
}
112 changes: 112 additions & 0 deletions integration/sanity_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Mgmt
// Copyright (C) 2013-2018+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package integration

import (
"net/http"
"os/exec"
"strings"
"testing"

errwrap "github.com/pkg/errors"
)

const (
stringExistingInHelp = "--version, -v print the version"
)

func TestHelp(t *testing.T) {
out, err := exec.Command(mgmt).Output()
if err != nil {
t.Fatalf("Error: %v", err)
}
if !strings.Contains(string(out), stringExistingInHelp) {
t.Logf("Command output: %s", string(out))
t.Fatal("Expected output not found")
}
}

// TestSmoketest makes sure the most basic functionality works.
// If this test fails assumptions made by the rest of the testsuite are invalid.
func TestSmoketest(t *testing.T) {
// create an mgmt test environment and ensure cleanup/debug logging on failure/exit
m := Instance{}
defer m.Cleanup(t)

// run mgmt to convergence
m.Run(t)

// verify output contains what is expected from a converging and finished run
m.Finished(t, true)
}

// TestSimple applies a simple mcl file and tests the result.
// If this test fails assumptions made by the rest of the testsuite are invalid.
func TestSimple(t *testing.T) {
// create an mgmt test environment and ensure cleanup/debug logging on failure/exit
m := Instance{}
defer m.Cleanup(t)

// apply the configration from lang/simple.mcl
m.RunLang(t, "lang/simple.mcl")

// verify output contains what is expected from a converging and finished run
m.Finished(t, true)

// verify if a non-empty `pass` file is created in the working directory
m.Pass(t)
}

// TestDeploy checks if background running and deployment works.
// If this test fails assumptions made by the rest of the testsuite are invalid.
func TestDeploy(t *testing.T) {
// create an mgmt test environment and ensure cleanup/debug logging on failure/exit
m := Instance{}
defer m.Cleanup(t)

// start a mgmt instance running in the background
m.RunBackground(t)

// wait until server is up and running
m.WaitUntilIdle(t)

// expect mgmt to listen on default client and server url
if _, err := http.Get("http://127.0.0.1:2379"); err != nil {
t.Fatal("default client url is not reachable over tcp")
}
if _, err := http.Get("http://127.0.0.1:2380"); err != nil {
t.Fatal("default server url is not reachable over tcp")
}

// deploy lang file to the just started instance
out, err := m.DeployLangFile(nil, "lang/simple.mcl")
if err != nil {
t.Fatal(errwrap.Wrapf(err, "deploy command failed, output: %s", out))
}
// wait for deploy to come to a rest
m.WaitUntilConverged(t)

// stop the running instance
m.StopBackground(t)

// verify output contains what is expected from a converged and cleanly finished run
m.Finished(t, false)

// verify if a non-empty `pass` file is created in the working directory
m.Pass(t)
}
74 changes: 74 additions & 0 deletions integration/shared_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Mgmt
// Copyright (C) 2013-2018+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package integration

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

var mgmt string

// TestMain ensures a temporary environment is created (and cleaned afterward) to perform tests in
func TestMain(m *testing.M) {
// get absolute path for mgmt binary from testenvironment
mgmt = os.Getenv("MGMT")
// fallback to assumption based on current directory if path is not provided
if mgmt == "" {
path, err := filepath.Abs("../mgmt")
if err != nil {
log.Printf("failed to get absolute mgmt path")
os.Exit(1)
}
mgmt = path
}
if _, err := os.Stat(mgmt); os.IsNotExist(err) {
log.Printf("mgmt executable %s does not exist", mgmt)
os.Exit(1)
}

// move to clean/stateless directory before running tests
cwd, err := os.Getwd()
if err != nil {
log.Printf("failed to get current directory")
os.Exit(1)
}
tmpdir, err := ioutil.TempDir("", "mgmt-integrationtest")
if err != nil {
log.Printf("failed to create test working directory")
os.Exit(1)
}
if err := os.Chdir(tmpdir); err != nil {
log.Printf("failed to enter test working directory")
os.Exit(1)
}

// run all the tests
os.Exit(m.Run())

// and back to where we started
os.Chdir(cwd)

if err := os.RemoveAll(tmpdir); err != nil {
log.Printf("failed to remove working directory")
os.Exit(1)
}
}
33 changes: 33 additions & 0 deletions lib/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,39 @@ func CLI(program, version string, flags Flags) error {
},
},
},
{
Name: "info",
Aliases: []string{"i"},
Usage: "info",
Action: info,
// Flags: ,
Subcommands: []cli.Command{
{
Name: "resources",
Aliases: []string{"res", "r"},
Usage: "list available resources",
Action: info,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "type, t",
Usage: "also show type info",
},
},
},
{
Name: "functions",
Aliases: []string{"funcs", "f"},
Usage: "list available functions",
Action: info,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "type, t",
Usage: "also show type info",
},
},
},
},
},
}
app.EnableBashCompletion = true
return app.Run(os.Args)
Expand Down
127 changes: 127 additions & 0 deletions lib/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Mgmt
// Copyright (C) 2013-2018+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package lib

import (
"bytes"
"fmt"
"os"
"reflect"
"sort"
"strings"
"text/tabwriter"

"github.com/purpleidea/mgmt/lang/funcs"
"github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/resources"
"github.com/urfave/cli"
)

const (
Copy link
Owner

Choose a reason for hiding this comment

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

https://golang.org/doc/effective_go.html#mixed-caps

probably these could be public if you want.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i know, i'm following the names set by the tabwriter package, let me know if you prefer proper go names instead

Copy link
Owner

Choose a reason for hiding this comment

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

oh gosh. Another inconsistency in their own style guide? Gah. Sorry. Do the right thing. If golangv2 fixes these things, we'll be ahead of the curve.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

twMinWidth = 0
twTabWidth = 8
twPadding = 2 // ensure columns have at least a space between them
twPadChar = ' ' // using a tab here creates 'jumpy' columns on output
twFlags = 0
)

// info wraps infocmd to produce output to stdout
func info(c *cli.Context) error {
output, err := infoCmd(c)
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, twMinWidth, twTabWidth, twPadding, twPadChar, twFlags)
fmt.Fprint(w, output.String())
w.Flush()
return nil
}

// infoCmd takes the cli context and returns the requested output
func infoCmd(c *cli.Context) (bytes.Buffer, error) {
var out bytes.Buffer
var names []string
descriptions := make(map[string]string)

switch c.Command.Name {
case "resources":
for _, name := range resources.RegisteredResourcesNames() {
names = append(names, name)

descriptions[name] = ""

res, err := resources.Lookup(name)
if err != nil {
continue
}

s := reflect.ValueOf(res).Elem()
typeOfT := s.Type()

var fields []string
Copy link
Owner

Choose a reason for hiding this comment

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

use, improve, or add to one of the functions in resources/util.go please,

if anything wouldn't we want the info sub commands to be separate functions?

for i := 0; i < s.NumField(); i++ {
field := typeOfT.Field(i)
// skip unexported fields
if field.PkgPath != "" {
continue
}
fieldname := field.Name
if fieldname == "BaseRes" {
continue
}
f := s.Field(i)
fieldtype := f.Type()
fields = append(fields, fmt.Sprintf("%s (%s)", strings.ToLower(fieldname), fieldtype))
}
descriptions[name] = strings.Join(fields, ", ")

}
case "functions":
for name := range funcs.RegisteredFuncs {
// skip internal functions (history, operations, etc)
if strings.HasPrefix(name, "_") {
continue
}
names = append(names, name)
descriptions[name] = ""
fn, err := funcs.Lookup(name)
if err != nil {
continue
}
if _, ok := fn.(interfaces.PolyFunc); !ok {
// TODO: skip for now, needs Build before Info
continue
}

// set function signature as description
descriptions[name] = strings.Replace(fn.Info().Sig.String(), "func", name, 1)
}
default:
return out, fmt.Errorf("invalid command")
}

sort.Strings(names)
for _, name := range names {
if c.Bool("type") {
fmt.Fprintf(&out, "%s\t%s\n", name, descriptions[name])
} else {
fmt.Fprintln(&out, name)
}
}
return out, nil
}
Loading