diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..2864611
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,31 @@
+language: go
+
+#omitting this line fails build bc it tries to use TLS1.0
+# which bitbucket blocks so a clone will not go through
+dist: xenial
+
+# Force-enable Go modules. This will be unnecessary when Go 1.12 lands.
+env:
+ - GO111MODULE=on
+
+# You don't need to test on very old version of the Go compiler. It's the user's
+# responsibility to keep their compilers up to date.
+go:
+ - 1.11.x
+
+# Only clone the most recent commit.
+git:
+ depth: 1
+
+# Skip the install step. Don't `go get` dependencies. Only build with the code
+# in vendor/
+install: true
+
+# email me results if a test fails.
+notifications:
+ recipients:
+ - ddmoorish@gmail.com
+ on_success: never
+ on_failure: always
+script:
+ - go test -v ./...
diff --git a/README.md b/README.md
index a207f75..bbccdf5 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ apt-get install -y gcc make socat git wget
wget https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.10.3.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
-export GOROOT=$PATH:/usr/local/go
+export GOROOT=/usr/local/go
Set up your Go workspace, set the GOPATH to it. This is where all your Go code should be.
mkdir $HOME/goworkspace
@@ -192,7 +192,7 @@ minikube ip -- verify that minikube is up and running
wget https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.10.3.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
-export GOROOT=$PATH:/usr/local/go
+export GOROOT=/usr/local/go
Set up your Go workspace, set the GOPATH to it. This is where all your Go code should be.
mkdir $HOME/goworkspace
@@ -284,6 +284,12 @@ kubectl get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgr
```
![alt text](https://github.com/cloud-ark/kubeprovenance/raw/master/docs/bisect.png)
+
+## Running Unit Tests:
+
+1. go test -v ./...
+
+
## Troubleshooting tips:
1) Check that the API server Pod is running:
diff --git a/pkg/provenance/provenance.go b/pkg/provenance/provenance.go
index 14d553c..9aee0b8 100644
--- a/pkg/provenance/provenance.go
+++ b/pkg/provenance/provenance.go
@@ -54,6 +54,23 @@ type ProvenanceOfObject struct {
Name string
}
+// Only used when I need to order the AttributeToData map for unit testing
+type pair struct {
+ Attribute string
+ Data interface{}
+}
+type OrderedMap []pair
+
+//Similar to a map access ..
+//returns Data, ok
+func (o OrderedMap) At(attrib string) (interface{}, bool) {
+ for _, my_pair := range o {
+ if my_pair.Attribute == attrib {
+ return my_pair.Data, true
+ }
+ }
+ return nil, false
+}
func init() {
serviceHost = os.Getenv("KUBERNETES_SERVICE_HOST")
servicePort = os.Getenv("KUBERNETES_SERVICE_PORT")
@@ -94,7 +111,7 @@ func CollectProvenance() {
readKindCompositionFile()
useSample := false
if onMinikube() {
- useSample = true
+ useSample = true
parse(useSample) //using a sample audit log, because
//currently audit logging is not supported for minikube
} else {
@@ -146,19 +163,60 @@ func FindProvenanceObjectByName(name string, allObjects []ProvenanceOfObject) *P
return nil
}
+// This String function must return the same output upon
+// different calls. So I am doing ordering and sorting
+// here because map is unordered and gives random outputs
+// when printing
func (s *Spec) String() string {
var b strings.Builder
fmt.Fprintf(&b, "Version: %d\n", s.Version)
- for attribute, data := range s.AttributeToData {
- fmt.Fprintf(&b, "%s: %s\n", attribute, data)
+
+ var keys []string
+ for k, _ := range s.AttributeToData {
+ keys = append(keys, k)
}
+ sort.Strings(keys)
+ for _, attribute := range keys {
+ data := s.AttributeToData[attribute]
+ integer, ok := data.(int)
+ if ok {
+ fmt.Fprintf(&b, " %s: %d\n", attribute, integer)
+ continue
+ }
+ v, ok := data.([]map[string]string)
+ if ok {
+ fmt.Fprintf(&b, " %s: [", attribute)
+ for _, innermap := range v {
+ fmt.Fprintf(&b, " map[")
+ var innerkeys []string
+ for k, _ := range innermap {
+ innerkeys = append(innerkeys, k)
+ }
+ sort.Strings(innerkeys)
+ var strs []string
+ for _, k1 := range innerkeys {
+ strs = append(strs, fmt.Sprintf("%s: %s", k1, innermap[k1]))
+ }
+ fmt.Fprintf(&b, strings.Join(strs, " "))
+ fmt.Fprintf(&b, "] ")
+ }
+ fmt.Fprintf(&b, "]\n")
+ } else {
+ fmt.Fprintf(&b, " %s: %s\n", attribute, data)
+ }
+ }
+
return b.String()
}
+// Returns the string representation of ObjectLineage
+// used in GetHistory
func (o ObjectLineage) String() string {
var b strings.Builder
- for version, spec := range o {
- fmt.Fprintf(&b, "Version: %d Data: %s\n", version, spec.String())
+ specs := getSpecsInOrder(o)
+
+ for _, spec := range specs {
+ fmt.Fprintf(&b, spec.String())
}
return b.String()
}
@@ -185,30 +243,33 @@ func (o ObjectLineage) GetVersions() string {
for _, spec := range specs {
outputs = append(outputs, fmt.Sprintf("%s: Version %d", spec.Timestamp, spec.Version)) //cast int to string
}
- return "[" + strings.Join(outputs, ", \n") + "]\n"
+ return "[" + strings.Join(outputs, ",\n") + "]\n"
}
-func (o ObjectLineage) SpecHistory() string {
+//https://stackoverflow.com/questions/23330781/sort-go-map-values-by-keys
+func (o ObjectLineage) stringInterval(s, e int) string {
+ var b strings.Builder
specs := getSpecsInOrder(o)
- specStrings := make([]string, 0)
+
for _, spec := range specs {
- specStrings = append(specStrings, spec.String())
+ if spec.Version >= s && spec.Version <= e {
+ fmt.Fprintf(&b, spec.String())
+ }
}
- return strings.Join(specStrings, "\n")
+ return b.String()
+}
+func (o ObjectLineage) SpecHistory() string {
+ return o.String()
}
func (o ObjectLineage) SpecHistoryInterval(vNumStart, vNumEnd int) string {
- specs := getSpecsInOrder(o)
- specStrings := make([]string, 0)
- for _, spec := range specs {
- if spec.Version >= vNumStart && spec.Version <= vNumEnd {
- specStrings = append(specStrings, spec.String())
- }
+ if vNumStart < 0 {
+ return "Invalid start parameter"
}
- return strings.Join(specStrings, "\n")
+ return o.stringInterval(vNumStart, vNumEnd)
}
func buildAttributeRelationships(specs []Spec, allQueryPairs [][]string) map[string][][]string {
- // A map from top level attribute to array of pairs (represented as 2 len array)
+ // Returns a map from top level attribute to array of pairs (represented as 2 len array)
// mapRelationships with one top level object users, looks like this:
// ex: map[users:[[username pallavi] [password pass123]]]
mapRelationships := make(map[string][][]string, 0)
@@ -290,8 +351,9 @@ func buildQueryPairsSlice(queryArgMap map[string]string) ([][]string, error) {
//field value pairs, searched based on the type.
func (o ObjectLineage) Bisect(argMap map[string]string) string {
specs := getSpecsInOrder(o)
-
allQueryPairs, err := buildQueryPairsSlice(argMap)
+ fmt.Printf("Query Attributes Map: %v\n", allQueryPairs)
+
if err != nil {
return err.Error()
}
@@ -303,7 +365,7 @@ func (o ObjectLineage) Bisect(argMap map[string]string) string {
// ask if username ever is daniel and password is ever 223843, because
// it could find that in different parts of the spec. They both must be satisfied in the same map object
mapRelationships := buildAttributeRelationships(specs, allQueryPairs)
- fmt.Printf("Query Attributes Map:%v\n", mapRelationships)
+ fmt.Printf("Query Attributes Same-parent-relationships: %v\n", mapRelationships)
// fmt.Println(specs)
for _, spec := range specs {
@@ -347,7 +409,7 @@ func (o ObjectLineage) Bisect(argMap map[string]string) string {
}
andGate = append(andGate, satisfied)
}
- fmt.Printf("Result of checking all attributes:%v\n", andGate)
+ // fmt.Printf("Result of checking all attributes: %v\n", andGate)
allTrue := all(andGate)
//all indexes in andGate must be true
if allTrue {
@@ -357,7 +419,7 @@ func (o ObjectLineage) Bisect(argMap map[string]string) string {
return "No version found that matches the query."
}
-//this is for a field like deploymentName where the underyling state or data is a string
+//this is for a field like deploymentName where the underlying state or data is a string
func handleTrivialFields(qkey, qval, mkey, fieldData string) bool {
if qkey == mkey && qval == fieldData {
return true
@@ -377,6 +439,22 @@ func handleSimpleFields(vStringSlice []string, qkey, qval, mkey string) bool {
return satisfied
}
func handleCompositeFields(vSliceMap []map[string]string, mapRelationships map[string][][]string, qkey, qval, mkey string) bool {
+ isPossible := false
+ for _, m := range vSliceMap {
+ for k, _ := range m {
+ if k == qkey {
+ isPossible = true
+ }
+ }
+ }
+ if !isPossible {
+ //for example databases qkey will never pass a
+ //composite field test since it is never a key under
+ //the map vSliceMap
+ // fmt.Printf("NOT POSSIBLE, %s %s\n", qkey, mkey)
+ return false
+ }
+
for _, mymap := range vSliceMap { //looping through each elem in the mapSlice
// check if there is any
// other necessary requirements for this mkey.
@@ -392,7 +470,6 @@ func handleCompositeFields(vSliceMap []map[string]string, mapRelationships map[s
// find all the related attributes associated with the top-level specs
// attribute mkey. this would be users for the postgres crd example.
attributeCombinedQuery, ok := mapRelationships[mkey]
-
if ok { //ensure multiple attributes are jointly satisfied
jointQueryResults := make([]bool, 0)
//jointQueryResults is a boolean slice that represents the satisfiability
@@ -484,69 +561,126 @@ func compareMaps(mapSlice1, mapSlice2 []map[string]string) bool {
}
return all(foundMatches)
}
+
+//Need some way to bring order to the elements of the AttributeToData map,
+// because otherwise, the output is randomly ordered and I cannot unit test that.
+// so This method orders the map based on the Attribute key and is similar
+// to C++'s pair.
+func (s Spec) OrderedPairs() OrderedMap {
+ var keys []string
+ for k, _ := range s.AttributeToData {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ orderedRet := make(OrderedMap, 0)
+ for _, k1 := range keys {
+ p := pair{}
+ p.Attribute = k1
+ p.Data = s.AttributeToData[k1]
+ orderedRet = append(orderedRet, p)
+ }
+ return orderedRet
+}
func (o ObjectLineage) FullDiff(vNumStart, vNumEnd int) string {
var b strings.Builder
- sp1 := o[vNumStart]
- sp2 := o[vNumEnd]
- for attribute, data1 := range sp1.AttributeToData {
- data2, ok := sp2.AttributeToData[attribute] //check if the attribute even exists
+ sp1 := o[vNumStart].OrderedPairs()
+ sp2 := o[vNumEnd].OrderedPairs()
+ for _, my_pair := range sp1 {
+ attr := my_pair.Attribute
+ data1 := my_pair.Data
+ data2, ok := sp2.At(attr) //check if the attribute even exists
if ok {
- getDiff(&b, attribute, data1, data2, vNumStart, vNumEnd)
+ getDiff(&b, attr, data1, data2, vNumStart, vNumEnd)
} else { //for the case where a key exists in spec 1 that doesn't exist in spec 2
- fmt.Fprintf(&b, "Found diff on attribute %s:\n", attribute)
- fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumStart, data1)
- fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumEnd, "No attribute found.")
+ fmt.Fprintf(&b, "Found diff on attribute %s:\n", attr)
+ fmt.Fprintf(&b, " Version %d: %s\n", vNumStart, data1)
+ fmt.Fprintf(&b, " Version %d: %s\n", vNumEnd, "No attribute found.")
}
}
//for the case where a key exists in spec 2 that doesn't exist in spec 1
- for attribute, data1 := range sp2.AttributeToData {
- if _, ok := sp2.AttributeToData[attribute]; !ok {
- fmt.Fprintf(&b, "Found diff on attribute %s:\n", attribute)
- fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumStart, "No attribute found.")
- fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumEnd, data1)
+ for _, my_pair := range sp2 {
+ attr := my_pair.Attribute
+ data1 := my_pair.Data
+ if _, ok := sp2.At(attr); !ok {
+ fmt.Fprintf(&b, "Found diff on attribute %s:\n", attr)
+ fmt.Fprintf(&b, " Version %d: %s\n", vNumStart, "No attribute found.")
+ fmt.Fprintf(&b, " Version %d: %s\n", vNumEnd, data1)
}
}
return b.String()
}
+func orderInnerMaps(m []map[string]string) []OrderedMap {
+ orderedMaps := make([]OrderedMap, 0)
+ for _, mi := range m {
+ var keys []string
+ for k, _ := range mi {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ orderedRet := make(OrderedMap, 0)
+ for _, k1 := range keys {
+ p := pair{}
+ p.Attribute = k1
+ p.Data = mi[k1]
+ orderedRet = append(orderedRet, p)
+ }
+ orderedMaps = append(orderedMaps, orderedRet)
+ }
+
+ return orderedMaps
+}
func getDiff(b *strings.Builder, fieldName string, data1, data2 interface{}, vNumStart, vNumEnd int) string {
str1, ok1 := data1.(string)
str2, ok2 := data2.(string)
if ok1 && ok2 && str1 != str2 {
fmt.Fprintf(b, "Found diff on attribute %s:\n", fieldName)
- fmt.Fprintf(b, "\tVersion %d: %s\n", vNumStart, data1)
- fmt.Fprintf(b, "\tVersion %d: %s\n", vNumEnd, data2)
+ fmt.Fprintf(b, " Version %d: %s\n", vNumStart, data1)
+ fmt.Fprintf(b, " Version %d: %s\n", vNumEnd, data2)
}
int1, ok1 := data1.(int)
int2, ok2 := data2.(int)
if ok1 && ok2 && int1 != int2 {
fmt.Fprintf(b, "Found diff on attribute %s:\n", fieldName)
- fmt.Fprintf(b, "\tVersion %d: %s\n", vNumStart, data1)
- fmt.Fprintf(b, "\tVersion %d: %s\n", vNumEnd, data2)
+ fmt.Fprintf(b, " Version %d: %d\n", vNumStart, int1)
+ fmt.Fprintf(b, " Version %d: %d\n", vNumEnd, int2)
}
strArray1, ok1 := data1.([]string)
strArray2, ok2 := data2.([]string)
if ok1 && ok2 {
- for _, str := range strArray1 {
- found := false
- for _, val := range strArray2 {
- if str == val {
- found = true
+ sort.Strings(strArray1)
+ sort.Strings(strArray2)
+
+ //When the arrays are not the same len, found a difference.
+ if len(strArray1) != len(strArray2) {
+ fmt.Fprintf(b, "Found diff on attribute %s:\n", fieldName)
+ fmt.Fprintf(b, " Version %d: %s\n", vNumStart, strArray1)
+ fmt.Fprintf(b, " Version %d: %s\n", vNumEnd, strArray2)
+ } else {
+ for _, val1 := range strArray1 {
+ found := false
+ for _, val2 := range strArray2 {
+ if val1 == val2 {
+ found = true
+ }
+ }
+ if !found { // if an element does not have a match in the next version
+ fmt.Fprintf(b, "Found diff on attribute %s:\n", fieldName)
+ fmt.Fprintf(b, " Version %d: %s\n", vNumStart, strArray1)
+ fmt.Fprintf(b, " Version %d: %s\n", vNumEnd, strArray2)
+ break
}
- }
- if !found { // if an element does not have a match in the next version
- fmt.Fprintf(b, "Found diff on attribute %s:\n", fieldName)
- fmt.Fprintf(b, "\tVersion %d: %s\n", vNumStart, strArray1)
- fmt.Fprintf(b, "\tVersion %d: %s\n", vNumEnd, strArray2)
}
}
}
strMap1, ok1 := data1.([]map[string]string)
strMap2, ok2 := data2.([]map[string]string)
if ok1 && ok2 {
+ orderedInnerMap1 := orderInnerMaps(strMap1)
+ orderedInnerMap2 := orderInnerMaps(strMap2)
if !compareMaps(strMap1, strMap2) {
fmt.Fprintf(b, "Found diff on attribute %s:\n", fieldName)
- fmt.Fprintf(b, "\tVersion %d: %s\n", vNumStart, strMap1)
- fmt.Fprintf(b, "\tVersion %d: %s\n", vNumEnd, strMap2)
+ fmt.Fprintf(b, " Version %d: %s\n", vNumStart, orderedInnerMap1)
+ fmt.Fprintf(b, " Version %d: %s\n", vNumEnd, orderedInnerMap2)
}
}
@@ -554,6 +688,8 @@ func getDiff(b *strings.Builder, fieldName string, data1, data2 interface{}, vNu
}
func (o ObjectLineage) FieldDiff(fieldName string, vNumStart, vNumEnd int) string {
var b strings.Builder
+ //Since this is a single field, do not have to do the OrderedMap business like the FullDiff.
+ //Same outp everytime
data1, ok1 := o[vNumStart].AttributeToData[fieldName]
data2, ok2 := o[vNumEnd].AttributeToData[fieldName]
switch {
@@ -561,12 +697,12 @@ func (o ObjectLineage) FieldDiff(fieldName string, vNumStart, vNumEnd int) strin
return getDiff(&b, fieldName, data1, data2, vNumStart, vNumEnd)
case !ok1 && ok2:
fmt.Fprintf(&b, "Found diff on attribute %s:\n", fieldName)
- fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumStart, "No attribute found.")
- fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumEnd, data2)
+ fmt.Fprintf(&b, " Version %d: %s\n", vNumStart, "No attribute found.")
+ fmt.Fprintf(&b, " Version %d: %s\n", vNumEnd, data2)
case ok1 && !ok2:
fmt.Fprintf(&b, "Found diff on attribute %s:\n", fieldName)
- fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumStart, data1)
- fmt.Fprintf(&b, "\tVersion %d: %s\n", vNumEnd, "No attribute found.")
+ fmt.Fprintf(&b, " Version %d: %s\n", vNumStart, data1)
+ fmt.Fprintf(&b, " Version %d: %s\n", vNumEnd, "No attribute found.")
case !ok1 && !ok2:
fmt.Fprintf(&b, "Attribute not found in either version %d or %d", vNumStart, vNumEnd)
}
@@ -576,24 +712,24 @@ func (o ObjectLineage) FieldDiff(fieldName string, vNumStart, vNumEnd int) strin
//Ref:https://www.sohamkamani.com/blog/2017/10/18/parsing-json-in-golang/#unstructured-data
func parse(useSample bool) {
- var log *os.File
+ var log *os.File
var err error
- if useSample {
- log, err = os.Open("/tmp/minikube-sample-audit.log")
- if err != nil {
- fmt.Println(fmt.Sprintf("could not open the log file %s", err))
- panic(err)
- }
+ if useSample {
+ log, err = os.Open("/tmp/minikube-sample-audit.log")
+ if err != nil {
+ fmt.Println(fmt.Sprintf("could not open the log file %s", err))
+ panic(err)
+ }
} else {
- if _, err := os.Stat("/tmp/kube-apiserver-audit.log"); os.IsNotExist(err) {
- fmt.Println(fmt.Sprintf("could not stat the path %s", err))
- panic(err)
- }
- log, err = os.Open("/tmp/kube-apiserver-audit.log")
- if err != nil {
- fmt.Println(fmt.Sprintf("could not open the log file %s", err))
- panic(err)
- }
+ if _, err := os.Stat("/tmp/kube-apiserver-audit.log"); os.IsNotExist(err) {
+ fmt.Println(fmt.Sprintf("could not stat the path %s", err))
+ panic(err)
+ }
+ log, err = os.Open("/tmp/kube-apiserver-audit.log")
+ if err != nil {
+ fmt.Println(fmt.Sprintf("could not open the log file %s", err))
+ panic(err)
+ }
}
defer log.Close()
diff --git a/pkg/provenance/provenance_test.go b/pkg/provenance/provenance_test.go
new file mode 100644
index 0000000..10b6dcb
--- /dev/null
+++ b/pkg/provenance/provenance_test.go
@@ -0,0 +1,472 @@
+package provenance
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "testing"
+ // "time"
+
+ "github.com/jinzhu/copier"
+ yaml "gopkg.in/yaml.v2"
+)
+
+//https://stackoverflow.com/questions/30947534/reading-a-yaml-file-in-golang
+type conf struct {
+ HistoryTest1 string `yaml:"HistoryTest1"`
+ HistoryIntervalTest1 string `yaml:"HistoryIntervalTest1"`
+ TestFullDiff1 string `yaml:"TestFullDiff1"`
+ TestFullDiff2 string `yaml:"TestFullDiff2"`
+ TestFullDiff3 string `yaml:"TestFullDiff3"`
+ TestFieldDiff1 string `yaml:"TestFieldDiff1"`
+ TestFieldDiff2 string `yaml:"TestFieldDiff2"`
+ TestFieldDiff3 string `yaml:"TestFieldDiff3"`
+ TestGetVersion1 string `yaml:"TestGetVersion1"`
+}
+
+// Parses an expected-output file 'conf' into some fields,
+// which will be used during Unit tests
+func (c *conf) getConf() *conf {
+
+ yamlFile, err := ioutil.ReadFile("tests.yaml")
+ if err != nil {
+ log.Printf("yamlFile.Get err #%v ", err)
+ }
+ err = yaml.Unmarshal(yamlFile, c)
+ if err != nil {
+ log.Fatalf("Unmarshal: %v", err)
+ }
+ return c
+}
+
+type Args struct {
+ Version int
+ DeploymentName string
+ Image string
+ Replicas int
+ Users []map[string]string
+ Databases []string
+}
+
+func addUser(users []map[string]string, user, pass string) []map[string]string {
+
+ s := make(map[string]string, 0)
+ s["username"] = user
+ s["password"] = pass
+ users = append(users, s)
+ return users
+}
+func updateUser(users []map[string]string, user, newPass string) []map[string]string {
+ copied := make([]map[string]string, len(users))
+ copy(copied, users)
+ for _, u := range copied {
+ found := u["username"] == user
+ if found {
+ u["password"] = newPass
+ }
+ }
+ return copied
+}
+func addDatabase(databases []string, database string) []string {
+ for _, d := range databases {
+ //already exists
+ if database == d {
+ return databases
+ }
+ }
+
+ databases = append(databases, database)
+ return databases
+}
+func removeDatabase(databases []string, database string) []string {
+
+ for i, d := range databases {
+ if d == database {
+ return append(databases[:i], databases[i+1:]...)
+ }
+ }
+ //did not find the database to remove
+ return databases
+}
+
+func makeSpec(a Args) Spec {
+ attributeToData := make(map[string]interface{}, 0)
+ attributeToData["deploymentName"] = a.DeploymentName
+ attributeToData["image"] = a.Image
+ attributeToData["replicas"] = a.Replicas
+ attributeToData["users"] = a.Users
+ attributeToData["databases"] = a.Databases
+ s := Spec{}
+ s.Version = a.Version
+ // Using a filler date for unit testing
+ s.Timestamp = "2006-01-02 15:04:05"
+ s.AttributeToData = attributeToData
+ return s
+}
+
+// Initializes an Args with some data
+func initArgs() Args {
+ ds := make([]string, 0)
+ ds = append(ds, "servers")
+ ds = append(ds, "staff")
+
+ users := make([]map[string]string, 0)
+ u1 := make(map[string]string, 0)
+ u1["username"] = "daniel"
+ u1["password"] = "4557832+#^"
+
+ u2 := make(map[string]string, 0)
+ u2["username"] = "kulkarni"
+ u2["password"] = "5*7832!@$"
+
+ users = append(users, u1)
+ users = append(users, u2)
+ a := Args{}
+ a.Users = users
+ a.Databases = ds
+ a.Image = "postgres:9.3"
+ a.Replicas = 3
+ a.Version = 1
+ a.DeploymentName = "MyDeployment"
+
+ return a
+}
+
+// This method is meant to deep copy the Args datastructure.
+// copier deep copies fields that are simple, but I had
+// to deep copy maps and slices myself.
+func deepCopyArgs(arg Args) Args {
+ nArgs := Args{}
+ err := copier.Copy(&nArgs, &arg)
+ if err != nil {
+ fmt.Printf("Error doing a deep copy of Args: %s", err)
+ return nArgs
+ }
+
+ //deep copying for maps,
+ // tried two popular libraries and they didn't work..
+ newUsers := make([]map[string]string, 0)
+ for _, m := range nArgs.Users {
+ u := make(map[string]string, 0)
+ for k, v := range m {
+ u[k] = v
+ }
+ newUsers = append(newUsers, u)
+
+ }
+ newDb := make([]string, 0)
+ for _, m := range nArgs.Databases {
+ newDb = append(newDb, m)
+ }
+ //The above code successfully made a deep copy of the map
+ nArgs.Users = newUsers
+ nArgs.Databases = newDb
+ return nArgs
+}
+
+//This method builds some lineage data
+func buildLineage() (ObjectLineage, Args) {
+ objectLineage := make(map[int]Spec)
+ args := initArgs()
+ spec1 := makeSpec(args)
+ objectLineage[spec1.Version] = spec1
+
+ args = deepCopyArgs(args)
+ users2 := addUser(args.Users, "johnson", "fluffy23")
+ args.Users = users2
+ args.Version = 2
+ spec2 := makeSpec(args)
+ objectLineage[spec2.Version] = spec2
+
+ args = deepCopyArgs(args)
+ users3 := addUser(args.Users, "thomas", "bedsheet85")
+ args.Users = users3
+ args.Version = 3
+ spec3 := makeSpec(args)
+ objectLineage[spec3.Version] = spec3
+
+ args = deepCopyArgs(args)
+ db1 := addDatabase(args.Databases, "logging")
+ args.Databases = db1
+ args.Version = 4
+ spec4 := makeSpec(args)
+ objectLineage[spec4.Version] = spec4
+
+ args = deepCopyArgs(args)
+ db2 := addDatabase(args.Databases, "demographics")
+ args.Databases = db2
+ args.Version = 5
+ spec5 := makeSpec(args)
+ objectLineage[spec5.Version] = spec5
+ args = deepCopyArgs(args)
+ return objectLineage, args
+}
+
+// This method is testing that the joint functionality of Bisect works
+// with a complex query involving a complex query of 3 fields
+func TestBisectGeneral(t *testing.T) {
+ objLineage, _ := buildLineage()
+ argMapTest := make(map[string]string, 0)
+ argMapTest["field1"] = "databases"
+ argMapTest["value1"] = "demographics"
+ argMapTest["field2"] = "username"
+ argMapTest["value2"] = "daniel"
+ argMapTest["field3"] = "password"
+ argMapTest["value3"] = "4557832+#^"
+
+ vOutput := objLineage.Bisect(argMapTest)
+ expected := "Version: 5"
+ if vOutput != expected {
+ t.Errorf("Version output for TestBisectGeneral() was incorrect, got: %s, want: %s.\n", vOutput, expected)
+ }
+}
+
+// This method changes a user's password, and is testing
+// whether the bisect will take a query with the same
+// parent element (user), and process a complicated
+// query involving maps.
+func TestBisectPasswordChanged(t *testing.T) {
+ objLineage, newArgs := buildLineage()
+
+ users := updateUser(newArgs.Users, "daniel", "JEK873BUL!")
+ newArgs.Users = users
+ newArgs.Version = 6
+ spec6 := makeSpec(newArgs)
+ objLineage[spec6.Version] = spec6
+ argMapTest := make(map[string]string, 0)
+ argMapTest["field1"] = "username"
+ argMapTest["value1"] = "daniel"
+ argMapTest["field2"] = "password"
+ argMapTest["value2"] = "JEK873BUL!"
+
+ vOutput := objLineage.Bisect(argMapTest)
+ expected := "Version: 6"
+ if vOutput != expected {
+ t.Errorf("Version output for TestBisectPasswordChanged() was incorrect, got: %s, want: %s.\n", vOutput, expected)
+ }
+}
+
+// This method adds a new databases, admins, and
+// is testing Bisect on a slice type.
+func TestBisectAddDatabase(t *testing.T) {
+ objLineage, newArgs := buildLineage()
+
+ db := addDatabase(newArgs.Databases, "admins")
+ newArgs.Databases = db
+ newArgs.Version = 6
+ spec6 := makeSpec(newArgs)
+ objLineage[spec6.Version] = spec6
+
+ argMapTest := make(map[string]string, 0)
+ argMapTest["field1"] = "databases"
+ argMapTest["value1"] = "admins"
+
+ vOutput := objLineage.Bisect(argMapTest)
+ expected := "Version: 6"
+ if vOutput != expected {
+ t.Errorf("Version output for TestBisectAddDatabase() was incorrect, got: %s, want: %s.", vOutput, expected)
+ }
+}
+
+// This method adds a new user, Stevejobs, and
+// ensures that a bisect query will see this.
+func TestBisectAddUser(t *testing.T) {
+ objLineage, newArgs := buildLineage()
+ users := addUser(newArgs.Users, "Stevejobs", "apple468")
+ newArgs.Users = users
+ newArgs.Version = 6
+ spec6 := makeSpec(newArgs)
+ objLineage[spec6.Version] = spec6
+
+ argMapTest := make(map[string]string, 0)
+ argMapTest["field1"] = "username"
+ argMapTest["value1"] = "Stevejobs"
+ argMapTest["field2"] = "password"
+ argMapTest["value2"] = "apple468"
+
+ vOutput := objLineage.Bisect(argMapTest)
+ expected := "Version: 6"
+ if vOutput != expected {
+ t.Errorf("Version output for TestBisectAddUser() was incorrect, got: %s, want: %s.", vOutput, expected)
+ }
+}
+
+// This Unit test changes a string field and ensures that the Bisect
+// will pick up this change.
+func TestBisectChangeDeploymentName(t *testing.T) {
+ objLineage, newArgs := buildLineage()
+ newArgs.DeploymentName = "Deployment66"
+ newArgs.Version = 6
+ spec6 := makeSpec(newArgs)
+ objLineage[spec6.Version] = spec6
+
+ argMapTest := make(map[string]string, 0)
+ argMapTest["field1"] = "deploymentName"
+ argMapTest["value1"] = "Deployment66"
+
+ vOutput := objLineage.Bisect(argMapTest)
+ expected := "Version: 6"
+ if vOutput != expected {
+ t.Errorf("Version output for TestBisectChangeDeploymentName() was incorrect, got: %s, want: %s.\n", vOutput, expected)
+ }
+}
+func TestHistory(t *testing.T) {
+ objLineage, _ := buildLineage()
+
+ vOutput := objLineage.SpecHistory()
+ var c conf
+ c.getConf()
+ expected := c.HistoryTest1
+
+ if vOutput != expected {
+ t.Errorf("History output for TestHistory() was incorrect, got: %s, want: %s.\n", vOutput, expected)
+ }
+}
+
+func TestHistoryInterval(t *testing.T) {
+ objLineage, _ := buildLineage()
+
+ vOutput := objLineage.SpecHistoryInterval(2, 4)
+ var c conf
+ c.getConf()
+ expected := c.HistoryIntervalTest1
+
+ if vOutput != expected {
+ t.Errorf("History output for TestHistoryInterval() was incorrect, got: %s, want: %s.\n", vOutput, expected)
+ }
+}
+
+//Tests changes made to ordinary string literals
+func TestFieldDiff1(t *testing.T) {
+ objLineage, newArgs := buildLineage()
+ newArgs.DeploymentName = "Deployment66"
+ newArgs.Version = 6
+ spec6 := makeSpec(newArgs)
+ objLineage[spec6.Version] = spec6
+ output := objLineage.FieldDiff("deploymentName", 5, 6)
+ var c conf
+ c.getConf()
+ expected := c.TestFieldDiff1
+
+ if output != expected {
+ t.Errorf("Diff output for TestFieldDiff1() was incorrect, got: %s, want: %s.\n", output, expected)
+ }
+}
+
+// Tests the edge case where an element in a slice has changed and the slices are
+// the same length.
+func TestFieldDiff2(t *testing.T) {
+ objLineage, newArgs := buildLineage()
+ databasest := removeDatabase(newArgs.Databases, "demographics")
+ databases := addDatabase(databasest, "testing")
+
+ newArgs.Databases = databases
+ newArgs.Version = 6
+ spec6 := makeSpec(newArgs)
+ objLineage[spec6.Version] = spec6
+ output := objLineage.FieldDiff("databases", 5, 6)
+
+ var c conf
+ c.getConf()
+ expected := c.TestFieldDiff2
+ if output != expected {
+ t.Errorf("Diff output for TestFieldDiff2() was incorrect, got: %s, want: %s.\n", output, expected)
+ }
+}
+
+//Testing diff changes for an ordinary int alteration
+func TestFieldDiff3(t *testing.T) {
+ objLineage, newArgs := buildLineage()
+
+ newArgs.Replicas = 10
+ newArgs.Version = 6
+ spec6 := makeSpec(newArgs)
+ objLineage[spec6.Version] = spec6
+ output := objLineage.FieldDiff("replicas", 5, 6)
+
+ var c conf
+ c.getConf()
+ expected := c.TestFieldDiff3
+ if output != expected {
+ t.Errorf("Diff output for TestFieldDiff3() was incorrect, got: %s, want: %s.\n", output, expected)
+ }
+}
+
+// Tests the Diff output for altering a string field
+func TestFullDiff1(t *testing.T) {
+ objLineage, newArgs := buildLineage()
+ newArgs.DeploymentName = "Deployment66"
+ newArgs.Version = 6
+ spec6 := makeSpec(newArgs)
+ objLineage[spec6.Version] = spec6
+
+ output := objLineage.FullDiff(5, 6)
+ var c conf
+ c.getConf()
+ expected := c.TestFullDiff1
+
+ if output != expected {
+ t.Errorf("Diff output for TestFullDiff1() was incorrect, got: %s, want: %s.\n", output, expected)
+ }
+}
+
+// Tests Diff functionality for when a user is added.
+func TestFullDiff2(t *testing.T) {
+ objLineage, newArgs := buildLineage()
+ users := addUser(newArgs.Users, "Stevejobs", "apple468")
+ newArgs.Users = users
+ newArgs.Version = 6
+ spec6 := makeSpec(newArgs)
+ objLineage[spec6.Version] = spec6
+
+ output := objLineage.FullDiff(5, 6)
+ var c conf
+ c.getConf()
+ expected := c.TestFullDiff2
+
+ if output != expected {
+ t.Errorf("Diff output for TestFullDiff2() was incorrect, got: %s, want: %s.\n", output, expected)
+ }
+}
+
+// This method tests FullDiff, by making a combination of changes on a custom resource
+// In doing so, tests the edge case where slices (such as Databases) is not equal
+func TestFullDiff3(t *testing.T) {
+ objLineage, newArgs := buildLineage()
+ users := addUser(newArgs.Users, "Stevejobs", "apple468")
+ databases := addDatabase(newArgs.Databases, "testing")
+
+ newArgs.Users = users
+ newArgs.Databases = databases
+ newArgs.Version = 6
+ spec6 := makeSpec(newArgs)
+ objLineage[spec6.Version] = spec6
+ output := objLineage.FullDiff(5, 6)
+
+ var c conf
+ c.getConf()
+ expected := c.TestFullDiff3
+
+ if output != expected {
+ t.Errorf("Diff output for TestFullDiff3() was incorrect, got: %s, want: %s.\n", output, expected)
+ }
+}
+
+// Tests GetVersions for when a new version is added
+func TestGetVersion1(t *testing.T) {
+ objLineage, newArgs := buildLineage()
+ databases := addDatabase(newArgs.Databases, "testing")
+
+ newArgs.Databases = databases
+ newArgs.Version = 6
+ spec6 := makeSpec(newArgs)
+ objLineage[spec6.Version] = spec6
+ output := objLineage.GetVersions()
+ var c conf
+ c.getConf()
+ expected := c.TestGetVersion1
+
+ if output != expected {
+ t.Errorf("Versions output for TestFullDiff3() was incorrect, got: %s, want: %s.\n", output, expected)
+ }
+}
diff --git a/pkg/provenance/tests.yaml b/pkg/provenance/tests.yaml
new file mode 100644
index 0000000..cc3ed4b
--- /dev/null
+++ b/pkg/provenance/tests.yaml
@@ -0,0 +1,84 @@
+HistoryTest1: |
+ Version: 1
+ databases: [servers staff]
+ deploymentName: MyDeployment
+ image: postgres:9.3
+ replicas: 3
+ users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] ]
+ Version: 2
+ databases: [servers staff]
+ deploymentName: MyDeployment
+ image: postgres:9.3
+ replicas: 3
+ users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] map[password: fluffy23 username: johnson] ]
+ Version: 3
+ databases: [servers staff]
+ deploymentName: MyDeployment
+ image: postgres:9.3
+ replicas: 3
+ users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] map[password: fluffy23 username: johnson] map[password: bedsheet85 username: thomas] ]
+ Version: 4
+ databases: [servers staff logging]
+ deploymentName: MyDeployment
+ image: postgres:9.3
+ replicas: 3
+ users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] map[password: fluffy23 username: johnson] map[password: bedsheet85 username: thomas] ]
+ Version: 5
+ databases: [servers staff logging demographics]
+ deploymentName: MyDeployment
+ image: postgres:9.3
+ replicas: 3
+ users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] map[password: fluffy23 username: johnson] map[password: bedsheet85 username: thomas] ]
+HistoryIntervalTest1: |
+ Version: 2
+ databases: [servers staff]
+ deploymentName: MyDeployment
+ image: postgres:9.3
+ replicas: 3
+ users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] map[password: fluffy23 username: johnson] ]
+ Version: 3
+ databases: [servers staff]
+ deploymentName: MyDeployment
+ image: postgres:9.3
+ replicas: 3
+ users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] map[password: fluffy23 username: johnson] map[password: bedsheet85 username: thomas] ]
+ Version: 4
+ databases: [servers staff logging]
+ deploymentName: MyDeployment
+ image: postgres:9.3
+ replicas: 3
+ users: [ map[password: 4557832+#^ username: daniel] map[password: 5*7832!@$ username: kulkarni] map[password: fluffy23 username: johnson] map[password: bedsheet85 username: thomas] ]
+TestFullDiff1: |
+ Found diff on attribute deploymentName:
+ Version 5: MyDeployment
+ Version 6: Deployment66
+TestFieldDiff1: |
+ Found diff on attribute deploymentName:
+ Version 5: MyDeployment
+ Version 6: Deployment66
+TestFieldDiff2: |
+ Found diff on attribute databases:
+ Version 5: [demographics logging servers staff]
+ Version 6: [logging servers staff testing]
+TestFieldDiff3: |
+ Found diff on attribute replicas:
+ Version 5: 3
+ Version 6: 10
+TestFullDiff2: |
+ Found diff on attribute users:
+ Version 5: [[{password 4557832+#^} {username daniel}] [{password 5*7832!@$} {username kulkarni}] [{password fluffy23} {username johnson}] [{password bedsheet85} {username thomas}]]
+ Version 6: [[{password 4557832+#^} {username daniel}] [{password 5*7832!@$} {username kulkarni}] [{password fluffy23} {username johnson}] [{password bedsheet85} {username thomas}] [{password apple468} {username Stevejobs}]]
+TestFullDiff3: |
+ Found diff on attribute databases:
+ Version 5: [demographics logging servers staff]
+ Version 6: [demographics logging servers staff testing]
+ Found diff on attribute users:
+ Version 5: [[{password 4557832+#^} {username daniel}] [{password 5*7832!@$} {username kulkarni}] [{password fluffy23} {username johnson}] [{password bedsheet85} {username thomas}]]
+ Version 6: [[{password 4557832+#^} {username daniel}] [{password 5*7832!@$} {username kulkarni}] [{password fluffy23} {username johnson}] [{password bedsheet85} {username thomas}] [{password apple468} {username Stevejobs}]]
+TestGetVersion1: |
+ [2006-01-02 15:04:05: Version 1,
+ 2006-01-02 15:04:05: Version 2,
+ 2006-01-02 15:04:05: Version 3,
+ 2006-01-02 15:04:05: Version 4,
+ 2006-01-02 15:04:05: Version 5,
+ 2006-01-02 15:04:05: Version 6]