diff --git a/README.ja.md b/README.ja.md index 9b7d499b1..ece7ca318 100644 --- a/README.ja.md +++ b/README.ja.md @@ -159,8 +159,9 @@ KHIは、Google Cloud サポートチームが開発し、その後オープン - **必須権限** - `logging.logEntries.list` - **推奨権限** - - 対象のクラスタのタイプに対するリスト権限(例:GKE の場合 `container.clusters.list`) - ログフィルタ生成ダイアログの候補の出力に使用します。KHI の主機能の利用に影響はありません。 + - New Inspectionダイアログでの入力時にオートコンプリートの入力候補を取得するために使用します。権限がなくても問題がありませんが、入力時にクラスタ名の候補が表示されません。 + - `monitoring.timeSeries.list` + - `container.clusters.list` (Cloud Composer向け機能利用時のみ) - **設定手順** - Compute Engine 仮想マシン上など、サービスアカウントがアタッチされた Google Cloud 環境で KHI を実行する場合、対応するリソースにアタッチされたサービスアカウントに上記権限を付与します。 diff --git a/README.md b/README.md index dc8c7e13e..3252e81a4 100644 --- a/README.md +++ b/README.md @@ -151,8 +151,9 @@ The following permissions are required or recommended. - **Required** - `logging.logEntries.list` - **Recommended** - - Permissions to list clusters for cluster type (eg. `container.clusters.list` for GKE) - This permission is used to show autofill candidates for the log filter. KHI's main functionality is not affected without this permission. + - These permissions are used to fetch autocomplete candidates in the New Inspection dialog. KHI works without these permissions, but cluster name suggestions will not be displayed. + - `monitoring.timeSeries.list` + - `container.clusters.list` (Only when using Cloud Composer features) - **Setting** - Running KHI on environments with a service account attached, such as Google Cloud Compute Engine Instance: Apply the permissions above to the attached service account. - Running KHI locally or on Cloud Shell with a user account: Apply the permissions above to your user account. diff --git a/go.mod b/go.mod index e2a8cf3f7..95bf47a53 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( cloud.google.com/go/compute/metadata v0.9.0 // indirect cloud.google.com/go/iam v1.5.3 // indirect cloud.google.com/go/longrunning v0.7.0 // indirect + cloud.google.com/go/monitoring v1.24.3 // indirect cloud.google.com/go/trace v1.11.7 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect github.com/bmatcuk/doublestar/v4 v4.0.2 // indirect diff --git a/pkg/api/googlecloud/clientfactory.go b/pkg/api/googlecloud/clientfactory.go index c87444439..765748bce 100644 --- a/pkg/api/googlecloud/clientfactory.go +++ b/pkg/api/googlecloud/clientfactory.go @@ -19,11 +19,9 @@ import ( compute "cloud.google.com/go/compute/apiv1" container "cloud.google.com/go/container/apiv1" - gkehub "cloud.google.com/go/gkehub/apiv1beta1" - gkemulticloud "cloud.google.com/go/gkemulticloud/apiv1" logging "cloud.google.com/go/logging/apiv2" + monitoring "cloud.google.com/go/monitoring/apiv3/v2" "google.golang.org/api/composer/v1" - "google.golang.org/api/gkeonprem/v1" "google.golang.org/api/option" ) @@ -45,14 +43,11 @@ type ClientFactory struct { ClientOptions []ClientFactoryOptionsModifiers ContextModifiers []ClientFactoryContextModifiers - ContainerClusterManagerClientOptions []ClientFactoryOptionsModifiers - GKEHubMembershipClientOptions []ClientFactoryOptionsModifiers - GKEMultiCloudAWSClustersClientOptions []ClientFactoryOptionsModifiers - GKEMultiCloudAzureClustersClientOptions []ClientFactoryOptionsModifiers - LoggingClientOptions []ClientFactoryOptionsModifiers - RegionsClientOptions []ClientFactoryOptionsModifiers - ComposerServiceOptions []ClientFactoryOptionsModifiers - GKEOnPremServiceOptions []ClientFactoryOptionsModifiers + ContainerClusterManagerClientOptions []ClientFactoryOptionsModifiers + LoggingClientOptions []ClientFactoryOptionsModifiers + RegionsClientOptions []ClientFactoryOptionsModifiers + ComposerServiceOptions []ClientFactoryOptionsModifiers + MonitoringMetricClientOptions []ClientFactoryOptionsModifiers } // NewClientFactory creates a new ClientFactory with the given options. @@ -118,36 +113,6 @@ func (s *ClientFactory) ContainerClusterManagerClient(ctx context.Context, c Res return container.NewClusterManagerClient(ctx, opts...) } -// GKEHubMembershipClient returns the MembershipClient of gkehub.googleapis.com from given context and the resource container. -func (s *ClientFactory) GKEHubMembershipClient(ctx context.Context, c ResourceContainer, opts ...option.ClientOption) (*gkehub.GkeHubMembershipClient, error) { - ctx, opts, err := s.prepareServiceInput(ctx, c, s.GKEHubMembershipClientOptions, opts...) - if err != nil { - return nil, err - } - - return gkehub.NewGkeHubMembershipClient(ctx, opts...) -} - -// GKEMultiCloudAWSClustersClient returns the AwsClusterClient of gkemulticloud.googleapis.com from given context and the resource container. -func (s *ClientFactory) GKEMultiCloudAWSClustersClient(ctx context.Context, c ResourceContainer, opts ...option.ClientOption) (*gkemulticloud.AwsClustersClient, error) { - ctx, opts, err := s.prepareServiceInput(ctx, c, s.GKEMultiCloudAWSClustersClientOptions, opts...) - if err != nil { - return nil, err - } - - return gkemulticloud.NewAwsClustersClient(ctx, opts...) -} - -// GKEMultiCloudAzureClustersClient returns the AzureClustersClient of gkemulticloud.googleapis.com from given context and the resource container. -func (s *ClientFactory) GKEMultiCloudAzureClustersClient(ctx context.Context, c ResourceContainer, opts ...option.ClientOption) (*gkemulticloud.AzureClustersClient, error) { - ctx, opts, err := s.prepareServiceInput(ctx, c, s.GKEMultiCloudAzureClustersClientOptions, opts...) - if err != nil { - return nil, err - } - - return gkemulticloud.NewAzureClustersClient(ctx, opts...) -} - // LoggingClient returns the client for logging.googleapis.com from given context and the resource container. func (s *ClientFactory) LoggingClient(ctx context.Context, c ResourceContainer, opts ...option.ClientOption) (*logging.Client, error) { ctx, opts, err := s.prepareServiceInput(ctx, c, s.LoggingClientOptions, opts...) @@ -178,13 +143,12 @@ func (s *ClientFactory) ComposerService(ctx context.Context, c ResourceContainer return composer.NewService(ctx, opts...) } -// GKEOnPremService returns the client for gkeonprem.googleapis.com from the given context and the resource cntainer. -// GKEOnPrem has no package defined by `cloud.google.com/go`, this method returns the low level API client from '"google.golang.org/api/gkeonprem/v1'. -func (s *ClientFactory) GKEOnPremService(ctx context.Context, c ResourceContainer, opts ...option.ClientOption) (*gkeonprem.Service, error) { - ctx, opts, err := s.prepareServiceInput(ctx, c, s.GKEOnPremServiceOptions, opts...) +// MonitoringMetricClient returns the client for monitoring.googleapis.com from given context and the resource container. +func (s *ClientFactory) MonitoringMetricClient(ctx context.Context, c ResourceContainer, opts ...option.ClientOption) (*monitoring.MetricClient, error) { + ctx, opts, err := s.prepareServiceInput(ctx, c, s.MonitoringMetricClientOptions, opts...) if err != nil { return nil, err } - return gkeonprem.NewService(ctx, opts...) + return monitoring.NewMetricClient(ctx, opts...) } diff --git a/pkg/api/googlecloud/monitoring_util.go b/pkg/api/googlecloud/monitoring_util.go new file mode 100644 index 000000000..3b08021c1 --- /dev/null +++ b/pkg/api/googlecloud/monitoring_util.go @@ -0,0 +1,81 @@ +// Copyright 2025 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 +// +// http://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 googlecloud + +import ( + "context" + "fmt" + "time" + + monitoring "cloud.google.com/go/monitoring/apiv3/v2" + "cloud.google.com/go/monitoring/apiv3/v2/monitoringpb" + "google.golang.org/api/iterator" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// QueryDistinctLabelValuesFromMetrics queries Cloud Monitoring for TimeSeries matching the filter and interval, +// and returns unique values for the specified label key. +// +// groupByKey: The full label key to group by (e.g. "resource.label.cluster_name"). +// resultLabelKey: The simple label key to extract from the result (e.g. "cluster_name"). +func QueryDistinctLabelValuesFromMetrics(ctx context.Context, client *monitoring.MetricClient, projectID string, filter string, startTime, endTime time.Time, groupByKey, resultLabelKey string) ([]string, error) { + d := endTime.Sub(startTime) + if d < 60*time.Second { + d = 60 * time.Second + } + req := &monitoringpb.ListTimeSeriesRequest{ + Name: "projects/" + projectID, + Filter: filter, + Interval: &monitoringpb.TimeInterval{ + StartTime: timestamppb.New(startTime), + EndTime: timestamppb.New(endTime), + }, + View: monitoringpb.ListTimeSeriesRequest_HEADERS, + Aggregation: &monitoringpb.Aggregation{ + AlignmentPeriod: &durationpb.Duration{Seconds: int64(d.Seconds())}, + PerSeriesAligner: monitoringpb.Aggregation_ALIGN_SUM, + CrossSeriesReducer: monitoringpb.Aggregation_REDUCE_NONE, + GroupByFields: []string{groupByKey}, + }, + } + + it := client.ListTimeSeries(ctx, req) + uniqueValues := make(map[string]struct{}) + for { + resp, err := it.Next() + if err == iterator.Done { + break + } + if err != nil { + return nil, fmt.Errorf("failed to list time series: %w", err) + } + + // Attempt to find the label in Resource or Metric labels + val, ok := resp.GetResource().GetLabels()[resultLabelKey] + if !ok { + val, ok = resp.GetMetric().GetLabels()[resultLabelKey] + } + if ok { + uniqueValues[val] = struct{}{} + } + } + + result := make([]string, 0, len(uniqueValues)) + for v := range uniqueValues { + result = append(result, v) + } + return result, nil +} diff --git a/pkg/task/inspection/googlecloudclustercomposer/contract/taskid.go b/pkg/task/inspection/googlecloudclustercomposer/contract/taskid.go index a24f99063..66128a306 100644 --- a/pkg/task/inspection/googlecloudclustercomposer/contract/taskid.go +++ b/pkg/task/inspection/googlecloudclustercomposer/contract/taskid.go @@ -24,7 +24,7 @@ import ( var GoogleCloudComposerTaskIDPrefix = "cloud.google.com/composer/" // AutocompleteComposerClusterNamesTaskID is the task id for the task that autocompletes GKE cluster names created by Cloud Composer. -var AutocompleteComposerClusterNamesTaskID = taskid.NewImplementationID(googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID, "composer") +var AutocompleteComposerClusterNamesTaskID = taskid.NewImplementationID(googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID.Ref(), "composer") // ComposerClusterNamePrefixTaskID is the task id for the task that returns the GKE cluster name prefix used by Cloud Composer. var ComposerClusterNamePrefixTaskID = taskid.NewImplementationID(googlecloudk8scommon_contract.ClusterNamePrefixTaskID, "composer") diff --git a/pkg/task/inspection/googlecloudclustercomposer/impl/autocompletecomposerclusternames_task.go b/pkg/task/inspection/googlecloudclustercomposer/impl/autocompletecomposerclusternames_task.go index 0c518632f..2efafb1ae 100644 --- a/pkg/task/inspection/googlecloudclustercomposer/impl/autocompletecomposerclusternames_task.go +++ b/pkg/task/inspection/googlecloudclustercomposer/impl/autocompletecomposerclusternames_task.go @@ -84,4 +84,6 @@ Note: Composer 3 does not run on your GKE. Please remove all Kubernetes/GKE ques ClusterNames: []string{clusterName}, }, }, nil -}, inspectioncore_contract.InspectionTypeLabel(googlecloudclustercomposer_contract.InspectionTypeId)) +}, inspectioncore_contract.InspectionTypeLabel(googlecloudclustercomposer_contract.InspectionTypeId), + coretask.WithSelectionPriority(1000), // Setting higher priority compared to the default autocomplete cluster name finder to override it. Composer cluster finder is currently overriding the common autocomplete cluster name finder using Cloud Monitoring to compare the environment label name. +) diff --git a/pkg/task/inspection/googlecloudclustergdcbaremetal/contract/clusterlistfetcher.go b/pkg/task/inspection/googlecloudclustergdcbaremetal/contract/clusterlistfetcher.go deleted file mode 100644 index d34875c46..000000000 --- a/pkg/task/inspection/googlecloudclustergdcbaremetal/contract/clusterlistfetcher.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergdcbaremetal_contract - -import ( - "context" - "fmt" - "strings" - - "github.com/GoogleCloudPlatform/khi/pkg/api/googlecloud" - coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" - "golang.org/x/sync/errgroup" - "google.golang.org/api/gkeonprem/v1" -) - -type ClusterListFetcher interface { - GetClusters(ctx context.Context, project string) ([]string, error) -} - -type ClusterListFetcherImpl struct{} - -// GetClusters implements ClusterListFetcher. -func (c *ClusterListFetcherImpl) GetClusters(ctx context.Context, project string) ([]string, error) { - cf := coretask.GetTaskResult(ctx, googlecloudcommon_contract.APIClientFactoryTaskID.Ref()) - injector := coretask.GetTaskResult(ctx, googlecloudcommon_contract.APIClientCallOptionsInjectorTaskID.Ref()) - - onpremAPI, err := cf.GKEOnPremService(ctx, googlecloud.Project(project)) - if err != nil { - return nil, fmt.Errorf("failed to generate onprem API client: %v", err) - } - - return getAdminAndUserClusters(ctx, project, func(ctx context.Context, parent string) ([]string, error) { - return getAdminClustersFromAPI(ctx, injector, onpremAPI, parent) - }, func(ctx context.Context, parent string) ([]string, error) { - return getUserClustersFromAPI(ctx, injector, onpremAPI, parent) - }) -} - -var _ ClusterListFetcher = (*ClusterListFetcherImpl)(nil) - -type fetchClusterFunc = func(ctx context.Context, parent string) ([]string, error) - -// getAdminAndUserClusters returns the list of clusters obtained from APIs. -func getAdminAndUserClusters(ctx context.Context, project string, fetchAdminCluster, fetchUserCluster fetchClusterFunc) ([]string, error) { - resultCh := make(chan []string, 2) - errGrp, groupCtx := errgroup.WithContext(ctx) - - errGrp.Go(func() error { - adminClusters, err := fetchAdminCluster(groupCtx, project) - if err != nil { - return err - } - resultCh <- adminClusters - return nil - }) - - errGrp.Go(func() error { - userClusters, err := fetchUserCluster(groupCtx, project) - if err != nil { - return err - } - resultCh <- userClusters - return nil - }) - - err := errGrp.Wait() - close(resultCh) - if err != nil { - return nil, err - } - - var result []string - for clusters := range resultCh { - result = append(result, clusters...) - } - return result, nil -} - -func getAdminClustersFromAPI(ctx context.Context, injector *googlecloud.CallOptionInjector, client *gkeonprem.Service, project string) ([]string, error) { - parent := fmt.Sprintf("projects/%s/locations/-", project) - var nextPageToken string - var result []string - for { - req := client.Projects.Locations.BareMetalAdminClusters.List(parent).PageToken(nextPageToken) - injector.InjectToCall(req, googlecloud.Project(project)) - resp, err := req.Context(ctx).Do() - if err != nil { - return nil, err - } - for _, cluster := range resp.BareMetalAdminClusters { - result = append(result, toShortClusterName(cluster.Name)) - } - nextPageToken = resp.NextPageToken - if nextPageToken == "" { - break - } - } - return result, nil -} - -func getUserClustersFromAPI(ctx context.Context, injector *googlecloud.CallOptionInjector, client *gkeonprem.Service, project string) ([]string, error) { - parent := fmt.Sprintf("projects/%s/locations/-", project) - var nextPageToken string - var result []string - for { - req := client.Projects.Locations.BareMetalClusters.List(parent).PageToken(nextPageToken) - injector.InjectToCall(req, googlecloud.Project(project)) - resp, err := req.Context(ctx).Do() - if err != nil { - return nil, err - } - for _, cluster := range resp.BareMetalClusters { - result = append(result, toShortClusterName(cluster.Name)) - } - nextPageToken = resp.NextPageToken - if nextPageToken == "" { - break - } - } - return result, nil -} - -// toShortClusterName converts the cluster name included in the api response to the name used in form field. -// The original format is /projects/{projectID}/locations/{location}/(baremetalClusters|baremetalAdminClusters)/{clusterName} -func toShortClusterName(longClusterName string) string { - li := strings.LastIndex(longClusterName, "/") - return longClusterName[li+1:] -} diff --git a/pkg/task/inspection/googlecloudclustergdcbaremetal/contract/clusterlistfetcher_test.go b/pkg/task/inspection/googlecloudclustergdcbaremetal/contract/clusterlistfetcher_test.go deleted file mode 100644 index 24c106ac1..000000000 --- a/pkg/task/inspection/googlecloudclustergdcbaremetal/contract/clusterlistfetcher_test.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergdcbaremetal_contract - -import ( - "context" - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" -) - -func TestGetAdminAndUserClusters(t *testing.T) { - tests := []struct { - name string - project string - fetchAdminCluster fetchClusterFunc - fetchUserCluster fetchClusterFunc - want []string - wantErr bool - expectedErr error - }{ - { - name: "successful fetch", - project: "test-project", - fetchAdminCluster: func(ctx context.Context, parent string) ([]string, error) { - return []string{"admin-cluster-1", "admin-cluster-2"}, nil - }, - fetchUserCluster: func(ctx context.Context, parent string) ([]string, error) { - return []string{"user-cluster-1", "user-cluster-2"}, nil - }, - want: []string{"admin-cluster-1", "admin-cluster-2", "user-cluster-1", "user-cluster-2"}, - wantErr: false, - }, - { - name: "error in fetchAdminCluster", - project: "test-project", - fetchAdminCluster: func(ctx context.Context, parent string) ([]string, error) { - return nil, fmt.Errorf("admin cluster fetch error") - }, - fetchUserCluster: func(ctx context.Context, parent string) ([]string, error) { - return []string{"user-cluster-1"}, nil - }, - wantErr: true, - expectedErr: fmt.Errorf("admin cluster fetch error"), - }, - { - name: "error in fetchUserCluster", - project: "test-project", - fetchAdminCluster: func(ctx context.Context, parent string) ([]string, error) { - return []string{"admin-cluster-1"}, nil - }, - fetchUserCluster: func(ctx context.Context, parent string) ([]string, error) { - return nil, fmt.Errorf("user cluster fetch error") - }, - wantErr: true, - expectedErr: fmt.Errorf("user cluster fetch error"), - }, - { - name: "empty results", - project: "test-project", - fetchAdminCluster: func(ctx context.Context, parent string) ([]string, error) { - return []string{}, nil - }, - fetchUserCluster: func(ctx context.Context, parent string) ([]string, error) { - return []string{}, nil - }, - want: []string{}, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := getAdminAndUserClusters(context.Background(), tt.project, tt.fetchAdminCluster, tt.fetchUserCluster) - if (err != nil) != tt.wantErr { - t.Errorf("getAdminAndUserClusters() error = %v, wantErr %v", err, tt.wantErr) - return - } - if tt.wantErr { - if err.Error() != tt.expectedErr.Error() { - t.Errorf("getAdminAndUserClusters() error = %v, expectedErr %v", err, tt.expectedErr) - } - return - } - - // Use a map to ignore order - gotMap := make(map[string]bool) - for _, s := range got { - gotMap[s] = true - } - wantMap := make(map[string]bool) - for _, s := range tt.want { - wantMap[s] = true - } - - if diff := cmp.Diff(gotMap, wantMap); diff != "" { - t.Errorf("getAdminAndUserClusters() mismatch (-got +want):\n%s", diff) - } - }) - } -} - -func TestToShortClusterName(t *testing.T) { - testCases := []struct { - name string - input string - want string - }{ - { - name: "valid user cluster name", - input: "projects/my-project/locations/us-central1/baremetalClusters/user-cluster-1", - want: "user-cluster-1", - }, - { - name: "valid admin cluster name", - input: "projects/my-project/locations/us-central1/baremetalAdminClusters/admin-cluster-1", - want: "admin-cluster-1", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - got := toShortClusterName(tc.input) - if diff := cmp.Diff(tc.want, got); diff != "" { - t.Errorf("toShortClusterName() mismatch (-want +got):\n%s", diff) - } - }) - } -} diff --git a/pkg/task/inspection/googlecloudclustergdcbaremetal/contract/taskid.go b/pkg/task/inspection/googlecloudclustergdcbaremetal/contract/taskid.go index 141edfc8f..10396a161 100644 --- a/pkg/task/inspection/googlecloudclustergdcbaremetal/contract/taskid.go +++ b/pkg/task/inspection/googlecloudclustergdcbaremetal/contract/taskid.go @@ -22,11 +22,5 @@ import ( // ClusterGDCBaremetalCommonTaskPrefix is the common task id prefix for GDC Baremetal clusters. var ClusterGDCBaremetalCommonTaskPrefix = googlecloudk8scommon_contract.GoogleCloudCommonK8STaskIDPrefix + "gdc-baremetal" -// AutocompleteGDCVForBaremetalClusterNamesTaskID is the task ID for listing up GDCV for Baremetal cluster names on the project. -var AutocompleteGDCVForBaremetalClusterNamesTaskID = taskid.NewImplementationID(googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID, "anthos-on-baremetal") - // ClusterNamePrefixTaskIDForGDCVForBaremetal is the task ID for the GDCV for Baremetal cluster name prefix. var ClusterNamePrefixTaskIDForGDCVForBaremetal = taskid.NewImplementationID(googlecloudk8scommon_contract.ClusterNamePrefixTaskID, "gdcv-for-baremetal") - -// ClusterListFetcherTaskID is the task ID to inject ClusterListFetcher instance. -var ClusterListFetcherTaskID = taskid.NewDefaultImplementationID[ClusterListFetcher](ClusterGDCBaremetalCommonTaskPrefix + "cluster-list-fetcher") diff --git a/pkg/task/inspection/googlecloudclustergdcbaremetal/impl/autocompletegdcvforbaremetalclusternames_task.go b/pkg/task/inspection/googlecloudclustergdcbaremetal/impl/autocompletegdcvforbaremetalclusternames_task.go deleted file mode 100644 index d73fce42f..000000000 --- a/pkg/task/inspection/googlecloudclustergdcbaremetal/impl/autocompletegdcvforbaremetalclusternames_task.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergdcbaremetal_impl - -import ( - "context" - "fmt" - "log/slog" - - inspectiontaskbase "github.com/GoogleCloudPlatform/khi/pkg/core/inspection/taskbase" - coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" - "github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid" - googlecloudclustergdcbaremetal_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergdcbaremetal/contract" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" - googlecloudk8scommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudk8scommon/contract" - inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract" -) - -// AutocompleteGDCVForBaremetalClusterNamesTask is a task that provides autocomplete suggestions for GDCV for Baremetal cluster names. -var AutocompleteGDCVForBaremetalClusterNamesTask = inspectiontaskbase.NewCachedTask(googlecloudclustergdcbaremetal_contract.AutocompleteGDCVForBaremetalClusterNamesTaskID, []taskid.UntypedTaskReference{ - googlecloudclustergdcbaremetal_contract.ClusterListFetcherTaskID.Ref(), - googlecloudcommon_contract.InputProjectIdTaskID.Ref(), -}, func(ctx context.Context, prevValue inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]) (inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList], error) { - - projectID := coretask.GetTaskResult(ctx, googlecloudcommon_contract.InputProjectIdTaskID.Ref()) - if projectID != "" && projectID == prevValue.DependencyDigest { - return prevValue, nil - } - - if projectID != "" { - clusterListFetcher := coretask.GetTaskResult(ctx, googlecloudclustergdcbaremetal_contract.ClusterListFetcherTaskID.Ref()) - clusterNames, err := clusterListFetcher.GetClusters(ctx, projectID) - if err != nil { - slog.WarnContext(ctx, fmt.Sprintf("Failed to read the cluster names in the project %s\n%s", projectID, err)) - return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ - DependencyDigest: projectID, - Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ - ClusterNames: []string{}, - Error: fmt.Sprintf("Failed to get the list from API:%s", err.Error()), - }, - }, nil - } - return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ - DependencyDigest: projectID, - Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ - ClusterNames: clusterNames, - Error: "", - }, - }, nil - } - return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ - DependencyDigest: projectID, - Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ - ClusterNames: []string{}, - Error: "Project ID is empty", - }, - }, nil -}, inspectioncore_contract.InspectionTypeLabel(googlecloudclustergdcbaremetal_contract.InspectionTypeId)) diff --git a/pkg/task/inspection/googlecloudclustergdcbaremetal/impl/autocompletegdcvforbaremetalclusternames_task_test.go b/pkg/task/inspection/googlecloudclustergdcbaremetal/impl/autocompletegdcvforbaremetalclusternames_task_test.go deleted file mode 100644 index 43a8c3ea8..000000000 --- a/pkg/task/inspection/googlecloudclustergdcbaremetal/impl/autocompletegdcvforbaremetalclusternames_task_test.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergdcbaremetal_impl - -import ( - "context" - "fmt" - "testing" - - inspectiontest "github.com/GoogleCloudPlatform/khi/pkg/core/inspection/test" - tasktest "github.com/GoogleCloudPlatform/khi/pkg/core/task/test" - googlecloudclustergdcbaremetal_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergdcbaremetal/contract" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" - googlecloudk8scommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudk8scommon/contract" - inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract" - "github.com/google/go-cmp/cmp" -) - -type mockGDCBaremetalClusterListFetcher struct { - responsePairs map[string][]string - responseWithError bool -} - -func (m *mockGDCBaremetalClusterListFetcher) GetClusters(ctx context.Context, projectID string) ([]string, error) { - if m.responseWithError { - return nil, fmt.Errorf("test error") - } - return m.responsePairs[projectID], nil -} - -var _ googlecloudclustergdcbaremetal_contract.ClusterListFetcher = (*mockGDCBaremetalClusterListFetcher)(nil) - -func TestAutocompleteGDCVForBaremetalClusterNamesTask(t *testing.T) { - testCase := []struct { - desc string - clusterList map[string][]string - listError bool - projectIDs []string - want []*googlecloudk8scommon_contract.AutocompleteClusterNameList - }{ - { - desc: "project id is empty", - clusterList: map[string][]string{}, - projectIDs: []string{""}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{}, - Error: "Project ID is empty", - }, - }, - }, - { - desc: "multiple call for single project", - clusterList: map[string][]string{ - "foo": {"qux", "quux"}, - }, - projectIDs: []string{"foo", "foo"}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{"qux", "quux"}, - Error: "", - }, - { - ClusterNames: []string{"qux", "quux"}, - Error: "", - }, - }, - }, - { - desc: "multiple projects", - clusterList: map[string][]string{ - "foo": {"qux", "quux"}, - "bar": {"hoge", "fuga"}, - }, - projectIDs: []string{"foo", "bar"}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{"qux", "quux"}, - Error: "", - }, - { - ClusterNames: []string{"hoge", "fuga"}, - Error: "", - }, - }, - }, - { - desc: "with error", - clusterList: map[string][]string{}, - listError: true, - projectIDs: []string{"foo"}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{}, - Error: "Failed to get the list from API:test error", - }, - }, - }, - } - - for _, tc := range testCase { - t.Run(tc.desc, func(t *testing.T) { - ctx := inspectiontest.WithDefaultTestInspectionTaskContext(t.Context()) - - mockClusterListFetcherInput := tasktest.NewTaskDependencyValuePair[googlecloudclustergdcbaremetal_contract.ClusterListFetcher](googlecloudclustergdcbaremetal_contract.ClusterListFetcherTaskID.Ref(), &mockGDCBaremetalClusterListFetcher{ - responsePairs: tc.clusterList, - responseWithError: tc.listError, - }) - for i := 0; i < len(tc.projectIDs); i++ { - projectIDInput := tasktest.NewTaskDependencyValuePair(googlecloudcommon_contract.InputProjectIdTaskID.Ref(), tc.projectIDs[i]) - result, _, err := inspectiontest.RunInspectionTask(ctx, AutocompleteGDCVForBaremetalClusterNamesTask, inspectioncore_contract.TaskModeDryRun, map[string]any{}, projectIDInput, mockClusterListFetcherInput) - if err != nil { - t.Fatalf("failed to run inspection task in loop %d: %v", i, err) - } - - if diff := cmp.Diff(tc.want[i], result); diff != "" { - t.Errorf("result of AutocompleteGDCVForBaremetalClusterNamesTask mismatch (-want +got):\n%s", diff) - } - } - }) - } -} diff --git a/pkg/task/inspection/googlecloudclustergdcbaremetal/impl/inject_task.go b/pkg/task/inspection/googlecloudclustergdcbaremetal/impl/inject_task.go deleted file mode 100644 index 61ade51f9..000000000 --- a/pkg/task/inspection/googlecloudclustergdcbaremetal/impl/inject_task.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergdcbaremetal_impl - -import ( - "context" - - coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" - "github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid" - googlecloudclustergdcbaremetal_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergdcbaremetal/contract" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" -) - -// ClusterListFetcherTask inject the default implementation for ClusterListFetcher -var ClusterListFetcherTask = coretask.NewTask(googlecloudclustergdcbaremetal_contract.ClusterListFetcherTaskID, []taskid.UntypedTaskReference{ - googlecloudcommon_contract.APIClientFactoryTaskID.Ref(), - googlecloudcommon_contract.APIClientCallOptionsInjectorTaskID.Ref(), -}, func(ctx context.Context) (googlecloudclustergdcbaremetal_contract.ClusterListFetcher, error) { - return &googlecloudclustergdcbaremetal_contract.ClusterListFetcherImpl{}, nil -}) diff --git a/pkg/task/inspection/googlecloudclustergdcbaremetal/impl/registration.go b/pkg/task/inspection/googlecloudclustergdcbaremetal/impl/registration.go index 9e6488a9c..2bdcdb246 100644 --- a/pkg/task/inspection/googlecloudclustergdcbaremetal/impl/registration.go +++ b/pkg/task/inspection/googlecloudclustergdcbaremetal/impl/registration.go @@ -27,8 +27,6 @@ func Register(registry coreinspection.InspectionTaskRegistry) error { return err } return coretask.RegisterTasks(registry, - AutocompleteGDCVForBaremetalClusterNamesTask, GDCVForBaremetalClusterNamePrefixTask, - ClusterListFetcherTask, ) } diff --git a/pkg/task/inspection/googlecloudclustergdcvmware/contract/clusterlistfetcher.go b/pkg/task/inspection/googlecloudclustergdcvmware/contract/clusterlistfetcher.go deleted file mode 100644 index 5c3ea5ae2..000000000 --- a/pkg/task/inspection/googlecloudclustergdcvmware/contract/clusterlistfetcher.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergdcvmware_contract - -import ( - "context" - "fmt" - "strings" - - "github.com/GoogleCloudPlatform/khi/pkg/api/googlecloud" - coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" - "golang.org/x/sync/errgroup" - "google.golang.org/api/gkeonprem/v1" -) - -type ClusterListFetcher interface { - GetClusters(ctx context.Context, project string) ([]string, error) -} - -type ClusterListFetcherImpl struct{} - -// GetClusters implements ClusterListFetcher. -func (c *ClusterListFetcherImpl) GetClusters(ctx context.Context, project string) ([]string, error) { - cf := coretask.GetTaskResult(ctx, googlecloudcommon_contract.APIClientFactoryTaskID.Ref()) - injector := coretask.GetTaskResult(ctx, googlecloudcommon_contract.APIClientCallOptionsInjectorTaskID.Ref()) - - onpremAPI, err := cf.GKEOnPremService(ctx, googlecloud.Project(project)) - if err != nil { - return nil, fmt.Errorf("failed to generate onprem API client: %v", err) - } - - return getAdminAndUserClusters(ctx, project, func(ctx context.Context, parent string) ([]string, error) { - return getAdminClustersFromAPI(ctx, injector, onpremAPI, parent) - }, func(ctx context.Context, parent string) ([]string, error) { - return getUserClustersFromAPI(ctx, injector, onpremAPI, parent) - }) -} - -var _ ClusterListFetcher = (*ClusterListFetcherImpl)(nil) - -type fetchClusterFunc = func(ctx context.Context, parent string) ([]string, error) - -// getAdminAndUserClusters returns the list of clusters obtained from APIs. -func getAdminAndUserClusters(ctx context.Context, project string, fetchAdminCluster, fetchUserCluster fetchClusterFunc) ([]string, error) { - resultCh := make(chan []string, 2) - errGrp, groupCtx := errgroup.WithContext(ctx) - - errGrp.Go(func() error { - adminClusters, err := fetchAdminCluster(groupCtx, project) - if err != nil { - return err - } - resultCh <- adminClusters - return nil - }) - - errGrp.Go(func() error { - userClusters, err := fetchUserCluster(groupCtx, project) - if err != nil { - return err - } - resultCh <- userClusters - return nil - }) - - err := errGrp.Wait() - close(resultCh) - if err != nil { - return nil, err - } - - var result []string - for clusters := range resultCh { - result = append(result, clusters...) - } - return result, nil -} - -func getAdminClustersFromAPI(ctx context.Context, injector *googlecloud.CallOptionInjector, client *gkeonprem.Service, project string) ([]string, error) { - parent := fmt.Sprintf("projects/%s/locations/-", project) - var nextPageToken string - var result []string - for { - req := client.Projects.Locations.VmwareAdminClusters.List(parent).PageToken(nextPageToken) - injector.InjectToCall(req, googlecloud.Project(project)) - resp, err := req.Context(ctx).Do() - if err != nil { - return nil, err - } - for _, cluster := range resp.VmwareAdminClusters { - result = append(result, toShortClusterName(cluster.Name)) - } - nextPageToken = resp.NextPageToken - if nextPageToken == "" { - break - } - } - return result, nil -} - -func getUserClustersFromAPI(ctx context.Context, injector *googlecloud.CallOptionInjector, client *gkeonprem.Service, project string) ([]string, error) { - parent := fmt.Sprintf("projects/%s/locations/-", project) - var nextPageToken string - var result []string - for { - req := client.Projects.Locations.VmwareClusters.List(parent).PageToken(nextPageToken) - injector.InjectToCall(req, googlecloud.Project(project)) - resp, err := req.Context(ctx).Do() - if err != nil { - return nil, err - } - for _, cluster := range resp.VmwareClusters { - result = append(result, toShortClusterName(cluster.Name)) - } - nextPageToken = resp.NextPageToken - if nextPageToken == "" { - break - } - } - return result, nil -} - -// toShortClusterName converts the cluster name included in the api response to the name used in form field. -// The original format is /projects/{projectID}/locations/{location}/(vmwareClusters|vmwareAdminClusters)/{clusterName} -func toShortClusterName(longClusterName string) string { - li := strings.LastIndex(longClusterName, "/") - return longClusterName[li+1:] -} diff --git a/pkg/task/inspection/googlecloudclustergdcvmware/contract/clusterlistfetcher_test.go b/pkg/task/inspection/googlecloudclustergdcvmware/contract/clusterlistfetcher_test.go deleted file mode 100644 index b8415a962..000000000 --- a/pkg/task/inspection/googlecloudclustergdcvmware/contract/clusterlistfetcher_test.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergdcvmware_contract - -import ( - "context" - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" -) - -func TestGetAdminAndUserClusters(t *testing.T) { - tests := []struct { - name string - project string - fetchAdminCluster fetchClusterFunc - fetchUserCluster fetchClusterFunc - want []string - wantErr bool - expectedErr error - }{ - { - name: "successful fetch", - project: "test-project", - fetchAdminCluster: func(ctx context.Context, parent string) ([]string, error) { - return []string{"admin-cluster-1", "admin-cluster-2"}, nil - }, - fetchUserCluster: func(ctx context.Context, parent string) ([]string, error) { - return []string{"user-cluster-1", "user-cluster-2"}, nil - }, - want: []string{"admin-cluster-1", "admin-cluster-2", "user-cluster-1", "user-cluster-2"}, - wantErr: false, - }, - { - name: "error in fetchAdminCluster", - project: "test-project", - fetchAdminCluster: func(ctx context.Context, parent string) ([]string, error) { - return nil, fmt.Errorf("admin cluster fetch error") - }, - fetchUserCluster: func(ctx context.Context, parent string) ([]string, error) { - return []string{"user-cluster-1"}, nil - }, - wantErr: true, - expectedErr: fmt.Errorf("admin cluster fetch error"), - }, - { - name: "error in fetchUserCluster", - project: "test-project", - fetchAdminCluster: func(ctx context.Context, parent string) ([]string, error) { - return []string{"admin-cluster-1"}, nil - }, - fetchUserCluster: func(ctx context.Context, parent string) ([]string, error) { - return nil, fmt.Errorf("user cluster fetch error") - }, - wantErr: true, - expectedErr: fmt.Errorf("user cluster fetch error"), - }, - { - name: "empty results", - project: "test-project", - fetchAdminCluster: func(ctx context.Context, parent string) ([]string, error) { - return []string{}, nil - }, - fetchUserCluster: func(ctx context.Context, parent string) ([]string, error) { - return []string{}, nil - }, - want: []string{}, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := getAdminAndUserClusters(context.Background(), tt.project, tt.fetchAdminCluster, tt.fetchUserCluster) - if (err != nil) != tt.wantErr { - t.Errorf("getAdminAndUserClusters() error = %v, wantErr %v", err, tt.wantErr) - return - } - if tt.wantErr { - if err.Error() != tt.expectedErr.Error() { - t.Errorf("getAdminAndUserClusters() error = %v, expectedErr %v", err, tt.expectedErr) - } - return - } - - // Use a map to ignore order - gotMap := make(map[string]bool) - for _, s := range got { - gotMap[s] = true - } - wantMap := make(map[string]bool) - for _, s := range tt.want { - wantMap[s] = true - } - - if diff := cmp.Diff(gotMap, wantMap); diff != "" { - t.Errorf("getAdminAndUserClusters() mismatch (-got +want):\n%s", diff) - } - }) - } -} - -func TestToShortClusterName(t *testing.T) { - testCases := []struct { - name string - input string - want string - }{ - { - name: "valid user cluster name", - input: "projects/my-project/locations/us-central1/vmwareClusters/user-cluster-1", - want: "user-cluster-1", - }, - { - name: "valid admin cluster name", - input: "projects/my-project/locations/us-central1/vmwareAdminClusters/admin-cluster-1", - want: "admin-cluster-1", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - got := toShortClusterName(tc.input) - if diff := cmp.Diff(tc.want, got); diff != "" { - t.Errorf("toShortClusterName() mismatch (-want +got):\n%s", diff) - } - }) - } -} diff --git a/pkg/task/inspection/googlecloudclustergdcvmware/contract/taskid.go b/pkg/task/inspection/googlecloudclustergdcvmware/contract/taskid.go index 290b710e8..87213eac7 100644 --- a/pkg/task/inspection/googlecloudclustergdcvmware/contract/taskid.go +++ b/pkg/task/inspection/googlecloudclustergdcvmware/contract/taskid.go @@ -22,11 +22,5 @@ import ( // ClusterGDCVMWareCommonTaskPrefix is the common task id prefix for GDC Vmware cluster related tasks. var ClusterGDCVMWareCommonTaskPrefix = googlecloudk8scommon_contract.GoogleCloudCommonK8STaskIDPrefix + "gdc-vmware" -// AutocompleteGDCVForVMWareClusterNamesTaskID is the task ID for listing up GDCV for VMWare cluster names on the project. -var AutocompleteGDCVForVMWareClusterNamesTaskID = taskid.NewImplementationID(googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID, "anthos-on-vmware") - // ClusterNamePrefixTaskIDForGDCVForVMWare is the task ID for the GDCV for VMWare cluster name prefix. var ClusterNamePrefixTaskIDForGDCVForVMWare = taskid.NewImplementationID(googlecloudk8scommon_contract.ClusterNamePrefixTaskID, "gdcv-for-vmware") - -// ClusterListFetcherTaskID injects the ClusterListFetcher implementation. -var ClusterListFetcherTaskID = taskid.NewDefaultImplementationID[ClusterListFetcher](ClusterGDCVMWareCommonTaskPrefix + "cluster-list-fetcher") diff --git a/pkg/task/inspection/googlecloudclustergdcvmware/impl/autocompletegdcvforvmwareclusternames_task.go b/pkg/task/inspection/googlecloudclustergdcvmware/impl/autocompletegdcvforvmwareclusternames_task.go deleted file mode 100644 index be629a609..000000000 --- a/pkg/task/inspection/googlecloudclustergdcvmware/impl/autocompletegdcvforvmwareclusternames_task.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergdcvmware_impl - -import ( - "context" - "fmt" - "log/slog" - - inspectiontaskbase "github.com/GoogleCloudPlatform/khi/pkg/core/inspection/taskbase" - coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" - "github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid" - googlecloudclustergdcvmware_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergdcvmware/contract" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" - googlecloudk8scommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudk8scommon/contract" - inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract" -) - -// AutocompleteGDCVForVMWareClusterNamesTask is a task that provides autocomplete suggestions for GDCV for VMWare cluster names. -var AutocompleteGDCVForVMWareClusterNamesTask = inspectiontaskbase.NewCachedTask(googlecloudclustergdcvmware_contract.AutocompleteGDCVForVMWareClusterNamesTaskID, []taskid.UntypedTaskReference{ - googlecloudclustergdcvmware_contract.ClusterListFetcherTaskID.Ref(), - googlecloudcommon_contract.InputProjectIdTaskID.Ref(), -}, func(ctx context.Context, prevValue inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]) (inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList], error) { - projectID := coretask.GetTaskResult(ctx, googlecloudcommon_contract.InputProjectIdTaskID.Ref()) - if projectID != "" && projectID == prevValue.DependencyDigest { - return prevValue, nil - } - - if projectID != "" { - clusterListFetcher := coretask.GetTaskResult(ctx, googlecloudclustergdcvmware_contract.ClusterListFetcherTaskID.Ref()) - clusterNames, err := clusterListFetcher.GetClusters(ctx, projectID) - if err != nil { - slog.WarnContext(ctx, fmt.Sprintf("Failed to read the cluster names in the project %s\n%s", projectID, err)) - return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ - DependencyDigest: projectID, - Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ - ClusterNames: []string{}, - Error: fmt.Sprintf("Failed to get the list from API:%s", err.Error()), - }, - }, nil - } - return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ - DependencyDigest: projectID, - Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ - ClusterNames: clusterNames, - Error: "", - }, - }, nil - } - return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ - DependencyDigest: projectID, - Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ - ClusterNames: []string{}, - Error: "Project ID is empty", - }, - }, nil -}, inspectioncore_contract.InspectionTypeLabel(googlecloudclustergdcvmware_contract.InspectionTypeId)) diff --git a/pkg/task/inspection/googlecloudclustergdcvmware/impl/autocompletegdcvforvmwareclusternames_task_test.go b/pkg/task/inspection/googlecloudclustergdcvmware/impl/autocompletegdcvforvmwareclusternames_task_test.go deleted file mode 100644 index 3fefac9cf..000000000 --- a/pkg/task/inspection/googlecloudclustergdcvmware/impl/autocompletegdcvforvmwareclusternames_task_test.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergdcvmware_impl - -import ( - "context" - "fmt" - "testing" - - inspectiontest "github.com/GoogleCloudPlatform/khi/pkg/core/inspection/test" - tasktest "github.com/GoogleCloudPlatform/khi/pkg/core/task/test" - googlecloudclustergdcvmware_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergdcvmware/contract" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" - googlecloudk8scommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudk8scommon/contract" - inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract" - "github.com/google/go-cmp/cmp" -) - -type mockGDCVmwareClusterListFetcher struct { - responsePairs map[string][]string - responseWithError bool -} - -func (m *mockGDCVmwareClusterListFetcher) GetClusters(ctx context.Context, projectID string) ([]string, error) { - if m.responseWithError { - return nil, fmt.Errorf("test error") - } - return m.responsePairs[projectID], nil -} - -var _ googlecloudclustergdcvmware_contract.ClusterListFetcher = (*mockGDCVmwareClusterListFetcher)(nil) - -func TestAutocompleteGDCVForVmwareClusterNamesTask(t *testing.T) { - testCase := []struct { - desc string - clusterList map[string][]string - listError bool - projectIDs []string - want []*googlecloudk8scommon_contract.AutocompleteClusterNameList - }{ - { - desc: "project id is empty", - clusterList: map[string][]string{}, - projectIDs: []string{""}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{}, - Error: "Project ID is empty", - }, - }, - }, - { - desc: "multiple call for single project", - clusterList: map[string][]string{ - "foo": {"vm-cluster-1", "vm-cluster-2"}, - }, - projectIDs: []string{"foo", "foo"}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{"vm-cluster-1", "vm-cluster-2"}, - Error: "", - }, - { - ClusterNames: []string{"vm-cluster-1", "vm-cluster-2"}, - Error: "", - }, - }, - }, - { - desc: "multiple projects", - clusterList: map[string][]string{ - "foo": {"vm-cluster-1", "vm-cluster-2"}, - "bar": {"test-cluster-a", "test-cluster-b"}, - }, - projectIDs: []string{"foo", "bar"}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{"vm-cluster-1", "vm-cluster-2"}, - Error: "", - }, - { - ClusterNames: []string{"test-cluster-a", "test-cluster-b"}, - Error: "", - }, - }, - }, - { - desc: "with error", - clusterList: map[string][]string{}, - listError: true, - projectIDs: []string{"foo"}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{}, - Error: "Failed to get the list from API:test error", - }, - }, - }, - } - - for _, tc := range testCase { - t.Run(tc.desc, func(t *testing.T) { - ctx := inspectiontest.WithDefaultTestInspectionTaskContext(t.Context()) - - mockClusterListFetcherInput := tasktest.NewTaskDependencyValuePair[googlecloudclustergdcvmware_contract.ClusterListFetcher](googlecloudclustergdcvmware_contract.ClusterListFetcherTaskID.Ref(), &mockGDCVmwareClusterListFetcher{ - responsePairs: tc.clusterList, - responseWithError: tc.listError, - }) - for i := 0; i < len(tc.projectIDs); i++ { - projectIDInput := tasktest.NewTaskDependencyValuePair(googlecloudcommon_contract.InputProjectIdTaskID.Ref(), tc.projectIDs[i]) - result, _, err := inspectiontest.RunInspectionTask(ctx, AutocompleteGDCVForVMWareClusterNamesTask, inspectioncore_contract.TaskModeDryRun, map[string]any{}, projectIDInput, mockClusterListFetcherInput) - if err != nil { - t.Fatalf("failed to run inspection task in loop %d: %v", i, err) - } - - if diff := cmp.Diff(tc.want[i], result); diff != "" { - t.Errorf("result of AutocompleteGDCVForVmwareClusterNamesTask mismatch (-want +got):\n%s", diff) - } - } - }) - } -} diff --git a/pkg/task/inspection/googlecloudclustergdcvmware/impl/inject_task.go b/pkg/task/inspection/googlecloudclustergdcvmware/impl/inject_task.go deleted file mode 100644 index 63f9771d2..000000000 --- a/pkg/task/inspection/googlecloudclustergdcvmware/impl/inject_task.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergdcvmware_impl - -import ( - "context" - - coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" - "github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid" - googlecloudclustergdcvmware_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergdcvmware/contract" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" -) - -// ClusterListFetcherTask inject the default implementation for ClusterListFetcher -var ClusterListFetcherTask = coretask.NewTask(googlecloudclustergdcvmware_contract.ClusterListFetcherTaskID, []taskid.UntypedTaskReference{ - googlecloudcommon_contract.APIClientFactoryTaskID.Ref(), - googlecloudcommon_contract.APIClientCallOptionsInjectorTaskID.Ref(), -}, func(ctx context.Context) (googlecloudclustergdcvmware_contract.ClusterListFetcher, error) { - return &googlecloudclustergdcvmware_contract.ClusterListFetcherImpl{}, nil -}) diff --git a/pkg/task/inspection/googlecloudclustergdcvmware/impl/registration.go b/pkg/task/inspection/googlecloudclustergdcvmware/impl/registration.go index 764ff3e63..1e4027620 100644 --- a/pkg/task/inspection/googlecloudclustergdcvmware/impl/registration.go +++ b/pkg/task/inspection/googlecloudclustergdcvmware/impl/registration.go @@ -27,8 +27,6 @@ func Register(registry coreinspection.InspectionTaskRegistry) error { return err } return coretask.RegisterTasks(registry, - AutocompleteGDCVForVMWareClusterNamesTask, GDCVForVMWareClusterNamePrefixTask, - ClusterListFetcherTask, ) } diff --git a/pkg/task/inspection/googlecloudclustergke/contract/gkeclusterlistfetcher.go b/pkg/task/inspection/googlecloudclustergke/contract/gkeclusterlistfetcher.go deleted file mode 100644 index 5df2a489a..000000000 --- a/pkg/task/inspection/googlecloudclustergke/contract/gkeclusterlistfetcher.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergke_contract - -import ( - "context" - "fmt" - - "cloud.google.com/go/container/apiv1/containerpb" - "github.com/GoogleCloudPlatform/khi/pkg/api/googlecloud" - coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" -) - -// ClusterListFetcher fetches the list of GKE cluster in the project. -type ClusterListFetcher interface { - GetClusterNames(ctx context.Context, projectID string) ([]string, error) -} - -// ClusterListFetcherImpl is the default implementation of ClusterListFetcher. -type ClusterListFetcherImpl struct{} - -// GetClusterNames implements ClusterListFetcher. -// This expects the task googlecloudcommon_contract.APIClientFactoryTaskID is already resolved. -func (g *ClusterListFetcherImpl) GetClusterNames(ctx context.Context, projectID string) ([]string, error) { - cf := coretask.GetTaskResult(ctx, googlecloudcommon_contract.APIClientFactoryTaskID.Ref()) - injector := coretask.GetTaskResult(ctx, googlecloudcommon_contract.APIClientCallOptionsInjectorTaskID.Ref()) - - ccmc, err := cf.ContainerClusterManagerClient(ctx, googlecloud.Project(projectID)) - if err != nil { - return nil, fmt.Errorf("failed to create container cluster manager client: %w", err) - } - defer ccmc.Close() - - ctx = injector.InjectToCallContext(ctx, googlecloud.Project(projectID)) - clusters, err := ccmc.ListClusters(ctx, &containerpb.ListClustersRequest{ - Parent: fmt.Sprintf("projects/%s/locations/-", projectID), - }) - if err != nil { - return nil, fmt.Errorf("failed to read the cluster names in the project %s: %w", projectID, err) - } - - return apiResponseToClusterNameList(clusters), nil -} - -var _ ClusterListFetcher = (*ClusterListFetcherImpl)(nil) - -// apiResponseToClusterNameList returns the list of cluster names from the API response. -func apiResponseToClusterNameList(response *containerpb.ListClustersResponse) []string { - if response == nil { - return []string{} - } - result := make([]string, 0, len(response.Clusters)) - for _, cluster := range response.Clusters { - result = append(result, cluster.Name) - } - return result -} diff --git a/pkg/task/inspection/googlecloudclustergke/contract/gkeclusterlistfetcher_test.go b/pkg/task/inspection/googlecloudclustergke/contract/gkeclusterlistfetcher_test.go deleted file mode 100644 index db19df12c..000000000 --- a/pkg/task/inspection/googlecloudclustergke/contract/gkeclusterlistfetcher_test.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergke_contract - -import ( - "testing" - - "cloud.google.com/go/container/apiv1/containerpb" - "github.com/google/go-cmp/cmp" -) - -func TestApiResponseToClusterNameList(t *testing.T) { - testCases := []struct { - desc string - response *containerpb.ListClustersResponse - want []string - }{ - { - desc: "empty response", - response: &containerpb.ListClustersResponse{}, - want: []string{}, - }, - { - desc: "standard response", - response: &containerpb.ListClustersResponse{ - Clusters: []*containerpb.Cluster{ - {Name: "foo"}, - {Name: "bar"}, - }, - }, - want: []string{"foo", "bar"}, - }, - { - desc: "nil response", - response: nil, - want: []string{}, - }, - } - for _, tc := range testCases { - t.Run(tc.desc, func(t *testing.T) { - got := apiResponseToClusterNameList(tc.response) - if diff := cmp.Diff(tc.want, got); diff != "" { - t.Errorf("apiResponseToClusterNameList() mismatch (-want +got):\n%s", diff) - } - }) - } -} diff --git a/pkg/task/inspection/googlecloudclustergke/contract/taskid.go b/pkg/task/inspection/googlecloudclustergke/contract/taskid.go index c0484cbb4..69771ad5b 100644 --- a/pkg/task/inspection/googlecloudclustergke/contract/taskid.go +++ b/pkg/task/inspection/googlecloudclustergke/contract/taskid.go @@ -23,11 +23,8 @@ import ( // ClusterGKETaskCommonPrefix is the task id prefix originally defined in googlecloudclustergke. var ClusterGKETaskCommonPrefix = googlecloudcommon_contract.GoogleCloudCommonTaskIDPrefix + "cluster/gke/" -// AutocompleteGKEClusterNamesTaskID is the task ID for listing up GKE cluster names on the project. -var AutocompleteGKEClusterNamesTaskID = taskid.NewImplementationID(googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID, "gke") - // ClusterNamePrefixTaskIDForGKE is the task ID for the GKE cluster name prefix(it's "" for GKE) var ClusterNamePrefixTaskIDForGKE = taskid.NewImplementationID(googlecloudk8scommon_contract.ClusterNamePrefixTaskID, "gke") -// ClusterListFetcherTaskID is the task ID for getting GKEClusterListFetcher interface to enable tests to inject mock list fetcher. -var ClusterListFetcherTaskID = taskid.NewDefaultImplementationID[ClusterListFetcher](ClusterGKETaskCommonPrefix + "cluster-list-fetcher") +// AutocompleteClusterNamesMetricsTypeTaskIDForGKE is the task ID for the metrics type used for autocomplete cluster names in GKE. +var AutocompleteClusterNamesMetricsTypeTaskIDForGKE = taskid.NewImplementationID(googlecloudk8scommon_contract.AutocompleteClusterNamesMetricsTypeTaskID.Ref(), "gke") diff --git a/pkg/task/inspection/googlecloudclustergke/impl/autocomplete.go b/pkg/task/inspection/googlecloudclustergke/impl/autocomplete.go new file mode 100644 index 000000000..66259df32 --- /dev/null +++ b/pkg/task/inspection/googlecloudclustergke/impl/autocomplete.go @@ -0,0 +1,31 @@ +// Copyright 2025 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 +// +// http://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 googlecloudclustergke_impl + +import ( + "context" + + coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" + "github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid" + googlecloudclustergke_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergke/contract" + googlecloudinspectiontypegroup_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudinspectiontypegroup/contract" + inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract" +) + +// AutocompleteClusterNamesMetricsTypeTask returns the metrics type used for autocomplete cluster names in GKE. +// The metrics type "kubernetes.io/container/uptime" is used for GKE instead of the default "kubernetes.io/anthos/container/uptime". +var AutocompleteClusterNamesMetricsTypeTask = coretask.NewTask(googlecloudclustergke_contract.AutocompleteClusterNamesMetricsTypeTaskIDForGKE, []taskid.UntypedTaskReference{}, func(ctx context.Context) (string, error) { + return "kubernetes.io/container/uptime", nil +}, coretask.WithSelectionPriority(1000), inspectioncore_contract.InspectionTypeLabel(googlecloudinspectiontypegroup_contract.GKEBasedClusterInspectionTypes...)) diff --git a/pkg/task/inspection/googlecloudclustergke/impl/autocompletegkeclusternames_task.go b/pkg/task/inspection/googlecloudclustergke/impl/autocompletegkeclusternames_task.go deleted file mode 100644 index 99821eae0..000000000 --- a/pkg/task/inspection/googlecloudclustergke/impl/autocompletegkeclusternames_task.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergke_impl - -import ( - "context" - "fmt" - "log/slog" - - inspectiontaskbase "github.com/GoogleCloudPlatform/khi/pkg/core/inspection/taskbase" - coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" - "github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid" - googlecloudclustergke_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergke/contract" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" - googlecloudk8scommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudk8scommon/contract" - inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract" -) - -// AutocompleteGKEClusterNamesTask is a task that provides autocomplete suggestions for GKE cluster names. -var AutocompleteGKEClusterNamesTask = inspectiontaskbase.NewCachedTask(googlecloudclustergke_contract.AutocompleteGKEClusterNamesTaskID, []taskid.UntypedTaskReference{ - googlecloudclustergke_contract.ClusterListFetcherTaskID.Ref(), - googlecloudcommon_contract.InputProjectIdTaskID.Ref(), -}, func(ctx context.Context, prevValue inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]) (inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList], error) { - listFetcher := coretask.GetTaskResult(ctx, googlecloudclustergke_contract.ClusterListFetcherTaskID.Ref()) - - projectID := coretask.GetTaskResult(ctx, googlecloudcommon_contract.InputProjectIdTaskID.Ref()) - if projectID != "" && projectID == prevValue.DependencyDigest { - return prevValue, nil - } - - if projectID != "" { - clusters, err := listFetcher.GetClusterNames(ctx, projectID) - if err != nil { - slog.WarnContext(ctx, "Failed to read cluster names for project", "projectID", projectID, "error", err) - return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ - DependencyDigest: projectID, - Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ - ClusterNames: []string{}, - Error: fmt.Sprintf("Failed to list GKE cluster names: %v", err), - }, - }, nil - } - return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ - DependencyDigest: projectID, - Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ - ClusterNames: clusters, - Error: "", - }, - }, nil - } - return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ - DependencyDigest: projectID, - Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ - ClusterNames: []string{}, - Error: "Project ID is empty", - }, - }, nil -}, inspectioncore_contract.InspectionTypeLabel(googlecloudclustergke_contract.InspectionTypeId)) diff --git a/pkg/task/inspection/googlecloudclustergke/impl/autocompletegkeclusternames_task_test.go b/pkg/task/inspection/googlecloudclustergke/impl/autocompletegkeclusternames_task_test.go deleted file mode 100644 index 066992314..000000000 --- a/pkg/task/inspection/googlecloudclustergke/impl/autocompletegkeclusternames_task_test.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergke_impl - -import ( - "context" - "fmt" - "testing" - - inspectiontest "github.com/GoogleCloudPlatform/khi/pkg/core/inspection/test" - tasktest "github.com/GoogleCloudPlatform/khi/pkg/core/task/test" - googlecloudclustergke_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergke/contract" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" - googlecloudk8scommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudk8scommon/contract" - inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract" - "github.com/google/go-cmp/cmp" -) - -type mockGKEClusterListFetcher struct { - responsePairs map[string][]string - responseWithError bool -} - -func (m *mockGKEClusterListFetcher) GetClusterNames(ctx context.Context, projectID string) ([]string, error) { - if m.responseWithError { - return nil, fmt.Errorf("test error") - } - return m.responsePairs[projectID], nil -} - -var _ googlecloudclustergke_contract.ClusterListFetcher = (*mockGKEClusterListFetcher)(nil) - -func TestAutocompleteGKEClusterNamesTask(t *testing.T) { - testCase := []struct { - desc string - clusterList map[string][]string - listError bool - projectIDs []string - want []*googlecloudk8scommon_contract.AutocompleteClusterNameList - }{ - { - desc: "project id is empty", - clusterList: map[string][]string{}, - projectIDs: []string{""}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{}, - Error: "Project ID is empty", - }, - }, - }, - { - desc: "multiple call for single project", - clusterList: map[string][]string{ - "foo": {"qux", "quux"}, - }, - projectIDs: []string{"foo", "foo"}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{"qux", "quux"}, - Error: "", - }, - { - ClusterNames: []string{"qux", "quux"}, - Error: "", - }, - }, - }, - { - desc: "multiple calls for different projects", - clusterList: map[string][]string{ - "foo": {"qux", "quux"}, - "bar": {"hoge", "fuga"}, - }, - projectIDs: []string{"foo", "bar"}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{"qux", "quux"}, - Error: "", - }, - { - ClusterNames: []string{"hoge", "fuga"}, - Error: "", - }, - }, - }, - { - desc: "with error", - clusterList: map[string][]string{}, - listError: true, - projectIDs: []string{"foo"}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{}, - Error: "Failed to list GKE cluster names: test error", - }, - }, - }, - } - - for _, tc := range testCase { - t.Run(tc.desc, func(t *testing.T) { - ctx := inspectiontest.WithDefaultTestInspectionTaskContext(t.Context()) - - mockClusterListFetcherInput := tasktest.NewTaskDependencyValuePair[googlecloudclustergke_contract.ClusterListFetcher](googlecloudclustergke_contract.ClusterListFetcherTaskID.Ref(), &mockGKEClusterListFetcher{ - responsePairs: tc.clusterList, - responseWithError: tc.listError, - }) - for i := 0; i < len(tc.projectIDs); i++ { - projectIDInput := tasktest.NewTaskDependencyValuePair(googlecloudcommon_contract.InputProjectIdTaskID.Ref(), tc.projectIDs[i]) - result, _, err := inspectiontest.RunInspectionTask(ctx, AutocompleteGKEClusterNamesTask, inspectioncore_contract.TaskModeDryRun, map[string]any{}, projectIDInput, mockClusterListFetcherInput) - if err != nil { - t.Fatalf("failed to run inspection task in loop %d: %v", i, err) - } - - if diff := cmp.Diff(tc.want[i], result); diff != "" { - t.Errorf("result of AutocompleteGKEClusterNamesTask mismatch (-want +got):\n%s", diff) - - } - } - }) - } -} diff --git a/pkg/task/inspection/googlecloudclustergke/impl/inject_task.go b/pkg/task/inspection/googlecloudclustergke/impl/inject_task.go deleted file mode 100644 index da9150517..000000000 --- a/pkg/task/inspection/googlecloudclustergke/impl/inject_task.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergke_impl - -import ( - "context" - - coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" - "github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid" - googlecloudclustergke_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergke/contract" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" -) - -// ClusterListFetcherTask is a task to inject ClusterListFetcherImpl instance and enable tests to inject mock instance. -var ClusterListFetcherTask = coretask.NewTask(googlecloudclustergke_contract.ClusterListFetcherTaskID, []taskid.UntypedTaskReference{ - googlecloudcommon_contract.APIClientFactoryTaskID.Ref(), - googlecloudcommon_contract.APIClientCallOptionsInjectorTaskID.Ref(), -}, func(ctx context.Context) (googlecloudclustergke_contract.ClusterListFetcher, error) { - return &googlecloudclustergke_contract.ClusterListFetcherImpl{}, nil -}) diff --git a/pkg/task/inspection/googlecloudclustergke/impl/registration.go b/pkg/task/inspection/googlecloudclustergke/impl/registration.go index d68c49df5..cb7b7b42d 100644 --- a/pkg/task/inspection/googlecloudclustergke/impl/registration.go +++ b/pkg/task/inspection/googlecloudclustergke/impl/registration.go @@ -27,8 +27,7 @@ func Register(registry coreinspection.InspectionTaskRegistry) error { return err } return coretask.RegisterTasks(registry, - AutocompleteGKEClusterNamesTask, GKEClusterNamePrefixTask, - ClusterListFetcherTask, + AutocompleteClusterNamesMetricsTypeTask, ) } diff --git a/pkg/task/inspection/googlecloudclustergkeonaws/contract/clusterlistfetcher.go b/pkg/task/inspection/googlecloudclustergkeonaws/contract/clusterlistfetcher.go deleted file mode 100644 index 89a1eccbb..000000000 --- a/pkg/task/inspection/googlecloudclustergkeonaws/contract/clusterlistfetcher.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergkeonaws_contract - -import ( - "context" - "fmt" - "strings" - - "cloud.google.com/go/gkemulticloud/apiv1/gkemulticloudpb" - "github.com/GoogleCloudPlatform/khi/pkg/api/googlecloud" - coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" - "google.golang.org/api/iterator" -) - -// ClusterListFetcher fetches the list of GKE on AWS cluster in the project. -type ClusterListFetcher interface { - GetClusterNames(ctx context.Context, projectID string) ([]string, error) -} - -// ClusterListFetcherImpl is the default implementation of ClusterListFetcher. -type ClusterListFetcherImpl struct{} - -// GetClusterNames implements ClusterListFetcher. -// This expects the task googlecloudcommon_contract.APIClientFactoryTaskID is already resolved. -func (g *ClusterListFetcherImpl) GetClusterNames(ctx context.Context, projectID string) ([]string, error) { - cf := coretask.GetTaskResult(ctx, googlecloudcommon_contract.APIClientFactoryTaskID.Ref()) - injector := coretask.GetTaskResult(ctx, googlecloudcommon_contract.APIClientCallOptionsInjectorTaskID.Ref()) - - gkeMultiCloudAwsClient, err := cf.GKEMultiCloudAWSClustersClient(ctx, googlecloud.Project(projectID)) - if err != nil { - return nil, fmt.Errorf("failed to get the GKE on AWS client:%v", err) - } - defer gkeMultiCloudAwsClient.Close() - - ctx = injector.InjectToCallContext(ctx, googlecloud.Project(projectID)) - itr := gkeMultiCloudAwsClient.ListAwsClusters(ctx, &gkemulticloudpb.ListAwsClustersRequest{ - Parent: fmt.Sprintf("projects/%s/locations/-", projectID), - }) - - var result []string - for { - resp, err := itr.Next() - if err == iterator.Done { - break - } - if err != nil { - return nil, fmt.Errorf("failed to list GKE on AWS clusters: %v", err) - } - result = append(result, awsClusterToClusterName(resp)) - } - - return result, nil -} - -var _ ClusterListFetcher = (*ClusterListFetcherImpl)(nil) - -// awsClusterToClusterName returns the list of cluster names from the API response. -func awsClusterToClusterName(awsCluster *gkemulticloudpb.AwsCluster) string { - li := strings.LastIndex(awsCluster.Name, "/") - return awsCluster.Name[li+1:] -} diff --git a/pkg/task/inspection/googlecloudclustergkeonaws/contract/clusterlistfetcher_test.go b/pkg/task/inspection/googlecloudclustergkeonaws/contract/clusterlistfetcher_test.go deleted file mode 100644 index 7c0c4bfca..000000000 --- a/pkg/task/inspection/googlecloudclustergkeonaws/contract/clusterlistfetcher_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergkeonaws_contract - -import ( - "testing" - - "cloud.google.com/go/gkemulticloud/apiv1/gkemulticloudpb" -) - -func TestAwsClusterToClusterName(t *testing.T) { - testCases := []struct { - name string - cluster *gkemulticloudpb.AwsCluster - want string - }{ - { - name: "valid cluster name", - cluster: &gkemulticloudpb.AwsCluster{ - Name: "projects/123456/locations/us-west1/awsClusters/my-aws-cluster", - }, - want: "my-aws-cluster", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - if got := awsClusterToClusterName(tc.cluster); got != tc.want { - t.Errorf("awsClusterToClusterName() = %v, want %v", got, tc.want) - } - }) - } -} diff --git a/pkg/task/inspection/googlecloudclustergkeonaws/contract/taskid.go b/pkg/task/inspection/googlecloudclustergkeonaws/contract/taskid.go index 78e341093..21a42545c 100644 --- a/pkg/task/inspection/googlecloudclustergkeonaws/contract/taskid.go +++ b/pkg/task/inspection/googlecloudclustergkeonaws/contract/taskid.go @@ -22,11 +22,5 @@ import ( // ClusterGKEOnAWSTaskCommonPrefix is the task id prefix defined in googlecloudclustergkeonaws. var ClusterGKEOnAWSTaskCommonPrefix = googlecloudk8scommon_contract.GoogleCloudCommonK8STaskIDPrefix + "cluster/gke-on-aws/" -// AutocompleteGKEOnAWSClusterNamesTaskID is the task ID for listing up GKE on AWS cluster names. -var AutocompleteGKEOnAWSClusterNamesTaskID = taskid.NewImplementationID(googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID, "anthos-on-aws") - // ClusterNamePrefixTaskID is the task ID for the GKE on AWS cluster name prefix. var ClusterNamePrefixTaskID = taskid.NewImplementationID(googlecloudk8scommon_contract.ClusterNamePrefixTaskID, "gke-on-aws") - -// ClusterListFetcherTaskID is the task ID for getting ClusterListFetcher interface. -var ClusterListFetcherTaskID = taskid.NewDefaultImplementationID[ClusterListFetcher](ClusterGKEOnAWSTaskCommonPrefix + "cluster-list-fetcher") diff --git a/pkg/task/inspection/googlecloudclustergkeonaws/impl/autocomplete.go b/pkg/task/inspection/googlecloudclustergkeonaws/impl/autocomplete.go deleted file mode 100644 index ce0fa48ef..000000000 --- a/pkg/task/inspection/googlecloudclustergkeonaws/impl/autocomplete.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2024 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 -// -// http://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 googlecloudclustergkeonaws_impl - -import ( - "context" - "fmt" - "log/slog" - - inspectiontaskbase "github.com/GoogleCloudPlatform/khi/pkg/core/inspection/taskbase" - coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" - "github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid" - googlecloudclustergkeonaws_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergkeonaws/contract" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" - googlecloudk8scommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudk8scommon/contract" - inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract" -) - -// AutocompleteGKEOnAWSClusterNames is a task that provides a list of GKE on AWS cluster names for autocompletion. -var AutocompleteGKEOnAWSClusterNames = inspectiontaskbase.NewCachedTask(googlecloudclustergkeonaws_contract.AutocompleteGKEOnAWSClusterNamesTaskID, []taskid.UntypedTaskReference{ - googlecloudclustergkeonaws_contract.ClusterListFetcherTaskID.Ref(), - googlecloudcommon_contract.InputProjectIdTaskID.Ref(), -}, func(ctx context.Context, prevValue inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]) (inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList], error) { - projectID := coretask.GetTaskResult(ctx, googlecloudcommon_contract.InputProjectIdTaskID.Ref()) - if projectID != "" && projectID == prevValue.DependencyDigest { - return prevValue, nil - } - - if projectID != "" { - clusterListFetcher := coretask.GetTaskResult(ctx, googlecloudclustergkeonaws_contract.ClusterListFetcherTaskID.Ref()) - clusterNames, err := clusterListFetcher.GetClusterNames(ctx, projectID) - if err != nil { - slog.WarnContext(ctx, fmt.Sprintf("Failed to read the cluster names in the project %s\n%s", projectID, err)) - return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ - DependencyDigest: projectID, - Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ - ClusterNames: []string{}, - Error: "Failed to get the list from API", - }, - }, nil - } - return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ - DependencyDigest: projectID, - Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ - ClusterNames: clusterNames, - Error: "", - }, - }, nil - } - return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ - DependencyDigest: projectID, - Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ - ClusterNames: []string{}, - Error: "Project ID is empty", - }, - }, nil -}, inspectioncore_contract.InspectionTypeLabel(googlecloudclustergkeonaws_contract.InspectionTypeId)) diff --git a/pkg/task/inspection/googlecloudclustergkeonaws/impl/autocomplete_test.go b/pkg/task/inspection/googlecloudclustergkeonaws/impl/autocomplete_test.go deleted file mode 100644 index 5bc370d37..000000000 --- a/pkg/task/inspection/googlecloudclustergkeonaws/impl/autocomplete_test.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2024 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 -// -// http://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 googlecloudclustergkeonaws_impl - -import ( - "context" - "fmt" - "testing" - - inspectiontest "github.com/GoogleCloudPlatform/khi/pkg/core/inspection/test" - tasktest "github.com/GoogleCloudPlatform/khi/pkg/core/task/test" - googlecloudclustergkeonaws_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergkeonaws/contract" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" - googlecloudk8scommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudk8scommon/contract" - inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract" - "github.com/google/go-cmp/cmp" -) - -type mockGKEOnAWSClusterListFetcher struct { - responsePairs map[string][]string - responseWithError bool -} - -func (m *mockGKEOnAWSClusterListFetcher) GetClusterNames(ctx context.Context, projectID string) ([]string, error) { - if m.responseWithError { - return nil, fmt.Errorf("test error") - } - return m.responsePairs[projectID], nil -} - -var _ googlecloudclustergkeonaws_contract.ClusterListFetcher = (*mockGKEOnAWSClusterListFetcher)(nil) - -func TestAutocompleteGKEOnAWSClusterNames(t *testing.T) { - testCase := []struct { - desc string - clusterList map[string][]string - listError bool - projectIDs []string - want []*googlecloudk8scommon_contract.AutocompleteClusterNameList - }{ - { - desc: "project id is empty", - clusterList: map[string][]string{}, - projectIDs: []string{""}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{}, - Error: "Project ID is empty", - }, - }, - }, - { - desc: "multiple call for single project", - clusterList: map[string][]string{ - "foo": {"aws-cluster-1", "aws-cluster-2"}, - }, - projectIDs: []string{"foo", "foo"}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{"aws-cluster-1", "aws-cluster-2"}, - Error: "", - }, - { - ClusterNames: []string{"aws-cluster-1", "aws-cluster-2"}, - Error: "", - }, - }, - }, - { - desc: "multiple projects", - clusterList: map[string][]string{ - "foo": {"aws-cluster-1", "aws-cluster-2"}, - "bar": {"test-cluster-a", "test-cluster-b"}, - }, - projectIDs: []string{"foo", "bar"}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{"aws-cluster-1", "aws-cluster-2"}, - Error: "", - }, - { - ClusterNames: []string{"test-cluster-a", "test-cluster-b"}, - Error: "", - }, - }, - }, - { - desc: "with error", - clusterList: map[string][]string{}, - listError: true, - projectIDs: []string{"foo"}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{}, - Error: "Failed to get the list from API", - }, - }, - }, - } - - for _, tc := range testCase { - t.Run(tc.desc, func(t *testing.T) { - ctx := inspectiontest.WithDefaultTestInspectionTaskContext(t.Context()) - - mockClusterListFetcherInput := tasktest.NewTaskDependencyValuePair[googlecloudclustergkeonaws_contract.ClusterListFetcher](googlecloudclustergkeonaws_contract.ClusterListFetcherTaskID.Ref(), &mockGKEOnAWSClusterListFetcher{ - responsePairs: tc.clusterList, - responseWithError: tc.listError, - }) - for i := 0; i < len(tc.projectIDs); i++ { - projectIDInput := tasktest.NewTaskDependencyValuePair(googlecloudcommon_contract.InputProjectIdTaskID.Ref(), tc.projectIDs[i]) - result, _, err := inspectiontest.RunInspectionTask(ctx, AutocompleteGKEOnAWSClusterNames, inspectioncore_contract.TaskModeDryRun, map[string]any{}, projectIDInput, mockClusterListFetcherInput) - if err != nil { - t.Fatalf("failed to run inspection task in loop %d: %v", i, err) - } - - if diff := cmp.Diff(tc.want[i], result); diff != "" { - t.Errorf("result of AutocompleteGKEOnAWSClusterNames mismatch (-want +got):\n%s", diff) - } - } - }) - } -} diff --git a/pkg/task/inspection/googlecloudclustergkeonaws/impl/inject_task.go b/pkg/task/inspection/googlecloudclustergkeonaws/impl/inject_task.go deleted file mode 100644 index 438dbfbf5..000000000 --- a/pkg/task/inspection/googlecloudclustergkeonaws/impl/inject_task.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergkeonaws_impl - -import ( - "context" - - coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" - "github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid" - googlecloudclustergkeonaws_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergkeonaws/contract" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" -) - -// ClusterListFetcherTask is a task to inject ClusterListFetcher reference. -var ClusterListFetcherTask = coretask.NewTask( - googlecloudclustergkeonaws_contract.ClusterListFetcherTaskID, - []taskid.UntypedTaskReference{ - googlecloudcommon_contract.APIClientFactoryTaskID.Ref(), - googlecloudcommon_contract.APIClientCallOptionsInjectorTaskID.Ref(), - }, - func(ctx context.Context) (googlecloudclustergkeonaws_contract.ClusterListFetcher, error) { - return &googlecloudclustergkeonaws_contract.ClusterListFetcherImpl{}, nil - }, -) diff --git a/pkg/task/inspection/googlecloudclustergkeonaws/impl/registration.go b/pkg/task/inspection/googlecloudclustergkeonaws/impl/registration.go index 515100e13..48ffe8fe0 100644 --- a/pkg/task/inspection/googlecloudclustergkeonaws/impl/registration.go +++ b/pkg/task/inspection/googlecloudclustergkeonaws/impl/registration.go @@ -27,8 +27,6 @@ func Register(registry coreinspection.InspectionTaskRegistry) error { return err } return coretask.RegisterTasks(registry, - AutocompleteGKEOnAWSClusterNames, AnthosOnAWSClusterNamePrefixTask, - ClusterListFetcherTask, ) } diff --git a/pkg/task/inspection/googlecloudclustergkeonazure/contract/clusterlistfetcher.go b/pkg/task/inspection/googlecloudclustergkeonazure/contract/clusterlistfetcher.go deleted file mode 100644 index 674bb21d2..000000000 --- a/pkg/task/inspection/googlecloudclustergkeonazure/contract/clusterlistfetcher.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergkeonazure_contract - -import ( - "context" - "fmt" - "strings" - - "cloud.google.com/go/gkemulticloud/apiv1/gkemulticloudpb" - "github.com/GoogleCloudPlatform/khi/pkg/api/googlecloud" - coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" - "google.golang.org/api/iterator" -) - -// ClusterListFetcher fetches the list of GKE on Azure cluster in the project. -type ClusterListFetcher interface { - GetClusterNames(ctx context.Context, projectID string) ([]string, error) -} - -// ClusterListFetcherImpl is the default implementation of ClusterListFetcher. -type ClusterListFetcherImpl struct{} - -// GetClusterNames implements ClusterListFetcher. -// This expects the task googlecloudcommon_contract.APIClientFactoryTaskID is already resolved. -func (g *ClusterListFetcherImpl) GetClusterNames(ctx context.Context, projectID string) ([]string, error) { - cf := coretask.GetTaskResult(ctx, googlecloudcommon_contract.APIClientFactoryTaskID.Ref()) - injector := coretask.GetTaskResult(ctx, googlecloudcommon_contract.APIClientCallOptionsInjectorTaskID.Ref()) - - gkeMultiCloudAzureClient, err := cf.GKEMultiCloudAzureClustersClient(ctx, googlecloud.Project(projectID)) - if err != nil { - return nil, fmt.Errorf("failed to get the GKE on Azure client:%v", err) - } - defer gkeMultiCloudAzureClient.Close() - - ctx = injector.InjectToCallContext(ctx, googlecloud.Project(projectID)) - itr := gkeMultiCloudAzureClient.ListAzureClusters(ctx, &gkemulticloudpb.ListAzureClustersRequest{ - Parent: fmt.Sprintf("projects/%s/locations/-", projectID), - }) - - var result []string - for { - resp, err := itr.Next() - if err == iterator.Done { - break - } - if err != nil { - return nil, fmt.Errorf("failed to list GKE on Azure clusters: %v", err) - } - result = append(result, azureClusterToClusterName(resp)) - } - - return result, nil -} - -var _ ClusterListFetcher = (*ClusterListFetcherImpl)(nil) - -// azureClusterToClusterName returns the list of cluster names from the API response. -func azureClusterToClusterName(azureCluster *gkemulticloudpb.AzureCluster) string { - li := strings.LastIndex(azureCluster.Name, "/") - return azureCluster.Name[li+1:] -} diff --git a/pkg/task/inspection/googlecloudclustergkeonazure/contract/clusterlistfetcher_test.go b/pkg/task/inspection/googlecloudclustergkeonazure/contract/clusterlistfetcher_test.go deleted file mode 100644 index 387cbe4ef..000000000 --- a/pkg/task/inspection/googlecloudclustergkeonazure/contract/clusterlistfetcher_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergkeonazure_contract - -import ( - "testing" - - "cloud.google.com/go/gkemulticloud/apiv1/gkemulticloudpb" -) - -func TestAzureClusterToClusterName(t *testing.T) { - testCases := []struct { - name string - cluster *gkemulticloudpb.AzureCluster - want string - }{ - { - name: "valid cluster name", - cluster: &gkemulticloudpb.AzureCluster{ - Name: "projects/123456/locations/us-west1/azureClusters/my-azure-cluster", - }, - want: "my-azure-cluster", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - if got := azureClusterToClusterName(tc.cluster); got != tc.want { - t.Errorf("azureClusterToClusterName() = %v, want %v", got, tc.want) - } - }) - } -} diff --git a/pkg/task/inspection/googlecloudclustergkeonazure/contract/taskid.go b/pkg/task/inspection/googlecloudclustergkeonazure/contract/taskid.go index 794b1fde7..cb1cb98bd 100644 --- a/pkg/task/inspection/googlecloudclustergkeonazure/contract/taskid.go +++ b/pkg/task/inspection/googlecloudclustergkeonazure/contract/taskid.go @@ -22,11 +22,5 @@ import ( // ClusterGKEOnAzureTaskCommonPrefix is the task id prefix defined in googlecloudclustergkeonazure. var ClusterGKEOnAzureTaskCommonPrefix = googlecloudk8scommon_contract.GoogleCloudCommonK8STaskIDPrefix + "cluster/gke-on-azure/" -// AutocompleteGKEOnAzureClusterNamesTaskID is the task ID for listing up GKE on Azure cluster names. -var AutocompleteGKEOnAzureClusterNamesTaskID = taskid.NewImplementationID(googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID, "anthos-on-azure") - // ClusterNamePrefixTaskID is the task ID for the GKE on Azure cluster name prefix. var ClusterNamePrefixTaskID = taskid.NewImplementationID(googlecloudk8scommon_contract.ClusterNamePrefixTaskID, "gke-on-azure") - -// ClusterListFetcherTaskID is the task ID for getting ClusterListFetcher interface. -var ClusterListFetcherTaskID = taskid.NewDefaultImplementationID[ClusterListFetcher](ClusterGKEOnAzureTaskCommonPrefix + "cluster-list-fetcher") diff --git a/pkg/task/inspection/googlecloudclustergkeonazure/impl/autocomplete.go b/pkg/task/inspection/googlecloudclustergkeonazure/impl/autocomplete.go deleted file mode 100644 index 3babb68f9..000000000 --- a/pkg/task/inspection/googlecloudclustergkeonazure/impl/autocomplete.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2024 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 -// -// http://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 googlecloudclustergkeonazure_impl - -import ( - "context" - "fmt" - "log/slog" - - inspectiontaskbase "github.com/GoogleCloudPlatform/khi/pkg/core/inspection/taskbase" - coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" - "github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid" - googlecloudclustergkeonazure_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergkeonazure/contract" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" - googlecloudk8scommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudk8scommon/contract" - inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract" -) - -// AutocompleteGKEOnAzureClusterNamesTask is a task that provides a list of GKE on Azure cluster names for autocompletion. -var AutocompleteGKEOnAzureClusterNamesTask = inspectiontaskbase.NewCachedTask(googlecloudclustergkeonazure_contract.AutocompleteGKEOnAzureClusterNamesTaskID, []taskid.UntypedTaskReference{ - googlecloudclustergkeonazure_contract.ClusterListFetcherTaskID.Ref(), - googlecloudcommon_contract.InputProjectIdTaskID.Ref(), -}, func(ctx context.Context, prevValue inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]) (inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList], error) { - projectID := coretask.GetTaskResult(ctx, googlecloudcommon_contract.InputProjectIdTaskID.Ref()) - if projectID != "" && projectID == prevValue.DependencyDigest { - return prevValue, nil - } - - if projectID != "" { - clusterListFetcher := coretask.GetTaskResult(ctx, googlecloudclustergkeonazure_contract.ClusterListFetcherTaskID.Ref()) - clusterNames, err := clusterListFetcher.GetClusterNames(ctx, projectID) - if err != nil { - slog.WarnContext(ctx, fmt.Sprintf("Failed to read the cluster names in the project %s\n%s", projectID, err)) - return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ - DependencyDigest: projectID, - Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ - ClusterNames: []string{}, - Error: fmt.Sprintf("Failed to get the list from API:%s", err.Error()), - }, - }, nil - } - return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ - DependencyDigest: projectID, - Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ - ClusterNames: clusterNames, - Error: "", - }, - }, nil - } - return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ - DependencyDigest: projectID, - Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ - ClusterNames: []string{}, - Error: "Project ID is empty", - }, - }, nil -}, inspectioncore_contract.InspectionTypeLabel(googlecloudclustergkeonazure_contract.InspectionTypeId)) diff --git a/pkg/task/inspection/googlecloudclustergkeonazure/impl/autocomplete_test.go b/pkg/task/inspection/googlecloudclustergkeonazure/impl/autocomplete_test.go deleted file mode 100644 index e7883d93c..000000000 --- a/pkg/task/inspection/googlecloudclustergkeonazure/impl/autocomplete_test.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2024 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 -// -// http://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 googlecloudclustergkeonazure_impl - -import ( - "context" - "fmt" - "testing" - - inspectiontest "github.com/GoogleCloudPlatform/khi/pkg/core/inspection/test" - tasktest "github.com/GoogleCloudPlatform/khi/pkg/core/task/test" - googlecloudclustergkeonazure_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergkeonazure/contract" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" - googlecloudk8scommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudk8scommon/contract" - inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract" - "github.com/google/go-cmp/cmp" -) - -type mockGKEOnAzureClusterListFetcher struct { - responsePairs map[string][]string - responseWithError bool -} - -func (m *mockGKEOnAzureClusterListFetcher) GetClusterNames(ctx context.Context, projectID string) ([]string, error) { - if m.responseWithError { - return nil, fmt.Errorf("test error") - } - return m.responsePairs[projectID], nil -} - -var _ googlecloudclustergkeonazure_contract.ClusterListFetcher = (*mockGKEOnAzureClusterListFetcher)(nil) - -func TestAutocompleteGKEOnAzureClusterNames(t *testing.T) { - testCase := []struct { - desc string - clusterList map[string][]string - listError bool - projectIDs []string - want []*googlecloudk8scommon_contract.AutocompleteClusterNameList - }{ - { - desc: "project id is empty", - clusterList: map[string][]string{}, - projectIDs: []string{""}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{}, - Error: "Project ID is empty", - }, - }, - }, - { - desc: "multiple call for single project", - clusterList: map[string][]string{ - "foo": {"azure-cluster-1", "azure-cluster-2"}, - }, - projectIDs: []string{"foo", "foo"}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{"azure-cluster-1", "azure-cluster-2"}, - Error: "", - }, - { - ClusterNames: []string{"azure-cluster-1", "azure-cluster-2"}, - Error: "", - }, - }, - }, - { - desc: "multiple projects", - clusterList: map[string][]string{ - "foo": {"azure-cluster-1", "azure-cluster-2"}, - "bar": {"test-cluster-a", "test-cluster-b"}, - }, - projectIDs: []string{"foo", "bar"}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{"azure-cluster-1", "azure-cluster-2"}, - Error: "", - }, - { - ClusterNames: []string{"test-cluster-a", "test-cluster-b"}, - Error: "", - }, - }, - }, - { - desc: "with error", - clusterList: map[string][]string{}, - listError: true, - projectIDs: []string{"foo"}, - want: []*googlecloudk8scommon_contract.AutocompleteClusterNameList{ - { - ClusterNames: []string{}, - Error: "Failed to get the list from API:test error", - }, - }, - }, - } - - for _, tc := range testCase { - t.Run(tc.desc, func(t *testing.T) { - ctx := inspectiontest.WithDefaultTestInspectionTaskContext(t.Context()) - - mockClusterListFetcherInput := tasktest.NewTaskDependencyValuePair[googlecloudclustergkeonazure_contract.ClusterListFetcher](googlecloudclustergkeonazure_contract.ClusterListFetcherTaskID.Ref(), &mockGKEOnAzureClusterListFetcher{ - responsePairs: tc.clusterList, - responseWithError: tc.listError, - }) - for i := 0; i < len(tc.projectIDs); i++ { - projectIDInput := tasktest.NewTaskDependencyValuePair(googlecloudcommon_contract.InputProjectIdTaskID.Ref(), tc.projectIDs[i]) - result, _, err := inspectiontest.RunInspectionTask(ctx, AutocompleteGKEOnAzureClusterNamesTask, inspectioncore_contract.TaskModeDryRun, map[string]any{}, projectIDInput, mockClusterListFetcherInput) - if err != nil { - t.Fatalf("failed to run inspection task in loop %d: %v", i, err) - } - - if diff := cmp.Diff(tc.want[i], result); diff != "" { - t.Errorf("result of AutocompleteGKEOnAzureClusterNames mismatch (-want +got):\n%s", diff) - } - } - }) - } -} diff --git a/pkg/task/inspection/googlecloudclustergkeonazure/impl/inject_task.go b/pkg/task/inspection/googlecloudclustergkeonazure/impl/inject_task.go deleted file mode 100644 index 41e645071..000000000 --- a/pkg/task/inspection/googlecloudclustergkeonazure/impl/inject_task.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2025 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 -// -// http://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 googlecloudclustergkeonazure_impl - -import ( - "context" - - coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" - "github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid" - googlecloudclustergkeonazure_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudclustergkeonazure/contract" - googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" -) - -// ClusterListFetcherTask is a task to inject ClusterListFetcher reference. -var ClusterListFetcherTask = coretask.NewTask(googlecloudclustergkeonazure_contract.ClusterListFetcherTaskID, []taskid.UntypedTaskReference{ - googlecloudcommon_contract.APIClientFactoryTaskID.Ref(), -}, - func(ctx context.Context) (googlecloudclustergkeonazure_contract.ClusterListFetcher, error) { - return &googlecloudclustergkeonazure_contract.ClusterListFetcherImpl{}, nil - }) diff --git a/pkg/task/inspection/googlecloudclustergkeonazure/impl/registration.go b/pkg/task/inspection/googlecloudclustergkeonazure/impl/registration.go index 5fe3ed230..9c37339df 100644 --- a/pkg/task/inspection/googlecloudclustergkeonazure/impl/registration.go +++ b/pkg/task/inspection/googlecloudclustergkeonazure/impl/registration.go @@ -27,8 +27,6 @@ func Register(registry coreinspection.InspectionTaskRegistry) error { return err } return coretask.RegisterTasks(registry, - AutocompleteGKEOnAzureClusterNamesTask, AnthosOnAzureClusterNamePrefixTask, - ClusterListFetcherTask, ) } diff --git a/pkg/task/inspection/googlecloudk8scommon/contract/autocomplete.go b/pkg/task/inspection/googlecloudk8scommon/contract/autocomplete.go index ad9499bd6..89b28f5bb 100644 --- a/pkg/task/inspection/googlecloudk8scommon/contract/autocomplete.go +++ b/pkg/task/inspection/googlecloudk8scommon/contract/autocomplete.go @@ -18,4 +18,5 @@ package googlecloudk8scommon_contract type AutocompleteClusterNameList struct { ClusterNames []string Error string + Hint string } diff --git a/pkg/task/inspection/googlecloudk8scommon/contract/taskid.go b/pkg/task/inspection/googlecloudk8scommon/contract/taskid.go index 0a23ab2ab..9c911393e 100644 --- a/pkg/task/inspection/googlecloudk8scommon/contract/taskid.go +++ b/pkg/task/inspection/googlecloudk8scommon/contract/taskid.go @@ -23,9 +23,11 @@ import ( // GoogleCloudCommonK8STaskIDPrefix is the prefix for common task used for K8s on Google Cloud related tasks IDs. var GoogleCloudCommonK8STaskIDPrefix = "cloud.google.com/k8s/" -// AutocompleteClusterNamesTaskID is the task ID reference for returning cluster name candidates as AutocompleteClusterNameList. -// Each cluster types implement their own implementation for this task reference. -var AutocompleteClusterNamesTaskID = taskid.NewTaskReference[*AutocompleteClusterNameList](GoogleCloudCommonK8STaskIDPrefix + "autocomplete/cluster-names") +// AutocompleteClusterNamesMetricsTypeTaskID is the task ID for returning cluster name candidates as AutocompleteClusterNameList. +var AutocompleteClusterNamesMetricsTypeTaskID = taskid.NewDefaultImplementationID[string](GoogleCloudCommonK8STaskIDPrefix + "autocomplete/cluster-names/metrics-type") + +// AutocompleteClusterNamesTaskID is the task ID for returning cluster name candidates as AutocompleteClusterNameList. +var AutocompleteClusterNamesTaskID = taskid.NewDefaultImplementationID[*AutocompleteClusterNameList](GoogleCloudCommonK8STaskIDPrefix + "autocomplete/cluster-names") // HeaderSuggestedFileNameTaskID is the task ID for the suggested file name of the inspection file included in the header metadata. This name is used for the default name of downloaded file. var HeaderSuggestedFileNameTaskID = taskid.NewDefaultImplementationID[struct{}](GoogleCloudCommonK8STaskIDPrefix + "header-suggested-file-name") diff --git a/pkg/task/inspection/googlecloudk8scommon/impl/autocompleteclustername_task.go b/pkg/task/inspection/googlecloudk8scommon/impl/autocompleteclustername_task.go new file mode 100644 index 000000000..a59fe58a3 --- /dev/null +++ b/pkg/task/inspection/googlecloudk8scommon/impl/autocompleteclustername_task.go @@ -0,0 +1,99 @@ +// Copyright 2025 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 +// +// http://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 googlecloudk8scommon_impl + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/GoogleCloudPlatform/khi/pkg/api/googlecloud" + inspectiontaskbase "github.com/GoogleCloudPlatform/khi/pkg/core/inspection/taskbase" + coretask "github.com/GoogleCloudPlatform/khi/pkg/core/task" + "github.com/GoogleCloudPlatform/khi/pkg/core/task/taskid" + googlecloudcommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudcommon/contract" + googlecloudk8scommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudk8scommon/contract" +) + +// AutocompleteClusterNamesMetricsTypeTask is the task to provide the default metrics type to collect the cluster names. +// The resource type "k8s_container" must be available on the returned metrics type. +// This task is overriden in GKE clusters. +var AutocompleteClusterNamesMetricsTypeTask = coretask.NewTask(googlecloudk8scommon_contract.AutocompleteClusterNamesMetricsTypeTaskID, []taskid.UntypedTaskReference{}, func(ctx context.Context) (string, error) { + // logging.googleapis.com/log_entry_count is better from the perspective of KHI's purpose, but use container metrics for longer retention period(24 months). + return "kubernetes.io/anthos/container/uptime", nil +}) + +var AutocompleteClusterNamesTask = inspectiontaskbase.NewCachedTask(googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID, []taskid.UntypedTaskReference{ + googlecloudk8scommon_contract.ClusterNamePrefixTaskID, + googlecloudcommon_contract.InputProjectIdTaskID.Ref(), + googlecloudcommon_contract.InputStartTimeTaskID.Ref(), + googlecloudcommon_contract.InputEndTimeTaskID.Ref(), + googlecloudk8scommon_contract.AutocompleteClusterNamesMetricsTypeTaskID.Ref(), + googlecloudcommon_contract.APIClientFactoryTaskID.Ref(), + googlecloudcommon_contract.APIClientCallOptionsInjectorTaskID.Ref(), +}, func(ctx context.Context, prevValue inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]) (inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList], error) { + clusterNamePrefix := coretask.GetTaskResult(ctx, googlecloudk8scommon_contract.ClusterNamePrefixTaskID) + projectID := coretask.GetTaskResult(ctx, googlecloudcommon_contract.InputProjectIdTaskID.Ref()) + startTime := coretask.GetTaskResult(ctx, googlecloudcommon_contract.InputStartTimeTaskID.Ref()) + endTime := coretask.GetTaskResult(ctx, googlecloudcommon_contract.InputEndTimeTaskID.Ref()) + metricsType := coretask.GetTaskResult(ctx, googlecloudk8scommon_contract.AutocompleteClusterNamesMetricsTypeTaskID.Ref()) + cf := coretask.GetTaskResult(ctx, googlecloudcommon_contract.APIClientFactoryTaskID.Ref()) + optionInjector := coretask.GetTaskResult(ctx, googlecloudcommon_contract.APIClientCallOptionsInjectorTaskID.Ref()) + + currentDigest := fmt.Sprintf("%s-%s-%d-%d", clusterNamePrefix, projectID, startTime.Unix(), endTime.Unix()) + if projectID != "" && currentDigest == prevValue.DependencyDigest { + return prevValue, nil + } + + errorString := "" + hintString := "" + if endTime.Before(time.Now().Add(-time.Hour * 24 * 30 * 24)) { + hintString = "The end time is more than 24 months ago. Suggested cluster names may not be complete." + } + + client, err := cf.MonitoringMetricClient(ctx, googlecloud.Project(projectID)) + if err != nil { + return prevValue, fmt.Errorf("failed to create monitoring metric client: %w", err) + } + defer client.Close() + + ctx = optionInjector.InjectToCallContext(ctx, googlecloud.Project(projectID)) + filter := fmt.Sprintf(`metric.type="%s" AND resource.type="k8s_container"`, metricsType) + clusterNames, err := googlecloud.QueryDistinctLabelValuesFromMetrics(ctx, client, projectID, filter, startTime, endTime, "resource.label.cluster_name", "cluster_name") + if err != nil { + errorString = err.Error() + } + if clusterNamePrefix != "" { + filteredClusters := make([]string, 0, len(clusterNames)) + for _, clusterName := range clusterNames { + if strings.HasPrefix(clusterName, clusterNamePrefix) { + filteredClusters = append(filteredClusters, clusterName) + } + } + clusterNames = filteredClusters + } + if hintString == "" && errorString == "" && len(clusterNames) == 0 { + hintString = fmt.Sprintf("No cluster names found between %s and %s. It is highly likely that the time range is incorrect. Please verify the time range, or proceed by manually entering the cluster name.", startTime.Format(time.RFC3339), endTime.Format(time.RFC3339)) + } + return inspectiontaskbase.CacheableTaskResult[*googlecloudk8scommon_contract.AutocompleteClusterNameList]{ + DependencyDigest: currentDigest, + Value: &googlecloudk8scommon_contract.AutocompleteClusterNameList{ + ClusterNames: clusterNames, + Error: errorString, + Hint: hintString, + }, + }, nil +}) diff --git a/pkg/task/inspection/googlecloudk8scommon/impl/inputclustername_task.go b/pkg/task/inspection/googlecloudk8scommon/impl/inputclustername_task.go index fe805f604..226ca22c5 100644 --- a/pkg/task/inspection/googlecloudk8scommon/impl/inputclustername_task.go +++ b/pkg/task/inspection/googlecloudk8scommon/impl/inputclustername_task.go @@ -36,10 +36,10 @@ var clusterNameValidator = regexp.MustCompile(`^\s*[0-9a-z\-]+\s*$`) // This task return the cluster name with the prefixes defined from the cluster type. For example, a cluster named foo-cluster is `foo-cluster` in GKE but `awsCluster/foo-cluster` in GKE on AWS. // This input also supports autocomplete cluster names from some task having ID for googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID. var InputClusterNameTask = formtask.NewTextFormTaskBuilder(googlecloudk8scommon_contract.InputClusterNameTaskID, googlecloudcommon_contract.PriorityForResourceIdentifierGroup+4000, "Cluster name"). - WithDependencies([]taskid.UntypedTaskReference{googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID, googlecloudk8scommon_contract.ClusterNamePrefixTaskID}). + WithDependencies([]taskid.UntypedTaskReference{googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID.Ref(), googlecloudk8scommon_contract.ClusterNamePrefixTaskID}). WithDescription("The cluster name to gather logs."). WithDefaultValueFunc(func(ctx context.Context, previousValues []string) (string, error) { - clusters := coretask.GetTaskResult(ctx, googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID) + clusters := coretask.GetTaskResult(ctx, googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID.Ref()) // If the previous value is included in the list of cluster names, the name is used as the default value. if len(previousValues) > 0 && slices.Index(clusters.ClusterNames, previousValues[0]) > -1 { return previousValues[0], nil @@ -50,17 +50,20 @@ var InputClusterNameTask = formtask.NewTextFormTaskBuilder(googlecloudk8scommon_ return clusters.ClusterNames[0], nil }). WithSuggestionsFunc(func(ctx context.Context, value string, previousValues []string) ([]string, error) { - clusters := coretask.GetTaskResult(ctx, googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID) + clusters := coretask.GetTaskResult(ctx, googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID.Ref()) return common.SortForAutocomplete(value, clusters.ClusterNames), nil }). WithHintFunc(func(ctx context.Context, value string, convertedValue any) (string, inspectionmetadata.ParameterHintType, error) { - clusters := coretask.GetTaskResult(ctx, googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID) + clusters := coretask.GetTaskResult(ctx, googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID.Ref()) prefix := coretask.GetTaskResult(ctx, googlecloudk8scommon_contract.ClusterNamePrefixTaskID) // on failure of getting the list of clusters if clusters.Error != "" { return fmt.Sprintf("Failed to obtain the cluster list due to the error '%s'.\n The suggestion list won't popup", clusters.Error), inspectionmetadata.Warning, nil } + if clusters.Hint != "" { + return clusters.Hint, inspectionmetadata.Info, nil + } convertedWithoutPrefix := strings.TrimPrefix(convertedValue.(string), prefix) for _, suggestedCluster := range clusters.ClusterNames { if suggestedCluster == convertedWithoutPrefix { diff --git a/pkg/task/inspection/googlecloudk8scommon/impl/inputclustername_task_test.go b/pkg/task/inspection/googlecloudk8scommon/impl/inputclustername_task_test.go index 739477034..a4f1f7177 100644 --- a/pkg/task/inspection/googlecloudk8scommon/impl/inputclustername_task_test.go +++ b/pkg/task/inspection/googlecloudk8scommon/impl/inputclustername_task_test.go @@ -28,7 +28,7 @@ import ( func TestClusterNameInput(t *testing.T) { wantDescription := "The cluster name to gather logs." testClusterNamePrefix := tasktest.StubTaskFromReferenceID(googlecloudk8scommon_contract.ClusterNamePrefixTaskID, "", nil) - mockClusterNamesTask1 := tasktest.StubTaskFromReferenceID(googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID, &googlecloudk8scommon_contract.AutocompleteClusterNameList{ + mockClusterNamesTask1 := tasktest.StubTaskFromReferenceID(googlecloudk8scommon_contract.AutocompleteClusterNamesTaskID.Ref(), &googlecloudk8scommon_contract.AutocompleteClusterNameList{ ClusterNames: []string{"foo-cluster", "bar-cluster"}, Error: "", }, nil) diff --git a/pkg/task/inspection/googlecloudk8scommon/impl/registration.go b/pkg/task/inspection/googlecloudk8scommon/impl/registration.go index 570413d59..c82903d03 100644 --- a/pkg/task/inspection/googlecloudk8scommon/impl/registration.go +++ b/pkg/task/inspection/googlecloudk8scommon/impl/registration.go @@ -23,6 +23,8 @@ import ( func Register(registry coreinspection.InspectionTaskRegistry) error { return coretask.RegisterTasks(registry, HeaderSuggestedFileNameTask, + AutocompleteClusterNamesMetricsTypeTask, + AutocompleteClusterNamesTask, DefaultK8sResourceMergeConfigTask, InputClusterNameTask, InputKindFilterTask,