diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index 4c2ebaf..99e0124 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -1,9 +1,7 @@
{
- "ImportPath": "github.com/ahmetalpbalkan/azure-extensions-cli",
+ "ImportPath": "github.com/Azure/azure-extensions-cli",
"GoVersion": "go1.5",
- "Packages": [
- "./..."
- ],
+ "GodepVersion": "v74",
"Deps": [
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/golang.org/x/crypto/pkcs12",
@@ -35,6 +33,18 @@
"Comment": "v0.8.7-55-gf7f79f7",
"Rev": "f7f79f729e0fbe2fcc061db48a9ba0263f588252"
},
+ {
+ "ImportPath": "github.com/approvals/go-approval-tests",
+ "Rev": "6f75c0a8ecdf051e948b74b02f7929a4fd8915ca"
+ },
+ {
+ "ImportPath": "github.com/approvals/go-approval-tests/reporters",
+ "Rev": "6f75c0a8ecdf051e948b74b02f7929a4fd8915ca"
+ },
+ {
+ "ImportPath": "github.com/approvals/go-approval-tests/utils",
+ "Rev": "6f75c0a8ecdf051e948b74b02f7929a4fd8915ca"
+ },
{
"ImportPath": "github.com/codegangsta/cli",
"Comment": "1.2.0-193-gf9cc300",
diff --git a/README.md b/README.md
index 1063cff..0690329 100644
--- a/README.md
+++ b/README.md
@@ -55,24 +55,22 @@ USAGE:
azure-extensions-cli [global options] command [command options] [arguments...]
COMMANDS:
- new-extension-manifest Creates an XML file used to publish or update extension.
- new-extension Creates a new type of extension, not for releasing new versions.
- new-extension-version Publishes a new type of extension internally.
- promote-single-region Promote published internal extension to a PROD Location.
- promote-two-regions Promote published extension to two PROD Locations.
- promote-to-prod Promote published extension to all PROD Locations.
- list-versions Lists all published extension versions for subscription
+ new-extension-manifest Creates an XML file used to publish or update extension.
+ new-extension Creates a new type of extension, not for releasing new versions.
+ new-extension-version Publishes a new type of extension internally.
+ promote Promote published internal extension to one or more PROD Locations.
+ promote-all-regions Promote published extension to all PROD Locations.
+ list-versions Lists all published extension versions for subscription
replication-status Retrieves replication status for an uploaded extension package
unpublish-version Marks the specified version of the extension internal. Does not delete.
- delete-version Deletes the extension version. It should be unpublished first.
- help, h Shows a list of commands or help for one command
+ delete-version Deletes the extension version. It should be unpublished first.
+ help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--help, -h show help
--version, -v print the version
```
-
## Installing (or building from source)
You can head over to the **Releases** section to download a binary built for various platforms.
@@ -81,10 +79,36 @@ If you need to compile from the source code, make sure you have Go compiler 1.6+
Check out the project, set the GOPATH environment variable correctly (if necessary) and
run `go build`. This should compile a binary.
-## Author
+## Overview
+
+The CLI makes it easy (easier) to publish an Azure extension. An example workflow is provided below. This workflow
+assumes an extension type already exists, which is why the command **new-extension-version** is used. (If the type does
+not exist use substitute for the command new-extension instead.)
+
+Not all command line parameters are shown for each command, only the salient options are shown.
+
+Step 1 - create an extension manifest.
+
+ 1. ./azure-extensions-cli new-extension-manifest
+
+Step 2 - publish an extension internally.
+
+ 1. ./azure-extensions-cli new-extension-version
+
+Step 3 - rollout the extension to Azure, by slowly including more and more regions. It is recommended that you pause
+24 hours between regions.
-Ahmet Alp Balkan
+> Every time a new region is added, the previous regions must be included with the promote command.
+
+ 1. ./azure-extensions-cli promote --region "West Central US"
+ 1. ./azure-extensions-cli promote --region "West Central US" --region "North US"
+ 1. ./azure-extensions-cli promote --region "West Central US" --region "North US" --region "West US"
+ 1. ./azure-extensions-cli promote ...
+
+Step 4 - promote the extension to all Azure regions.
+ 1. ./azure-extensions-cli promote-all-regions
+
## TODO
- [ ] make `replication-status` exit with appropriate code if replication is not completed.
diff --git a/main.go b/main.go
index 2766929..5111565 100644
--- a/main.go
+++ b/main.go
@@ -61,14 +61,10 @@ var (
flStorageAccount = cli.StringFlag{
Name: "storage-account",
Usage: "Name of an existing storage account to be used in uploading the extension package temporarily."}
- flRegion1 = cli.StringFlag{
- Name: "region-1",
- Usage: "Primary pilot location to roll out the extension (e.g. 'Japan East')",
- EnvVar: "REGION1"}
- flRegion2 = cli.StringFlag{
- Name: "region-2",
- Usage: "Primary pilot location to roll out the extension (e.g. 'Brazil South')",
- EnvVar: "REGION2"}
+ flRegion = cli.StringSliceFlag{
+ Name: "region",
+ Usage: "List of one or more regions to rollout an extension (e.g. 'Japan East')",
+ }
flJSON = cli.BoolFlag{
Name: "json",
Usage: "Print output as JSON"}
@@ -120,21 +116,17 @@ func main() {
Usage: "Publishes a new type of extension internally.",
Flags: []cli.Flag{flMgtURL, flSubsID, flSubsCert, flManifest},
Action: updateExtension},
- {Name: "promote-single-region",
- Usage: "Promote published internal extension to PROD in a Location.",
- Flags: []cli.Flag{flMgtURL, flSubsID, flSubsCert, flManifest, flRegion1},
- Action: promoteToFirstSlice},
- {Name: "promote-two-regions",
- Usage: "Promote published extension to PROD in two Locations.",
- Flags: []cli.Flag{flMgtURL, flSubsID, flSubsCert, flManifest, flRegion1, flRegion2},
- Action: promoteToSecondSlice},
+ {Name: "promote",
+ Usage: "Promote published internal extension to PROD in one or more locations.",
+ Flags: []cli.Flag{flMgtURL, flSubsID, flSubsCert, flManifest, flRegion},
+ Action: promoteToRegions},
{Name: "promote-all-regions",
Usage: "Promote published extension to all Locations.",
Flags: []cli.Flag{flMgtURL, flSubsID, flSubsCert, flManifest},
Action: promoteToAllRegions},
{Name: "list-versions",
Usage: "Lists all published extension versions for subscription",
- Flags: []cli.Flag{flMgtURL, flSubsID, flSubsCert},
+ Flags: []cli.Flag{flMgtURL, flSubsID, flSubsCert, flJSON},
Action: listVersions},
{Name: "replication-status",
Usage: "Retrieves replication status for an uploaded extension package",
diff --git a/manifest.go b/manifest.go
index 68b01a4..e1a9ead 100644
--- a/manifest.go
+++ b/manifest.go
@@ -1,76 +1,175 @@
package main
import (
- "os"
- "text/template"
+ "encoding/xml"
+ "fmt"
log "github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
+ "io/ioutil"
+ "strings"
)
+type certificate struct {
+ StoreLocation string `xml:"StoreLocation,omitempty"`
+ StoreName string `xml:"StoreName,omitempty"`
+ ThumbprintRequired bool `xml:"ThumbprintRequired,omitempty"`
+ ThumbprintAlgorithm string `xml:"ThumbprintAlgorithm,omitempty"`
+}
+
+// NOTE(@boumenot): there is probably a better way to express this. If
+// you know please share...
+//
+// The only difference between ExtensionImage and ExtensionImageGlobal is the
+// Regions element. This element can be in three different states to my
+// knowledge.
+//
+// 1. not defined
+// 2. Region1;Region2
+// 3.
+//
+// Case (1) occurs when an extension is first published. Case(2) occurs when
+// an extension is promoted to one or two regions. Case (3) occurs when an
+// extension is published to all regions.
+//
+// I do not know how to express all three cases using Go's XML serializer.
+//
+type extensionImage struct {
+ XMLName string `xml:"ExtensionImage"`
+ NS string `xml:"xmlns,attr"`
+ ProviderNameSpace string `xml:"ProviderNameSpace"`
+ Type string `xml:"Type"`
+ Version string `xml:"Version"`
+ Label string `xml:"Label"`
+ HostingResources string `xml:"HostingResources"`
+ MediaLink string `xml:"MediaLink"`
+ Certificate *certificate `xml:"Certificate,omitempty"`
+ PublicConfigurationSchema string `xml:"PublicConfigurationSchema,omitempty"`
+ PrivateConfigurationSchema string `xml:"PrivateConfigurationSchema,omitempty"`
+ Description string `xml:"Description"`
+ BlockRoleUponFailure string `xml:"BlockRoleUponFailure,omitempty"`
+ IsInternalExtension bool `xml:"IsInternalExtension"`
+ Eula string `xml:"Eula,omitempty"`
+ PrivacyURI string `xml:"PrivacyUri,omitempty"`
+ HomepageURI string `xml:"HomepageUri,omitempty"`
+ IsJSONExtension bool `xml:"IsJsonExtension,omitempty"`
+ CompanyName string `xml:"CompanyName,omitempty"`
+ SupportedOS string `xml:"SupportedOS,omitempty"`
+ Regions string `xml:"Regions,omitempty"`
+}
+
+type extensionImageGlobal struct {
+ XMLName string `xml:"ExtensionImage"`
+ NS string `xml:"xmlns,attr"`
+ ProviderNameSpace string `xml:"ProviderNameSpace"`
+ Type string `xml:"Type"`
+ Version string `xml:"Version"`
+ Label string `xml:"Label"`
+ HostingResources string `xml:"HostingResources"`
+ MediaLink string `xml:"MediaLink"`
+ Certificate *certificate `xml:"Certificate,omitempty"`
+ PublicConfigurationSchema string `xml:"PublicConfigurationSchema,omitempty"`
+ PrivateConfigurationSchema string `xml:"PrivateConfigurationSchema,omitempty"`
+ Description string `xml:"Description"`
+ BlockRoleUponFailure string `xml:"BlockRoleUponFailure,omitempty"`
+ IsInternalExtension bool `xml:"IsInternalExtension"`
+ Eula string `xml:"Eula,omitempty"`
+ PrivacyURI string `xml:"PrivacyUri,omitempty"`
+ HomepageURI string `xml:"HomepageUri,omitempty"`
+ IsJSONExtension bool `xml:"IsJsonExtension,omitempty"`
+ CompanyName string `xml:"CompanyName,omitempty"`
+ SupportedOS string `xml:"SupportedOS,omitempty"`
+ Regions string `xml:"Regions"`
+}
+
+type extensionManifest interface {
+ Marshal() ([]byte, error)
+}
+
+func isGuestAgent(providerNameSpace string) bool {
+ return "Microsoft.OSTCLinuxAgent" == providerNameSpace
+}
+
+func newExtensionImageManifest(filename string, regions []string) (extensionManifest, error) {
+ b, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, err
+ }
+
+ var manifest extensionImage
+ err = xml.Unmarshal(b, &manifest)
+ if err != nil {
+ return nil, err
+ }
+
+ manifest.Regions = strings.Join(regions, ";")
+
+ if !isGuestAgent(manifest.ProviderNameSpace) {
+ manifest.IsInternalExtension = false
+ } else {
+ log.Debug("VM agent namespace detected, IsInternalExtension ignored")
+ }
+
+ return &manifest, nil
+}
+
+func newExtensionImageGlobalManifest(filename string) (extensionManifest, error) {
+ b, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, err
+ }
+
+ var manifest extensionImageGlobal
+ err = xml.Unmarshal(b, &manifest)
+ if err != nil {
+ return nil, err
+ }
+
+ manifest.IsInternalExtension = !isGuestAgent(manifest.ProviderNameSpace)
+ return &manifest, nil
+}
+
+func (ext *extensionImage) Marshal() ([]byte, error) {
+ return xml.Marshal(*ext)
+}
+
+func (ext *extensionImageGlobal) Marshal() ([]byte, error) {
+ return xml.Marshal(*ext)
+}
+
func newExtensionManifest(c *cli.Context) {
cl := mkClient(checkFlag(c, flMgtURL.Name), checkFlag(c, flSubsID.Name), checkFlag(c, flSubsCert.Name))
storageRealm := checkFlag(c, flStorageRealm.Name)
storageAccount := checkFlag(c, flStorageAccount.Name)
extensionPkg := checkFlag(c, flPackage.Name)
- var p struct {
- Namespace, Name, Version, BlobURL, Label, Description, Eula, Privacy, Homepage, Company, OS string
- }
- flags := []struct {
- ref *string
- fl string
- }{
- {&p.Namespace, flNamespace.Name},
- {&p.Name, flName.Name},
- {&p.Version, flVersion.Name},
- {&p.Label, "label"},
- {&p.Description, "description"},
- {&p.Eula, "eula-url"},
- {&p.Privacy, "privacy-url"},
- {&p.Homepage, "homepage-url"},
- {&p.Company, "company"},
- {&p.OS, "supported-os"},
- }
- for _, f := range flags {
- *f.ref = checkFlag(c, f.fl)
- }
-
// Upload extension blob
blobURL, err := uploadBlob(cl, storageRealm, storageAccount, extensionPkg)
if err != nil {
log.Fatal(err)
}
log.Debugf("Extension package uploaded to: %s", blobURL)
- p.BlobURL = blobURL
-
- // doing a text template is easier and let us create comments (xml encoder can't)
- // that are used as placeholders later on.
- manifestXML := `
-
-
- {{.Namespace}}
- {{.Name}}
- {{.Version}}
-
- VmRole
- {{.BlobURL}}
- {{.Description}}
- true
- {{.Eula}}
- {{.Privacy}}
- {{.Homepage}}
- true
- {{.Company}}
- {{.OS}}
-
-
-`
- tpl, err := template.New("manifest").Parse(manifestXML)
- if err != nil {
- log.Fatalf("template parse error: %v", err)
+
+ manifest := extensionImage{
+ ProviderNameSpace: checkFlag(c, flNamespace.Name),
+ Type: checkFlag(c, flName.Name),
+ Version: checkFlag(c, flVersion.Name),
+ Label: "label",
+ Description: "description",
+ IsInternalExtension: true,
+ MediaLink: blobURL,
+ Eula: "eula-url",
+ PrivacyURI: "privacy-url",
+ HomepageURI: "homepage-url",
+ IsJSONExtension: true,
+ CompanyName: "company",
+ SupportedOS: "supported-os",
}
- if err = tpl.Execute(os.Stdout, p); err != nil {
- log.Fatalf("template execute error: %v", err)
+
+ bs, err := xml.MarshalIndent(manifest, "", " ")
+ if err != nil {
+ log.Fatalf("xml marshall error: %v", err)
}
+
+ fmt.Println(string(bs))
}
diff --git a/manifest_test.TestRoundTripExtensionImage.approved.txt b/manifest_test.TestRoundTripExtensionImage.approved.txt
new file mode 100644
index 0000000..a358227
--- /dev/null
+++ b/manifest_test.TestRoundTripExtensionImage.approved.txt
@@ -0,0 +1,17 @@
+
+ Microsoft.OSCTExtensions
+ CustomScriptForLinux
+ 4.3.2.1
+
+ VmRole
+ http://localhost/extension.zip
+ Please consider using Microsoft.Azure.Extensions.CustomScript instead.
+ true
+ https://github.com/Azure/azure-linux-extensions/blob/master/LICENSE-2_0.txt
+ http://www.microsoft.com/privacystatement/en-us/OnlineServices/Default.aspx
+ https://github.com/Azure/azure-linux-extensions
+ true
+ Microsoft
+ Linux
+ South Central US
+
\ No newline at end of file
diff --git a/manifest_test.TestRoundTripExtensionImageGlobal.approved.txt b/manifest_test.TestRoundTripExtensionImageGlobal.approved.txt
new file mode 100644
index 0000000..906267b
--- /dev/null
+++ b/manifest_test.TestRoundTripExtensionImageGlobal.approved.txt
@@ -0,0 +1,17 @@
+
+ Microsoft.OSCTExtensions
+ CustomScriptForLinux
+ 4.3.2.1
+
+ VmRole
+ http://localhost/extension.zip
+ Please consider using Microsoft.Azure.Extensions.CustomScript instead.
+ true
+ https://github.com/Azure/azure-linux-extensions/blob/master/LICENSE-2_0.txt
+ http://www.microsoft.com/privacystatement/en-us/OnlineServices/Default.aspx
+ https://github.com/Azure/azure-linux-extensions
+ true
+ Microsoft
+ Linux
+
+
\ No newline at end of file
diff --git a/manifest_test.TestRoundTripExtensionImageGlobal.received.txt b/manifest_test.TestRoundTripExtensionImageGlobal.received.txt
new file mode 100644
index 0000000..afdd40f
--- /dev/null
+++ b/manifest_test.TestRoundTripExtensionImageGlobal.received.txt
@@ -0,0 +1,17 @@
+
+ Microsoft.OSCTExtensions
+ CustomScriptForLinux
+ 4.3.2.1
+
+ VmRole
+ http://localhost/extension.zip
+ Please consider using Microsoft.Azure.Extensions.CustomScript instead.
+ true
+ https://github.com/Azure/azure-linux-extensions/blob/master/LICENSE-2_0.txt
+ http://www.microsoft.com/privacystatement/en-us/OnlineServices/Default.aspx
+ https://github.com/Azure/azure-linux-extensions
+ true
+ Microsoft
+ Linux
+
+
\ No newline at end of file
diff --git a/manifest_test.TestRoundTripXmlExtensionImage.approved.txt b/manifest_test.TestRoundTripXmlExtensionImage.approved.txt
new file mode 100644
index 0000000..af44b9c
--- /dev/null
+++ b/manifest_test.TestRoundTripXmlExtensionImage.approved.txt
@@ -0,0 +1,26 @@
+
+ Microsoft.OSCTExtensions
+ PaaS
+ 4.3.2.1
+
+ WebRole|WorkerRole
+ http://localhost/extension.zip
+
+ LocalMachine
+ My
+ true
+ sha1
+
+ --public-configuration-schema--
+ --private-configuration-schema--
+ Please consider using Microsoft.Azure.Extensions.CustomScript instead.
+ false
+ true
+ https://github.com/Azure/azure-linux-extensions/blob/master/LICENSE-2_0.txt
+ http://www.microsoft.com/privacystatement/en-us/OnlineServices/Default.aspx
+ https://github.com/Azure/azure-linux-extensions
+ true
+ Microsoft
+ Linux
+ South Central US
+
\ No newline at end of file
diff --git a/manifest_test.TestRoundTripXmlExtensionImageGlobal.approved.txt b/manifest_test.TestRoundTripXmlExtensionImageGlobal.approved.txt
new file mode 100644
index 0000000..ca03442
--- /dev/null
+++ b/manifest_test.TestRoundTripXmlExtensionImageGlobal.approved.txt
@@ -0,0 +1,26 @@
+
+ Microsoft.OSCTExtensions
+ PaaS
+ 4.3.2.1
+
+ WebRole|WorkerRole
+ http://localhost/extension.zip
+
+ LocalMachine
+ My
+ true
+ sha1
+
+ --public-configuration-schema--
+ --private-configuration-schema--
+ Please consider using Microsoft.Azure.Extensions.CustomScript instead.
+ false
+ true
+ https://github.com/Azure/azure-linux-extensions/blob/master/LICENSE-2_0.txt
+ http://www.microsoft.com/privacystatement/en-us/OnlineServices/Default.aspx
+ https://github.com/Azure/azure-linux-extensions
+ true
+ Microsoft
+ Linux
+
+
\ No newline at end of file
diff --git a/manifest_test.TestSuppressRegionWhenEmpty.approved.txt b/manifest_test.TestSuppressRegionWhenEmpty.approved.txt
new file mode 100644
index 0000000..a43f076
--- /dev/null
+++ b/manifest_test.TestSuppressRegionWhenEmpty.approved.txt
@@ -0,0 +1,16 @@
+
+ Microsoft.OSCTExtensions
+ CustomScriptForLinux
+ 4.3.2.1
+
+ VmRole
+ http://localhost/extension.zip
+ Please consider using Microsoft.Azure.Extensions.CustomScript instead.
+ true
+ https://github.com/Azure/azure-linux-extensions/blob/master/LICENSE-2_0.txt
+ http://www.microsoft.com/privacystatement/en-us/OnlineServices/Default.aspx
+ https://github.com/Azure/azure-linux-extensions
+ true
+ Microsoft
+ Linux
+
\ No newline at end of file
diff --git a/manifest_test.go b/manifest_test.go
new file mode 100644
index 0000000..38ae7fd
--- /dev/null
+++ b/manifest_test.go
@@ -0,0 +1,170 @@
+package main
+
+import (
+ "bytes"
+ "encoding/xml"
+ "testing"
+
+ "github.com/approvals/go-approval-tests"
+)
+
+func TestRoundTripExtensionImage(t *testing.T) {
+ xmlString := []byte(`
+ Microsoft.OSCTExtensions
+ CustomScriptForLinux
+ 4.3.2.1
+
+ VmRole
+ http://localhost/extension.zip
+ Please consider using Microsoft.Azure.Extensions.CustomScript instead.
+ true
+ https://github.com/Azure/azure-linux-extensions/blob/master/LICENSE-2_0.txt
+ http://www.microsoft.com/privacystatement/en-us/OnlineServices/Default.aspx
+ https://github.com/Azure/azure-linux-extensions
+ true
+ Microsoft
+ Linux
+ South Central US
+`)
+
+ var obj extensionImage
+ err := xml.Unmarshal(xmlString, &obj)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ bs, err := xml.MarshalIndent(obj, "", " ")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = approvaltests.Verify(t, bytes.NewReader(bs))
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestRoundTripXmlExtensionImage(t *testing.T) {
+ xmlString := []byte(`
+ Microsoft.OSCTExtensions
+ PaaS
+ 4.3.2.1
+
+ WebRole|WorkerRole
+ http://localhost/extension.zip
+
+ LocalMachine
+ My
+ true
+ sha1
+
+ --public-configuration-schema--
+ --private-configuration-schema--
+ Please consider using Microsoft.Azure.Extensions.CustomScript instead.
+ false
+ true
+ https://github.com/Azure/azure-linux-extensions/blob/master/LICENSE-2_0.txt
+ http://www.microsoft.com/privacystatement/en-us/OnlineServices/Default.aspx
+ https://github.com/Azure/azure-linux-extensions
+ true
+ Microsoft
+ Linux
+ South Central US
+`)
+
+ var obj extensionImage
+ err := xml.Unmarshal(xmlString, &obj)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ bs, err := xml.MarshalIndent(obj, "", " ")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = approvaltests.Verify(t, bytes.NewReader(bs))
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestSuppressRegionWhenEmpty(t *testing.T) {
+ xmlString := []byte(`
+ Microsoft.OSCTExtensions
+ CustomScriptForLinux
+ 4.3.2.1
+
+ VmRole
+ http://localhost/extension.zip
+ Please consider using Microsoft.Azure.Extensions.CustomScript instead.
+ true
+ https://github.com/Azure/azure-linux-extensions/blob/master/LICENSE-2_0.txt
+ http://www.microsoft.com/privacystatement/en-us/OnlineServices/Default.aspx
+ https://github.com/Azure/azure-linux-extensions
+ true
+ Microsoft
+ Linux
+`)
+
+ var obj extensionImage
+ err := xml.Unmarshal(xmlString, &obj)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ bs, err := xml.MarshalIndent(obj, "", " ")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = approvaltests.Verify(t, bytes.NewReader(bs))
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestRoundTripXmlExtensionImageGlobal(t *testing.T) {
+ xmlString := []byte(`
+ Microsoft.OSCTExtensions
+ PaaS
+ 4.3.2.1
+
+ WebRole|WorkerRole
+ http://localhost/extension.zip
+
+ LocalMachine
+ My
+ true
+ sha1
+
+ --public-configuration-schema--
+ --private-configuration-schema--
+ Please consider using Microsoft.Azure.Extensions.CustomScript instead.
+ false
+ true
+ https://github.com/Azure/azure-linux-extensions/blob/master/LICENSE-2_0.txt
+ http://www.microsoft.com/privacystatement/en-us/OnlineServices/Default.aspx
+ https://github.com/Azure/azure-linux-extensions
+ true
+ Microsoft
+ Linux
+
+`)
+
+ var obj extensionImageGlobal
+ err := xml.Unmarshal(xmlString, &obj)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ bs, err := xml.MarshalIndent(obj, "", " ")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = approvaltests.Verify(t, bytes.NewReader(bs))
+ if err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/promote.go b/promote.go
index 3722106..9f3dd4d 100644
--- a/promote.go
+++ b/promote.go
@@ -1,74 +1,53 @@
package main
import (
- "bytes"
- "fmt"
- "io/ioutil"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
)
-func promoteToFirstSlice(c *cli.Context) {
- if err := promoteExtension(c, mkRegionElement(
- checkFlag(c, flRegion1.Name))); err != nil {
- log.Fatal(err)
+func promoteToRegions(c *cli.Context) {
+ regions := c.StringSlice(flRegion.Name)
+
+ if len(regions) == 0 {
+ log.Fatalf("At least one region must be specified!")
+ return
}
- log.Info("Extension is promoted to PROD in one region. See replication-status.")
-}
-func promoteToSecondSlice(c *cli.Context) {
- if err := promoteExtension(c, mkRegionElement(
- checkFlag(c, flRegion1.Name),
- checkFlag(c, flRegion2.Name))); err != nil {
+
+ if err := promoteExtension(c, func() (extensionManifest, error) {
+ return newExtensionImageManifest(checkFlag(c, flManifest.Name), regions)
+ }); err != nil {
log.Fatal(err)
}
- log.Info("Extension is promoted to PROD in two regions. See replication-status.")
+
+ log.Infof("Extension is promoted to PROD in %s. See replication-status.", strings.Join(regions, ","))
}
func promoteToAllRegions(c *cli.Context) {
- regions := `` // replace placeholder with empty string to omit the element.
- if err := promoteExtension(c, regions); err != nil {
+ if err := promoteExtension(c, func() (extensionManifest, error) {
+ return newExtensionImageGlobalManifest(checkFlag(c, flManifest.Name))
+ }); err != nil {
log.Fatal(err)
}
+
log.Info("Extension is promoted to all regions. See replication-status.")
}
-func promoteExtension(c *cli.Context, regionsXML string) error {
- b, err := updateManifestRegions(checkFlag(c, flManifest.Name), regionsXML)
+func promoteExtension(c *cli.Context, factory func() (extensionManifest, error)) error {
+ manifest, err := factory()
if err != nil {
return err
}
- if err := publishExtension(c, "UpdateExtension", b,
- mkClient(checkFlag(c, flMgtURL.Name), checkFlag(c, flSubsID.Name), checkFlag(c, flSubsCert.Name)).UpdateExtension); err != nil {
- return err
- }
- return nil
-}
-
-func mkRegionElement(regions ...string) string {
- return fmt.Sprintf(`%s`, strings.Join(regions, ";"))
-}
-// updateManifestRegions makes an in-memory update to the
-// placeholder string in the manifest XML for further usage, and
-// sets according to whether the package
-// should remain internal (vm agent) or not (extensions)
-func updateManifestRegions(manifestPath string, regionsXMLElement string) ([]byte, error) {
- b, err := ioutil.ReadFile(manifestPath)
+ b, err := manifest.Marshal()
if err != nil {
- return nil, fmt.Errorf("Error reading manifest: %v", err)
+ return err
}
- // todo: improve this
- b = bytes.Replace(b, []byte(``), []byte(regionsXMLElement), 1)
-
- updateInternal := !bytes.Contains(b, []byte(`Microsoft.OSTCLinuxAgent`))
- if updateInternal {
- b = bytes.Replace(b, []byte(`true`), []byte(`false`), 1)
- } else {
- log.Debug("VM agent namespace detected, IsInternalExtension ignored")
+ if err := publishExtension(c, "UpdateExtension", b,
+ mkClient(checkFlag(c, flMgtURL.Name), checkFlag(c, flSubsID.Name), checkFlag(c, flSubsCert.Name)).UpdateExtension); err != nil {
+ return err
}
-
- return b, err
+ return nil
}
diff --git a/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go b/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go
deleted file mode 100644
index 3187f6d..0000000
--- a/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package main
-
-import (
- "github.com/Sirupsen/logrus"
- "gopkg.in/gemnasium/logrus-airbrake-hook.v2"
-)
-
-var log = logrus.New()
-
-func init() {
- log.Formatter = new(logrus.TextFormatter) // default
- log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
-}
-
-func main() {
- log.WithFields(logrus.Fields{
- "animal": "walrus",
- "size": 10,
- }).Info("A group of walrus emerges from the ocean")
-
- log.WithFields(logrus.Fields{
- "omg": true,
- "number": 122,
- }).Warn("The group's number increased tremendously!")
-
- log.WithFields(logrus.Fields{
- "omg": true,
- "number": 100,
- }).Fatal("The ice breaks!")
-}
diff --git a/vendor/github.com/approvals/go-approval-tests/.gitignore b/vendor/github.com/approvals/go-approval-tests/.gitignore
new file mode 100644
index 0000000..116a346
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/.gitignore
@@ -0,0 +1,25 @@
+*.received.*
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
diff --git a/vendor/github.com/approvals/go-approval-tests/.travis.yml b/vendor/github.com/approvals/go-approval-tests/.travis.yml
new file mode 100644
index 0000000..2f2aa09
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/.travis.yml
@@ -0,0 +1,16 @@
+sudo: false
+
+language: go
+
+go:
+ - 1.6
+
+install:
+ - go get -u github.com/golang/lint/golint
+
+script:
+ - test -z "$(go fmt -s -l -w ./... | tee /dev/stderr)"
+ - golint -set_exit_status ./...
+ - go vet ./...
+ - go build -v ./...
+ - go test -v ./...
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/LICENSE.md b/vendor/github.com/approvals/go-approval-tests/LICENSE.md
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/LICENSE.md
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ 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.
diff --git a/vendor/github.com/approvals/go-approval-tests/README.md b/vendor/github.com/approvals/go-approval-tests/README.md
new file mode 100644
index 0000000..9531d95
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/README.md
@@ -0,0 +1,53 @@
+# ApprovalTests.go
+
+ApprovalTests for go
+
+[![Build Status](https://travis-ci.org/approvals/go-approval-tests.png?branch=master)](https://travis-ci.org/approvals/go-approval-tests)
+
+# Golden master Verification Library
+ApprovalTests allows for easy testing of larger objects, strings and anything else that can be saved to a file (images, sounds, csv, etc...)
+
+#Examples
+##In Project
+Note: ApprovalTests uses approvaltests to test itself. Therefore there are many examples in the code itself.
+
+ * [approvals_test.go](approvals_test.go)
+
+##JSON
+VerifyJSONBytes - Simple Formatting for easy comparison. Also uses the .json file extension
+
+```go
+func TestVerifyJSON(t *testing.T) {
+ jsonb := []byte("{ \"foo\": \"bar\", \"age\": 42, \"bark\": \"woof\" }")
+ VerifyJSONBytes(t, jsonb)
+}
+```
+Matches file: approvals_test.TestVerifyJSON.received.json
+
+```json
+{
+ "age": 42,
+ "bark": "woof",
+ "foo": "bar"
+}
+```
+
+##Reporters
+ApprovalTests becomes *much* more powerful with reporters. Reporters launch programs on failure to help you understand, fix and approve results.
+
+You can make your own easily, [here's an example](reporters/beyond_compare.go)
+You can also declare which one to use. Either at the
+### Method level
+```go
+r := UseReporter(reporters.NewIntelliJ())
+defer r.Close()
+```
+### Test Level
+```go
+func TestMain(m *testing.M) {
+ r := UseReporter(reporters.NewBeyondCompareReporter())
+ defer r.Close()
+
+ os.Exit(m.Run())
+}
+```
diff --git a/vendor/github.com/approvals/go-approval-tests/TODO.md b/vendor/github.com/approvals/go-approval-tests/TODO.md
new file mode 100644
index 0000000..bd53e51
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/TODO.md
@@ -0,0 +1 @@
+- [ ] Newbie Reporters ?
diff --git a/vendor/github.com/approvals/go-approval-tests/approval_name.go b/vendor/github.com/approvals/go-approval-tests/approval_name.go
new file mode 100644
index 0000000..1ef043e
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approval_name.go
@@ -0,0 +1,130 @@
+package approvaltests
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path"
+ "runtime"
+ "strings"
+)
+
+type approvalName struct {
+ pc uintptr
+ fullName string
+ name string
+ fileName string
+ fileLine int
+}
+
+func newApprovalName(pc uintptr, f *runtime.Func) (*approvalName, error) {
+ namer := &approvalName{
+ pc: pc,
+ fullName: f.Name(),
+ }
+
+ namer.fileName, namer.fileLine = f.FileLine(pc)
+
+ splits := strings.Split(namer.fullName, ".")
+ namer.name = splits[len(splits)-1]
+
+ return namer, nil
+}
+
+// Walk the call stack, and try to find the test method that was executed.
+// The test method is identified by looking for the test runner, which is
+// *assumed* to be common across all callers. The test runner has a Name() of
+// 'testing.tRunner'. The method immediately previous to this is the test
+// method.
+func getApprovalName() (*approvalName, error) {
+ pc := make([]uintptr, 100)
+ count := runtime.Callers(0, pc)
+
+ i := 0
+ var lastFunc *runtime.Func
+
+ for ; i < count; i++ {
+ lastFunc = runtime.FuncForPC(pc[i])
+ if isTestRunner(lastFunc) {
+ break
+ }
+ }
+
+ if i == 0 || !isTestRunner(lastFunc) {
+ return nil, fmt.Errorf("approvals: could not find the test method")
+ }
+
+ testMethod := runtime.FuncForPC(pc[i-1])
+ return newApprovalName(pc[i-1], testMethod)
+}
+
+func isTestRunner(f *runtime.Func) bool {
+ return f != nil && f.Name() == "testing.tRunner"
+}
+
+func (s *approvalName) compare(approvalFile, receivedFile string, reader io.Reader) error {
+ received, err := ioutil.ReadAll(reader)
+ if err != nil {
+ return err
+ }
+
+ // Ideally, this should only be written if
+ // 1. the approval file does not exist
+ // 2. the results differ
+ err = s.dumpReceivedTestResult(received, receivedFile)
+ if err != nil {
+ return err
+ }
+
+ fh, err := os.Open(approvalFile)
+ if err != nil {
+ return err
+ }
+ defer fh.Close()
+
+ approved, err := ioutil.ReadAll(fh)
+ if err != nil {
+ return err
+ }
+
+ received = s.normalizeLineEndings(received)
+ approved = s.normalizeLineEndings(approved)
+
+ // The two sides are identical, nothing more to do.
+ if bytes.Compare(received, approved) == 0 {
+ return nil
+ }
+
+ return fmt.Errorf("failed to approved %s", s.name)
+}
+
+func (s *approvalName) normalizeLineEndings(bs []byte) []byte {
+ return bytes.Replace(bs, []byte("\r\n"), []byte("\n"), -1)
+}
+
+func (s *approvalName) dumpReceivedTestResult(bs []byte, receivedFile string) error {
+ err := ioutil.WriteFile(receivedFile, bs, 0644)
+
+ return err
+}
+
+func (s *approvalName) getFileName(extWithDot string, suffix string) string {
+ if !strings.HasPrefix(extWithDot, ".") {
+ extWithDot = fmt.Sprintf(".%s", extWithDot)
+ }
+
+ baseName := path.Base(s.fileName)
+ baseWithoutExt := baseName[:len(baseName)-len(path.Ext(s.fileName))]
+
+ return fmt.Sprintf("%s.%s.%s%s", baseWithoutExt, s.name, suffix, extWithDot)
+}
+
+func (s *approvalName) getReceivedFile(extWithDot string) string {
+ return s.getFileName(extWithDot, "received")
+}
+
+func (s *approvalName) getApprovalFile(extWithDot string) string {
+ return s.getFileName(extWithDot, "approved")
+}
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals.go b/vendor/github.com/approvals/go-approval-tests/approvals.go
new file mode 100644
index 0000000..f4b3a83
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals.go
@@ -0,0 +1,204 @@
+package approvaltests
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+
+ "encoding/xml"
+ "github.com/approvals/go-approval-tests/reporters"
+ "github.com/approvals/go-approval-tests/utils"
+ "reflect"
+)
+
+var (
+ defaultReporter = reporters.NewDiffReporter()
+ defaultFrontLoadedReporter = reporters.NewFrontLoadedReporter()
+)
+
+// Failable is an interface wrapper around testing.T
+type Failable interface {
+ Fail()
+}
+
+// VerifyWithExtension Example:
+// VerifyWithExtension(t, strings.NewReader("Hello"), ".txt")
+func VerifyWithExtension(t Failable, reader io.Reader, extWithDot string) error {
+ namer, err := getApprovalName()
+ if err != nil {
+ return err
+ }
+
+ reporter := getReporter()
+ err = namer.compare(namer.getApprovalFile(extWithDot), namer.getReceivedFile(extWithDot), reader)
+ if err != nil {
+ reporter.Report(namer.getApprovalFile(extWithDot), namer.getReceivedFile(extWithDot))
+ t.Fail()
+ } else {
+ os.Remove(namer.getReceivedFile(extWithDot))
+ }
+
+ return err
+}
+
+// Verify Example:
+// Verify(t, strings.NewReader("Hello"))
+func Verify(t Failable, reader io.Reader) error {
+ return VerifyWithExtension(t, reader, ".txt")
+}
+
+// VerifyString Example:
+// VerifyString(t, "Hello")
+func VerifyString(t Failable, s string) error {
+ reader := strings.NewReader(s)
+ return Verify(t, reader)
+}
+
+// VerifyXMLStruct Example:
+// VerifyXMLStruct(t, xml)
+func VerifyXMLStruct(t Failable, obj interface{}) error {
+ xmlb, err := xml.MarshalIndent(obj, "", " ")
+ if err != nil {
+ tip := ""
+ if reflect.TypeOf(obj).Name() == "" {
+ tip = "when using anonymous types be sure to include\n XMLName xml.Name `xml:\"Your_Name_Here\"`\n"
+ }
+ message := fmt.Sprintf("error while pretty printing XML\n%verror:\n %v\nXML:\n %v\n", tip, err, obj)
+ return VerifyWithExtension(t, strings.NewReader(message), ".xml")
+ }
+
+ return VerifyWithExtension(t, bytes.NewReader(xmlb), ".xml")
+}
+
+// VerifyXMLBytes Example:
+// VerifyXMLBytes(t, []byte(""))
+func VerifyXMLBytes(t Failable, bs []byte) error {
+ type node struct {
+ Attr []xml.Attr
+ XMLName xml.Name
+ Children []node `xml:",any"`
+ Text string `xml:",chardata"`
+ }
+ x := node{}
+
+ err := xml.Unmarshal(bs, &x)
+ if err != nil {
+ message := fmt.Sprintf("error while parsing XML\nerror:\n %s\nXML:\n %s\n", err, string(bs))
+ return VerifyWithExtension(t, strings.NewReader(message), ".xml")
+ }
+
+ return VerifyXMLStruct(t, x)
+}
+
+// VerifyJSONStruct Example:
+// VerifyJSONStruct(t, json)
+func VerifyJSONStruct(t Failable, obj interface{}) error {
+ jsonb, err := json.MarshalIndent(obj, "", " ")
+ if err != nil {
+ message := fmt.Sprintf("error while pretty printing JSON\nerror:\n %s\nJSON:\n %s\n", err, obj)
+ return VerifyWithExtension(t, strings.NewReader(message), ".json")
+ }
+
+ return VerifyWithExtension(t, bytes.NewReader(jsonb), ".json")
+}
+
+// VerifyJSONBytes Example:
+// VerifyJSONBytes(t, []byte("{ \"Greeting\": \"Hello\" }"))
+func VerifyJSONBytes(t Failable, bs []byte) error {
+ var obj map[string]interface{}
+ err := json.Unmarshal(bs, &obj)
+ if err != nil {
+ message := fmt.Sprintf("error while parsing JSON\nerror:\n %s\nJSON:\n %s\n", err, string(bs))
+ return VerifyWithExtension(t, strings.NewReader(message), ".json")
+ }
+
+ return VerifyJSONStruct(t, obj)
+}
+
+// VerifyMap Example:
+// VerifyMap(t, map[string][string] { "dog": "bark" })
+func VerifyMap(t Failable, m interface{}) error {
+ outputText := utils.PrintMap(m)
+ return VerifyString(t, outputText)
+}
+
+// VerifyArray Example:
+// VerifyArray(t, []string{"dog", "cat"})
+func VerifyArray(t Failable, array interface{}) error {
+ outputText := utils.PrintArray(array)
+ return VerifyString(t, outputText)
+}
+
+// VerifyAll Example:
+// VerifyAll(t, "uppercase", []string("dog", "cat"}, func(x interface{}) string { return strings.ToUpper(x.(string)) })
+func VerifyAll(t Failable, header string, collection interface{}, transform func(interface{}) string) error {
+ if len(header) != 0 {
+ header = fmt.Sprintf("%s\n\n\n", header)
+ }
+
+ outputText := header + strings.Join(utils.MapToString(collection, transform), "\n")
+ return VerifyString(t, outputText)
+}
+
+type reporterCloser struct {
+ reporter *reporters.Reporter
+}
+
+func (s *reporterCloser) Close() error {
+ defaultReporter = s.reporter
+ return nil
+}
+
+type frontLoadedReporterCloser struct {
+ reporter *reporters.Reporter
+}
+
+func (s *frontLoadedReporterCloser) Close() error {
+ defaultFrontLoadedReporter = s.reporter
+ return nil
+}
+
+// UseReporter configures which reporter to use on failure.
+// Add at the test or method level to configure your reporter.
+//
+// The following examples shows how to use a reporter for all of your test cases
+// in a package directory through go's setup feature.
+//
+//
+// func TestMain(m *testing.M) {
+// r := UseReporter(reporters.NewBeyondCompareReporter())
+// defer r.Close()
+//
+// os.Exit(m.Run())
+// }
+//
+func UseReporter(reporter reporters.Reporter) io.Closer {
+ closer := &reporterCloser{
+ reporter: defaultReporter,
+ }
+
+ defaultReporter = &reporter
+ return closer
+}
+
+// UseFrontLoadedReporter configures reporters ahead of all other reporters to
+// handle situations like CI. These reporters usually prevent reporting in
+// scenarios that are headless.
+func UseFrontLoadedReporter(reporter reporters.Reporter) io.Closer {
+ closer := &frontLoadedReporterCloser{
+ reporter: defaultFrontLoadedReporter,
+ }
+
+ defaultFrontLoadedReporter = &reporter
+ return closer
+}
+
+func getReporter() reporters.Reporter {
+ return reporters.NewFirstWorkingReporter(
+ *defaultFrontLoadedReporter,
+ *defaultReporter,
+ )
+}
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestReporterFromSetup.approved.txt b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestReporterFromSetup.approved.txt
new file mode 100644
index 0000000..c57eff5
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestReporterFromSetup.approved.txt
@@ -0,0 +1 @@
+Hello World!
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyAllCombinationsFor1.approved.txt b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyAllCombinationsFor1.approved.txt
new file mode 100644
index 0000000..3ba9ad1
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyAllCombinationsFor1.approved.txt
@@ -0,0 +1,5 @@
+uppercase
+
+
+[Christopher] => CHRISTOPHER
+[Llewellyn] => LLEWELLYN
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyAllCombinationsFor2.approved.txt b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyAllCombinationsFor2.approved.txt
new file mode 100644
index 0000000..d0ba3fa
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyAllCombinationsFor2.approved.txt
@@ -0,0 +1,7 @@
+character at
+
+
+[Christopher,0] => C
+[Christopher,1] => h
+[Llewellyn,0] => L
+[Llewellyn,1] => l
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyAllCombinationsFor9.approved.txt b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyAllCombinationsFor9.approved.txt
new file mode 100644
index 0000000..e5ed07e
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyAllCombinationsFor9.approved.txt
@@ -0,0 +1,259 @@
+sum numbers
+
+
+[Christopher,0,2,4,6,8,10,12,14] => Christopher[56]
+[Christopher,0,2,4,6,8,10,12,15] => Christopher[57]
+[Christopher,0,2,4,6,8,10,13,14] => Christopher[57]
+[Christopher,0,2,4,6,8,10,13,15] => Christopher[58]
+[Christopher,0,2,4,6,8,11,12,14] => Christopher[57]
+[Christopher,0,2,4,6,8,11,12,15] => Christopher[58]
+[Christopher,0,2,4,6,8,11,13,14] => Christopher[58]
+[Christopher,0,2,4,6,8,11,13,15] => Christopher[59]
+[Christopher,0,2,4,6,9,10,12,14] => Christopher[57]
+[Christopher,0,2,4,6,9,10,12,15] => Christopher[58]
+[Christopher,0,2,4,6,9,10,13,14] => Christopher[58]
+[Christopher,0,2,4,6,9,10,13,15] => Christopher[59]
+[Christopher,0,2,4,6,9,11,12,14] => Christopher[58]
+[Christopher,0,2,4,6,9,11,12,15] => Christopher[59]
+[Christopher,0,2,4,6,9,11,13,14] => Christopher[59]
+[Christopher,0,2,4,6,9,11,13,15] => Christopher[60]
+[Christopher,0,2,4,7,8,10,12,14] => Christopher[57]
+[Christopher,0,2,4,7,8,10,12,15] => Christopher[58]
+[Christopher,0,2,4,7,8,10,13,14] => Christopher[58]
+[Christopher,0,2,4,7,8,10,13,15] => Christopher[59]
+[Christopher,0,2,4,7,8,11,12,14] => Christopher[58]
+[Christopher,0,2,4,7,8,11,12,15] => Christopher[59]
+[Christopher,0,2,4,7,8,11,13,14] => Christopher[59]
+[Christopher,0,2,4,7,8,11,13,15] => Christopher[60]
+[Christopher,0,2,4,7,9,10,12,14] => Christopher[58]
+[Christopher,0,2,4,7,9,10,12,15] => Christopher[59]
+[Christopher,0,2,4,7,9,10,13,14] => Christopher[59]
+[Christopher,0,2,4,7,9,10,13,15] => Christopher[60]
+[Christopher,0,2,4,7,9,11,12,14] => Christopher[59]
+[Christopher,0,2,4,7,9,11,12,15] => Christopher[60]
+[Christopher,0,2,4,7,9,11,13,14] => Christopher[60]
+[Christopher,0,2,4,7,9,11,13,15] => Christopher[61]
+[Christopher,0,2,5,6,8,10,12,14] => Christopher[57]
+[Christopher,0,2,5,6,8,10,12,15] => Christopher[58]
+[Christopher,0,2,5,6,8,10,13,14] => Christopher[58]
+[Christopher,0,2,5,6,8,10,13,15] => Christopher[59]
+[Christopher,0,2,5,6,8,11,12,14] => Christopher[58]
+[Christopher,0,2,5,6,8,11,12,15] => Christopher[59]
+[Christopher,0,2,5,6,8,11,13,14] => Christopher[59]
+[Christopher,0,2,5,6,8,11,13,15] => Christopher[60]
+[Christopher,0,2,5,6,9,10,12,14] => Christopher[58]
+[Christopher,0,2,5,6,9,10,12,15] => Christopher[59]
+[Christopher,0,2,5,6,9,10,13,14] => Christopher[59]
+[Christopher,0,2,5,6,9,10,13,15] => Christopher[60]
+[Christopher,0,2,5,6,9,11,12,14] => Christopher[59]
+[Christopher,0,2,5,6,9,11,12,15] => Christopher[60]
+[Christopher,0,2,5,6,9,11,13,14] => Christopher[60]
+[Christopher,0,2,5,6,9,11,13,15] => Christopher[61]
+[Christopher,0,2,5,7,8,10,12,14] => Christopher[58]
+[Christopher,0,2,5,7,8,10,12,15] => Christopher[59]
+[Christopher,0,2,5,7,8,10,13,14] => Christopher[59]
+[Christopher,0,2,5,7,8,10,13,15] => Christopher[60]
+[Christopher,0,2,5,7,8,11,12,14] => Christopher[59]
+[Christopher,0,2,5,7,8,11,12,15] => Christopher[60]
+[Christopher,0,2,5,7,8,11,13,14] => Christopher[60]
+[Christopher,0,2,5,7,8,11,13,15] => Christopher[61]
+[Christopher,0,2,5,7,9,10,12,14] => Christopher[59]
+[Christopher,0,2,5,7,9,10,12,15] => Christopher[60]
+[Christopher,0,2,5,7,9,10,13,14] => Christopher[60]
+[Christopher,0,2,5,7,9,10,13,15] => Christopher[61]
+[Christopher,0,2,5,7,9,11,12,14] => Christopher[60]
+[Christopher,0,2,5,7,9,11,12,15] => Christopher[61]
+[Christopher,0,2,5,7,9,11,13,14] => Christopher[61]
+[Christopher,0,2,5,7,9,11,13,15] => Christopher[62]
+[Christopher,0,3,4,6,8,10,12,14] => Christopher[57]
+[Christopher,0,3,4,6,8,10,12,15] => Christopher[58]
+[Christopher,0,3,4,6,8,10,13,14] => Christopher[58]
+[Christopher,0,3,4,6,8,10,13,15] => Christopher[59]
+[Christopher,0,3,4,6,8,11,12,14] => Christopher[58]
+[Christopher,0,3,4,6,8,11,12,15] => Christopher[59]
+[Christopher,0,3,4,6,8,11,13,14] => Christopher[59]
+[Christopher,0,3,4,6,8,11,13,15] => Christopher[60]
+[Christopher,0,3,4,6,9,10,12,14] => Christopher[58]
+[Christopher,0,3,4,6,9,10,12,15] => Christopher[59]
+[Christopher,0,3,4,6,9,10,13,14] => Christopher[59]
+[Christopher,0,3,4,6,9,10,13,15] => Christopher[60]
+[Christopher,0,3,4,6,9,11,12,14] => Christopher[59]
+[Christopher,0,3,4,6,9,11,12,15] => Christopher[60]
+[Christopher,0,3,4,6,9,11,13,14] => Christopher[60]
+[Christopher,0,3,4,6,9,11,13,15] => Christopher[61]
+[Christopher,0,3,4,7,8,10,12,14] => Christopher[58]
+[Christopher,0,3,4,7,8,10,12,15] => Christopher[59]
+[Christopher,0,3,4,7,8,10,13,14] => Christopher[59]
+[Christopher,0,3,4,7,8,10,13,15] => Christopher[60]
+[Christopher,0,3,4,7,8,11,12,14] => Christopher[59]
+[Christopher,0,3,4,7,8,11,12,15] => Christopher[60]
+[Christopher,0,3,4,7,8,11,13,14] => Christopher[60]
+[Christopher,0,3,4,7,8,11,13,15] => Christopher[61]
+[Christopher,0,3,4,7,9,10,12,14] => Christopher[59]
+[Christopher,0,3,4,7,9,10,12,15] => Christopher[60]
+[Christopher,0,3,4,7,9,10,13,14] => Christopher[60]
+[Christopher,0,3,4,7,9,10,13,15] => Christopher[61]
+[Christopher,0,3,4,7,9,11,12,14] => Christopher[60]
+[Christopher,0,3,4,7,9,11,12,15] => Christopher[61]
+[Christopher,0,3,4,7,9,11,13,14] => Christopher[61]
+[Christopher,0,3,4,7,9,11,13,15] => Christopher[62]
+[Christopher,0,3,5,6,8,10,12,14] => Christopher[58]
+[Christopher,0,3,5,6,8,10,12,15] => Christopher[59]
+[Christopher,0,3,5,6,8,10,13,14] => Christopher[59]
+[Christopher,0,3,5,6,8,10,13,15] => Christopher[60]
+[Christopher,0,3,5,6,8,11,12,14] => Christopher[59]
+[Christopher,0,3,5,6,8,11,12,15] => Christopher[60]
+[Christopher,0,3,5,6,8,11,13,14] => Christopher[60]
+[Christopher,0,3,5,6,8,11,13,15] => Christopher[61]
+[Christopher,0,3,5,6,9,10,12,14] => Christopher[59]
+[Christopher,0,3,5,6,9,10,12,15] => Christopher[60]
+[Christopher,0,3,5,6,9,10,13,14] => Christopher[60]
+[Christopher,0,3,5,6,9,10,13,15] => Christopher[61]
+[Christopher,0,3,5,6,9,11,12,14] => Christopher[60]
+[Christopher,0,3,5,6,9,11,12,15] => Christopher[61]
+[Christopher,0,3,5,6,9,11,13,14] => Christopher[61]
+[Christopher,0,3,5,6,9,11,13,15] => Christopher[62]
+[Christopher,0,3,5,7,8,10,12,14] => Christopher[59]
+[Christopher,0,3,5,7,8,10,12,15] => Christopher[60]
+[Christopher,0,3,5,7,8,10,13,14] => Christopher[60]
+[Christopher,0,3,5,7,8,10,13,15] => Christopher[61]
+[Christopher,0,3,5,7,8,11,12,14] => Christopher[60]
+[Christopher,0,3,5,7,8,11,12,15] => Christopher[61]
+[Christopher,0,3,5,7,8,11,13,14] => Christopher[61]
+[Christopher,0,3,5,7,8,11,13,15] => Christopher[62]
+[Christopher,0,3,5,7,9,10,12,14] => Christopher[60]
+[Christopher,0,3,5,7,9,10,12,15] => Christopher[61]
+[Christopher,0,3,5,7,9,10,13,14] => Christopher[61]
+[Christopher,0,3,5,7,9,10,13,15] => Christopher[62]
+[Christopher,0,3,5,7,9,11,12,14] => Christopher[61]
+[Christopher,0,3,5,7,9,11,12,15] => Christopher[62]
+[Christopher,0,3,5,7,9,11,13,14] => Christopher[62]
+[Christopher,0,3,5,7,9,11,13,15] => Christopher[63]
+[Christopher,1,2,4,6,8,10,12,14] => Christopher[57]
+[Christopher,1,2,4,6,8,10,12,15] => Christopher[58]
+[Christopher,1,2,4,6,8,10,13,14] => Christopher[58]
+[Christopher,1,2,4,6,8,10,13,15] => Christopher[59]
+[Christopher,1,2,4,6,8,11,12,14] => Christopher[58]
+[Christopher,1,2,4,6,8,11,12,15] => Christopher[59]
+[Christopher,1,2,4,6,8,11,13,14] => Christopher[59]
+[Christopher,1,2,4,6,8,11,13,15] => Christopher[60]
+[Christopher,1,2,4,6,9,10,12,14] => Christopher[58]
+[Christopher,1,2,4,6,9,10,12,15] => Christopher[59]
+[Christopher,1,2,4,6,9,10,13,14] => Christopher[59]
+[Christopher,1,2,4,6,9,10,13,15] => Christopher[60]
+[Christopher,1,2,4,6,9,11,12,14] => Christopher[59]
+[Christopher,1,2,4,6,9,11,12,15] => Christopher[60]
+[Christopher,1,2,4,6,9,11,13,14] => Christopher[60]
+[Christopher,1,2,4,6,9,11,13,15] => Christopher[61]
+[Christopher,1,2,4,7,8,10,12,14] => Christopher[58]
+[Christopher,1,2,4,7,8,10,12,15] => Christopher[59]
+[Christopher,1,2,4,7,8,10,13,14] => Christopher[59]
+[Christopher,1,2,4,7,8,10,13,15] => Christopher[60]
+[Christopher,1,2,4,7,8,11,12,14] => Christopher[59]
+[Christopher,1,2,4,7,8,11,12,15] => Christopher[60]
+[Christopher,1,2,4,7,8,11,13,14] => Christopher[60]
+[Christopher,1,2,4,7,8,11,13,15] => Christopher[61]
+[Christopher,1,2,4,7,9,10,12,14] => Christopher[59]
+[Christopher,1,2,4,7,9,10,12,15] => Christopher[60]
+[Christopher,1,2,4,7,9,10,13,14] => Christopher[60]
+[Christopher,1,2,4,7,9,10,13,15] => Christopher[61]
+[Christopher,1,2,4,7,9,11,12,14] => Christopher[60]
+[Christopher,1,2,4,7,9,11,12,15] => Christopher[61]
+[Christopher,1,2,4,7,9,11,13,14] => Christopher[61]
+[Christopher,1,2,4,7,9,11,13,15] => Christopher[62]
+[Christopher,1,2,5,6,8,10,12,14] => Christopher[58]
+[Christopher,1,2,5,6,8,10,12,15] => Christopher[59]
+[Christopher,1,2,5,6,8,10,13,14] => Christopher[59]
+[Christopher,1,2,5,6,8,10,13,15] => Christopher[60]
+[Christopher,1,2,5,6,8,11,12,14] => Christopher[59]
+[Christopher,1,2,5,6,8,11,12,15] => Christopher[60]
+[Christopher,1,2,5,6,8,11,13,14] => Christopher[60]
+[Christopher,1,2,5,6,8,11,13,15] => Christopher[61]
+[Christopher,1,2,5,6,9,10,12,14] => Christopher[59]
+[Christopher,1,2,5,6,9,10,12,15] => Christopher[60]
+[Christopher,1,2,5,6,9,10,13,14] => Christopher[60]
+[Christopher,1,2,5,6,9,10,13,15] => Christopher[61]
+[Christopher,1,2,5,6,9,11,12,14] => Christopher[60]
+[Christopher,1,2,5,6,9,11,12,15] => Christopher[61]
+[Christopher,1,2,5,6,9,11,13,14] => Christopher[61]
+[Christopher,1,2,5,6,9,11,13,15] => Christopher[62]
+[Christopher,1,2,5,7,8,10,12,14] => Christopher[59]
+[Christopher,1,2,5,7,8,10,12,15] => Christopher[60]
+[Christopher,1,2,5,7,8,10,13,14] => Christopher[60]
+[Christopher,1,2,5,7,8,10,13,15] => Christopher[61]
+[Christopher,1,2,5,7,8,11,12,14] => Christopher[60]
+[Christopher,1,2,5,7,8,11,12,15] => Christopher[61]
+[Christopher,1,2,5,7,8,11,13,14] => Christopher[61]
+[Christopher,1,2,5,7,8,11,13,15] => Christopher[62]
+[Christopher,1,2,5,7,9,10,12,14] => Christopher[60]
+[Christopher,1,2,5,7,9,10,12,15] => Christopher[61]
+[Christopher,1,2,5,7,9,10,13,14] => Christopher[61]
+[Christopher,1,2,5,7,9,10,13,15] => Christopher[62]
+[Christopher,1,2,5,7,9,11,12,14] => Christopher[61]
+[Christopher,1,2,5,7,9,11,12,15] => Christopher[62]
+[Christopher,1,2,5,7,9,11,13,14] => Christopher[62]
+[Christopher,1,2,5,7,9,11,13,15] => Christopher[63]
+[Christopher,1,3,4,6,8,10,12,14] => Christopher[58]
+[Christopher,1,3,4,6,8,10,12,15] => Christopher[59]
+[Christopher,1,3,4,6,8,10,13,14] => Christopher[59]
+[Christopher,1,3,4,6,8,10,13,15] => Christopher[60]
+[Christopher,1,3,4,6,8,11,12,14] => Christopher[59]
+[Christopher,1,3,4,6,8,11,12,15] => Christopher[60]
+[Christopher,1,3,4,6,8,11,13,14] => Christopher[60]
+[Christopher,1,3,4,6,8,11,13,15] => Christopher[61]
+[Christopher,1,3,4,6,9,10,12,14] => Christopher[59]
+[Christopher,1,3,4,6,9,10,12,15] => Christopher[60]
+[Christopher,1,3,4,6,9,10,13,14] => Christopher[60]
+[Christopher,1,3,4,6,9,10,13,15] => Christopher[61]
+[Christopher,1,3,4,6,9,11,12,14] => Christopher[60]
+[Christopher,1,3,4,6,9,11,12,15] => Christopher[61]
+[Christopher,1,3,4,6,9,11,13,14] => Christopher[61]
+[Christopher,1,3,4,6,9,11,13,15] => Christopher[62]
+[Christopher,1,3,4,7,8,10,12,14] => Christopher[59]
+[Christopher,1,3,4,7,8,10,12,15] => Christopher[60]
+[Christopher,1,3,4,7,8,10,13,14] => Christopher[60]
+[Christopher,1,3,4,7,8,10,13,15] => Christopher[61]
+[Christopher,1,3,4,7,8,11,12,14] => Christopher[60]
+[Christopher,1,3,4,7,8,11,12,15] => Christopher[61]
+[Christopher,1,3,4,7,8,11,13,14] => Christopher[61]
+[Christopher,1,3,4,7,8,11,13,15] => Christopher[62]
+[Christopher,1,3,4,7,9,10,12,14] => Christopher[60]
+[Christopher,1,3,4,7,9,10,12,15] => Christopher[61]
+[Christopher,1,3,4,7,9,10,13,14] => Christopher[61]
+[Christopher,1,3,4,7,9,10,13,15] => Christopher[62]
+[Christopher,1,3,4,7,9,11,12,14] => Christopher[61]
+[Christopher,1,3,4,7,9,11,12,15] => Christopher[62]
+[Christopher,1,3,4,7,9,11,13,14] => Christopher[62]
+[Christopher,1,3,4,7,9,11,13,15] => Christopher[63]
+[Christopher,1,3,5,6,8,10,12,14] => Christopher[59]
+[Christopher,1,3,5,6,8,10,12,15] => Christopher[60]
+[Christopher,1,3,5,6,8,10,13,14] => Christopher[60]
+[Christopher,1,3,5,6,8,10,13,15] => Christopher[61]
+[Christopher,1,3,5,6,8,11,12,14] => Christopher[60]
+[Christopher,1,3,5,6,8,11,12,15] => Christopher[61]
+[Christopher,1,3,5,6,8,11,13,14] => Christopher[61]
+[Christopher,1,3,5,6,8,11,13,15] => Christopher[62]
+[Christopher,1,3,5,6,9,10,12,14] => Christopher[60]
+[Christopher,1,3,5,6,9,10,12,15] => Christopher[61]
+[Christopher,1,3,5,6,9,10,13,14] => Christopher[61]
+[Christopher,1,3,5,6,9,10,13,15] => Christopher[62]
+[Christopher,1,3,5,6,9,11,12,14] => Christopher[61]
+[Christopher,1,3,5,6,9,11,12,15] => Christopher[62]
+[Christopher,1,3,5,6,9,11,13,14] => Christopher[62]
+[Christopher,1,3,5,6,9,11,13,15] => Christopher[63]
+[Christopher,1,3,5,7,8,10,12,14] => Christopher[60]
+[Christopher,1,3,5,7,8,10,12,15] => Christopher[61]
+[Christopher,1,3,5,7,8,10,13,14] => Christopher[61]
+[Christopher,1,3,5,7,8,10,13,15] => Christopher[62]
+[Christopher,1,3,5,7,8,11,12,14] => Christopher[61]
+[Christopher,1,3,5,7,8,11,12,15] => Christopher[62]
+[Christopher,1,3,5,7,8,11,13,14] => Christopher[62]
+[Christopher,1,3,5,7,8,11,13,15] => Christopher[63]
+[Christopher,1,3,5,7,9,10,12,14] => Christopher[61]
+[Christopher,1,3,5,7,9,10,12,15] => Christopher[62]
+[Christopher,1,3,5,7,9,10,13,14] => Christopher[62]
+[Christopher,1,3,5,7,9,10,13,15] => Christopher[63]
+[Christopher,1,3,5,7,9,11,12,14] => Christopher[62]
+[Christopher,1,3,5,7,9,11,12,15] => Christopher[63]
+[Christopher,1,3,5,7,9,11,13,14] => Christopher[63]
+[Christopher,1,3,5,7,9,11,13,15] => Christopher[64]
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyAllCombinationsForSkipped.approved.txt b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyAllCombinationsForSkipped.approved.txt
new file mode 100644
index 0000000..5a8f4c3
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyAllCombinationsForSkipped.approved.txt
@@ -0,0 +1,10 @@
+skipped divisible by 3
+
+
+[1] => 1
+[2] => 2
+[4] => 4
+[5] => 5
+[7] => 7
+[8] => 8
+[10] => 10
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyArray.approved.txt b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyArray.approved.txt
new file mode 100644
index 0000000..da3e1dd
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyArray.approved.txt
@@ -0,0 +1,3 @@
+[0]=dog
+[1]=cat
+[2]=bird
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyArrayBadArray.approved.txt b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyArrayBadArray.approved.txt
new file mode 100644
index 0000000..871a226
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyArrayBadArray.approved.txt
@@ -0,0 +1,3 @@
+error while printing array
+received a string
+ string
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyArrayEmptyArray.approved.txt b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyArrayEmptyArray.approved.txt
new file mode 100644
index 0000000..17a8305
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyArrayEmptyArray.approved.txt
@@ -0,0 +1 @@
+len(array) == 0
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyArrayTransformation.approved.txt b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyArrayTransformation.approved.txt
new file mode 100644
index 0000000..ab60bde
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyArrayTransformation.approved.txt
@@ -0,0 +1,5 @@
+uppercase
+
+
+Christopher => CHRISTOPHER
+Llewellyn => LLEWELLYN
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyBadJSONBytes.approved.json b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyBadJSONBytes.approved.json
new file mode 100644
index 0000000..83b1447
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyBadJSONBytes.approved.json
@@ -0,0 +1,5 @@
+error while parsing JSON
+error:
+ invalid character 'f' looking for beginning of object key string
+JSON:
+ { foo: "bar", "age": 42, "bark": "woof" }
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyBadXMLBytes.approved.xml b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyBadXMLBytes.approved.xml
new file mode 100644
index 0000000..43dde05
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyBadXMLBytes.approved.xml
@@ -0,0 +1,5 @@
+error while parsing XML
+error:
+ XML syntax error on line 1: unexpected end element
+XML:
+ Test>
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyBadXMLStruct.approved.xml b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyBadXMLStruct.approved.xml
new file mode 100644
index 0000000..33e85da
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyBadXMLStruct.approved.xml
@@ -0,0 +1,7 @@
+error while pretty printing XML
+when using anonymous types be sure to include
+ XMLName xml.Name `xml:"Your_Name_Here"`
+error:
+ xml: unsupported type: struct { Title string }
+XML:
+ {Hello World!}
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyJSONBytes.approved.json b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyJSONBytes.approved.json
new file mode 100644
index 0000000..4b3c1b1
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyJSONBytes.approved.json
@@ -0,0 +1,5 @@
+{
+ "age": 42,
+ "bark": "woof",
+ "foo": "bar"
+}
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyJSONStruct.approved.json b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyJSONStruct.approved.json
new file mode 100644
index 0000000..4d8edbe
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyJSONStruct.approved.json
@@ -0,0 +1,5 @@
+{
+ "Title": "Hello World!",
+ "Name": "Peter Pan",
+ "Age": 100
+}
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyMap.approved.txt b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyMap.approved.txt
new file mode 100644
index 0000000..1e5c312
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyMap.approved.txt
@@ -0,0 +1,2 @@
+[cat]=meow
+[dog]=bark
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyMapBadMap.approved.txt b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyMapBadMap.approved.txt
new file mode 100644
index 0000000..a353125
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyMapBadMap.approved.txt
@@ -0,0 +1,3 @@
+error while printing map
+received a string
+ foo
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyMapEmptyMap.approved.txt b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyMapEmptyMap.approved.txt
new file mode 100644
index 0000000..9061572
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyMapEmptyMap.approved.txt
@@ -0,0 +1 @@
+len(map) == 0
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyStringApproval.approved.txt b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyStringApproval.approved.txt
new file mode 100644
index 0000000..c57eff5
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyStringApproval.approved.txt
@@ -0,0 +1 @@
+Hello World!
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyXMLBytes.approved.xml b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyXMLBytes.approved.xml
new file mode 100644
index 0000000..7454ab2
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyXMLBytes.approved.xml
@@ -0,0 +1,5 @@
+
+ Hello World!
+ Peter Pan
+ 100
+
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyXMLStruct.approved.xml b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyXMLStruct.approved.xml
new file mode 100644
index 0000000..7454ab2
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/approvals_test.TestVerifyXMLStruct.approved.xml
@@ -0,0 +1,5 @@
+
+ Hello World!
+ Peter Pan
+ 100
+
\ No newline at end of file
diff --git a/vendor/github.com/approvals/go-approval-tests/combination_approvals.go b/vendor/github.com/approvals/go-approval-tests/combination_approvals.go
new file mode 100644
index 0000000..9c775a8
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/combination_approvals.go
@@ -0,0 +1,296 @@
+package approvaltests
+
+import (
+ "fmt"
+ "strings"
+
+ "reflect"
+)
+
+type emptyType struct{}
+
+var (
+ // SkipThisCombination should be returned if you do not want to process a particular combination
+ SkipThisCombination = "♬ SKIP THIS COMBINATION ♬"
+
+ empty = emptyType{}
+ emptyCollection = []emptyType{empty}
+)
+
+// VerifyAllCombinationsFor1 Example:
+// VerifyAllCombinationsFor1(t, "uppercase", func(x interface{}) string { return strings.ToUpper(x.(string)) }, []string("dog", "cat"})
+func VerifyAllCombinationsFor1(t Failable, header string, transform func(interface{}) string, collection1 interface{}) error {
+ transform2 := func(p1, p2, p3, p4, p5, p6, p7, p8, p9 interface{}) string {
+ return transform(p1)
+ }
+
+ return VerifyAllCombinationsFor9(t, header, transform2, collection1,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection)
+}
+
+// VerifyAllCombinationsFor2 Example:
+// VerifyAllCombinationsFor2(t, "uppercase", func(x interface{}) string { return strings.ToUpper(x.(string)) }, []string("dog", "cat"}, []int{1,2)
+func VerifyAllCombinationsFor2(t Failable, header string, transform func(interface{}, interface{}) string, collection1 interface{}, collection2 interface{}) error {
+ transform2 := func(p1, p2, p3, p4, p5, p6, p7, p8, p9 interface{}) string {
+ return transform(p1, p2)
+ }
+
+ return VerifyAllCombinationsFor9(t, header, transform2, collection1,
+ collection2,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection)
+}
+
+// VerifyAllCombinationsFor3 is for combinations of 3.
+func VerifyAllCombinationsFor3(
+ t Failable,
+ header string,
+ transform func(p1, p2, p3 interface{}) string,
+ collection1, collection2, collection3 interface{}) error {
+
+ kerning := func(p1, p2, p3, p4, p5, p6, p7, p8, p9 interface{}) string {
+ return transform(p1, p2, p3)
+ }
+
+ return VerifyAllCombinationsFor9(t, header, kerning,
+ collection1,
+ collection2,
+ collection3,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection)
+}
+
+// VerifyAllCombinationsFor4 is for combinations of 4.
+func VerifyAllCombinationsFor4(
+ t Failable,
+ header string,
+ transform func(p1, p2, p3, p4 interface{}) string,
+ collection1, collection2, collection3, collection4 interface{}) error {
+
+ kerning := func(p1, p2, p3, p4, p5, p6, p7, p8, p9 interface{}) string {
+ return transform(p1, p2, p3, p4)
+ }
+
+ return VerifyAllCombinationsFor9(t, header, kerning,
+ collection1,
+ collection2,
+ collection3,
+ collection4,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection)
+}
+
+// VerifyAllCombinationsFor5 is for combinations of 5.
+func VerifyAllCombinationsFor5(
+ t Failable,
+ header string,
+ transform func(p1, p2, p3, p4, p5 interface{}) string,
+ collection1, collection2, collection3, collection4, collection5 interface{}) error {
+
+ kerning := func(p1, p2, p3, p4, p5, p6, p7, p8, p9 interface{}) string {
+ return transform(p1, p2, p3, p4, p5)
+ }
+
+ return VerifyAllCombinationsFor9(t, header, kerning,
+ collection1,
+ collection2,
+ collection3,
+ collection4,
+ collection5,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection)
+}
+
+// VerifyAllCombinationsFor6 is for combinations of 6.
+func VerifyAllCombinationsFor6(
+ t Failable,
+ header string,
+ transform func(p1, p2, p3, p4, p5, p6 interface{}) string,
+ collection1, collection2, collection3, collection4, collection5, collection6 interface{}) error {
+
+ kerning := func(p1, p2, p3, p4, p5, p6, p7, p8, p9 interface{}) string {
+ return transform(p1, p2, p3, p4, p5, p6)
+ }
+
+ return VerifyAllCombinationsFor9(t, header, kerning,
+ collection1,
+ collection2,
+ collection3,
+ collection4,
+ collection5,
+ collection6,
+ emptyCollection,
+ emptyCollection,
+ emptyCollection)
+}
+
+// VerifyAllCombinationsFor7 is for combinations of 7.
+func VerifyAllCombinationsFor7(
+ t Failable,
+ header string,
+ transform func(p1, p2, p3, p4, p5, p6, p7 interface{}) string,
+ collection1, collection2, collection3, collection4, collection5, collection6, collection7 interface{}) error {
+
+ kerning := func(p1, p2, p3, p4, p5, p6, p7, p8, p9 interface{}) string {
+ return transform(p1, p2, p3, p4, p5, p6, p7)
+ }
+
+ return VerifyAllCombinationsFor9(t, header, kerning,
+ collection1,
+ collection2,
+ collection3,
+ collection4,
+ collection5,
+ collection6,
+ collection7,
+ emptyCollection,
+ emptyCollection)
+}
+
+// VerifyAllCombinationsFor8 is for combinations of 8.
+func VerifyAllCombinationsFor8(
+ t Failable,
+ header string,
+ transform func(p1, p2, p3, p4, p5, p6, p7, p8 interface{}) string,
+ collection1, collection2, collection3, collection4, collection5, collection6, collection7, collection8 interface{}) error {
+
+ kerning := func(p1, p2, p3, p4, p5, p6, p7, p8, p9 interface{}) string {
+ return transform(p1, p2, p3, p4, p5, p6, p7, p8)
+ }
+
+ return VerifyAllCombinationsFor9(t, header, kerning,
+ collection1,
+ collection2,
+ collection3,
+ collection4,
+ collection5,
+ collection6,
+ collection7,
+ collection8,
+ emptyCollection)
+}
+
+// VerifyAllCombinationsFor9 is for combinations of 9.
+func VerifyAllCombinationsFor9(
+ t Failable,
+ header string,
+ transform func(a, b, c, d, e, f, g, h, i interface{}) string,
+ collection1,
+ collection2,
+ collection3,
+ collection4,
+ collection5,
+ collection6,
+ collection7,
+ collection8,
+ collection9 interface{}) error {
+
+ if len(header) != 0 {
+ header = fmt.Sprintf("%s\n\n\n", header)
+ }
+
+ var mapped []string
+
+ slice1 := reflect.ValueOf(collection1)
+ slice2 := reflect.ValueOf(collection2)
+ slice3 := reflect.ValueOf(collection3)
+ slice4 := reflect.ValueOf(collection4)
+ slice5 := reflect.ValueOf(collection5)
+ slice6 := reflect.ValueOf(collection6)
+ slice7 := reflect.ValueOf(collection7)
+ slice8 := reflect.ValueOf(collection8)
+ slice9 := reflect.ValueOf(collection9)
+
+ for i1 := 0; i1 < slice1.Len(); i1++ {
+ for i2 := 0; i2 < slice2.Len(); i2++ {
+ for i3 := 0; i3 < slice3.Len(); i3++ {
+ for i4 := 0; i4 < slice4.Len(); i4++ {
+ for i5 := 0; i5 < slice5.Len(); i5++ {
+ for i6 := 0; i6 < slice6.Len(); i6++ {
+ for i7 := 0; i7 < slice7.Len(); i7++ {
+ for i8 := 0; i8 < slice8.Len(); i8++ {
+ for i9 := 0; i9 < slice9.Len(); i9++ {
+ p1 := slice1.Index(i1).Interface()
+ p2 := slice2.Index(i2).Interface()
+ p3 := slice3.Index(i3).Interface()
+ p4 := slice4.Index(i4).Interface()
+ p5 := slice5.Index(i5).Interface()
+ p6 := slice6.Index(i6).Interface()
+ p7 := slice7.Index(i7).Interface()
+ p8 := slice8.Index(i8).Interface()
+ p9 := slice9.Index(i9).Interface()
+
+ parameterText := getParameterText(p1, p2, p3, p4, p5, p6, p7, p8, p9)
+ transformText := getTransformText(transform, p1, p2, p3, p4, p5, p6, p7, p8, p9)
+ if transformText != SkipThisCombination {
+ mapped = append(mapped, fmt.Sprintf("%s => %s", parameterText, transformText))
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ outputText := header + strings.Join(mapped, "\n")
+ return VerifyString(t, outputText)
+}
+
+func getParameterText(args ...interface{}) string {
+ parameterText := "["
+ for _, x := range args {
+ if x != empty {
+ parameterText += fmt.Sprintf("%v,", x)
+ }
+ }
+
+ parameterText = parameterText[0 : len(parameterText)-1]
+ parameterText += "]"
+
+ return parameterText
+}
+
+func getTransformText(
+ transform func(a, b, c, d, e, f, g, h, i interface{}) string,
+ p1,
+ p2,
+ p3,
+ p4,
+ p5,
+ p6,
+ p7,
+ p8,
+ p9 interface{}) (s string) {
+ defer func() {
+ r := recover()
+ if r != nil {
+ s = "panic occurred"
+ }
+ }()
+
+ return transform(p1, p2, p3, p4, p5, p6, p7, p8, p9)
+}
diff --git a/vendor/github.com/approvals/go-approval-tests/reporters/all_failing.go b/vendor/github.com/approvals/go-approval-tests/reporters/all_failing.go
new file mode 100644
index 0000000..788a1e3
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/reporters/all_failing.go
@@ -0,0 +1,18 @@
+package reporters
+
+var (
+ clipboardScratchData = ""
+)
+
+type allFailing struct{}
+
+// NewAllFailingTestReporter copies move file command to your clipboard
+func NewAllFailingTestReporter() Reporter {
+ return &allFailing{}
+}
+
+func (s *allFailing) Report(approved, received string) bool {
+ move := getMoveCommandText(approved, received)
+ clipboardScratchData = clipboardScratchData + move + "\n"
+ return copyToClipboard(clipboardScratchData)
+}
diff --git a/vendor/github.com/approvals/go-approval-tests/reporters/beyond_compare.go b/vendor/github.com/approvals/go-approval-tests/reporters/beyond_compare.go
new file mode 100644
index 0000000..81dccd2
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/reporters/beyond_compare.go
@@ -0,0 +1,15 @@
+package reporters
+
+type beyondCompare struct{}
+
+// NewBeyondCompareReporter creates a new reporter for Beyond Compare 4.
+func NewBeyondCompareReporter() Reporter {
+ return &beyondCompare{}
+}
+
+func (s *beyondCompare) Report(approved, received string) bool {
+ xs := []string{received, approved}
+ programName := "C:/Program Files/Beyond Compare 4/BComp.exe"
+
+ return launchProgram(programName, approved, xs...)
+}
diff --git a/vendor/github.com/approvals/go-approval-tests/reporters/clipboard.go b/vendor/github.com/approvals/go-approval-tests/reporters/clipboard.go
new file mode 100644
index 0000000..4f4f9ff
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/reporters/clipboard.go
@@ -0,0 +1,66 @@
+package reporters
+
+import (
+ "fmt"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+)
+
+type clipboard struct{}
+
+// NewClipboardReporter copies move file command to your clipboard
+func NewClipboardReporter() Reporter {
+ return &clipboard{}
+}
+
+func (s *clipboard) Report(approved, received string) bool {
+ move := getMoveCommandText(approved, received)
+ return copyToClipboard(move)
+}
+
+func copyToClipboard(move string) bool {
+ switch runtime.GOOS {
+ case "windows":
+ return copyToWindowsClipboard(move)
+ default:
+ return copyToDarwinClipboard(move)
+ }
+}
+
+func getMoveCommandText(approved, received string) string {
+ receivedFull, _ := filepath.Abs(received)
+ approvedFull, _ := filepath.Abs(approved)
+
+ var move string
+
+ switch runtime.GOOS {
+ case "windows":
+ move = fmt.Sprintf("move /Y \"%s\" \"%s\"", receivedFull, approvedFull)
+ default:
+ move = fmt.Sprintf("mv %s %s", receivedFull, approvedFull)
+ }
+
+ return move
+}
+func copyToWindowsClipboard(text string) bool {
+ return pipeToProgram("clip", text)
+}
+
+func copyToDarwinClipboard(text string) bool {
+ return pipeToProgram("pbcopy", text)
+}
+
+func pipeToProgram(programName, text string) bool {
+ c := exec.Command(programName)
+ pipe, err := c.StdinPipe()
+ if err != nil {
+ fmt.Printf("StdinPipe: err=%s", err)
+ return false
+ }
+ pipe.Write([]byte(text))
+ pipe.Close()
+
+ c.Start()
+ return true
+}
diff --git a/vendor/github.com/approvals/go-approval-tests/reporters/continuous_integration.go b/vendor/github.com/approvals/go-approval-tests/reporters/continuous_integration.go
new file mode 100644
index 0000000..923feb7
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/reporters/continuous_integration.go
@@ -0,0 +1,28 @@
+package reporters
+
+import (
+ "os"
+ "strconv"
+)
+
+type continuousIntegration struct{}
+
+// NewContinuousIntegrationReporter creates a new reporter for CI.
+//
+// The reporter checks the environment variable CI for a value of true.
+func NewContinuousIntegrationReporter() Reporter {
+ return &continuousIntegration{}
+}
+
+func (s *continuousIntegration) Report(approved, received string) bool {
+ value, exists := os.LookupEnv("CI")
+
+ if exists {
+ ci, err := strconv.ParseBool(value)
+ if err == nil {
+ return ci
+ }
+ }
+
+ return false
+}
diff --git a/vendor/github.com/approvals/go-approval-tests/reporters/diff_reporter.go b/vendor/github.com/approvals/go-approval-tests/reporters/diff_reporter.go
new file mode 100644
index 0000000..630548e
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/reporters/diff_reporter.go
@@ -0,0 +1,40 @@
+package reporters
+
+import (
+ "os/exec"
+
+ "github.com/approvals/go-approval-tests/utils"
+)
+
+// NewFrontLoadedReporter creates the default front loaded reporter.
+func NewFrontLoadedReporter() *Reporter {
+ tmp := NewFirstWorkingReporter(
+ NewContinuousIntegrationReporter(),
+ )
+
+ return &tmp
+}
+
+// NewDiffReporter creates the default diff reporter.
+func NewDiffReporter() *Reporter {
+ tmp := NewFirstWorkingReporter(
+ NewBeyondCompareReporter(),
+ NewIntelliJReporter(),
+ NewPrintSupportedDiffProgramsReporter(),
+ NewQuietReporter(),
+ )
+
+ return &tmp
+}
+
+func launchProgram(programName, approved string, args ...string) bool {
+ if !utils.DoesFileExist(programName) {
+ return false
+ }
+
+ utils.EnsureExists(approved)
+
+ cmd := exec.Command(programName, args...)
+ cmd.Start()
+ return true
+}
diff --git a/vendor/github.com/approvals/go-approval-tests/reporters/file_launcher.go b/vendor/github.com/approvals/go-approval-tests/reporters/file_launcher.go
new file mode 100644
index 0000000..cb9bd5b
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/reporters/file_launcher.go
@@ -0,0 +1,27 @@
+package reporters
+
+import (
+ "os/exec"
+ "runtime"
+)
+
+type fileLauncher struct{}
+
+// NewFileLauncherReporter launches registered application of the received file's type only.
+func NewFileLauncherReporter() Reporter {
+ return &fileLauncher{}
+}
+
+func (s *fileLauncher) Report(approved, received string) bool {
+ var cmd *exec.Cmd
+
+ switch runtime.GOOS {
+ case "windows":
+ cmd = exec.Command("cmd", "/C", "start", "Needed Title", received, "/B")
+ default:
+ cmd = exec.Command("open", received)
+ }
+
+ cmd.Start()
+ return true
+}
diff --git a/vendor/github.com/approvals/go-approval-tests/reporters/intellij.go b/vendor/github.com/approvals/go-approval-tests/reporters/intellij.go
new file mode 100644
index 0000000..63e9506
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/reporters/intellij.go
@@ -0,0 +1,15 @@
+package reporters
+
+type intellij struct{}
+
+// NewIntelliJReporter creates a new reporter for IntelliJ.
+func NewIntelliJReporter() Reporter {
+ return &intellij{}
+}
+
+func (s *intellij) Report(approved, received string) bool {
+ xs := []string{"diff", received, approved}
+ programName := "C:/Program Files (x86)/JetBrains/IntelliJ IDEA 2016/bin/idea.exe"
+
+ return launchProgram(programName, approved, xs...)
+}
diff --git a/vendor/github.com/approvals/go-approval-tests/reporters/newbie.go b/vendor/github.com/approvals/go-approval-tests/reporters/newbie.go
new file mode 100644
index 0000000..8e662bf
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/reporters/newbie.go
@@ -0,0 +1,18 @@
+package reporters
+
+import (
+ "fmt"
+)
+
+type printSupportedDiffPrograms struct{}
+
+// NewPrintSupportedDiffProgramsReporter creates a new reporter that states what reporters are supported.
+func NewPrintSupportedDiffProgramsReporter() Reporter {
+ return &quiet{}
+}
+
+func (s *printSupportedDiffPrograms) Report(approved, received string) bool {
+ fmt.Printf("no diff reporters found on your system\ncurrently supported reporters are [in order of preference]:\nBeyond Compare\nIntelliJ")
+
+ return false
+}
diff --git a/vendor/github.com/approvals/go-approval-tests/reporters/quiet.go b/vendor/github.com/approvals/go-approval-tests/reporters/quiet.go
new file mode 100644
index 0000000..a715c0e
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/reporters/quiet.go
@@ -0,0 +1,29 @@
+package reporters
+
+import (
+ "fmt"
+ "path/filepath"
+
+ "github.com/approvals/go-approval-tests/utils"
+)
+
+type quiet struct{}
+
+// NewQuietReporter creates a new reporter that does nothing.
+func NewQuietReporter() Reporter {
+ return &quiet{}
+}
+
+func (s *quiet) Report(approved, received string) bool {
+ approvedFull, _ := filepath.Abs(approved)
+ receivedFull, _ := filepath.Abs(received)
+
+ if utils.DoesFileExist(approved) {
+ fmt.Printf("approval files did not match\napproved: %v\nreceived: %v\n", approvedFull, receivedFull)
+
+ } else {
+ fmt.Printf("result never approved\napproved: %v\nreceived: %v\n", approvedFull, receivedFull)
+ }
+
+ return true
+}
diff --git a/vendor/github.com/approvals/go-approval-tests/reporters/reporter.go b/vendor/github.com/approvals/go-approval-tests/reporters/reporter.go
new file mode 100644
index 0000000..99d6866
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/reporters/reporter.go
@@ -0,0 +1,53 @@
+package reporters
+
+// Reporter are called on failing approvals.
+type Reporter interface {
+ // Report is called when the approved and received file do not match.
+ Report(approved, received string) bool
+}
+
+// FirstWorkingReporter reports using the first possible reporter.
+type FirstWorkingReporter struct {
+ Reporters []Reporter
+}
+
+// Report is called when the approved and received file do not match.
+func (s *FirstWorkingReporter) Report(approved, received string) bool {
+ for _, reporter := range s.Reporters {
+ result := reporter.Report(approved, received)
+ if result {
+ return true
+ }
+ }
+
+ return false
+}
+
+// NewFirstWorkingReporter creates in the order reporters are passed in.
+func NewFirstWorkingReporter(reporters ...Reporter) Reporter {
+ return &FirstWorkingReporter{
+ Reporters: reporters,
+ }
+}
+
+// MultiReporter reports all reporters.
+type MultiReporter struct {
+ Reporters []Reporter
+}
+
+// Report is called when the approved and received file do not match.
+func (s *MultiReporter) Report(approved, received string) bool {
+ result := false
+ for _, reporter := range s.Reporters {
+ result = reporter.Report(approved, received) || result
+ }
+
+ return result
+}
+
+// NewMultiReporter calls all reporters.
+func NewMultiReporter(reporters ...Reporter) Reporter {
+ return &MultiReporter{
+ Reporters: reporters,
+ }
+}
diff --git a/vendor/github.com/approvals/go-approval-tests/utils/collection_utils.go b/vendor/github.com/approvals/go-approval-tests/utils/collection_utils.go
new file mode 100644
index 0000000..1602fa6
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/utils/collection_utils.go
@@ -0,0 +1,77 @@
+package utils
+
+import (
+ "fmt"
+ "reflect"
+ "sort"
+ "strings"
+)
+
+// PrintMap prints a map
+func PrintMap(m interface{}) string {
+ var outputText string
+
+ v := reflect.ValueOf(m)
+ if v.Kind() != reflect.Map {
+ outputText = fmt.Sprintf("error while printing map\nreceived a %T\n %s\n", m, m)
+ } else {
+
+ keys := v.MapKeys()
+ var xs []string
+
+ for _, k := range keys {
+ xs = append(xs, fmt.Sprintf("[%s]=%s", k, v.MapIndex(k)))
+ }
+
+ sort.Strings(xs)
+ if len(xs) == 0 {
+ outputText = "len(map) == 0"
+ } else {
+ outputText = strings.Join(xs, "\n")
+ }
+ }
+
+ return outputText
+}
+
+// PrintArray prints an array
+func PrintArray(m interface{}) string {
+ var outputText string
+
+ switch reflect.TypeOf(m).Kind() {
+ case reflect.Slice:
+ var xs []string
+
+ slice := reflect.ValueOf(m)
+ for i := 0; i < slice.Len(); i++ {
+ xs = append(xs, fmt.Sprintf("[%d]=%s", i, slice.Index(i)))
+ }
+
+ if len(xs) == 0 {
+ outputText = "len(array) == 0"
+ } else {
+ outputText = strings.Join(xs, "\n")
+ }
+ default:
+ outputText = fmt.Sprintf("error while printing array\nreceived a %T\n %s\n", m, m)
+ }
+
+ return outputText
+}
+
+// MapToString maps a collection to a string collection
+func MapToString(collection interface{}, transform func(x interface{}) string) []string {
+ switch reflect.TypeOf(collection).Kind() {
+ case reflect.Slice:
+ var xs []string
+
+ slice := reflect.ValueOf(collection)
+ for i := 0; i < slice.Len(); i++ {
+ xs = append(xs, transform(slice.Index(i).Interface()))
+ }
+
+ return xs
+ default:
+ panic(fmt.Sprintf("error while mapping array to string\nreceived a %T\n %s\n", collection, collection))
+ }
+}
diff --git a/vendor/github.com/approvals/go-approval-tests/utils/file_utils.go b/vendor/github.com/approvals/go-approval-tests/utils/file_utils.go
new file mode 100644
index 0000000..bec3b95
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/utils/file_utils.go
@@ -0,0 +1,24 @@
+package utils
+
+import (
+ "io/ioutil"
+ "os"
+)
+
+// DoesFileExist checks if a file exists.
+func DoesFileExist(fileName string) bool {
+ _, err := os.Stat(fileName)
+ if os.IsNotExist(err) {
+ return false
+ }
+ return true
+}
+
+// EnsureExists creates if the file does not already exist.
+func EnsureExists(fileName string) {
+ if DoesFileExist(fileName) {
+ return
+ }
+
+ ioutil.WriteFile(fileName, []byte(""), 0644)
+}
diff --git a/vendor/github.com/approvals/go-approval-tests/utils/testing_utils.go b/vendor/github.com/approvals/go-approval-tests/utils/testing_utils.go
new file mode 100644
index 0000000..c6c9c8a
--- /dev/null
+++ b/vendor/github.com/approvals/go-approval-tests/utils/testing_utils.go
@@ -0,0 +1,11 @@
+package utils
+
+import "testing"
+
+// AssertEqual Example:
+// AssertEqual(t, 10, number, "number")
+func AssertEqual(t *testing.T, expected, actual interface{}, message string) {
+ if expected != actual {
+ t.Fatalf(message+"\n[expected != actual]\n[%s != %s]", expected, actual)
+ }
+}
diff --git a/versions.go b/versions.go
index 941fafc..763aad5 100644
--- a/versions.go
+++ b/versions.go
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
+ "encoding/json"
log "github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"github.com/olekukonko/tablewriter"
@@ -15,8 +16,31 @@ func listVersions(c *cli.Context) {
if err != nil {
log.Fatalf("Request failed: %v", err)
}
+
+ json := c.Bool(flJSON.Name)
+ var f func(_ ListVersionsResponse) error
+ if json {
+ f = printListVersionsAsJSON
+ } else {
+ f = printListVersionsAsTable
+ }
+ if err := f(v); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func printListVersionsAsJSON(r ListVersionsResponse) error {
+ b, err := json.MarshalIndent(r.Extensions, "", " ")
+ if err != nil {
+ return fmt.Errorf("failed to format as json: %+v", err)
+ }
+ fmt.Fprintf(os.Stdout, "%s", string(b))
+ return nil
+}
+
+func printListVersionsAsTable(v ListVersionsResponse) error {
table := tablewriter.NewWriter(os.Stdout)
- table.SetColWidth(100)
+ table.SetColWidth(4000)
table.SetHeader([]string{"Namespace", "Type", "Version", "Replicated?", "Internal?", "Regions"})
data := [][]string{}
for _, e := range v.Extensions {
@@ -24,4 +48,6 @@ func listVersions(c *cli.Context) {
}
table.AppendBulk(data)
table.Render()
+
+ return nil
}