Repository: doitintl/kube-no-trouble (go)

Issue:
display target api to use
Hello

I did run your script and it's nice, but would be cool to specify some helpful information if possible.

```
1:24PM INF >>> Kube No Trouble `kubent` <<<
1:24PM INF version 0.2.0 (git sha b9b45f347c7e67cf488d8dd6e20c8b848c595a3e)
1:24PM INF Initializing collectors and retrieving data
1:24PM INF Retrieved 70 resources from collector name=Cluster
1:24PM INF Retrieved 0 resources from collector name="Helm v2"
1:24PM INF Retrieved 0 resources from collector name="Helm v3"
1:24PM INF Loaded ruleset name=deprecated-1-16.rego
1:24PM INF Loaded ruleset name=deprecated-1-20.rego
________________________________________________________________________________________
>>> 1.20 Deprecated APIs <<<
----------------------------------------------------------------------------------------
KIND      NAMESPACE    NAME                 API_VERSION    NEW_API_VERSION
Ingress   test         test-ingress    extensions/v1beta1  networking.k8s.io/v1beta1
```

or event a link to the current version for deprecation api.
Example : https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/ or https://v1-14.docs.kubernetes.io/docs/concepts/services-networking/ingress/



Pull request:
Feat replace since
This PR adds support to display replacement API version and since when this new API is available in `REPLACE_WITH (SINCE)` column (separate `ReplaceWith` and `Since` fields in json):

```
__________________________________________________________________________________________
>>> Deprecated APIs removed in 1.16 <<<
------------------------------------------------------------------------------------------
KIND         NAMESPACE     NAME                   API_VERSION    REPLACE_WITH (SINCE)
Deployment   <undefined>   nginx-deployment-old   apps/v1beta1   apps/v1 (1.9+)
__________________________________________________________________________________________
>>> Deprecated APIs removed in 1.22 <<<
------------------------------------------------------------------------------------------
KIND      NAMESPACE     NAME           API_VERSION          REPLACE_WITH (SINCE)
Ingress   <undefined>   test-ingress   extensions/v1beta1   networking.k8s.io/v1beta1 (1.14+)
```

Fixes #5 

Patch:
diff --git a/Makefile b/Makefile
index aa67e88..685c3d2 100644
--- a/Makefile
+++ b/Makefile
@@ -100,7 +100,7 @@ $(RELEASE_DIR)/%-$(RELEASE_SUFFIX): $(PACKED_DIR)/%-$(BIN_ARCH)
 
 ## Run Go tests
 test: generate test-fmt test-git
-	go test -coverprofile fmtcoverage.html ./...
+	go test -v -coverprofile fmtcoverage.html ./...
 .PHONY: test
 
 ## Run go and opt fmt checks
diff --git a/fixtures/ingress-v1beta1.yaml b/fixtures/ingress-v1beta1.yaml
new file mode 100644
index 0000000..8135cd1
--- /dev/null
+++ b/fixtures/ingress-v1beta1.yaml
@@ -0,0 +1,14 @@
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+  name: test-ingress
+  annotations:
+    nginx.ingress.kubernetes.io/rewrite-target: /
+spec:
+  rules:
+  - http:
+      paths:
+      - path: /testpath
+        backend:
+          serviceName: test
+          servicePort: 80
diff --git a/generated/statik/statik.go b/generated/statik/statik.go
index 58baf3a..589fd2c 100644
--- a/generated/statik/statik.go
+++ b/generated/statik/statik.go
@@ -7,6 +7,6 @@ import (
 )
 
 func init() {
-	data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\xc7\x85\x0bQ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00	\x00deprecated-1-16.regoUT\x05\x00\x01V\xcb2_\x8cS\xc1\x8a\xdb0\x10=[_1h/	x]\xbc\x87\xa5\x84&\xb0\x90K),a\x03\xbd\x18c&\xf68\x15\x96%!\xc9n\xc3\x92\x7f/\x8a\xe3l\xd2Mhn\xd2\xcc\x9b7OoF\x06\xcb\x06\xb7\x04\x15\x19K%z\xaa\xd2\xf4\x99\xb1\x16\x85\xca,\xf9\xce\xaa\x1c\xdeYd\xc9\xe9\xce\x96\x04\xb39\x08e:\x9f\x159\x8b\xb4\xac\n4\"\x04?\xea\x8b\x11;\x19\x0f\xd3P\x1f\xa8\x02\xf0\x9dE\x11\x7f\xc5\x96\xf8\x0cF@\xd2\x92\xc7\n=&\n[\x8aY\x14=@\x808\x83%A\xa5\xc9\x81\xd2\x1e~aO\xe05l\x82\xdcZ(\xaa@((\xd1\x11\xe8\x1a\xa4.QB\x8bJ\xd4\xe4\xbc\x1b\xdb\x1c8\xf8\x0c\xb6\xe4\x8b\x8aj\xec\xa4\x9f|\xea\x1b\x03W'l\x0c\xfc[\xa7\x8e\x1d\x16|\x1a\xf4\xf0\x1fBU\xe7\x8a\x1b\xa1\xaaC\xe2\xc5\x88\x9fd\x9d\xd0\x8a\xcf\xe0h\xc8!\xf1\xd6IZ\x93\xe73\xe0\xcb\x939\xf0\xb2\xfa\xee\xc0R\xab\xfbA}\x9a\xa4\xcf\xc0c\x16\xed\xd9\x9e\xb1\xab.Na>\x12\x07\xf7\xae\x9b\x8eFL\xec\xa0\nl\x82'Q\xd3\x7fh\x03n@\xa1\x11E\x7f\x04]v\xb8\x84;8\x0emIF\xea]K*\xbc)\xe3\xf4\xc7\x93\n\xd5\xeeK\x9fn\xc8c\x1a\x9cCcn\xdd\x9fx\x1e\xc3\x03<.\xe0\x18<\xcc\xf9q\x01\x8a\xfcom\x1b\xa1\xb6I\xf3\xd5%B\x0f9\xfe:\xc4WZ\x8arw\xabg\x1e\x8f4\xe6\x80\x1b\x13\x81`\xa5\xab5\x95\x9d\x15~w/\xc9\x874\xbeDj\xb5\x1aFx\xc7s\x9f\xae\xb3\xac=z\xaa;9\xf2\xfc\xc7\xa0+\x0cod\xa4(\xf1n!\xd7i\xf7\xec\xd3`\xb3\xb0\x08yV\xe40\x9f\x9f\xaf\xc3\xc5\x8e\x9d\xc7\xf7\x8c\x9d\x7f\xa3\x1ee\x0c\x0d\xedb(\xc2\x06\xf5(\xb3\x86v\xf9-P\x8dRn\xb0l\x02v<\x87\xcd\n_\xfbT\xbbg\x7f\x03\x00\x00\xff\xffPK\x07\x08\x9ee,\x94\xe2\x01\x00\x00\x92\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\xc7\x85\x0bQ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00	\x00deprecated-1-22.regoUT\x05\x00\x01V\xcb2_t\x92\xc1\x8a\xdb0\x10\x86\xcf\xd6S\x0c\xdaK\x02\xae\xdb\xe4TL\xbd\xb0\xd0\xcbR(\xa5\x85^\x8c1\x13k\x9c\n\xcb##\xc9n\x97\xe0w/J\xe24\xe9foB\xfaf\xe6\x9b\xdf\x1e\xb0\xe9pO\xa0hp\xd4` \xb5\xd9~\x10\xa2G\xcd\xa5\xa30:\xae\xe0 \x12G\xde\x8e\xae!\xc8\x0b\xd0<\x8c\xa1\xac+\x91X\xa3j\x1ct\xbc\xfcW_/\xecj9\xacc}l\x15\xc1\x83H\x12\xf9\x15{\x929,@\xd6S@\x85\x013\xc6\x9eR\x91$\x0f\x10\x11?`C\xa0,y`\x1b\xe0\x17N\x04\xc1\xc2.\xea\xb6\x9aI\x81fh\xd0\x13\xd8\x16\x8cm\xd0@\x8f\xac[\xf2\xc1/c\x8e=d\x0e{\n\xb5\xa2\x16G\x13V\xaf\xe6\xa6 \xf9\xc2\xa6 ?\x8d|\x9e\xf0(\xd7\xd1G~\xd1\xac\xae\x8d;\xcd\xea\xf8\xf04\xe8\x9f\xe4\xbc\xb6,s8\x07r|\xf8>\x1a\xfaAA\xe6 ?_\xc2\x81\xa7o\xcf\x1e\x1c\xf5v:\xd9o\xb2\xedV\xa6\"\x99\xc5,\xc4\xdd\x10\xd7P,}cx\xf73\xc7A\xaf\xdcI\n\\\x86\x17\xa7\xf5\x7fm#w\xa2p\xd0\xf5t\x86n'\xdc\xe2\x1e\n8\xc8g\xde;\xf2^\xe6PJ\xfa\x13\x88c\x9d\x7f?mv\x14p#\xab\x19\x1e\xe0\xdd#0\x85\xdf\xd6u\x9a\xf7Y\xf7\xd1g\xda.\x84x\xd5\xb6\x8c\x1aUYWP\x14\xd727\x1b^\xdf\xcfB\\\x7f\xc3	M\n\x1d\xbd\xa4PG\xff	M\xd9\xd1K\xf5\x16\xd4\xa21;l\xba\xc8.\xe7\xb8l\xfc\xaf.\xb5\xb3\xf8\x1b\x00\x00\xff\xffPK\x07\x08fx\xc8V\x8c\x01\x00\x00\x0f\x03\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xc7\x85\x0bQ\x9ee,\x94\xe2\x01\x00\x00\x92\x04\x00\x00\x14\x00	\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x00deprecated-1-16.regoUT\x05\x00\x01V\xcb2_PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\xc7\x85\x0bQfx\xc8V\x8c\x01\x00\x00\x0f\x03\x00\x00\x14\x00	\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81-\x02\x00\x00deprecated-1-22.regoUT\x05\x00\x01V\xcb2_PK\x05\x06\x00\x00\x00\x00\x02\x00\x02\x00\x96\x00\x00\x00\x04\x04\x00\x00\x00\x00"
+	data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\x0f\x94\x9eQ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00	\x00deprecated-1-16.regoUT\x05\x00\x01\xbe\xc7\xec_\xacTMk\xdc0\x10=[\xbfbP/Y0n\xddCH\x97n!\x90K)\x84\xd0\x85\xf6\xb0,fb\x8f\x13aY\x12\x96\xect	\xfb\xdf\xcb\xf8c\xe3$\x1bZ\xc2\xde\xec\x997\xf3\xde<i\xe40\xaf\xf0\x8e\xa0 \xd7P\x8e\x81\x8a4=\x17\xa2Fe6\x0d\x85\xb61[x\x14QC\xde\xb6MN\xb0\\\x812\xae\x0d\x9bl+\"t\x8a\x03O\xb5\xd9\x84;\x9b>\x16\\\xcbm\x18\xf8(\xa2H^cMr	\x13 \xa9)`\x81\x01\x13\x835\xc5\"\x8a>\x00C\xbc\xc3\x9c\xa0\xb0\xe4\xc1\xd8\x00\xf7\xd8\x11\x04\x0b\xb7,\xb5T\x86\nP\x06r\xf4\x04\xb6\x04ms\xd4P\xa3Q%\xf9\xe0'\x9a\xbe\x87\\\xc2\x1d\x85\xac\xa0\x12[\x1d\xce^\xf1\xc6 \xcd\x01\x1b\x83\xfc\xda\x9a\x91\xe1\x9b\\\xb0\x1e\xf9C\x99b\xae\xb8R\xa6\xe8\x13\x97N\xfd\xa2\xc6+k\xe4\x12\xd0\xa9\xc4\xea!\xf1\x93\x9c\xc6\x9c~\xabp?f\x0c=\x0c\x99V\xd3\x9a\x82\\\x82\xbc:\xd8\x06\x977\xdf=4T\xdbn\x98+M\xd2s\xd9\xe3\xd7\xca\xf4#p\x0f\xcf\xdf\xb1\x88\xf6b/\xc4Q\xd3\x17\xb0b$\x1b\xfd\xfal\xd0\xa9\xb3f\x10\x0fM\x82\x07\xed\x8b\x17\xed\x187\xa0\xd0\xa9\xac\x1bAO\x9d\x9fC=\x8c\xe7zEN\xdb]M\x86\x87\xe3H$\xadf\xdf6\x92\xfe\x042\xdc\xc5\x7f\xec\xd2[\n\x98\xb2\xd1\xe8\xdc[\xff\x9f\xe56\xee;\x18z`\xab\xc6\x94\x1c\x82~\xf4D\xa6\xc9\x97>\xb4\xef\xad\xba\xa6\xf0`\x9b\xea\xc6j\x95\xef\xfeG\xc3s\x0e3\x94+s\x97T\x17>Q\xf6(\xe1\xc5\x8c\xf0\xc6\x16k\xca\xdbF\x85\xdd{I]_\xf7\xe4\xc2K\xba\xf4\xd3\x8c\xef\n\xa9\xb6f\xb8=\xff\xe49\x81\xa1\xeb\x80\x81\xcaV\x1fc<\xf5\xe1\xf1\xc6\xa8\x1c\xdf5\xdc	\x04\xec\xc5\xab{\xbd\xe1\x1d\xd8\xf2Jo\xb2-\xacV\xf3m8\xacW\x7f\xf3\x07\xa1\xb3t?\xd1@~\xbc\xe9\xf4\x1aLZ\x8e\xa3\x9e\xed\xfb\xfc\x0d\xebP\xc7P\xd1.\x86\x8c\xf7\xb2C\xbd\xa9h\xb7}\x0bT\xa2\xd6\xb7\x98W\x8c\x9d\xbeY9\xbf\xab\x87\xda\xbd\xf8\x1b\x00\x00\xff\xffPK\x07\x08q\xe1s\xb6\x1f\x02\x00\x00\x0b\x06\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x12\x94\x9eQ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00	\x00deprecated-1-22.regoUT\x05\x00\x01\xc5\xc7\xec_t\x93Q\x8b\x9b@\x10\xc7\x9f\xddO1l_. \xb6\x1e}(\xd2\x14\x0e\xfar\x14J\xe9A\xfb \"\x13\x9d\xe4\x16\xd7]\xd9]M\x8f\x90\xef^F\xa3\xe75\xe9\xdb\xb0\xf3\x9b\x99\xff\xfcG;\xac\x1a<\x10\xd4\xd49\xaa0P\x9d\xde\x7f\x10\xa2EerG\xa1w\xa6\x80\x93\x88\x1cy\xdb\xbb\x8a \xdb\x822]\x1f\xf2\xb2\x10\x11v\x8a\x1f^k\xcb\x99\xbb\x9b\x83\x0d\xd7r\x1b\x06O\"\x8a\xe4wlIf0\x03IK\x01k\x0c\x98\x18l)\x16Q\xf4\x0e\x18\xf1\x1dV\x04\xb5%\x0f\xc6\x06x\xc6\x81 X\xd8\xb1\xd4\xbd2T\x832P\xa1'\xb0{\xd0\xb6B\x0d-\x1a\xb5'\x1f\xfc<f\xec!38P(k\xdac\xaf\xc3\xdd\xd5\xdc\x18\xa4Y\xd8\x18\xe4\xe7\xde\\&|\x91\x1b\xd6#\xbf)S\xaf\x157\xca\xd4c\xe2\xa1S\xbf\xc8ye\x8d\xcc\x00;\x95X=%~R\xa7\xb1\xa2\xdf*<_2\x86\x8eS\xa6\xd7\xf4DAf \xbf.\xb6\xc1\xc3\x8fG\x0f\x8eZ;L{\xa5\xc9\xfd\xbd\x1c\xf9'e\xc6\x15\xb8\x87\xe78\x16\xd1Y\x9c\x85\xb8i\xfa\x06\xb6L\xb2\xd1\xd7\xb7\xc1N\xdd\xb9I<\xb8\x04\x17\xed\x9b\x7f\xda17Q\xd8\xa9r\xb8@\xaf\x9d\xdf\xa2\x1e\xb6p\x92\x8f\xe6\xe0\xc8{\x99M7\xb6\x9a\x0d\xcb%\xfd	d\xb8\xdc\xbf\x1f\xd2\x1d\x05Le1\xaee\xe8\xc8\x16\x18\nG\xeb\x1ae\x0eI\xf3\xc9'\xca.\xd8H\xf9\xcb\xf22M\xd2\x8f\xfct>\x8b\xab\xf99k-\xd8\xfa\xbc,`\xbb]\xab\x16\x8b\x0f+Y\xab\xfcJ\xcb\xed\xae\xf3\xd9f%\xb7\xa97\x87Y\x7fl\x03\xea\x18\x1az\x89\xa1d\x03\x07\xd4yC/\xc5\xff\xa0=j\xbd\xc3\xaaav\x8eY9\xff\x00K\xedY\xfc\x0d\x00\x00\xff\xffPK\x07\x08\xe9\x8a\x04\xd1\xc3\x01\x00\x00\xb4\x03\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x0f\x94\x9eQq\xe1s\xb6\x1f\x02\x00\x00\x0b\x06\x00\x00\x14\x00	\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x00deprecated-1-16.regoUT\x05\x00\x01\xbe\xc7\xec_PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x12\x94\x9eQ\xe9\x8a\x04\xd1\xc3\x01\x00\x00\xb4\x03\x00\x00\x14\x00	\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81j\x02\x00\x00deprecated-1-22.regoUT\x05\x00\x01\xc5\xc7\xec_PK\x05\x06\x00\x00\x00\x00\x02\x00\x02\x00\x96\x00\x00\x00x\x04\x00\x00\x00\x00"
 	fs.Register(data)
 }
diff --git a/pkg/judge/judge.go b/pkg/judge/judge.go
index 4891a32..c4816cf 100644
--- a/pkg/judge/judge.go
+++ b/pkg/judge/judge.go
@@ -1,11 +1,13 @@
 package judge
 
 type Result struct {
-	Name       string
-	Namespace  string
-	Kind       string
-	ApiVersion string
-	RuleSet    string
+	Name        string
+	Namespace   string
+	Kind        string
+	ApiVersion  string
+	RuleSet     string
+	ReplaceWith string
+	Since       string
 }
 
 type Judge interface {
diff --git a/pkg/judge/rego.go b/pkg/judge/rego.go
index eeafc18..727db79 100644
--- a/pkg/judge/rego.go
+++ b/pkg/judge/rego.go
@@ -72,11 +72,13 @@ func (j *RegoJudge) Eval(input []map[string]interface{}) ([]Result, error) {
 			for _, i := range e.Value.([]interface{}) {
 				m := i.(map[string]interface{})
 				results = append(results, Result{
-					Name:       m["Name"].(string),
-					Namespace:  m["Namespace"].(string),
-					Kind:       m["Kind"].(string),
-					ApiVersion: m["ApiVersion"].(string),
-					RuleSet:    m["RuleSet"].(string),
+					Name:        m["Name"].(string),
+					Namespace:   m["Namespace"].(string),
+					Kind:        m["Kind"].(string),
+					ApiVersion:  m["ApiVersion"].(string),
+					ReplaceWith: m["ReplaceWith"].(string),
+					RuleSet:     m["RuleSet"].(string),
+					Since:       m["Since"].(string),
 				})
 			}
 		}
diff --git a/pkg/printer/text.go b/pkg/printer/text.go
index 5482ad6..dba301e 100644
--- a/pkg/printer/text.go
+++ b/pkg/printer/text.go
@@ -46,9 +46,9 @@ func (c *TextPrinter) Print(results []judge.Result) error {
 			fmt.Fprintf(w, "%s\n", strings.Repeat("_", 90))
 			fmt.Fprintf(w, ">>> %s <<<\n", ruleSet)
 			fmt.Fprintf(w, "%s\n", strings.Repeat("-", 90))
-			fmt.Fprintf(w, "%s\t%s\t%s\t%s\t\n", "KIND", "NAMESPACE", "NAME", "API_VERSION")
+			fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s (%s)\n", "KIND", "NAMESPACE", "NAME", "API_VERSION", "REPLACE_WITH", "SINCE")
 		}
-		fmt.Fprintf(w, "%s\t%s\t%s\t%s\t\n", r.Kind, r.Namespace, r.Name, r.ApiVersion)
+		fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s (%s+)\n", r.Kind, r.Namespace, r.Name, r.ApiVersion, r.ReplaceWith, r.Since)
 	}
 	w.Flush()
 	return nil
diff --git a/rules/deprecated-1-16.rego b/rules/deprecated-1-16.rego
index 5016b5e..4cf5602 100644
--- a/rules/deprecated-1-16.rego
+++ b/rules/deprecated-1-16.rego
@@ -2,38 +2,63 @@ package deprecated116
 
 main[return] {
 	resource := input[_]
-	old_api := deprecated_resource(resource)
+	api := deprecated_resource(resource)
 	return := {
 		"Name": resource.metadata.name,
 		# Namespace does not have to be defined in case of local manifests
 		"Namespace": get_default(resource.metadata, "namespace", "<undefined>"),
 		"Kind": resource.kind,
-		"ApiVersion": old_api,
+		"ApiVersion": api.old,
+		"ReplaceWith": api.new,
 		"RuleSet": "Deprecated APIs removed in 1.16",
+		"Since": api.since,
 	}
 }
 
-deprecated_resource(r) = old_api {
-	old_api := deprecated_api(r.kind, r.apiVersion)
+deprecated_resource(r) = api {
+	api := deprecated_api(r.kind, r.apiVersion)
 }
 
-deprecated_api(kind, api_version) = old_api {
+deprecated_api(kind, api_version) = api {
 	deprecated_apis = {
-		"Deployment": ["extensions/v1beta1", "apps/v1beta1", "apps/v1beta2"], # -> apps/v1
-		# -> networking.k8s.io/v1
-		"NetworkPolicy": ["extensions/v1beta1"],
-		# -> policy/v1beta1
-		"PodSecurityPolicy": ["extensions/v1beta1"],
-		# -> apps/v1
-		"DaemonSet": ["extensions/v1beta1", "apps/v1beta2"],
-		# -> apps/v1
-		"StatefulSet": ["apps/v1beta1", "apps/v1beta2"],
-		# -> apps/v1
-		"ReplicaSet": ["extensions/v1beta1", "apps/v1beta1", "apps/v1beta2"],
+		"Deployment": {
+			"old": ["extensions/v1beta1", "apps/v1beta1", "apps/v1beta2"],
+			"new": "apps/v1",
+			"since": "1.9",
+		},
+		"NetworkPolicy": {
+			"old": ["extensions/v1beta1"],
+			"new": "networking.k8s.io/v1",
+			"since": "1.8",
+		},
+		"PodSecurityPolicy": {
+			"old": ["extensions/v1beta1"],
+			"new": "policy/v1beta1",
+			"since": "1.10",
+		},
+		"DaemonSet": {
+			"old": ["extensions/v1beta1", "apps/v1beta2"],
+			"new": "apps/v1",
+			"since": "1.9",
+		},
+		"StatefulSet": {
+			"old": ["apps/v1beta1", "apps/v1beta2"],
+			"new": "apps/v1",
+			"since": "1.9",
+		},
+		"ReplicaSet": {
+			"old": ["extensions/v1beta1", "apps/v1beta1", "apps/v1beta2"],
+			"new": "apps/v1",
+			"since": "1.9",
+		},
 	}
 
-	deprecated_apis[kind][_] == api_version
-	old_api := api_version
+	deprecated_apis[kind].old[_] == api_version
+	api := {
+		"old": api_version,
+		"new": deprecated_apis[kind].new,
+		"since": deprecated_apis[kind].since,
+	}
 }
 
 get_default(val, key, _) = val[key]
diff --git a/rules/deprecated-1-22.rego b/rules/deprecated-1-22.rego
index 99ee906..a55e768 100644
--- a/rules/deprecated-1-22.rego
+++ b/rules/deprecated-1-22.rego
@@ -2,26 +2,37 @@ package deprecated120
 
 main[return] {
 	resource := input[_]
-	old_api := deprecated_resource(resource)
+	api := deprecated_resource(resource)
 	return := {
 		"Name": resource.metadata.name,
 		# Namespace does not have to be defined in case of local manifests
 		"Namespace": get_default(resource.metadata, "namespace", "<undefined>"),
 		"Kind": resource.kind,
-		"ApiVersion": old_api,
+		"ApiVersion": api.old,
+		"ReplaceWith": api.new,
 		"RuleSet": "Deprecated APIs removed in 1.22",
+		"Since": api.since,
 	}
 }
 
-deprecated_resource(r) = old_api {
-	old_api := deprecated_api(r.kind, r.apiVersion)
+deprecated_resource(r) = api {
+	api := deprecated_api(r.kind, r.apiVersion)
 }
 
-deprecated_api(kind, api_version) = old_api {
-	deprecated_apis = {"Ingress": ["extensions/v1beta1"]} # -> networking.k8s.io/v1beta1
+deprecated_api(kind, api_version) = api {
+	deprecated_apis = {"Ingress": {
+		"old": ["extensions/v1beta1"],
+		"new": "networking.k8s.io/v1beta1",
+		"since": "1.14",
+	}}
 
-	deprecated_apis[kind][_] == api_version
-	old_api := api_version
+	deprecated_apis[kind].old[_] == api_version
+
+	api := {
+		"old": api_version,
+		"new": deprecated_apis[kind].new,
+		"since": deprecated_apis[kind].since,
+	}
 }
 
 get_default(val, key, _) = val[key]


Test patch:
diff --git a/pkg/judge/rego_test.go b/pkg/judge/rego_test.go
index 0ba0423..a0919fb 100644
--- a/pkg/judge/rego_test.go
+++ b/pkg/judge/rego_test.go
@@ -1,9 +1,18 @@
 package judge
 
 import (
+	"github.com/ghodss/yaml"
+	"io/ioutil"
 	"testing"
 )
 
+func TestNewRegoJudge(t *testing.T) {
+	_, err := NewRegoJudge(&RegoOpts{})
+	if err != nil {
+		t.Errorf("failed to create judge instance: %s", err)
+	}
+}
+
 func TestEvalEmpty(t *testing.T) {
 	inputs := []map[string]interface{}{}
 
@@ -21,3 +30,60 @@ func TestEvalEmpty(t *testing.T) {
 		t.Errorf("expected empty array, instead got: %v", results)
 	}
 }
+
+func TestEvalRules(t *testing.T) {
+	testCases := []struct {
+		name       string
+		inputFiles []string // file list
+		expected   []string // findings - kinds
+	}{
+		{"deprecated-1-16.rego", []string{"../../fixtures/deployment-v1beta1.yaml"}, []string{"Deployment"}},
+		{"deprecated-1-22.rego", []string{"../../fixtures/ingress-v1beta1.yaml"}, []string{"Ingress"}},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+
+			var manifests []map[string]interface{}
+
+			for _, f := range tc.inputFiles {
+				var input []byte
+				var err error
+
+				input, err = ioutil.ReadFile(f)
+				if err != nil {
+					t.Errorf("failed to read file %s: %v", f, err)
+				}
+
+				var manifest map[string]interface{}
+				err = yaml.Unmarshal([]byte(input), &manifest)
+				if err != nil {
+					t.Errorf("failed to parse file %s: %v", f, err)
+				}
+
+				manifests = append(manifests, manifest)
+			}
+
+			judge, err := NewRegoJudge(&RegoOpts{})
+			if err != nil {
+				t.Errorf("failed to create judge instance: %s", err)
+			}
+
+			results, err := judge.Eval(manifests)
+			if err != nil {
+				t.Errorf("failed to evaluate input: %s", err)
+			}
+
+			if len(results) != len(tc.expected) {
+				t.Errorf("expected %d findings, instead got: %d", len(tc.expected), len(results))
+			}
+
+			for i, _ := range results {
+				if results[i].Kind != tc.expected[i] {
+					t.Errorf("expected to get %s finding, instead got: %s", tc.expected[i], results[i].Kind)
+				}
+			}
+		})
+	}
+
+}