diff --git a/README.md b/README.md index fde1013..6767b4c 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ export PATH=$PATH:/usr/local/go/bin
curl -L https://github.com/coreos/etcd/releases/download/v3.2.18/etcd-v3.2.18-linux-amd64.tar.gz -o etcd-v3.2.18-linux-amd64.tar.gz && tar xzvf etcd-v3.2.18-linux-amd64.tar.gz && /bin/cp -f etcd-v3.2.18-linux-amd64/{etcd,etcdctl} /usr/bin && rm -rf etcd-v3.2.18-linux-amd64*
**4. Install Docker**
Follow steps here: reference: https://docs.docker.com/install/linux/docker-ce/ubuntu/#set-up-the-repository
-docker version
//check if it is installed +docker version //check if it is installed
set up your go workspace, set the GOPATH to it. this is where all your go code should be.
mkdir $HOME/goworkspace
@@ -153,10 +153,10 @@ kubectl.sh get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/pos kubectl.sh get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgreses/client25/diff?start=1&end=2&field=databases" ``` -5) Find out in which version the field 'abc' was given value 'def' +5) Find out in which version the user 'pallavi' was given password 'pass123' ``` -kubectl.sh get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgreses/client25/bisect?field=abc&value=def" +kubectl get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgreses/client25/bisect?field1=username&value1=pallavi&field2=password&value2=pass123" ``` ![alt text](https://github.com/cloud-ark/kubeprovenance/raw/master/docs/spechistory.png) ![alt text](https://github.com/cloud-ark/kubeprovenance/raw/master/docs/getdiff_databases.png) diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index dcabba4..78558b1 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -216,12 +216,14 @@ func bisect(request *restful.Request, response *restful.Response) { var provenanceInfo string provenanceInfo = "Resource Name:" + resourceName + " Resource Kind:" + resourceKind - fmt.Println(provenanceInfo) - field := request.QueryParameter("field") - value := request.QueryParameter("value") + field1 := request.QueryParameter("field1") + value1 := request.QueryParameter("value1") - provenanceInfo = provenanceInfo + " Field:" + field + "Value: " + value + "\n" + field2 := request.QueryParameter("field2") + value2 := request.QueryParameter("value2") + provenanceInfo = provenanceInfo + " Field1:" + field1 + " Value1: " + value1 + "\n" + provenanceInfo = provenanceInfo + " Field2:" + field1 + " Value2: " + value1 + "\n" fmt.Printf("ProvenanceInfo:%v", provenanceInfo) @@ -232,7 +234,7 @@ func bisect(request *restful.Request, response *restful.Response) { s := fmt.Sprintf("Could not find any provenance history for resource name: %s", resourceName) response.Write([]byte(s)) } else { - response.Write([]byte("Version: " + intendedProvObj.ObjectFullHistory.Bisect(field, value))) + response.Write([]byte("Version: " + intendedProvObj.ObjectFullHistory.Bisect(field1, value1, field2, value2))) response.Write([]byte(string("\n"))) } } diff --git a/pkg/provenance/provenance.go b/pkg/provenance/provenance.go index 728a916..42c185a 100644 --- a/pkg/provenance/provenance.go +++ b/pkg/provenance/provenance.go @@ -41,7 +41,7 @@ type Event v1beta1.Event //for example a postgres type ObjectLineage map[int]Spec type Spec struct { - AttributeToData map[string]string + AttributeToData map[string]interface{} Version int } @@ -110,7 +110,7 @@ func NewProvenanceOfObject() *ProvenanceOfObject { func NewSpec() *Spec { var s Spec - s.AttributeToData = make(map[string]string) + s.AttributeToData = make(map[string]interface{}) return &s } @@ -170,26 +170,81 @@ func (o ObjectLineage) SpecHistory() string { } return strings.Join(specs, "\n") } -func (o ObjectLineage) Bisect(field, value string) string { - s := make([]Spec, 0) - for _, spec := range o { - s = append(s, spec) +func (o ObjectLineage) Bisect(field1, value1, field2, value2 string) string { + s := make([]int, 0) + for _, value := range o { + s = append(s, value.Version) } - sort.Slice(s, func(i, j int) bool { - return s[i].Version < s[i].Version - }) - if len(s) == 1 { + sort.Ints(s) + //get all versions, sort by version, make string array of them + specs := make([]Spec, 0) + for _, version := range s { + specs = append(specs, o[version]) //cast Spec to String + } + noSpecFound := fmt.Sprintf("Bisect for field %s: %s, %s: %s was not successful. Custom resource never reached this state.", field1, value1, field2, value2) + + if len(specs) == 1 { //check - if s[0].AttributeToData[field] == value { - return strconv.Itoa(1) + + u, ok1 := specs[0].AttributeToData[field1] + if !ok1 { + fmt.Printf("Field %s not found.\n", field1) + return noSpecFound } - } - for _, v := range s { - if v.AttributeToData[field] == value { - return strconv.Itoa(v.Version) + p, ok2 := specs[0].AttributeToData[field2] + if !ok2 { + fmt.Printf("Field %s not found.\n", field2) + return noSpecFound + } + users, ok1 := p.([]string) + if !ok1 { + fmt.Printf("Type assertion failed. Underlying data is incorrect and is not a slice of strings: %s\n", u) + return noSpecFound + } + passwords, ok2 := u.([]string) + if !ok2 { + fmt.Printf("Type assertion failed. Underlying data is incorrect and is not a slice of strings: %s\n", p) + return noSpecFound + } + + for i, v := range users { + if value1 == v && passwords[i] == value2 { + return "Version: " + strconv.Itoa(1) + } + } + } else { //there is more than one spec. More than one Event found in the log + for _, spec := range specs { + //check + u, ok1 := spec.AttributeToData[field1] + if !ok1 { + fmt.Printf("Field %s not found.\n", field1) + return noSpecFound + } + p, ok2 := spec.AttributeToData[field2] + if !ok2 { + fmt.Printf("Field %s not found.\n", field2) + return noSpecFound + } + users, ok1 := p.([]string) + if !ok1 { + fmt.Printf("Type assertion failed. Underlying data is incorrect and is not a slice of strings: %s\n", u) + return noSpecFound + } + passwords, ok2 := u.([]string) + if !ok2 { + fmt.Printf("Type assertion failed. Underlying data is incorrect and is not a slice of strings: %s\n", p) + return noSpecFound + } + + for i1, v1 := range users { + if value1 == v1 && passwords[i1] == value2 { + return "Version: " + strconv.Itoa(spec.Version) + } + } } } - return strconv.Itoa(-1) + + return noSpecFound } //TODO: add optional parameters to spechistory route in apiserver.go, and call this method. @@ -359,6 +414,8 @@ func ParseRequestObject(objectProvenance *ProvenanceOfObject, requestObjBytes [] // fmt.Println(spec) if ok { fmt.Println("Successfully parsed") + } else { + fmt.Println("Unsuccessful parse") } newVersion := len(objectProvenance.ObjectFullHistory) + 1 newSpec := buildSpec(spec) @@ -369,16 +426,73 @@ func ParseRequestObject(objectProvenance *ProvenanceOfObject, requestObjBytes [] func buildSpec(spec map[string]interface{}) Spec { mySpec := *NewSpec() for attribute, value := range spec { - bytes, err := json.MarshalIndent(value, "", " ") - if err != nil { - fmt.Println("Error could not marshal json: " + err.Error()) + var isMap, isStringSlice, isString, isInt, isFloat bool + //note that I cannot do type assertions because the underlying data + //of the interface{} is not a map[string]string or an []slice + //so that means that every type assertion to + //value.([]map[string]string) fails, neither will []string. have to cast, store that data as desired + var mapSliceField []map[string]string + bytes, _ := json.MarshalIndent(value, "", " ") + if err := json.Unmarshal(bytes, &mapSliceField); err == nil { + isMap = true + } + var stringSliceField []string + if err := json.Unmarshal(bytes, &stringSliceField); err == nil { + isStringSlice = true + } + var plainStringField string + if err := json.Unmarshal(bytes, &plainStringField); err == nil { + isString = true + } + var floatField float64 + if err := json.Unmarshal(bytes, &floatField); err == nil { + isFloat = true + } + var intField int + if err := json.Unmarshal(bytes, &intField); err == nil { + isInt = true + } + switch { + case isMap: + //we don't know the keys and don't know the data + //could be this for example: + //usernames = [daniel, steve, jenny] + //passwords = [22d732, 4343e2, 434343b] + attributeToSlices := make(map[string][]string, 0) + //build this and then i will loop through and add this to the spec + for _, mapl := range mapSliceField { + + for key, data := range mapl { + slice, ok := attributeToSlices[key] + if ok { + slice = append(slice, data) + attributeToSlices[key] = slice + } else { // first time seeing this key + slice := make([]string, 0) + attributeToSlices[key] = slice + } + } + } + //now add to the attributes + for key, value := range attributeToSlices { + mySpec.AttributeToData[key] = value + } + + case isStringSlice: + mySpec.AttributeToData[attribute] = stringSliceField + case isString: + mySpec.AttributeToData[attribute] = plainStringField + case isFloat: + mySpec.AttributeToData[attribute] = floatField + case isInt: + mySpec.AttributeToData[attribute] = intField + default: + fmt.Println(value) + fmt.Println("Error with the spec data. not a map slice, float, int, string slice, or string.") } - attributeData := string(bytes) - mySpec.AttributeToData[attribute] = attributeData } return mySpec } - func printMaps() { fmt.Println("Printing kindVersionMap") for key, value := range kindVersionMap {