зеркало из 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.
|
# 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
|
# Avoid warnings by switching to noninteractive
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
@ -18,6 +18,7 @@ RUN apt-get update \
|
||||||
&& go get -x -d github.com/stamblerre/gocode 2>&1 \
|
&& go get -x -d github.com/stamblerre/gocode 2>&1 \
|
||||||
&& go build -o gocode-gomod github.com/stamblerre/gocode \
|
&& go build -o gocode-gomod github.com/stamblerre/gocode \
|
||||||
&& mv gocode-gomod $GOPATH/bin/ \
|
&& mv gocode-gomod $GOPATH/bin/ \
|
||||||
|
&& GO111MODULE=on go get -v golang.org/x/tools/gopls@latest \
|
||||||
&& go get -u -v \
|
&& go get -u -v \
|
||||||
github.com/google/wire/cmd/wire \
|
github.com/google/wire/cmd/wire \
|
||||||
github.com/rakyll/gotest \
|
github.com/rakyll/gotest \
|
||||||
|
@ -38,17 +39,15 @@ RUN apt-get update \
|
||||||
github.com/cweill/gotests/... \
|
github.com/cweill/gotests/... \
|
||||||
golang.org/x/tools/cmd/goimports \
|
golang.org/x/tools/cmd/goimports \
|
||||||
golang.org/x/lint/golint \
|
golang.org/x/lint/golint \
|
||||||
golang.org/x/tools/cmd/gopls \
|
|
||||||
github.com/alecthomas/gometalinter \
|
github.com/alecthomas/gometalinter \
|
||||||
honnef.co/go/tools/... \
|
honnef.co/go/tools/... \
|
||||||
github.com/mgechev/revive \
|
github.com/mgechev/revive \
|
||||||
github.com/derekparker/delve/cmd/dlv 2>&1 \
|
github.com/derekparker/delve/cmd/dlv 2>&1 \
|
||||||
&& GO111MODULE=on go get golang.org/x/tools/gopls@latest \
|
&& 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 autoremove -y \
|
||||||
&& apt-get clean -y \
|
&& apt-get clean -y \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
# Install Docker CE CLI
|
# Install Docker CE CLI
|
||||||
|
|
|
@ -2,21 +2,23 @@ run:
|
||||||
deadline: 5m
|
deadline: 5m
|
||||||
skip-files: []
|
skip-files: []
|
||||||
linters-settings:
|
linters-settings:
|
||||||
linters-settings.govet:
|
govet:
|
||||||
check-shadowing: true
|
check-shadowing: true
|
||||||
linters-settings.gocyclo:
|
gocyclo:
|
||||||
min-complexity: 12.0
|
min-complexity: 12.0
|
||||||
linters-settings.maligned:
|
maligned:
|
||||||
suggest-new: true
|
suggest-new: true
|
||||||
linters-settings.goconst:
|
goconst:
|
||||||
min-len: 3.0
|
min-len: 3.0
|
||||||
min-occurrences: 3.0
|
min-occurrences: 3.0
|
||||||
linters-settings.misspell:
|
misspell:
|
||||||
locale: "US"
|
locale: "UK"
|
||||||
ignore-words:
|
ignore-words:
|
||||||
- listend
|
- listend
|
||||||
- analyses
|
- analyses
|
||||||
- cancelling
|
- cancelling
|
||||||
|
- color
|
||||||
|
- colors
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- vet
|
- vet
|
||||||
|
|
|
@ -32,7 +32,7 @@ With everything installed and running, you can continue.
|
||||||
|
|
||||||
You can find sample constellation files in the following location:
|
You can find sample constellation files in the following location:
|
||||||
|
|
||||||
`/sample/constellation/`
|
`/examples/constellation/`
|
||||||
|
|
||||||
Using these files you can test the app binary.
|
Using these files you can test the app binary.
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
trigger:
|
trigger:
|
||||||
|
|
||||||
branches:
|
branches:
|
||||||
include:
|
include:
|
||||||
- master
|
- master
|
||||||
|
@ -15,12 +16,16 @@ variables:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- task: GoTool@0
|
- task: GoTool@0
|
||||||
displayName: 'Using Go v1.13'
|
displayName: 'Using Go v1.13.1'
|
||||||
inputs:
|
inputs:
|
||||||
version: '1.13'
|
version: '1.13.1'
|
||||||
GOPATH: '$(GOPATH)'
|
goPath: '$(GOPATH)'
|
||||||
GOBIN: '$(GOBIN)'
|
goBin: '$(GOBIN)'
|
||||||
|
|
||||||
|
- bash: |
|
||||||
|
echo "##vso[task.setvariable variable=PATH]${PATH}:$(go env GOPATH)/bin"
|
||||||
|
displayName: 'Exporting GOPATH to PATH'
|
||||||
|
|
||||||
- task: Go@0
|
- task: Go@0
|
||||||
displayName: 'Resolving Dependencies'
|
displayName: 'Resolving Dependencies'
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -38,14 +43,14 @@ steps:
|
||||||
make lint-all
|
make lint-all
|
||||||
displayName: 'Lint Abstrakt'
|
displayName: 'Lint Abstrakt'
|
||||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||||
env: { GOPTH: '$(GOPATH)' }
|
env: { GOPATH: '$(GOPATH)' }
|
||||||
failOnStderr: true
|
failOnStderr: true
|
||||||
|
|
||||||
- script: |
|
- script: |
|
||||||
make test-export-all
|
make test-export-all
|
||||||
displayName: 'Test Abstrakt'
|
displayName: 'Test Abstrakt'
|
||||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||||
env: { GOPTH: '$(GOPATH)' }
|
env: { GOPATH: '$(GOPATH)' }
|
||||||
|
|
||||||
- task: PublishTestResults@2
|
- task: PublishTestResults@2
|
||||||
inputs:
|
inputs:
|
|
@ -1,7 +1,7 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/microsoft/abstrakt/internal/tools/logger"
|
"github.com/microsoft/abstrakt/tools/logger"
|
||||||
cobra "github.com/spf13/cobra"
|
cobra "github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/microsoft/abstrakt/internal/chartservice"
|
"github.com/microsoft/abstrakt/internal/compose"
|
||||||
"github.com/microsoft/abstrakt/internal/composeservice"
|
"github.com/microsoft/abstrakt/internal/platform/chart"
|
||||||
"github.com/microsoft/abstrakt/internal/tools/logger"
|
"github.com/microsoft/abstrakt/tools/logger"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"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)
|
return fmt.Errorf("Template type: %v is not known", cc.templateType)
|
||||||
}
|
}
|
||||||
|
|
||||||
service := composeservice.NewComposeService()
|
service := new(compose.Composer)
|
||||||
_ = service.LoadFromFile(cc.constellationFilePath, cc.mapsFilePath)
|
err = service.LoadFile(cc.constellationFilePath, cc.mapsFilePath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
logger.Debugf("noChecks is set to %t", *cc.noChecks)
|
logger.Debugf("noChecks is set to %t", *cc.noChecks)
|
||||||
|
|
||||||
if !*cc.noChecks {
|
if !*cc.noChecks {
|
||||||
logger.Debug("Starting validating constellation")
|
logger.Debug("Starting validating constellation")
|
||||||
|
|
||||||
err = validateDag(&service.DagConfigService)
|
err = validateDag(&service.Constellation)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -60,12 +64,12 @@ Example: abstrakt compose [chart name] -t [templateType] -f [constellationFilePa
|
||||||
logger.Debug("Finished validating constellation")
|
logger.Debug("Finished validating constellation")
|
||||||
}
|
}
|
||||||
|
|
||||||
chart, err := service.Compose(chartName, cc.outputPath)
|
helm, err := service.Build(chartName, cc.outputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Could not compose: %v", err)
|
return fmt.Errorf("Could not compose: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = chartservice.SaveChartToDir(chart, cc.outputPath)
|
err = chart.SaveToDir(helm, cc.outputPath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("There was an error saving the chart: %v", err)
|
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)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("There was an error saving the chart: %v", err)
|
return fmt.Errorf("There was an error saving the chart: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *cc.zipChart {
|
if *cc.zipChart {
|
||||||
_, err = chartservice.ZipChartToDir(chart, cc.outputPath)
|
_, err = chart.ZipToDir(helm, cc.outputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("There was an error zipping the chart: %v", err)
|
return fmt.Errorf("There was an error zipping the chart: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,134 +1,67 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
helper "github.com/microsoft/abstrakt/tools/test"
|
||||||
"fmt"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"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) {
|
func TestComposeCommandReturnsErrorIfTemplateTypeIsInvalid(t *testing.T) {
|
||||||
templateType := "ble"
|
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)
|
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)
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Did not received expected error. \nGot:\n %v", output)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestComposeCommandDoesNotErrorIfTemplateTypeIsEmptyOrHelm(t *testing.T) {
|
func TestComposeCommandDoesNotErrorIfTemplateTypeIsEmptyOrHelm(t *testing.T) {
|
||||||
templateType := ""
|
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"
|
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 {
|
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)
|
||||||
t.Errorf("Did not expect error:\n %v\n output: %v", err, output)
|
assert.NoErrorf(t, err, "Did not expect error:\n %v\n output: %v", err, output)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestComposeCommandReturnsErrorWithInvalidFilePaths(t *testing.T) {
|
func TestComposeCommandReturnsErrorWithInvalidFilePaths(t *testing.T) {
|
||||||
output, err := executeCommand(newComposeCmd().cmd, "test-compose-cmd-returns-error-with-invalid-files", "-f", "invalid", "-m", "invalid", "-o", "invalid")
|
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)
|
||||||
if err == nil {
|
|
||||||
t.Errorf("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) {
|
func TestComposeCmdVerifyRequiredFlags(t *testing.T) {
|
||||||
expected := "required flag(s) \"constellationFilePath\", \"mapsFilePath\", \"outputPath\" not set"
|
expected := "required flag(s) \"constellationFilePath\", \"mapsFilePath\", \"outputPath\" not set"
|
||||||
|
|
||||||
output, err := executeCommand(newComposeCmd().cmd, "")
|
output, err := helper.ExecuteCommand(newComposeCmd().cmd, "")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
checkStringContains(t, err.Error(), expected)
|
assert.Contains(t, err.Error(), expected)
|
||||||
} else {
|
} else {
|
||||||
t.Errorf("Expecting error: \n %v\nGot:\n %v\n", expected, output)
|
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) {
|
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)
|
output, err := helper.ExecuteCommand(newComposeCmd().cmd, "test-compose-cmd-with-flags", "-f", constellationPath, "-m", mapsPath, "-o", tdir)
|
||||||
if err != nil {
|
assert.NoErrorf(t, err, "error: \n %v\noutput:\n %v\n", err, output)
|
||||||
t.Errorf("error: \n %v\noutput:\n %v\n", err, output)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestComposeWithRealFiles(t *testing.T) {
|
func TestComposeWithRealFiles(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-real-files", "-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-real-files", "-f", constellationPath, "-m", mapsPath, "-o", tdir)
|
||||||
|
assert.NoErrorf(t, err, "error: \n %v\noutput:\n %v\n", err, output)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
288
cmd/diff.go
288
cmd/diff.go
|
@ -1,38 +1,23 @@
|
||||||
package cmd
|
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 (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/awalterschulze/gographviz"
|
"github.com/microsoft/abstrakt/internal/diff"
|
||||||
set "github.com/deckarep/golang-set"
|
"github.com/microsoft/abstrakt/internal/platform/constellation"
|
||||||
"github.com/microsoft/abstrakt/internal/dagconfigservice"
|
"github.com/microsoft/abstrakt/tools/logger"
|
||||||
"github.com/microsoft/abstrakt/internal/tools/logger"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
// "os"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type diffCmd struct {
|
type diffCmd struct {
|
||||||
constellationFilePathOrg string
|
constellationFilePathOrg string
|
||||||
constellationFilePathNew string
|
constellationFilePathNew string
|
||||||
showOriginal bool
|
showOriginal *bool
|
||||||
showNew bool
|
showNew *bool
|
||||||
*baseCmd
|
*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 {
|
func newDiffCmd() *diffCmd {
|
||||||
cc := &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)
|
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]`,
|
Example: abstrakt diff -o [constellationFilePathOriginal] -n [constellationFilePathNew]`,
|
||||||
|
SilenceUsage: true,
|
||||||
|
SilenceErrors: true,
|
||||||
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
logger.Debug("args: " + strings.Join(args, " "))
|
logger.Debug("args: " + strings.Join(args, " "))
|
||||||
logger.Debug("constellationFilePathOrg: " + cc.constellationFilePathOrg)
|
logger.Debugf("constellationFilePathOrg: %v", cc.constellationFilePathOrg)
|
||||||
logger.Debug("constellationFilePathNew: " + cc.constellationFilePathNew)
|
logger.Debugf("constellationFilePathNew: %v", cc.constellationFilePathNew)
|
||||||
|
|
||||||
if cmd.Flag("showOriginalOutput").Value.String() == "true" {
|
logger.Debugf("showOriginalOutput: %t", *cc.showOriginal)
|
||||||
logger.Debug("showOriginalOutput: true")
|
logger.Debugf("showNewOutput: %t", *cc.showNew)
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if !fileExists(cc.constellationFilePathOrg) {
|
dsGraphOrg := new(constellation.Config)
|
||||||
return fmt.Errorf("Could not open original YAML input file for reading %v", cc.constellationFilePathOrg)
|
err := dsGraphOrg.LoadFile(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)
|
|
||||||
if err != nil {
|
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 {
|
if *cc.showOriginal {
|
||||||
resStringOrg := generateGraph(dsGraphOrg)
|
out := &bytes.Buffer{}
|
||||||
|
var resStringOrg string
|
||||||
|
resStringOrg, err = dsGraphOrg.GenerateGraph(out)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
logger.Output(resStringOrg)
|
logger.Output(resStringOrg)
|
||||||
}
|
}
|
||||||
|
|
||||||
dsGraphNew := dagconfigservice.NewDagConfigService()
|
dsGraphNew := new(constellation.Config)
|
||||||
err = dsGraphNew.LoadDagConfigFromFile(cc.constellationFilePathNew)
|
err = dsGraphNew.LoadFile(cc.constellationFilePathNew)
|
||||||
if err != nil {
|
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 {
|
if *cc.showNew {
|
||||||
resStringNew := generateGraph(dsGraphNew)
|
out := &bytes.Buffer{}
|
||||||
|
var resStringNew string
|
||||||
|
resStringNew, err = dsGraphNew.GenerateGraph(out)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
logger.Output(resStringNew)
|
logger.Output(resStringNew)
|
||||||
}
|
}
|
||||||
|
|
||||||
resStringDiff := compareConstellations(dsGraphOrg, dsGraphNew)
|
constellationSets := diff.Compare{Original: dsGraphOrg, New: dsGraphNew}
|
||||||
// logger.Output is also outputting a timestamp and 'level' message when used on the command line which causes
|
resStringDiff, err := constellationSets.CompareConstellations()
|
||||||
// graphviz to fail !?
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
logger.Output(resStringDiff)
|
logger.Output(resStringDiff)
|
||||||
// fmt.Println(resStringDiff)
|
|
||||||
|
|
||||||
return nil
|
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.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().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.showOriginal = 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.showNew = cc.cmd.Flags().Bool("showNewOutput", false, "will additionally produce dot notation for new constellation")
|
||||||
_ = cc.cmd.MarkFlagRequired("constellationFilePathOriginal")
|
_ = cc.cmd.MarkFlagRequired("constellationFilePathOriginal")
|
||||||
_ = cc.cmd.MarkFlagRequired("constellationFilePathNew")
|
_ = cc.cmd.MarkFlagRequired("constellationFilePathNew")
|
||||||
|
|
||||||
return cc
|
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
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
helper "github.com/microsoft/abstrakt/tools/test"
|
||||||
set "github.com/deckarep/golang-set"
|
|
||||||
"github.com/microsoft/abstrakt/internal/dagconfigservice"
|
|
||||||
"github.com/sirupsen/logrus/hooks/test"
|
"github.com/sirupsen/logrus/hooks/test"
|
||||||
"io/ioutil"
|
"github.com/stretchr/testify/assert"
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestDiffCmdWithAllRequirementsNoError - test diff command parameters
|
// TestDiffCmdWithAllRequirementsNoError - test diff command parameters
|
||||||
// use valid arguments so expect no failures
|
// use valid arguments so expect no failures
|
||||||
func TestDiffCmdWithAllRequirementsNoError(t *testing.T) {
|
func TestDiffCmdWithAllRequirementsNoError(t *testing.T) {
|
||||||
constellationPathOrg, constellationPathNew, _, _ := localPrepareRealFilesForTest(t)
|
constellationPathOrg, constellationPathNew, _, _ := helper.PrepareTwoRealConstellationFilesForTest(t)
|
||||||
|
|
||||||
hook := test.NewGlobal()
|
hook := test.NewGlobal()
|
||||||
_, err := executeCommand(newDiffCmd().cmd, "-o", constellationPathOrg, "-n", constellationPathNew)
|
_, err := helper.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)
|
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, helper.CompareGraphOutputAsSets(testDiffComparisonOutputString, hook.LastEntry().Message))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestDffCmdFailYaml - test diff command parameters
|
// TestDffCmdFailYaml - test diff command parameters
|
||||||
// Test both required command line parameters (-o, -n) failing each in turn
|
// Test both required command line parameters (-o, -n) failing each in turn
|
||||||
func TestDffCmdFailYaml(t *testing.T) {
|
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 {
|
assert.Error(t, err)
|
||||||
checkStringContains(t, err.Error(), expected)
|
assert.EqualError(t, err, expected)
|
||||||
} else {
|
|
||||||
t.Errorf("Did not fail and it should have. Expected: %v \nGot: %v", expected, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
_, err = helper.ExecuteCommand(newDiffCmd().cmd, "-o", "../examples/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)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.EqualError(t, err, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestDiffCmdFailNotYaml - test diff command parameters
|
// 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
|
// 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) {
|
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 {
|
assert.Error(t, err)
|
||||||
checkStringContains(t, err.Error(), expected)
|
assert.EqualError(t, err, expected)
|
||||||
} else {
|
|
||||||
t.Errorf("Did not fail. Expected: %v \nGot: %v", expected, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
assert.Error(t, err)
|
||||||
checkStringContains(t, err.Error(), expected)
|
assert.EqualError(t, err, expected)
|
||||||
} else {
|
|
||||||
t.Errorf("Did not fail. Expected: %v \nGot: %v", expected, output)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
const testDiffComparisonOutputString = `digraph Azure_Event_Hubs_Sample_Changed_diff {
|
||||||
rankdir=LR;
|
rankdir=LR;
|
||||||
"9f1bcb3d-ff58-41d4-8779-f71e7b8800f8"->"3aa1e546-1ed5-4d67-a59c-be0d5905b490"[ color="#d8ffa8" ];
|
"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:
|
Maps:
|
||||||
- ChartName: "event_hub_sample_event_generator"
|
- ChartName: "event_hub_sample_event_generator"
|
||||||
Type: "EventGenerator"
|
Type: "EventGenerator"
|
||||||
Location: "file://../../../sample/deps/event_hub_sample_event_generator"
|
Location: "../../helm/basictest"
|
||||||
Version: "1.0.0"
|
Version: "1.0.0"
|
||||||
- ChartName: "event_hub_sample_event_logger"
|
- ChartName: "event_hub_sample_event_logger"
|
||||||
Type: "EventLogger"
|
Type: "EventLogger"
|
||||||
Location: "file://../../../sample/deps/event_hub_sample_event_logger"
|
Location: "../../helm/basictest2"
|
||||||
Version: "1.0.0"
|
Version: "1.0.0"
|
||||||
- ChartName: "event_hub_sample_event_hub"
|
- ChartName: "event_hub_sample_event_hub"
|
||||||
Type: "EventHub"
|
Type: "EventHub"
|
||||||
Location: "file://../../../sample/deps/event_hub_sample_event_hub"
|
Location: "../../helm/basictest3"
|
||||||
Version: "1.0.0"
|
Version: "1.0.0"
|
||||||
|
|
180
cmd/validate.go
180
cmd/validate.go
|
@ -2,14 +2,16 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/microsoft/abstrakt/internal/dagconfigservice"
|
"github.com/microsoft/abstrakt/internal/platform/constellation"
|
||||||
"github.com/microsoft/abstrakt/internal/tools/logger"
|
"github.com/microsoft/abstrakt/internal/platform/mapper"
|
||||||
"github.com/microsoft/abstrakt/internal/validationservice"
|
"github.com/microsoft/abstrakt/tools/find"
|
||||||
|
"github.com/microsoft/abstrakt/tools/logger"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type validateCmd struct {
|
type validateCmd struct {
|
||||||
constellationFilePath string
|
constellationFilePath string
|
||||||
|
mapperFilePath string
|
||||||
*baseCmd
|
*baseCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,24 +21,57 @@ func newValidateCmd() *validateCmd {
|
||||||
cc.baseCmd = newBaseCmd(&cobra.Command{
|
cc.baseCmd = newBaseCmd(&cobra.Command{
|
||||||
Use: "validate",
|
Use: "validate",
|
||||||
Short: "Validate a constellation file for correct schema and ensure correctness.",
|
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,
|
SilenceUsage: true,
|
||||||
SilenceErrors: true,
|
SilenceErrors: true,
|
||||||
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
d := dagconfigservice.NewDagConfigService()
|
if len(cc.constellationFilePath) == 0 && len(cc.mapperFilePath) == 0 {
|
||||||
err = d.LoadDagConfigFromFile(cc.constellationFilePath)
|
_ = cc.baseCmd.cmd.Usage()
|
||||||
|
return fmt.Errorf("no flags were set")
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateDag(&d)
|
var d constellation.Config
|
||||||
|
var m mapper.Config
|
||||||
|
|
||||||
if err == nil {
|
fail := false
|
||||||
logger.Info("Constellation is valid.")
|
|
||||||
|
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
|
return
|
||||||
|
@ -44,41 +79,138 @@ Example: abstrakt validate -f [constellationFilePath]`,
|
||||||
})
|
})
|
||||||
|
|
||||||
cc.cmd.Flags().StringVarP(&cc.constellationFilePath, "constellationFilePath", "f", "", "constellation file path")
|
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
|
return cc
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateDag takes a constellation dag and returns any errors.
|
func validateDagAndMapper(d *constellation.Config, m *mapper.Config) (err error) {
|
||||||
func validateDag(dag *dagconfigservice.DagConfigService) (err error) {
|
types := []string{}
|
||||||
service := validationservice.Validator{Config: dag}
|
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 {
|
if err != nil {
|
||||||
return
|
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 {
|
if duplicates != nil {
|
||||||
logger.Error("Duplicate IDs found:")
|
logger.Error("Duplicate `ID` present in config")
|
||||||
for _, i := range duplicates {
|
for _, i := range duplicates {
|
||||||
logger.Errorf("'%v'", i)
|
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 {
|
if len(connections) > 0 {
|
||||||
|
logger.Error("Missing relationship(s)")
|
||||||
for key, i := range connections {
|
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 {
|
for _, j := range i {
|
||||||
logger.Errorf("'%v'", j)
|
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
|
return
|
||||||
|
|
|
@ -1,27 +1,148 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
helper "github.com/microsoft/abstrakt/tools/test"
|
||||||
|
"github.com/sirupsen/logrus/hooks/test"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValidateCommand(t *testing.T) {
|
func TestValidateCommandNoArgs(t *testing.T) {
|
||||||
expected := "open does-not-exist: no such file or directory"
|
_, err := helper.ExecuteCommand(newValidateCmd().cmd)
|
||||||
|
assert.Error(t, err)
|
||||||
constellationPath, _, tdir := PrepareRealFilesForTest(t)
|
assert.EqualError(t, err, "no flags were set")
|
||||||
|
}
|
||||||
defer CleanTempTestFiles(t, tdir)
|
|
||||||
|
func TestValidateCommandConstellationAndMapper(t *testing.T) {
|
||||||
output, err := executeCommand(newValidateCmd().cmd, "-f", constellationPath)
|
constellationPath := "testdata/constellation/valid.yaml"
|
||||||
|
mapPath := "testdata/mapper/valid.yaml"
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Did not received expected error. \nGot:\n %v", output)
|
output, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", constellationPath, "-m", mapPath)
|
||||||
}
|
assert.NoErrorf(t, err, "Did not received expected error. \nGot:\n %v", output)
|
||||||
|
}
|
||||||
_, err = executeCommand(newValidateCmd().cmd, "-f", "does-not-exist")
|
|
||||||
|
func TestValidateCommandConstellationExist(t *testing.T) {
|
||||||
if err != nil {
|
constellationPath := "testdata/constellation/valid.yaml"
|
||||||
checkStringContains(t, err.Error(), expected)
|
|
||||||
} else {
|
output, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", constellationPath)
|
||||||
t.Errorf("Did not received expected error. \nExpected: %v\nGot:\n %v", expected, err.Error())
|
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
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
logger "github.com/microsoft/abstrakt/internal/tools/logger"
|
logger "github.com/microsoft/abstrakt/tools/logger"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
helper "github.com/microsoft/abstrakt/tools/test"
|
||||||
"github.com/sirupsen/logrus/hooks/test"
|
"github.com/sirupsen/logrus/hooks/test"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"os"
|
"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)
|
// 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) {
|
func TestVersion(t *testing.T) {
|
||||||
expected := "0.0.1"
|
expected := "0.0.1"
|
||||||
version := Version()
|
version := Version()
|
||||||
|
assert.Equal(t, expected, version)
|
||||||
if version != expected {
|
|
||||||
t.Errorf("Did not find correct abstrakt version. Expected %v, got %v", expected, version)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVersionCmd(t *testing.T) {
|
func TestVersionCmd(t *testing.T) {
|
||||||
expected := "0.0.1"
|
expected := "0.0.1"
|
||||||
|
|
||||||
hook := test.NewGlobal()
|
hook := test.NewGlobal()
|
||||||
_, err := executeCommand(newVersionCmd().cmd)
|
_, err := helper.ExecuteCommand(newVersionCmd().cmd)
|
||||||
|
|
||||||
if err != nil {
|
assert.NoError(t, err)
|
||||||
t.Error(err)
|
assert.Contains(t, hook.LastEntry().Message, expected)
|
||||||
} else {
|
|
||||||
checkStringContains(t, hook.LastEntry().Message, expected)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,11 @@ package cmd
|
||||||
// has to be run through a graphviz visualisation tool/utiliyy
|
// has to be run through a graphviz visualisation tool/utiliyy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/awalterschulze/gographviz"
|
"github.com/microsoft/abstrakt/internal/platform/constellation"
|
||||||
"github.com/microsoft/abstrakt/internal/dagconfigservice"
|
"github.com/microsoft/abstrakt/tools/logger"
|
||||||
"github.com/microsoft/abstrakt/internal/tools/logger"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,17 +33,19 @@ Example: abstrakt visualise -f [constellationFilePath]`,
|
||||||
logger.Debug("args: " + strings.Join(args, " "))
|
logger.Debug("args: " + strings.Join(args, " "))
|
||||||
logger.Debug("constellationFilePath: " + cc.constellationFilePath)
|
logger.Debug("constellationFilePath: " + cc.constellationFilePath)
|
||||||
|
|
||||||
if !fileExists(cc.constellationFilePath) {
|
dsGraph := new(constellation.Config)
|
||||||
return fmt.Errorf("Could not open YAML input file for reading %v", cc.constellationFilePath)
|
err := dsGraph.LoadFile(cc.constellationFilePath)
|
||||||
}
|
|
||||||
|
|
||||||
dsGraph := dagconfigservice.NewDagConfigService()
|
|
||||||
err := dsGraph.LoadDagConfigFromFile(cc.constellationFilePath)
|
|
||||||
if err != nil {
|
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)
|
logger.Output(resString)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -56,68 +57,3 @@ Example: abstrakt visualise -f [constellationFilePath]`,
|
||||||
|
|
||||||
return cc
|
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
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/microsoft/abstrakt/internal/platform/constellation"
|
||||||
"github.com/microsoft/abstrakt/internal/dagconfigservice"
|
"github.com/microsoft/abstrakt/tools/file"
|
||||||
|
helper "github.com/microsoft/abstrakt/tools/test"
|
||||||
"github.com/sirupsen/logrus/hooks/test"
|
"github.com/sirupsen/logrus/hooks/test"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -11,57 +13,37 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestVisualiseCmdWithAllRequirementsNoError(t *testing.T) {
|
func TestVisualiseCmdWithAllRequirementsNoError(t *testing.T) {
|
||||||
constellationPath, _, _ := PrepareRealFilesForTest(t)
|
constellationPath := "testdata/constellation/valid.yaml"
|
||||||
|
|
||||||
hook := test.NewGlobal()
|
hook := test.NewGlobal()
|
||||||
_, err := executeCommand(newVisualiseCmd().cmd, "-f", constellationPath)
|
_, err := helper.ExecuteCommand(newVisualiseCmd().cmd, "-f", constellationPath)
|
||||||
|
|
||||||
if err != nil {
|
assert.NoError(t, err)
|
||||||
t.Error("Did not receive output")
|
assert.True(t, helper.CompareGraphOutputAsSets(validGraphString, hook.LastEntry().Message))
|
||||||
} 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVisualiseCmdFailYaml(t *testing.T) {
|
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 {
|
assert.Error(t, err)
|
||||||
checkStringContains(t, err.Error(), expected)
|
assert.Contains(t, err.Error(), expected)
|
||||||
} else {
|
|
||||||
t.Errorf("Did not fail. Expected: %v \nGot: %v", expected, output)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVisualiseCmdFailNotYaml(t *testing.T) {
|
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 {
|
assert.Error(t, err)
|
||||||
checkStringContains(t, err.Error(), expected)
|
assert.Contains(t, err.Error(), expected)
|
||||||
} else {
|
|
||||||
t.Errorf("Did not fail. Expected: %v \nGot: %v", expected, output)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileExists(t *testing.T) {
|
func TestFileExists(t *testing.T) {
|
||||||
|
_, _, tdir := helper.PrepareRealFilesForTest(t)
|
||||||
|
|
||||||
tdir, err := ioutil.TempDir("", "helm-")
|
defer helper.CleanTempTestFiles(t, tdir)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
err = os.RemoveAll(tdir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
//Setup variables and content for test
|
//Setup variables and content for test
|
||||||
testValidFilename := filepath.Join(tdir, "testVisualise.out")
|
testValidFilename := filepath.Join(tdir, "testVisualise.out")
|
||||||
|
@ -69,115 +51,37 @@ func TestFileExists(t *testing.T) {
|
||||||
testData := []byte("A file to test with")
|
testData := []byte("A file to test with")
|
||||||
|
|
||||||
//Create a file to test against
|
//Create a file to test against
|
||||||
err = ioutil.WriteFile(testValidFilename, testData, 0644)
|
err := ioutil.WriteFile(testValidFilename, testData, 0644)
|
||||||
if err != nil {
|
assert.NoError(t, err, "Could not create output testing file, cannot proceed")
|
||||||
fmt.Println("Could not create output testing file, cannot proceed")
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Test that a valid file (created above) can be seen
|
//Test that a valid file (created above) can be seen
|
||||||
var result bool = fileExists(testValidFilename) //Expecting true - file does exists
|
var result bool = file.Exists(testValidFilename) //Expecting true - file does exists
|
||||||
if result == false {
|
assert.True(t, result, "Test file does exist but testFile returns that it does not")
|
||||||
t.Errorf("Test file does exist but testFile returns that it does not")
|
|
||||||
}
|
|
||||||
|
|
||||||
//Test that an invalid file (does not exist) is not seen
|
//Test that an invalid file (does not exist) is not seen
|
||||||
result = fileExists(testInvalidFilename) //Expecting false - file does not exist
|
result = file.Exists(testInvalidFilename) //Expecting false - file does not exist
|
||||||
if result != false {
|
assert.False(t, result, "Test file does not exist but testFile says it does")
|
||||||
t.Errorf("Test file does not exist but testFile says it does")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.Remove(testValidFilename)
|
err = os.Remove(testValidFilename)
|
||||||
if err != nil {
|
assert.NoError(t, err)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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) {
|
func TestParseYaml(t *testing.T) {
|
||||||
|
retConfig := new(constellation.Config)
|
||||||
|
err := retConfig.LoadString(testValidYAMLString)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
retConfig := dagconfigservice.NewDagConfigService()
|
errMsg := "YAML did not parse correctly and it should have"
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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 = `
|
const testValidYAMLString = `
|
||||||
Description: "Event Generator to Event Hub connection"
|
Description: "Event Generator to Event Hub connection"
|
||||||
From: 9e1bcb3d-ff58-41d4-8779-f71e7b8800f8
|
From: 9e1bcb3d-ff58-41d4-8779-f71e7b8800f8
|
||||||
|
@ -196,13 +100,11 @@ Type: EventGenerator
|
||||||
|
|
||||||
const validGraphString = `digraph Azure_Event_Hubs_Sample {
|
const validGraphString = `digraph Azure_Event_Hubs_Sample {
|
||||||
rankdir=LR;
|
rankdir=LR;
|
||||||
"9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"->"3aa1e546-1ed5-4d67-a59c-be0d5905b490";
|
"Event_Generator"->"Azure_Event_Hub";
|
||||||
"3aa1e546-1ed5-4d67-a59c-be0d5905b490"->"1d0255d4-5b8c-4a52-b0bb-ac024cda37e5";
|
"Azure_Event_Hub"->"Event_Logger";
|
||||||
"3aa1e546-1ed5-4d67-a59c-be0d5905b490"->"a268fae5-2a82-4a3e-ada7-a52eeb7019ac";
|
"Azure_Event_Hub";
|
||||||
"1d0255d4-5b8c-4a52-b0bb-ac024cda37e5";
|
"Event_Generator";
|
||||||
"3aa1e546-1ed5-4d67-a59c-be0d5905b490";
|
"Event_Logger";
|
||||||
"9e1bcb3d-ff58-41d4-8779-f71e7b8800f8";
|
|
||||||
"a268fae5-2a82-4a3e-ada7-a52eeb7019ac";
|
|
||||||
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -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.
|
Create a Helm chart named `http-demo` to be generated under ./output.
|
||||||
|
|
||||||
```bash
|
```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__
|
With __.tgz__
|
||||||
```bash
|
```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`
|
### abstrakt `validate`
|
||||||
|
@ -129,7 +129,7 @@ Global Flags:
|
||||||
|
|
||||||
#### Examples
|
#### Examples
|
||||||
|
|
||||||
Run visualise on a file
|
Run visualise on a file
|
||||||
|
|
||||||
abstrakt visualise -f basic_azure_event_hubs.yaml
|
abstrakt visualise -f basic_azure_event_hubs.yaml
|
||||||
digraph Azure_Event_Hubs_Sample {
|
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)
|
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:
|
Maps:
|
||||||
- ChartName: "sender"
|
- ChartName: "sender"
|
||||||
Type: "WormholeSender"
|
Type: "WormholeSender"
|
||||||
Location: "file://../../../sample/helm/http_sample/sender"
|
Location: "file://../../../examples/helm/http_sample/sender"
|
||||||
Version: "0.0.1"
|
Version: "0.0.1"
|
||||||
- ChartName: "receiver"
|
- ChartName: "receiver"
|
||||||
Type: "WormholeReceiver"
|
Type: "WormholeReceiver"
|
||||||
Location: "file://../../../sample/helm/http_sample/receiver"
|
Location: "file://../../../examples/helm/http_sample/receiver"
|
||||||
Version: "0.0.1"
|
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
|
appVersion: 1.16.0
|
||||||
dependencies:
|
dependencies:
|
||||||
- name: event_hub_sample_event_generator
|
- 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
|
version: 1.0.0
|
||||||
- name: event_hub_sample_event_hub
|
- 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
|
version: 1.0.0
|
||||||
- name: event_hub_sample_event_logger
|
- 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
|
version: 1.0.0
|
||||||
- alias: event_hub_sample_event_logger1
|
- alias: event_hub_sample_event_logger1
|
||||||
name: event_hub_sample_event_logger
|
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
|
version: 1.0.0
|
||||||
description: A Helm chart for Kubernetes
|
description: A Helm chart for Kubernetes
|
||||||
name: test
|
name: test
|
1
go.mod
1
go.mod
|
@ -19,6 +19,7 @@ require (
|
||||||
github.com/spf13/cobra v0.0.5
|
github.com/spf13/cobra v0.0.5
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/viper v1.4.0
|
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/crypto v0.0.0-20191028145041-f83a4685e152
|
||||||
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea // indirect
|
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // 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 (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/microsoft/abstrakt/internal/platform/chart"
|
||||||
"github.com/microsoft/abstrakt/internal/buildmapservice"
|
"github.com/microsoft/abstrakt/internal/platform/constellation"
|
||||||
"github.com/microsoft/abstrakt/internal/chartservice"
|
"github.com/microsoft/abstrakt/internal/platform/mapper"
|
||||||
"github.com/microsoft/abstrakt/internal/dagconfigservice"
|
helm "helm.sh/helm/v3/pkg/chart"
|
||||||
|
|
||||||
"helm.sh/helm/v3/pkg/chart"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//ComposeService takes maps and configs and builds out the helm chart
|
//Composer takes maps and configs and builds out the helm chart
|
||||||
type ComposeService struct {
|
type Composer struct {
|
||||||
DagConfigService dagconfigservice.DagConfigService
|
Constellation constellation.Config
|
||||||
BuildMapService buildmapservice.BuildMapService
|
Mapper mapper.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
//Compose takes the loaded DAG and maps and builds the Helm values and requirements documents
|
//Build 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) {
|
func (c *Composer) Build(name string, dir string) (*helm.Chart, error) {
|
||||||
if m.DagConfigService.Name == "" || m.BuildMapService.Name == "" {
|
if c.Constellation.Name == "" || c.Mapper.Name == "" {
|
||||||
return nil, errors.New("Please initialise with LoadFromFile or LoadFromString")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -31,12 +28,12 @@ func (m *ComposeService) Compose(name string, dir string) (*chart.Chart, error)
|
||||||
|
|
||||||
serviceMap := make(map[string]int)
|
serviceMap := make(map[string]int)
|
||||||
aliasMap := make(map[string]string)
|
aliasMap := make(map[string]string)
|
||||||
deps := make([]*chart.Dependency, 0)
|
deps := make([]*helm.Dependency, 0)
|
||||||
|
|
||||||
values := newChart.Values
|
values := newChart.Values
|
||||||
|
|
||||||
for _, n := range m.DagConfigService.Services {
|
for _, n := range c.Constellation.Services {
|
||||||
service := m.BuildMapService.FindByType(n.Type)
|
service := c.Mapper.FindByType(n.Type)
|
||||||
if service == nil {
|
if service == nil {
|
||||||
return nil, fmt.Errorf("Could not find service %v", service)
|
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]++
|
serviceMap[service.Type]++
|
||||||
|
|
||||||
dep := &chart.Dependency{
|
dep := &helm.Dependency{
|
||||||
Name: service.ChartName, Version: service.Version, Repository: service.Location,
|
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{})
|
relationships := make(map[string][]interface{})
|
||||||
valMap["relationships"] = &relationships
|
valMap["relationships"] = &relationships
|
||||||
toRels := m.DagConfigService.FindRelationshipByToName(n.ID)
|
toRels := c.Constellation.FindRelationshipByToName(n.ID)
|
||||||
fromRels := m.DagConfigService.FindRelationshipByFromName(n.ID)
|
fromRels := c.Constellation.FindRelationshipByFromName(n.ID)
|
||||||
|
|
||||||
for _, i := range toRels {
|
for _, i := range toRels {
|
||||||
toRelations := make(map[string]string)
|
toRelations := make(map[string]string)
|
||||||
relationships["input"] = append(relationships["input"], &toRelations)
|
relationships["input"] = append(relationships["input"], &toRelations)
|
||||||
|
|
||||||
//find the target service
|
//find the target service
|
||||||
foundService := m.DagConfigService.FindService(i.From)
|
foundService := c.Constellation.FindService(i.From)
|
||||||
|
|
||||||
if foundService == nil {
|
if foundService == nil {
|
||||||
return nil, fmt.Errorf("Service '%v' referenced in relationship '%v' not found", i.From, i.ID)
|
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)
|
relationships["output"] = append(relationships["output"], &fromRelations)
|
||||||
|
|
||||||
//find the target service
|
//find the target service
|
||||||
foundService := m.DagConfigService.FindService(i.To)
|
foundService := c.Constellation.FindService(i.To)
|
||||||
|
|
||||||
if foundService == nil {
|
if foundService == nil {
|
||||||
return nil, fmt.Errorf("Service '%v' referenced in relationship '%v' not found", i.To, i.ID)
|
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
|
newChart.Metadata.Dependencies = deps
|
||||||
|
|
||||||
return newChart, nil
|
return newChart, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//LoadFromFile takes a string dag and map and loads them
|
//LoadFile takes a string dag and map and loads them
|
||||||
func (m *ComposeService) LoadFromFile(dagFile string, mapFile string) (err error) {
|
func (c *Composer) LoadFile(dagFile string, mapFile string) (err error) {
|
||||||
err = m.DagConfigService.LoadDagConfigFromFile(dagFile)
|
err = c.Constellation.LoadFile(dagFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = m.BuildMapService.LoadMapFromFile(mapFile)
|
return c.Mapper.LoadFile(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
|
|
||||||
}
|
}
|
|
@ -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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -17,8 +17,8 @@ description: A Helm chart for Kubernetes
|
||||||
version: 4.3.2
|
version: 4.3.2
|
||||||
home: ""`
|
home: ""`
|
||||||
|
|
||||||
//CreateChart makes a new chart at the specified location
|
//Create makes a new chart at the specified location
|
||||||
func CreateChart(name string, dir string) (chartReturn *chart.Chart, err error) {
|
func Create(name string, dir string) (chartReturn *chart.Chart, err error) {
|
||||||
tdir, err := ioutil.TempDir("./", "output-")
|
tdir, err := ioutil.TempDir("./", "output-")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -77,8 +77,8 @@ func CreateChart(name string, dir string) (chartReturn *chart.Chart, err error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadChartFromDir loads a Helm chart from the specified director
|
// LoadFromDir loads a Helm chart from the specified director
|
||||||
func LoadChartFromDir(dir string) (*chart.Chart, error) {
|
func LoadFromDir(dir string) (*chart.Chart, error) {
|
||||||
h, err := loader.LoadDir(dir)
|
h, err := loader.LoadDir(dir)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -88,18 +88,18 @@ func LoadChartFromDir(dir string) (*chart.Chart, error) {
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveChartToDir takes the chart object and saves it as a set of files in the specified director
|
// SaveToDir takes the chart object and saves it as a set of files in the specified director
|
||||||
func SaveChartToDir(chart *chart.Chart, dir string) error {
|
func SaveToDir(chart *chart.Chart, dir string) error {
|
||||||
return chartutil.SaveDir(chart, dir)
|
return chartutil.SaveDir(chart, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ZipChartToDir compresses the chart and saves it in compiled format
|
// ZipToDir compresses the chart and saves it in compiled format
|
||||||
func ZipChartToDir(chart *chart.Chart, dir string) (string, error) {
|
func ZipToDir(chart *chart.Chart, dir string) (string, error) {
|
||||||
return chartutil.Save(chart, dir)
|
return chartutil.Save(chart, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildChart download charts
|
// Build download charts
|
||||||
func BuildChart(dir string) (out *bytes.Buffer, err error) {
|
func Build(dir string) (out *bytes.Buffer, err error) {
|
||||||
|
|
||||||
out = &bytes.Buffer{}
|
out = &bytes.Buffer{}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package chartservice
|
package chart_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
|
@ -6,6 +6,8 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"flag"
|
"flag"
|
||||||
|
"github.com/microsoft/abstrakt/internal/platform/chart"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -25,38 +27,38 @@ func TestUpdate(t *testing.T) {
|
||||||
|
|
||||||
err := os.RemoveAll("testdata/golden")
|
err := os.RemoveAll("testdata/golden")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.MkdirAll("testdata/golden/helm/charts", os.ModePerm)
|
err = os.MkdirAll("testdata/golden/helm/charts", os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
err = exec.Command("cp", "-r", "testdata/sample/helm", "testdata/golden/").Run()
|
err = exec.Command("cp", "-r", "testdata/sample/helm", "testdata/golden/").Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create zipped dependant charts
|
// 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()
|
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 {
|
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()
|
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 {
|
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()
|
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 {
|
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()
|
err = exec.Command("tar", "cfz", "testdata/golden/test-0.1.0.tgz", "testdata/sample/helm").Run()
|
||||||
if err != nil {
|
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-")
|
tdir2, err2 := ioutil.TempDir("./", "output-")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
t.Fatal(err2)
|
assert.FailNow(t, err2.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
err = os.RemoveAll(tdir)
|
err = os.RemoveAll(tdir)
|
||||||
if err != nil {
|
assert.NoError(t, err)
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
err = os.RemoveAll(tdir2)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
err = os.RemoveAll(tdir2)
|
||||||
|
assert.NoError(t, err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
c, err := CreateChart("foo", tdir)
|
c, err := chart.Create("foo", tdir)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
err = SaveChartToDir(c, tdir2)
|
err = chart.SaveToDir(c, tdir2)
|
||||||
if err != nil {
|
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")
|
newPath := filepath.Join(tdir2, "foo")
|
||||||
|
|
||||||
_, err = LoadChartFromDir(newPath)
|
_, err = chart.LoadFromDir(newPath)
|
||||||
|
|
||||||
if err != nil {
|
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) {
|
func TestChartBuildChart(t *testing.T) {
|
||||||
tdir, err := ioutil.TempDir("./", "output-")
|
tdir, err := ioutil.TempDir("./", "output-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
err = os.RemoveAll(tdir)
|
err = os.RemoveAll(tdir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = exec.Command("cp", "-r", "testdata/sample/helm", tdir+"/").Run()
|
err = exec.Command("cp", "-r", "testdata/sample/helm", tdir+"/").Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
err = exec.Command("cp", "-r", "testdata/sample/deps", tdir+"/").Run()
|
err = exec.Command("cp", "-r", "testdata/sample/deps", tdir+"/").Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = BuildChart(tdir + "/helm")
|
_, err = chart.Build(tdir + "/helm")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to BuildChart(): %s", err)
|
assert.FailNowf(t, "Failed to BuildChart(): %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
chartsDir := tdir + "/helm/charts/"
|
chartsDir := tdir + "/helm/charts/"
|
||||||
|
@ -142,23 +140,23 @@ func TestChartBuildChart(t *testing.T) {
|
||||||
func TestZipChartToDir(t *testing.T) {
|
func TestZipChartToDir(t *testing.T) {
|
||||||
tdir, err := ioutil.TempDir("./", "output-")
|
tdir, err := ioutil.TempDir("./", "output-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
err = os.RemoveAll(tdir)
|
err = os.RemoveAll(tdir)
|
||||||
if err != nil {
|
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 {
|
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 {
|
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")
|
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) {
|
func readGz(t *testing.T, file string) (out bytes.Buffer) {
|
||||||
f, err := os.Open(file)
|
f, err := os.Open(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
err = f.Close()
|
err = f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
zw, err := gzip.NewReader(f)
|
zw, err := gzip.NewReader(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
err = zw.Close()
|
err = zw.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
writer := bufio.NewWriter(&out)
|
writer := bufio.NewWriter(&out)
|
||||||
_, err = io.Copy(writer, zw)
|
_, err = io.Copy(writer, zw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
assert.FailNow(t, err.Error())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче