diff --git a/Gopkg.lock b/Gopkg.lock
index 663b893..bddd48e 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -3,32 +3,43 @@
[[projects]]
branch = "default"
+ digest = "1:24df057f15e7a09e75c1241cbe6f6590fd3eac9804f1110b02efade3214f042d"
name = "bitbucket.org/ww/goautoneg"
packages = ["."]
+ pruneopts = "UT"
revision = "75cd24fc2f2c2a2088577d12123ddee5f54e0675"
[[projects]]
+ digest = "1:002edd3097e9eb1ad56da7e29ed3bfdc8f85765d667a84246b38a7226c829ad2"
name = "github.com/NYTimes/gziphandler"
packages = ["."]
+ pruneopts = "UT"
revision = "56545f4a5d46df9a6648819d1664c3a03a13ffdb"
[[projects]]
+ digest = "1:792c6f8317411834d22db5be14276cd87d589cb0f8dcc51c042f0dddf67d60b1"
name = "github.com/PuerkitoBio/purell"
packages = ["."]
+ pruneopts = "UT"
revision = "8a290539e2e8629dbc4e6bad948158f790ec31f4"
version = "v1.0.0"
[[projects]]
+ digest = "1:61e5d7b1fabd5b6734b2595912944dbd9f6e0eaa4adef25e5cbf98754fc91df1"
name = "github.com/PuerkitoBio/urlesc"
packages = ["."]
+ pruneopts = "UT"
revision = "5bd2802263f21d8788851d5305584c82a5c75d7e"
[[projects]]
+ digest = "1:2daf57e573d4174757646e9d416d25e4dbe4533237961a90b986091a2de12830"
name = "github.com/beorn7/perks"
packages = ["quantile"]
+ pruneopts = "UT"
revision = "3ac7bf7a47d159a033b107610db8a1b6575507a4"
[[projects]]
+ digest = "1:3814dc656fae3665c018c17165264b164ae7fb90255b48f12971b191fef27180"
name = "github.com/coreos/etcd"
packages = [
"auth/authpb",
@@ -42,91 +53,121 @@
"pkg/tlsutil",
"pkg/transport",
"pkg/types",
- "version"
+ "version",
]
+ pruneopts = "UT"
revision = "95a726a27e09030f9ccbd9982a1508f5a6d25ada"
version = "v3.2.13"
[[projects]]
+ digest = "1:c298fe11665f7157328f2978bc60f761ffcc706b94bf077e661a59fe29b291a0"
name = "github.com/coreos/go-semver"
packages = ["semver"]
+ pruneopts = "UT"
revision = "568e959cd89871e61434c1143528d9162da89ef2"
[[projects]]
+ digest = "1:fa91847d50d3f656fc2d2d608b9749b97d77528e8988ad8001f957640545e91e"
name = "github.com/coreos/go-systemd"
packages = ["daemon"]
+ pruneopts = "UT"
revision = "48702e0da86bd25e76cfef347e2adeb434a0d0a6"
version = "v14"
[[projects]]
+ digest = "1:6b21090f60571b20b3ddc2c8e48547dffcf409498ed6002c2cada023725ed377"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
+ pruneopts = "UT"
revision = "782f4967f2dc4564575ca782fe2d04090b5faca8"
[[projects]]
+ digest = "1:1f1d454098ba2f8e7b87f0166e30533a72dddb80bace79643a78933fa4889613"
name = "github.com/elazarl/go-bindata-assetfs"
packages = ["."]
+ pruneopts = "UT"
revision = "3dcc96556217539f50599357fb481ac0dc7439b9"
[[projects]]
+ digest = "1:87c056b4f0548034ade51456237b47f723b901f373d7c11295f80da5d3b91950"
name = "github.com/emicklei/go-restful"
packages = [
".",
- "log"
+ "log",
]
+ pruneopts = "UT"
revision = "ff4f55a206334ef123e4f79bbf348980da81ca46"
[[projects]]
+ digest = "1:ddab18e89cf46e40707b89dbe3835b4a591b0ea298e1035eefa84002aa9a4b4e"
name = "github.com/emicklei/go-restful-swagger12"
packages = ["."]
+ pruneopts = "UT"
revision = "dcef7f55730566d41eae5db10e7d6981829720f6"
version = "1.0.1"
[[projects]]
branch = "master"
+ digest = "1:a1621e5cc9f1188a1320061c1a08889029a74349471ae389224f22f63e0f58fc"
name = "github.com/evanphx/json-patch"
packages = ["."]
+ pruneopts = "UT"
revision = "94e38aa1586e8a6c8a75770bddf5ff84c48a106b"
[[projects]]
+ digest = "1:c45cef8e0074ea2f8176a051df38553ba997a3616f1ec2d35222b1cf9864881e"
name = "github.com/ghodss/yaml"
packages = ["."]
+ pruneopts = "UT"
revision = "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
[[projects]]
+ digest = "1:172569c4bdc486213be0121e6039df4c272e9ff29397d9fd3716c31e4b37e15d"
name = "github.com/go-openapi/jsonpointer"
packages = ["."]
+ pruneopts = "UT"
revision = "46af16f9f7b149af66e5d1bd010e3574dc06de98"
[[projects]]
+ digest = "1:f30ccde775458301b306f4576e11de88d3ed0d91e68a5f3591c4ed8afbca76fa"
name = "github.com/go-openapi/jsonreference"
packages = ["."]
+ pruneopts = "UT"
revision = "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272"
[[projects]]
+ digest = "1:ed3642d1497a543323f731039927aef565de45bafaffc97d138d7dc5bc14b5b5"
name = "github.com/go-openapi/spec"
packages = ["."]
+ pruneopts = "UT"
revision = "1de3e0542de65ad8d75452a595886fdd0befb363"
[[projects]]
+ digest = "1:3a42f9cbdeb4db3a14e0c3bb35852b7426b69f73386d52b606baf5d0fecfb4d7"
name = "github.com/go-openapi/swag"
packages = ["."]
+ pruneopts = "UT"
revision = "f3f9494671f93fcff853e3c6e9e948b3eb71e590"
[[projects]]
+ digest = "1:f83d740263b44fdeef3e1bce6147b5d7283fcad1a693d39639be33993ecf3db1"
name = "github.com/gogo/protobuf"
packages = [
"proto",
- "sortkeys"
+ "sortkeys",
]
+ pruneopts = "UT"
revision = "c0656edd0d9eab7c66d1eb0c568f9039345796f7"
[[projects]]
+ digest = "1:2edd2416f89b4e841df0e4a78802ce14d2bc7ad79eba1a45986e39f0f8cb7d87"
name = "github.com/golang/glog"
packages = ["."]
+ pruneopts = "UT"
revision = "44145f04b68cf362d9c4df2182967c2275eaefed"
[[projects]]
+ digest = "1:14834e04828af9e53954f1be45ea7f190c4c26d746009c2ab07c5828595539e9"
name = "github.com/golang/protobuf"
packages = [
"proto",
@@ -134,154 +175,202 @@
"ptypes",
"ptypes/any",
"ptypes/duration",
- "ptypes/timestamp"
+ "ptypes/timestamp",
]
+ pruneopts = "UT"
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
version = "v1.1.0"
[[projects]]
+ digest = "1:62dfb39fe3bddeabb02cc001075ed9f951b044da2cd5b0f970ca798b1553bac3"
name = "github.com/google/btree"
packages = ["."]
+ pruneopts = "UT"
revision = "7d79101e329e5a3adf994758c578dab82b90c017"
[[projects]]
+ digest = "1:41bfd4219241b7f7d6e6fdb13fc712576f1337e68e6b895136283b76928fdd66"
name = "github.com/google/gofuzz"
packages = ["."]
+ pruneopts = "UT"
revision = "44d81051d367757e1c7c6a5a86423ece9afcf63c"
[[projects]]
+ digest = "1:75eb87381d25cc75212f52358df9c3a2719584eaa9685cd510ce28699122f39d"
name = "github.com/googleapis/gnostic"
packages = [
"OpenAPIv2",
"compiler",
- "extensions"
+ "extensions",
]
+ pruneopts = "UT"
revision = "0c5108395e2debce0d731cf0287ddf7242066aba"
[[projects]]
+ digest = "1:878f0defa9b853f9acfaf4a162ba450a89d0050eff084f9fe7f5bd15948f172a"
name = "github.com/gregjones/httpcache"
packages = [
".",
- "diskcache"
+ "diskcache",
]
+ pruneopts = "UT"
revision = "787624de3eb7bd915c329cba748687a3b22666a6"
[[projects]]
+ digest = "1:3f90d23757c18b1e07bf11494dbe737ee2c44d881c0f41e681611abdadad62fa"
name = "github.com/hashicorp/golang-lru"
packages = [
".",
- "simplelru"
+ "simplelru",
]
+ pruneopts = "UT"
revision = "a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4"
[[projects]]
+ digest = "1:06ec9147400aabb0d6960dd8557638603b5f320cd4cb8a3eceaae407e782849a"
name = "github.com/imdario/mergo"
packages = ["."]
+ pruneopts = "UT"
revision = "6633656539c1639d9d78127b7d47c622b5d7b6dc"
[[projects]]
+ digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
+ pruneopts = "UT"
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
+ digest = "1:74f68b2d82d28b446fb3f8d00a12df551761cfef10d60379558143dbe8e6dcb5"
name = "github.com/json-iterator/go"
packages = ["."]
+ pruneopts = "UT"
revision = "2ddf6d758266fcb080a4f9e054b9f292c85e6798"
[[projects]]
+ digest = "1:a867990aee2ebc1ac86614ed702bf1e63061a79eac12d4326203cb9084b61839"
name = "github.com/mailru/easyjson"
packages = [
"buffer",
"jlexer",
- "jwriter"
+ "jwriter",
]
+ pruneopts = "UT"
revision = "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
[[projects]]
+ digest = "1:f1bb94f5fab2a670687ec7a30a9160b0193d147ae82d5650231c01b2b3a8d0db"
name = "github.com/matttproud/golang_protobuf_extensions"
packages = ["pbutil"]
+ pruneopts = "UT"
revision = "fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a"
[[projects]]
+ digest = "1:33422d238f147d247752996a26574ac48dcf472976eda7f5134015f06bf16563"
name = "github.com/modern-go/concurrent"
packages = ["."]
+ pruneopts = "UT"
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
version = "1.0.3"
[[projects]]
+ digest = "1:d711dfcf661439f1ef0b202a02e8a1ff4deac48f26f34253520dcdbecbd7c5f1"
name = "github.com/modern-go/reflect2"
packages = ["."]
+ pruneopts = "UT"
revision = "1df9eeb2bb81f327b96228865c5687bc2194af3f"
version = "1.0.0"
[[projects]]
+ digest = "1:9072181164e616e422cbfbe48ca9ac249a4d76301ca0876c9f56b937cf214a2f"
name = "github.com/pborman/uuid"
packages = ["."]
+ pruneopts = "UT"
revision = "ca53cad383cad2479bbba7f7a1a05797ec1386e4"
[[projects]]
branch = "master"
+ digest = "1:3bf17a6e6eaa6ad24152148a631d18662f7212e21637c2699bff3369b7f00fa2"
name = "github.com/petar/GoLLRB"
packages = ["llrb"]
+ pruneopts = "UT"
revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4"
[[projects]]
+ digest = "1:0e7775ebbcf00d8dd28ac663614af924411c868dca3d5aa762af0fae3808d852"
name = "github.com/peterbourgon/diskv"
packages = ["."]
+ pruneopts = "UT"
revision = "5f041e8faa004a95c88a202771f4cc3e991971e6"
version = "v2.0.1"
[[projects]]
+ digest = "1:1cec06981f734a2a5927ce17a22b34e9f3fa458b5c70f5db9ed58f7e389338f7"
name = "github.com/prometheus/client_golang"
packages = ["prometheus"]
+ pruneopts = "UT"
revision = "e7e903064f5e9eb5da98208bae10b475d4db0f8c"
[[projects]]
+ digest = "1:9fe8945a11a9f588a9d306b4741cad634da9015a704271b9506810e2cc77fa17"
name = "github.com/prometheus/client_model"
packages = ["go"]
+ pruneopts = "UT"
revision = "fa8ad6fec33561be4280a8f0514318c79d7f6cb6"
[[projects]]
+ digest = "1:0d5f8e2195ad2beef202367f3217c4a7981582d96ccf4876b9aa2c5c9c9b3510"
name = "github.com/prometheus/common"
packages = [
"expfmt",
"internal/bitbucket.org/ww/goautoneg",
- "model"
+ "model",
]
+ pruneopts = "UT"
revision = "13ba4ddd0caa9c28ca7b7bffe1dfa9ed8d5ef207"
[[projects]]
+ digest = "1:c78edab144d03422b52cd34d5fa4ffc9a59fef90b3afdcf2efc4dd333479f243"
name = "github.com/prometheus/procfs"
packages = [
".",
- "xfs"
+ "xfs",
]
+ pruneopts = "UT"
revision = "65c1f6f8f0fc1e2185eb9863a3bc751496404259"
[[projects]]
+ digest = "1:7c7ef7c1bf5eb925d97c670fba41f85f6dce02c334c96c23f74a4dadb208e4a7"
name = "github.com/spf13/cobra"
packages = ["."]
+ pruneopts = "UT"
revision = "c439c4fa093711d42e1b01acb1235b52004753c1"
[[projects]]
+ digest = "1:9424f440bba8f7508b69414634aef3b2b3a877e522d8a4624692412805407bb7"
name = "github.com/spf13/pflag"
packages = ["."]
+ pruneopts = "UT"
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
+ digest = "1:a02bd33c56ab567d53246eeb40c76810c339af9fff7e43b5dd266f7d58d7ca4a"
name = "github.com/ugorji/go"
packages = ["codec"]
+ pruneopts = "UT"
revision = "ded73eae5db7e7a0ef6f55aace87a2873c5d2b74"
[[projects]]
+ digest = "1:38cb27d3525635c34e84e2dbc2207c37d10832776997665bf0ddaeae2c861f1f"
name = "golang.org/x/crypto"
packages = ["ssh/terminal"]
+ pruneopts = "UT"
revision = "49796115aa4b964c318aad4f3084fdb41e9aa067"
[[projects]]
+ digest = "1:8d39c4868e2d58e7111613ca0410ae30e43fed360fed7680d0308a8c55cea124"
name = "golang.org/x/net"
packages = [
"context",
@@ -291,19 +380,23 @@
"internal/timeseries",
"lex/httplex",
"trace",
- "websocket"
+ "websocket",
]
+ pruneopts = "UT"
revision = "1c05540f6879653db88113bc4a2b70aec4bd491f"
[[projects]]
+ digest = "1:e1a85d3648114c446b2874647bf30f646a8594e7e4e45db87fe962aba60e51f5"
name = "golang.org/x/sys"
packages = [
"unix",
- "windows"
+ "windows",
]
+ pruneopts = "UT"
revision = "95c6576299259db960f6c5b9b69ea52422860fce"
[[projects]]
+ digest = "1:52c441d338d4a491f7b2a2e62dbf35bc531c9883c804b3f781afd6990ed72207"
name = "golang.org/x/text"
packages = [
"cases",
@@ -321,24 +414,30 @@
"unicode/cldr",
"unicode/norm",
"unicode/rangetable",
- "width"
+ "width",
]
+ pruneopts = "UT"
revision = "b19bf474d317b857955b12035d2c5acb57ce8b01"
[[projects]]
+ digest = "1:d37b0ef2944431fe9e8ef35c6fffc8990d9e2ca300588df94a6890f3649ae365"
name = "golang.org/x/time"
packages = ["rate"]
+ pruneopts = "UT"
revision = "f51c12702a4d776e4c1fa9b0fabab841babae631"
[[projects]]
+ digest = "1:0efcfe82e59b828eb6f4115bba88ff45c0898c38e823fbe7f450bdffed9e739b"
name = "google.golang.org/genproto"
packages = [
"googleapis/api/annotations",
- "googleapis/rpc/status"
+ "googleapis/rpc/status",
]
+ pruneopts = "UT"
revision = "09f6ed296fc66555a25fe4ce95173148778dfa85"
[[projects]]
+ digest = "1:45101b53b1b93d50e2464e3b164bfa72cc77229fb998cfcd029987006488c641"
name = "google.golang.org/grpc"
packages = [
".",
@@ -358,28 +457,36 @@
"stats",
"status",
"tap",
- "transport"
+ "transport",
]
+ pruneopts = "UT"
revision = "5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e"
version = "v1.7.5"
[[projects]]
+ digest = "1:ef72505cf098abdd34efeea032103377bec06abb61d8a06f002d5d296a4b1185"
name = "gopkg.in/inf.v0"
packages = ["."]
+ pruneopts = "UT"
revision = "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4"
version = "v0.9.0"
[[projects]]
+ digest = "1:74075f052f91808a4299b45b231aa5f7e2ccd3bac4ec4a5f832468485d91b9d3"
name = "gopkg.in/natefinch/lumberjack.v2"
packages = ["."]
+ pruneopts = "UT"
revision = "20b71e5b60d756d3d2f80def009790325acc2b23"
[[projects]]
+ digest = "1:fa62cd569ff15e4dba6dfc6d826e97a7913ef299eccd5804c9d614a84863e485"
name = "gopkg.in/yaml.v2"
packages = ["."]
+ pruneopts = "UT"
revision = "670d4cfef0544295bc27a114dbac37980d83185a"
[[projects]]
+ digest = "1:c54220c2efa40139a882d509bc8fbac7edc3b3b02a8bcad1091234ad6a2d7a48"
name = "k8s.io/api"
packages = [
"admission/v1beta1",
@@ -411,12 +518,14 @@
"settings/v1alpha1",
"storage/v1",
"storage/v1alpha1",
- "storage/v1beta1"
+ "storage/v1beta1",
]
+ pruneopts = "UT"
revision = "4c8191c9c7bfa20f74a2d477f2c3530e711e7620"
[[projects]]
branch = "master"
+ digest = "1:42bd31a2fcc44b22893d4d39190e5687c76130ea73295beaf25e2aa416bc4983"
name = "k8s.io/apimachinery"
packages = [
"pkg/api/equality",
@@ -466,11 +575,13 @@
"pkg/version",
"pkg/watch",
"third_party/forked/golang/json",
- "third_party/forked/golang/reflect"
+ "third_party/forked/golang/reflect",
]
+ pruneopts = "UT"
revision = "521145febf93d5639dce48a49ee8dc080863b034"
[[projects]]
+ digest = "1:ca440710c16089907a06b0d2bcbe41680ead253c8be665fd6de7a19798be1503"
name = "k8s.io/apiserver"
packages = [
"pkg/admission",
@@ -561,11 +672,13 @@
"plugin/pkg/audit/truncate",
"plugin/pkg/audit/webhook",
"plugin/pkg/authenticator/token/webhook",
- "plugin/pkg/authorizer/webhook"
+ "plugin/pkg/authorizer/webhook",
]
+ pruneopts = "UT"
revision = "0553b9748924ffb5737340cb0afa6f0de2b12c42"
[[projects]]
+ digest = "1:3a63d464ff2894a6e7cef03583cf1a8dcbc828356d129278ebfbff5887a704f0"
name = "k8s.io/client-go"
packages = [
"discovery",
@@ -689,24 +802,43 @@
"util/flowcontrol",
"util/homedir",
"util/integer",
- "util/retry"
+ "util/retry",
]
+ pruneopts = "UT"
revision = "ddee7171183be1d6f42cf9b3a3daf121fbf79595"
[[projects]]
+ digest = "1:42e8e68e181107176148eb35a8987f1bab0a6db566af1e14908f5d46ccc9df8b"
name = "k8s.io/kube-openapi"
packages = [
"pkg/builder",
"pkg/common",
"pkg/handler",
"pkg/util",
- "pkg/util/proto"
+ "pkg/util/proto",
]
+ pruneopts = "UT"
revision = "8a9b82f00b3a86eac24681da3f9fe6c34c01cea2"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
- inputs-digest = "b709a5361a6dc08a29f144881b58a761320f76f653982bf88082ab0d6027eb59"
+ input-imports = [
+ "github.com/emicklei/go-restful",
+ "github.com/golang/glog",
+ "github.com/spf13/cobra",
+ "gopkg.in/yaml.v2",
+ "k8s.io/apimachinery/pkg/apis/meta/v1",
+ "k8s.io/apimachinery/pkg/runtime",
+ "k8s.io/apimachinery/pkg/runtime/schema",
+ "k8s.io/apimachinery/pkg/runtime/serializer",
+ "k8s.io/apimachinery/pkg/util/errors",
+ "k8s.io/apimachinery/pkg/util/runtime",
+ "k8s.io/apimachinery/pkg/version",
+ "k8s.io/apiserver/pkg/apis/audit/v1beta1",
+ "k8s.io/apiserver/pkg/server",
+ "k8s.io/apiserver/pkg/server/options",
+ "k8s.io/apiserver/pkg/util/logs",
+ ]
solver-name = "gps-cdcl"
solver-version = 1
diff --git a/README.md b/README.md
index eafdf18..1468937 100644
--- a/README.md
+++ b/README.md
@@ -4,15 +4,16 @@ A Kubernetes Aggregated API Server to find out Provenance/Lineage information fo
## What is it?
-Kubernetes custom resources extend base API to manage third-party platform elements declaratively.
-It is important to track chronology of declarative operations performed on custom resources to understand
-how these operations affect underlying platform elements - e.g. for an instance of Postgres custom resource we may want to know:
+
+Kubernetes custom resources extend base API to manage third-party platform elements declaratively.
+It is important to track chronology of declarative operations performed on custom resources to understand
+how these operations affect underlying platform elements - e.g. for an instance of Postgres custom resource we may want to know:
how many db users were created in a month, when was password changed for a db user, etc.
-For this, a generic approach is needed to maintain provenance information of custom resources.
+For this, a generic approach is needed to maintain provenance information of custom resources.
-kubeprovenance is a tool that helps you find Provenance information about different Kubernetes custom resources in your cluster.
+kubeprovenance is a tool that helps you find Provenance information about different Kubernetes custom resources in your cluster.
-Kubeprovenance is a Kubernetes aggregated API server. It uses Kubernetes audit logs for building custom resource provenance.
+Kubeprovenance is a Kubernetes aggregated API server. It uses Kubernetes audit logs for building custom resource provenance.
Provenance query operators like history, diff, bisect are defined for custom resource instance tracking. Provenance information is accessible via kubectl.
## How does it work?
@@ -33,6 +34,7 @@ We are working on changing kubeprovenance's information source from static audit
## Try it Out:
Steps to Run Kubernetes Local Cluster on a GCE or AWS instance (or any node), configure auditing and running/testing Kubeprovenance aggregated api server
+
**1. Setting up environment.**
Reference: https://dzone.com/articles/easy-step-by-step-local-kubernetes-source-code-cha
@@ -44,15 +46,20 @@ apt-get install -y gcc make socat git
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
+
+set up your go workspace, set the GOPATH to it. this is where all your go code should be.
+mkdir $HOME/goworkspace
+mkdir $HOME/goworkspace/src
+mkdir $HOME/goworkspace/bin
+
+export GOPATH=$HOME/goworkspace
**3. Install etcd3.2.18:**
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**
-sudo apt-get update
-sudo apt-get install docker-ce
-
-Set up your go workspace, set the GOPATH to it. This is where all your go code should be.
-export GOPATH=/gopath
+Follow steps here: reference: https://docs.docker.com/install/linux/docker-ce/ubuntu/#set-up-the-repository
+docker version //check if it is installed
**5. Get The Kubernetes Source Code:**
git clone https://github.com/kubernetes/kubernetes $GOPATH/src/k8s.io/kubernetes
@@ -66,10 +73,10 @@ In a new shell, test that it is working :
root@host: $GOPATH/src/k8s.io/kubernetes# cluster/kubectl.sh cluster-info
Kubernetes master is running at http://127.0.0.1:8080 # => works!
-Add $GOPATH/src/k8s.io/kubernetes/cluster to PATH.
+Add $GOPATH/src/k8s.io/kubernetes/cluster to PATH:
export PATH=$PATH:$GOPATH/src/k8s.io/kubernetes/cluster
-Commands look like kubectl.sh get pods instead of kubectl get pods...
+Now, Commands look like kubectl.sh get pods instead of kubectl get pods...
**7. Enabling auditing:**
@@ -98,11 +105,10 @@ line 486: add audit-policy file to audit_args:
This file defines what actions and resources will generate logs.
- An example of a audit-policy file: reference the docs if you are looking to make one:
- https://kubernetes.io/docs/tasks/debug-application-cluster/audit/
-
+ reference the docs if you are looking to make one:
+ https://kubernetes.io/docs/tasks/debug-application-cluster/audit/
For running kubeprovenance to track only a postgres custom resource, audit-policy would look like this:
- Add more rules to the audit-policy to track different or more than one custom resource:
+ Note: Add more rules to the audit-policy to track different or more than one custom resource:
root@provenance:~# more audit-policy.yaml
apiVersion: audit.k8s.io/v1beta1
@@ -127,11 +133,12 @@ line 486: add audit-policy file to audit_args:
Install dep:
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
+Move dep executable to somewhere on your $PATH
+dep version //to verify that it is installed correctly
-git clone https://github.com/cloud-ark/kubeprovenance.git
-mv kubeprovenance $GOPATH/src/github.com/cloud-ark
+git clone https://github.com/cloud-ark/kubeprovenance.git $GOPATH/src/github.com/cloud-ark
cd $GOPATH/src/github.com/cloud-ark/kubeprovenance
-dep ensure
+dep ensure -v
Make sure kubernetes is running:
$ kubectl.sh cluster-info
@@ -158,10 +165,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/spechistory"
```
-3) Get diff of Postgres custom resource instance between version 1 and version 2
+3) Get diff of Postgres custom resource instance between version 1 and version 5
```
-kubectl.sh get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgreses/client25/diff?start=1&end=2"
+kubectl.sh get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgreses/client25/diff?start=1&end=5"
```
4) Get diff of the field databases for a Postgres custom resource instance between version 1 and version 2
@@ -170,12 +177,17 @@ 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) Get diff of the field users for a Postgres custom resource instance between version 1 and version 3
```
-kubectl.sh get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgreses/client25/bisect?field=abc&value=def"
+kubectl.sh get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgreses/client25/diff?start=1&end=3&field=users"
```
+6) 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?field1=username&value1=pallavi&field2=password&value2=pass123"
+```
## Try it on Minikube
@@ -210,13 +222,13 @@ kubectl get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgr
![alt text](https://github.com/cloud-ark/kubeprovenance/raw/master/docs/spechistory.png)
-3) Get diff of Postgres custom resource instance between version 1 and version 2
+3) Get diff of Postgres custom resource instance between version 1 and version 5
```
-kubectl get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgreses/client25/diff?start=1&end=2"
+kubectl get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgreses/client25/diff?start=1&end=5"
```
-![alt text](https://github.com/cloud-ark/kubeprovenance/raw/master/docs/nodiff.png)
+![alt text](https://github.com/cloud-ark/kubeprovenance/raw/master/docs/getfulldiff.png)
4) Get diff of the field databases for a Postgres custom resource instance between version 1 and version 2
@@ -224,7 +236,7 @@ kubectl get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgr
```
kubectl get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgreses/client25/diff?start=1&end=2&field=databases"
```
-![alt text](https://github.com/cloud-ark/kubeprovenance/raw/master/docs/getdiff_databases.png)
+![alt text](https://github.com/cloud-ark/kubeprovenance/raw/master/docs/getfielddiff.png)
5) Get diff of the field users for a Postgres custom resource instance between version 1 and version 3
@@ -233,13 +245,13 @@ kubectl get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgr
kubectl get --raw "/apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgreses/client25/diff?start=1&end=3&field=users"
```
-![alt text](https://github.com/cloud-ark/kubeprovenance/raw/master/docs/getdiff_users.png)
+![alt text](https://github.com/cloud-ark/kubeprovenance/raw/master/docs/usersfielddiff.png)
-5) Find out in which version the field 'abc' was given value 'def'
+6) Find out in which version the user 'pallavi' was given password 'pass123'
```
-kubectl 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"
```
## Troubleshooting tips:
diff --git a/docs/bisect.png b/docs/bisect.png
new file mode 100644
index 0000000..2be82e3
Binary files /dev/null and b/docs/bisect.png differ
diff --git a/docs/deployments.png b/docs/deployments.png
deleted file mode 100644
index faceba5..0000000
Binary files a/docs/deployments.png and /dev/null differ
diff --git a/docs/etcd-clusters.png b/docs/etcd-clusters.png
deleted file mode 100644
index 7ea369a..0000000
Binary files a/docs/etcd-clusters.png and /dev/null differ
diff --git a/docs/getdiff_databases.png b/docs/getdiff_databases.png
deleted file mode 100644
index 7db6a9c..0000000
Binary files a/docs/getdiff_databases.png and /dev/null differ
diff --git a/docs/getdiff_users.png b/docs/getdiff_users.png
deleted file mode 100644
index c7ce515..0000000
Binary files a/docs/getdiff_users.png and /dev/null differ
diff --git a/docs/getfielddiff.png b/docs/getfielddiff.png
new file mode 100644
index 0000000..622c9e0
Binary files /dev/null and b/docs/getfielddiff.png differ
diff --git a/docs/getfulldiff.png b/docs/getfulldiff.png
new file mode 100644
index 0000000..7a65c65
Binary files /dev/null and b/docs/getfulldiff.png differ
diff --git a/docs/hello-minikube-deployment.png b/docs/hello-minikube-deployment.png
deleted file mode 100644
index 3ad0ca1..0000000
Binary files a/docs/hello-minikube-deployment.png and /dev/null differ
diff --git a/docs/nodiff.png b/docs/nodiff.png
deleted file mode 100644
index 31e03c4..0000000
Binary files a/docs/nodiff.png and /dev/null differ
diff --git a/docs/spechistory.png b/docs/spechistory.png
index d85da61..0d9aead 100644
Binary files a/docs/spechistory.png and b/docs/spechistory.png differ
diff --git a/docs/usersfielddiff.png b/docs/usersfielddiff.png
new file mode 100644
index 0000000..20f6278
Binary files /dev/null and b/docs/usersfielddiff.png differ
diff --git a/docs/versions.png b/docs/versions.png
index a5350ef..c6ad159 100644
Binary files a/docs/versions.png and b/docs/versions.png differ
diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go
index dcabba4..a3ead08 100644
--- a/pkg/apiserver/apiserver.go
+++ b/pkg/apiserver/apiserver.go
@@ -194,6 +194,9 @@ func getHistory(request *restful.Request, response *restful.Response) {
provenanceInfo := "Resource Name:" + resourceName + " Resource Kind:" + resourceKind + "\n"
response.Write([]byte(provenanceInfo))
intendedProvObj := provenance.FindProvenanceObjectByName(resourceName, provenance.AllProvenanceObjects)
+ //optional parameters
+ start := request.QueryParameter("start")
+ end := request.QueryParameter("end")
//TODO: Validate request based on the correct namespace and the correct plural type.
//I have the namespace/pluralkind datain my ProvenanceOfObject struct so it is easy to make these changes later
@@ -201,8 +204,26 @@ func getHistory(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 {
- //TODO: handle optional interval parameters
- response.Write([]byte(intendedProvObj.ObjectFullHistory.SpecHistory()))
+ if start != "" && end != "" { //have both a start and an end
+ fmt.Printf("Start:%s", start)
+ fmt.Printf("End:%s", end)
+ startInt, err := strconv.Atoi(start)
+ if err != nil {
+ s := fmt.Sprintf("Could not parse start query parameter to int: %s", err.Error())
+ response.Write([]byte(s))
+ return
+ }
+ endInt, err := strconv.Atoi(end)
+ if err != nil {
+ s := fmt.Sprintf("Could not parse end query parameter to int: %s", err.Error())
+ response.Write([]byte(s))
+ return
+ }
+ fmt.Printf("Spec history starting with version %d and ending with version %d", startInt, endInt)
+ response.Write([]byte(intendedProvObj.ObjectFullHistory.SpecHistoryInterval(startInt, endInt)))
+ } else { //start and end
+ response.Write([]byte(intendedProvObj.ObjectFullHistory.SpecHistory()))
+ }
}
}
@@ -210,29 +231,29 @@ func getHistory(request *restful.Request, response *restful.Response) {
func bisect(request *restful.Request, response *restful.Response) {
fmt.Println("Inside bisect")
resourceName := request.PathParameter("resource-id")
- requestPath := request.Request.URL.Path
- resourcePathSlice := strings.Split(requestPath, "/")
- resourceKind := resourcePathSlice[6] // Kind is 7th element in the slice
-
- var provenanceInfo string
- provenanceInfo = "Resource Name:" + resourceName + " Resource Kind:" + resourceKind
- fmt.Println(provenanceInfo)
-
- field := request.QueryParameter("field")
- value := request.QueryParameter("value")
-
- provenanceInfo = provenanceInfo + " Field:" + field + "Value: " + value + "\n"
-
- fmt.Printf("ProvenanceInfo:%v", provenanceInfo)
-
- response.Write([]byte(provenanceInfo))
+ requestPath := request.Request.URL.String()
+ // assuming that the last slash is where the query starts.
+ strs := strings.Split(requestPath, "/")
+ // apis/kubeprovenance.cloudark.io/v1/namespaces/default/postgreses/client25/bisect?field1=username&field2=password&value1=pallavi&value2=pass123
+ // field1=username&field2=password&value1=pallavi&value2=pass123
+ args := strings.Split(strs[len(strs)-1], "?")[1] //get rid of "bisect?""
+ argMap := make(map[string]string)
+ argsArray := strings.Split(args, "&")
+ for _, val := range argsArray {
+ fieldToValue := strings.Split(val, "=")
+ argMap[fieldToValue[0]] = fieldToValue[1]
+ }
+ // var provenanceInfo string
+ // provenanceInfo = "Resource Name:" + resourceName + " Resource Kind:" + resourceKind
+ // fmt.Println(provenanceInfo)
+ //Validate that there is ProvenanceHistory for the resource with name resourceName (PathParameter of the request)
intendedProvObj := provenance.FindProvenanceObjectByName(resourceName, provenance.AllProvenanceObjects)
if intendedProvObj == nil {
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(intendedProvObj.ObjectFullHistory.Bisect(argMap)))
response.Write([]byte(string("\n")))
}
}
@@ -281,7 +302,7 @@ func getDiff(request *restful.Request, response *restful.Response) {
fmt.Printf("Diff for Field requested. Field:%s", field)
diffInfo = intendedProvObj.ObjectFullHistory.FieldDiff(field, startInt, endInt)
} else {
- fmt.Println("Diff for Spec requested.")
+ fmt.Println("Diff for Full Spec requested.")
diffInfo = intendedProvObj.ObjectFullHistory.FullDiff(startInt, endInt)
}
}
diff --git a/pkg/provenance/provenance.go b/pkg/provenance/provenance.go
index 728a916..1c51fbb 100644
--- a/pkg/provenance/provenance.go
+++ b/pkg/provenance/provenance.go
@@ -10,7 +10,7 @@ import (
"sort"
"strconv"
"strings"
-
+ "errors"
yaml "gopkg.in/yaml.v2"
"k8s.io/apiserver/pkg/apis/audit/v1beta1"
)
@@ -41,8 +41,9 @@ 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
+ Timestamp string
}
type ProvenanceOfObject struct {
@@ -110,7 +111,7 @@ func NewProvenanceOfObject() *ProvenanceOfObject {
func NewSpec() *Spec {
var s Spec
- s.AttributeToData = make(map[string]string)
+ s.AttributeToData = make(map[string]interface{})
return &s
}
@@ -139,130 +140,406 @@ func (o ObjectLineage) String() string {
}
return b.String()
}
-
-func (o ObjectLineage) GetVersions() string {
+// Method to build a sorted slice of Spec from ObjectLineage map.
+// Sorting is necessary because want to scan the spec versions in order, maps
+// are unordered (ObjectLineage obj).
+func getSpecsInOrder(o ObjectLineage) []Spec{
+ // Get all versions, sort by version, make slice of specs
s := make([]int, 0)
for _, value := range o {
s = append(s, value.Version)
}
sort.Ints(s)
- //get all versions, sort by version, make string array of them
- versions := make([]string, 0)
+ specs := make([]Spec, 0)
for _, version := range s {
- versions = append(versions, fmt.Sprint(version)) //cast int to string
+ specs = append(specs, o[version])
}
- return "[" + strings.Join(versions, ", ") + "]\n"
+ return specs
+}
+func (o ObjectLineage) GetVersions() string {
+ specs := getSpecsInOrder(o)
+ outputs := make([]string, 0)
+ 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"
}
-//what happens if I delete the object?
-//need to delete the ObjectFullProvenance for the object
-//add type of ObjectFullProvenance, postgreses for example
func (o ObjectLineage) SpecHistory() string {
- s := make([]int, 0)
- for _, value := range o {
- s = append(s, value.Version)
+ specs := getSpecsInOrder(o)
+ specStrings := make([]string, 0)
+ for _, spec := range specs {
+ specStrings = append(specStrings, spec.String())
}
- sort.Ints(s)
- //get all versions, sort by version, make string array of them
- specs := make([]string, 0)
- for _, version := range s {
- specs = append(specs, fmt.Sprint(o[version])) //cast Spec to String
+ return strings.Join(specStrings, "\n")
+}
+
+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())
+ }
+ }
+ return strings.Join(specStrings, "\n")
+}
+func buildAttributeRelationships(specs []Spec, allQueryPairs [][]string) map[string][][]string{
+ // 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)
+
+ for _, spec := range specs {
+ for _, pair := range allQueryPairs {
+ for mkey, mvalue := range spec.AttributeToData {
+
+ qkey := pair[0] //query key
+ //each qkey qval has to be satisfied
+ vSliceMap, ok := mvalue.([]map[string]string)
+ if ok {
+ for _, mymap := range vSliceMap {
+ for okey, _ := range mymap {
+ if qkey == okey {
+ pairs, ok := mapRelationships[mkey]
+ if ok {
+ pairExistsAlready := false
+ for _, v := range pairs{ //check existing pairs
+ //pair[0] is the field, pair[1] is the value.
+ if v[0] == pair[0]{
+ pairExistsAlready = true
+ }
+
+ }
+ //don't want duplicate fields, but want to catch all mapRelationships
+ //over the lineage.
+ if !pairExistsAlready{
+ mapRelationships[mkey] = append(mapRelationships[mkey], pair)
+ }
+ } else {
+ pairs = make([][]string, 0)
+ pairs = append(pairs, pair)
+ mapRelationships[mkey] = pairs
+ }
+
+ }
+ }
+ }
+ }
+ }
+ }
}
- return strings.Join(specs, "\n")
+ return mapRelationships
}
-func (o ObjectLineage) Bisect(field, value string) string {
- s := make([]Spec, 0)
- for _, spec := range o {
- s = append(s, spec)
- }
- sort.Slice(s, func(i, j int) bool {
- return s[i].Version < s[i].Version
- })
- if len(s) == 1 {
- //check
- if s[0].AttributeToData[field] == value {
- return strconv.Itoa(1)
+func buildQueryPairsSlice(queryArgMap map[string]string) ([][]string, error){
+ allQueryPairs := make([][]string, 0)
+ //this section is storing the mappings from the query, into the allQueryPairs object
+ //these are the queries, each element must be satisfied somewhere in the spec for the bisect to succeed
+ for key, value := range queryArgMap {
+ attributeValueSlice := make([]string, 2)
+ if strings.Contains(key, "field") {
+ //find associated value in argMap, the query params are such that field1=bar, field2=foo
+ fieldNum, err := strconv.Atoi(key[5:])
+ if err != nil {
+ return nil,errors.New(fmt.Sprintf("Failure, could not convert %s. Invalid Query parameters.", key))
+
+ }
+ //find associated value by looking in the map for value+fieldNum.
+ valueOfKey, ok := queryArgMap["value"+strconv.Itoa(fieldNum)]
+ if !ok {
+ return nil,errors.New(fmt.Sprintf("Could not find an associated value for field: %s", key))
+ }
+ attributeValueSlice[0] = value
+ attributeValueSlice[1] = valueOfKey
+ allQueryPairs = append(allQueryPairs, attributeValueSlice)
}
}
- for _, v := range s {
- if v.AttributeToData[field] == value {
- return strconv.Itoa(v.Version)
+ return allQueryPairs,nil
+}
+//Steps taken in Bisect are:
+//Sort the spec elements in order of their version number.
+
+//Outer loop is going through each of the versions in order.
+//First I parse the query into a slice of field/value pairs.
+//Then build the map of related fields that belong to the same top level attribute
+// and need to be found in the same underlying map. Then I looped over the
+//field value pairs, searched based on the type.
+func (o ObjectLineage) Bisect(argMap map[string]string) string {
+ specs := getSpecsInOrder(o)
+
+ allQueryPairs, err := buildQueryPairsSlice(argMap)
+ if err!=nil{
+ return err.Error()
+ }
+ // fmt.Printf("attributeValuePairs%s\n", allQueryPairs)
+
+ // This method is to build the attributeRelationships from the Query
+ // I Will use this to ensure that fields belonging to the same top-level
+ // attribute, will be treated as a joint query. So you can't just
+ // 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.Println(specs)
+ for _, spec := range specs {
+
+ //every element represents whether a query pair was satisfied. they all must be true.
+ //if they all are true, then that will be the version where the query is first satisfied.
+ andGate := make([]bool, 0)
+ for _, pair := range allQueryPairs {
+ qkey := pair[0]
+ qval := pair[1]
+ satisfied := false
+ for mkey, mvalue := range spec.AttributeToData {
+ //search through the attributes in the spec. Possible types
+ //are string, array of strings, and a map. so I will need to check these
+ //with different search methods
+ vString, ok := mvalue.(string)
+ if ok { //if underlying value is a string
+ satisfied = handleTrivialFields(vString, qkey, qval, mkey)
+ if satisfied{
+ break //qkey/qval was satisfied somewhere in the spec attributes, so move on to the next qkey/qval
+ }
+ }
+
+ vStringSlice, ok := mvalue.([]string)
+ if ok { //if it is a slice of strings
+ satisfied = handleSimpleFields(vStringSlice, qkey, qval, mkey)
+ if satisfied{
+ break //qkey/qval was satisfied somewhere in the spec attributes, so move on to the next qkey/qval
+ }
+ }
+
+ vSliceMap, ok := mvalue.([]map[string]string)
+ //if it is a map, will need to check the underlying data to see
+ //if the qkey/qval exists below the top level attribute. username exists
+ //in the map contained by the 'users' attribute in the spec object, for example.
+ if ok {
+ satisfied = handleCompositeFields(vSliceMap, mapRelationships, qkey, qval, mkey)
+ if satisfied{
+ break //qkey/qval was satisfied somewhere in the spec attributes, so move on to the next qkey/qval
+ }
+ }
+ }
+ andGate = append(andGate, satisfied)
+ }
+ fmt.Printf("Result of checking all attributes:%v\n", andGate)
+ allTrue := all(andGate)
+ //all indexes in andGate must be true
+ if allTrue {
+ return fmt.Sprintf("Version: %d", spec.Version)
}
}
- return strconv.Itoa(-1)
+ return "No version found that matches the query."
}
+//this is for a field like deploymentName where the underyling state or data is a string
+func handleTrivialFields(qkey, qval, mkey, fieldData string) bool{
+ if qkey == mkey && qval == fieldData {
+ return true
+ }
+ return false
+}
+//this is for a field like databases where the underyling state or data is a slice of strings.
+func handleSimpleFields(vStringSlice []string, qkey, qval, mkey string) bool{
+ satisfied := false
+ for _, str := range vStringSlice {
+ if qkey == mkey && qval == str {
+ satisfied = true
+ break
+ }
+ }
+ return satisfied
+}
+func handleCompositeFields(vSliceMap []map[string]string, mapRelationships map[string][][]string, qkey, qval, mkey string) bool{
+ for _, mymap := range vSliceMap { //looping through each elem in the mapSlice
+ // check if there is any
+ // other necessary requirements for this mkey.
+ // if key exists, then multiple attributes have to be satisfied
+ // at once for the query to work.
+
+ //For example, say fields username and password belong to
+ //an attribute in the spec called 'users'. The username and password
+ //must be satisfied together in some element of vSliceMap since,
+ //Since It cannot be the case where username is found in index 1 of vSliceMapSlice and password cannot
+ //is found in index 2 of vSliceMapSlice.
+
+ // 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
+ //of the joint query. (all need to be true for it to have found qkey to be true)
+ for _, pair := range attributeCombinedQuery {
+ qckey := pair[0] //for each field/value pair, must find each qckey in
+ //the map object mymap
+ qcval := pair[1]
+ pairSatisfied := false
+ for okey, ovalue := range mymap {
+ if qckey == okey && qcval == ovalue {
+ pairSatisfied = true
+ }
+ }
+ jointQueryResults = append(jointQueryResults, pairSatisfied)
+ }
-//TODO: add optional parameters to spechistory route in apiserver.go, and call this method.
-// Right now it is actually unused
-func (o ObjectLineage) SpecHistoryInterval(vNumStart, vNumEnd int) []string {
- //order keys so we append in order later, reference: https://blog.golang.org/go-maps-in-action#TOC_7.
- s := make([]Spec, 0)
- for _, spec := range o {
- s = append(s, spec)
- }
- sort.Slice(s, func(i, j int) bool {
- return s[i].Version < s[i].Version
- })
- specs := make([]string, 0)
- for _, spec := range s {
- if spec.Version >= vNumStart && spec.Version <= vNumEnd {
- specs = append(specs, spec.String())
+ allTrue := all(jointQueryResults)
+ // Note: I cannot just return allTrue because this breaks the outer loop ..
+ // The logic goes: if allTrue is never set to True, out of all the elements in outer loop mymap,
+ // then the loop will finish and return false. return allTrue with no if statement, would stop the
+ // loop and only check one mymap! Need to scan through all before returning false
+ if allTrue{//satisfied the joint query
+ return allTrue
+ }
+ } else {
+ //if there is no attribute relationship, but the mapslice type assert was fine,
+ //only need to find an okey in one of the maps, where that query field/value (qkey,qvalue)
+ //is satisfied.
+ for okey, ovalue := range mymap {
+ //If it never returns here, out of all the elements in mymap, Then
+ //the qkey/qval was never found in the attributes within any map in the slice.
+ if qkey == okey && qval == ovalue {
+ return true
+ }
+ }
}
}
- return specs
+ return false
+}
+//Method that returns true if all the elements in boolSlice are True
+func all(boolSlice []bool) bool{
+ allTrue := true
+ for _, b := range boolSlice{
+ if !b{
+ allTrue = false
+ }
+ }
+ return allTrue
}
+//Method that compares the elements within 2 mapSlices.
+//Each map must have a corresponding map
+func compareMaps(mapSlice1, mapSlice2 []map[string]string) bool {
+ //little trick so that I loop through the bigger map slice,
+ if len(mapSlice2) != len(mapSlice1){
+ return false
+ }
+
+ foundMatches := make([]bool, 0)
+ for i := 0; i < len(mapSlice1); i++ {
+ mapleft := mapSlice1[i]
+ foundMatch := false
+ //each element in map1, must have a elem in map2 that matches it.
+ for j := 0; j < len(mapSlice2); j++ {
+ mapright := mapSlice2[j]
+ // Each attribute in left map must match an attribute in right map,
+ // so andGate represents each attribute's match.
+ andGate := make([]bool, 0)
+ for lkey, lval := range mapleft{
+ //if ok is true, that means that the keys matched.
+ rval,ok := mapright[lkey]
+ if ok && lval ==rval{
+ andGate = append(andGate, true)
+ }else{
+ andGate = append(andGate, false)
+ }
+ }
+ foundMatch = all(andGate)
+ // If foundMatch is true, then we found a match for mapleft, then break the map2 loop
+ // and move on to the next element in mapLeft.
+ if foundMatch{
+ break
+ }
+ }
+ foundMatches = append(foundMatches, foundMatch)
+ }
+ return all(foundMatches)
+}
func (o ObjectLineage) FullDiff(vNumStart, vNumEnd int) string {
var b strings.Builder
sp1 := o[vNumStart]
sp2 := o[vNumEnd]
for attribute, data1 := range sp1.AttributeToData {
- if data2, ok := sp2.AttributeToData[attribute]; ok {
- if data1 != data2 {
- fmt.Fprintf(&b, "FOUND DIFF")
- fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumStart, data1)
- fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumEnd, data2)
- } else {
- fmt.Fprintf(&b, "No difference for attribute %s \n", attribute)
- }
+ data2, ok := sp2.AttributeToData[attribute] //check if the attribute even exists
+ if ok {
+ getDiff(&b, attribute, 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")
- fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumStart, data1)
- fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumEnd, "No attribute found.")
+ 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.")
}
}
//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")
- fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumStart, "No attribute found.")
- fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumEnd, data1)
+ 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)
}
}
return b.String()
}
+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)
+ }
+ 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)
+ }
+ 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
+ }
+ }
+ 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 {
+ 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)
+ }
+ }
+
+ return b.String()
+}
func (o ObjectLineage) FieldDiff(fieldName string, vNumStart, vNumEnd int) string {
var b strings.Builder
data1, ok1 := o[vNumStart].AttributeToData[fieldName]
data2, ok2 := o[vNumEnd].AttributeToData[fieldName]
switch {
case ok1 && ok2:
- if data1 != data2 {
- fmt.Fprintf(&b, "FOUND DIFF\n")
- fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumStart, data1)
- fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumEnd, data2)
- } else {
- fmt.Fprintf(&b, "No difference for attribute %s \n", fieldName)
- }
+ return getDiff(&b, fieldName, data1, data2, vNumStart, vNumEnd)
case !ok1 && ok2:
- fmt.Fprintf(&b, "FOUND DIFF")
- fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumStart, "No attribute found.")
- fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumEnd, data2)
+ 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)
case ok1 && !ok2:
- fmt.Fprintf(&b, "FOUND DIFF")
- fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumStart, data1)
- fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumEnd, "No attribute found.")
+ 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.")
case !ok1 && !ok2:
fmt.Fprintf(&b, "Attribute not found in either version %d or %d", vNumStart, vNumEnd)
}
@@ -313,72 +590,95 @@ func parse() {
}
requestobj := event.RequestObject
+ timestamp := fmt.Sprint(event.RequestReceivedTimestamp.Format("2006-01-02 15:04:05"))
//now parse the spec into this provenanceObject that we found or created
- ParseRequestObject(provObjPtr, requestobj.Raw)
+ parseRequestObject(provObjPtr, requestobj.Raw, timestamp)
}
if err := scanner.Err(); err != nil {
panic(err)
}
- fmt.Println("Done parsing.")
}
-
-func ParseRequestObject(objectProvenance *ProvenanceOfObject, requestObjBytes []byte) {
+//This method is to parse the bytes of the requestObject attribute of Event,
+//build the spec object, and save that spec to the ObjectLineage map under the next version number.
+func parseRequestObject(objectProvenance *ProvenanceOfObject, requestObjBytes []byte, timestamp string) {
fmt.Println("entering parse request")
var result map[string]interface{}
json.Unmarshal([]byte(requestObjBytes), &result)
- l1, ok := result["metadata"].(map[string]interface{})
+ map1, ok := result["metadata"].(map[string]interface{})
- l2, ok := l1["annotations"].(map[string]interface{})
+ map2, ok := map1["annotations"].(map[string]interface{})
if !ok {
//sp, _ := result["spec"].(map[string]interface{})
- //TODO: for the case where a crd ObjectFullProvenance is first created, like initialize,
- //the metadata spec is empty. instead the spec field has the data
- //from the requestobjbytes: metadata:map[creationTimestamp: name:client25 namespace:default]
- //instead of "requestObject":{
- // "metadata":{
- // "annotations":{
- // "kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"postgrescontroller.kubeplus/v1\",\"kind\":\"Postgres\",\"metadata\":{\"annotations\":{},\"name\":\"client25\",\"namespace\":\"default\"},\"spec\":{\"databases\":[\"moodle\",\"wordpress\"],\"deploymentName\":\"client25\",\"image\":\"postgres:9.3\",\"replicas\":1,\"users\":[{\"password\":\"pass123\",\"username\":\"devdatta\"},{\"password\":\"pass123\",\"username\":\"pallavi\"}]}}\n"
- // }
- // },
- //fmt.Println("a: not ok") //hits here
//fmt.Println(sp)
+
+ //TODO: for the case where a crd is first created, the
+ //the annotations spec is empty, which is how subsequent requests are parsed.
+ // instead all the data I want to parse is in the spec field.
+ //Right now it is actually skipping this case.
+
return
}
- l3, ok := l2["kubectl.kubernetes.io/last-applied-configuration"].(string)
+ map3, ok := map2["kubectl.kubernetes.io/last-applied-configuration"].(string)
if !ok {
- // fmt.Println("b")
- // fmt.Println(l3)
fmt.Println("Incorrect parsing of the auditEvent.requestObj.metadata")
}
- in := []byte(l3)
+ in := []byte(map3)
var raw map[string]interface{}
json.Unmarshal(in, &raw)
spec, ok := raw["spec"].(map[string]interface{})
- // fmt.Println("c")
- // fmt.Println(spec)
if ok {
- fmt.Println("Successfully parsed")
+ fmt.Println("Parse was successful!")
+ } else {
+ fmt.Println("Parse was unsuccessful!")
}
newVersion := len(objectProvenance.ObjectFullHistory) + 1
newSpec := buildSpec(spec)
newSpec.Version = newVersion
+ newSpec.Timestamp = timestamp
objectProvenance.ObjectFullHistory[newVersion] = newSpec
fmt.Println("exiting parse request")
}
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 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 intField int
+ if err := json.Unmarshal(bytes, &intField); err == nil {
+ isInt = true
+ }
+ switch {
+ case isMap:
+ mySpec.AttributeToData[attribute] = mapSliceField
+ case isStringSlice:
+ mySpec.AttributeToData[attribute] = stringSliceField
+ case isString:
+ mySpec.AttributeToData[attribute] = plainStringField
+ case isInt:
+ mySpec.AttributeToData[attribute] = intField
+ default:
+ 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 {