...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader/servicemappingloader.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package servicemappingloader
    16  
    17  import (
    18  	"fmt"
    19  	"sort"
    20  
    21  	"github.com/GoogleCloudPlatform/k8s-config-connector/config/servicemappings"
    22  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
    23  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gcp"
    24  	autogenloader "github.com/GoogleCloudPlatform/k8s-config-connector/scripts/resource-autogen/servicemapping/servicemappingloader"
    25  
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	"k8s.io/apimachinery/pkg/runtime/schema"
    28  	"sigs.k8s.io/yaml"
    29  )
    30  
    31  type ServiceMappingLoader struct {
    32  	groupToSM           map[string]v1alpha1.ServiceMapping
    33  	serviceHostNameToSM map[string]v1alpha1.ServiceMapping
    34  }
    35  
    36  func New() (*ServiceMappingLoader, error) {
    37  	serviceMappings, err := GetServiceMappings()
    38  	if err != nil {
    39  		return nil, fmt.Errorf("error loading service mappings: %v", err)
    40  	}
    41  	return NewFromServiceMappings(serviceMappings), nil
    42  }
    43  
    44  func NewFromServiceMappings(serviceMappings []v1alpha1.ServiceMapping) *ServiceMappingLoader {
    45  	groupToSM := make(map[string]v1alpha1.ServiceMapping)
    46  	serviceHostNameToSM := make(map[string]v1alpha1.ServiceMapping)
    47  	for _, sm := range serviceMappings {
    48  		groupToSM[sm.Name] = sm
    49  		serviceHostNameToSM[sm.Spec.ServiceHostName] = sm
    50  	}
    51  	loader := ServiceMappingLoader{
    52  		groupToSM:           groupToSM,
    53  		serviceHostNameToSM: serviceHostNameToSM,
    54  	}
    55  	return &loader
    56  }
    57  
    58  func (s *ServiceMappingLoader) GetServiceMapping(name string) (*v1alpha1.ServiceMapping, error) {
    59  	sm, ok := s.groupToSM[name]
    60  	if !ok {
    61  		return nil, fmt.Errorf("unable to get service mapping: no mapping with name '%v' found", name)
    62  	}
    63  	return &sm, nil
    64  }
    65  
    66  func (s *ServiceMappingLoader) GetServiceMappingForServiceHostName(hostName string) (*v1alpha1.ServiceMapping, error) {
    67  	sm, ok := s.serviceHostNameToSM[hostName]
    68  	if !ok {
    69  		return nil, fmt.Errorf("unable to get service mapping: no mapping with service host name '%v' found", hostName)
    70  	}
    71  	return &sm, nil
    72  }
    73  
    74  func (s *ServiceMappingLoader) GetServiceMappings() []v1alpha1.ServiceMapping {
    75  	serviceMappings := make([]v1alpha1.ServiceMapping, 0, len(s.groupToSM))
    76  	for _, v := range s.groupToSM {
    77  		serviceMappings = append(serviceMappings, v)
    78  	}
    79  	return serviceMappings
    80  }
    81  
    82  func (s *ServiceMappingLoader) GetResourceConfig(u *unstructured.Unstructured) (*v1alpha1.ResourceConfig, error) {
    83  	sm, err := s.GetServiceMapping(u.GroupVersionKind().Group)
    84  	if err != nil {
    85  		return nil, fmt.Errorf("error getting service mapping: %v", err)
    86  	}
    87  	return GetResourceConfig(sm, u)
    88  }
    89  
    90  // a single GVK can be associated with multiple ResourceConfigs because some resources have both Global and Regional ResourceConfigs
    91  func (s *ServiceMappingLoader) GetResourceConfigs(gvk schema.GroupVersionKind) ([]*v1alpha1.ResourceConfig, error) {
    92  	sm, err := s.GetServiceMapping(gvk.Group)
    93  	if err != nil {
    94  		return nil, fmt.Errorf("error getting service mapping for group '%v': %v", gvk.Group, err)
    95  	}
    96  	return GetResourceConfigsForKind(sm, gvk.Kind), nil
    97  }
    98  
    99  func (s *ServiceMappingLoader) GetAutoGenOnlyGroups() map[string]bool {
   100  	autoGenOnlyServices := make(map[string]bool)
   101  	for group, sm := range s.groupToSM {
   102  		if len(sm.Spec.Resources) == 0 {
   103  			continue
   104  		}
   105  
   106  		hasManualResources := false
   107  		for _, rc := range sm.Spec.Resources {
   108  			if !rc.AutoGenerated {
   109  				hasManualResources = true
   110  				break
   111  			}
   112  		}
   113  		if !hasManualResources {
   114  			autoGenOnlyServices[group] = true
   115  		}
   116  	}
   117  	return autoGenOnlyServices
   118  }
   119  
   120  func GetResourceConfig(sm *v1alpha1.ServiceMapping, u *unstructured.Unstructured) (*v1alpha1.ResourceConfig, error) {
   121  	rcs := GetResourceConfigsForKind(sm, u.GetKind())
   122  	if len(rcs) == 0 {
   123  		return nil, fmt.Errorf("couldn't find any ResourceConfig defined for kind %v", u.GetKind())
   124  	}
   125  	if len(rcs) == 1 {
   126  		return rcs[0], nil
   127  	}
   128  
   129  	// Special scenario: Compute Instance
   130  	if u.GetKind() == "ComputeInstance" {
   131  		return getComputeInstanceTFResource(u, rcs)
   132  	}
   133  
   134  	l, err := getLocationalityOfResource(u)
   135  	if err != nil {
   136  		return nil, fmt.Errorf("couldn't find the right ResourceConfig for Kind %v: %v", u.GetKind(), err)
   137  	}
   138  	for _, rc := range rcs {
   139  		if rc.Locationality == l {
   140  			return rc, nil
   141  		}
   142  	}
   143  	return nil, fmt.Errorf("couldn't find the right ResourceConfig for Kind %v given the locationality of the resource - %v: %v", u.GetKind(), l, err)
   144  }
   145  
   146  func GetResourceConfigsForKind(sm *v1alpha1.ServiceMapping, kind string) []*v1alpha1.ResourceConfig {
   147  	rcs := make([]*v1alpha1.ResourceConfig, 0)
   148  	for _, r := range sm.Spec.Resources {
   149  		r := r
   150  		if r.Kind == kind {
   151  			rcs = append(rcs, &r)
   152  		}
   153  	}
   154  	return rcs
   155  }
   156  
   157  func GetResourceConfigsForTFType(sm *v1alpha1.ServiceMapping, tfType string) (*v1alpha1.ResourceConfig, error) {
   158  	for _, r := range sm.Spec.Resources {
   159  		r := r
   160  		if r.Name == tfType {
   161  			return &r, nil
   162  		}
   163  	}
   164  	return nil, fmt.Errorf("can't find resource config for TF type '%v' in group '%v'", tfType, sm.Name)
   165  }
   166  
   167  func getLocationalityOfResource(u *unstructured.Unstructured) (string, error) {
   168  	location, found, err := unstructured.NestedString(u.Object, "spec", "location")
   169  	if err != nil || !found {
   170  		return "", fmt.Errorf("couldn't find location field: %v", err)
   171  	}
   172  
   173  	if location == gcp.Global {
   174  		return gcp.Global, nil
   175  	}
   176  
   177  	if gcp.IsLocationRegional(location) {
   178  		return gcp.Regional, nil
   179  	}
   180  
   181  	if gcp.IsLocationZonal(location) {
   182  		return gcp.Zonal, nil
   183  	}
   184  	return "", fmt.Errorf("location is neither global nor regional nor zonal")
   185  }
   186  
   187  func GetServiceMappings() ([]v1alpha1.ServiceMapping, error) {
   188  	keys, err := servicemappings.AllKeys()
   189  	if err != nil {
   190  		return nil, fmt.Errorf("error listing servicemappings: %w", err)
   191  	}
   192  
   193  	autoGenSMMap, err := autogenloader.GetServiceMappingMap()
   194  	if err != nil {
   195  		return nil, fmt.Errorf("error getting auto-generated service mappings: %w", err)
   196  	}
   197  
   198  	serviceMappings := make([]v1alpha1.ServiceMapping, 0)
   199  	for _, key := range keys {
   200  		sm, err := fileToServiceMapping(key)
   201  		if err != nil {
   202  			return nil, err
   203  		}
   204  
   205  		// Merge autogen ResourceConfigs into the existent ServiceMapping
   206  		// configured manually.
   207  		if autoGenSM, ok := autoGenSMMap[sm.Name]; ok {
   208  			rcMap := make(map[string]bool)
   209  			for _, rc := range sm.Spec.Resources {
   210  				rcMap[rc.Name] = true
   211  			}
   212  
   213  			for _, rc := range autoGenSM.Spec.Resources {
   214  				if _, ok := rcMap[rc.Name]; !ok {
   215  					sm.Spec.Resources = append(sm.Spec.Resources, rc)
   216  				}
   217  			}
   218  
   219  			sort.Slice(sm.Spec.Resources, func(i, j int) bool {
   220  				return sm.Spec.Resources[i].Name < sm.Spec.Resources[j].Name
   221  			})
   222  
   223  			delete(autoGenSMMap, sm.Name)
   224  		}
   225  
   226  		serviceMappings = append(serviceMappings, *sm)
   227  	}
   228  
   229  	for _, autoGenSM := range autoGenSMMap {
   230  		serviceMappings = append(serviceMappings, autoGenSM)
   231  	}
   232  
   233  	return serviceMappings, nil
   234  }
   235  
   236  func fileToServiceMapping(key string) (*v1alpha1.ServiceMapping, error) {
   237  	b, err := servicemappings.ServiceMapping(key)
   238  	if err != nil {
   239  		return nil, fmt.Errorf("error reading servicemapping %q: %w", key, err)
   240  	}
   241  	sm, err := parseServiceMapping(b)
   242  	if err != nil {
   243  		return nil, fmt.Errorf("error parsing %q to service mapping: %w", key, err)
   244  	}
   245  	return sm, nil
   246  }
   247  
   248  func parseServiceMapping(b []byte) (*v1alpha1.ServiceMapping, error) {
   249  	var sm v1alpha1.ServiceMapping
   250  	if err := yaml.UnmarshalStrict(b, &sm); err != nil {
   251  		return nil, fmt.Errorf("error unmarshaling byte to service mapping: %w", err)
   252  	}
   253  	return &sm, nil
   254  }
   255  
   256  func getComputeInstanceTFResource(u *unstructured.Unstructured, rcs []*v1alpha1.ResourceConfig) (*v1alpha1.ResourceConfig, error) {
   257  	// If the configuration defines the instanceTemplateRef, use TF resource compute_instance_from_template.
   258  	// Otherwise, use TF resource compute_instance.
   259  	_, hasTemplate, err := unstructured.NestedMap(u.Object, "spec", "instanceTemplateRef")
   260  	if err != nil {
   261  		return nil, fmt.Errorf("instanceTemplateRef should be a map in Kind %v: %v", u.GetKind(), err)
   262  	}
   263  	for _, rc := range rcs {
   264  		if rc.Name == "google_compute_instance_from_template" && hasTemplate {
   265  			return rc, nil
   266  		}
   267  		if rc.Name == "google_compute_instance" && !hasTemplate {
   268  			return rc, nil
   269  		}
   270  	}
   271  
   272  	return nil, fmt.Errorf("couldn't find the right ResourceConfig for Kind %v: %v", u.GetKind(), err)
   273  }
   274  

View as plain text