<commit_before>// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      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 filename

import (
	"fmt"
	"path"
	"strings"

	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
	iamapi "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/iam/v1beta1"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/cli/gcpclient"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/resourceskeleton"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"

	"github.com/ghodss/yaml"
	"github.com/gosimple/slug"
	tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

var refFields = []string{"projectRef", "folderRef", "organizationRef"}

func Get(u *unstructured.Unstructured, smLoader *servicemappingloader.ServiceMappingLoader, tfProvider *tfschema.Provider) (string, error) {
	if iamapi.IsHandwrittenIAM(u.GroupVersionKind()) {
		return getIAM(u, smLoader, tfProvider)
	}
	parentPrefix, err := getParentPrefix(u, smLoader)
	if err != nil {
		return "", fmt.Errorf("error getting parent for %v '%v': %w", u.GetKind(), u.GetName(), err)
	}
	location, err := getLocation(u)
	if err != nil {
		return "", fmt.Errorf("error getting location field for %v '%v': %v", u.GetKind(), u.GetName(), err)
	}
	return path.Join(parentPrefix, u.GetKind(), location, u.GetName()), nil
}

func getIAM(u *unstructured.Unstructured, smLoader *servicemappingloader.ServiceMappingLoader, tfProvider *tfschema.Provider) (string, error) {
	resourceRefField := "resourceRef"
	resourceRef, found, err := getFieldAsIAMResourceRef(u, resourceRefField)
	if err != nil {
		return "", err
	}
	if !found {
		return "", fmt.Errorf("%v '%v' does not contain required field '%v'", u.GetKind(), u.GetName(), resourceRefField)
	}
	// turn the import id into a resource and then get the path for it
	sm, err := smLoader.GetServiceMapping(resourceRef.GroupVersionKind().Group)
	if err != nil {
		return "", fmt.Errorf("error getting service mapping for %v: %w", resourceRef.GroupVersionKind().Group, err)
	}
	uri := fmt.Sprintf("//%v/%v", sm.Spec.ServiceHostName, resourceRef.External)
	parentSkel, err := resourceskeleton.NewFromURI(uri, smLoader, tfProvider)
	if err != nil {
		// not all resources support NewFromURI, example: ServiceAccount, for those, fall back to the external value
		// converted to a legal filename -- this could benefit from the same refactor that would enable not fetching
		// hierarchal resources again. Example for ServiceAccount: IAMPolicy/ServiceAccount/full-external-path-resource-name.yaml
		name := slug.Make(fmt.Sprintf("%v-%v", resourceRef.External, u.GetName()))
		return path.Join(u.GetKind(), resourceRef.Kind, name), nil
	}
	if isHierarchalKind(resourceRef.Kind) {
		// we do not have enough information in the external id to properly reconstruct a hierarchal resource so we need to fetch it again
		// this fetch could be avoided if we calculated the for a resource and its associated IAMPolicy at the same time,
		// but that will require more refactoring and the benefit would be low (it would mean less GCP api requests)
		parent, err := getResource(parentSkel, smLoader, tfProvider)
		if err != nil {
			return "", fmt.Errorf("error getting parent resource for ref '%v': %w", *resourceRef, err)
		}
		parentSkel = parent
	}
	parentPath, err := Get(parentSkel, smLoader, tfProvider)
	if err != nil {
		return "", fmt.Errorf("error getting parent path for resource ref '%v': %w", *resourceRef, err)
	}
	//replace the last element of the path with the name of this iam resource
	lastSlashIdx := strings.LastIndex(parentPath, "/")
	if lastSlashIdx < 0 {
		return "", fmt.Errorf("invalid parent path '%v': does not contain a '/' character", parentPath)
	}
	return path.Join(parentPath[0:lastSlashIdx], u.GetName()), nil
}

func isHierarchalKind(kind string) bool {
	return kind == "Folder" || kind == "Project" || kind == "Organization"
}

func getResource(u *unstructured.Unstructured, smLoader *servicemappingloader.ServiceMappingLoader, tfProvider *tfschema.Provider) (*unstructured.Unstructured, error) {
	client := gcpclient.New(tfProvider, smLoader)
	return client.Get(u)
}

func getParentPrefix(u *unstructured.Unstructured, smLoader *servicemappingloader.ServiceMappingLoader) (string, error) {
	parentRefs, err := getParentResourceReferences(u, smLoader)
	if err != nil {
		return "", err
	}
	if err != nil {
		return "", err
	}
	if len(parentRefs) == 0 {
		return getHierarchalParentPath(u)
	}
	// the assumption there can only be a single parent filled in at any time is carried over from tf/controller.go
	// so the loop terminates if a parent is found
	for _, refConfig := range parentRefs {
		resourceRef, found, err := getFieldAsKCCResourceRef(u, refConfig.Key)
		if err != nil {
			return "", err
		}
		if !found {
			continue
		}
		// it is assumed only external references are being used as that is all that is supported by the CLI
		external := trimExternalRef(resourceRef.External)
		if external == "" {
			return "", fmt.Errorf("field '%v.external' contains an unexpected empty value in spec.%v '%v'",
				refConfig.Key, u.GetKind(), u.GetName())
		}
		if !isHierarchalQualifier(strings.Split(external, "/")[0]) {
			hierarchalParent, err := getHierarchalParentPath(u)
			if err != nil {
				return "", err
			}
			external = fmt.Sprintf("%v/%v", hierarchalParent, external)<commit_msg>Automated Config Connector import.<commit_after>// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      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 filename

import (
	"fmt"
	"path"
	"strings"

	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
	iamapi "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/iam/v1beta1"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/cli/gcpclient"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/resourceskeleton"
	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"

	"github.com/ghodss/yaml"
	"github.com/gosimple/slug"
	tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

var refFields = []string{"projectRef", "folderRef", "organizationRef"}

func Get(u *unstructured.Unstructured, smLoader *servicemappingloader.ServiceMappingLoader, tfProvider *tfschema.Provider) (string, error) {
	if iamapi.IsHandwrittenIAM(u.GroupVersionKind()) {
		return getIAM(u, smLoader, tfProvider)
	}
	parentPrefix, err := getParentPrefix(u, smLoader)
	if err != nil {
		return "", fmt.Errorf("error getting parent for %v '%v': %w", u.GetKind(), u.GetName(), err)
	}
	location, err := getLocation(u)
	if err != nil {
		return "", fmt.Errorf("error getting location field for %v '%v': %v", u.GetKind(), u.GetName(), err)
	}
	return path.Join(parentPrefix, u.GetKind(), location, u.GetName()), nil
}

func getIAM(u *unstructured.Unstructured, smLoader *servicemappingloader.ServiceMappingLoader, tfProvider *tfschema.Provider) (string, error) {
	resourceRefField := "resourceRef"
	resourceRef, found, err := getFieldAsIAMResourceRef(u, resourceRefField)
	if err != nil {
		return "", err
	}
	if !found {
		return "", fmt.Errorf("%v '%v' does not contain required field '%v'", u.GetKind(), u.GetName(), resourceRefField)
	}
	// turn the import id into a resource and then get the path for it
	sm, err := smLoader.GetServiceMapping(resourceRef.GroupVersionKind().Group)
	if err != nil {
		return "", fmt.Errorf("error getting service mapping for %v: %w", resourceRef.GroupVersionKind().Group, err)
	}
	uri := fmt.Sprintf("//%v/%v", sm.Spec.ServiceHostName, resourceRef.External)
	parentSkel, err := resourceskeleton.NewFromURI(uri, smLoader, tfProvider)
	if err != nil {
		// not all resources support NewFromURI, example: ServiceAccount, for those, fall back to the external value
		// converted to a legal filename -- this could benefit from the same refactor that would enable not fetching
		// hierarchal resources again. Example for ServiceAccount: IAMPolicy/ServiceAccount/full-external-path-resource-name.yaml
		name := slug.Make(fmt.Sprintf("%v-%v", resourceRef.External, u.GetName()))
		return path.Join(u.GetKind(), resourceRef.Kind, name), nil
	}
	if isHierarchalKind(resourceRef.Kind) {
		// we do not have enough information in the external id to properly reconstruct a hierarchal resource so we need to fetch it again
		// this fetch could be avoided if we calculated the for a resource and its associated IAMPolicy at the same time,
		// but that will require more refactoring and the benefit would be low (it would mean less GCP api requests)
		parent, err := getResource(parentSkel, smLoader, tfProvider)
		if err != nil {
			return "", fmt.Errorf("error getting parent resource for ref '%v': %w", *resourceRef, err)
		}
		parentSkel = parent
	}
	parentPath, err := Get(parentSkel, smLoader, tfProvider)
	if err != nil {
		return "", fmt.Errorf("error getting parent path for resource ref '%v': %w", *resourceRef, err)
	}
	//replace the last element of the path with the name of this iam resource
	lastSlashIdx := strings.LastIndex(parentPath, "/")
	if lastSlashIdx < 0 {
		return "", fmt.Errorf("invalid parent path '%v': does not contain a '/' character", parentPath)
	}
	return path.Join(parentPath[0:lastSlashIdx], u.GetName()), nil
}

func isHierarchalKind(kind string) bool {
	return kind == "Folder" || kind == "Project" || kind == "Organization"
}

func getResource(u *unstructured.Unstructured, smLoader *servicemappingloader.ServiceMappingLoader, tfProvider *tfschema.Provider) (*unstructured.Unstructured, error) {
	client := gcpclient.New(tfProvider, smLoader)
	return client.Get(u)
}

func getParentPrefix(u *unstructured.Unstructured, smLoader *servicemappingloader.ServiceMappingLoader) (string, error) {
	parentRefs, err := getParentResourceReferences(u, smLoader)
	if err != nil {
		return "", err
	}
	if len(parentRefs) == 0 {
		return getHierarchalParentPath(u)
	}
	// the assumption there can only be a single parent filled in at any time is carried over from tf/controller.go
	// so the loop terminates if a parent is found
	for _, refConfig := range parentRefs {
		resourceRef, found, err := getFieldAsKCCResourceRef(u, refConfig.Key)
		if err != nil {
			return "", err
		}
		if !found {
			continue
		}
		// it is assumed only external references are being used as that is all that is supported by the CLI
		external := trimExternalRef(resourceRef.External)
		if external == "" {
			return "", fmt.Errorf("field '%v.external' contains an unexpected empty value in spec.%v '%v'",
				refConfig.Key, u.GetKind(), u.GetName())
		}
		if !isHierarchalQualifier(strings.Split(external, "/")[0]) {
			hierarchalParent, err := getHierarchalParentPath(u)
			if err != nil {
				return "", err
			}
			external = fmt.Sprintf("%v/%v", hierarchalParent, external)
{'fraction_non_alphanumeric': 0.1217535153019024, 'fraction_numerical': 0.004301075268817204, 'mean_word_length': 4.587338262476894, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6599443', 'n_tokens_mistral': 3878, 'n_tokens_neox': 3695, 'n_words': 1147}