Merge pull request #61 from microsoft/feat/50

Refactor Project and Clean-up Repo
This commit is contained in:
Jordan Knight 2020-02-10 14:39:06 +11:00 коммит произвёл GitHub
Родитель 21ebb91744 45f20493c7
Коммит 20515f7b78
180 изменённых файлов: 2605 добавлений и 2033 удалений

Просмотреть файл

@ -3,7 +3,7 @@
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#-------------------------------------------------------------------------------------------------------------
FROM golang:1.13.1
FROM golang:1.13.1-stretch
# Avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive
@ -18,6 +18,7 @@ RUN apt-get update \
&& go get -x -d github.com/stamblerre/gocode 2>&1 \
&& go build -o gocode-gomod github.com/stamblerre/gocode \
&& mv gocode-gomod $GOPATH/bin/ \
&& GO111MODULE=on go get -v golang.org/x/tools/gopls@latest \
&& go get -u -v \
github.com/google/wire/cmd/wire \
github.com/rakyll/gotest \
@ -38,18 +39,16 @@ RUN apt-get update \
github.com/cweill/gotests/... \
golang.org/x/tools/cmd/goimports \
golang.org/x/lint/golint \
golang.org/x/tools/cmd/gopls \
github.com/alecthomas/gometalinter \
honnef.co/go/tools/... \
github.com/mgechev/revive \
github.com/derekparker/delve/cmd/dlv 2>&1 \
&& GO111MODULE=on go get golang.org/x/tools/gopls@latest \
&& curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.21.0 \
&& curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.23.1 \
&& apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
RUN apt-get update \
# Install Docker CE CLI
&& apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common lsb-release \

Просмотреть файл

@ -2,21 +2,23 @@ run:
deadline: 5m
skip-files: []
linters-settings:
linters-settings.govet:
govet:
check-shadowing: true
linters-settings.gocyclo:
gocyclo:
min-complexity: 12.0
linters-settings.maligned:
maligned:
suggest-new: true
linters-settings.goconst:
goconst:
min-len: 3.0
min-occurrences: 3.0
linters-settings.misspell:
locale: "US"
misspell:
locale: "UK"
ignore-words:
- listend
- analyses
- cancelling
- color
- colors
linters:
enable:
- vet

Просмотреть файл

@ -32,7 +32,7 @@ With everything installed and running, you can continue.
You can find sample constellation files in the following location:
`/sample/constellation/`
`/examples/constellation/`
Using these files you can test the app binary.

Просмотреть файл

@ -1,4 +1,5 @@
trigger:
branches:
include:
- master
@ -15,11 +16,15 @@ variables:
steps:
- task: GoTool@0
displayName: 'Using Go v1.13'
displayName: 'Using Go v1.13.1'
inputs:
version: '1.13'
GOPATH: '$(GOPATH)'
GOBIN: '$(GOBIN)'
version: '1.13.1'
goPath: '$(GOPATH)'
goBin: '$(GOBIN)'
- bash: |
echo "##vso[task.setvariable variable=PATH]${PATH}:$(go env GOPATH)/bin"
displayName: 'Exporting GOPATH to PATH'
- task: Go@0
displayName: 'Resolving Dependencies'
@ -38,14 +43,14 @@ steps:
make lint-all
displayName: 'Lint Abstrakt'
workingDirectory: '$(System.DefaultWorkingDirectory)'
env: { GOPTH: '$(GOPATH)' }
env: { GOPATH: '$(GOPATH)' }
failOnStderr: true
- script: |
make test-export-all
displayName: 'Test Abstrakt'
workingDirectory: '$(System.DefaultWorkingDirectory)'
env: { GOPTH: '$(GOPATH)' }
env: { GOPATH: '$(GOPATH)' }
- task: PublishTestResults@2
inputs:

Просмотреть файл

@ -1,7 +1,7 @@
package cmd
import (
"github.com/microsoft/abstrakt/internal/tools/logger"
"github.com/microsoft/abstrakt/tools/logger"
cobra "github.com/spf13/cobra"
)

Просмотреть файл

