зеркало из https://github.com/microsoft/abstrakt.git
Merge pull request #61 from microsoft/feat/50
Refactor Project and Clean-up Repo
This commit is contained in:
Коммит
20515f7b78
|
@ -3,7 +3,7 @@
|
|||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
FROM golang:1.13.1
|
||||
FROM golang:1.13.1-stretch
|
||||
|
||||
# Avoid warnings by switching to noninteractive
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
@ -18,6 +18,7 @@ RUN apt-get update \
|
|||
&& go get -x -d github.com/stamblerre/gocode 2>&1 \
|
||||
&& go build -o gocode-gomod github.com/stamblerre/gocode \
|
||||
&& mv gocode-gomod $GOPATH/bin/ \
|
||||
&& GO111MODULE=on go get -v golang.org/x/tools/gopls@latest \
|
||||
&& go get -u -v \
|
||||
github.com/google/wire/cmd/wire \
|
||||
github.com/rakyll/gotest \
|
||||
|
@ -38,17 +39,15 @@ RUN apt-get update \
|
|||
github.com/cweill/gotests/... \
|
||||
golang.org/x/tools/cmd/goimports \
|
||||
golang.org/x/lint/golint \
|
||||
golang.org/x/tools/cmd/gopls \
|
||||
github.com/alecthomas/gometalinter \
|
||||
honnef.co/go/tools/... \
|
||||
github.com/mgechev/revive \
|
||||
github.com/derekparker/delve/cmd/dlv 2>&1 \
|
||||
&& GO111MODULE=on go get golang.org/x/tools/gopls@latest \
|
||||
&& curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.21.0 \
|
||||
&& curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.23.1 \
|
||||
&& apt-get autoremove -y \
|
||||
&& apt-get clean -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
RUN apt-get update \
|
||||
# Install Docker CE CLI
|
||||
|
|
|
@ -2,21 +2,23 @@ run:
|
|||
deadline: 5m
|
||||
skip-files: []
|
||||
linters-settings:
|
||||
linters-settings.govet:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
linters-settings.gocyclo:
|
||||
gocyclo:
|
||||
min-complexity: 12.0
|
||||
linters-settings.maligned:
|
||||
maligned:
|
||||
suggest-new: true
|
||||
linters-settings.goconst:
|
||||
goconst:
|
||||
min-len: 3.0
|
||||
min-occurrences: 3.0
|
||||
linters-settings.misspell:
|
||||
locale: "US"
|
||||
ignore-words:
|
||||
misspell:
|
||||
locale: "UK"
|
||||
ignore-words:
|
||||
- listend
|
||||
- analyses
|
||||
- cancelling
|
||||
- color
|
||||
- colors
|
||||
linters:
|
||||
enable:
|
||||
- vet
|
||||
|
|
|
@ -32,7 +32,7 @@ With everything installed and running, you can continue.
|
|||
|
||||
You can find sample constellation files in the following location:
|
||||
|
||||
`/sample/constellation/`
|
||||
`/examples/constellation/`
|
||||
|
||||
Using these files you can test the app binary.
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
trigger:
|
||||
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
|
@ -15,12 +16,16 @@ variables:
|
|||
|
||||
steps:
|
||||
- task: GoTool@0
|
||||
displayName: 'Using Go v1.13'
|
||||
displayName: 'Using Go v1.13.1'
|
||||
inputs:
|
||||
version: '1.13'
|
||||
GOPATH: '$(GOPATH)'
|
||||
GOBIN: '$(GOBIN)'
|
||||
|
||||
version: '1.13.1'
|
||||
goPath: '$(GOPATH)'
|
||||
goBin: '$(GOBIN)'
|
||||
|
||||
- bash: |
|
||||
echo "##vso[task.setvariable variable=PATH]${PATH}:$(go env GOPATH)/bin"
|
||||
displayName: 'Exporting GOPATH to PATH'
|
||||
|
||||
- task: Go@0
|
||||
displayName: 'Resolving Dependencies'
|
||||
inputs:
|
||||
|
@ -38,14 +43,14 @@ steps:
|
|||
make lint-all
|
||||
displayName: 'Lint Abstrakt'
|
||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||
env: { GOPTH: '$(GOPATH)' }
|
||||
env: { GOPATH: '$(GOPATH)' }
|
||||
failOnStderr: true
|
||||
|
||||
- script: |
|
||||
make test-export-all
|
||||
displayName: 'Test Abstrakt'
|
||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||
env: { GOPTH: '$(GOPATH)' }
|
||||
env: { GOPATH: '$(GOPATH)' }
|
||||
|
||||
- task: PublishTestResults@2
|
||||
inputs:
|
|
@ -1,7 +1,7 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/microsoft/abstrakt/internal/tools/logger"
|
||||
"github.com/microsoft/abstrakt/tools/logger"
|
||||
cobra "github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ package cmd
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/microsoft/abstrakt/internal/chartservice"
|
||||
"github.com/microsoft/abstrakt/internal/composeservice"
|
||||
"github.com/microsoft/abstrakt/internal/tools/logger"
|
||||
"github.com/microsoft/abstrakt/internal/compose"
|
||||
"github.com/microsoft/abstrakt/internal/platform/chart"
|
||||
"github.com/microsoft/abstrakt/tools/logger"
|
||||
"github.com/spf13/cobra"
|
||||
"path"
|
||||
"strings"
|
||||
|
@ -43,15 +43,19 @@ Example: abstrakt compose [chart name] -t [templateType] -f [constellationFilePa
|
|||
return fmt.Errorf("Template type: %v is not known", cc.templateType)
|
||||
}
|
||||
|
||||
service := composeservice.NewComposeService()
|
||||
_ = service.LoadFromFile(cc.constellationFilePath, cc.mapsFilePath)
|
||||
service := new(compose.Composer)
|
||||
err = service.LoadFile(cc.constellationFilePath, cc.mapsFilePath)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debugf("noChecks is set to %t", *cc.noChecks)
|
||||
|
||||
if !*cc.noChecks {
|
||||
logger.Debug("Starting validating constellation")
|
||||
|
||||
err = validateDag(&service.DagConfigService)
|
||||
err = validateDag(&service.Constellation)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -60,12 +64,12 @@ Example: abstrakt compose [chart name] -t [templateType] -f [constellationFilePa
|
|||
logger.Debug("Finished validating constellation")
|
||||
}
|
||||
|
||||
chart, err := service.Compose(chartName, cc.outputPath)
|
||||
helm, err := service.Build(chartName, cc.outputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not compose: %v", err)
|
||||
}
|
||||
|
||||
err = chartservice.SaveChartToDir(chart, cc.outputPath)
|
||||
err = chart.SaveToDir(helm, cc.outputPath)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("There was an error saving the chart: %v", err)
|
||||
|
@ -73,14 +77,14 @@ Example: abstrakt compose [chart name] -t [templateType] -f [constellationFilePa
|
|||
|
||||
logger.Infof("Chart was saved to: %v", cc.outputPath)
|
||||
|
||||
out, err := chartservice.BuildChart(path.Join(cc.outputPath, chartName))
|
||||
out, err := chart.Build(path.Join(cc.outputPath, chartName))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("There was an error saving the chart: %v", err)
|
||||
}
|
||||
|
||||
if *cc.zipChart {
|
||||
_, err = chartservice.ZipChartToDir(chart, cc.outputPath)
|
||||
_, err = chart.ZipToDir(helm, cc.outputPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("There was an error zipping the chart: %v", err)
|
||||
}
|
||||
|
|
|
@ -1,134 +1,67 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
helper "github.com/microsoft/abstrakt/tools/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func executeCommand(root *cobra.Command, args ...string) (output string, err error) {
|
||||
_, output, err = executeCommandC(root, args...)
|
||||
return output, err
|
||||
}
|
||||
|
||||
func executeCommandC(root *cobra.Command, args ...string) (c *cobra.Command, output string, err error) {
|
||||
buf := new(bytes.Buffer)
|
||||
root.SetOutput(buf)
|
||||
root.SetArgs(args)
|
||||
c, err = root.ExecuteC()
|
||||
return
|
||||
}
|
||||
|
||||
func TestComposeCommandReturnsErrorIfTemplateTypeIsInvalid(t *testing.T) {
|
||||
templateType := "ble"
|
||||
constellationPath, mapsPath, tdir := PrepareRealFilesForTest(t)
|
||||
constellationPath, mapsPath, tdir := helper.PrepareRealFilesForTest(t)
|
||||
|
||||
defer CleanTempTestFiles(t, tdir)
|
||||
defer helper.CleanTempTestFiles(t, tdir)
|
||||
|
||||
output, err := executeCommand(newComposeCmd().cmd, "test-compose-cmd-returns-error-if-template-type-is-invalid", "-f", constellationPath, "-m", mapsPath, "-t", templateType, "-o", tdir)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Did not received expected error. \nGot:\n %v", output)
|
||||
}
|
||||
output, err := helper.ExecuteCommand(newComposeCmd().cmd, "test-compose-cmd-returns-error-if-template-type-is-invalid", "-f", constellationPath, "-m", mapsPath, "-t", templateType, "-o", tdir)
|
||||
assert.Errorf(t, err, "Did not received expected error. \nGot:\n %v", output)
|
||||
}
|
||||
|
||||
func TestComposeCommandDoesNotErrorIfTemplateTypeIsEmptyOrHelm(t *testing.T) {
|
||||
templateType := ""
|
||||
constellationPath, mapsPath, tdir := PrepareRealFilesForTest(t)
|
||||
constellationPath, mapsPath, tdir := helper.PrepareRealFilesForTest(t)
|
||||
|
||||
defer CleanTempTestFiles(t, tdir)
|
||||
defer helper.CleanTempTestFiles(t, tdir)
|
||||
|
||||
output, err := executeCommand(newComposeCmd().cmd, "test-compose-cmd-does-not-error-if-template-type-is-empty-or-helm", "-f", constellationPath, "-m", mapsPath, "-t", templateType, "-o", tdir)
|
||||
output, err := helper.ExecuteCommand(newComposeCmd().cmd, "test-compose-cmd-does-not-error-if-template-type-is-empty-or-helm", "-f", constellationPath, "-m", mapsPath, "-t", templateType, "-o", tdir)
|
||||
assert.NoErrorf(t, err, "Did not expect error:\n %v\n output: %v", err, output)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Did not expect error:\n %v\n output: %v", err, output)
|
||||
}
|
||||
templateType = "helm"
|
||||
output, err = executeCommand(newComposeCmd().cmd, "test-compose-cmd-does-not-error-if-template-type-is-empty-or-helm", "-f", constellationPath, "-m", mapsPath, "-t", templateType, "-o", tdir)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Did not expect error:\n %v\n output: %v", err, output)
|
||||
}
|
||||
output, err = helper.ExecuteCommand(newComposeCmd().cmd, "test-compose-cmd-does-not-error-if-template-type-is-empty-or-helm", "-f", constellationPath, "-m", mapsPath, "-t", templateType, "-o", tdir)
|
||||
assert.NoErrorf(t, err, "Did not expect error:\n %v\n output: %v", err, output)
|
||||
}
|
||||
|
||||
func TestComposeCommandReturnsErrorWithInvalidFilePaths(t *testing.T) {
|
||||
output, err := executeCommand(newComposeCmd().cmd, "test-compose-cmd-returns-error-with-invalid-files", "-f", "invalid", "-m", "invalid", "-o", "invalid")
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Did not received expected error. \nGot:\n %v", output)
|
||||
}
|
||||
output, err := helper.ExecuteCommand(newComposeCmd().cmd, "test-compose-cmd-returns-error-with-invalid-files", "-f", "invalid", "-m", "invalid", "-o", "invalid")
|
||||
assert.Errorf(t, err, "Did not received expected error. \nGot:\n %v", output)
|
||||
}
|
||||
|
||||
// TODO bug #43: figure out how to make this test work reliably.
|
||||
// Something weird is making this test fail when run along with other tests in the package.
|
||||
// It passes whenever it runs on it's own.
|
||||
func TestComposeCmdVerifyRequiredFlags(t *testing.T) {
|
||||
expected := "required flag(s) \"constellationFilePath\", \"mapsFilePath\", \"outputPath\" not set"
|
||||
|
||||
output, err := executeCommand(newComposeCmd().cmd, "")
|
||||
output, err := helper.ExecuteCommand(newComposeCmd().cmd, "")
|
||||
|
||||
if err != nil {
|
||||
checkStringContains(t, err.Error(), expected)
|
||||
assert.Contains(t, err.Error(), expected)
|
||||
} else {
|
||||
t.Errorf("Expecting error: \n %v\nGot:\n %v\n", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func checkStringContains(t *testing.T, got, expected string) {
|
||||
if !strings.Contains(got, expected) {
|
||||
t.Errorf("Expected to contain: \n %v\nGot:\n %v\n", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposeCmdWithValidFlags(t *testing.T) {
|
||||
constellationPath, mapsPath, tdir := PrepareRealFilesForTest(t)
|
||||
constellationPath, mapsPath, tdir := helper.PrepareRealFilesForTest(t)
|
||||
|
||||
defer CleanTempTestFiles(t, tdir)
|
||||
defer helper.CleanTempTestFiles(t, tdir)
|
||||
|
||||
output, err := executeCommand(newComposeCmd().cmd, "test-compose-cmd-with-flags", "-f", constellationPath, "-m", mapsPath, "-o", tdir)
|
||||
if err != nil {
|
||||
t.Errorf("error: \n %v\noutput:\n %v\n", err, output)
|
||||
}
|
||||
output, err := helper.ExecuteCommand(newComposeCmd().cmd, "test-compose-cmd-with-flags", "-f", constellationPath, "-m", mapsPath, "-o", tdir)
|
||||
assert.NoErrorf(t, err, "error: \n %v\noutput:\n %v\n", err, output)
|
||||
}
|
||||
|
||||
func TestComposeWithRealFiles(t *testing.T) {
|
||||
constellationPath, mapsPath, tdir := PrepareRealFilesForTest(t)
|
||||
constellationPath, mapsPath, tdir := helper.PrepareRealFilesForTest(t)
|
||||
|
||||
defer CleanTempTestFiles(t, tdir)
|
||||
|
||||
output, err := executeCommand(newComposeCmd().cmd, "test-compose-cmd-with-real-files", "-f", constellationPath, "-m", mapsPath, "-o", tdir)
|
||||
if err != nil {
|
||||
t.Errorf("error: \n %v\noutput:\n %v\n", err, output)
|
||||
}
|
||||
defer helper.CleanTempTestFiles(t, tdir)
|
||||
|
||||
}
|
||||
|
||||
func PrepareRealFilesForTest(t *testing.T) (string, string, string) {
|
||||
tdir, err := ioutil.TempDir("./", "output-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cwd, err2 := os.Getwd()
|
||||
if err2 != nil {
|
||||
t.Fatal(err2)
|
||||
}
|
||||
|
||||
fmt.Print(cwd)
|
||||
|
||||
constellationPath := path.Join(cwd, "../sample/constellation/sample_constellation.yaml")
|
||||
mapsPath := path.Join(cwd, "../sample/constellation/sample_constellation_maps.yaml")
|
||||
|
||||
return constellationPath, mapsPath, tdir
|
||||
}
|
||||
|
||||
func CleanTempTestFiles(t *testing.T, temp string) {
|
||||
err := os.RemoveAll(temp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
output, err := helper.ExecuteCommand(newComposeCmd().cmd, "test-compose-cmd-with-real-files", "-f", constellationPath, "-m", mapsPath, "-o", tdir)
|
||||
assert.NoErrorf(t, err, "error: \n %v\noutput:\n %v\n", err, output)
|
||||
}
|
||||
|
|
288
cmd/diff.go
288
cmd/diff.go
|
@ -1,38 +1,23 @@
|
|||
package cmd
|
||||
|
||||
// visualise is a subcommand that constructs a graph representation of the yaml
|
||||
// input file and renders this into GraphViz 'dot' notation.
|
||||
// Initial version renders to dot syntax only, to graphically depict this the output
|
||||
// has to be run through a graphviz visualisation tool/utiliyy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/awalterschulze/gographviz"
|
||||
set "github.com/deckarep/golang-set"
|
||||
"github.com/microsoft/abstrakt/internal/dagconfigservice"
|
||||
"github.com/microsoft/abstrakt/internal/tools/logger"
|
||||
"github.com/microsoft/abstrakt/internal/diff"
|
||||
"github.com/microsoft/abstrakt/internal/platform/constellation"
|
||||
"github.com/microsoft/abstrakt/tools/logger"
|
||||
"github.com/spf13/cobra"
|
||||
// "os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type diffCmd struct {
|
||||
constellationFilePathOrg string
|
||||
constellationFilePathNew string
|
||||
showOriginal bool
|
||||
showNew bool
|
||||
showOriginal *bool
|
||||
showNew *bool
|
||||
*baseCmd
|
||||
}
|
||||
|
||||
type setsForComparison struct {
|
||||
setCommonSvcs set.Set
|
||||
setCommonRels set.Set
|
||||
setAddedSvcs set.Set
|
||||
setAddedRels set.Set
|
||||
setDelSvcs set.Set
|
||||
setDelRels set.Set
|
||||
}
|
||||
|
||||
func newDiffCmd() *diffCmd {
|
||||
cc := &diffCmd{}
|
||||
|
||||
|
@ -42,62 +27,57 @@ func newDiffCmd() *diffCmd {
|
|||
Long: `Diff is for producing a Graphviz dot notation representation of the difference between two constellations (line an old and new version)
|
||||
|
||||
Example: abstrakt diff -o [constellationFilePathOriginal] -n [constellationFilePathNew]`,
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
logger.Debug("args: " + strings.Join(args, " "))
|
||||
logger.Debug("constellationFilePathOrg: " + cc.constellationFilePathOrg)
|
||||
logger.Debug("constellationFilePathNew: " + cc.constellationFilePathNew)
|
||||
logger.Debugf("constellationFilePathOrg: %v", cc.constellationFilePathOrg)
|
||||
logger.Debugf("constellationFilePathNew: %v", cc.constellationFilePathNew)
|
||||
|
||||
if cmd.Flag("showOriginalOutput").Value.String() == "true" {
|
||||
logger.Debug("showOriginalOutput: true")
|
||||
cc.showOriginal = true
|
||||
} else {
|
||||
logger.Debug("showOriginalOutput: false")
|
||||
cc.showOriginal = false
|
||||
}
|
||||
if cmd.Flag("showNewOutput").Value.String() == "true" {
|
||||
logger.Debug("showNewOutput: true")
|
||||
cc.showNew = true
|
||||
} else {
|
||||
logger.Debug("showNewOutput: false")
|
||||
cc.showNew = false
|
||||
}
|
||||
logger.Debugf("showOriginalOutput: %t", *cc.showOriginal)
|
||||
logger.Debugf("showNewOutput: %t", *cc.showNew)
|
||||
|
||||
if !fileExists(cc.constellationFilePathOrg) {
|
||||
return fmt.Errorf("Could not open original YAML input file for reading %v", cc.constellationFilePathOrg)
|
||||
}
|
||||
|
||||
if !fileExists(cc.constellationFilePathNew) {
|
||||
return fmt.Errorf("Could not open new YAML input file for reading %v", cc.constellationFilePathNew)
|
||||
}
|
||||
|
||||
dsGraphOrg := dagconfigservice.NewDagConfigService()
|
||||
err := dsGraphOrg.LoadDagConfigFromFile(cc.constellationFilePathOrg)
|
||||
dsGraphOrg := new(constellation.Config)
|
||||
err := dsGraphOrg.LoadFile(cc.constellationFilePathOrg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dagConfigService failed to load file %q: %s", cc.constellationFilePathOrg, err)
|
||||
return fmt.Errorf("Constellation config failed to load file %q: %s", cc.constellationFilePathOrg, err)
|
||||
}
|
||||
|
||||
if cc.showOriginal {
|
||||
resStringOrg := generateGraph(dsGraphOrg)
|
||||
if *cc.showOriginal {
|
||||
out := &bytes.Buffer{}
|
||||
var resStringOrg string
|
||||
resStringOrg, err = dsGraphOrg.GenerateGraph(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Output(resStringOrg)
|
||||
}
|
||||
|
||||
dsGraphNew := dagconfigservice.NewDagConfigService()
|
||||
err = dsGraphNew.LoadDagConfigFromFile(cc.constellationFilePathNew)
|
||||
dsGraphNew := new(constellation.Config)
|
||||
err = dsGraphNew.LoadFile(cc.constellationFilePathNew)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dagConfigService failed to load file %q: %s", cc.constellationFilePathNew, err)
|
||||
return fmt.Errorf("Constellation config failed to load file %q: %s", cc.constellationFilePathNew, err)
|
||||
}
|
||||
|
||||
if cc.showNew {
|
||||
resStringNew := generateGraph(dsGraphNew)
|
||||
if *cc.showNew {
|
||||
out := &bytes.Buffer{}
|
||||
var resStringNew string
|
||||
resStringNew, err = dsGraphNew.GenerateGraph(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Output(resStringNew)
|
||||
}
|
||||
|
||||
resStringDiff := compareConstellations(dsGraphOrg, dsGraphNew)
|
||||
// logger.Output is also outputting a timestamp and 'level' message when used on the command line which causes
|
||||
// graphviz to fail !?
|
||||
constellationSets := diff.Compare{Original: dsGraphOrg, New: dsGraphNew}
|
||||
resStringDiff, err := constellationSets.CompareConstellations()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Output(resStringDiff)
|
||||
// fmt.Println(resStringDiff)
|
||||
|
||||
return nil
|
||||
},
|
||||
|
@ -105,196 +85,10 @@ Example: abstrakt diff -o [constellationFilePathOriginal] -n [constellationFileP
|
|||
|
||||
cc.cmd.Flags().StringVarP(&cc.constellationFilePathOrg, "constellationFilePathOriginal", "o", "", "original or base constellation file path")
|
||||
cc.cmd.Flags().StringVarP(&cc.constellationFilePathNew, "constellationFilePathNew", "n", "", "new or changed constellation file path")
|
||||
cc.cmd.Flags().Bool("showOriginalOutput", false, "will additionally produce dot notation for original constellation")
|
||||
cc.cmd.Flags().Bool("showNewOutput", false, "will additionally produce dot notation for new constellation")
|
||||
cc.showOriginal = cc.cmd.Flags().Bool("showOriginalOutput", false, "will additionally produce dot notation for original constellation")
|
||||
cc.showNew = cc.cmd.Flags().Bool("showNewOutput", false, "will additionally produce dot notation for new constellation")
|
||||
_ = cc.cmd.MarkFlagRequired("constellationFilePathOriginal")
|
||||
_ = cc.cmd.MarkFlagRequired("constellationFilePathNew")
|
||||
|
||||
return cc
|
||||
}
|
||||
|
||||
//compareConstellations
|
||||
func compareConstellations(dsOrg dagconfigservice.DagConfigService, dsNew dagconfigservice.DagConfigService) string {
|
||||
sets := &setsForComparison{}
|
||||
|
||||
// populate comparison sets with changes between original and new graph
|
||||
fillComparisonSets(dsOrg, dsNew, sets)
|
||||
|
||||
// build the graphviz output from new graph and comparison sets
|
||||
resString := createGraphWithChanges(dsNew, sets)
|
||||
|
||||
return resString
|
||||
}
|
||||
|
||||
// fillComparisonSets - loads provided set struct with data from the constellations and then determines the various differences between the
|
||||
// sets (original constellation and new) to help detemine what has been added, removed or changed.
|
||||
func fillComparisonSets(dsOrg dagconfigservice.DagConfigService, dsNew dagconfigservice.DagConfigService, sets *setsForComparison) {
|
||||
setOrgSvcs, setOrgRel := createSet(dsOrg)
|
||||
setNewSvcs, setNewRel := createSet(dsNew)
|
||||
|
||||
// "Added" means in the new constellation but not the original one
|
||||
// "Deleted" means something is present in the original constellation but not in the new constellation
|
||||
// Services
|
||||
sets.setCommonSvcs = setOrgSvcs.Intersect(setNewSvcs) //present in both
|
||||
sets.setAddedSvcs = setNewSvcs.Difference(setOrgSvcs) //pressent in new but not in original
|
||||
sets.setDelSvcs = setOrgSvcs.Difference(setNewSvcs) //present in original but not in new
|
||||
// Relationships
|
||||
sets.setCommonRels = setOrgRel.Intersect(setNewRel) //present in both
|
||||
sets.setAddedRels = setNewRel.Difference(setOrgRel) //pressent in new but not in original
|
||||
sets.setDelRels = setOrgRel.Difference(setNewRel) //present in original but not in new
|
||||
}
|
||||
|
||||
// createSet - utility function used to create a pair of result sets (services + relationships) based on an input constellation DAG
|
||||
func createSet(dsGraph dagconfigservice.DagConfigService) (set.Set, set.Set) {
|
||||
|
||||
// Create sets to hold services and relationships - used to find differences between old and new using intersection and difference operations
|
||||
retSetServices := set.NewSet()
|
||||
retSetRelationships := set.NewSet()
|
||||
|
||||
//Store all services in the services set
|
||||
for _, v := range dsGraph.Services {
|
||||
retSetServices.Add(v.ID)
|
||||
}
|
||||
|
||||
//Store relationships in the relationship set
|
||||
for _, v := range dsGraph.Relationships {
|
||||
retSetRelationships.Add(v.From + "|" + v.To)
|
||||
}
|
||||
|
||||
return retSetServices, retSetRelationships
|
||||
}
|
||||
|
||||
// createGraphWithChanges - use both input constellations (new and original) as well as the comparison sets to create
|
||||
// a dag that can be visualised. It uses the comparison sets to identify additions, deletions and changes between the original
|
||||
// and new constellations.
|
||||
func createGraphWithChanges(newGraph dagconfigservice.DagConfigService, sets *setsForComparison) string {
|
||||
// Lookup is used to map IDs to names. Names are easier to visualise but IDs are more important to ensure the
|
||||
// presented constellation is correct and IDs are used to link nodes together
|
||||
lookup := make(map[string]string)
|
||||
g := gographviz.NewGraph()
|
||||
|
||||
// Replace spaces with underscores, names with spaces can break graphviz engines
|
||||
if err := g.SetName(strings.Replace(newGraph.Name, " ", "_", -1) + "_diff"); err != nil {
|
||||
logger.Fatalf("error setting graph name: %v", err)
|
||||
}
|
||||
// Attribute in graphviz to change graph orientation - LR indicates Left to Right. Default is top to bottom
|
||||
if err := g.AddAttr(g.Name, "rankdir", "LR"); err != nil {
|
||||
logger.Fatalf("error adding node: %v", err)
|
||||
}
|
||||
|
||||
// Make the graph directed (a constellation is DAG)
|
||||
if err := g.SetDir(true); err != nil {
|
||||
logger.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
// Add all services from the new constellation
|
||||
// - New services - highlight with colour (i.e in setAddedSvcs)
|
||||
// - Deleted services (i.e. in setDelSvcs) - include and format appropriately
|
||||
for _, v := range newGraph.Services {
|
||||
logger.Debugf("Adding node %s", v.ID)
|
||||
newName := strings.Replace(v.ID, " ", "_", -1) // Replace spaces in names with underscores, names with spaces can break graphviz engines)
|
||||
|
||||
// attributes are graphviz specific that control formatting. The linrary used requires then in the form of a map passed as an argument so it needs
|
||||
// to be built before adding the item
|
||||
attrs := make(map[string]string)
|
||||
|
||||
// Check if the service is new to this constellation
|
||||
if sets.setAddedSvcs.Contains(newName) {
|
||||
attrs["color"] = "\"#d8ffa8\""
|
||||
}
|
||||
attrs["label"] = "\"" + v.Type + "\n" + v.ID + "\""
|
||||
fillShapeAndStyleForNodeType(v.Type, attrs)
|
||||
|
||||
lookup[v.ID] = newName
|
||||
err := g.AddNode(newGraph.Name, "\""+newName+"\"", attrs) //Surround names/labels with quotes, stops graphviz seeing special characters and breaking
|
||||
if err != nil {
|
||||
logger.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//======================================== process deleted services ==========================================
|
||||
//Process services that have been removed from the original constellation
|
||||
for v := range sets.setDelSvcs.Iter() {
|
||||
logger.Debug(v)
|
||||
vString, _ := v.(string)
|
||||
newName := strings.Replace(vString, " ", "_", -1)
|
||||
attrs := make(map[string]string)
|
||||
attrs["color"] = "\"#ff9494\""
|
||||
fillShapeAndStyleForNodeType("", attrs)
|
||||
logger.Debug("Adding deleted service ", newName)
|
||||
if err := g.AddNode(newGraph.Name, "\""+newName+"\"", attrs); err != nil {
|
||||
logger.Fatalf("error adding node: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//======================================== process relationships ==========================================
|
||||
// Add Relationships from the new constellation
|
||||
// - New relationships - highlight with colour (i.e. in setAddedSvcs)
|
||||
// - Deleted relationships (i.e. in setDelRels) - include and format appropriately
|
||||
for _, v := range newGraph.Relationships {
|
||||
logger.Debugf("Adding relationship from %s ---> %s", v.From, v.To)
|
||||
//Surround names/labels with quotes, stops graphviz seeing special characters and breaking
|
||||
localFrom := "\"" + lookup[v.From] + "\""
|
||||
localTo := "\"" + lookup[v.To] + "\""
|
||||
|
||||
attrs := make(map[string]string)
|
||||
// Relationship is stored in the map as source|destination - both are needed to tell if a relationship is new (this is how they are stored in the sets created earlier)
|
||||
relLookupName := lookup[v.From] + "|" + lookup[v.To]
|
||||
|
||||
// Check if the relationship is new (it will then not be in the common list of relationships between old and new constellation)
|
||||
if sets.setAddedRels.Contains(relLookupName) {
|
||||
attrs["color"] = "\"#d8ffa8\""
|
||||
}
|
||||
|
||||
err := g.AddEdge(localFrom, localTo, true, attrs)
|
||||
if err != nil {
|
||||
logger.Fatalf("error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
//======================================== process deleted relationships =====================================
|
||||
// Deleted relationships
|
||||
for v := range sets.setDelRels.Iter() {
|
||||
logger.Debug(v)
|
||||
vString := strings.Replace(v.(string), " ", "_", -1)
|
||||
if len(strings.Split(vString, "|")) != 2 {
|
||||
logger.Fatal("Relationships string should be two items separated by | but got ", vString)
|
||||
}
|
||||
newFrom := "\"" + strings.Split(vString, "|")[0] + "\""
|
||||
newTo := "\"" + strings.Split(vString, "|")[1] + "\""
|
||||
|
||||
attrs := make(map[string]string)
|
||||
attrs["color"] = "\"#ff9494\""
|
||||
logger.Debug("Adding deleted service relationship from ", newFrom, " to ", newTo)
|
||||
err := g.AddEdge(newFrom, newTo, true, attrs)
|
||||
if err != nil {
|
||||
logger.Fatalf("error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Produce resulting graph in dot notation format
|
||||
return g.String()
|
||||
}
|
||||
|
||||
// Populate attrs map with additional attributes based on the node type
|
||||
// Easier to change in a single place
|
||||
func fillShapeAndStyleForNodeType(nodeType string, existAttrs map[string]string) {
|
||||
|
||||
switch nodeType {
|
||||
case "EventLogger":
|
||||
existAttrs["shape"] = "rectangle"
|
||||
existAttrs["style"] = "\"rounded, filled\""
|
||||
case "EventGenerator":
|
||||
existAttrs["shape"] = "rectangle"
|
||||
existAttrs["style"] = "\"rounded, filled\""
|
||||
case "EventHub":
|
||||
existAttrs["shape"] = "rectangle"
|
||||
existAttrs["style"] = "\"rounded, filled\""
|
||||
default:
|
||||
existAttrs["shape"] = "rectangle"
|
||||
existAttrs["style"] = "\"rounded, filled\""
|
||||
}
|
||||
|
||||
}
|
||||
|
|
325
cmd/diff_test.go
325
cmd/diff_test.go
|
@ -1,341 +1,58 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
set "github.com/deckarep/golang-set"
|
||||
"github.com/microsoft/abstrakt/internal/dagconfigservice"
|
||||
helper "github.com/microsoft/abstrakt/tools/test"
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestDiffCmdWithAllRequirementsNoError - test diff command parameters
|
||||
// use valid arguments so expect no failures
|
||||
func TestDiffCmdWithAllRequirementsNoError(t *testing.T) {
|
||||
constellationPathOrg, constellationPathNew, _, _ := localPrepareRealFilesForTest(t)
|
||||
constellationPathOrg, constellationPathNew, _, _ := helper.PrepareTwoRealConstellationFilesForTest(t)
|
||||
|
||||
hook := test.NewGlobal()
|
||||
_, err := executeCommand(newDiffCmd().cmd, "-o", constellationPathOrg, "-n", constellationPathNew)
|
||||
|
||||
// fmt.Println(hook.LastEntry().Message)
|
||||
// fmt.Println(testDiffComparisonOutputString)
|
||||
|
||||
if err != nil {
|
||||
t.Error("Did not receive output")
|
||||
} else {
|
||||
if !compareGraphOutputAsSets(testDiffComparisonOutputString, hook.LastEntry().Message) {
|
||||
t.Errorf("Expcted output and produced output do not match : expected %s produced %s", testDiffComparisonOutputString, hook.LastEntry().Message)
|
||||
}
|
||||
// Did use this initially but wont work with the strongs output from the graphviz library as the sequence of entries in the output can change
|
||||
// while the sequence may change the result is still valid and the same so am usinga local comparison function to get around this problem
|
||||
// checkStringContains(t, hook.LastEntry().Message, testDiffComparisonOutputString)
|
||||
}
|
||||
}
|
||||
|
||||
// localPrepareRealFilesForTest - global function assumes only a single input file, this use the two required for the diff command
|
||||
func localPrepareRealFilesForTest(t *testing.T) (string, string, string, string) {
|
||||
tdir, err := ioutil.TempDir("./", "output-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = os.RemoveAll(tdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
cwd, err2 := os.Getwd()
|
||||
if err2 != nil {
|
||||
t.Fatal(err2)
|
||||
}
|
||||
|
||||
fmt.Print(cwd)
|
||||
|
||||
constellationPathOrg := path.Join(cwd, "../sample/constellation/sample_constellation.yaml")
|
||||
constellationPathNew := path.Join(cwd, "../sample/constellation/sample_constellation_changed.yaml")
|
||||
mapsPath := path.Join(cwd, "../sample/constellation/sample_constellation_maps.yaml")
|
||||
|
||||
return constellationPathOrg, constellationPathNew, mapsPath, tdir
|
||||
}
|
||||
|
||||
// compareGraphOutputAsSets - the graphviz library does not always output the result string with nodes and edges
|
||||
// in the same order (it can vary between calls). This does not impact using the result but makes testing the result a
|
||||
// headache as the assumption is that the expected string and the produced string would match exactly. When the sequence
|
||||
// changes they dont match. This function converts the strings into sets of lines and compares if the lines in the two outputs
|
||||
// are the same
|
||||
func compareGraphOutputAsSets(expected, produced string) bool {
|
||||
|
||||
lstExpected := strings.Split(expected, "\n")
|
||||
lstProduced := strings.Split(produced, "\n")
|
||||
|
||||
setExpected := set.NewSet()
|
||||
setProduced := set.NewSet()
|
||||
|
||||
for l := range lstExpected {
|
||||
setExpected.Add(l)
|
||||
}
|
||||
|
||||
for l := range lstProduced {
|
||||
setProduced.Add(l)
|
||||
}
|
||||
|
||||
return setProduced.Equal(setExpected)
|
||||
_, err := helper.ExecuteCommand(newDiffCmd().cmd, "-o", constellationPathOrg, "-n", constellationPathNew)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, helper.CompareGraphOutputAsSets(testDiffComparisonOutputString, hook.LastEntry().Message))
|
||||
}
|
||||
|
||||
// TestDffCmdFailYaml - test diff command parameters
|
||||
// Test both required command line parameters (-o, -n) failing each in turn
|
||||
func TestDffCmdFailYaml(t *testing.T) {
|
||||
expected := "Could not open original YAML input file for reading constellationPathOrg"
|
||||
expected := "Constellation config failed to load file \"constellationPathOrg\": open constellationPathOrg: no such file or directory"
|
||||
|
||||
output, err := executeCommand(newDiffCmd().cmd, "-o", "constellationPathOrg", "-n", "constellationPathNew")
|
||||
_, err := helper.ExecuteCommand(newDiffCmd().cmd, "-o", "constellationPathOrg", "-n", "constellationPathNew")
|
||||
|
||||
if err != nil {
|
||||
checkStringContains(t, err.Error(), expected)
|
||||
} else {
|
||||
t.Errorf("Did not fail and it should have. Expected: %v \nGot: %v", expected, output)
|
||||
}
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, expected)
|
||||
|
||||
expected = "Could not open new YAML input file for reading constellationPathNew"
|
||||
expected = "Constellation config failed to load file \"constellationPathNew\": open constellationPathNew: no such file or directory"
|
||||
|
||||
output, err = executeCommand(newDiffCmd().cmd, "-o", "../sample/constellation/sample_constellation.yaml", "-n", "constellationPathNew")
|
||||
|
||||
if err != nil {
|
||||
checkStringContains(t, err.Error(), expected)
|
||||
} else {
|
||||
t.Errorf("Did not fail and it should have. Expected: %v \nGot: %v", expected, output)
|
||||
}
|
||||
_, err = helper.ExecuteCommand(newDiffCmd().cmd, "-o", "../examples/constellation/sample_constellation.yaml", "-n", "constellationPathNew")
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, expected)
|
||||
}
|
||||
|
||||
// TestDiffCmdFailNotYaml - test diff command parameters
|
||||
// Test both required command line parameter files fail when provided with invalid input files (-o, -n) failing each in turn
|
||||
func TestDiffCmdFailNotYaml(t *testing.T) {
|
||||
expected := "dagConfigService failed to load file"
|
||||
expected := "Constellation config failed to load file \"diff.go\": yaml: line 25: mapping values are not allowed in this context"
|
||||
|
||||
output, err := executeCommand(newDiffCmd().cmd, "-o", "diff.go", "-n", "diff.go")
|
||||
_, err := helper.ExecuteCommand(newDiffCmd().cmd, "-o", "diff.go", "-n", "diff.go")
|
||||
|
||||
if err != nil {
|
||||
checkStringContains(t, err.Error(), expected)
|
||||
} else {
|
||||
t.Errorf("Did not fail. Expected: %v \nGot: %v", expected, output)
|
||||
}
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, expected)
|
||||
|
||||
output, err = executeCommand(newDiffCmd().cmd, "-o", "../sample/constellation/sample_constellation.yaml", "-n", "diff.go")
|
||||
_, err = helper.ExecuteCommand(newDiffCmd().cmd, "-o", "../examples/constellation/sample_constellation.yaml", "-n", "diff.go")
|
||||
|
||||
if err != nil {
|
||||
checkStringContains(t, err.Error(), expected)
|
||||
} else {
|
||||
t.Errorf("Did not fail. Expected: %v \nGot: %v", expected, output)
|
||||
}
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, expected)
|
||||
}
|
||||
|
||||
// TestGetComparisonSets - generate sets of common, added and removed services and relationships from input YAML
|
||||
// and compare to manually created sets with a known or expected outcome
|
||||
func TestGetComparisonSets(t *testing.T) {
|
||||
|
||||
dsGraphOrg := dagconfigservice.NewDagConfigService()
|
||||
err := dsGraphOrg.LoadDagConfigFromString(testOrgDagStr)
|
||||
if err != nil {
|
||||
t.Errorf("dagConfigService failed to load dag from test string %s", err)
|
||||
}
|
||||
|
||||
dsGraphNew := dagconfigservice.NewDagConfigService()
|
||||
err = dsGraphNew.LoadDagConfigFromString(testNewDagStr)
|
||||
if err != nil {
|
||||
t.Errorf("dagConfigService failed to load file %s", err)
|
||||
}
|
||||
|
||||
//construct sets struct for loaded constellation
|
||||
loadedSets := &setsForComparison{}
|
||||
// will populate with expected/known outcomes
|
||||
knownSets := &setsForComparison{}
|
||||
|
||||
populateComparisonSets(knownSets)
|
||||
|
||||
// function being tested
|
||||
fillComparisonSets(dsGraphOrg, dsGraphNew, loadedSets)
|
||||
|
||||
if !knownSets.setCommonSvcs.Equal(loadedSets.setCommonSvcs) {
|
||||
t.Errorf("Common services - did not match between expected result and input yaml")
|
||||
}
|
||||
|
||||
if !knownSets.setCommonRels.Equal(loadedSets.setCommonRels) {
|
||||
t.Errorf("Common relationships - did not match between expected result and input yaml")
|
||||
}
|
||||
|
||||
if !knownSets.setAddedSvcs.Equal(loadedSets.setAddedSvcs) {
|
||||
t.Errorf("Added services - did not match between expected result and input yaml")
|
||||
}
|
||||
|
||||
if !knownSets.setAddedRels.Equal(loadedSets.setAddedRels) {
|
||||
t.Errorf("Added relationships - did not match between expected result and input yaml")
|
||||
}
|
||||
|
||||
if !knownSets.setDelSvcs.Equal(loadedSets.setDelSvcs) {
|
||||
t.Errorf("Deleted services - did not match between expected result and input yaml")
|
||||
}
|
||||
|
||||
if !knownSets.setDelRels.Equal(loadedSets.setDelRels) {
|
||||
t.Errorf("Deleted relationships - did not match between expected result and input yaml")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// testGraphWithChanges - test diff comparison function
|
||||
func TestGraphWithChanges(t *testing.T) {
|
||||
|
||||
dsGraphOrg := dagconfigservice.NewDagConfigService()
|
||||
err := dsGraphOrg.LoadDagConfigFromString(testOrgDagStr)
|
||||
if err != nil {
|
||||
t.Errorf("dagConfigService failed to load dag from test string %s", err)
|
||||
}
|
||||
|
||||
dsGraphNew := dagconfigservice.NewDagConfigService()
|
||||
err = dsGraphNew.LoadDagConfigFromString(testNewDagStr)
|
||||
if err != nil {
|
||||
t.Errorf("dagConfigService failed to load file %s", err)
|
||||
}
|
||||
|
||||
//construct sets struct for loaded constellation
|
||||
loadedSets := &setsForComparison{}
|
||||
|
||||
// function being tested
|
||||
fillComparisonSets(dsGraphOrg, dsGraphNew, loadedSets)
|
||||
|
||||
resString := createGraphWithChanges(dsGraphNew, loadedSets)
|
||||
|
||||
if !compareGraphOutputAsSets(testDiffComparisonOutputString, resString) {
|
||||
t.Errorf("Resulting output does not match the reference comparison input \n RESULT \n%s EXPECTED \n%s", resString, testDiffComparisonOutputString)
|
||||
}
|
||||
}
|
||||
|
||||
// Utility to populate comparison sets with expected/known result
|
||||
func populateComparisonSets(target *setsForComparison) {
|
||||
target.setCommonSvcs = set.NewSet()
|
||||
target.setCommonSvcs.Add("3aa1e546-1ed5-4d67-a59c-be0d5905b490")
|
||||
target.setCommonSvcs.Add("1d0255d4-5b8c-4a52-b0bb-ac024cda37e5")
|
||||
target.setCommonSvcs.Add("a268fae5-2a82-4a3e-ada7-a52eeb7019ac")
|
||||
|
||||
target.setCommonRels = set.NewSet()
|
||||
target.setCommonRels.Add("3aa1e546-1ed5-4d67-a59c-be0d5905b490" + "|" + "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5")
|
||||
|
||||
target.setAddedSvcs = set.NewSet()
|
||||
target.setAddedSvcs.Add("9f1bcb3d-ff58-41d4-8779-f71e7b8800f8")
|
||||
target.setAddedSvcs.Add("b268fae5-2a82-4a3e-ada7-a52eeb7019ac")
|
||||
|
||||
target.setAddedRels = set.NewSet()
|
||||
target.setAddedRels.Add("9f1bcb3d-ff58-41d4-8779-f71e7b8800f8" + "|" + "3aa1e546-1ed5-4d67-a59c-be0d5905b490")
|
||||
target.setAddedRels.Add("1d0255d4-5b8c-4a52-b0bb-ac024cda37e5" + "|" + "a268fae5-2a82-4a3e-ada7-a52eeb7019ac")
|
||||
target.setAddedRels.Add("a268fae5-2a82-4a3e-ada7-a52eeb7019ac" + "|" + "b268fae5-2a82-4a3e-ada7-a52eeb7019ac")
|
||||
|
||||
target.setDelSvcs = set.NewSet()
|
||||
target.setDelSvcs.Add("9e1bcb3d-ff58-41d4-8779-f71e7b8800f8")
|
||||
|
||||
target.setDelRels = set.NewSet()
|
||||
target.setDelRels.Add("9e1bcb3d-ff58-41d4-8779-f71e7b8800f8" + "|" + "3aa1e546-1ed5-4d67-a59c-be0d5905b490")
|
||||
target.setDelRels.Add("3aa1e546-1ed5-4d67-a59c-be0d5905b490" + "|" + "a268fae5-2a82-4a3e-ada7-a52eeb7019ac")
|
||||
|
||||
}
|
||||
|
||||
// Sample DAG file data - original file
|
||||
const testOrgDagStr = `
|
||||
Name: "Azure Event Hubs Sample"
|
||||
Id: "d6e4a5e9-696a-4626-ba7a-534d6ff450a5"
|
||||
Services:
|
||||
- Name: "Event Generator"
|
||||
Id: "9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"
|
||||
Type: "EventGenerator"
|
||||
Properties: {}
|
||||
- Name: "Azure Event Hub"
|
||||
Id: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
Type: "EventHub"
|
||||
Properties: {}
|
||||
- Name: "Event Logger"
|
||||
Id: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
- Name: "Event Logger"
|
||||
Id: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
Relationships:
|
||||
- Name: "Generator to Event Hubs Link"
|
||||
Id: "211a55bd-5d92-446c-8be8-190f8f0e623e"
|
||||
Description: "Event Generator to Event Hub connection"
|
||||
From: "9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"
|
||||
To: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
Properties: {}
|
||||
- Name: "Event Hubs to Event Logger Link"
|
||||
Id: "08ccbd67-456f-4349-854a-4e6959e5017b"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
To: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
|
||||
Properties: {}
|
||||
- Name: "Event Hubs to Event Logger Link Repeat"
|
||||
Id: "c8a719e0-164d-408f-9ed1-06e08dc5abbe"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
To: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Properties: {}`
|
||||
|
||||
// An updated (new) constellation dag with known differences
|
||||
const testNewDagStr = `
|
||||
Name: "Azure Event Hubs Sample Changed"
|
||||
Id: "d6e4a5e9-696a-4626-ba7a-534d6ff450a5"
|
||||
Services:
|
||||
- Name: "Event Generator"
|
||||
Id: "9f1bcb3d-ff58-41d4-8779-f71e7b8800f8"
|
||||
Type: "EventGenerator"
|
||||
Properties: {}
|
||||
- Name: "Azure Event Hub"
|
||||
Id: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
Type: "EventHub"
|
||||
Properties: {}
|
||||
- Name: "Event Logger"
|
||||
Id: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
- Name: "Event Logger Added"
|
||||
Id: "b268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
- Name: "Event Logger"
|
||||
Id: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
Relationships:
|
||||
- Name: "Generator to Event Hubs Link"
|
||||
Id: "211a55bd-5d92-446c-8be8-190f8f0e623e"
|
||||
Description: "Event Generator to Event Hub connection"
|
||||
From: "9f1bcb3d-ff58-41d4-8779-f71e7b8800f8"
|
||||
To: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
Properties: {}
|
||||
- Name: "Event Hubs to Event Logger Link"
|
||||
Id: "08ccbd67-456f-4349-854a-4e6959e5017b"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
To: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
|
||||
Properties: {}
|
||||
- Name: "Event Hubs to Event Logger Link Repeat"
|
||||
Id: "c8a719e0-164d-408f-9ed1-06e08dc5abbe"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
|
||||
To: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Properties: {}
|
||||
- Name: "Event Hubs to Event Logger Link Added to the end Repeat"
|
||||
Id: "d8a719e0-164d-408f-9ed1-06e08dc5abbe"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
To: "b268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Properties: {}
|
||||
`
|
||||
|
||||
const testDiffComparisonOutputString = `digraph Azure_Event_Hubs_Sample_Changed_diff {
|
||||
rankdir=LR;
|
||||
"9f1bcb3d-ff58-41d4-8779-f71e7b8800f8"->"3aa1e546-1ed5-4d67-a59c-be0d5905b490"[ color="#d8ffa8" ];
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
Name: "Azure Event Hubs Sample"
|
||||
Id: "d6e4a5e9-696a-4626-ba7a-534d6ff450a5"
|
||||
Services:
|
||||
- Id: "Event Generator"
|
||||
Type: "EventGenerator"
|
||||
Properties: {}
|
||||
- Id: "Event Generator"
|
||||
Type: "EventHub"
|
||||
Properties: {}
|
||||
- Id: "Event Generator"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
Relationships:
|
||||
- Id: "Generator to Event Hubs Link"
|
||||
Description: "Event Generator to Event Hub connection"
|
||||
From: "Event Generator"
|
||||
To: "Event Generator"
|
||||
Properties: {}
|
||||
- Id: "Event Hubs to Event Logger Link"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "Event Generator"
|
||||
To: "Event Logger"
|
||||
Properties: {}
|
|
@ -0,0 +1,11 @@
|
|||
Name: "Basic Azure Event Hubs maps"
|
||||
Id: "a5a7c413-a020-44a2-bd23-1941adb7ad58"
|
||||
Maps:
|
||||
- ChartName: "event_hub_sample_event_hub"
|
||||
Type: "EventHub"
|
||||
Location: "../../helm/basictest"
|
||||
Version: "1.0.0"
|
||||
- ChartName: "event_hub_sample_event_hub"
|
||||
Type: "EventHub"
|
||||
Location: "../../helm/basictest"
|
||||
Version: "1.0.0"
|
|
@ -3,14 +3,13 @@ Id: "a5a7c413-a020-44a2-bd23-1941adb7ad58"
|
|||
Maps:
|
||||
- ChartName: "event_hub_sample_event_generator"
|
||||
Type: "EventGenerator"
|
||||
Location: "file://../../../sample/deps/event_hub_sample_event_generator"
|
||||
Location: "../../helm/basictest"
|
||||
Version: "1.0.0"
|
||||
- ChartName: "event_hub_sample_event_logger"
|
||||
Type: "EventLogger"
|
||||
Location: "file://../../../sample/deps/event_hub_sample_event_logger"
|
||||
Location: "../../helm/basictest2"
|
||||
Version: "1.0.0"
|
||||
- ChartName: "event_hub_sample_event_hub"
|
||||
Type: "EventHub"
|
||||
Location: "file://../../../sample/deps/event_hub_sample_event_hub"
|
||||
Location: "../../helm/basictest3"
|
||||
Version: "1.0.0"
|
||||
|
180
cmd/validate.go
180
cmd/validate.go
|
@ -2,14 +2,16 @@ package cmd
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/microsoft/abstrakt/internal/dagconfigservice"
|
||||
"github.com/microsoft/abstrakt/internal/tools/logger"
|
||||
"github.com/microsoft/abstrakt/internal/validationservice"
|
||||
"github.com/microsoft/abstrakt/internal/platform/constellation"
|
||||
"github.com/microsoft/abstrakt/internal/platform/mapper"
|
||||
"github.com/microsoft/abstrakt/tools/find"
|
||||
"github.com/microsoft/abstrakt/tools/logger"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type validateCmd struct {
|
||||
constellationFilePath string
|
||||
mapperFilePath string
|
||||
*baseCmd
|
||||
}
|
||||
|
||||
|
@ -19,24 +21,57 @@ func newValidateCmd() *validateCmd {
|
|||
cc.baseCmd = newBaseCmd(&cobra.Command{
|
||||
Use: "validate",
|
||||
Short: "Validate a constellation file for correct schema and ensure correctness.",
|
||||
Long: `Validate is used to ensure the correctness of a constellation file.
|
||||
Long: `Validate is used to ensure the correctness of a constellation and mapper files.
|
||||
|
||||
Example: abstrakt validate -f [constellationFilePath]`,
|
||||
Example: abstrakt validate -f [constellationFilePath] -m [mapperFilePath]
|
||||
abstrakt validate -f [constellationFilePath]
|
||||
abstrakt validate -m [mapperFilePath]`,
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
|
||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
d := dagconfigservice.NewDagConfigService()
|
||||
err = d.LoadDagConfigFromFile(cc.constellationFilePath)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
if len(cc.constellationFilePath) == 0 && len(cc.mapperFilePath) == 0 {
|
||||
_ = cc.baseCmd.cmd.Usage()
|
||||
return fmt.Errorf("no flags were set")
|
||||
}
|
||||
|
||||
err = validateDag(&d)
|
||||
var d constellation.Config
|
||||
var m mapper.Config
|
||||
|
||||
if err == nil {
|
||||
logger.Info("Constellation is valid.")
|
||||
fail := false
|
||||
|
||||
if len(cc.mapperFilePath) > 0 {
|
||||
m, err = loadAndValidateMapper(cc.mapperFilePath)
|
||||
if err != nil {
|
||||
logger.Errorf("Mapper: %v", err)
|
||||
fail = true
|
||||
} else {
|
||||
logger.Info("Mapper: valid")
|
||||
}
|
||||
}
|
||||
|
||||
if len(cc.constellationFilePath) > 0 {
|
||||
d, err = loadAndValidateDag(cc.constellationFilePath)
|
||||
if err != nil {
|
||||
logger.Errorf("Constellation: %v", err)
|
||||
fail = true
|
||||
} else {
|
||||
logger.Info("Constellation: valid")
|
||||
}
|
||||
}
|
||||
|
||||
if !d.IsEmpty() && !m.IsEmpty() {
|
||||
err = validateDagAndMapper(&d, &m)
|
||||
if err != nil {
|
||||
logger.Errorf("Deployment: %v", err)
|
||||
fail = true
|
||||
} else {
|
||||
logger.Info("Deployment: valid")
|
||||
}
|
||||
}
|
||||
|
||||
if fail {
|
||||
err = fmt.Errorf("Invalid configuration(s)")
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -44,41 +79,138 @@ Example: abstrakt validate -f [constellationFilePath]`,
|
|||
})
|
||||
|
||||
cc.cmd.Flags().StringVarP(&cc.constellationFilePath, "constellationFilePath", "f", "", "constellation file path")
|
||||
_ = cc.cmd.MarkFlagRequired("constellationFilePath")
|
||||
cc.cmd.Flags().StringVarP(&cc.mapperFilePath, "mapperFilePath", "m", "", "mapper file path")
|
||||
|
||||
return cc
|
||||
}
|
||||
|
||||
// validateDag takes a constellation dag and returns any errors.
|
||||
func validateDag(dag *dagconfigservice.DagConfigService) (err error) {
|
||||
service := validationservice.Validator{Config: dag}
|
||||
func validateDagAndMapper(d *constellation.Config, m *mapper.Config) (err error) {
|
||||
types := []string{}
|
||||
mapTypes := []string{}
|
||||
|
||||
err = service.ValidateModel()
|
||||
for _, i := range d.Services {
|
||||
_, exists := find.Slice(types, i.Type)
|
||||
if !exists {
|
||||
types = append(types, i.Type)
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range m.Maps {
|
||||
mapTypes = append(mapTypes, i.Type)
|
||||
}
|
||||
|
||||
logger.Debug("deployment: checking if `Service` exists in map")
|
||||
for _, i := range types {
|
||||
_, exists := find.Slice(mapTypes, i)
|
||||
if !exists {
|
||||
logger.Error("Missing map configuration(s)")
|
||||
logger.Errorf("Service `%v` does not exist in map", i)
|
||||
err = fmt.Errorf("invalid")
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func loadAndValidateDag(path string) (config constellation.Config, err error) {
|
||||
err = config.LoadFile(path)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
duplicates := service.CheckDuplicates()
|
||||
return config, validateDag(&config)
|
||||
}
|
||||
|
||||
// validateDag takes a constellation dag and returns any errors.
|
||||
func validateDag(d *constellation.Config) (err error) {
|
||||
logger.Debug("Constellation: validating schema")
|
||||
err = d.ValidateModel()
|
||||
|
||||
if err != nil {
|
||||
logger.Debug(err)
|
||||
return fmt.Errorf("invalid schema")
|
||||
}
|
||||
|
||||
logger.Debug("constellation: checking for duplicate `ID`")
|
||||
duplicates := d.FindDuplicateIDs()
|
||||
|
||||
if duplicates != nil {
|
||||
logger.Error("Duplicate IDs found:")
|
||||
logger.Error("Duplicate `ID` present in config")
|
||||
for _, i := range duplicates {
|
||||
logger.Errorf("'%v'", i)
|
||||
}
|
||||
err = error(fmt.Errorf("Constellation is invalid"))
|
||||
err = fmt.Errorf("invalid")
|
||||
}
|
||||
|
||||
connections := service.CheckServiceExists()
|
||||
logger.Debug("Constellation: checking if `Service` exists")
|
||||
connections := d.ServiceExists()
|
||||
|
||||
if len(connections) > 0 {
|
||||
logger.Error("Missing relationship(s)")
|
||||
for key, i := range connections {
|
||||
logger.Errorf("Relationship '%v' has missing Services:", key)
|
||||
logger.Errorf("Relationship '%v' has missing `Services`:", key)
|
||||
for _, j := range i {
|
||||
logger.Errorf("'%v'", j)
|
||||
}
|
||||
}
|
||||
err = error(fmt.Errorf("Constellation is invalid"))
|
||||
err = fmt.Errorf("invalid")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func loadAndValidateMapper(path string) (config mapper.Config, err error) {
|
||||
err = config.LoadFile(path)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return config, validateMapper(&config)
|
||||
}
|
||||
|
||||
// validateMapper takes a constellation mapper and returns any errors.
|
||||
func validateMapper(m *mapper.Config) (err error) {
|
||||
logger.Debug("Mapper: validating schema")
|
||||
err = m.ValidateModel()
|
||||
|
||||
if err != nil {
|
||||
logger.Debug(err)
|
||||
return fmt.Errorf("invalid schema")
|
||||
}
|
||||
|
||||
logger.Debug("Mapper: checking for duplicate `ChartName`")
|
||||
duplicates := m.FindDuplicateChartName()
|
||||
|
||||
if duplicates != nil {
|
||||
logger.Error("Duplicate `ChartName` present in config")
|
||||
for _, i := range duplicates {
|
||||
logger.Errorf("'%v'", i)
|
||||
}
|
||||
err = fmt.Errorf("invalid")
|
||||
}
|
||||
|
||||
logger.Debug("Mapper: checking for duplicate `Type`")
|
||||
duplicates = m.FindDuplicateType()
|
||||
|
||||
if duplicates != nil {
|
||||
logger.Error("Duplicate `Type` present in config")
|
||||
for _, i := range duplicates {
|
||||
logger.Errorf("'%v'", i)
|
||||
}
|
||||
err = fmt.Errorf("invalid")
|
||||
}
|
||||
|
||||
logger.Debug("Mapper: checking for duplicate `Location`")
|
||||
duplicates = m.FindDuplicateLocation()
|
||||
|
||||
if duplicates != nil {
|
||||
logger.Error("Duplicate `Location` present in config")
|
||||
for _, i := range duplicates {
|
||||
logger.Errorf("'%v'", i)
|
||||
}
|
||||
err = fmt.Errorf("invalid")
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
@ -1,27 +1,148 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
helper "github.com/microsoft/abstrakt/tools/test"
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateCommand(t *testing.T) {
|
||||
expected := "open does-not-exist: no such file or directory"
|
||||
|
||||
constellationPath, _, tdir := PrepareRealFilesForTest(t)
|
||||
|
||||
defer CleanTempTestFiles(t, tdir)
|
||||
|
||||
output, err := executeCommand(newValidateCmd().cmd, "-f", constellationPath)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Did not received expected error. \nGot:\n %v", output)
|
||||
}
|
||||
|
||||
_, err = executeCommand(newValidateCmd().cmd, "-f", "does-not-exist")
|
||||
|
||||
if err != nil {
|
||||
checkStringContains(t, err.Error(), expected)
|
||||
} else {
|
||||
t.Errorf("Did not received expected error. \nExpected: %v\nGot:\n %v", expected, err.Error())
|
||||
}
|
||||
func TestValidateCommandNoArgs(t *testing.T) {
|
||||
_, err := helper.ExecuteCommand(newValidateCmd().cmd)
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, "no flags were set")
|
||||
}
|
||||
|
||||
func TestValidateCommandConstellationAndMapper(t *testing.T) {
|
||||
constellationPath := "testdata/constellation/valid.yaml"
|
||||
mapPath := "testdata/mapper/valid.yaml"
|
||||
|
||||
output, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", constellationPath, "-m", mapPath)
|
||||
assert.NoErrorf(t, err, "Did not received expected error. \nGot:\n %v", output)
|
||||
}
|
||||
|
||||
func TestValidateCommandConstellationExist(t *testing.T) {
|
||||
constellationPath := "testdata/constellation/valid.yaml"
|
||||
|
||||
output, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", constellationPath)
|
||||
assert.NoErrorf(t, err, "Did not received expected error. \nGot:\n %v", output)
|
||||
}
|
||||
|
||||
func TestValidateCommandMapExist(t *testing.T) {
|
||||
mapPath := "testdata/mapper/valid.yaml"
|
||||
|
||||
output, err := helper.ExecuteCommand(newValidateCmd().cmd, "-m", mapPath)
|
||||
assert.NoErrorf(t, err, "Did not received expected error. \nGot:\n %v", output)
|
||||
}
|
||||
|
||||
func TestValidateCommandConstellationFail(t *testing.T) {
|
||||
expected := "Constellation: open does-not-exist: no such file or directory"
|
||||
|
||||
hook := test.NewGlobal()
|
||||
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", "does-not-exist")
|
||||
|
||||
entries := []string{}
|
||||
|
||||
for _, i := range hook.AllEntries() {
|
||||
entries = append(entries, i.Message)
|
||||
}
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, entries, expected)
|
||||
}
|
||||
|
||||
func TestValidateCommandMapFail(t *testing.T) {
|
||||
expected := "Mapper: open does-not-exist: no such file or directory"
|
||||
|
||||
hook := test.NewGlobal()
|
||||
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-m", "does-not-exist")
|
||||
|
||||
entries := helper.GetAllLogs(hook.AllEntries())
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, entries, expected)
|
||||
}
|
||||
|
||||
func TestValidateCommandConstellationInvalidSchema(t *testing.T) {
|
||||
mapPath := "testdata/mapper/valid.yaml"
|
||||
|
||||
hook := test.NewGlobal()
|
||||
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", mapPath)
|
||||
|
||||
entries := helper.GetAllLogs(hook.AllEntries())
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, entries, "Constellation: invalid schema")
|
||||
assert.EqualError(t, err, "Invalid configuration(s)")
|
||||
}
|
||||
|
||||
func TestValidateCommandNapperInvalidSchema(t *testing.T) {
|
||||
constellationPath := "testdata/constellation/valid.yaml"
|
||||
|
||||
hook := test.NewGlobal()
|
||||
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-m", constellationPath)
|
||||
|
||||
entries := helper.GetAllLogs(hook.AllEntries())
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, entries, "Mapper: invalid schema")
|
||||
assert.EqualError(t, err, "Invalid configuration(s)")
|
||||
}
|
||||
|
||||
func TestValidateDeploymentFail(t *testing.T) {
|
||||
constellationPath := "testdata/constellation/valid.yaml"
|
||||
mapPath := "testdata/mapper/invalid.yaml"
|
||||
|
||||
hook := test.NewGlobal()
|
||||
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", constellationPath, "-m", mapPath)
|
||||
|
||||
entries := helper.GetAllLogs(hook.AllEntries())
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, entries, "Service `EventLogger` does not exist in map")
|
||||
assert.EqualError(t, err, "Invalid configuration(s)")
|
||||
}
|
||||
|
||||
func TestValidateMapperDuplicates(t *testing.T) {
|
||||
mapPath := "testdata/mapper/invalid.yaml"
|
||||
|
||||
hook := test.NewGlobal()
|
||||
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-m", mapPath)
|
||||
|
||||
entries := helper.GetAllLogs(hook.AllEntries())
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, entries, "Duplicate `ChartName` present in config")
|
||||
assert.Contains(t, entries, "Duplicate `Type` present in config")
|
||||
assert.Contains(t, entries, "Duplicate `Location` present in config")
|
||||
assert.Contains(t, entries, "Mapper: invalid")
|
||||
assert.EqualError(t, err, "Invalid configuration(s)")
|
||||
}
|
||||
|
||||
func TestValidateConstellationDuplicateIDs(t *testing.T) {
|
||||
constellationPath := "testdata/constellation/invalid.yaml"
|
||||
|
||||
hook := test.NewGlobal()
|
||||
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", constellationPath)
|
||||
|
||||
entries := helper.GetAllLogs(hook.AllEntries())
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, entries, "Duplicate `ID` present in config")
|
||||
assert.Contains(t, entries, "Constellation: invalid")
|
||||
assert.EqualError(t, err, "Invalid configuration(s)")
|
||||
}
|
||||
|
||||
func TestValidateConstellationMissingServices(t *testing.T) {
|
||||
constellationPath := "testdata/constellation/invalid.yaml"
|
||||
|
||||
hook := test.NewGlobal()
|
||||
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", constellationPath)
|
||||
|
||||
entries := helper.GetAllLogs(hook.AllEntries())
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, entries, "Relationship 'Event Hubs to Event Logger Link' has missing `Services`:")
|
||||
assert.Contains(t, entries, "Constellation: invalid")
|
||||
assert.EqualError(t, err, "Invalid configuration(s)")
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
logger "github.com/microsoft/abstrakt/internal/tools/logger"
|
||||
logger "github.com/microsoft/abstrakt/tools/logger"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
helper "github.com/microsoft/abstrakt/tools/test"
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing" // based on standard golang testing library https://golang.org/pkg/testing/
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestMain does setup or teardown (tests run when m.Run() is called)
|
||||
|
@ -15,21 +17,15 @@ func TestMain(m *testing.M) {
|
|||
func TestVersion(t *testing.T) {
|
||||
expected := "0.0.1"
|
||||
version := Version()
|
||||
|
||||
if version != expected {
|
||||
t.Errorf("Did not find correct abstrakt version. Expected %v, got %v", expected, version)
|
||||
}
|
||||
assert.Equal(t, expected, version)
|
||||
}
|
||||
|
||||
func TestVersionCmd(t *testing.T) {
|
||||
expected := "0.0.1"
|
||||
|
||||
hook := test.NewGlobal()
|
||||
_, err := executeCommand(newVersionCmd().cmd)
|
||||
_, err := helper.ExecuteCommand(newVersionCmd().cmd)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
checkStringContains(t, hook.LastEntry().Message, expected)
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, hook.LastEntry().Message, expected)
|
||||
}
|
||||
|
|
|
@ -6,12 +6,11 @@ package cmd
|
|||
// has to be run through a graphviz visualisation tool/utiliyy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/awalterschulze/gographviz"
|
||||
"github.com/microsoft/abstrakt/internal/dagconfigservice"
|
||||
"github.com/microsoft/abstrakt/internal/tools/logger"
|
||||
"github.com/microsoft/abstrakt/internal/platform/constellation"
|
||||
"github.com/microsoft/abstrakt/tools/logger"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -34,17 +33,19 @@ Example: abstrakt visualise -f [constellationFilePath]`,
|
|||
logger.Debug("args: " + strings.Join(args, " "))
|
||||
logger.Debug("constellationFilePath: " + cc.constellationFilePath)
|
||||
|
||||
if !fileExists(cc.constellationFilePath) {
|
||||
return fmt.Errorf("Could not open YAML input file for reading %v", cc.constellationFilePath)
|
||||
}
|
||||
|
||||
dsGraph := dagconfigservice.NewDagConfigService()
|
||||
err := dsGraph.LoadDagConfigFromFile(cc.constellationFilePath)
|
||||
dsGraph := new(constellation.Config)
|
||||
err := dsGraph.LoadFile(cc.constellationFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dagConfigService failed to load file %q: %s", cc.constellationFilePath, err)
|
||||
return fmt.Errorf("Constellation config failed to load file %q: %s", cc.constellationFilePath, err)
|
||||
}
|
||||
|
||||
resString := generateGraph(dsGraph)
|
||||
out := &bytes.Buffer{}
|
||||
resString, err := dsGraph.GenerateGraph(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.PrintBuffer(out, true)
|
||||
logger.Output(resString)
|
||||
|
||||
return nil
|
||||
|
@ -56,68 +57,3 @@ Example: abstrakt visualise -f [constellationFilePath]`,
|
|||
|
||||
return cc
|
||||
}
|
||||
|
||||
// fileExists - basic utility function to check the provided filename can be opened and is not a folder/directory
|
||||
func fileExists(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return !info.IsDir()
|
||||
}
|
||||
|
||||
// generateGraph - function to take a dagconfigService structure and create a graph object that contains the
|
||||
// representation of the graph. Also outputs a string representation (GraphViz dot notation) of the resulting graph
|
||||
// this can be passed on to GraphViz to graphically render the resulting graph
|
||||
func generateGraph(readGraph dagconfigservice.DagConfigService) string {
|
||||
|
||||
// Lookup is used to map IDs to names. Names are easier to visualise but IDs are more important to ensure the
|
||||
// presented constellation is correct and IDs are used to link nodes together
|
||||
lookup := make(map[string]string)
|
||||
|
||||
g := gographviz.NewGraph()
|
||||
|
||||
// Replace spaces with underscores, names with spaces can break graphviz engines
|
||||
if err := g.SetName(strings.Replace(readGraph.Name, " ", "_", -1)); err != nil {
|
||||
logger.Fatalf("error: %v", err)
|
||||
}
|
||||
if err := g.AddAttr(g.Name, "rankdir", "LR"); err != nil {
|
||||
logger.Fatalf("error adding attribute: %v", err)
|
||||
}
|
||||
|
||||
// Make the graph directed (a constellation is DAG)
|
||||
if err := g.SetDir(true); err != nil {
|
||||
logger.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
// Add all nodes to the graph storing the lookup from ID to name (for later adding relationships)
|
||||
// Replace spaces in names with underscores, names with spaces can break graphviz engines)
|
||||
for _, v := range readGraph.Services {
|
||||
logger.Debugf("Adding node %s", v.ID)
|
||||
newName := strings.Replace(v.ID, " ", "_", -1)
|
||||
|
||||
if strings.Compare(newName, v.ID) != 0 {
|
||||
logger.Debugf("Changing %s to %s", v.ID, newName)
|
||||
}
|
||||
lookup[v.ID] = newName
|
||||
err := g.AddNode(readGraph.Name, "\""+newName+"\"", nil)
|
||||
if err != nil {
|
||||
logger.Fatalf("error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Add relationships to the graph linking using the lookup IDs to name map
|
||||
// Replace spaces in names with underscores, names with spaces can break graphviz engines)
|
||||
for _, v := range readGraph.Relationships {
|
||||
logger.Debugf("Adding relationship from %s ---> %s", v.From, v.To)
|
||||
localFrom := "\"" + lookup[v.From] + "\""
|
||||
localTo := "\"" + lookup[v.To] + "\""
|
||||
err := g.AddEdge(localFrom, localTo, true, nil)
|
||||
if err != nil {
|
||||
logger.Fatalf("error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Produce resulting graph in dot notation format
|
||||
return g.String()
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/microsoft/abstrakt/internal/dagconfigservice"
|
||||
"github.com/microsoft/abstrakt/internal/platform/constellation"
|
||||
"github.com/microsoft/abstrakt/tools/file"
|
||||
helper "github.com/microsoft/abstrakt/tools/test"
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -11,57 +13,37 @@ import (
|
|||
)
|
||||
|
||||
func TestVisualiseCmdWithAllRequirementsNoError(t *testing.T) {
|
||||
constellationPath, _, _ := PrepareRealFilesForTest(t)
|
||||
constellationPath := "testdata/constellation/valid.yaml"
|
||||
|
||||
hook := test.NewGlobal()
|
||||
_, err := executeCommand(newVisualiseCmd().cmd, "-f", constellationPath)
|
||||
_, err := helper.ExecuteCommand(newVisualiseCmd().cmd, "-f", constellationPath)
|
||||
|
||||
if err != nil {
|
||||
t.Error("Did not receive output")
|
||||
} else {
|
||||
if !compareGraphOutputAsSets(validGraphString, hook.LastEntry().Message) {
|
||||
t.Errorf("Expcted output and produced output do not match : expected %s produced %s", validGraphString, hook.LastEntry().Message)
|
||||
}
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, helper.CompareGraphOutputAsSets(validGraphString, hook.LastEntry().Message))
|
||||
}
|
||||
|
||||
func TestVisualiseCmdFailYaml(t *testing.T) {
|
||||
expected := "Could not open YAML input file for reading"
|
||||
expected := "Constellation config failed to load file"
|
||||
|
||||
output, err := executeCommand(newVisualiseCmd().cmd, "-f", "constellationPath")
|
||||
_, err := helper.ExecuteCommand(newVisualiseCmd().cmd, "-f", "constellationPath")
|
||||
|
||||
if err != nil {
|
||||
checkStringContains(t, err.Error(), expected)
|
||||
} else {
|
||||
t.Errorf("Did not fail. Expected: %v \nGot: %v", expected, output)
|
||||
}
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), expected)
|
||||
}
|
||||
|
||||
func TestVisualiseCmdFailNotYaml(t *testing.T) {
|
||||
expected := "dagConfigService failed to load file"
|
||||
expected := "Constellation config failed to load file"
|
||||
|
||||
output, err := executeCommand(newVisualiseCmd().cmd, "-f", "visualise.go")
|
||||
_, err := helper.ExecuteCommand(newVisualiseCmd().cmd, "-f", "visualise.go")
|
||||
|
||||
if err != nil {
|
||||
checkStringContains(t, err.Error(), expected)
|
||||
} else {
|
||||
t.Errorf("Did not fail. Expected: %v \nGot: %v", expected, output)
|
||||
}
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), expected)
|
||||
}
|
||||
|
||||
func TestFileExists(t *testing.T) {
|
||||
_, _, tdir := helper.PrepareRealFilesForTest(t)
|
||||
|
||||
tdir, err := ioutil.TempDir("", "helm-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = os.RemoveAll(tdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
defer helper.CleanTempTestFiles(t, tdir)
|
||||
|
||||
//Setup variables and content for test
|
||||
testValidFilename := filepath.Join(tdir, "testVisualise.out")
|
||||
|
@ -69,115 +51,37 @@ func TestFileExists(t *testing.T) {
|
|||
testData := []byte("A file to test with")
|
||||
|
||||
//Create a file to test against
|
||||
err = ioutil.WriteFile(testValidFilename, testData, 0644)
|
||||
if err != nil {
|
||||
fmt.Println("Could not create output testing file, cannot proceed")
|
||||
t.Error(err)
|
||||
}
|
||||
err := ioutil.WriteFile(testValidFilename, testData, 0644)
|
||||
assert.NoError(t, err, "Could not create output testing file, cannot proceed")
|
||||
|
||||
//Test that a valid file (created above) can be seen
|
||||
var result bool = fileExists(testValidFilename) //Expecting true - file does exists
|
||||
if result == false {
|
||||
t.Errorf("Test file does exist but testFile returns that it does not")
|
||||
}
|
||||
var result bool = file.Exists(testValidFilename) //Expecting true - file does exists
|
||||
assert.True(t, result, "Test file does exist but testFile returns that it does not")
|
||||
|
||||
//Test that an invalid file (does not exist) is not seen
|
||||
result = fileExists(testInvalidFilename) //Expecting false - file does not exist
|
||||
if result != false {
|
||||
t.Errorf("Test file does not exist but testFile says it does")
|
||||
}
|
||||
result = file.Exists(testInvalidFilename) //Expecting false - file does not exist
|
||||
assert.False(t, result, "Test file does not exist but testFile says it does")
|
||||
|
||||
err = os.Remove(testValidFilename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
result = fileExists(testValidFilename) //Expecting false - file has been removed
|
||||
if result == true {
|
||||
t.Errorf("Test file has been removed but fileExists is finding it")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGenerateGraph(t *testing.T) {
|
||||
|
||||
retConfig := dagconfigservice.NewDagConfigService()
|
||||
err := retConfig.LoadDagConfigFromString(test01DagStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cmpString := test02ConstGraphString
|
||||
retString := generateGraph(retConfig)
|
||||
|
||||
if !compareGraphOutputAsSets(cmpString, retString) {
|
||||
t.Errorf("Input graph did not generate expected output graphviz representation")
|
||||
t.Errorf("Expected:\n%v \nGot:\n%v", cmpString, retString)
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
result = file.Exists(testValidFilename) //Expecting false - file has been removed
|
||||
assert.False(t, result, "Test file has been removed but fileExists is finding it")
|
||||
}
|
||||
|
||||
func TestParseYaml(t *testing.T) {
|
||||
retConfig := new(constellation.Config)
|
||||
err := retConfig.LoadString(testValidYAMLString)
|
||||
assert.NoError(t, err)
|
||||
|
||||
retConfig := dagconfigservice.NewDagConfigService()
|
||||
err := retConfig.LoadDagConfigFromString(testValidYAMLString)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if retConfig.Name != "Azure Event Hubs Sample" &&
|
||||
retConfig.ID != "d6e4a5e9-696a-4626-ba7a-534d6ff450a5" &&
|
||||
len(retConfig.Services) != 1 &&
|
||||
len(retConfig.Relationships) != 1 {
|
||||
t.Errorf("YAML did not parse correctly and it should have")
|
||||
}
|
||||
errMsg := "YAML did not parse correctly and it should have"
|
||||
|
||||
assert.Equalf(t, retConfig.Name, "Azure Event Hubs Sample", errMsg)
|
||||
assert.EqualValuesf(t, retConfig.ID, "211a55bd-5d92-446c-8be8-190f8f0e623e", errMsg)
|
||||
assert.Equalf(t, len(retConfig.Services), 1, errMsg)
|
||||
assert.Equalf(t, len(retConfig.Relationships), 1, errMsg)
|
||||
}
|
||||
|
||||
// Sample DAG file data
|
||||
const test01DagStr = `Name: "Azure Event Hubs Sample"
|
||||
Id: "d6e4a5e9-696a-4626-ba7a-534d6ff450a5"
|
||||
Services:
|
||||
- Id: "Event Generator"
|
||||
Type: "EventGenerator"
|
||||
Properties: {}
|
||||
- Id: "Azure Event Hub"
|
||||
Type: "EventHub"
|
||||
Properties: {}
|
||||
- Id: "Event Logger"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
- Id: "Event Logger"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
Relationships:
|
||||
- Id: "Generator to Event Hubs Link"
|
||||
Description: "Event Generator to Event Hub connection"
|
||||
From: "Event Generator"
|
||||
To: "Azure Event Hub"
|
||||
Properties: {}
|
||||
- Id: "Event Hubs to Event Logger Link"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "Azure Event Hub"
|
||||
To: "Event Logger"
|
||||
Properties: {}
|
||||
- Id: "Event Hubs to Event Logger Link Repeat"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "Azure Event Hub"
|
||||
To: "Event Logger"
|
||||
Properties: {}`
|
||||
|
||||
const test02ConstGraphString = `digraph Azure_Event_Hubs_Sample {
|
||||
rankdir=LR;
|
||||
"Event_Generator"->"Azure_Event_Hub";
|
||||
"Azure_Event_Hub"->"Event_Logger";
|
||||
"Azure_Event_Hub"->"Event_Logger";
|
||||
"Azure_Event_Hub";
|
||||
"Event_Generator";
|
||||
"Event_Logger";
|
||||
|
||||
}
|
||||
`
|
||||
const testValidYAMLString = `
|
||||
Description: "Event Generator to Event Hub connection"
|
||||
From: 9e1bcb3d-ff58-41d4-8779-f71e7b8800f8
|
||||
|
@ -196,13 +100,11 @@ Type: EventGenerator
|
|||
|
||||
const validGraphString = `digraph Azure_Event_Hubs_Sample {
|
||||
rankdir=LR;
|
||||
"9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"->"3aa1e546-1ed5-4d67-a59c-be0d5905b490";
|
||||
"3aa1e546-1ed5-4d67-a59c-be0d5905b490"->"1d0255d4-5b8c-4a52-b0bb-ac024cda37e5";
|
||||
"3aa1e546-1ed5-4d67-a59c-be0d5905b490"->"a268fae5-2a82-4a3e-ada7-a52eeb7019ac";
|
||||
"1d0255d4-5b8c-4a52-b0bb-ac024cda37e5";
|
||||
"3aa1e546-1ed5-4d67-a59c-be0d5905b490";
|
||||
"9e1bcb3d-ff58-41d4-8779-f71e7b8800f8";
|
||||
"a268fae5-2a82-4a3e-ada7-a52eeb7019ac";
|
||||
"Event_Generator"->"Azure_Event_Hub";
|
||||
"Azure_Event_Hub"->"Event_Logger";
|
||||
"Azure_Event_Hub";
|
||||
"Event_Generator";
|
||||
"Event_Logger";
|
||||
|
||||
}
|
||||
`
|
||||
|
|
|
@ -54,12 +54,12 @@ Can compose a Helm chart directory (default) or a __.tgz__ of the produced helm
|
|||
Create a Helm chart named `http-demo` to be generated under ./output.
|
||||
|
||||
```bash
|
||||
./abstrakt compose http-demo -f ./sample/constellation/http_constellation.yaml -m ./sample/constellation/http_constellation_maps.yaml -o ./output/http-demo
|
||||
./abstrakt compose http-demo -f ./examples/constellation/http_constellation.yaml -m ./examples/constellation/http_constellation_maps.yaml -o ./output/http-demo
|
||||
```
|
||||
|
||||
With __.tgz__
|
||||
```bash
|
||||
./abstrakt compose http-demo -f ./sample/constellation/http_constellation.yaml -m ./sample/constellation/http_constellation_maps.yaml -o ./output/http-demo -z
|
||||
./abstrakt compose http-demo -f ./examples/constellation/http_constellation.yaml -m ./examples/constellation/http_constellation_maps.yaml -o ./output/http-demo -z
|
||||
```
|
||||
|
||||
### abstrakt `validate`
|
||||
|
@ -129,7 +129,7 @@ Global Flags:
|
|||
|
||||
#### Examples
|
||||
|
||||
Run visualise on a file
|
||||
Run visualise on a file
|
||||
|
||||
abstrakt visualise -f basic_azure_event_hubs.yaml
|
||||
digraph Azure_Event_Hubs_Sample {
|
||||
|
@ -143,4 +143,4 @@ Run visualise on a file
|
|||
|
||||
Pipe visualise output to Graphviz producing a file called result.png (assumes Graphviz is installed and can be called from the location abstrakt is being run)
|
||||
|
||||
abstrakt visualise -f ./sample/constellation/sample_consteallation.yaml | dot -Tpng > result.png
|
||||
abstrakt visualise -f ./examples/constellation/sample_consteallation.yaml | dot -Tpng > result.png
|
|
@ -3,9 +3,9 @@ Id: "a5a7c413-a020-44a2-bd23-1941adb7ad58"
|
|||
Maps:
|
||||
- ChartName: "sender"
|
||||
Type: "WormholeSender"
|
||||
Location: "file://../../../sample/helm/http_sample/sender"
|
||||
Location: "file://../../../examples/helm/http_sample/sender"
|
||||
Version: "0.0.1"
|
||||
- ChartName: "receiver"
|
||||
Type: "WormholeReceiver"
|
||||
Location: "file://../../../sample/helm/http_sample/receiver"
|
||||
Location: "file://../../../examples/helm/http_sample/receiver"
|
||||
Version: "0.0.1"
|
|
@ -0,0 +1,16 @@
|
|||
Name: "Basic Azure Event Hubs maps"
|
||||
Id: "a5a7c413-a020-44a2-bd23-1941adb7ad58"
|
||||
Maps:
|
||||
- ChartName: "event_hub_sample_event_generator"
|
||||
Type: "EventGenerator"
|
||||
Location: "file://../../../examples/deps/event_hub_sample_event_generator"
|
||||
Version: "1.0.0"
|
||||
- ChartName: "event_hub_sample_event_logger"
|
||||
Type: "EventLogger"
|
||||
Location: "file://../../../examples/deps/event_hub_sample_event_logger"
|
||||
Version: "1.0.0"
|
||||
- ChartName: "event_hub_sample_event_hub"
|
||||
Type: "EventHub"
|
||||
Location: "file://../../../examples/deps/event_hub_sample_event_hub"
|
||||
Version: "1.0.0"
|
||||
|
|
@ -2,17 +2,17 @@ apiVersion: v2
|
|||
appVersion: 1.16.0
|
||||
dependencies:
|
||||
- name: event_hub_sample_event_generator
|
||||
repository: "file://../../../../sample/deps/event_hub_sample_event_generator"
|
||||
repository: "file://../../../../examples/deps/event_hub_sample_event_generator"
|
||||
version: 1.0.0
|
||||
- name: event_hub_sample_event_hub
|
||||
repository: "file://../../../../sample/deps/event_hub_sample_event_hub"
|
||||
repository: "file://../../../../examples/deps/event_hub_sample_event_hub"
|
||||
version: 1.0.0
|
||||
- name: event_hub_sample_event_logger
|
||||
repository: "file://../../../../sample/deps/event_hub_sample_event_logger"
|
||||
repository: "file://../../../../examples/deps/event_hub_sample_event_logger"
|
||||
version: 1.0.0
|
||||
- alias: event_hub_sample_event_logger1
|
||||
name: event_hub_sample_event_logger
|
||||
repository: "file://../../../../sample/deps/event_hub_sample_event_logger"
|
||||
repository: "file://../../../../examples/deps/event_hub_sample_event_logger"
|
||||
version: 1.0.0
|
||||
description: A Helm chart for Kubernetes
|
||||
name: test
|
1
go.mod
1
go.mod
|
@ -19,6 +19,7 @@ require (
|
|||
github.com/spf13/cobra v0.0.5
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/viper v1.4.0
|
||||
github.com/stretchr/testify v1.4.0
|
||||
golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152
|
||||
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
package buildmapservice
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// BuildMapService: Process map files relating services
|
||||
// to helm chart files. For example, see accompanying
|
||||
// test file.
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/microsoft/abstrakt/internal/tools/guid"
|
||||
yamlParser "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Note: the yaml mapping attributes are necessary (despite the nearly
|
||||
// uniform 1-1 name correspondence). The yaml parser would otherwise
|
||||
// expect the names in the YAML file to be all lower-case.
|
||||
// e.g. ChartName would only work if "chartname" was used in the yaml file.
|
||||
|
||||
// BuildMapInfo -- info about an individual component
|
||||
type BuildMapInfo struct {
|
||||
ChartName string `yaml:"ChartName"`
|
||||
Type string `yaml:"Type"`
|
||||
Location string `yaml:"Location"`
|
||||
Version string `yaml:"Version"`
|
||||
}
|
||||
|
||||
// BuildMapService -- data from the entire build map.
|
||||
type BuildMapService struct {
|
||||
Name string `yaml:"Name"`
|
||||
ID guid.GUID `yaml:"Id"`
|
||||
Maps []BuildMapInfo `yaml:"Maps"`
|
||||
}
|
||||
|
||||
// FindByName -- Look up a map by chart name.
|
||||
func (m *BuildMapService) FindByName(chartName string) (res *BuildMapInfo) {
|
||||
for _, wmi := range m.Maps {
|
||||
// try first for an exact match
|
||||
if chartName == wmi.ChartName {
|
||||
return &wmi
|
||||
}
|
||||
// if we want to tolerate case being incorrect (e.g., ABC vs. abc),
|
||||
if guid.TolerateMiscasedKey && strings.EqualFold(string(wmi.ChartName), chartName) {
|
||||
return &wmi
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindByType -- Look up a map by the "Type" value.
|
||||
func (m *BuildMapService) FindByType(typeName string) (res *BuildMapInfo) {
|
||||
for _, wmi := range m.Maps {
|
||||
// try first for an exact match
|
||||
if typeName == wmi.Type {
|
||||
return &wmi
|
||||
}
|
||||
// if we want to tolerate case being incorrect (e.g., ABC vs. abc),
|
||||
if guid.TolerateMiscasedKey && strings.EqualFold(string(wmi.Type), typeName) {
|
||||
return &wmi
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadMapFromFile -- New Map info instance from the named file.
|
||||
func (m *BuildMapService) LoadMapFromFile(fileName string) (err error) {
|
||||
err = nil
|
||||
contentBytes, err := ioutil.ReadFile(fileName)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
err = m.LoadMapFromString(string(contentBytes))
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadMapFromString -- New Map info instance from the given yaml string.
|
||||
func (m *BuildMapService) LoadMapFromString(yamlString string) (err error) {
|
||||
err = nil
|
||||
|
||||
err = yamlParser.Unmarshal([]byte(yamlString), m)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
//NewBuildMapService returns a new instance of NewBuildMapService
|
||||
func NewBuildMapService() BuildMapService {
|
||||
return BuildMapService{}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
package buildmapservice
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/abstrakt/internal/tools/guid"
|
||||
)
|
||||
|
||||
func TestMapFromString(t *testing.T) {
|
||||
type args struct {
|
||||
yamlString string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantRet *BuildMapService
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Test.01",
|
||||
args: args{yamlString: configMapTest01String},
|
||||
wantRet: &buildMap01,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mapper := &BuildMapService{}
|
||||
err := mapper.LoadMapFromString(tt.args.yamlString)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("LoadMapFromString() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(mapper, tt.wantRet) {
|
||||
t.Errorf("LoadMapFromString() = %v, want %v", mapper, tt.wantRet)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const configMapTest01String = `
|
||||
Name: "Basic Azure Event Hubs maps"
|
||||
Id: "a5a7c413-a020-44a2-bd23-1941adb7ad58"
|
||||
Maps:
|
||||
- ChartName: "event_hub_sample_event_generator"
|
||||
Type: "EventGenerator"
|
||||
Location: "../../helm/basictest"
|
||||
Version: "1.0.0"
|
||||
- ChartName: "event_hub_sample_event_logger"
|
||||
Type: "EventLogger"
|
||||
Location: "../../helm/basictest"
|
||||
Version: "1.0.0"
|
||||
- ChartName: "event_hub_sample_event_hub"
|
||||
Type: "EventHub"
|
||||
Location: "../../helm/basictest"
|
||||
Version: "1.0.0"
|
||||
`
|
||||
|
||||
var buildMap01 = BuildMapService{
|
||||
Name: "Basic Azure Event Hubs maps",
|
||||
ID: guid.GUID("a5a7c413-a020-44a2-bd23-1941adb7ad58"),
|
||||
Maps: []BuildMapInfo{
|
||||
{
|
||||
ChartName: "event_hub_sample_event_generator",
|
||||
Type: "EventGenerator",
|
||||
Location: "../../helm/basictest",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
{
|
||||
ChartName: "event_hub_sample_event_logger",
|
||||
Type: "EventLogger",
|
||||
Location: "../../helm/basictest",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
{
|
||||
ChartName: "event_hub_sample_event_hub",
|
||||
Type: "EventHub",
|
||||
Location: "../../helm/basictest",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,29 +1,26 @@
|
|||
package composeservice
|
||||
package compose
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/microsoft/abstrakt/internal/buildmapservice"
|
||||
"github.com/microsoft/abstrakt/internal/chartservice"
|
||||
"github.com/microsoft/abstrakt/internal/dagconfigservice"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"github.com/microsoft/abstrakt/internal/platform/chart"
|
||||
"github.com/microsoft/abstrakt/internal/platform/constellation"
|
||||
"github.com/microsoft/abstrakt/internal/platform/mapper"
|
||||
helm "helm.sh/helm/v3/pkg/chart"
|
||||
)
|
||||
|
||||
//ComposeService takes maps and configs and builds out the helm chart
|
||||
type ComposeService struct {
|
||||
DagConfigService dagconfigservice.DagConfigService
|
||||
BuildMapService buildmapservice.BuildMapService
|
||||
//Composer takes maps and configs and builds out the helm chart
|
||||
type Composer struct {
|
||||
Constellation constellation.Config
|
||||
Mapper mapper.Config
|
||||
}
|
||||
|
||||
//Compose takes the loaded DAG and maps and builds the Helm values and requirements documents
|
||||
func (m *ComposeService) Compose(name string, dir string) (*chart.Chart, error) {
|
||||
if m.DagConfigService.Name == "" || m.BuildMapService.Name == "" {
|
||||
return nil, errors.New("Please initialise with LoadFromFile or LoadFromString")
|
||||
//Build takes the loaded DAG and maps and builds the Helm values and requirements documents
|
||||
func (c *Composer) Build(name string, dir string) (*helm.Chart, error) {
|
||||
if c.Constellation.Name == "" || c.Mapper.Name == "" {
|
||||
return nil, fmt.Errorf("Please initialise with LoadFromFile or LoadFromString")
|
||||
}
|
||||
|
||||
newChart, err := chartservice.CreateChart(name, dir)
|
||||
newChart, err := chart.Create(name, dir)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -31,12 +28,12 @@ func (m *ComposeService) Compose(name string, dir string) (*chart.Chart, error)
|
|||
|
||||
serviceMap := make(map[string]int)
|
||||
aliasMap := make(map[string]string)
|
||||
deps := make([]*chart.Dependency, 0)
|
||||
deps := make([]*helm.Dependency, 0)
|
||||
|
||||
values := newChart.Values
|
||||
|
||||
for _, n := range m.DagConfigService.Services {
|
||||
service := m.BuildMapService.FindByType(n.Type)
|
||||
for _, n := range c.Constellation.Services {
|
||||
service := c.Mapper.FindByType(n.Type)
|
||||
if service == nil {
|
||||
return nil, fmt.Errorf("Could not find service %v", service)
|
||||
}
|
||||
|
@ -49,7 +46,7 @@ func (m *ComposeService) Compose(name string, dir string) (*chart.Chart, error)
|
|||
|
||||
serviceMap[service.Type]++
|
||||
|
||||
dep := &chart.Dependency{
|
||||
dep := &helm.Dependency{
|
||||
Name: service.ChartName, Version: service.Version, Repository: service.Location,
|
||||
}
|
||||
|
||||
|
@ -69,15 +66,15 @@ func (m *ComposeService) Compose(name string, dir string) (*chart.Chart, error)
|
|||
|
||||
relationships := make(map[string][]interface{})
|
||||
valMap["relationships"] = &relationships
|
||||
toRels := m.DagConfigService.FindRelationshipByToName(n.ID)
|
||||
fromRels := m.DagConfigService.FindRelationshipByFromName(n.ID)
|
||||
toRels := c.Constellation.FindRelationshipByToName(n.ID)
|
||||
fromRels := c.Constellation.FindRelationshipByFromName(n.ID)
|
||||
|
||||
for _, i := range toRels {
|
||||
toRelations := make(map[string]string)
|
||||
relationships["input"] = append(relationships["input"], &toRelations)
|
||||
|
||||
//find the target service
|
||||
foundService := m.DagConfigService.FindService(i.From)
|
||||
foundService := c.Constellation.FindService(i.From)
|
||||
|
||||
if foundService == nil {
|
||||
return nil, fmt.Errorf("Service '%v' referenced in relationship '%v' not found", i.From, i.ID)
|
||||
|
@ -99,7 +96,7 @@ func (m *ComposeService) Compose(name string, dir string) (*chart.Chart, error)
|
|||
relationships["output"] = append(relationships["output"], &fromRelations)
|
||||
|
||||
//find the target service
|
||||
foundService := m.DagConfigService.FindService(i.To)
|
||||
foundService := c.Constellation.FindService(i.To)
|
||||
|
||||
if foundService == nil {
|
||||
return nil, fmt.Errorf("Service '%v' referenced in relationship '%v' not found", i.To, i.ID)
|
||||
|
@ -121,36 +118,13 @@ func (m *ComposeService) Compose(name string, dir string) (*chart.Chart, error)
|
|||
newChart.Metadata.Dependencies = deps
|
||||
|
||||
return newChart, nil
|
||||
|
||||
}
|
||||
|
||||
//LoadFromFile takes a string dag and map and loads them
|
||||
func (m *ComposeService) LoadFromFile(dagFile string, mapFile string) (err error) {
|
||||
err = m.DagConfigService.LoadDagConfigFromFile(dagFile)
|
||||
//LoadFile takes a string dag and map and loads them
|
||||
func (c *Composer) LoadFile(dagFile string, mapFile string) (err error) {
|
||||
err = c.Constellation.LoadFile(dagFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = m.BuildMapService.LoadMapFromFile(mapFile)
|
||||
return
|
||||
}
|
||||
|
||||
//LoadFromString takes a string dag and map and loads them
|
||||
func (m *ComposeService) LoadFromString(dagString string, mapString string) (err error) {
|
||||
err = m.DagConfigService.LoadDagConfigFromString(dagString)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = m.BuildMapService.LoadMapFromString(mapString)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//NewComposeService constructs a new compose service
|
||||
func NewComposeService() ComposeService {
|
||||
s := ComposeService{}
|
||||
s.DagConfigService = dagconfigservice.NewDagConfigService()
|
||||
s.BuildMapService = buildmapservice.NewBuildMapService()
|
||||
return s
|
||||
return c.Mapper.LoadFile(mapFile)
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package compose_test
|
||||
|
||||
import (
|
||||
"github.com/microsoft/abstrakt/internal/compose"
|
||||
helper "github.com/microsoft/abstrakt/tools/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestComposeService(t *testing.T) {
|
||||
_, _, tdir := helper.PrepareRealFilesForTest(t)
|
||||
|
||||
defer helper.CleanTempTestFiles(t, tdir)
|
||||
|
||||
comp := new(compose.Composer)
|
||||
_, err := comp.Build("test", tdir)
|
||||
|
||||
assert.Error(t, err, "Compose should fail if not yet loaded")
|
||||
|
||||
dag := "testdata/constellation.yaml"
|
||||
mapper := "testdata/mapper.yaml"
|
||||
|
||||
_ = comp.LoadFile(dag, mapper)
|
||||
|
||||
h, err := comp.Build("test", tdir)
|
||||
|
||||
assert.NoError(t, err, "Compose should have loaded")
|
||||
|
||||
_ = chartutil.SaveDir(h, tdir)
|
||||
h, _ = loader.LoadDir(tdir)
|
||||
|
||||
contentBytes, err := ioutil.ReadFile("testdata/values.yaml")
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, raw := range h.Raw {
|
||||
if raw.Name == "test/values.yaml" {
|
||||
assert.Equal(t, string(contentBytes), string(raw.Data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelmLibCompose(t *testing.T) {
|
||||
_, _, tdir := helper.PrepareRealFilesForTest(t)
|
||||
|
||||
defer helper.CleanTempTestFiles(t, tdir)
|
||||
|
||||
c, err := chartutil.Create("foo", tdir)
|
||||
if err != nil {
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
|
||||
dir := filepath.Join(tdir, "foo")
|
||||
|
||||
mychart, err := loader.LoadDir(c)
|
||||
if err != nil {
|
||||
assert.FailNowf(t, "Failed to load newly created chart %q: %s", c, err)
|
||||
}
|
||||
|
||||
assert.Equalf(t, "foo", mychart.Name(), "Expected name to be 'foo', got %q", mychart.Name())
|
||||
|
||||
for _, f := range []string{
|
||||
chartutil.ChartfileName,
|
||||
chartutil.DeploymentName,
|
||||
chartutil.HelpersName,
|
||||
chartutil.IgnorefileName,
|
||||
chartutil.NotesName,
|
||||
chartutil.ServiceAccountName,
|
||||
chartutil.ServiceName,
|
||||
chartutil.TemplatesDir,
|
||||
chartutil.TemplatesTestsDir,
|
||||
chartutil.TestConnectionName,
|
||||
chartutil.ValuesfileName,
|
||||
} {
|
||||
_, err := os.Stat(filepath.Join(dir, f))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
mychart.Values["Jordan"] = "testing123"
|
||||
|
||||
deps := []*chart.Dependency{
|
||||
{Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"},
|
||||
{Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"},
|
||||
}
|
||||
|
||||
t.Logf("Directory: %v", tdir)
|
||||
|
||||
mychart.Metadata.Dependencies = deps
|
||||
|
||||
_ = chartutil.SaveDir(mychart, filepath.Join(tdir, "anotheretst"))
|
||||
|
||||
}
|
||||
|
||||
func TestLoadFromString(t *testing.T) {
|
||||
comp := new(compose.Composer)
|
||||
|
||||
dag := "testdata/constellation.yaml"
|
||||
mapper := "testdata/mapper.yaml"
|
||||
|
||||
err := comp.LoadFile(dag, mapper)
|
||||
assert.NoErrorf(t, err, "Error: %v", err)
|
||||
|
||||
err = comp.LoadFile("sfdsd", mapper)
|
||||
assert.Error(t, err, "Didn't get error when should")
|
||||
|
||||
err = comp.LoadFile(dag, "sdfsdf")
|
||||
assert.Error(t, err, "Didn't get error when should")
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
Name: "Azure Event Hubs Sample"
|
||||
Id: "d6e4a5e9-696a-4626-ba7a-534d6ff450a5"
|
||||
Services:
|
||||
- Name: "Event Generator"
|
||||
Id: "9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"
|
||||
Type: "EventGenerator"
|
||||
Properties: {}
|
||||
- Name: "Azure Event Hub"
|
||||
Id: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
Type: "EventHub"
|
||||
Properties: {}
|
||||
- Name: "Event Logger"
|
||||
Id: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
- Name: "Event Logger"
|
||||
Id: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
Relationships:
|
||||
- Name: "Generator to Event Hubs Link"
|
||||
Id: "211a55bd-5d92-446c-8be8-190f8f0e623e"
|
||||
Description: "Event Generator to Event Hub connection"
|
||||
From: "9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"
|
||||
To: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
Properties: {}
|
||||
- Name: "Event Hubs to Event Logger Link"
|
||||
Id: "08ccbd67-456f-4349-854a-4e6959e5017b"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
To: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
|
||||
Properties: {}
|
||||
- Name: "Event Hubs to Event Logger Link Repeat"
|
||||
Id: "c8a719e0-164d-408f-9ed1-06e08dc5abbe"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
To: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Properties: {}
|
|
@ -0,0 +1,15 @@
|
|||
Name: "Basic Azure Event Hubs maps"
|
||||
Id: "a5a7c413-a020-44a2-bd23-1941adb7ad58"
|
||||
Maps:
|
||||
- ChartName: "event_hub_sample_event_generator"
|
||||
Type: "EventGenerator"
|
||||
Location: "../../helm/basictest"
|
||||
Version: "1.0.0"
|
||||
- ChartName: "event_hub_sample_event_logger"
|
||||
Type: "EventLogger"
|
||||
Location: "../../helm/basictest"
|
||||
Version: "1.0.0"
|
||||
- ChartName: "event_hub_sample_event_hub"
|
||||
Type: "EventHub"
|
||||
Location: "../../helm/basictest"
|
||||
Version: "1.0.0"
|
|
@ -0,0 +1,39 @@
|
|||
event_hub_sample_event_generator:
|
||||
name: event_hub_sample_event_generator
|
||||
relationships:
|
||||
output:
|
||||
- name: event_hub_sample_event_hub
|
||||
service: 211a55bd-5d92-446c-8be8-190f8f0e623e
|
||||
type: EventHub
|
||||
type: EventGenerator
|
||||
event_hub_sample_event_hub:
|
||||
name: event_hub_sample_event_hub
|
||||
relationships:
|
||||
input:
|
||||
- name: event_hub_sample_event_generator
|
||||
service: 211a55bd-5d92-446c-8be8-190f8f0e623e
|
||||
type: EventGenerator
|
||||
output:
|
||||
- name: event_hub_sample_event_logger1
|
||||
service: 08ccbd67-456f-4349-854a-4e6959e5017b
|
||||
type: EventLogger
|
||||
- name: event_hub_sample_event_logger
|
||||
service: c8a719e0-164d-408f-9ed1-06e08dc5abbe
|
||||
type: EventLogger
|
||||
type: EventHub
|
||||
event_hub_sample_event_logger:
|
||||
name: event_hub_sample_event_logger
|
||||
relationships:
|
||||
input:
|
||||
- name: event_hub_sample_event_hub
|
||||
service: c8a719e0-164d-408f-9ed1-06e08dc5abbe
|
||||
type: EventHub
|
||||
type: EventLogger
|
||||
event_hub_sample_event_logger1:
|
||||
name: event_hub_sample_event_logger1
|
||||
relationships:
|
||||
input:
|
||||
- name: event_hub_sample_event_hub
|
||||
service: 08ccbd67-456f-4349-854a-4e6959e5017b
|
||||
type: EventHub
|
||||
type: EventLogger
|
|
@ -1,193 +0,0 @@
|
|||
package composeservice
|
||||
|
||||
//"helm.sh/helm/v3/pkg/chart"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestComposeService(t *testing.T) {
|
||||
|
||||
tdir, err := ioutil.TempDir("", "helm-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = os.RemoveAll(tdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
comp := NewComposeService()
|
||||
_, err = comp.Compose("test", tdir)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Compose should fail if not yet loaded")
|
||||
}
|
||||
|
||||
_ = comp.LoadFromString(test01DagStr, configMapTest01String)
|
||||
|
||||
h, err := comp.Compose("test", tdir)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Compose should have loaded")
|
||||
}
|
||||
|
||||
_ = chartutil.SaveDir(h, tdir)
|
||||
h, _ = loader.LoadDir(tdir)
|
||||
for _, raw := range h.Raw {
|
||||
if raw.Name == "test/values.yaml" {
|
||||
fmt.Print(string(raw.Data))
|
||||
}
|
||||
}
|
||||
|
||||
//chartYaml = ioutil.ReadFile(filepath.Join(tdir, "test", "Chart.yaml"))
|
||||
|
||||
}
|
||||
|
||||
func TestHelmLibCompose(t *testing.T) {
|
||||
|
||||
tdir, err := ioutil.TempDir("", "helm-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err = os.RemoveAll(tdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := chartutil.Create("foo", tdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dir := filepath.Join(tdir, "foo")
|
||||
|
||||
mychart, err := loader.LoadDir(c)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load newly created chart %q: %s", c, err)
|
||||
}
|
||||
|
||||
if mychart.Name() != "foo" {
|
||||
t.Errorf("Expected name to be 'foo', got %q", mychart.Name())
|
||||
}
|
||||
|
||||
for _, f := range []string{
|
||||
chartutil.ChartfileName,
|
||||
chartutil.DeploymentName,
|
||||
chartutil.HelpersName,
|
||||
chartutil.IgnorefileName,
|
||||
chartutil.NotesName,
|
||||
chartutil.ServiceAccountName,
|
||||
chartutil.ServiceName,
|
||||
chartutil.TemplatesDir,
|
||||
chartutil.TemplatesTestsDir,
|
||||
chartutil.TestConnectionName,
|
||||
chartutil.ValuesfileName,
|
||||
} {
|
||||
if _, err := os.Stat(filepath.Join(dir, f)); err != nil {
|
||||
t.Errorf("Expected %s file: %s", f, err)
|
||||
}
|
||||
}
|
||||
|
||||
mychart.Values["Jordan"] = "testing123"
|
||||
|
||||
deps := []*chart.Dependency{
|
||||
{Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"},
|
||||
{Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"},
|
||||
}
|
||||
|
||||
t.Logf("Directory: %v", tdir)
|
||||
|
||||
mychart.Metadata.Dependencies = deps
|
||||
|
||||
_ = chartutil.SaveDir(mychart, filepath.Join(tdir, "anotheretst"))
|
||||
|
||||
}
|
||||
|
||||
func TestLoadFromString(t *testing.T) {
|
||||
comp := NewComposeService()
|
||||
err := comp.LoadFromString(test01DagStr, configMapTest01String)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Error: %v", err)
|
||||
}
|
||||
|
||||
err = comp.LoadFromString("sfdsd", configMapTest01String)
|
||||
if err == nil {
|
||||
t.Errorf("Didn't get error when should")
|
||||
}
|
||||
|
||||
err = comp.LoadFromString(test01DagStr, "sdfsdf")
|
||||
if err == nil {
|
||||
t.Errorf("Didn't get error when should")
|
||||
}
|
||||
}
|
||||
|
||||
const test01DagStr = `Name: "Azure Event Hubs Sample"
|
||||
Id: "d6e4a5e9-696a-4626-ba7a-534d6ff450a5"
|
||||
Services:
|
||||
- Name: "Event Generator"
|
||||
Id: "9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"
|
||||
Type: "EventGenerator"
|
||||
Properties: {}
|
||||
- Name: "Azure Event Hub"
|
||||
Id: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
Type: "EventHub"
|
||||
Properties: {}
|
||||
- Name: "Event Logger"
|
||||
Id: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
- Name: "Event Logger"
|
||||
Id: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
Relationships:
|
||||
- Name: "Generator to Event Hubs Link"
|
||||
Id: "211a55bd-5d92-446c-8be8-190f8f0e623e"
|
||||
Description: "Event Generator to Event Hub connection"
|
||||
From: "9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"
|
||||
To: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
Properties: {}
|
||||
- Name: "Event Hubs to Event Logger Link"
|
||||
Id: "08ccbd67-456f-4349-854a-4e6959e5017b"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
To: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
|
||||
Properties: {}
|
||||
- Name: "Event Hubs to Event Logger Link Repeat"
|
||||
Id: "c8a719e0-164d-408f-9ed1-06e08dc5abbe"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
To: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Properties: {}
|
||||
`
|
||||
|
||||
const configMapTest01String = `
|
||||
Name: "Basic Azure Event Hubs maps"
|
||||
Id: "a5a7c413-a020-44a2-bd23-1941adb7ad58"
|
||||
Maps:
|
||||
- ChartName: "event_hub_sample_event_generator"
|
||||
Type: "EventGenerator"
|
||||
Location: "../../helm/basictest"
|
||||
Version: "1.0.0"
|
||||
- ChartName: "event_hub_sample_event_logger"
|
||||
Type: "EventLogger"
|
||||
Location: "../../helm/basictest"
|
||||
Version: "1.0.0"
|
||||
- ChartName: "event_hub_sample_event_hub"
|
||||
Type: "EventHub"
|
||||
Location: "../../helm/basictest"
|
||||
Version: "1.0.0"
|
||||
`
|
|
@ -1,133 +0,0 @@
|
|||
package dagconfigservice
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// DagConfig class - information for a deployment regarding
|
||||
// the Services and the Relationships between them.
|
||||
//
|
||||
// Usual starting point would be to construct a DatConfig
|
||||
// instance from the corresponding yaml using either:
|
||||
// dcPointer := NewDagConfigFromFile(<filename>)
|
||||
// or
|
||||
// dcPointer := NewDagConfigFromString(<yamlTextString>)
|
||||
//
|
||||
// Parsing failures are indicated by a nil return.
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
import (
|
||||
"github.com/microsoft/abstrakt/internal/tools/guid"
|
||||
yamlParser "gopkg.in/yaml.v2"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Note: the yaml mappings are necessary (despite the 1-1 name correspondence).
|
||||
// The yaml parser would otherwise expect the names in the YAML file to be all
|
||||
// lower-case. e.g. ChartName would only work if "chartname" was used in the
|
||||
// yaml file.
|
||||
|
||||
// DagProperty - an individual property in the DAG.
|
||||
// For now, these are just interfaces as the value types are not firmed up
|
||||
// for individual properties. As the entire set of properties becomes
|
||||
// known, each should be promoted out of the Properties collection to
|
||||
// the main struct -- handling presence/absence via using pointer members,
|
||||
// so as to allow for nil value == absence.
|
||||
type DagProperty interface{}
|
||||
|
||||
// DagService -- a DAG Service description
|
||||
type DagService struct {
|
||||
ID string `yaml:"Id" validate:"empty=false"`
|
||||
Type string `yaml:"Type" validate:"empty=false"`
|
||||
Properties map[string]DagProperty `yaml:"Properties"`
|
||||
}
|
||||
|
||||
// DagRelationship -- a relationship between Services
|
||||
type DagRelationship struct {
|
||||
ID string `yaml:"Id" validate:"empty=false"`
|
||||
Description string `yaml:"Description"`
|
||||
From string `yaml:"From" validate:"empty=false"`
|
||||
To string `yaml:"To" validate:"empty=false"`
|
||||
Properties map[string]DagProperty `yaml:"Properties"`
|
||||
}
|
||||
|
||||
// DagConfigService -- The DAG config for a deployment
|
||||
type DagConfigService struct {
|
||||
Name string `yaml:"Name" validate:"empty=false"`
|
||||
ID guid.GUID `yaml:"Id" validate:"empty=false"`
|
||||
Services []DagService `yaml:"Services" validate:"empty=false"`
|
||||
Relationships []DagRelationship `yaml:"Relationships"`
|
||||
}
|
||||
|
||||
// NewDagConfigService -- Create a new DagConfigService instance
|
||||
func NewDagConfigService() DagConfigService {
|
||||
return DagConfigService{}
|
||||
}
|
||||
|
||||
// FindService -- Find a Service by id.
|
||||
func (m *DagConfigService) FindService(serviceID string) (res *DagService) {
|
||||
for _, val := range m.Services {
|
||||
// try first for an exact match
|
||||
if val.ID == serviceID {
|
||||
return &val
|
||||
}
|
||||
// if we want to tolerate case being incorrect (e.g., ABC vs. abc) ...
|
||||
if guid.TolerateMiscasedKey && strings.EqualFold(val.ID, serviceID) {
|
||||
return &val
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindRelationship -- Find a Relationship by id.
|
||||
func (m *DagConfigService) FindRelationship(relationshipID string) (res *DagRelationship) {
|
||||
for _, val := range m.Relationships {
|
||||
// try first for an exact match
|
||||
if val.ID == relationshipID {
|
||||
return &val
|
||||
} else if guid.TolerateMiscasedKey && strings.EqualFold(val.ID, relationshipID) {
|
||||
return &val
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindRelationshipByToName -- Find a Relationship by the name that is the target of the rel.
|
||||
func (m *DagConfigService) FindRelationshipByToName(relationshipToName string) (res []DagRelationship) {
|
||||
for _, val := range m.Relationships {
|
||||
// try first for an exact match
|
||||
if val.To == relationshipToName {
|
||||
res = append(res, val)
|
||||
} else if guid.TolerateMiscasedKey && strings.EqualFold(string(val.To), relationshipToName) {
|
||||
res = append(res, val)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindRelationshipByFromName -- Find a Relationship by the name that is the source of the rel.
|
||||
func (m *DagConfigService) FindRelationshipByFromName(relationshipFromName string) (res []DagRelationship) {
|
||||
for _, val := range m.Relationships {
|
||||
// try first for an exact match
|
||||
if val.From == relationshipFromName {
|
||||
res = append(res, val)
|
||||
} else if guid.TolerateMiscasedKey && strings.EqualFold(string(val.From), relationshipFromName) {
|
||||
res = append(res, val)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// LoadDagConfigFromFile -- New DAG info instance from the named file.
|
||||
func (m *DagConfigService) LoadDagConfigFromFile(fileName string) (err error) {
|
||||
err = nil
|
||||
contentBytes, err := ioutil.ReadFile(fileName)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
err = m.LoadDagConfigFromString(string(contentBytes))
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadDagConfigFromString -- New DAG info instance from the given yaml string.
|
||||
func (m *DagConfigService) LoadDagConfigFromString(yamlString string) (err error) {
|
||||
return yamlParser.Unmarshal([]byte(yamlString), m)
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
package dagconfigservice
|
||||
|
||||
import (
|
||||
"github.com/microsoft/abstrakt/internal/tools/guid"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRelationshipFinding(t *testing.T) {
|
||||
dag := &DagConfigService{}
|
||||
_ = dag.LoadDagConfigFromString(test01DagStr)
|
||||
rel1 := dag.FindRelationshipByFromName("Event Generator")
|
||||
rel2 := dag.FindRelationshipByToName("Azure Event Hub")
|
||||
|
||||
if rel1[0].From != rel2[0].From || rel1[0].To != rel2[0].To {
|
||||
t.Error("Relationships were not correctly resolved")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNewDagConfigFromString(t *testing.T) {
|
||||
type targs struct {
|
||||
yamlString string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args targs
|
||||
wantRet *DagConfigService
|
||||
wantErr bool
|
||||
}{
|
||||
{ // TEST START
|
||||
name: "Test.01",
|
||||
args: targs{yamlString: test01DagStr},
|
||||
wantRet: &test01WantDag,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
dag := &DagConfigService{}
|
||||
err := dag.LoadDagConfigFromString(tt.args.yamlString)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("LoadDagConfigFromString() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(dag, tt.wantRet) {
|
||||
t.Errorf("LoadDagConfigFromString() =\n%#v,\nWant:\n%#v\n", dag, tt.wantRet)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleInstanceInRelationships(t *testing.T) {
|
||||
testDag := test01DagStr
|
||||
testDag += `- Id: "Event Generator to Event Logger Link"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "Event Generator"
|
||||
To: "Event Logger"
|
||||
Properties: {}`
|
||||
|
||||
dag := &DagConfigService{}
|
||||
_ = dag.LoadDagConfigFromString(testDag)
|
||||
|
||||
from := dag.FindRelationshipByFromName("Event Generator")
|
||||
to := dag.FindRelationshipByToName("Event Logger")
|
||||
|
||||
if len(from) != 2 {
|
||||
t.Error("Event Generator did not have the correct number of `From` relationships")
|
||||
}
|
||||
|
||||
if len(to) != 2 {
|
||||
t.Error("Event Logger did not have the correct number of `To` relationships")
|
||||
}
|
||||
}
|
||||
|
||||
// Sample DAG file data
|
||||
const test01DagStr = `Name: "Azure Event Hubs Sample"
|
||||
Id: "d6e4a5e9-696a-4626-ba7a-534d6ff450a5"
|
||||
Services:
|
||||
- Id: "Event Generator"
|
||||
Type: "EventGenerator"
|
||||
Properties: {}
|
||||
- Id: "Azure Event Hub"
|
||||
Type: "EventHub"
|
||||
Properties: {}
|
||||
- Id: "Event Logger"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
Relationships:
|
||||
- Id: "Generator to Event Hubs Link"
|
||||
Description: "Event Generator to Event Hub connection"
|
||||
From: "Event Generator"
|
||||
To: "Azure Event Hub"
|
||||
Properties: {}
|
||||
- Id: "Event Hubs to Event Logger Link"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "Azure Event Hub"
|
||||
To: "Event Logger"
|
||||
Properties: {}
|
||||
`
|
||||
|
||||
var test01WantDag DagConfigService = DagConfigService{
|
||||
Name: "Azure Event Hubs Sample",
|
||||
ID: guid.GUID("d6e4a5e9-696a-4626-ba7a-534d6ff450a5"),
|
||||
Services: []DagService{
|
||||
{
|
||||
ID: "Event Generator",
|
||||
Type: "EventGenerator",
|
||||
Properties: make(map[string]DagProperty),
|
||||
},
|
||||
{
|
||||
ID: "Azure Event Hub",
|
||||
Type: "EventHub",
|
||||
Properties: make(map[string]DagProperty),
|
||||
},
|
||||
{
|
||||
ID: "Event Logger",
|
||||
Type: "EventLogger",
|
||||
Properties: make(map[string]DagProperty),
|
||||
},
|
||||
},
|
||||
Relationships: []DagRelationship{
|
||||
{
|
||||
ID: "Generator to Event Hubs Link",
|
||||
Description: "Event Generator to Event Hub connection",
|
||||
From: "Event Generator",
|
||||
To: "Azure Event Hub",
|
||||
Properties: make(map[string]DagProperty),
|
||||
},
|
||||
{
|
||||
ID: "Event Hubs to Event Logger Link",
|
||||
Description: "Event Hubs to Event Logger connection",
|
||||
From: "Azure Event Hub",
|
||||
To: "Event Logger",
|
||||
Properties: make(map[string]DagProperty),
|
||||
},
|
||||
},
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
package diff
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/awalterschulze/gographviz"
|
||||
set "github.com/deckarep/golang-set"
|
||||
"github.com/microsoft/abstrakt/internal/platform/constellation"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ComparisonSet is a combination of two graphviz graphs.
|
||||
type ComparisonSet struct {
|
||||
SetCommonSvcs set.Set
|
||||
SetCommonRels set.Set
|
||||
SetAddedSvcs set.Set
|
||||
SetAddedRels set.Set
|
||||
SetDelSvcs set.Set
|
||||
SetDelRels set.Set
|
||||
}
|
||||
|
||||
// Compare contains two constellation configurations to compare against one another.
|
||||
type Compare struct {
|
||||
Original *constellation.Config
|
||||
New *constellation.Config
|
||||
}
|
||||
|
||||
//CompareConstellations takes two constellation configurations, compares and returns the differences.
|
||||
func (d *Compare) CompareConstellations() (string, error) {
|
||||
// populate comparison sets with changes between original and new graph
|
||||
sets := d.FillComparisonSets()
|
||||
|
||||
// build the graphviz output from new graph and comparison sets
|
||||
return CreateGraphWithChanges(d.New, &sets)
|
||||
}
|
||||
|
||||
// FillComparisonSets - loads provided set struct with data from the constellations and then determines the various differences between the
|
||||
// sets (original constellation and new) to help detemine what has been added, removed or changed.
|
||||
func (d *Compare) FillComparisonSets() (sets ComparisonSet) {
|
||||
setOrgSvcs, setOrgRel := createSet(d.Original)
|
||||
setNewSvcs, setNewRel := createSet(d.New)
|
||||
|
||||
// "Added" means in the new constellation but not the original one
|
||||
// "Deleted" means something is present in the original constellation but not in the new constellation
|
||||
// Services
|
||||
sets.SetCommonSvcs = setOrgSvcs.Intersect(setNewSvcs) //present in both
|
||||
sets.SetAddedSvcs = setNewSvcs.Difference(setOrgSvcs) //pressent in new but not in original
|
||||
sets.SetDelSvcs = setOrgSvcs.Difference(setNewSvcs) //present in original but not in new
|
||||
// Relationships
|
||||
sets.SetCommonRels = setOrgRel.Intersect(setNewRel) //present in both
|
||||
sets.SetAddedRels = setNewRel.Difference(setOrgRel) //pressent in new but not in original
|
||||
sets.SetDelRels = setOrgRel.Difference(setNewRel) //present in original but not in new
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// createSet - utility function used to create a pair of result sets (services + relationships) based on an input constellation DAG
|
||||
func createSet(dsGraph *constellation.Config) (set.Set, set.Set) {
|
||||
|
||||
// Create sets to hold services and relationships - used to find differences between old and new using intersection and difference operations
|
||||
retSetServices := set.NewSet()
|
||||
retSetRelationships := set.NewSet()
|
||||
|
||||
//Store all services in the services set
|
||||
for _, v := range dsGraph.Services {
|
||||
retSetServices.Add(v.ID)
|
||||
}
|
||||
|
||||
//Store relationships in the relationship set
|
||||
for _, v := range dsGraph.Relationships {
|
||||
retSetRelationships.Add(v.From + "|" + v.To)
|
||||
}
|
||||
|
||||
return retSetServices, retSetRelationships
|
||||
}
|
||||
|
||||
// CreateGraphWithChanges - use both input constellations (new and original) as well as the comparison sets to create
|
||||
// a dag that can be visualised. It uses the comparison sets to identify additions, deletions and changes between the original
|
||||
// and new constellations.
|
||||
func CreateGraphWithChanges(newGraph *constellation.Config, sets *ComparisonSet) (string, error) {
|
||||
// Lookup is used to map IDs to names. Names are easier to visualise but IDs are more important to ensure the
|
||||
// presented constellation is correct and IDs are used to link nodes together
|
||||
lookup := make(map[string]string)
|
||||
g := gographviz.NewGraph()
|
||||
|
||||
// Replace spaces with underscores, names with spaces can break graphviz engines
|
||||
if err := g.SetName(strings.Replace(newGraph.Name, " ", "_", -1) + "_diff"); err != nil {
|
||||
return "", fmt.Errorf("error setting graph name: %v", err)
|
||||
}
|
||||
// Attribute in graphviz to change graph orientation - LR indicates Left to Right. Default is top to bottom
|
||||
if err := g.AddAttr(g.Name, "rankdir", "LR"); err != nil {
|
||||
return "", fmt.Errorf("error adding node: %v", err)
|
||||
}
|
||||
|
||||
// Make the graph directed (a constellation is DAG)
|
||||
if err := g.SetDir(true); err != nil {
|
||||
return "", fmt.Errorf("error: %v", err)
|
||||
}
|
||||
|
||||
// Add all services from the new constellation
|
||||
// - New services - highlight with color (i.e in setAddedSvcs)
|
||||
// - Deleted services (i.e. in setDelSvcs) - include and format appropriately
|
||||
for _, v := range newGraph.Services {
|
||||
newName := strings.Replace(v.ID, " ", "_", -1) // Replace spaces in names with underscores, names with spaces can break graphviz engines)
|
||||
|
||||
// attributes are graphviz specific that control formatting. The linrary used requires then in the form of a map passed as an argument so it needs
|
||||
// to be built before adding the item
|
||||
attrs := make(map[string]string)
|
||||
|
||||
// Check if the service is new to this constellation
|
||||
if sets.SetAddedSvcs.Contains(newName) {
|
||||
attrs["color"] = "\"#d8ffa8\""
|
||||
}
|
||||
attrs["label"] = "\"" + v.Type + "\n" + v.ID + "\""
|
||||
fillShapeAndStyleForNodeType(v.Type, attrs)
|
||||
|
||||
lookup[v.ID] = newName
|
||||
err := g.AddNode(newGraph.Name, "\""+newName+"\"", attrs) //Surround names/labels with quotes, stops graphviz seeing special characters and breaking
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
//======================================== process deleted services ==========================================
|
||||
//Process services that have been removed from the original constellation
|
||||
for v := range sets.SetDelSvcs.Iter() {
|
||||
vString, _ := v.(string)
|
||||
newName := strings.Replace(vString, " ", "_", -1)
|
||||
attrs := make(map[string]string)
|
||||
attrs["color"] = "\"#ff9494\""
|
||||
fillShapeAndStyleForNodeType("", attrs)
|
||||
if err := g.AddNode(newGraph.Name, "\""+newName+"\"", attrs); err != nil {
|
||||
return "", fmt.Errorf("error adding node: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
//======================================== process relationships ==========================================
|
||||
// Add Relationships from the new constellation
|
||||
// - New relationships - highlight with color (i.e. in setAddedSvcs)
|
||||
// - Deleted relationships (i.e. in setDelRels) - include and format appropriately
|
||||
for _, v := range newGraph.Relationships {
|
||||
//Surround names/labels with quotes, stops graphviz seeing special characters and breaking
|
||||
localFrom := "\"" + lookup[v.From] + "\""
|
||||
localTo := "\"" + lookup[v.To] + "\""
|
||||
|
||||
attrs := make(map[string]string)
|
||||
// Relationship is stored in the map as source|destination - both are needed to tell if a relationship is new (this is how they are stored in the sets created earlier)
|
||||
relLookupName := lookup[v.From] + "|" + lookup[v.To]
|
||||
|
||||
// Check if the relationship is new (it will then not be in the common list of relationships between old and new constellation)
|
||||
if sets.SetAddedRels.Contains(relLookupName) {
|
||||
attrs["color"] = "\"#d8ffa8\""
|
||||
}
|
||||
|
||||
err := g.AddEdge(localFrom, localTo, true, attrs)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
//======================================== process deleted relationships =====================================
|
||||
// Deleted relationships
|
||||
for v := range sets.SetDelRels.Iter() {
|
||||
vString := strings.Replace(v.(string), " ", "_", -1)
|
||||
if len(strings.Split(vString, "|")) != 2 {
|
||||
return "", fmt.Errorf("Relationships string should be two items separated by | but got %v", vString)
|
||||
}
|
||||
newFrom := "\"" + strings.Split(vString, "|")[0] + "\""
|
||||
newTo := "\"" + strings.Split(vString, "|")[1] + "\""
|
||||
|
||||
attrs := make(map[string]string)
|
||||
attrs["color"] = "\"#ff9494\""
|
||||
err := g.AddEdge(newFrom, newTo, true, attrs)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Produce resulting graph in dot notation format
|
||||
return g.String(), nil
|
||||
}
|
||||
|
||||
// Populate attrs map with additional attributes based on the node type
|
||||
// Easier to change in a single place
|
||||
func fillShapeAndStyleForNodeType(nodeType string, existAttrs map[string]string) {
|
||||
switch nodeType {
|
||||
case "EventLogger":
|
||||
existAttrs["shape"] = "rectangle"
|
||||
existAttrs["style"] = "\"rounded, filled\""
|
||||
case "EventGenerator":
|
||||
existAttrs["shape"] = "rectangle"
|
||||
existAttrs["style"] = "\"rounded, filled\""
|
||||
case "EventHub":
|
||||
existAttrs["shape"] = "rectangle"
|
||||
existAttrs["style"] = "\"rounded, filled\""
|
||||
default:
|
||||
existAttrs["shape"] = "rectangle"
|
||||
existAttrs["style"] = "\"rounded, filled\""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package diff_test
|
||||
|
||||
import (
|
||||
set "github.com/deckarep/golang-set"
|
||||
"github.com/microsoft/abstrakt/internal/diff"
|
||||
"github.com/microsoft/abstrakt/internal/platform/constellation"
|
||||
helper "github.com/microsoft/abstrakt/tools/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestGetComparisonSets - generate sets of common, added and removed services and relationships from input YAML
|
||||
// and compare to manually created sets with a known or expected outcome
|
||||
func TestGetComparisonSets(t *testing.T) {
|
||||
|
||||
dsGraphOrg := new(constellation.Config)
|
||||
err := dsGraphOrg.LoadFile("testdata/original.yaml")
|
||||
assert.NoErrorf(t, err, "dagConfigService failed to load dag from test string %s", err)
|
||||
|
||||
dsGraphNew := new(constellation.Config)
|
||||
err = dsGraphNew.LoadFile("testdata/modified.yaml")
|
||||
assert.NoErrorf(t, err, "dagConfigService failed to load file %s", err)
|
||||
|
||||
// will populate with expected/known outcomes
|
||||
knownSets := &diff.ComparisonSet{}
|
||||
|
||||
populateComparisonSets(knownSets)
|
||||
|
||||
// function being tested
|
||||
diffSet := diff.Compare{Original: dsGraphOrg, New: dsGraphNew}
|
||||
loadedSets := diffSet.FillComparisonSets()
|
||||
|
||||
assert.True(t, knownSets.SetCommonSvcs.Equal(loadedSets.SetCommonSvcs), "Common services - did not match between expected result and input yaml")
|
||||
assert.True(t, knownSets.SetCommonRels.Equal(loadedSets.SetCommonRels), "Common relationships - did not match between expected result and input yaml")
|
||||
assert.True(t, knownSets.SetAddedSvcs.Equal(loadedSets.SetAddedSvcs), "Added services - did not match between expected result and input yaml")
|
||||
assert.True(t, knownSets.SetAddedRels.Equal(loadedSets.SetAddedRels), "Added relationships - did not match between expected result and input yaml")
|
||||
assert.True(t, knownSets.SetDelSvcs.Equal(loadedSets.SetDelSvcs), "Deleted services - did not match between expected result and input yaml")
|
||||
assert.True(t, knownSets.SetDelRels.Equal(loadedSets.SetDelRels), "Deleted relationships - did not match between expected result and input yaml")
|
||||
}
|
||||
|
||||
// TestGraphWithChanges - test diff comparison function
|
||||
func TestGraphWithChanges(t *testing.T) {
|
||||
|
||||
dsGraphOrg := new(constellation.Config)
|
||||
err := dsGraphOrg.LoadFile("testdata/original.yaml")
|
||||
assert.NoError(t, err)
|
||||
|
||||
dsGraphNew := new(constellation.Config)
|
||||
err = dsGraphNew.LoadFile("testdata/modified.yaml")
|
||||
assert.NoErrorf(t, err, "dagConfigService failed to load file %s", err)
|
||||
|
||||
// function being tested
|
||||
diffSet := diff.Compare{Original: dsGraphOrg, New: dsGraphNew}
|
||||
loadedSets := diffSet.FillComparisonSets()
|
||||
|
||||
resString, err := diff.CreateGraphWithChanges(dsGraphNew, &loadedSets)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Truef(t, helper.CompareGraphOutputAsSets(testDiffComparisonOutputString, resString), "Resulting output does not match the reference comparison input \n RESULT \n%s EXPECTED \n%s", resString, testDiffComparisonOutputString)
|
||||
}
|
||||
|
||||
// Utility to populate comparison sets with expected/known result
|
||||
func populateComparisonSets(target *diff.ComparisonSet) {
|
||||
target.SetCommonSvcs = set.NewSet()
|
||||
target.SetCommonSvcs.Add("3aa1e546-1ed5-4d67-a59c-be0d5905b490")
|
||||
target.SetCommonSvcs.Add("1d0255d4-5b8c-4a52-b0bb-ac024cda37e5")
|
||||
target.SetCommonSvcs.Add("a268fae5-2a82-4a3e-ada7-a52eeb7019ac")
|
||||
|
||||
target.SetCommonRels = set.NewSet()
|
||||
target.SetCommonRels.Add("3aa1e546-1ed5-4d67-a59c-be0d5905b490" + "|" + "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5")
|
||||
|
||||
target.SetAddedSvcs = set.NewSet()
|
||||
target.SetAddedSvcs.Add("9f1bcb3d-ff58-41d4-8779-f71e7b8800f8")
|
||||
target.SetAddedSvcs.Add("b268fae5-2a82-4a3e-ada7-a52eeb7019ac")
|
||||
|
||||
target.SetAddedRels = set.NewSet()
|
||||
target.SetAddedRels.Add("9f1bcb3d-ff58-41d4-8779-f71e7b8800f8" + "|" + "3aa1e546-1ed5-4d67-a59c-be0d5905b490")
|
||||
target.SetAddedRels.Add("1d0255d4-5b8c-4a52-b0bb-ac024cda37e5" + "|" + "a268fae5-2a82-4a3e-ada7-a52eeb7019ac")
|
||||
target.SetAddedRels.Add("a268fae5-2a82-4a3e-ada7-a52eeb7019ac" + "|" + "b268fae5-2a82-4a3e-ada7-a52eeb7019ac")
|
||||
|
||||
target.SetDelSvcs = set.NewSet()
|
||||
target.SetDelSvcs.Add("9e1bcb3d-ff58-41d4-8779-f71e7b8800f8")
|
||||
|
||||
target.SetDelRels = set.NewSet()
|
||||
target.SetDelRels.Add("9e1bcb3d-ff58-41d4-8779-f71e7b8800f8" + "|" + "3aa1e546-1ed5-4d67-a59c-be0d5905b490")
|
||||
target.SetDelRels.Add("3aa1e546-1ed5-4d67-a59c-be0d5905b490" + "|" + "a268fae5-2a82-4a3e-ada7-a52eeb7019ac")
|
||||
}
|
||||
|
||||
const testDiffComparisonOutputString = `digraph Azure_Event_Hubs_Sample_Changed_diff {
|
||||
rankdir=LR;
|
||||
"9f1bcb3d-ff58-41d4-8779-f71e7b8800f8"->"3aa1e546-1ed5-4d67-a59c-be0d5905b490"[ color="#d8ffa8" ];
|
||||
"3aa1e546-1ed5-4d67-a59c-be0d5905b490"->"1d0255d4-5b8c-4a52-b0bb-ac024cda37e5";
|
||||
"1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"->"a268fae5-2a82-4a3e-ada7-a52eeb7019ac"[ color="#d8ffa8" ];
|
||||
"a268fae5-2a82-4a3e-ada7-a52eeb7019ac"->"b268fae5-2a82-4a3e-ada7-a52eeb7019ac"[ color="#d8ffa8" ];
|
||||
"3aa1e546-1ed5-4d67-a59c-be0d5905b490"->"a268fae5-2a82-4a3e-ada7-a52eeb7019ac"[ color="#ff9494" ];
|
||||
"9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"->"3aa1e546-1ed5-4d67-a59c-be0d5905b490"[ color="#ff9494" ];
|
||||
"1d0255d4-5b8c-4a52-b0bb-ac024cda37e5" [ label="EventLogger
|
||||
1d0255d4-5b8c-4a52-b0bb-ac024cda37e5", shape=rectangle, style="rounded, filled" ];
|
||||
"3aa1e546-1ed5-4d67-a59c-be0d5905b490" [ label="EventHub
|
||||
3aa1e546-1ed5-4d67-a59c-be0d5905b490", shape=rectangle, style="rounded, filled" ];
|
||||
"9e1bcb3d-ff58-41d4-8779-f71e7b8800f8" [ color="#ff9494", shape=rectangle, style="rounded, filled" ];
|
||||
"9f1bcb3d-ff58-41d4-8779-f71e7b8800f8" [ color="#d8ffa8", label="EventGenerator
|
||||
9f1bcb3d-ff58-41d4-8779-f71e7b8800f8", shape=rectangle, style="rounded, filled" ];
|
||||
"a268fae5-2a82-4a3e-ada7-a52eeb7019ac" [ label="EventLogger
|
||||
a268fae5-2a82-4a3e-ada7-a52eeb7019ac", shape=rectangle, style="rounded, filled" ];
|
||||
"b268fae5-2a82-4a3e-ada7-a52eeb7019ac" [ color="#d8ffa8", label="EventLogger
|
||||
b268fae5-2a82-4a3e-ada7-a52eeb7019ac", shape=rectangle, style="rounded, filled" ];
|
||||
|
||||
}
|
||||
`
|
|
@ -0,0 +1,48 @@
|
|||
Name: "Azure Event Hubs Sample Changed"
|
||||
Id: "d6e4a5e9-696a-4626-ba7a-534d6ff450a5"
|
||||
Services:
|
||||
- Name: "Event Generator"
|
||||
Id: "9f1bcb3d-ff58-41d4-8779-f71e7b8800f8"
|
||||
Type: "EventGenerator"
|
||||
Properties: {}
|
||||
- Name: "Azure Event Hub"
|
||||
Id: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
Type: "EventHub"
|
||||
Properties: {}
|
||||
- Name: "Event Logger"
|
||||
Id: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
- Name: "Event Logger Added"
|
||||
Id: "b268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
- Name: "Event Logger"
|
||||
Id: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
Relationships:
|
||||
- Name: "Generator to Event Hubs Link"
|
||||
Id: "211a55bd-5d92-446c-8be8-190f8f0e623e"
|
||||
Description: "Event Generator to Event Hub connection"
|
||||
From: "9f1bcb3d-ff58-41d4-8779-f71e7b8800f8"
|
||||
To: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
Properties: {}
|
||||
- Name: "Event Hubs to Event Logger Link"
|
||||
Id: "08ccbd67-456f-4349-854a-4e6959e5017b"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
To: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
|
||||
Properties: {}
|
||||
- Name: "Event Hubs to Event Logger Link Repeat"
|
||||
Id: "c8a719e0-164d-408f-9ed1-06e08dc5abbe"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
|
||||
To: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Properties: {}
|
||||
- Name: "Event Hubs to Event Logger Link Added to the end Repeat"
|
||||
Id: "d8a719e0-164d-408f-9ed1-06e08dc5abbe"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
To: "b268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Properties: {}
|
|
@ -0,0 +1,38 @@
|
|||
Name: "Azure Event Hubs Sample"
|
||||
Id: "d6e4a5e9-696a-4626-ba7a-534d6ff450a5"
|
||||
Services:
|
||||
- Name: "Event Generator"
|
||||
Id: "9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"
|
||||
Type: "EventGenerator"
|
||||
Properties: {}
|
||||
- Name: "Azure Event Hub"
|
||||
Id: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
Type: "EventHub"
|
||||
Properties: {}
|
||||
- Name: "Event Logger"
|
||||
Id: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
- Name: "Event Logger"
|
||||
Id: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
|
||||
Type: "EventLogger"
|
||||
Properties: {}
|
||||
Relationships:
|
||||
- Name: "Generator to Event Hubs Link"
|
||||
Id: "211a55bd-5d92-446c-8be8-190f8f0e623e"
|
||||
Description: "Event Generator to Event Hub connection"
|
||||
From: "9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"
|
||||
To: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
Properties: {}
|
||||
- Name: "Event Hubs to Event Logger Link"
|
||||
Id: "08ccbd67-456f-4349-854a-4e6959e5017b"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
To: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
|
||||
Properties: {}
|
||||
- Name: "Event Hubs to Event Logger Link Repeat"
|
||||
Id: "c8a719e0-164d-408f-9ed1-06e08dc5abbe"
|
||||
Description: "Event Hubs to Event Logger connection"
|
||||
From: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
|
||||
To: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
|
||||
Properties: {}
|
|
@ -1,4 +1,4 @@
|
|||
package chartservice
|
||||
package chart
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -17,8 +17,8 @@ description: A Helm chart for Kubernetes
|
|||
version: 4.3.2
|
||||
home: ""`
|
||||
|
||||
//CreateChart makes a new chart at the specified location
|
||||
func CreateChart(name string, dir string) (chartReturn *chart.Chart, err error) {
|
||||
//Create makes a new chart at the specified location
|
||||
func Create(name string, dir string) (chartReturn *chart.Chart, err error) {
|
||||
tdir, err := ioutil.TempDir("./", "output-")
|
||||
|
||||
if err != nil {
|
||||
|
@ -77,8 +77,8 @@ func CreateChart(name string, dir string) (chartReturn *chart.Chart, err error)
|
|||
return
|
||||
}
|
||||
|
||||
// LoadChartFromDir loads a Helm chart from the specified director
|
||||
func LoadChartFromDir(dir string) (*chart.Chart, error) {
|
||||
// LoadFromDir loads a Helm chart from the specified director
|
||||
func LoadFromDir(dir string) (*chart.Chart, error) {
|
||||
h, err := loader.LoadDir(dir)
|
||||
|
||||
if err != nil {
|
||||
|
@ -88,18 +88,18 @@ func LoadChartFromDir(dir string) (*chart.Chart, error) {
|
|||
return h, nil
|
||||
}
|
||||
|
||||
// SaveChartToDir takes the chart object and saves it as a set of files in the specified director
|
||||
func SaveChartToDir(chart *chart.Chart, dir string) error {
|
||||
// SaveToDir takes the chart object and saves it as a set of files in the specified director
|
||||
func SaveToDir(chart *chart.Chart, dir string) error {
|
||||
return chartutil.SaveDir(chart, dir)
|
||||
}
|
||||
|
||||
// ZipChartToDir compresses the chart and saves it in compiled format
|
||||
func ZipChartToDir(chart *chart.Chart, dir string) (string, error) {
|
||||
// ZipToDir compresses the chart and saves it in compiled format
|
||||
func ZipToDir(chart *chart.Chart, dir string) (string, error) {
|
||||
return chartutil.Save(chart, dir)
|
||||
}
|
||||
|
||||
// BuildChart download charts
|
||||
func BuildChart(dir string) (out *bytes.Buffer, err error) {
|
||||
// Build download charts
|
||||
func Build(dir string) (out *bytes.Buffer, err error) {
|
||||
|
||||
out = &bytes.Buffer{}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package chartservice
|
||||
package chart_test
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
|
@ -6,6 +6,8 @@ import (
|
|||
"bytes"
|
||||
"compress/gzip"
|
||||
"flag"
|
||||
"github.com/microsoft/abstrakt/internal/platform/chart"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -25,38 +27,38 @@ func TestUpdate(t *testing.T) {
|
|||
|
||||
err := os.RemoveAll("testdata/golden")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
|
||||
err = os.MkdirAll("testdata/golden/helm/charts", os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
|
||||
err = exec.Command("cp", "-r", "testdata/sample/helm", "testdata/golden/").Run()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
|
||||
// Create zipped dependant charts
|
||||
err = exec.Command("tar", "cfz", "testdata/golden/helm/charts/event_hub_sample_event_generator-1.0.0.tgz", "testdata/sample/deps/event_hub_sample_event_generator/").Run()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
|
||||
err = exec.Command("tar", "cfz", "testdata/golden/helm/charts/event_hub_sample_event_hub-1.0.0.tgz", "testdata/sample/deps/event_hub_sample_event_hub/").Run()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
|
||||
err = exec.Command("tar", "cfz", "testdata/golden/helm/charts/event_hub_sample_event_logger-1.0.0.tgz", "testdata/sample/deps/event_hub_sample_event_logger/").Run()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
|
||||
err = exec.Command("tar", "cfz", "testdata/golden/test-0.1.0.tgz", "testdata/sample/helm").Run()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,71 +67,67 @@ func TestChartSavesAndLoads(t *testing.T) {
|
|||
tdir2, err2 := ioutil.TempDir("./", "output-")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
|
||||
if err2 != nil {
|
||||
t.Fatal(err2)
|
||||
assert.FailNow(t, err2.Error())
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = os.RemoveAll(tdir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = os.RemoveAll(tdir2)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.RemoveAll(tdir2)
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
c, err := CreateChart("foo", tdir)
|
||||
c, err := chart.Create("foo", tdir)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
|
||||
err = SaveChartToDir(c, tdir2)
|
||||
err = chart.SaveToDir(c, tdir2)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save newly created chart %q: %s", tdir2, err)
|
||||
assert.FailNowf(t, "Failed to save newly created chart %q: %s", tdir2, err)
|
||||
}
|
||||
|
||||
newPath := filepath.Join(tdir2, "foo")
|
||||
|
||||
_, err = LoadChartFromDir(newPath)
|
||||
_, err = chart.LoadFromDir(newPath)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load newly created chart %q: %s", newPath, err)
|
||||
assert.FailNowf(t, "Failed to load newly created chart %q: %s", newPath, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestChartBuildChart(t *testing.T) {
|
||||
tdir, err := ioutil.TempDir("./", "output-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = os.RemoveAll(tdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
err = exec.Command("cp", "-r", "testdata/sample/helm", tdir+"/").Run()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
|
||||
err = exec.Command("cp", "-r", "testdata/sample/deps", tdir+"/").Run()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
|
||||
_, err = BuildChart(tdir + "/helm")
|
||||
_, err = chart.Build(tdir + "/helm")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to BuildChart(): %s", err)
|
||||
assert.FailNowf(t, "Failed to BuildChart(): %s", err.Error())
|
||||
}
|
||||
|
||||
chartsDir := tdir + "/helm/charts/"
|
||||
|
@ -142,23 +140,23 @@ func TestChartBuildChart(t *testing.T) {
|
|||
func TestZipChartToDir(t *testing.T) {
|
||||
tdir, err := ioutil.TempDir("./", "output-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
defer func() {
|
||||
err = os.RemoveAll(tdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
chart, err := LoadChartFromDir("testdata/sample/helm")
|
||||
helm, err := chart.LoadFromDir("testdata/sample/helm")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed on LoadChartFromDir(): %s", err)
|
||||
assert.FailNowf(t, "Failed on LoadChartFromDir(): %s", err.Error())
|
||||
}
|
||||
|
||||
_, err = ZipChartToDir(chart, tdir)
|
||||
_, err = chart.ZipToDir(helm, tdir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed on ZipChartToDir(): %s", err)
|
||||
assert.FailNowf(t, "Failed on ZipChartToDir(): %s", err.Error())
|
||||
}
|
||||
compareFiles(t, "testdata/golden/test-0.1.0.tgz", tdir+"/test-0.1.0.tgz")
|
||||
}
|
||||
|
@ -181,30 +179,30 @@ func compareFiles(t *testing.T, expected, test string) {
|
|||
func readGz(t *testing.T, file string) (out bytes.Buffer) {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
defer func() {
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
zw, err := gzip.NewReader(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
defer func() {
|
||||
err = zw.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
writer := bufio.NewWriter(&out)
|
||||
_, err = io.Copy(writer, zw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
assert.FailNow(t, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче