diff --git a/cli/parser.go b/cli/parser.go index e4330fb..f4d9127 100644 --- a/cli/parser.go +++ b/cli/parser.go @@ -164,6 +164,7 @@ func ValidateKubernetesObjects(objects []string) ([]string, []string) { "clusterrole": {"clusterrole", "clusterroles"}, "clusterrolebinding": {"clusterrolebinding", "clusterrolebindings"}, "crd": {"crd", "crds", "customresourcedefinition", "customresourcedefinitions"}, + "networkpolicy": {"networkpolicy", "networkpolicies"}, // Add more valid objects and their aliases as needed } diff --git a/compare/compare.go b/compare/compare.go index 3213b6a..4bf3bd7 100644 --- a/compare/compare.go +++ b/compare/compare.go @@ -83,6 +83,10 @@ var typeAssertions = map[string]TypeAssertionFunc{ val, ok := obj.(*RbacV1.ClusterRoleBindingList) return ok, val }, + "*networkingv1.NetworkPolicyList": func(obj interface{}) (bool, interface{}) { + val, ok := obj.(*networkingv1.NetworkPolicyList) + return ok, val + }, } func GetTypeInfo(obj interface{}) (string, interface{}) { diff --git a/compare/networkpolicy.go b/compare/networkpolicy.go new file mode 100644 index 0000000..093fa32 --- /dev/null +++ b/compare/networkpolicy.go @@ -0,0 +1,32 @@ +package compare + +import ( + "fmt" + "kompare/DAO" + "kompare/cli" + "kompare/query" + "kompare/tools" + + "k8s.io/client-go/kubernetes" +) + +func CompareNetworkPolicies(clientsetToSource, clientsetToTarget *kubernetes.Clientset, namespaceName string, TheArgs cli.ArgumentsReceivedValidated) ([]DAO.DiffWithName, error) { + var TheDiff []DAO.DiffWithName + sourceNetworkPolicies, err := query.ListNetworkPolicies(clientsetToSource, namespaceName) + if err != nil { + fmt.Printf("Error getting services list: %v\n", err) + return TheDiff, err + } + targetNetworkPolicies, err := query.ListNetworkPolicies(clientsetToTarget, namespaceName) + if err != nil { + fmt.Printf("Error getting services list: %v\n", err) + return TheDiff, err + } + var diffCriteria []string + if TheArgs.FiltersForObject == "" { + diffCriteria = []string{"Spec", "Name", "Annotations"} + } else { + diffCriteria = tools.ParseCommaSeparateList(TheArgs.FiltersForObject) + } + return CompareVerboseVSNonVerbose(sourceNetworkPolicies, targetNetworkPolicies, diffCriteria, TheArgs) +} diff --git a/main.go b/main.go index d90a6fe..43c15ad 100644 --- a/main.go +++ b/main.go @@ -27,14 +27,14 @@ func main() { // Connect to source cluster clientsetToSource, err := connect.ConnectToSource(args.SourceClusterContext, &args.KubeconfigFile) if err != nil { - err = fmt.Errorf("error connecting to source cluster: %v\n", err) + err = fmt.Errorf("error connecting to source cluster: %v", err) panic(err) } // Connect to target cluster clientsetToTarget, err := connect.ContextSwitch(args.TargetClusterContext, &args.KubeconfigFile) if err != nil { - err = fmt.Errorf("error switching context: %v\n", err) + err = fmt.Errorf("error switching context: %v", err) panic(err) } @@ -184,7 +184,7 @@ func iterateGoglabObjects(clientsetToSource, clientsetToTarget *kubernetes.Clien func compareAllResourcesInNamespace(clientsetToSource, clientsetToTarget *kubernetes.Clientset, namespace string, TheArgs cli.ArgumentsReceivedValidated) { fmt.Printf("Looping on Namespace: %s\n", namespace) // Compare all resources for the namespace - resources := []string{"deployment", "ingress", "service", "serviceaccount", "configmap", "secret", "role", "rolebinding", "hpa", "cronjob"} + resources := []string{"deployment", "ingress", "service", "serviceaccount", "configmap", "secret", "role", "rolebinding", "hpa", "cronjob", "networkpolicy"} // Create a title case converter for English titleCase := cases.Title(language.English) @@ -209,7 +209,7 @@ func compareResourcesByLists(clientsetToSource, clientsetToTarget *kubernetes.Cl titleCase := cases.Title(language.English) // Define all resources - allResources := []string{"deployment", "ingress", "service", "sa", "configmap", "secret", "role", "rolebinding"} + resources := []string{"deployment", "ingress", "service", "serviceaccout", "configmap", "secret", "role", "rolebinding", "networkpolicy", "hpa", "cronjob"} // Compare resources based on include list for _, resource := range includeResources { @@ -222,7 +222,7 @@ func compareResourcesByLists(clientsetToSource, clientsetToTarget *kubernetes.Cl // Compare resources based on exclude list if excludeResources != nil { - for _, resource := range allResources { + for _, resource := range resources { // Check if resource is not in the exclude list if !tools.IsInList(resource, excludeResources) { titleResource := titleCase.String(resource) @@ -254,7 +254,7 @@ func compareResource(clientsetToSource, clientsetToTarget *kubernetes.Clientset, err = fmt.Errorf("error comparing Services: %v", err) panic(err) } - case "sa": + case "serviceaccount": _, err := compare.CompareServiceAccounts(clientsetToSource, clientsetToTarget, namespace, TheArgs) if err != nil { err = fmt.Errorf("error comparing Service Accounts: %v", err) @@ -296,6 +296,12 @@ func compareResource(clientsetToSource, clientsetToTarget *kubernetes.Clientset, err = fmt.Errorf("error comparing Cron Jobs: %v", err) panic(err) } + case "networkpolicy": + _, err := compare.CompareNetworkPolicies(clientsetToSource, clientsetToTarget, namespace, TheArgs) + if err != nil { + err = fmt.Errorf("error comparing Network Policies: %v", err) + panic(err) + } } } @@ -308,7 +314,7 @@ func iterateNamespaces(sourceNameSpacesList *v1.NamespaceList, clientsetToSource } } else { // Compare resources based on include or exclude lists - resources := []string{"deployment", "ingress", "service", "serviceaccount", "configmap", "secret", "role", "rolebinding", "hpa", "cronjob"} + resources := []string{"deployment", "ingress", "service", "serviceaccount", "configmap", "secret", "role", "rolebinding", "hpa", "cronjob", "networkpolicy"} if tools.AreAnyInLists(TheArgs.Include, resources) || tools.AreAnyInLists(TheArgs.Exclude, resources) { for _, ns := range sourceNameSpacesList.Items { compareResourcesByLists(clientsetToSource, clientsetToTarget, ns.Name, TheArgs) diff --git a/mock/k8s.go b/mock/k8s.go index c14c4d7..e94a49a 100644 --- a/mock/k8s.go +++ b/mock/k8s.go @@ -40,6 +40,7 @@ func StartMockCluster() (string, *mux.Router, error) { r.HandleFunc("/apis/rbac.authorization.k8s.io/v1/clusterroles", GetClusterRoles).Methods("GET") r.HandleFunc("/apis/rbac.authorization.k8s.io/v1/clusterrolebindings", GetClusterRoleBindings).Methods("GET") r.HandleFunc("/api/v1/namespaces/{namespace}/serviceaccounts", GetServiceAccounts).Methods("GET") + r.HandleFunc("/apis/networking.k8s.io/v1/namespaces/{namespace}/networkpolicies", GetNetworkPolicies).Methods("GET") // Create a HTTP server instance server := &http.Server{ @@ -720,6 +721,81 @@ func GetServices(w http.ResponseWriter, r *http.Request) { } } +func GetNetworkPolicies(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + namespace := vars["namespace"] + + // Check if the namespace is "namespace2" + if namespace == "namespace2" { + // Create three sample network policies for namespace2 + networkPolicies := &networkingv1.NetworkPolicyList{ + ListMeta: metav1.ListMeta{ + ResourceVersion: "320850103", // Set a sample resource version + }, + Items: []networkingv1.NetworkPolicy{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "networkpolicy1", + Namespace: "namespace2", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "networkpolicy2", + Namespace: "namespace2", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "networkpolicy3", + Namespace: "namespace2", + }, + }, + }, + } + + // Convert the NetworkPolicyList object to JSON + jsonResponse, err := json.Marshal(networkPolicies) + if err != nil { + http.Error(w, "Error marshalling JSON response", http.StatusInternalServerError) + return + } + + // Set the response headers and write the JSON response + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err = w.Write(jsonResponse) + if err != nil { + http.Error(w, fmt.Sprintf("Error writing the JSON response: %v", err), http.StatusInternalServerError) + return + } + } else { + // If the namespace is not "namespace2", return an empty list of network policies + networkPolicies := &networkingv1.NetworkPolicyList{ + ListMeta: metav1.ListMeta{ + ResourceVersion: "0", + }, + Items: []networkingv1.NetworkPolicy{}, + } + + // Convert the NetworkPolicyList object to JSON + jsonResponse, err := json.Marshal(networkPolicies) + if err != nil { + http.Error(w, "Error marshalling JSON response", http.StatusInternalServerError) + return + } + + // Set the response headers and write the JSON response + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err = w.Write(jsonResponse) + if err != nil { + http.Error(w, fmt.Sprintf("Error writing the JSON response: %v", err), http.StatusInternalServerError) + return + } + } +} + func SetupTestEnvironment() (string, string, *os.File) { // Set up temporary kubeconfig file with mock cluster URLs sourceClusterURL, _, _ := StartMockCluster() diff --git a/query/query.go b/query/query.go index 7deb3c3..02257a4 100644 --- a/query/query.go +++ b/query/query.go @@ -29,7 +29,7 @@ import ( func ListNameSpaces(clientset *kubernetes.Clientset) (*Corev1.NamespaceList, error) { nsList, err := clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("Failed to query the NameSpace List: %w", err) + return nil, fmt.Errorf("failed to query the NameSpace List: %w", err) } return nsList, nil } @@ -37,7 +37,7 @@ func ListNameSpaces(clientset *kubernetes.Clientset) (*Corev1.NamespaceList, err func GetNamespace(clientset *kubernetes.Clientset, name string) (*Corev1.Namespace, error) { ns, err := clientset.CoreV1().Namespaces().Get(context.TODO(), name, metav1.GetOptions{}) if err != nil { - return nil, fmt.Errorf("Failed to query the NameSpace Get: %w", err) + return nil, fmt.Errorf("failed to query the NameSpace Get: %w", err) } return ns, nil } @@ -55,7 +55,7 @@ func ListDeployments(clientset *kubernetes.Clientset, nameSpace string) (*v1.Dep } deployments_list, err := clientset.AppsV1().Deployments(nameSpace).List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("Failed to query the Deployment List: %w", err) + return nil, fmt.Errorf("failed to query the Deployment List: %w", err) } return deployments_list, nil } @@ -72,7 +72,7 @@ func ListHPAs(clientset *kubernetes.Clientset, nameSpace string) (*autoscalingv1 } listHPA, err := clientset.AutoscalingV1().HorizontalPodAutoscalers(nameSpace).List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("Failed to query the HPA List: %w", err) + return nil, fmt.Errorf("failed to query the HPA List: %w", err) } return listHPA, nil } @@ -87,7 +87,7 @@ func ListHPAs(clientset *kubernetes.Clientset, nameSpace string) (*autoscalingv1 func ListCronJobs(clientset *kubernetes.Clientset, nameSpace string) (*batchv1.CronJobList, error) { listCronJobs, err := clientset.BatchV1().CronJobs(nameSpace).List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("Failed to query the Cron Job List: %w", err) + return nil, fmt.Errorf("failed to query the Cron Job List: %w", err) } return listCronJobs, nil } @@ -102,16 +102,16 @@ func ListCronJobs(clientset *kubernetes.Clientset, nameSpace string) (*batchv1.C func ListCRDs(ctx, kubeconfig string) (*apiextensionv1.CustomResourceDefinitionList, error) { config, err := connect.BuildConfigWithContextFromFlags(ctx, kubeconfig) if err != nil { - return nil, fmt.Errorf("Failed to create config for quering CRDs: %w", err) + return nil, fmt.Errorf("failed to create config for quering CRDs: %w", err) } kubeClient, err := apiextension.NewForConfig(config) if err != nil { - return nil, fmt.Errorf("Failed to get clientset for quering CRDs: %w", err) + return nil, fmt.Errorf("failed to get clientset for quering CRDs: %w", err) } listCRDs, err := kubeClient.ApiextensionsV1().CustomResourceDefinitions().List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("Failed to query the CRD List: %w", err) + return nil, fmt.Errorf("failed to query the CRD List: %w", err) } return listCRDs, nil } @@ -126,7 +126,7 @@ func ListCRDs(ctx, kubeconfig string) (*apiextensionv1.CustomResourceDefinitionL func ListIngresses(clientset *kubernetes.Clientset, nameSpace string) (*networkingv1.IngressList, error) { listIngress, err := clientset.NetworkingV1().Ingresses(nameSpace).List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("Failed to query the Ingress List: %w", err) + return nil, fmt.Errorf("failed to query the Ingress List: %w", err) } return listIngress, nil } @@ -141,7 +141,7 @@ func ListIngresses(clientset *kubernetes.Clientset, nameSpace string) (*networki func ListServices(clientset *kubernetes.Clientset, nameSpace string) (*Corev1.ServiceList, error) { listServices, err := clientset.CoreV1().Services(nameSpace).List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("Failed to query the Kubernetes Services List: %w", err) + return nil, fmt.Errorf("failed to query the Kubernetes Services List: %w", err) } return listServices, nil } @@ -150,7 +150,7 @@ func ListServices(clientset *kubernetes.Clientset, nameSpace string) (*Corev1.Se func ListConfigMaps(clientset *kubernetes.Clientset, nameSpace string) (*Corev1.ConfigMapList, error) { ListConfigMaps, err := clientset.CoreV1().ConfigMaps(nameSpace).List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("Failed to query the Config Map List: %w", err) + return nil, fmt.Errorf("failed to query the Config Map List: %w", err) } return ListConfigMaps, nil } @@ -159,7 +159,7 @@ func ListConfigMaps(clientset *kubernetes.Clientset, nameSpace string) (*Corev1. func ListSecrets(clientset *kubernetes.Clientset, nameSpace string) (*Corev1.SecretList, error) { ListSercrets, err := clientset.CoreV1().Secrets(nameSpace).List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("Failed to query the Secrets List: %w", err) + return nil, fmt.Errorf("failed to query the Secrets List: %w", err) } return ListSercrets, nil } @@ -168,7 +168,7 @@ func ListSecrets(clientset *kubernetes.Clientset, nameSpace string) (*Corev1.Sec func ListServiceAccounts(clientset *kubernetes.Clientset, nameSpace string) (*Corev1.ServiceAccountList, error) { ListServiceAccounts, err := clientset.CoreV1().ServiceAccounts(nameSpace).List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("Failed to query the Service Accounts List: %w", err) + return nil, fmt.Errorf("failed to query the Service Accounts List: %w", err) } return ListServiceAccounts, nil } @@ -177,7 +177,7 @@ func ListServiceAccounts(clientset *kubernetes.Clientset, nameSpace string) (*Co func ListRoles(clientset *kubernetes.Clientset, nameSpace string) (*RbacV1.RoleList, error) { Listroles, err := clientset.RbacV1().Roles(nameSpace).List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("Failed to query the Roles List: %w", err) + return nil, fmt.Errorf("failed to query the Roles List: %w", err) } return Listroles, nil } @@ -186,7 +186,7 @@ func ListRoles(clientset *kubernetes.Clientset, nameSpace string) (*RbacV1.RoleL func ListRoleBindings(clientset *kubernetes.Clientset, nameSpace string) (*RbacV1.RoleBindingList, error) { ListRoleBindings, err := clientset.RbacV1().RoleBindings(nameSpace).List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("Failed to query the Role bindings List: %w", err) + return nil, fmt.Errorf("failed to query the Role bindings List: %w", err) } return ListRoleBindings, nil } @@ -195,7 +195,7 @@ func ListRoleBindings(clientset *kubernetes.Clientset, nameSpace string) (*RbacV func ListClusterRoles(clientset *kubernetes.Clientset) (*RbacV1.ClusterRoleList, error) { ListClusterRoles, err := clientset.RbacV1().ClusterRoles().List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("Failed to query the Cluster Roles List: %w", err) + return nil, fmt.Errorf("failed to query the Cluster Roles List: %w", err) } return ListClusterRoles, nil } @@ -204,7 +204,16 @@ func ListClusterRoles(clientset *kubernetes.Clientset) (*RbacV1.ClusterRoleList, func ListClusterRoleBindings(clientset *kubernetes.Clientset) (*RbacV1.ClusterRoleBindingList, error) { ListClusterRoleBindings, err := clientset.RbacV1().ClusterRoleBindings().List(context.TODO(), metav1.ListOptions{}) if err != nil { - return nil, fmt.Errorf("Failed to query the Cluster Role Bindings List: %w", err) + return nil, fmt.Errorf("failed to query the Cluster Role Bindings List: %w", err) } return ListClusterRoleBindings, nil } + +// ListNetworkPolicy retrieves a list of NetworkPolicies in the specified namespace. +func ListNetworkPolicies(clientset *kubernetes.Clientset, nameSpace string) (*networkingv1.NetworkPolicyList, error) { + listNetworkPolicy, err := clientset.NetworkingV1().NetworkPolicies(nameSpace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to query the Network Policy List: %w", err) + } + return listNetworkPolicy, nil +} diff --git a/query/query_test.go b/query/query_test.go index c522c34..011ef75 100644 --- a/query/query_test.go +++ b/query/query_test.go @@ -622,3 +622,45 @@ func TestListRoleBindings2(t *testing.T) { } } + +// TestNetworkPolicies tests the ListNetworkPolicies function. +func TestListNetworkPolicies(t *testing.T) { + // Set up test environment and get the temporary kubeconfig file + _, _, tempKubeconfig := mock.SetupTestEnvironment() + defer tempKubeconfig.Close() // Close the file after the test completes + + // Load the kubeconfig data + tempKubeconfigByte, err := os.ReadFile(tempKubeconfig.Name()) + if err != nil { + t.Fatalf("Error reading kubeconfig file: %v", err) + } + kubeconfig, err := clientcmd.Load(tempKubeconfigByte) + if err != nil { + t.Fatalf("Error loading kubeconfig: %v", err) + } + + // Choose one of the contexts from the kubeconfig + var testContext string + for context := range kubeconfig.Contexts { + testContext = context + break // Choose the first context, you can modify this logic as needed + } + + // Connect to the Kubernetes cluster using the test context and kubeconfig file path + x := tempKubeconfig.Name() + config, err := connect.ConnectToSource(testContext, &x) + if err != nil { + t.Fatalf("Error creating config: %v", err) + } + + // Perform the test + networkPolicies, err := ListNetworkPolicies(config, "namespace2") + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + + expectedLength := 3 // Update this value with the expected number of network policies + if len(networkPolicies.Items) != expectedLength { + t.Errorf("Expected %d network policies, got: %d", expectedLength, len(networkPolicies.Items)) + } +}