@ -2,9 +2,9 @@ package cmd
import (
"fmt"
"github.com/microsoft/abstrakt/internal/chartservice"
"github.com/microsoft/abstrakt/internal/composeservice"
"github.com/microsoft/abstrakt/internal/tools/logger"
"github.com/microsoft/abstrakt/internal/compose"
"github.com/microsoft/abstrakt/internal/platform/chart"
"github.com/microsoft/abstrakt/tools/logger"
"github.com/spf13/cobra"
"path"
"strings"
@ -43,15 +43,19 @@ Example: abstrakt compose [chart name] -t [templateType] -f [constellationFilePa
return fmt.Errorf("Template type: %v is not known", cc.templateType)
}
service := composeservice.NewComposeService()
_ = service.LoadFromFile(cc.constellationFilePath, cc.mapsFilePath)
service := new(compose.Composer)
err = service.LoadFile(cc.constellationFilePath, cc.mapsFilePath)
if err != nil {
return
}
logger.Debugf("noChecks is set to %t", *cc.noChecks)
if !*cc.noChecks {
logger.Debug("Starting validating constellation")
err = validateDag(&service.DagConfigService)
err = validateDag(&service.Constellation)
if err != nil {
return
@ -60,12 +64,12 @@ Example: abstrakt compose [chart name] -t [templateType] -f [constellationFilePa
logger.Debug("Finished validating constellation")
}
chart, err := service.Compose(chartName, cc.outputPath)
helm, err := service.Build(chartName, cc.outputPath)
if err != nil {
return fmt.Errorf("Could not compose: %v", err)
}
err = chartservice.SaveChartToDir(chart, cc.outputPath)
err = chart.SaveToDir(helm, cc.outputPath)
if err != nil {
return fmt.Errorf("There was an error saving the chart: %v", err)
@ -73,14 +77,14 @@ Example: abstrakt compose [chart name] -t [templateType] -f [constellationFilePa
logger.Infof("Chart was saved to: %v", cc.outputPath)
out, err := chartservice.BuildChart(path.Join(cc.outputPath, chartName))
out, err := chart.Build(path.Join(cc.outputPath, chartName))
if err != nil {
return fmt.Errorf("There was an error saving the chart: %v", err)
}
if *cc.zipChart {
_, err = chartservice.ZipChartToDir(chart, cc.outputPath)
_, err = chart.ZipToDir(helm, cc.outputPath)
if err != nil {
return fmt.Errorf("There was an error zipping the chart: %v", err)
}

Просмотреть файл

@ -1,134 +1,67 @@
package cmd
import (
"bytes"
"fmt"
"github.com/spf13/cobra"
"io/ioutil"
"os"
"path"
"strings"
helper "github.com/microsoft/abstrakt/tools/test"
"github.com/stretchr/testify/assert"
"testing"
)
func executeCommand(root *cobra.Command, args ...string) (output string, err error) {
_, output, err = executeCommandC(root, args...)
return output, err
}
func executeCommandC(root *cobra.Command, args ...string) (c *cobra.Command, output string, err error) {
buf := new(bytes.Buffer)
root.SetOutput(buf)
root.SetArgs(args)
c, err = root.ExecuteC()
return
}
func TestComposeCommandReturnsErrorIfTemplateTypeIsInvalid(t *testing.T) {
templateType := "ble"
constellationPath, mapsPath, tdir := PrepareRealFilesForTest(t)
constellationPath, mapsPath, tdir := helper.PrepareRealFilesForTest(t)
defer CleanTempTestFiles(t, tdir)
defer helper.CleanTempTestFiles(t, tdir)
output, err := executeCommand(newComposeCmd().cmd, "test-compose-cmd-returns-error-if-template-type-is-invalid", "-f", constellationPath, "-m", mapsPath, "-t", templateType, "-o", tdir)
if err == nil {
t.Errorf("Did not received expected error. \nGot:\n %v", output)
}
output, err := helper.ExecuteCommand(newComposeCmd().cmd, "test-compose-cmd-returns-error-if-template-type-is-invalid", "-f", constellationPath, "-m", mapsPath, "-t", templateType, "-o", tdir)
assert.Errorf(t, err, "Did not received expected error. \nGot:\n %v", output)
}
func TestComposeCommandDoesNotErrorIfTemplateTypeIsEmptyOrHelm(t *testing.T) {
templateType := ""
constellationPath, mapsPath, tdir := PrepareRealFilesForTest(t)
constellationPath, mapsPath, tdir := helper.PrepareRealFilesForTest(t)
defer CleanTempTestFiles(t, tdir)
defer helper.CleanTempTestFiles(t, tdir)
output, err := executeCommand(newComposeCmd().cmd, "test-compose-cmd-does-not-error-if-template-type-is-empty-or-helm", "-f", constellationPath, "-m", mapsPath, "-t", templateType, "-o", tdir)
output, err := helper.ExecuteCommand(newComposeCmd().cmd, "test-compose-cmd-does-not-error-if-template-type-is-empty-or-helm", "-f", constellationPath, "-m", mapsPath, "-t", templateType, "-o", tdir)
assert.NoErrorf(t, err, "Did not expect error:\n %v\n output: %v", err, output)
if err != nil {
t.Errorf("Did not expect error:\n %v\n output: %v", err, output)
}
templateType = "helm"
output, err = executeCommand(newComposeCmd().cmd, "test-compose-cmd-does-not-error-if-template-type-is-empty-or-helm", "-f", constellationPath, "-m", mapsPath, "-t", templateType, "-o", tdir)
if err != nil {
t.Errorf("Did not expect error:\n %v\n output: %v", err, output)
}
output, err = helper.ExecuteCommand(newComposeCmd().cmd, "test-compose-cmd-does-not-error-if-template-type-is-empty-or-helm", "-f", constellationPath, "-m", mapsPath, "-t", templateType, "-o", tdir)
assert.NoErrorf(t, err, "Did not expect error:\n %v\n output: %v", err, output)
}
func TestComposeCommandReturnsErrorWithInvalidFilePaths(t *testing.T) {
output, err := executeCommand(newComposeCmd().cmd, "test-compose-cmd-returns-error-with-invalid-files", "-f", "invalid", "-m", "invalid", "-o", "invalid")
if err == nil {
t.Errorf("Did not received expected error. \nGot:\n %v", output)
}
output, err := helper.ExecuteCommand(newComposeCmd().cmd, "test-compose-cmd-returns-error-with-invalid-files", "-f", "invalid", "-m", "invalid", "-o", "invalid")
assert.Errorf(t, err, "Did not received expected error. \nGot:\n %v", output)
}
// TODO bug #43: figure out how to make this test work reliably.
// Something weird is making this test fail when run along with other tests in the package.
// It passes whenever it runs on it's own.
func TestComposeCmdVerifyRequiredFlags(t *testing.T) {
expected := "required flag(s) \"constellationFilePath\", \"mapsFilePath\", \"outputPath\" not set"
output, err := executeCommand(newComposeCmd().cmd, "")
output, err := helper.ExecuteCommand(newComposeCmd().cmd, "")
if err != nil {
checkStringContains(t, err.Error(), expected)
assert.Contains(t, err.Error(), expected)
} else {
t.Errorf("Expecting error: \n %v\nGot:\n %v\n", expected, output)
}
}
func checkStringContains(t *testing.T, got, expected string) {
if !strings.Contains(got, expected) {
t.Errorf("Expected to contain: \n %v\nGot:\n %v\n", expected, got)
}
}
func TestComposeCmdWithValidFlags(t *testing.T) {
constellationPath, mapsPath, tdir := PrepareRealFilesForTest(t)
constellationPath, mapsPath, tdir := helper.PrepareRealFilesForTest(t)
defer CleanTempTestFiles(t, tdir)
defer helper.CleanTempTestFiles(t, tdir)
output, err := executeCommand(newComposeCmd().cmd, "test-compose-cmd-with-flags", "-f", constellationPath, "-m", mapsPath, "-o", tdir)
if err != nil {
t.Errorf("error: \n %v\noutput:\n %v\n", err, output)
}
output, err := helper.ExecuteCommand(newComposeCmd().cmd, "test-compose-cmd-with-flags", "-f", constellationPath, "-m", mapsPath, "-o", tdir)
assert.NoErrorf(t, err, "error: \n %v\noutput:\n %v\n", err, output)
}
func TestComposeWithRealFiles(t *testing.T) {
constellationPath, mapsPath, tdir := PrepareRealFilesForTest(t)
constellationPath, mapsPath, tdir := helper.PrepareRealFilesForTest(t)
defer CleanTempTestFiles(t, tdir)
output, err := executeCommand(newComposeCmd().cmd, "test-compose-cmd-with-real-files", "-f", constellationPath, "-m", mapsPath, "-o", tdir)
if err != nil {
t.Errorf("error: \n %v\noutput:\n %v\n", err, output)
}
defer helper.CleanTempTestFiles(t, tdir)
}
func PrepareRealFilesForTest(t *testing.T) (string, string, string) {
tdir, err := ioutil.TempDir("./", "output-")
if err != nil {
t.Fatal(err)
}
cwd, err2 := os.Getwd()
if err2 != nil {
t.Fatal(err2)
}
fmt.Print(cwd)
constellationPath := path.Join(cwd, "../sample/constellation/sample_constellation.yaml")
mapsPath := path.Join(cwd, "../sample/constellation/sample_constellation_maps.yaml")
return constellationPath, mapsPath, tdir
}
func CleanTempTestFiles(t *testing.T, temp string) {
err := os.RemoveAll(temp)
if err != nil {
t.Fatal(err)
}
output, err := helper.ExecuteCommand(newComposeCmd().cmd, "test-compose-cmd-with-real-files", "-f", constellationPath, "-m", mapsPath, "-o", tdir)
assert.NoErrorf(t, err, "error: \n %v\noutput:\n %v\n", err, output)
}

Просмотреть файл

@ -1,38 +1,23 @@
package cmd
// visualise is a subcommand that constructs a graph representation of the yaml
// input file and renders this into GraphViz 'dot' notation.
// Initial version renders to dot syntax only, to graphically depict this the output
// has to be run through a graphviz visualisation tool/utiliyy
import (
"bytes"
"fmt"
"github.com/awalterschulze/gographviz"
set "github.com/deckarep/golang-set"
"github.com/microsoft/abstrakt/internal/dagconfigservice"
"github.com/microsoft/abstrakt/internal/tools/logger"
"github.com/microsoft/abstrakt/internal/diff"
"github.com/microsoft/abstrakt/internal/platform/constellation"
"github.com/microsoft/abstrakt/tools/logger"
"github.com/spf13/cobra"
// "os"
"strings"
)
type diffCmd struct {
constellationFilePathOrg string
constellationFilePathNew string
showOriginal bool
showNew bool
showOriginal *bool
showNew *bool
*baseCmd
}
type setsForComparison struct {
setCommonSvcs set.Set
setCommonRels set.Set
setAddedSvcs set.Set
setAddedRels set.Set
setDelSvcs set.Set
setDelRels set.Set
}
func newDiffCmd() *diffCmd {
cc := &diffCmd{}
@ -42,62 +27,57 @@ func newDiffCmd() *diffCmd {
Long: `Diff is for producing a Graphviz dot notation representation of the difference between two constellations (line an old and new version)
Example: abstrakt diff -o [constellationFilePathOriginal] -n [constellationFilePathNew]`,
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
logger.Debug("args: " + strings.Join(args, " "))
logger.Debug("constellationFilePathOrg: " + cc.constellationFilePathOrg)
logger.Debug("constellationFilePathNew: " + cc.constellationFilePathNew)
logger.Debugf("constellationFilePathOrg: %v", cc.constellationFilePathOrg)
logger.Debugf("constellationFilePathNew: %v", cc.constellationFilePathNew)
if cmd.Flag("showOriginalOutput").Value.String() == "true" {
logger.Debug("showOriginalOutput: true")
cc.showOriginal = true
} else {
logger.Debug("showOriginalOutput: false")
cc.showOriginal = false
}
if cmd.Flag("showNewOutput").Value.String() == "true" {
logger.Debug("showNewOutput: true")
cc.showNew = true
} else {
logger.Debug("showNewOutput: false")
cc.showNew = false
}
logger.Debugf("showOriginalOutput: %t", *cc.showOriginal)
logger.Debugf("showNewOutput: %t", *cc.showNew)
if !fileExists(cc.constellationFilePathOrg) {
return fmt.Errorf("Could not open original YAML input file for reading %v", cc.constellationFilePathOrg)
}
if !fileExists(cc.constellationFilePathNew) {
return fmt.Errorf("Could not open new YAML input file for reading %v", cc.constellationFilePathNew)
}
dsGraphOrg := dagconfigservice.NewDagConfigService()
err := dsGraphOrg.LoadDagConfigFromFile(cc.constellationFilePathOrg)
dsGraphOrg := new(constellation.Config)
err := dsGraphOrg.LoadFile(cc.constellationFilePathOrg)
if err != nil {
return fmt.Errorf("dagConfigService failed to load file %q: %s", cc.constellationFilePathOrg, err)
return fmt.Errorf("Constellation config failed to load file %q: %s", cc.constellationFilePathOrg, err)
}
if cc.showOriginal {
resStringOrg := generateGraph(dsGraphOrg)
if *cc.showOriginal {
out := &bytes.Buffer{}
var resStringOrg string
resStringOrg, err = dsGraphOrg.GenerateGraph(out)
if err != nil {
return err
}
logger.Output(resStringOrg)
}
dsGraphNew := dagconfigservice.NewDagConfigService()
err = dsGraphNew.LoadDagConfigFromFile(cc.constellationFilePathNew)
dsGraphNew := new(constellation.Config)
err = dsGraphNew.LoadFile(cc.constellationFilePathNew)
if err != nil {
return fmt.Errorf("dagConfigService failed to load file %q: %s", cc.constellationFilePathNew, err)
return fmt.Errorf("Constellation config failed to load file %q: %s", cc.constellationFilePathNew, err)
}
if cc.showNew {
resStringNew := generateGraph(dsGraphNew)
if *cc.showNew {
out := &bytes.Buffer{}
var resStringNew string
resStringNew, err = dsGraphNew.GenerateGraph(out)
if err != nil {
return err
}
logger.Output(resStringNew)
}
resStringDiff := compareConstellations(dsGraphOrg, dsGraphNew)
// logger.Output is also outputting a timestamp and 'level' message when used on the command line which causes
// graphviz to fail !?
constellationSets := diff.Compare{Original: dsGraphOrg, New: dsGraphNew}
resStringDiff, err := constellationSets.CompareConstellations()
if err != nil {
return err
}
logger.Output(resStringDiff)
// fmt.Println(resStringDiff)
return nil
},
@ -105,196 +85,10 @@ Example: abstrakt diff -o [constellationFilePathOriginal] -n [constellationFileP
cc.cmd.Flags().StringVarP(&cc.constellationFilePathOrg, "constellationFilePathOriginal", "o", "", "original or base constellation file path")
cc.cmd.Flags().StringVarP(&cc.constellationFilePathNew, "constellationFilePathNew", "n", "", "new or changed constellation file path")
cc.cmd.Flags().Bool("showOriginalOutput", false, "will additionally produce dot notation for original constellation")
cc.cmd.Flags().Bool("showNewOutput", false, "will additionally produce dot notation for new constellation")
cc.showOriginal = cc.cmd.Flags().Bool("showOriginalOutput", false, "will additionally produce dot notation for original constellation")
cc.showNew = cc.cmd.Flags().Bool("showNewOutput", false, "will additionally produce dot notation for new constellation")
_ = cc.cmd.MarkFlagRequired("constellationFilePathOriginal")
_ = cc.cmd.MarkFlagRequired("constellationFilePathNew")
return cc
}
//compareConstellations
func compareConstellations(dsOrg dagconfigservice.DagConfigService, dsNew dagconfigservice.DagConfigService) string {
sets := &setsForComparison{}
// populate comparison sets with changes between original and new graph
fillComparisonSets(dsOrg, dsNew, sets)
// build the graphviz output from new graph and comparison sets
resString := createGraphWithChanges(dsNew, sets)
return resString
}
// fillComparisonSets - loads provided set struct with data from the constellations and then determines the various differences between the
// sets (original constellation and new) to help detemine what has been added, removed or changed.
func fillComparisonSets(dsOrg dagconfigservice.DagConfigService, dsNew dagconfigservice.DagConfigService, sets *setsForComparison) {
setOrgSvcs, setOrgRel := createSet(dsOrg)
setNewSvcs, setNewRel := createSet(dsNew)
// "Added" means in the new constellation but not the original one
// "Deleted" means something is present in the original constellation but not in the new constellation
// Services
sets.setCommonSvcs = setOrgSvcs.Intersect(setNewSvcs) //present in both
sets.setAddedSvcs = setNewSvcs.Difference(setOrgSvcs) //pressent in new but not in original
sets.setDelSvcs = setOrgSvcs.Difference(setNewSvcs) //present in original but not in new
// Relationships
sets.setCommonRels = setOrgRel.Intersect(setNewRel) //present in both
sets.setAddedRels = setNewRel.Difference(setOrgRel) //pressent in new but not in original
sets.setDelRels = setOrgRel.Difference(setNewRel) //present in original but not in new
}
// createSet - utility function used to create a pair of result sets (services + relationships) based on an input constellation DAG
func createSet(dsGraph dagconfigservice.DagConfigService) (set.Set, set.Set) {
// Create sets to hold services and relationships - used to find differences between old and new using intersection and difference operations
retSetServices := set.NewSet()
retSetRelationships := set.NewSet()
//Store all services in the services set
for _, v := range dsGraph.Services {
retSetServices.Add(v.ID)
}
//Store relationships in the relationship set
for _, v := range dsGraph.Relationships {
retSetRelationships.Add(v.From + "|" + v.To)
}
return retSetServices, retSetRelationships
}
// createGraphWithChanges - use both input constellations (new and original) as well as the comparison sets to create
// a dag that can be visualised. It uses the comparison sets to identify additions, deletions and changes between the original
// and new constellations.
func createGraphWithChanges(newGraph dagconfigservice.DagConfigService, sets *setsForComparison) string {
// Lookup is used to map IDs to names. Names are easier to visualise but IDs are more important to ensure the
// presented constellation is correct and IDs are used to link nodes together
lookup := make(map[string]string)
g := gographviz.NewGraph()
// Replace spaces with underscores, names with spaces can break graphviz engines
if err := g.SetName(strings.Replace(newGraph.Name, " ", "_", -1) + "_diff"); err != nil {
logger.Fatalf("error setting graph name: %v", err)
}
// Attribute in graphviz to change graph orientation - LR indicates Left to Right. Default is top to bottom
if err := g.AddAttr(g.Name, "rankdir", "LR"); err != nil {
logger.Fatalf("error adding node: %v", err)
}
// Make the graph directed (a constellation is DAG)
if err := g.SetDir(true); err != nil {
logger.Fatalf("error: %v", err)
}
// Add all services from the new constellation
// - New services - highlight with colour (i.e in setAddedSvcs)
// - Deleted services (i.e. in setDelSvcs) - include and format appropriately
for _, v := range newGraph.Services {
logger.Debugf("Adding node %s", v.ID)
newName := strings.Replace(v.ID, " ", "_", -1) // Replace spaces in names with underscores, names with spaces can break graphviz engines)
// attributes are graphviz specific that control formatting. The linrary used requires then in the form of a map passed as an argument so it needs
// to be built before adding the item
attrs := make(map[string]string)
// Check if the service is new to this constellation
if sets.setAddedSvcs.Contains(newName) {
attrs["color"] = "\"#d8ffa8\""
}
attrs["label"] = "\"" + v.Type + "\n" + v.ID + "\""
fillShapeAndStyleForNodeType(v.Type, attrs)
lookup[v.ID] = newName
err := g.AddNode(newGraph.Name, "\""+newName+"\"", attrs) //Surround names/labels with quotes, stops graphviz seeing special characters and breaking
if err != nil {
logger.Fatalf("error: %v", err)
}
}
//======================================== process deleted services ==========================================
//Process services that have been removed from the original constellation
for v := range sets.setDelSvcs.Iter() {
logger.Debug(v)
vString, _ := v.(string)
newName := strings.Replace(vString, " ", "_", -1)
attrs := make(map[string]string)
attrs["color"] = "\"#ff9494\""
fillShapeAndStyleForNodeType("", attrs)
logger.Debug("Adding deleted service ", newName)
if err := g.AddNode(newGraph.Name, "\""+newName+"\"", attrs); err != nil {
logger.Fatalf("error adding node: %v", err)
}
}
//======================================== process relationships ==========================================
// Add Relationships from the new constellation
// - New relationships - highlight with colour (i.e. in setAddedSvcs)
// - Deleted relationships (i.e. in setDelRels) - include and format appropriately
for _, v := range newGraph.Relationships {
logger.Debugf("Adding relationship from %s ---> %s", v.From, v.To)
//Surround names/labels with quotes, stops graphviz seeing special characters and breaking
localFrom := "\"" + lookup[v.From] + "\""
localTo := "\"" + lookup[v.To] + "\""
attrs := make(map[string]string)
// Relationship is stored in the map as source|destination - both are needed to tell if a relationship is new (this is how they are stored in the sets created earlier)
relLookupName := lookup[v.From] + "|" + lookup[v.To]
// Check if the relationship is new (it will then not be in the common list of relationships between old and new constellation)
if sets.setAddedRels.Contains(relLookupName) {
attrs["color"] = "\"#d8ffa8\""
}
err := g.AddEdge(localFrom, localTo, true, attrs)
if err != nil {
logger.Fatalf("error: %v", err)
}
}
//======================================== process deleted relationships =====================================
// Deleted relationships
for v := range sets.setDelRels.Iter() {
logger.Debug(v)
vString := strings.Replace(v.(string), " ", "_", -1)
if len(strings.Split(vString, "|")) != 2 {
logger.Fatal("Relationships string should be two items separated by | but got ", vString)
}
newFrom := "\"" + strings.Split(vString, "|")[0] + "\""
newTo := "\"" + strings.Split(vString, "|")[1] + "\""
attrs := make(map[string]string)
attrs["color"] = "\"#ff9494\""
logger.Debug("Adding deleted service relationship from ", newFrom, " to ", newTo)
err := g.AddEdge(newFrom, newTo, true, attrs)
if err != nil {
logger.Fatalf("error: %v", err)
}
}
// Produce resulting graph in dot notation format
return g.String()
}
// Populate attrs map with additional attributes based on the node type
// Easier to change in a single place
func fillShapeAndStyleForNodeType(nodeType string, existAttrs map[string]string) {
switch nodeType {
case "EventLogger":
existAttrs["shape"] = "rectangle"
existAttrs["style"] = "\"rounded, filled\""
case "EventGenerator":
existAttrs["shape"] = "rectangle"
existAttrs["style"] = "\"rounded, filled\""
case "EventHub":
existAttrs["shape"] = "rectangle"
existAttrs["style"] = "\"rounded, filled\""
default:
existAttrs["shape"] = "rectangle"
existAttrs["style"] = "\"rounded, filled\""
}
}

Просмотреть файл

@ -1,341 +1,58 @@
package cmd
import (
"fmt"
set "github.com/deckarep/golang-set"
"github.com/microsoft/abstrakt/internal/dagconfigservice"
helper "github.com/microsoft/abstrakt/tools/test"
"github.com/sirupsen/logrus/hooks/test"
"io/ioutil"
"os"
"path"
"strings"
"github.com/stretchr/testify/assert"
"testing"
)
// TestDiffCmdWithAllRequirementsNoError - test diff command parameters
// use valid arguments so expect no failures
func TestDiffCmdWithAllRequirementsNoError(t *testing.T) {
constellationPathOrg, constellationPathNew, _, _ := localPrepareRealFilesForTest(t)
constellationPathOrg, constellationPathNew, _, _ := helper.PrepareTwoRealConstellationFilesForTest(t)
hook := test.NewGlobal()
_, err := executeCommand(newDiffCmd().cmd, "-o", constellationPathOrg, "-n", constellationPathNew)
// fmt.Println(hook.LastEntry().Message)
// fmt.Println(testDiffComparisonOutputString)
if err != nil {
t.Error("Did not receive output")
} else {
if !compareGraphOutputAsSets(testDiffComparisonOutputString, hook.LastEntry().Message) {
t.Errorf("Expcted output and produced output do not match : expected %s produced %s", testDiffComparisonOutputString, hook.LastEntry().Message)
}
// Did use this initially but wont work with the strongs output from the graphviz library as the sequence of entries in the output can change
// while the sequence may change the result is still valid and the same so am usinga local comparison function to get around this problem
// checkStringContains(t, hook.LastEntry().Message, testDiffComparisonOutputString)
}
}
// localPrepareRealFilesForTest - global function assumes only a single input file, this use the two required for the diff command
func localPrepareRealFilesForTest(t *testing.T) (string, string, string, string) {
tdir, err := ioutil.TempDir("./", "output-")
if err != nil {
t.Fatal(err)
}
defer func() {
err = os.RemoveAll(tdir)
if err != nil {
t.Fatal(err)
}
}()
cwd, err2 := os.Getwd()
if err2 != nil {
t.Fatal(err2)
}
fmt.Print(cwd)
constellationPathOrg := path.Join(cwd, "../sample/constellation/sample_constellation.yaml")
constellationPathNew := path.Join(cwd, "../sample/constellation/sample_constellation_changed.yaml")
mapsPath := path.Join(cwd, "../sample/constellation/sample_constellation_maps.yaml")
return constellationPathOrg, constellationPathNew, mapsPath, tdir
}
// compareGraphOutputAsSets - the graphviz library does not always output the result string with nodes and edges
// in the same order (it can vary between calls). This does not impact using the result but makes testing the result a
// headache as the assumption is that the expected string and the produced string would match exactly. When the sequence
// changes they dont match. This function converts the strings into sets of lines and compares if the lines in the two outputs
// are the same
func compareGraphOutputAsSets(expected, produced string) bool {
lstExpected := strings.Split(expected, "\n")
lstProduced := strings.Split(produced, "\n")
setExpected := set.NewSet()
setProduced := set.NewSet()
for l := range lstExpected {
setExpected.Add(l)
}
for l := range lstProduced {
setProduced.Add(l)
}
return setProduced.Equal(setExpected)
_, err := helper.ExecuteCommand(newDiffCmd().cmd, "-o", constellationPathOrg, "-n", constellationPathNew)
assert.NoError(t, err)
assert.True(t, helper.CompareGraphOutputAsSets(testDiffComparisonOutputString, hook.LastEntry().Message))
}
// TestDffCmdFailYaml - test diff command parameters
// Test both required command line parameters (-o, -n) failing each in turn
func TestDffCmdFailYaml(t *testing.T) {
expected := "Could not open original YAML input file for reading constellationPathOrg"
expected := "Constellation config failed to load file \"constellationPathOrg\": open constellationPathOrg: no such file or directory"
output, err := executeCommand(newDiffCmd().cmd, "-o", "constellationPathOrg", "-n", "constellationPathNew")
_, err := helper.ExecuteCommand(newDiffCmd().cmd, "-o", "constellationPathOrg", "-n", "constellationPathNew")
if err != nil {
checkStringContains(t, err.Error(), expected)
} else {
t.Errorf("Did not fail and it should have. Expected: %v \nGot: %v", expected, output)
}
assert.Error(t, err)
assert.EqualError(t, err, expected)
expected = "Could not open new YAML input file for reading constellationPathNew"
expected = "Constellation config failed to load file \"constellationPathNew\": open constellationPathNew: no such file or directory"
output, err = executeCommand(newDiffCmd().cmd, "-o", "../sample/constellation/sample_constellation.yaml", "-n", "constellationPathNew")
if err != nil {
checkStringContains(t, err.Error(), expected)
} else {
t.Errorf("Did not fail and it should have. Expected: %v \nGot: %v", expected, output)
}
_, err = helper.ExecuteCommand(newDiffCmd().cmd, "-o", "../examples/constellation/sample_constellation.yaml", "-n", "constellationPathNew")
assert.Error(t, err)
assert.EqualError(t, err, expected)
}
// TestDiffCmdFailNotYaml - test diff command parameters
// Test both required command line parameter files fail when provided with invalid input files (-o, -n) failing each in turn
func TestDiffCmdFailNotYaml(t *testing.T) {
expected := "dagConfigService failed to load file"
expected := "Constellation config failed to load file \"diff.go\": yaml: line 25: mapping values are not allowed in this context"
output, err := executeCommand(newDiffCmd().cmd, "-o", "diff.go", "-n", "diff.go")
_, err := helper.ExecuteCommand(newDiffCmd().cmd, "-o", "diff.go", "-n", "diff.go")
if err != nil {
checkStringContains(t, err.Error(), expected)
} else {
t.Errorf("Did not fail. Expected: %v \nGot: %v", expected, output)
}
assert.Error(t, err)
assert.EqualError(t, err, expected)
output, err = executeCommand(newDiffCmd().cmd, "-o", "../sample/constellation/sample_constellation.yaml", "-n", "diff.go")
_, err = helper.ExecuteCommand(newDiffCmd().cmd, "-o", "../examples/constellation/sample_constellation.yaml", "-n", "diff.go")
if err != nil {
checkStringContains(t, err.Error(), expected)
} else {
t.Errorf("Did not fail. Expected: %v \nGot: %v", expected, output)
}
assert.Error(t, err)
assert.EqualError(t, err, expected)
}
// TestGetComparisonSets - generate sets of common, added and removed services and relationships from input YAML
// and compare to manually created sets with a known or expected outcome
func TestGetComparisonSets(t *testing.T) {
dsGraphOrg := dagconfigservice.NewDagConfigService()
err := dsGraphOrg.LoadDagConfigFromString(testOrgDagStr)
if err != nil {
t.Errorf("dagConfigService failed to load dag from test string %s", err)
}
dsGraphNew := dagconfigservice.NewDagConfigService()
err = dsGraphNew.LoadDagConfigFromString(testNewDagStr)
if err != nil {
t.Errorf("dagConfigService failed to load file %s", err)
}
//construct sets struct for loaded constellation
loadedSets := &setsForComparison{}
// will populate with expected/known outcomes
knownSets := &setsForComparison{}
populateComparisonSets(knownSets)
// function being tested
fillComparisonSets(dsGraphOrg, dsGraphNew, loadedSets)
if !knownSets.setCommonSvcs.Equal(loadedSets.setCommonSvcs) {
t.Errorf("Common services - did not match between expected result and input yaml")
}
if !knownSets.setCommonRels.Equal(loadedSets.setCommonRels) {
t.Errorf("Common relationships - did not match between expected result and input yaml")
}
if !knownSets.setAddedSvcs.Equal(loadedSets.setAddedSvcs) {
t.Errorf("Added services - did not match between expected result and input yaml")
}
if !knownSets.setAddedRels.Equal(loadedSets.setAddedRels) {
t.Errorf("Added relationships - did not match between expected result and input yaml")
}
if !knownSets.setDelSvcs.Equal(loadedSets.setDelSvcs) {
t.Errorf("Deleted services - did not match between expected result and input yaml")
}
if !knownSets.setDelRels.Equal(loadedSets.setDelRels) {
t.Errorf("Deleted relationships - did not match between expected result and input yaml")
}
}
// testGraphWithChanges - test diff comparison function
func TestGraphWithChanges(t *testing.T) {
dsGraphOrg := dagconfigservice.NewDagConfigService()
err := dsGraphOrg.LoadDagConfigFromString(testOrgDagStr)
if err != nil {
t.Errorf("dagConfigService failed to load dag from test string %s", err)
}
dsGraphNew := dagconfigservice.NewDagConfigService()
err = dsGraphNew.LoadDagConfigFromString(testNewDagStr)
if err != nil {
t.Errorf("dagConfigService failed to load file %s", err)
}
//construct sets struct for loaded constellation
loadedSets := &setsForComparison{}
// function being tested
fillComparisonSets(dsGraphOrg, dsGraphNew, loadedSets)
resString := createGraphWithChanges(dsGraphNew, loadedSets)
if !compareGraphOutputAsSets(testDiffComparisonOutputString, resString) {
t.Errorf("Resulting output does not match the reference comparison input \n RESULT \n%s EXPECTED \n%s", resString, testDiffComparisonOutputString)
}
}
// Utility to populate comparison sets with expected/known result
func populateComparisonSets(target *setsForComparison) {
target.setCommonSvcs = set.NewSet()
target.setCommonSvcs.Add("3aa1e546-1ed5-4d67-a59c-be0d5905b490")
target.setCommonSvcs.Add("1d0255d4-5b8c-4a52-b0bb-ac024cda37e5")
target.setCommonSvcs.Add("a268fae5-2a82-4a3e-ada7-a52eeb7019ac")
target.setCommonRels = set.NewSet()
target.setCommonRels.Add("3aa1e546-1ed5-4d67-a59c-be0d5905b490" + "|" + "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5")
target.setAddedSvcs = set.NewSet()
target.setAddedSvcs.Add("9f1bcb3d-ff58-41d4-8779-f71e7b8800f8")
target.setAddedSvcs.Add("b268fae5-2a82-4a3e-ada7-a52eeb7019ac")
target.setAddedRels = set.NewSet()
target.setAddedRels.Add("9f1bcb3d-ff58-41d4-8779-f71e7b8800f8" + "|" + "3aa1e546-1ed5-4d67-a59c-be0d5905b490")
target.setAddedRels.Add("1d0255d4-5b8c-4a52-b0bb-ac024cda37e5" + "|" + "a268fae5-2a82-4a3e-ada7-a52eeb7019ac")
target.setAddedRels.Add("a268fae5-2a82-4a3e-ada7-a52eeb7019ac" + "|" + "b268fae5-2a82-4a3e-ada7-a52eeb7019ac")
target.setDelSvcs = set.NewSet()
target.setDelSvcs.Add("9e1bcb3d-ff58-41d4-8779-f71e7b8800f8")
target.setDelRels = set.NewSet()
target.setDelRels.Add("9e1bcb3d-ff58-41d4-8779-f71e7b8800f8" + "|" + "3aa1e546-1ed5-4d67-a59c-be0d5905b490")
target.setDelRels.Add("3aa1e546-1ed5-4d67-a59c-be0d5905b490" + "|" + "a268fae5-2a82-4a3e-ada7-a52eeb7019ac")
}
// Sample DAG file data - original file
const testOrgDagStr = `
Name: "Azure Event Hubs Sample"
Id: "d6e4a5e9-696a-4626-ba7a-534d6ff450a5"
Services:
- Name: "Event Generator"
Id: "9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"
Type: "EventGenerator"
Properties: {}
- Name: "Azure Event Hub"
Id: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
Type: "EventHub"
Properties: {}
- Name: "Event Logger"
Id: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
Type: "EventLogger"
Properties: {}
- Name: "Event Logger"
Id: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
Type: "EventLogger"
Properties: {}
Relationships:
- Name: "Generator to Event Hubs Link"
Id: "211a55bd-5d92-446c-8be8-190f8f0e623e"
Description: "Event Generator to Event Hub connection"
From: "9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"
To: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
Properties: {}
- Name: "Event Hubs to Event Logger Link"
Id: "08ccbd67-456f-4349-854a-4e6959e5017b"
Description: "Event Hubs to Event Logger connection"
From: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
To: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
Properties: {}
- Name: "Event Hubs to Event Logger Link Repeat"
Id: "c8a719e0-164d-408f-9ed1-06e08dc5abbe"
Description: "Event Hubs to Event Logger connection"
From: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
To: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
Properties: {}`
// An updated (new) constellation dag with known differences
const testNewDagStr = `
Name: "Azure Event Hubs Sample Changed"
Id: "d6e4a5e9-696a-4626-ba7a-534d6ff450a5"
Services:
- Name: "Event Generator"
Id: "9f1bcb3d-ff58-41d4-8779-f71e7b8800f8"
Type: "EventGenerator"
Properties: {}
- Name: "Azure Event Hub"
Id: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
Type: "EventHub"
Properties: {}
- Name: "Event Logger"
Id: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
Type: "EventLogger"
Properties: {}
- Name: "Event Logger Added"
Id: "b268fae5-2a82-4a3e-ada7-a52eeb7019ac"
Type: "EventLogger"
Properties: {}
- Name: "Event Logger"
Id: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
Type: "EventLogger"
Properties: {}
Relationships:
- Name: "Generator to Event Hubs Link"
Id: "211a55bd-5d92-446c-8be8-190f8f0e623e"
Description: "Event Generator to Event Hub connection"
From: "9f1bcb3d-ff58-41d4-8779-f71e7b8800f8"
To: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
Properties: {}
- Name: "Event Hubs to Event Logger Link"
Id: "08ccbd67-456f-4349-854a-4e6959e5017b"
Description: "Event Hubs to Event Logger connection"
From: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
To: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
Properties: {}
- Name: "Event Hubs to Event Logger Link Repeat"
Id: "c8a719e0-164d-408f-9ed1-06e08dc5abbe"
Description: "Event Hubs to Event Logger connection"
From: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
To: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
Properties: {}
- Name: "Event Hubs to Event Logger Link Added to the end Repeat"
Id: "d8a719e0-164d-408f-9ed1-06e08dc5abbe"
Description: "Event Hubs to Event Logger connection"
From: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
To: "b268fae5-2a82-4a3e-ada7-a52eeb7019ac"
Properties: {}
`
const testDiffComparisonOutputString = `digraph Azure_Event_Hubs_Sample_Changed_diff {
rankdir=LR;
"9f1bcb3d-ff58-41d4-8779-f71e7b8800f8"->"3aa1e546-1ed5-4d67-a59c-be0d5905b490"[ color="#d8ffa8" ];

23
cmd/testdata/constellation/invalid.yaml поставляемый Normal file
Просмотреть файл

@ -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: {}

Просмотреть файл

11
cmd/testdata/mapper/invalid.yaml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,11 @@
Name: "Basic Azure Event Hubs maps"
Id: "a5a7c413-a020-44a2-bd23-1941adb7ad58"
Maps:
- ChartName: "event_hub_sample_event_hub"
Type: "EventHub"
Location: "../../helm/basictest"
Version: "1.0.0"
- ChartName: "event_hub_sample_event_hub"
Type: "EventHub"
Location: "../../helm/basictest"
Version: "1.0.0"

Просмотреть файл

@ -3,14 +3,13 @@ Id: "a5a7c413-a020-44a2-bd23-1941adb7ad58"
Maps:
- ChartName: "event_hub_sample_event_generator"
Type: "EventGenerator"
Location: "file://../../../sample/deps/event_hub_sample_event_generator"
Location: "../../helm/basictest"
Version: "1.0.0"
- ChartName: "event_hub_sample_event_logger"
Type: "EventLogger"
Location: "file://../../../sample/deps/event_hub_sample_event_logger"
Location: "../../helm/basictest2"
Version: "1.0.0"
- ChartName: "event_hub_sample_event_hub"
Type: "EventHub"
Location: "file://../../../sample/deps/event_hub_sample_event_hub"
Location: "../../helm/basictest3"
Version: "1.0.0"

Просмотреть файл

@ -2,14 +2,16 @@ package cmd
import (
"fmt"
"github.com/microsoft/abstrakt/internal/dagconfigservice"
"github.com/microsoft/abstrakt/internal/tools/logger"
"github.com/microsoft/abstrakt/internal/validationservice"
"github.com/microsoft/abstrakt/internal/platform/constellation"
"github.com/microsoft/abstrakt/internal/platform/mapper"
"github.com/microsoft/abstrakt/tools/find"
"github.com/microsoft/abstrakt/tools/logger"
"github.com/spf13/cobra"
)
type validateCmd struct {
constellationFilePath string
mapperFilePath string
*baseCmd
}
@ -19,24 +21,57 @@ func newValidateCmd() *validateCmd {
cc.baseCmd = newBaseCmd(&cobra.Command{
Use: "validate",
Short: "Validate a constellation file for correct schema and ensure correctness.",
Long: `Validate is used to ensure the correctness of a constellation file.
Long: `Validate is used to ensure the correctness of a constellation and mapper files.
Example: abstrakt validate -f [constellationFilePath]`,
Example: abstrakt validate -f [constellationFilePath] -m [mapperFilePath]
abstrakt validate -f [constellationFilePath]
abstrakt validate -m [mapperFilePath]`,
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) (err error) {
d := dagconfigservice.NewDagConfigService()
err = d.LoadDagConfigFromFile(cc.constellationFilePath)
if err != nil {
return
if len(cc.constellationFilePath) == 0 && len(cc.mapperFilePath) == 0 {
_ = cc.baseCmd.cmd.Usage()
return fmt.Errorf("no flags were set")
}
err = validateDag(&d)
var d constellation.Config
var m mapper.Config
if err == nil {
logger.Info("Constellation is valid.")
fail := false
if len(cc.mapperFilePath) > 0 {
m, err = loadAndValidateMapper(cc.mapperFilePath)
if err != nil {
logger.Errorf("Mapper: %v", err)
fail = true
} else {
logger.Info("Mapper: valid")
}
}
if len(cc.constellationFilePath) > 0 {
d, err = loadAndValidateDag(cc.constellationFilePath)
if err != nil {
logger.Errorf("Constellation: %v", err)
fail = true
} else {
logger.Info("Constellation: valid")
}
}
if !d.IsEmpty() && !m.IsEmpty() {
err = validateDagAndMapper(&d, &m)
if err != nil {
logger.Errorf("Deployment: %v", err)
fail = true
} else {
logger.Info("Deployment: valid")
}
}
if fail {
err = fmt.Errorf("Invalid configuration(s)")
}
return
@ -44,41 +79,138 @@ Example: abstrakt validate -f [constellationFilePath]`,
})
cc.cmd.Flags().StringVarP(&cc.constellationFilePath, "constellationFilePath", "f", "", "constellation file path")
_ = cc.cmd.MarkFlagRequired("constellationFilePath")
cc.cmd.Flags().StringVarP(&cc.mapperFilePath, "mapperFilePath", "m", "", "mapper file path")
return cc
}
// validateDag takes a constellation dag and returns any errors.
func validateDag(dag *dagconfigservice.DagConfigService) (err error) {
service := validationservice.Validator{Config: dag}
func validateDagAndMapper(d *constellation.Config, m *mapper.Config) (err error) {
types := []string{}
mapTypes := []string{}
err = service.ValidateModel()
for _, i := range d.Services {
_, exists := find.Slice(types, i.Type)
if !exists {
types = append(types, i.Type)
}
}
for _, i := range m.Maps {
mapTypes = append(mapTypes, i.Type)
}
logger.Debug("deployment: checking if `Service` exists in map")
for _, i := range types {
_, exists := find.Slice(mapTypes, i)
if !exists {
logger.Error("Missing map configuration(s)")
logger.Errorf("Service `%v` does not exist in map", i)
err = fmt.Errorf("invalid")
}
}
return
}
func loadAndValidateDag(path string) (config constellation.Config, err error) {
err = config.LoadFile(path)
if err != nil {
return
}
duplicates := service.CheckDuplicates()
return config, validateDag(&config)
}
// validateDag takes a constellation dag and returns any errors.
func validateDag(d *constellation.Config) (err error) {
logger.Debug("Constellation: validating schema")
err = d.ValidateModel()
if err != nil {
logger.Debug(err)
return fmt.Errorf("invalid schema")
}
logger.Debug("constellation: checking for duplicate `ID`")
duplicates := d.FindDuplicateIDs()
if duplicates != nil {
logger.Error("Duplicate IDs found:")
logger.Error("Duplicate `ID` present in config")
for _, i := range duplicates {
logger.Errorf("'%v'", i)
}
err = error(fmt.Errorf("Constellation is invalid"))
err = fmt.Errorf("invalid")
}
connections := service.CheckServiceExists()
logger.Debug("Constellation: checking if `Service` exists")
connections := d.ServiceExists()
if len(connections) > 0 {
logger.Error("Missing relationship(s)")
for key, i := range connections {
logger.Errorf("Relationship '%v' has missing Services:", key)
logger.Errorf("Relationship '%v' has missing `Services`:", key)
for _, j := range i {
logger.Errorf("'%v'", j)
}
}
err = error(fmt.Errorf("Constellation is invalid"))
err = fmt.Errorf("invalid")
}
return
}
func loadAndValidateMapper(path string) (config mapper.Config, err error) {
err = config.LoadFile(path)
if err != nil {
return
}
return config, validateMapper(&config)
}
// validateMapper takes a constellation mapper and returns any errors.
func validateMapper(m *mapper.Config) (err error) {
logger.Debug("Mapper: validating schema")
err = m.ValidateModel()
if err != nil {
logger.Debug(err)
return fmt.Errorf("invalid schema")
}
logger.Debug("Mapper: checking for duplicate `ChartName`")
duplicates := m.FindDuplicateChartName()
if duplicates != nil {
logger.Error("Duplicate `ChartName` present in config")
for _, i := range duplicates {
logger.Errorf("'%v'", i)
}
err = fmt.Errorf("invalid")
}
logger.Debug("Mapper: checking for duplicate `Type`")
duplicates = m.FindDuplicateType()
if duplicates != nil {
logger.Error("Duplicate `Type` present in config")
for _, i := range duplicates {
logger.Errorf("'%v'", i)
}
err = fmt.Errorf("invalid")
}
logger.Debug("Mapper: checking for duplicate `Location`")
duplicates = m.FindDuplicateLocation()
if duplicates != nil {
logger.Error("Duplicate `Location` present in config")
for _, i := range duplicates {
logger.Errorf("'%v'", i)
}
err = fmt.Errorf("invalid")
}
return

Просмотреть файл

@ -1,27 +1,148 @@
package cmd
import (
helper "github.com/microsoft/abstrakt/tools/test"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"testing"
)
func TestValidateCommand(t *testing.T) {
expected := "open does-not-exist: no such file or directory"
constellationPath, _, tdir := PrepareRealFilesForTest(t)
defer CleanTempTestFiles(t, tdir)
output, err := executeCommand(newValidateCmd().cmd, "-f", constellationPath)
if err != nil {
t.Errorf("Did not received expected error. \nGot:\n %v", output)
}
_, err = executeCommand(newValidateCmd().cmd, "-f", "does-not-exist")
if err != nil {
checkStringContains(t, err.Error(), expected)
} else {
t.Errorf("Did not received expected error. \nExpected: %v\nGot:\n %v", expected, err.Error())
}
func TestValidateCommandNoArgs(t *testing.T) {
_, err := helper.ExecuteCommand(newValidateCmd().cmd)
assert.Error(t, err)
assert.EqualError(t, err, "no flags were set")
}
func TestValidateCommandConstellationAndMapper(t *testing.T) {
constellationPath := "testdata/constellation/valid.yaml"
mapPath := "testdata/mapper/valid.yaml"
output, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", constellationPath, "-m", mapPath)
assert.NoErrorf(t, err, "Did not received expected error. \nGot:\n %v", output)
}
func TestValidateCommandConstellationExist(t *testing.T) {
constellationPath := "testdata/constellation/valid.yaml"
output, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", constellationPath)
assert.NoErrorf(t, err, "Did not received expected error. \nGot:\n %v", output)
}
func TestValidateCommandMapExist(t *testing.T) {
mapPath := "testdata/mapper/valid.yaml"
output, err := helper.ExecuteCommand(newValidateCmd().cmd, "-m", mapPath)
assert.NoErrorf(t, err, "Did not received expected error. \nGot:\n %v", output)
}
func TestValidateCommandConstellationFail(t *testing.T) {
expected := "Constellation: open does-not-exist: no such file or directory"
hook := test.NewGlobal()
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", "does-not-exist")
entries := []string{}
for _, i := range hook.AllEntries() {
entries = append(entries, i.Message)
}
assert.Error(t, err)
assert.Contains(t, entries, expected)
}
func TestValidateCommandMapFail(t *testing.T) {
expected := "Mapper: open does-not-exist: no such file or directory"
hook := test.NewGlobal()
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-m", "does-not-exist")
entries := helper.GetAllLogs(hook.AllEntries())
assert.Error(t, err)
assert.Contains(t, entries, expected)
}
func TestValidateCommandConstellationInvalidSchema(t *testing.T) {
mapPath := "testdata/mapper/valid.yaml"
hook := test.NewGlobal()
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", mapPath)
entries := helper.GetAllLogs(hook.AllEntries())
assert.Error(t, err)
assert.Contains(t, entries, "Constellation: invalid schema")
assert.EqualError(t, err, "Invalid configuration(s)")
}
func TestValidateCommandNapperInvalidSchema(t *testing.T) {
constellationPath := "testdata/constellation/valid.yaml"
hook := test.NewGlobal()
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-m", constellationPath)
entries := helper.GetAllLogs(hook.AllEntries())
assert.Error(t, err)
assert.Contains(t, entries, "Mapper: invalid schema")
assert.EqualError(t, err, "Invalid configuration(s)")
}
func TestValidateDeploymentFail(t *testing.T) {
constellationPath := "testdata/constellation/valid.yaml"
mapPath := "testdata/mapper/invalid.yaml"
hook := test.NewGlobal()
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", constellationPath, "-m", mapPath)
entries := helper.GetAllLogs(hook.AllEntries())
assert.Error(t, err)
assert.Contains(t, entries, "Service `EventLogger` does not exist in map")
assert.EqualError(t, err, "Invalid configuration(s)")
}
func TestValidateMapperDuplicates(t *testing.T) {
mapPath := "testdata/mapper/invalid.yaml"
hook := test.NewGlobal()
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-m", mapPath)
entries := helper.GetAllLogs(hook.AllEntries())
assert.Error(t, err)
assert.Contains(t, entries, "Duplicate `ChartName` present in config")
assert.Contains(t, entries, "Duplicate `Type` present in config")
assert.Contains(t, entries, "Duplicate `Location` present in config")
assert.Contains(t, entries, "Mapper: invalid")
assert.EqualError(t, err, "Invalid configuration(s)")
}
func TestValidateConstellationDuplicateIDs(t *testing.T) {
constellationPath := "testdata/constellation/invalid.yaml"
hook := test.NewGlobal()
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", constellationPath)
entries := helper.GetAllLogs(hook.AllEntries())
assert.Error(t, err)
assert.Contains(t, entries, "Duplicate `ID` present in config")
assert.Contains(t, entries, "Constellation: invalid")
assert.EqualError(t, err, "Invalid configuration(s)")
}
func TestValidateConstellationMissingServices(t *testing.T) {
constellationPath := "testdata/constellation/invalid.yaml"
hook := test.NewGlobal()
_, err := helper.ExecuteCommand(newValidateCmd().cmd, "-f", constellationPath)
entries := helper.GetAllLogs(hook.AllEntries())
assert.Error(t, err)
assert.Contains(t, entries, "Relationship 'Event Hubs to Event Logger Link' has missing `Services`:")
assert.Contains(t, entries, "Constellation: invalid")
assert.EqualError(t, err, "Invalid configuration(s)")
}

Просмотреть файл

@ -1,7 +1,7 @@
package cmd
import (
logger "github.com/microsoft/abstrakt/internal/tools/logger"
logger "github.com/microsoft/abstrakt/tools/logger"
"github.com/spf13/cobra"
)

Просмотреть файл

@ -1,9 +1,11 @@
package cmd
import (
helper "github.com/microsoft/abstrakt/tools/test"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"os"
"testing" // based on standard golang testing library https://golang.org/pkg/testing/
"testing"
)
// TestMain does setup or teardown (tests run when m.Run() is called)
@ -15,21 +17,15 @@ func TestMain(m *testing.M) {
func TestVersion(t *testing.T) {
expected := "0.0.1"
version := Version()
if version != expected {
t.Errorf("Did not find correct abstrakt version. Expected %v, got %v", expected, version)
}
assert.Equal(t, expected, version)
}
func TestVersionCmd(t *testing.T) {
expected := "0.0.1"
hook := test.NewGlobal()
_, err := executeCommand(newVersionCmd().cmd)
_, err := helper.ExecuteCommand(newVersionCmd().cmd)
if err != nil {
t.Error(err)
} else {
checkStringContains(t, hook.LastEntry().Message, expected)
}
assert.NoError(t, err)
assert.Contains(t, hook.LastEntry().Message, expected)
}

Просмотреть файл

@ -6,12 +6,11 @@ package cmd
// has to be run through a graphviz visualisation tool/utiliyy
import (
"bytes"
"fmt"
"github.com/awalterschulze/gographviz"
"github.com/microsoft/abstrakt/internal/dagconfigservice"
"github.com/microsoft/abstrakt/internal/tools/logger"
"github.com/microsoft/abstrakt/internal/platform/constellation"
"github.com/microsoft/abstrakt/tools/logger"
"github.com/spf13/cobra"
"os"
"strings"
)
@ -34,17 +33,19 @@ Example: abstrakt visualise -f [constellationFilePath]`,
logger.Debug("args: " + strings.Join(args, " "))
logger.Debug("constellationFilePath: " + cc.constellationFilePath)
if !fileExists(cc.constellationFilePath) {
return fmt.Errorf("Could not open YAML input file for reading %v", cc.constellationFilePath)
}
dsGraph := dagconfigservice.NewDagConfigService()
err := dsGraph.LoadDagConfigFromFile(cc.constellationFilePath)
dsGraph := new(constellation.Config)
err := dsGraph.LoadFile(cc.constellationFilePath)
if err != nil {
return fmt.Errorf("dagConfigService failed to load file %q: %s", cc.constellationFilePath, err)
return fmt.Errorf("Constellation config failed to load file %q: %s", cc.constellationFilePath, err)
}
resString := generateGraph(dsGraph)
out := &bytes.Buffer{}
resString, err := dsGraph.GenerateGraph(out)
if err != nil {
return err
}
logger.PrintBuffer(out, true)
logger.Output(resString)
return nil
@ -56,68 +57,3 @@ Example: abstrakt visualise -f [constellationFilePath]`,
return cc
}
// fileExists - basic utility function to check the provided filename can be opened and is not a folder/directory
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
// generateGraph - function to take a dagconfigService structure and create a graph object that contains the
// representation of the graph. Also outputs a string representation (GraphViz dot notation) of the resulting graph
// this can be passed on to GraphViz to graphically render the resulting graph
func generateGraph(readGraph dagconfigservice.DagConfigService) string {
// Lookup is used to map IDs to names. Names are easier to visualise but IDs are more important to ensure the
// presented constellation is correct and IDs are used to link nodes together
lookup := make(map[string]string)
g := gographviz.NewGraph()
// Replace spaces with underscores, names with spaces can break graphviz engines
if err := g.SetName(strings.Replace(readGraph.Name, " ", "_", -1)); err != nil {
logger.Fatalf("error: %v", err)
}
if err := g.AddAttr(g.Name, "rankdir", "LR"); err != nil {
logger.Fatalf("error adding attribute: %v", err)
}
// Make the graph directed (a constellation is DAG)
if err := g.SetDir(true); err != nil {
logger.Fatalf("error: %v", err)
}
// Add all nodes to the graph storing the lookup from ID to name (for later adding relationships)
// Replace spaces in names with underscores, names with spaces can break graphviz engines)
for _, v := range readGraph.Services {
logger.Debugf("Adding node %s", v.ID)
newName := strings.Replace(v.ID, " ", "_", -1)
if strings.Compare(newName, v.ID) != 0 {
logger.Debugf("Changing %s to %s", v.ID, newName)
}
lookup[v.ID] = newName
err := g.AddNode(readGraph.Name, "\""+newName+"\"", nil)
if err != nil {
logger.Fatalf("error: %v", err)
}
}
// Add relationships to the graph linking using the lookup IDs to name map
// Replace spaces in names with underscores, names with spaces can break graphviz engines)
for _, v := range readGraph.Relationships {
logger.Debugf("Adding relationship from %s ---> %s", v.From, v.To)
localFrom := "\"" + lookup[v.From] + "\""
localTo := "\"" + lookup[v.To] + "\""
err := g.AddEdge(localFrom, localTo, true, nil)
if err != nil {
logger.Fatalf("error: %v", err)
}
}
// Produce resulting graph in dot notation format
return g.String()
}

Просмотреть файл

@ -1,9 +1,11 @@
package cmd
import (
"fmt"
"github.com/microsoft/abstrakt/internal/dagconfigservice"
"github.com/microsoft/abstrakt/internal/platform/constellation"
"github.com/microsoft/abstrakt/tools/file"
helper "github.com/microsoft/abstrakt/tools/test"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"path/filepath"
@ -11,57 +13,37 @@ import (
)
func TestVisualiseCmdWithAllRequirementsNoError(t *testing.T) {
constellationPath, _, _ := PrepareRealFilesForTest(t)
constellationPath := "testdata/constellation/valid.yaml"
hook := test.NewGlobal()
_, err := executeCommand(newVisualiseCmd().cmd, "-f", constellationPath)
_, err := helper.ExecuteCommand(newVisualiseCmd().cmd, "-f", constellationPath)
if err != nil {
t.Error("Did not receive output")
} else {
if !compareGraphOutputAsSets(validGraphString, hook.LastEntry().Message) {
t.Errorf("Expcted output and produced output do not match : expected %s produced %s", validGraphString, hook.LastEntry().Message)
}
}
assert.NoError(t, err)
assert.True(t, helper.CompareGraphOutputAsSets(validGraphString, hook.LastEntry().Message))
}
func TestVisualiseCmdFailYaml(t *testing.T) {
expected := "Could not open YAML input file for reading"
expected := "Constellation config failed to load file"
output, err := executeCommand(newVisualiseCmd().cmd, "-f", "constellationPath")
_, err := helper.ExecuteCommand(newVisualiseCmd().cmd, "-f", "constellationPath")
if err != nil {
checkStringContains(t, err.Error(), expected)
} else {
t.Errorf("Did not fail. Expected: %v \nGot: %v", expected, output)
}
assert.Error(t, err)
assert.Contains(t, err.Error(), expected)
}
func TestVisualiseCmdFailNotYaml(t *testing.T) {
expected := "dagConfigService failed to load file"
expected := "Constellation config failed to load file"
output, err := executeCommand(newVisualiseCmd().cmd, "-f", "visualise.go")
_, err := helper.ExecuteCommand(newVisualiseCmd().cmd, "-f", "visualise.go")
if err != nil {
checkStringContains(t, err.Error(), expected)
} else {
t.Errorf("Did not fail. Expected: %v \nGot: %v", expected, output)
}
assert.Error(t, err)
assert.Contains(t, err.Error(), expected)
}
func TestFileExists(t *testing.T) {
_, _, tdir := helper.PrepareRealFilesForTest(t)
tdir, err := ioutil.TempDir("", "helm-")
if err != nil {
t.Fatal(err)
}
defer func() {
err = os.RemoveAll(tdir)
if err != nil {
t.Fatal(err)
}
}()
defer helper.CleanTempTestFiles(t, tdir)
//Setup variables and content for test
testValidFilename := filepath.Join(tdir, "testVisualise.out")
@ -69,115 +51,37 @@ func TestFileExists(t *testing.T) {
testData := []byte("A file to test with")
//Create a file to test against
err = ioutil.WriteFile(testValidFilename, testData, 0644)
if err != nil {
fmt.Println("Could not create output testing file, cannot proceed")
t.Error(err)
}
err := ioutil.WriteFile(testValidFilename, testData, 0644)
assert.NoError(t, err, "Could not create output testing file, cannot proceed")
//Test that a valid file (created above) can be seen
var result bool = fileExists(testValidFilename) //Expecting true - file does exists
if result == false {
t.Errorf("Test file does exist but testFile returns that it does not")
}
var result bool = file.Exists(testValidFilename) //Expecting true - file does exists
assert.True(t, result, "Test file does exist but testFile returns that it does not")
//Test that an invalid file (does not exist) is not seen
result = fileExists(testInvalidFilename) //Expecting false - file does not exist
if result != false {
t.Errorf("Test file does not exist but testFile says it does")
}
result = file.Exists(testInvalidFilename) //Expecting false - file does not exist
assert.False(t, result, "Test file does not exist but testFile says it does")
err = os.Remove(testValidFilename)
if err != nil {
panic(err)
}
result = fileExists(testValidFilename) //Expecting false - file has been removed
if result == true {
t.Errorf("Test file has been removed but fileExists is finding it")
}
}
func TestGenerateGraph(t *testing.T) {
retConfig := dagconfigservice.NewDagConfigService()
err := retConfig.LoadDagConfigFromString(test01DagStr)
if err != nil {
panic(err)
}
cmpString := test02ConstGraphString
retString := generateGraph(retConfig)
if !compareGraphOutputAsSets(cmpString, retString) {
t.Errorf("Input graph did not generate expected output graphviz representation")
t.Errorf("Expected:\n%v \nGot:\n%v", cmpString, retString)
}
assert.NoError(t, err)
result = file.Exists(testValidFilename) //Expecting false - file has been removed
assert.False(t, result, "Test file has been removed but fileExists is finding it")
}
func TestParseYaml(t *testing.T) {
retConfig := new(constellation.Config)
err := retConfig.LoadString(testValidYAMLString)
assert.NoError(t, err)
retConfig := dagconfigservice.NewDagConfigService()
err := retConfig.LoadDagConfigFromString(testValidYAMLString)
if err != nil {
panic(err)
}
if retConfig.Name != "Azure Event Hubs Sample" &&
retConfig.ID != "d6e4a5e9-696a-4626-ba7a-534d6ff450a5" &&
len(retConfig.Services) != 1 &&
len(retConfig.Relationships) != 1 {
t.Errorf("YAML did not parse correctly and it should have")
}
errMsg := "YAML did not parse correctly and it should have"
assert.Equalf(t, retConfig.Name, "Azure Event Hubs Sample", errMsg)
assert.EqualValuesf(t, retConfig.ID, "211a55bd-5d92-446c-8be8-190f8f0e623e", errMsg)
assert.Equalf(t, len(retConfig.Services), 1, errMsg)
assert.Equalf(t, len(retConfig.Relationships), 1, errMsg)
}
// Sample DAG file data
const test01DagStr = `Name: "Azure Event Hubs Sample"
Id: "d6e4a5e9-696a-4626-ba7a-534d6ff450a5"
Services:
- Id: "Event Generator"
Type: "EventGenerator"
Properties: {}
- Id: "Azure Event Hub"
Type: "EventHub"
Properties: {}
- Id: "Event Logger"
Type: "EventLogger"
Properties: {}
- Id: "Event Logger"
Type: "EventLogger"
Properties: {}
Relationships:
- Id: "Generator to Event Hubs Link"
Description: "Event Generator to Event Hub connection"
From: "Event Generator"
To: "Azure Event Hub"
Properties: {}
- Id: "Event Hubs to Event Logger Link"
Description: "Event Hubs to Event Logger connection"
From: "Azure Event Hub"
To: "Event Logger"
Properties: {}
- Id: "Event Hubs to Event Logger Link Repeat"
Description: "Event Hubs to Event Logger connection"
From: "Azure Event Hub"
To: "Event Logger"
Properties: {}`
const test02ConstGraphString = `digraph Azure_Event_Hubs_Sample {
rankdir=LR;
"Event_Generator"->"Azure_Event_Hub";
"Azure_Event_Hub"->"Event_Logger";
"Azure_Event_Hub"->"Event_Logger";
"Azure_Event_Hub";
"Event_Generator";
"Event_Logger";
}
`
const testValidYAMLString = `
Description: "Event Generator to Event Hub connection"
From: 9e1bcb3d-ff58-41d4-8779-f71e7b8800f8
@ -196,13 +100,11 @@ Type: EventGenerator
const validGraphString = `digraph Azure_Event_Hubs_Sample {
rankdir=LR;
"9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"->"3aa1e546-1ed5-4d67-a59c-be0d5905b490";
"3aa1e546-1ed5-4d67-a59c-be0d5905b490"->"1d0255d4-5b8c-4a52-b0bb-ac024cda37e5";
"3aa1e546-1ed5-4d67-a59c-be0d5905b490"->"a268fae5-2a82-4a3e-ada7-a52eeb7019ac";
"1d0255d4-5b8c-4a52-b0bb-ac024cda37e5";
"3aa1e546-1ed5-4d67-a59c-be0d5905b490";
"9e1bcb3d-ff58-41d4-8779-f71e7b8800f8";
"a268fae5-2a82-4a3e-ada7-a52eeb7019ac";
"Event_Generator"->"Azure_Event_Hub";
"Azure_Event_Hub"->"Event_Logger";
"Azure_Event_Hub";
"Event_Generator";
"Event_Logger";
}
`

Просмотреть файл

@ -54,12 +54,12 @@ Can compose a Helm chart directory (default) or a __.tgz__ of the produced helm
Create a Helm chart named `http-demo` to be generated under ./output.
```bash
./abstrakt compose http-demo -f ./sample/constellation/http_constellation.yaml -m ./sample/constellation/http_constellation_maps.yaml -o ./output/http-demo
./abstrakt compose http-demo -f ./examples/constellation/http_constellation.yaml -m ./examples/constellation/http_constellation_maps.yaml -o ./output/http-demo
```
With __.tgz__
```bash
./abstrakt compose http-demo -f ./sample/constellation/http_constellation.yaml -m ./sample/constellation/http_constellation_maps.yaml -o ./output/http-demo -z
./abstrakt compose http-demo -f ./examples/constellation/http_constellation.yaml -m ./examples/constellation/http_constellation_maps.yaml -o ./output/http-demo -z
```
### abstrakt `validate`
@ -143,4 +143,4 @@ Run visualise on a file
Pipe visualise output to Graphviz producing a file called result.png (assumes Graphviz is installed and can be called from the location abstrakt is being run)
abstrakt visualise -f ./sample/constellation/sample_consteallation.yaml | dot -Tpng > result.png
abstrakt visualise -f ./examples/constellation/sample_consteallation.yaml | dot -Tpng > result.png

Просмотреть файл

@ -3,9 +3,9 @@ Id: "a5a7c413-a020-44a2-bd23-1941adb7ad58"
Maps:
- ChartName: "sender"
Type: "WormholeSender"
Location: "file://../../../sample/helm/http_sample/sender"
Location: "file://../../../examples/helm/http_sample/sender"
Version: "0.0.1"
- ChartName: "receiver"
Type: "WormholeReceiver"
Location: "file://../../../sample/helm/http_sample/receiver"
Location: "file://../../../examples/helm/http_sample/receiver"
Version: "0.0.1"

Просмотреть файл

@ -0,0 +1,16 @@
Name: "Basic Azure Event Hubs maps"
Id: "a5a7c413-a020-44a2-bd23-1941adb7ad58"
Maps:
- ChartName: "event_hub_sample_event_generator"
Type: "EventGenerator"
Location: "file://../../../examples/deps/event_hub_sample_event_generator"
Version: "1.0.0"
- ChartName: "event_hub_sample_event_logger"
Type: "EventLogger"
Location: "file://../../../examples/deps/event_hub_sample_event_logger"
Version: "1.0.0"
- ChartName: "event_hub_sample_event_hub"
Type: "EventHub"
Location: "file://../../../examples/deps/event_hub_sample_event_hub"
Version: "1.0.0"

Просмотреть файл

@ -2,17 +2,17 @@ apiVersion: v2
appVersion: 1.16.0
dependencies:
- name: event_hub_sample_event_generator
repository: "file://../../../../sample/deps/event_hub_sample_event_generator"
repository: "file://../../../../examples/deps/event_hub_sample_event_generator"
version: 1.0.0
- name: event_hub_sample_event_hub
repository: "file://../../../../sample/deps/event_hub_sample_event_hub"
repository: "file://../../../../examples/deps/event_hub_sample_event_hub"
version: 1.0.0
- name: event_hub_sample_event_logger
repository: "file://../../../../sample/deps/event_hub_sample_event_logger"
repository: "file://../../../../examples/deps/event_hub_sample_event_logger"
version: 1.0.0
- alias: event_hub_sample_event_logger1
name: event_hub_sample_event_logger
repository: "file://../../../../sample/deps/event_hub_sample_event_logger"
repository: "file://../../../../examples/deps/event_hub_sample_event_logger"
version: 1.0.0
description: A Helm chart for Kubernetes
name: test

1
go.mod
Просмотреть файл

@ -19,6 +19,7 @@ require (
github.com/spf13/cobra v0.0.5
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.4.0
github.com/stretchr/testify v1.4.0
golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect

Просмотреть файл

@ -1,90 +0,0 @@
package buildmapservice
//////////////////////////////////////////////////////
// BuildMapService: Process map files relating services
// to helm chart files. For example, see accompanying
// test file.
//////////////////////////////////////////////////////
import (
"io/ioutil"
"strings"
"github.com/microsoft/abstrakt/internal/tools/guid"
yamlParser "gopkg.in/yaml.v2"
)
// Note: the yaml mapping attributes are necessary (despite the nearly
// uniform 1-1 name correspondence). The yaml parser would otherwise
// expect the names in the YAML file to be all lower-case.
// e.g. ChartName would only work if "chartname" was used in the yaml file.
// BuildMapInfo -- info about an individual component
type BuildMapInfo struct {
ChartName string `yaml:"ChartName"`
Type string `yaml:"Type"`
Location string `yaml:"Location"`
Version string `yaml:"Version"`
}
// BuildMapService -- data from the entire build map.
type BuildMapService struct {
Name string `yaml:"Name"`
ID guid.GUID `yaml:"Id"`
Maps []BuildMapInfo `yaml:"Maps"`
}
// FindByName -- Look up a map by chart name.
func (m *BuildMapService) FindByName(chartName string) (res *BuildMapInfo) {
for _, wmi := range m.Maps {
// try first for an exact match
if chartName == wmi.ChartName {
return &wmi
}
// if we want to tolerate case being incorrect (e.g., ABC vs. abc),
if guid.TolerateMiscasedKey && strings.EqualFold(string(wmi.ChartName), chartName) {
return &wmi
}
}
return nil
}
// FindByType -- Look up a map by the "Type" value.
func (m *BuildMapService) FindByType(typeName string) (res *BuildMapInfo) {
for _, wmi := range m.Maps {
// try first for an exact match
if typeName == wmi.Type {
return &wmi
}
// if we want to tolerate case being incorrect (e.g., ABC vs. abc),
if guid.TolerateMiscasedKey && strings.EqualFold(string(wmi.Type), typeName) {
return &wmi
}
}
return nil
}
// LoadMapFromFile -- New Map info instance from the named file.
func (m *BuildMapService) LoadMapFromFile(fileName string) (err error) {
err = nil
contentBytes, err := ioutil.ReadFile(fileName)
if nil != err {
return err
}
err = m.LoadMapFromString(string(contentBytes))
return err
}
// LoadMapFromString -- New Map info instance from the given yaml string.
func (m *BuildMapService) LoadMapFromString(yamlString string) (err error) {
err = nil
err = yamlParser.Unmarshal([]byte(yamlString), m)
return err
}
//NewBuildMapService returns a new instance of NewBuildMapService
func NewBuildMapService() BuildMapService {
return BuildMapService{}
}

Просмотреть файл

@ -1,83 +0,0 @@
package buildmapservice
import (
"reflect"
"testing"
"github.com/microsoft/abstrakt/internal/tools/guid"
)
func TestMapFromString(t *testing.T) {
type args struct {
yamlString string
}
tests := []struct {
name string
args args
wantRet *BuildMapService
wantErr bool
}{
{
name: "Test.01",
args: args{yamlString: configMapTest01String},
wantRet: &buildMap01,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mapper := &BuildMapService{}
err := mapper.LoadMapFromString(tt.args.yamlString)
if (err != nil) != tt.wantErr {
t.Errorf("LoadMapFromString() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(mapper, tt.wantRet) {
t.Errorf("LoadMapFromString() = %v, want %v", mapper, tt.wantRet)
}
})
}
}
const configMapTest01String = `
Name: "Basic Azure Event Hubs maps"
Id: "a5a7c413-a020-44a2-bd23-1941adb7ad58"
Maps:
- ChartName: "event_hub_sample_event_generator"
Type: "EventGenerator"
Location: "../../helm/basictest"
Version: "1.0.0"
- ChartName: "event_hub_sample_event_logger"
Type: "EventLogger"
Location: "../../helm/basictest"
Version: "1.0.0"
- ChartName: "event_hub_sample_event_hub"
Type: "EventHub"
Location: "../../helm/basictest"
Version: "1.0.0"
`
var buildMap01 = BuildMapService{
Name: "Basic Azure Event Hubs maps",
ID: guid.GUID("a5a7c413-a020-44a2-bd23-1941adb7ad58"),
Maps: []BuildMapInfo{
{
ChartName: "event_hub_sample_event_generator",
Type: "EventGenerator",
Location: "../../helm/basictest",
Version: "1.0.0",
},
{
ChartName: "event_hub_sample_event_logger",
Type: "EventLogger",
Location: "../../helm/basictest",
Version: "1.0.0",
},
{
ChartName: "event_hub_sample_event_hub",
Type: "EventHub",
Location: "../../helm/basictest",
Version: "1.0.0",
},
},
}

Просмотреть файл

@ -1,29 +1,26 @@
package composeservice
package compose
import (
"errors"
"fmt"
"github.com/microsoft/abstrakt/internal/buildmapservice"
"github.com/microsoft/abstrakt/internal/chartservice"
"github.com/microsoft/abstrakt/internal/dagconfigservice"
"helm.sh/helm/v3/pkg/chart"
"github.com/microsoft/abstrakt/internal/platform/chart"
"github.com/microsoft/abstrakt/internal/platform/constellation"
"github.com/microsoft/abstrakt/internal/platform/mapper"
helm "helm.sh/helm/v3/pkg/chart"
)
//ComposeService takes maps and configs and builds out the helm chart
type ComposeService struct {
DagConfigService dagconfigservice.DagConfigService
BuildMapService buildmapservice.BuildMapService
//Composer takes maps and configs and builds out the helm chart
type Composer struct {
Constellation constellation.Config
Mapper mapper.Config
}
//Compose takes the loaded DAG and maps and builds the Helm values and requirements documents
func (m *ComposeService) Compose(name string, dir string) (*chart.Chart, error) {
if m.DagConfigService.Name == "" || m.BuildMapService.Name == "" {
return nil, errors.New("Please initialise with LoadFromFile or LoadFromString")
//Build takes the loaded DAG and maps and builds the Helm values and requirements documents
func (c *Composer) Build(name string, dir string) (*helm.Chart, error) {
if c.Constellation.Name == "" || c.Mapper.Name == "" {
return nil, fmt.Errorf("Please initialise with LoadFromFile or LoadFromString")
}
newChart, err := chartservice.CreateChart(name, dir)
newChart, err := chart.Create(name, dir)
if err != nil {
return nil, err
@ -31,12 +28,12 @@ func (m *ComposeService) Compose(name string, dir string) (*chart.Chart, error)
serviceMap := make(map[string]int)
aliasMap := make(map[string]string)
deps := make([]*chart.Dependency, 0)
deps := make([]*helm.Dependency, 0)
values := newChart.Values
for _, n := range m.DagConfigService.Services {
service := m.BuildMapService.FindByType(n.Type)
for _, n := range c.Constellation.Services {
service := c.Mapper.FindByType(n.Type)
if service == nil {
return nil, fmt.Errorf("Could not find service %v", service)
}
@ -49,7 +46,7 @@ func (m *ComposeService) Compose(name string, dir string) (*chart.Chart, error)
serviceMap[service.Type]++
dep := &chart.Dependency{
dep := &helm.Dependency{
Name: service.ChartName, Version: service.Version, Repository: service.Location,
}
@ -69,15 +66,15 @@ func (m *ComposeService) Compose(name string, dir string) (*chart.Chart, error)
relationships := make(map[string][]interface{})
valMap["relationships"] = &relationships
toRels := m.DagConfigService.FindRelationshipByToName(n.ID)
fromRels := m.DagConfigService.FindRelationshipByFromName(n.ID)
toRels := c.Constellation.FindRelationshipByToName(n.ID)
fromRels := c.Constellation.FindRelationshipByFromName(n.ID)
for _, i := range toRels {
toRelations := make(map[string]string)
relationships["input"] = append(relationships["input"], &toRelations)
//find the target service
foundService := m.DagConfigService.FindService(i.From)
foundService := c.Constellation.FindService(i.From)
if foundService == nil {
return nil, fmt.Errorf("Service '%v' referenced in relationship '%v' not found", i.From, i.ID)
@ -99,7 +96,7 @@ func (m *ComposeService) Compose(name string, dir string) (*chart.Chart, error)
relationships["output"] = append(relationships["output"], &fromRelations)
//find the target service
foundService := m.DagConfigService.FindService(i.To)
foundService := c.Constellation.FindService(i.To)
if foundService == nil {
return nil, fmt.Errorf("Service '%v' referenced in relationship '%v' not found", i.To, i.ID)
@ -121,36 +118,13 @@ func (m *ComposeService) Compose(name string, dir string) (*chart.Chart, error)
newChart.Metadata.Dependencies = deps
return newChart, nil
}
//LoadFromFile takes a string dag and map and loads them
func (m *ComposeService) LoadFromFile(dagFile string, mapFile string) (err error) {
err = m.DagConfigService.LoadDagConfigFromFile(dagFile)
//LoadFile takes a string dag and map and loads them
func (c *Composer) LoadFile(dagFile string, mapFile string) (err error) {
err = c.Constellation.LoadFile(dagFile)
if err != nil {
return err
}
err = m.BuildMapService.LoadMapFromFile(mapFile)
return
}
//LoadFromString takes a string dag and map and loads them
func (m *ComposeService) LoadFromString(dagString string, mapString string) (err error) {
err = m.DagConfigService.LoadDagConfigFromString(dagString)
if err != nil {
return
}
err = m.BuildMapService.LoadMapFromString(mapString)
return
}
//NewComposeService constructs a new compose service
func NewComposeService() ComposeService {
s := ComposeService{}
s.DagConfigService = dagconfigservice.NewDagConfigService()
s.BuildMapService = buildmapservice.NewBuildMapService()
return s
return c.Mapper.LoadFile(mapFile)
}

Просмотреть файл

@ -0,0 +1,113 @@
package compose_test
import (
"github.com/microsoft/abstrakt/internal/compose"
helper "github.com/microsoft/abstrakt/tools/test"
"github.com/stretchr/testify/assert"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestComposeService(t *testing.T) {
_, _, tdir := helper.PrepareRealFilesForTest(t)
defer helper.CleanTempTestFiles(t, tdir)
comp := new(compose.Composer)
_, err := comp.Build("test", tdir)
assert.Error(t, err, "Compose should fail if not yet loaded")
dag := "testdata/constellation.yaml"
mapper := "testdata/mapper.yaml"
_ = comp.LoadFile(dag, mapper)
h, err := comp.Build("test", tdir)
assert.NoError(t, err, "Compose should have loaded")
_ = chartutil.SaveDir(h, tdir)
h, _ = loader.LoadDir(tdir)
contentBytes, err := ioutil.ReadFile("testdata/values.yaml")
assert.NoError(t, err)
for _, raw := range h.Raw {
if raw.Name == "test/values.yaml" {
assert.Equal(t, string(contentBytes), string(raw.Data))
}
}
}
func TestHelmLibCompose(t *testing.T) {
_, _, tdir := helper.PrepareRealFilesForTest(t)
defer helper.CleanTempTestFiles(t, tdir)
c, err := chartutil.Create("foo", tdir)
if err != nil {
assert.FailNow(t, err.Error())
}
dir := filepath.Join(tdir, "foo")
mychart, err := loader.LoadDir(c)
if err != nil {
assert.FailNowf(t, "Failed to load newly created chart %q: %s", c, err)
}
assert.Equalf(t, "foo", mychart.Name(), "Expected name to be 'foo', got %q", mychart.Name())
for _, f := range []string{
chartutil.ChartfileName,
chartutil.DeploymentName,
chartutil.HelpersName,
chartutil.IgnorefileName,
chartutil.NotesName,
chartutil.ServiceAccountName,
chartutil.ServiceName,
chartutil.TemplatesDir,
chartutil.TemplatesTestsDir,
chartutil.TestConnectionName,
chartutil.ValuesfileName,
} {
_, err := os.Stat(filepath.Join(dir, f))
assert.NoError(t, err)
}
mychart.Values["Jordan"] = "testing123"
deps := []*chart.Dependency{
{Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"},
{Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"},
}
t.Logf("Directory: %v", tdir)
mychart.Metadata.Dependencies = deps
_ = chartutil.SaveDir(mychart, filepath.Join(tdir, "anotheretst"))
}
func TestLoadFromString(t *testing.T) {
comp := new(compose.Composer)
dag := "testdata/constellation.yaml"
mapper := "testdata/mapper.yaml"
err := comp.LoadFile(dag, mapper)
assert.NoErrorf(t, err, "Error: %v", err)
err = comp.LoadFile("sfdsd", mapper)
assert.Error(t, err, "Didn't get error when should")
err = comp.LoadFile(dag, "sdfsdf")
assert.Error(t, err, "Didn't get error when should")
}

38
internal/compose/testdata/constellation.yaml поставляемый Normal file
Просмотреть файл

@ -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: {}

15
internal/compose/testdata/mapper.yaml поставляемый Normal file
Просмотреть файл

@ -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"

39
internal/compose/testdata/values.yaml поставляемый Normal file
Просмотреть файл

@ -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),
},
},
}

199
internal/diff/diff.go Normal file
Просмотреть файл

@ -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\""
}
}

110
internal/diff/diff_test.go Normal file
Просмотреть файл

@ -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" ];
}
`

48
internal/diff/testdata/modified.yaml поставляемый Normal file
Просмотреть файл

@ -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: {}

38
internal/diff/testdata/original.yaml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,38 @@
Name: "Azure Event Hubs Sample"
Id: "d6e4a5e9-696a-4626-ba7a-534d6ff450a5"
Services:
- Name: "Event Generator"
Id: "9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"
Type: "EventGenerator"
Properties: {}
- Name: "Azure Event Hub"
Id: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
Type: "EventHub"
Properties: {}
- Name: "Event Logger"
Id: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
Type: "EventLogger"
Properties: {}
- Name: "Event Logger"
Id: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
Type: "EventLogger"
Properties: {}
Relationships:
- Name: "Generator to Event Hubs Link"
Id: "211a55bd-5d92-446c-8be8-190f8f0e623e"
Description: "Event Generator to Event Hub connection"
From: "9e1bcb3d-ff58-41d4-8779-f71e7b8800f8"
To: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
Properties: {}
- Name: "Event Hubs to Event Logger Link"
Id: "08ccbd67-456f-4349-854a-4e6959e5017b"
Description: "Event Hubs to Event Logger connection"
From: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
To: "1d0255d4-5b8c-4a52-b0bb-ac024cda37e5"
Properties: {}
- Name: "Event Hubs to Event Logger Link Repeat"
Id: "c8a719e0-164d-408f-9ed1-06e08dc5abbe"
Description: "Event Hubs to Event Logger connection"
From: "3aa1e546-1ed5-4d67-a59c-be0d5905b490"
To: "a268fae5-2a82-4a3e-ada7-a52eeb7019ac"
Properties: {}

Просмотреть файл

@ -1,4 +1,4 @@
package chartservice
package chart
import (
"bytes"
@ -17,8 +17,8 @@ description: A Helm chart for Kubernetes
version: 4.3.2
home: ""`
//CreateChart makes a new chart at the specified location
func CreateChart(name string, dir string) (chartReturn *chart.Chart, err error) {
//Create makes a new chart at the specified location
func Create(name string, dir string) (chartReturn *chart.Chart, err error) {
tdir, err := ioutil.TempDir("./", "output-")
if err != nil {
@ -77,8 +77,8 @@ func CreateChart(name string, dir string) (chartReturn *chart.Chart, err error)
return
}
// LoadChartFromDir loads a Helm chart from the specified director
func LoadChartFromDir(dir string) (*chart.Chart, error) {
// LoadFromDir loads a Helm chart from the specified director
func LoadFromDir(dir string) (*chart.Chart, error) {
h, err := loader.LoadDir(dir)
if err != nil {
@ -88,18 +88,18 @@ func LoadChartFromDir(dir string) (*chart.Chart, error) {
return h, nil
}
// SaveChartToDir takes the chart object and saves it as a set of files in the specified director
func SaveChartToDir(chart *chart.Chart, dir string) error {
// SaveToDir takes the chart object and saves it as a set of files in the specified director
func SaveToDir(chart *chart.Chart, dir string) error {
return chartutil.SaveDir(chart, dir)
}
// ZipChartToDir compresses the chart and saves it in compiled format
func ZipChartToDir(chart *chart.Chart, dir string) (string, error) {
// ZipToDir compresses the chart and saves it in compiled format
func ZipToDir(chart *chart.Chart, dir string) (string, error) {
return chartutil.Save(chart, dir)
}
// BuildChart download charts
func BuildChart(dir string) (out *bytes.Buffer, err error) {
// Build download charts
func Build(dir string) (out *bytes.Buffer, err error) {
out = &bytes.Buffer{}

Просмотреть файл

@ -1,4 +1,4 @@
package chartservice
package chart_test
import (
"archive/tar"
@ -6,6 +6,8 @@ import (
"bytes"
"compress/gzip"
"flag"
"github.com/microsoft/abstrakt/internal/platform/chart"
"github.com/stretchr/testify/assert"
"io"
"io/ioutil"
"os"
@ -25,38 +27,38 @@ func TestUpdate(t *testing.T) {
err := os.RemoveAll("testdata/golden")
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
err = os.MkdirAll("testdata/golden/helm/charts", os.ModePerm)
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
err = exec.Command("cp", "-r", "testdata/sample/helm", "testdata/golden/").Run()
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
// Create zipped dependant charts
err = exec.Command("tar", "cfz", "testdata/golden/helm/charts/event_hub_sample_event_generator-1.0.0.tgz", "testdata/sample/deps/event_hub_sample_event_generator/").Run()
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
err = exec.Command("tar", "cfz", "testdata/golden/helm/charts/event_hub_sample_event_hub-1.0.0.tgz", "testdata/sample/deps/event_hub_sample_event_hub/").Run()
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
err = exec.Command("tar", "cfz", "testdata/golden/helm/charts/event_hub_sample_event_logger-1.0.0.tgz", "testdata/sample/deps/event_hub_sample_event_logger/").Run()
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
err = exec.Command("tar", "cfz", "testdata/golden/test-0.1.0.tgz", "testdata/sample/helm").Run()
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
}
@ -65,71 +67,67 @@ func TestChartSavesAndLoads(t *testing.T) {
tdir2, err2 := ioutil.TempDir("./", "output-")
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
if err2 != nil {
t.Fatal(err2)
assert.FailNow(t, err2.Error())
}
defer func() {
err = os.RemoveAll(tdir)
if err != nil {
t.Error(err)
}
err = os.RemoveAll(tdir2)
if err != nil {
t.Error(err)
}
assert.NoError(t, err)
err = os.RemoveAll(tdir2)
assert.NoError(t, err)
}()
c, err := CreateChart("foo", tdir)
c, err := chart.Create("foo", tdir)
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
err = SaveChartToDir(c, tdir2)
err = chart.SaveToDir(c, tdir2)
if err != nil {
t.Fatalf("Failed to save newly created chart %q: %s", tdir2, err)
assert.FailNowf(t, "Failed to save newly created chart %q: %s", tdir2, err)
}
newPath := filepath.Join(tdir2, "foo")
_, err = LoadChartFromDir(newPath)
_, err = chart.LoadFromDir(newPath)
if err != nil {
t.Fatalf("Failed to load newly created chart %q: %s", newPath, err)
assert.FailNowf(t, "Failed to load newly created chart %q: %s", newPath, err)
}
}
func TestChartBuildChart(t *testing.T) {
tdir, err := ioutil.TempDir("./", "output-")
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
defer func() {
err = os.RemoveAll(tdir)
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
}()
err = exec.Command("cp", "-r", "testdata/sample/helm", tdir+"/").Run()
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
err = exec.Command("cp", "-r", "testdata/sample/deps", tdir+"/").Run()
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
_, err = BuildChart(tdir + "/helm")
_, err = chart.Build(tdir + "/helm")
if err != nil {
t.Fatalf("Failed to BuildChart(): %s", err)
assert.FailNowf(t, "Failed to BuildChart(): %s", err.Error())
}
chartsDir := tdir + "/helm/charts/"
@ -142,23 +140,23 @@ func TestChartBuildChart(t *testing.T) {
func TestZipChartToDir(t *testing.T) {
tdir, err := ioutil.TempDir("./", "output-")
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
defer func() {
err = os.RemoveAll(tdir)
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
}()
chart, err := LoadChartFromDir("testdata/sample/helm")
helm, err := chart.LoadFromDir("testdata/sample/helm")
if err != nil {
t.Fatalf("Failed on LoadChartFromDir(): %s", err)
assert.FailNowf(t, "Failed on LoadChartFromDir(): %s", err.Error())
}
_, err = ZipChartToDir(chart, tdir)
_, err = chart.ZipToDir(helm, tdir)
if err != nil {
t.Fatalf("Failed on ZipChartToDir(): %s", err)
assert.FailNowf(t, "Failed on ZipChartToDir(): %s", err.Error())
}
compareFiles(t, "testdata/golden/test-0.1.0.tgz", tdir+"/test-0.1.0.tgz")
}
@ -181,30 +179,30 @@ func compareFiles(t *testing.T, expected, test string) {
func readGz(t *testing.T, file string) (out bytes.Buffer) {
f, err := os.Open(file)
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
defer func() {
err = f.Close()
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
}()
zw, err := gzip.NewReader(f)
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
defer func() {
err = zw.Close()
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
}()
writer := bufio.NewWriter(&out)
_, err = io.Copy(writer, zw)
if err != nil {
t.Fatal(err)
assert.FailNow(t, err.Error())
}
return
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше