sigs.k8s.io/cluster-api@v1.6.3/cmd/clusterctl/hack/create-local-repository.py (about)

     1  #!/usr/bin/env python3
     2  
     3  # Copyright 2020 The Kubernetes Authors.
     4  #
     5  # Licensed under the Apache License, Version 2.0 (the "License");
     6  # you may not use this file except in compliance with the License.
     7  # You may obtain a copy of the License at
     8  #
     9  #     http://www.apache.org/licenses/LICENSE-2.0
    10  #
    11  # Unless required by applicable law or agreed to in writing, software
    12  # distributed under the License is distributed on an "AS IS" BASIS,
    13  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  # See the License for the specific language governing permissions and
    15  # limitations under the License.
    16  
    17  ###################
    18  
    19  # create-local-repository.py takes in input a list of provider and, for each of them, generates the components YAML from the
    20  # local repositories (the GitHub repositories clone), and finally stores it in the clusterctl local override folder
    21  
    22  # prerequisites:
    23  
    24  # - the script should be executed from sigs.k8s.io/cluster-api/ by calling cmd/clusterctl/hack/create-local-repository.py
    25  # - there should be a sigs.k8s.io/cluster-api/clusterctl-settings.json file with the list of provider for which
    26  #   the local overrides should be generated and the list of provider repositories to be included (on top of cluster-api).
    27  # {
    28  #    "providers": [ "cluster-api", "bootstrap-kubeadm", "infrastructure-aws"],
    29  #    "provider_repos": ["../cluster-api-provider-aws"]
    30  # }
    31  # - for each additional provider repository there should be a sigs.k8s.io/<provider_repo>/clusterctl-settings.json file e.g.
    32  # {
    33  #   "name": "infrastructure-aws",
    34  #   "config": {
    35  #     "componentsFile": "infrastructure-components.yaml",
    36  #     "nextVersion": "v0.5.0",
    37  # }
    38  
    39  ###################
    40  
    41  from __future__ import unicode_literals
    42  
    43  import errno
    44  import json
    45  import os
    46  import subprocess
    47  from distutils.dir_util import copy_tree
    48  from distutils.file_util import copy_file
    49  
    50  settings = {}
    51  
    52  providers = {
    53      'cluster-api': {
    54          'componentsFile': 'core-components.yaml',
    55          'nextVersion': 'v1.6.99',
    56          'type': 'CoreProvider',
    57      },
    58      'bootstrap-kubeadm': {
    59          'componentsFile': 'bootstrap-components.yaml',
    60          'nextVersion': 'v1.6.99',
    61          'type': 'BootstrapProvider',
    62          'configFolder': 'bootstrap/kubeadm/config/default',
    63      },
    64      'control-plane-kubeadm': {
    65          'componentsFile': 'control-plane-components.yaml',
    66          'nextVersion': 'v1.6.99',
    67          'type': 'ControlPlaneProvider',
    68          'configFolder': 'controlplane/kubeadm/config/default',
    69      },
    70      'infrastructure-docker': {
    71          'componentsFile': 'infrastructure-components.yaml',
    72          'nextVersion': 'v1.6.99',
    73          'type': 'InfrastructureProvider',
    74          'configFolder': 'test/infrastructure/docker/config/default',
    75      },
    76      'infrastructure-in-memory': {
    77            'componentsFile': 'infrastructure-components.yaml',
    78            'nextVersion': 'v1.6.99',
    79            'type': 'InfrastructureProvider',
    80            'configFolder': 'test/infrastructure/inmemory/config/default',
    81        },
    82        'runtime-extension-test': {
    83          'componentsFile': 'runtime-extension-components.yaml',
    84          'nextVersion': 'v1.6.99',
    85          'type': 'RuntimeExtensionProvider',
    86          'configFolder': 'test/extension/config/default',
    87      },
    88  }
    89  
    90  
    91  def load_settings():
    92      global settings
    93      try:
    94          settings = json.load(open('clusterctl-settings.json'))
    95      except  Exception as e:
    96          raise Exception('failed to load clusterctl-settings.json: {}'.format(e))
    97  
    98  
    99  def load_providers():
   100      provider_repos = settings.get('provider_repos', [])
   101      for repo in provider_repos:
   102          file = repo + '/clusterctl-settings.json'
   103          try:
   104              provider_details = json.load(open(file))
   105              provider_name = provider_details['name']
   106              provider_config = provider_details['config']
   107              provider_config['repo'] = repo
   108              providers[provider_name] = provider_config
   109          except  Exception as e:
   110              raise Exception('failed to load clusterctl-settings.json from repo {}: {}'.format(repo, e))
   111  
   112  
   113  def execCmd(args):
   114      try:
   115          out = subprocess.Popen(args,
   116                                 stdout=subprocess.PIPE,
   117                                 stderr=subprocess.STDOUT)
   118  
   119          stdout, stderr = out.communicate()
   120          if stderr is not None:
   121              raise Exception('stderr contains: \n{}'.format(stderr))
   122  
   123          return stdout
   124      except Exception as e:
   125          raise Exception('failed to run {}: {}'.format(args, e))
   126  
   127  
   128  def get_repository_folder():
   129      config_dir = os.getenv("XDG_CONFIG_HOME", "")
   130      if config_dir == "":
   131          home_dir = os.getenv("HOME", "")
   132          if home_dir == "":
   133              raise Exception('HOME variable is not set')
   134          config_dir = os.path.join(home_dir, ".config")
   135      return os.path.join(config_dir, 'cluster-api', 'dev-repository')
   136  
   137  
   138  def write_local_repository(provider, version, components_file, components_yaml, metadata_file):
   139      try:
   140          repository_folder = get_repository_folder()
   141          provider_folder = os.path.join(repository_folder, provider, version)
   142          try:
   143              os.makedirs(provider_folder)
   144          except OSError as e:
   145              if e.errno != errno.EEXIST:
   146                  raise
   147          components_path = os.path.join(provider_folder, components_file)
   148          f = open(components_path, 'wb')
   149          f.write(components_yaml)
   150          f.close()
   151  
   152          copy_file(metadata_file, provider_folder)
   153  
   154          if provider == "infrastructure-docker":
   155              copy_tree("test/infrastructure/docker/templates", provider_folder)
   156  
   157          if provider == "infrastructure-in-memory":
   158              copy_tree("test/infrastructure/inmemory/templates", provider_folder)
   159  
   160          return components_path
   161      except Exception as e:
   162          raise Exception('failed to write {} to {}: {}'.format(components_file, provider_folder, e))
   163  
   164  
   165  def create_local_repositories():
   166      providerList = settings.get('providers', [])
   167      assert providerList is not None, 'invalid configuration: please define the list of providers to override'
   168      assert len(providerList)>0, 'invalid configuration: please define at least one provider to override'
   169  
   170      for provider in providerList:
   171          p = providers.get(provider)
   172          assert p is not None, 'invalid configuration: please specify the configuration for the {} provider'.format(
   173              provider)
   174  
   175          repo = p.get('repo', '.')
   176          config_folder = p.get('configFolder', 'config/default')
   177          metadata_file = repo + '/metadata.yaml'
   178  
   179          next_version = p.get('nextVersion')
   180          assert next_version is not None, 'invalid configuration for provider {}: please provide nextVersion value'.format(
   181              provider)
   182  
   183          name, type = splitNameAndType(provider)
   184          assert name is not None, 'invalid configuration for provider {}: please use a valid provider label'.format(
   185              provider)
   186  
   187          components_file = p.get('componentsFile')
   188          assert components_file is not None, 'invalid configuration for provider {}: please provide componentsFile value'.format(
   189              provider)
   190  
   191          execCmd(['make', 'kustomize'])
   192          components_yaml = execCmd(['./hack/tools/bin/kustomize', 'build', os.path.join(repo, config_folder)])
   193          components_path = write_local_repository(provider, next_version, components_file, components_yaml,
   194                                                   metadata_file)
   195  
   196          yield name, type, next_version, components_path
   197  
   198  
   199  def injectLatest(path):
   200      head, tail = os.path.split(path)
   201      return '{}/latest/{}'.format(head, tail)
   202  
   203  
   204  def create_dev_config(repos):
   205      yaml = "providers:\n"
   206      for name, type, next_version, components_path in repos:
   207          yaml += "- name: \"{}\"\n".format(name)
   208          yaml += "  type: \"{}\"\n".format(type)
   209          yaml += "  url: \"{}\"\n".format(components_path)
   210      yaml += "overridesFolder: \"{}/overrides\"\n".format(get_repository_folder())
   211  
   212      try:
   213          repository_folder = get_repository_folder()
   214          config_path = os.path.join(repository_folder, "config.yaml")
   215          f = open(config_path, 'w')
   216          f.write(yaml)
   217          f.close()
   218          return components_path
   219      except Exception as e:
   220          raise Exception('failed to write {}: {}'.format(config_path, e))
   221  
   222  
   223  def splitNameAndType(provider):
   224      if provider == 'cluster-api':
   225          return 'cluster-api', 'CoreProvider'
   226      if provider.startswith('bootstrap-'):
   227          return provider[len('bootstrap-'):], 'BootstrapProvider'
   228      if provider.startswith('control-plane-'):
   229          return provider[len('control-plane-'):], 'ControlPlaneProvider'
   230      if provider.startswith('infrastructure-'):
   231          return provider[len('infrastructure-'):], 'InfrastructureProvider'
   232      if provider.startswith('ipam-'):
   233          return provider[len('ipam-'):], 'IPAMProvider'
   234      if provider.startswith('runtime-extension-'):
   235          return provider[len('runtime-extension-'):], 'RuntimeExtensionProvider'
   236      if provider.startswith('addon-'):
   237          return provider[len('addon-'):], 'AddonProvider'
   238      return None, None
   239  
   240  
   241  def CoreProviderFlag():
   242      return '--core'
   243  
   244  
   245  def BootstrapProviderFlag():
   246      return '--bootstrap'
   247  
   248  
   249  def ControlPlaneProviderFlag():
   250      return '--control-plane'
   251  
   252  
   253  def InfrastructureProviderFlag():
   254      return '--infrastructure'
   255  
   256  
   257  def IPAMProviderFlag():
   258      return '--ipam'
   259  
   260  
   261  def RuntimeExtensionProviderFlag():
   262      return '--runtime-extension'
   263  
   264  
   265  def AddonProviderFlag():
   266      return '--addon'
   267  
   268  
   269  def type_to_flag(type):
   270      switcher = {
   271          'CoreProvider': CoreProviderFlag,
   272          'BootstrapProvider': BootstrapProviderFlag,
   273          'ControlPlaneProvider': ControlPlaneProviderFlag,
   274          'InfrastructureProvider': InfrastructureProviderFlag,
   275          'IPAMProvider': IPAMProviderFlag,
   276          'RuntimeExtensionProvider': RuntimeExtensionProviderFlag,
   277          'AddonProvider': AddonProviderFlag
   278      }
   279      func = switcher.get(type, lambda: 'Invalid type')
   280      return func()
   281  
   282  
   283  def print_instructions(repos):
   284      providerList = settings.get('providers', [])
   285      print('clusterctl local overrides generated from local repositories for the {} providers.'.format(
   286          ', '.join(providerList)))
   287      print('in order to use them, please run:')
   288      print
   289      cmd = "clusterctl init \\\n"
   290      for name, type, next_version, components_path in repos:
   291          cmd += "   {} {}:{} \\\n".format(type_to_flag(type), name, next_version)
   292      cmd += "   --config $XDG_CONFIG_HOME/cluster-api/dev-repository/config.yaml"
   293      print(cmd)
   294      print
   295      if 'infrastructure-docker' in providerList:
   296          print('please check the documentation for additional steps required for using the docker provider')
   297          print
   298      if 'infrastructure-in-memory' in providerList:
   299          print ('please check the documentation for additional steps required for using the in-memory provider')
   300          print
   301  
   302  
   303  load_settings()
   304  
   305  load_providers()
   306  
   307  repos = list(create_local_repositories())
   308  
   309  create_dev_config(repos)
   310  
   311  print_instructions(repos)