diff --git a/internal/app/app.go b/internal/app/app.go index 27b463a3..64f127b8 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -215,79 +215,6 @@ func (p *PolicyAutomationApp) CheckScalability() error { return p.evaluateClusters([]string{regoPackageBaseScalability}) } -func (p *PolicyAutomationApp) evaluateClusters(regoPackageBases []string) error { - log.Info("Cluster review starting") - files, err := p.loadPolicyFiles() - if err != nil { - return err - } - if len(files) == 0 { - p.out.ColorPrintf("[yellow][bold]No policies to check against\n") - log.Errorf("No policies to check against") - return errNoPolicies - } - // create a PolicyAgent client instance - pa := policy.NewPolicyAgent(p.ctx) - p.out.ColorPrintf("%s [light_gray][bold]Parsing REGO policies...\n", outputs.ICON_INFO) - log.Info("Parsing rego policies") - // parsing policies before running checks - if err := pa.WithFiles(files, p.config.PolicyExclusions); err != nil { - p.out.ErrorPrint("could not parse policy files", err) - log.Errorf("could not parse policy files: %s", err) - return err - } - - clusterIds, err := p.getClusters() - if err != nil { - p.out.ErrorPrint("could not get clusters", err) - log.Errorf("could not get clusters: %s", err) - return err - } - evalResults := &evaluationResults{} - for _, clusterId := range clusterIds { - log.Infof("Fetching GKE cluster %s", clusterId) - p.out.ColorPrintf("%s [light_gray][bold]Fetching GKE cluster details... [%s]\n", outputs.ICON_INFO, clusterId) - cluster, err := p.gke.GetCluster(clusterId) - if err != nil { - p.out.ErrorPrint("could not fetch the cluster details", err) - log.Errorf("could not fetch cluster details: %s", err) - return err - } - p.out.ColorPrintf("%s [light_gray][bold]Evaluating policies against GKE cluster... [%s]\n", - outputs.ICON_INFO, clusterId) - log.Infof("Evaluating policies against GKE cluster %s", clusterId) - for _, pkgBase := range regoPackageBases { - evalResult, err := pa.Evaluate(cluster, pkgBase) - if err != nil { - p.out.ErrorPrint("failed to evaluate policies", err) - log.Errorf("could not evaluate rego policies on cluster %s: %s", cluster.Id, err) - return err - } - evalResult.ClusterID = clusterId - evalResults.Add(evalResult) - } - } - - for _, c := range p.collectors { - log.Infof("Collector %s registering the results", c.Name()) - p.out.ColorPrintf("%s [light_gray][bold]Writing evaluation results ... [%s]\n", outputs.ICON_INFO, c.Name()) - if err = c.RegisterResult(evalResults.List()); err != nil { - p.out.ErrorPrint("failed to register evaluation results", err) - log.Errorf("could not register evaluation results: %s", err) - return err - } - if err = c.Close(); err != nil { - p.out.ErrorPrint("failed to close results registration", err) - log.Errorf("could not finalize registering evaluation results: %s", err) - return err - } - log.Infof("Collector %s processing closed", c.Name()) - } - log.Info("Cluster review finished") - p.out.ColorPrintf("\u2139 [light_gray][bold]Cluster review finished\n") - return nil -} - func (p *PolicyAutomationApp) ClusterJSONData() error { clusterIds, err := p.getClusters() if err != nil { @@ -400,71 +327,6 @@ func (p *PolicyAutomationApp) loadPolicyFiles() ([]*policy.PolicyFile, error) { return policyFiles, nil } -//getClusters retrieves lists of a clusters for further processing -//from the sources that are defined in a configuration. -func (p *PolicyAutomationApp) getClusters() ([]string, error) { - if p.config.DumpFile != "" { - log.Debugf("using local cluster discovery client on a file %s", p.config.DumpFile) - dc := gke.NewLocalDiscoveryClient(p.config.DumpFile) - return dc.GetClustersInOrg("doesn't-matter-for-local-discovery") - } - if p.config.ClusterDiscovery.Enabled { - var dc gke.DiscoveryClient - var err error - if p.config.CredentialsFile != "" { - log.Debugf("instantiating cluster discovery client with a credentials file") - dc, err = gke.NewDiscoveryClientWithCredentialsFile(p.ctx, p.config.CredentialsFile) - } else { - log.Debugf("instantiating cluster discovery client") - dc, err = gke.NewDiscoveryClient(p.ctx) - } - if err != nil { - return nil, err - } - p.discovery = dc - return p.discoverClusters() - } - clusters := make([]string, 0, len(p.config.Clusters)) - for _, configCluster := range p.config.Clusters { - clusterName, err := getClusterName(configCluster) - if err != nil { - return nil, err - } - clusters = append(clusters, clusterName) - } - return clusters, nil -} - -//discoverClusters discovers clusters according to the cluster discovery configuration. -func (p *PolicyAutomationApp) discoverClusters() ([]string, error) { - if p.config.ClusterDiscovery.Organization != "" { - log.Infof("Discovering clusters in organization %s", p.config.ClusterDiscovery.Organization) - p.out.ColorPrintf("%s [light_gray][bold]Discovering clusters in for organization... [%s]\n", outputs.ICON_INFO, p.config.ClusterDiscovery.Organization) - return p.discovery.GetClustersInOrg(p.config.ClusterDiscovery.Organization) - } - clusters := make([]string, 0) - for _, folder := range p.config.ClusterDiscovery.Folders { - log.Infof("Discovering clusters in folder %s", folder) - p.out.ColorPrintf("%s [light_gray][bold]Discovering clusters in folder... [%s]\n", outputs.ICON_INFO, folder) - results, err := p.discovery.GetClustersInFolder(folder) - if err != nil { - return nil, err - } - clusters = append(clusters, results...) - } - for _, project := range p.config.ClusterDiscovery.Projects { - log.Infof("Discovering clusters in project %s", project) - p.out.ColorPrintf("%s [light_gray][bold]Discovering clusters in project... [%s]\n", outputs.ICON_INFO, project) - results, err := p.discovery.GetClustersInProject(project) - if err != nil { - return nil, err - } - clusters = append(clusters, results...) - } - log.Debugf("discovered %v clusters in projects and folders", len(clusters)) - return clusters, nil -} - func (p *PolicyAutomationApp) configureSccOutput(config cfg.SecurityCommandCenterOutput, credsFile string) error { if config.OrganizationNumber == "" { return nil diff --git a/internal/app/app_check_clusters.go b/internal/app/app_check_clusters.go new file mode 100644 index 00000000..a092d957 --- /dev/null +++ b/internal/app/app_check_clusters.go @@ -0,0 +1,186 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package app + +import ( + "errors" + + "github.com/google/gke-policy-automation/internal/gke" + "github.com/google/gke-policy-automation/internal/log" + "github.com/google/gke-policy-automation/internal/outputs" + "github.com/google/gke-policy-automation/internal/policy" + "github.com/googleapis/gax-go/v2/apierror" + apiCodes "google.golang.org/grpc/codes" +) + +//getClusters retrieves lists of a clusters for further processing +//from the sources that are defined in a configuration. +func (p *PolicyAutomationApp) getClusters() ([]string, error) { + if p.config.DumpFile != "" { + log.Debugf("using local cluster discovery client on a file %s", p.config.DumpFile) + dc := gke.NewLocalDiscoveryClient(p.config.DumpFile) + return dc.GetClustersInOrg("doesn't-matter-for-local-discovery") + } + if p.config.ClusterDiscovery.Enabled { + var dc gke.DiscoveryClient + var err error + if p.config.CredentialsFile != "" { + log.Debugf("instantiating cluster discovery client with a credentials file") + dc, err = gke.NewDiscoveryClientWithCredentialsFile(p.ctx, p.config.CredentialsFile) + } else { + log.Debugf("instantiating cluster discovery client") + dc, err = gke.NewDiscoveryClient(p.ctx) + } + if err != nil { + return nil, err + } + p.discovery = dc + return p.discoverClusters() + } + clusters := make([]string, 0, len(p.config.Clusters)) + for _, configCluster := range p.config.Clusters { + clusterName, err := getClusterName(configCluster) + if err != nil { + return nil, err + } + clusters = append(clusters, clusterName) + } + return clusters, nil +} + +//discoverClusters discovers clusters according to the cluster discovery configuration. +func (p *PolicyAutomationApp) discoverClusters() ([]string, error) { + if p.config.ClusterDiscovery.Organization != "" { + log.Infof("Discovering clusters in organization %s", p.config.ClusterDiscovery.Organization) + p.out.ColorPrintf("%s [light_gray][bold]Discovering clusters in for organization... [%s]\n", outputs.ICON_INFO, p.config.ClusterDiscovery.Organization) + return p.discovery.GetClustersInOrg(p.config.ClusterDiscovery.Organization) + } + clusters := make([]string, 0) + for _, folder := range p.config.ClusterDiscovery.Folders { + log.Infof("Discovering clusters in folder %s", folder) + p.out.ColorPrintf("%s [light_gray][bold]Discovering clusters in folder... [%s]\n", outputs.ICON_INFO, folder) + results, err := p.discovery.GetClustersInFolder(folder) + if err != nil { + return nil, err + } + clusters = append(clusters, results...) + } + for _, project := range p.config.ClusterDiscovery.Projects { + log.Infof("Discovering clusters in project %s", project) + p.out.ColorPrintf("%s [light_gray][bold]Discovering clusters in project... [%s]\n", outputs.ICON_INFO, project) + results, err := p.discovery.GetClustersInProject(project) + if err != nil { + return nil, err + } + clusters = append(clusters, results...) + } + log.Debugf("discovered %v clusters in projects and folders", len(clusters)) + return clusters, nil +} + +func (p *PolicyAutomationApp) evaluateClusters(regoPackageBases []string) error { + log.Info("Cluster review starting") + files, err := p.loadPolicyFiles() + if err != nil { + return err + } + if len(files) == 0 { + p.out.ColorPrintf("[yellow][bold]No policies to check against\n") + log.Errorf("No policies to check against") + return errNoPolicies + } + // create a PolicyAgent client instance + pa := policy.NewPolicyAgent(p.ctx) + p.out.ColorPrintf("%s [light_gray][bold]Parsing REGO policies...\n", outputs.ICON_INFO) + log.Info("Parsing rego policies") + // parsing policies before running checks + if err := pa.WithFiles(files, p.config.PolicyExclusions); err != nil { + p.out.ErrorPrint("could not parse policy files", err) + log.Errorf("could not parse policy files: %s", err) + return err + } + + clusterIds, err := p.getClusters() + if err != nil { + p.out.ErrorPrint("could not identify clusters", err) + log.Errorf("could not identify clusters: %s", err) + return err + } + clusterData, err := p.getClusterData(clusterIds) + if err != nil { + p.out.ErrorPrint("could not fetch the cluster details", err) + log.Errorf("could not fetch cluster details: %s", err) + return err + } + + evalResults := &evaluationResults{} + for _, cluster := range clusterData { + clusterId := cluster.ReadableId() + p.out.ColorPrintf("%s [light_gray][bold]Evaluating policies against GKE cluster... [%s]\n", + outputs.ICON_INFO, clusterId) + log.Infof("Evaluating policies against GKE cluster %s", clusterId) + for _, pkgBase := range regoPackageBases { + evalResult, err := pa.Evaluate(cluster, pkgBase) + if err != nil { + p.out.ErrorPrint("failed to evaluate policies", err) + log.Errorf("could not evaluate rego policies on cluster %s: %s", clusterId, err) + return err + } + evalResult.ClusterID = clusterId + evalResults.Add(evalResult) + } + } + + for _, c := range p.collectors { + log.Infof("Collector %s registering the results", c.Name()) + p.out.ColorPrintf("%s [light_gray][bold]Writing evaluation results ... [%s]\n", outputs.ICON_INFO, c.Name()) + if err = c.RegisterResult(evalResults.List()); err != nil { + p.out.ErrorPrint("failed to register evaluation results", err) + log.Errorf("could not register evaluation results: %s", err) + return err + } + if err = c.Close(); err != nil { + p.out.ErrorPrint("failed to close results registration", err) + log.Errorf("could not finalize registering evaluation results: %s", err) + return err + } + log.Infof("Collector %s processing closed", c.Name()) + } + log.Info("Cluster review finished") + p.out.ColorPrintf("\u2139 [light_gray][bold]Cluster review finished\n") + return nil +} + +func (p *PolicyAutomationApp) getClusterData(ids []string) ([]*gke.Cluster, error) { + results := make([]*gke.Cluster, 0) + for _, clusterId := range ids { + log.Infof("Fetching GKE cluster %s", clusterId) + p.out.ColorPrintf("%s [light_gray][bold]Fetching GKE cluster details... [%s]\n", outputs.ICON_INFO, clusterId) + cluster, err := p.gke.GetCluster(clusterId) + if err != nil { + var apiErr *apierror.APIError + if errors.As(err, &apiErr) { + if apiErr.GRPCStatus().Code() == apiCodes.NotFound { + p.out.ColorPrintf("%s [yellow][bold]Could not fetch cluster: cluster not found [%s]\n", outputs.ICON_INFO, clusterId) + log.Warnf("could not fetch cluster details: %s", err) + continue + } + } + return nil, err + } + results = append(results, cluster) + } + return results, nil +} diff --git a/internal/app/app_check_clusters_test.go b/internal/app/app_check_clusters_test.go new file mode 100644 index 00000000..48d7a4cd --- /dev/null +++ b/internal/app/app_check_clusters_test.go @@ -0,0 +1,238 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package app + +import ( + "context" + "errors" + "reflect" + "testing" + + cfg "github.com/google/gke-policy-automation/internal/config" + "github.com/google/gke-policy-automation/internal/gke" + "github.com/google/gke-policy-automation/internal/outputs" + "github.com/googleapis/gax-go/v2/apierror" + apiCodes "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type mockGKEClient struct { + getClusterFn func(name string) (*gke.Cluster, error) + closeFn func() error +} + +func (m *mockGKEClient) GetCluster(name string) (*gke.Cluster, error) { + return m.getClusterFn(name) +} + +func (m *mockGKEClient) Close() error { + return m.closeFn() +} + +type mockAPIError struct { + gRPCStatusFn func() *status.Status + errorFn func() string +} + +func (m mockAPIError) GRPCStatus() *status.Status { + return m.gRPCStatusFn() +} + +func (m mockAPIError) Error() string { + return m.errorFn() + +} + +func TestGetClusters_config(t *testing.T) { + clusters := []string{"cluster1", "cluster2"} + pa := PolicyAutomationApp{ + config: &cfg.Config{ + Clusters: []cfg.ConfigCluster{ + {ID: clusters[0]}, + {ID: clusters[1]}, + }, + }, + } + results, err := pa.getClusters() + if err != nil { + t.Fatalf("err is not nil; want nil; err = %s", err) + } + if !reflect.DeepEqual(results, clusters) { + t.Errorf("results = %v; want %v", results, clusters) + } +} + +func TestGetClusters_discovery(t *testing.T) { + pa := PolicyAutomationApp{ + out: outputs.NewSilentOutput(), + ctx: context.Background(), + config: &cfg.Config{ + CredentialsFile: "test-fixtures/test_credentials.json", + ClusterDiscovery: cfg.ClusterDiscovery{ + Enabled: true, + }, + }, + } + _, err := pa.getClusters() + if err != nil { + t.Fatalf("err is not nil; want nil; err = %s", err) + } + if _, ok := pa.discovery.(*gke.AssetInventoryDiscoveryClient); !ok { + t.Errorf("policy automation discovery client is not *gke.AssetInventoryDiscoveryClient") + } +} + +func TestDiscoverClusters_org(t *testing.T) { + clusters := []string{"clusterOne", "clusterTwo"} + orgNumber := "123456789" + clusterInOrgFn := func(number string) ([]string, error) { + if number != orgNumber { + t.Errorf("received org number is %v; want %v", number, orgNumber) + } + return clusters, nil + } + pa := PolicyAutomationApp{ + out: outputs.NewSilentOutput(), + discovery: DiscoveryClientMock{GetClustersInOrgFn: clusterInOrgFn}, + config: &cfg.Config{ClusterDiscovery: cfg.ClusterDiscovery{Enabled: true, Organization: orgNumber}}, + } + results, err := pa.discoverClusters() + if err != nil { + t.Fatalf("err is not nil; want nil; err = %s", err) + } + if !reflect.DeepEqual(results, clusters) { + t.Fatalf("results are %v; want %v", results, clusters) + } +} + +func TestDiscoverClusters_folders(t *testing.T) { + folders := []string{"12345", "6789"} + foldersContent := map[string][]string{ + folders[0]: {"clusterOne", "clusterTwo"}, + folders[1]: {"clusterThree", "clusterFour"}, + } + clusterInFoldersFn := func(number string) ([]string, error) { + clusters, ok := foldersContent[number] + if !ok { + t.Errorf("received folder number = %v; not defined in a test", number) + } + return clusters, nil + } + pa := PolicyAutomationApp{ + out: outputs.NewSilentOutput(), + discovery: DiscoveryClientMock{GetClustersInFolderFn: clusterInFoldersFn}, + config: &cfg.Config{ClusterDiscovery: cfg.ClusterDiscovery{Enabled: true, Folders: folders}}, + } + results, err := pa.discoverClusters() + if err != nil { + t.Fatalf("err is not nil; want nil; err = %s", err) + } + allFoldersContent := make([]string, 0) + allFoldersContent = append(allFoldersContent, foldersContent[folders[0]]...) + allFoldersContent = append(allFoldersContent, foldersContent[folders[1]]...) + if !reflect.DeepEqual(results, allFoldersContent) { + t.Fatalf("results are %v; want %v", results, allFoldersContent) + } +} + +func TestDiscoverClusters_projects(t *testing.T) { + projects := []string{"projectOne", "projectTwo"} + projectsContent := map[string][]string{ + projects[0]: {"clusterOne", "clusterTwo"}, + projects[1]: {"clusterThree", "clusterFour"}, + } + clusterInProjectsFn := func(name string) ([]string, error) { + clusters, ok := projectsContent[name] + if !ok { + t.Errorf("received project name = %v; not defined in a test", name) + } + return clusters, nil + } + pa := PolicyAutomationApp{ + out: outputs.NewSilentOutput(), + discovery: DiscoveryClientMock{GetClustersInProjectFn: clusterInProjectsFn}, + config: &cfg.Config{ClusterDiscovery: cfg.ClusterDiscovery{Enabled: true, Projects: projects}}, + } + results, err := pa.discoverClusters() + if err != nil { + t.Fatalf("err is not nil; want nil; err = %s", err) + } + allProjectsContent := make([]string, 0) + allProjectsContent = append(allProjectsContent, projectsContent[projects[0]]...) + allProjectsContent = append(allProjectsContent, projectsContent[projects[1]]...) + if !reflect.DeepEqual(results, allProjectsContent) { + t.Fatalf("results are %v; want %v", results, allProjectsContent) + } +} + +func TestGetClusterData(t *testing.T) { + mock := &mockGKEClient{ + getClusterFn: func(name string) (*gke.Cluster, error) { + switch name { + case "cluster-one": + return &gke.Cluster{}, nil + default: + mockErr := mockAPIError{ + errorFn: func() string { + return "not found" + }, + gRPCStatusFn: func() *status.Status { + return status.New(apiCodes.NotFound, "cluster not found") + }, + } + mockApiErr, _ := apierror.FromError(mockErr) + return nil, mockApiErr + } + + }, + closeFn: func() error { + return nil + }, + } + pa := &PolicyAutomationApp{ + ctx: context.TODO(), + out: outputs.NewSilentOutput(), + gke: mock, + } + ids := []string{"cluster-one", "cluster-two"} + data, err := pa.getClusterData(ids) + if err != nil { + t.Fatalf("err is %v; want nil", err) + } + if len(data) != 1 { + t.Errorf("len(data) is %v; want %v", len(data), 1) + } +} + +func TestGetClusterData_error(t *testing.T) { + mock := &mockGKEClient{ + getClusterFn: func(name string) (*gke.Cluster, error) { + return nil, errors.New("test error") + }, + closeFn: func() error { + return nil + }, + } + pa := &PolicyAutomationApp{ + ctx: context.TODO(), + out: outputs.NewSilentOutput(), + gke: mock, + } + ids := []string{"cluster-one", "cluster-two"} + _, err := pa.getClusterData(ids) + if err == nil { + t.Fatalf("err is nil; want error") + } +} diff --git a/internal/app/app_config.go b/internal/app/app_config.go new file mode 100644 index 00000000..bc37ccf8 --- /dev/null +++ b/internal/app/app_config.go @@ -0,0 +1,15 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package app diff --git a/internal/app/app_test.go b/internal/app/app_test.go index 1c091bc6..04c4179b 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -25,7 +25,6 @@ import ( "gopkg.in/yaml.v2" cfg "github.com/google/gke-policy-automation/internal/config" - "github.com/google/gke-policy-automation/internal/gke" "github.com/google/gke-policy-automation/internal/outputs" ) @@ -72,128 +71,6 @@ func TestNewPolicyAutomationApp(t *testing.T) { } } -func TestGetClusters_config(t *testing.T) { - clusters := []string{"cluster1", "cluster2"} - pa := PolicyAutomationApp{ - config: &cfg.Config{ - Clusters: []cfg.ConfigCluster{ - {ID: clusters[0]}, - {ID: clusters[1]}, - }, - }, - } - results, err := pa.getClusters() - if err != nil { - t.Fatalf("err is not nil; want nil; err = %s", err) - } - if !reflect.DeepEqual(results, clusters) { - t.Errorf("results = %v; want %v", results, clusters) - } -} - -func TestGetClusters_discovery(t *testing.T) { - pa := PolicyAutomationApp{ - out: outputs.NewSilentOutput(), - ctx: context.Background(), - config: &cfg.Config{ - CredentialsFile: "test-fixtures/test_credentials.json", - ClusterDiscovery: cfg.ClusterDiscovery{ - Enabled: true, - }, - }, - } - _, err := pa.getClusters() - if err != nil { - t.Fatalf("err is not nil; want nil; err = %s", err) - } - if _, ok := pa.discovery.(*gke.AssetInventoryDiscoveryClient); !ok { - t.Errorf("policy automation discovery client is not *gke.AssetInventoryDiscoveryClient") - } -} - -func TestDiscoverClusters_org(t *testing.T) { - clusters := []string{"clusterOne", "clusterTwo"} - orgNumber := "123456789" - clusterInOrgFn := func(number string) ([]string, error) { - if number != orgNumber { - t.Errorf("received org number is %v; want %v", number, orgNumber) - } - return clusters, nil - } - pa := PolicyAutomationApp{ - out: outputs.NewSilentOutput(), - discovery: DiscoveryClientMock{GetClustersInOrgFn: clusterInOrgFn}, - config: &cfg.Config{ClusterDiscovery: cfg.ClusterDiscovery{Enabled: true, Organization: orgNumber}}, - } - results, err := pa.discoverClusters() - if err != nil { - t.Fatalf("err is not nil; want nil; err = %s", err) - } - if !reflect.DeepEqual(results, clusters) { - t.Fatalf("results are %v; want %v", results, clusters) - } -} - -func TestDiscoverClusters_folders(t *testing.T) { - folders := []string{"12345", "6789"} - foldersContent := map[string][]string{ - folders[0]: {"clusterOne", "clusterTwo"}, - folders[1]: {"clusterThree", "clusterFour"}, - } - clusterInFoldersFn := func(number string) ([]string, error) { - clusters, ok := foldersContent[number] - if !ok { - t.Errorf("received folder number = %v; not defined in a test", number) - } - return clusters, nil - } - pa := PolicyAutomationApp{ - out: outputs.NewSilentOutput(), - discovery: DiscoveryClientMock{GetClustersInFolderFn: clusterInFoldersFn}, - config: &cfg.Config{ClusterDiscovery: cfg.ClusterDiscovery{Enabled: true, Folders: folders}}, - } - results, err := pa.discoverClusters() - if err != nil { - t.Fatalf("err is not nil; want nil; err = %s", err) - } - allFoldersContent := make([]string, 0) - allFoldersContent = append(allFoldersContent, foldersContent[folders[0]]...) - allFoldersContent = append(allFoldersContent, foldersContent[folders[1]]...) - if !reflect.DeepEqual(results, allFoldersContent) { - t.Fatalf("results are %v; want %v", results, allFoldersContent) - } -} - -func TestDiscoverClusters_projects(t *testing.T) { - projects := []string{"projectOne", "projectTwo"} - projectsContent := map[string][]string{ - projects[0]: {"clusterOne", "clusterTwo"}, - projects[1]: {"clusterThree", "clusterFour"}, - } - clusterInProjectsFn := func(name string) ([]string, error) { - clusters, ok := projectsContent[name] - if !ok { - t.Errorf("received project name = %v; not defined in a test", name) - } - return clusters, nil - } - pa := PolicyAutomationApp{ - out: outputs.NewSilentOutput(), - discovery: DiscoveryClientMock{GetClustersInProjectFn: clusterInProjectsFn}, - config: &cfg.Config{ClusterDiscovery: cfg.ClusterDiscovery{Enabled: true, Projects: projects}}, - } - results, err := pa.discoverClusters() - if err != nil { - t.Fatalf("err is not nil; want nil; err = %s", err) - } - allProjectsContent := make([]string, 0) - allProjectsContent = append(allProjectsContent, projectsContent[projects[0]]...) - allProjectsContent = append(allProjectsContent, projectsContent[projects[1]]...) - if !reflect.DeepEqual(results, allProjectsContent) { - t.Fatalf("results are %v; want %v", results, allProjectsContent) - } -} - func TestLoadCliConfig_file(t *testing.T) { testConfigPath := "./test-fixtures/test_config.yaml" cliConfig := &CliConfig{ConfigFile: testConfigPath} diff --git a/internal/gke/gke.go b/internal/gke/gke.go index 88ee0b61..5ec6b38f 100644 --- a/internal/gke/gke.go +++ b/internal/gke/gke.go @@ -18,6 +18,7 @@ import ( "context" "encoding/base64" "fmt" + "regexp" "strings" container "cloud.google.com/go/container/apiv1" @@ -100,6 +101,20 @@ type Cluster struct { Resources []*Resource } +func (c Cluster) ReadableId() string { + r := regexp.MustCompile(`.+/(projects/.+/(locations|zones)/.+/clusters/.+)`) + if !r.MatchString(c.SelfLink) { + log.Warnf("cluster selfLink %s does not match readable identifier regex", c.SelfLink) + return c.Id + } + matches := r.FindStringSubmatch(c.SelfLink) + if len(matches) != 3 { + log.Warnf("cluster selfLink %s has invalid number of readable identifier regex matches", c.SelfLink) + return c.Id + } + return matches[1] +} + //GetCluster returns a Cluster object with all the information regarding the cluster, //externally through the Containers API and Internally with the K8s APIs func (c *GKEApiClient) GetCluster(name string) (*Cluster, error) { diff --git a/internal/gke/gke_test.go b/internal/gke/gke_test.go index 3a73cae3..5d3efb98 100644 --- a/internal/gke/gke_test.go +++ b/internal/gke/gke_test.go @@ -272,3 +272,16 @@ func TestGetClusterResourcesForNonEmptyConfig(t *testing.T) { t.Errorf("should return resources for v1 configuration. Returned %d; want 1", len(cluster.Resources)) } } + +func TestReadableId(t *testing.T) { + expected := "projects/test/zones/europe-north1-a/clusters/cluster-demo" + cluster := &Cluster{ + Cluster: &containerpb.Cluster{ + SelfLink: fmt.Sprintf("%s/%s", "https://container.googleapis.com/v1", expected), + }, + } + readableId := cluster.ReadableId() + if readableId != expected { + t.Errorf("readable id = %v; want %v", readableId, expected) + } +}