sigs.k8s.io/cluster-api@v1.7.1/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 sys
    44  import errno
    45  import json
    46  import os
    47  import subprocess
    48  import urllib.request
    49  from distutils.dir_util import copy_tree
    50  from distutils.file_util import copy_file
    51  
    52  settings = {}
    53  
    54  providers = {
    55      'cluster-api': {
    56          'componentsFile': 'core-components.yaml',
    57          'nextVersion': 'v1.7.99',
    58          'type': 'CoreProvider',
    59      },
    60      'bootstrap-kubeadm': {
    61          'componentsFile': 'bootstrap-components.yaml',
    62          'nextVersion': 'v1.7.99',
    63          'type': 'BootstrapProvider',
    64          'configFolder': 'bootstrap/kubeadm/config/default',
    65      },
    66      'control-plane-kubeadm': {
    67          'componentsFile': 'control-plane-components.yaml',
    68          'nextVersion': 'v1.7.99',
    69          'type': 'ControlPlaneProvider',
    70          'configFolder': 'controlplane/kubeadm/config/default',
    71      },
    72      'infrastructure-docker': {
    73          'componentsFile': 'infrastructure-components-development.yaml',
    74          'nextVersion': 'v1.7.99',
    75          'type': 'InfrastructureProvider',
    76          'configFolder': 'test/infrastructure/docker/config/default',
    77      },
    78      'infrastructure-in-memory': {
    79            'componentsFile': 'infrastructure-components-in-memory-development.yaml',
    80            'nextVersion': 'v1.7.99',
    81            'type': 'InfrastructureProvider',
    82            'configFolder': 'test/infrastructure/inmemory/config/default',
    83        },
    84        'runtime-extension-test': {
    85          'componentsFile': 'runtime-extension-components-development.yaml',
    86          'nextVersion': 'v1.7.99',
    87          'type': 'RuntimeExtensionProvider',
    88          'configFolder': 'test/extension/config/default',
    89      },
    90  }
    91  
    92  
    93  def load_settings():
    94      global settings
    95      try:
    96          settings = json.load(open('clusterctl-settings.json'))
    97      except  Exception as e:
    98          raise Exception('failed to load clusterctl-settings.json: {}'.format(e))
    99  
   100  
   101  def load_providers():
   102      provider_repos = settings.get('provider_repos', [])
   103      for repo in provider_repos:
   104          file = repo + '/clusterctl-settings.json'
   105          try:
   106              provider_details = json.load(open(file))
   107              provider_name = provider_details['name']
   108              provider_config = provider_details['config']
   109              provider_config['repo'] = repo
   110              providers[provider_name] = provider_config
   111          except  Exception as e:
   112              raise Exception('failed to load clusterctl-settings.json from repo {}: {}'.format(repo, e))
   113  
   114  
   115  def execCmd(args):
   116      try:
   117          out = subprocess.Popen(args,
   118                                 stdout=subprocess.PIPE,
   119                                 stderr=subprocess.STDOUT)
   120  
   121          stdout, stderr = out.communicate()
   122          if stderr is not None:
   123              raise Exception('stderr contains: \n{}'.format(stderr))
   124  
   125          return stdout
   126      except Exception as e:
   127          raise Exception('failed to run {}: {}'.format(args, e))
   128  
   129  
   130  def get_repository_folder():
   131      config_dir = os.getenv("XDG_CONFIG_HOME", "")
   132      if config_dir == "":
   133          home_dir = os.getenv("HOME", "")
   134          if home_dir == "":
   135              raise Exception('HOME variable is not set')
   136          config_dir = os.path.join(home_dir, ".config")
   137      return os.path.join(config_dir, 'cluster-api', 'dev-repository')
   138  
   139  
   140  def write_local_repository(provider, version, components_file, components_yaml, metadata_file):
   141      try:
   142          repository_folder = get_repository_folder()
   143          provider_folder = os.path.join(repository_folder, provider, version)
   144          try:
   145              os.makedirs(provider_folder)
   146          except OSError as e:
   147              if e.errno != errno.EEXIST:
   148                  raise
   149          components_path = os.path.join(provider_folder, components_file)
   150          f = open(components_path, 'wb')
   151          f.write(components_yaml)
   152          f.close()
   153  
   154          copy_file(metadata_file, provider_folder)
   155  
   156          if provider == "infrastructure-docker":
   157              copy_tree("test/infrastructure/docker/templates", provider_folder)
   158  
   159          if provider == "infrastructure-in-memory":
   160              copy_tree("test/infrastructure/inmemory/templates", provider_folder)
   161  
   162          return components_path
   163      except Exception as e:
   164          raise Exception('failed to write {} to {}: {}'.format(components_file, provider_folder, e))
   165  
   166  
   167  def create_local_repositories():
   168      providerList = settings.get('providers', [])
   169      assert providerList is not None, 'invalid configuration: please define the list of providers to override'
   170      assert len(providerList)>0, 'invalid configuration: please define at least one provider to override'
   171  
   172      if len(sys.argv) == 1:
   173          execCmd(['make', 'kustomize'])
   174  
   175      for provider in providerList:
   176          p = providers.get(provider)
   177          assert p is not None, 'invalid configuration: please specify the configuration for the {} provider'.format(
   178              provider)
   179  
   180          repo = p.get('repo', '.')
   181          config_folder = p.get('configFolder', 'config/default')
   182          metadata_file = repo + '/metadata.yaml'
   183  
   184          next_version = p.get('nextVersion')
   185          assert next_version is not None, 'invalid configuration for provider {}: please provide nextVersion value'.format(
   186              provider)
   187  
   188          name, type = splitNameAndType(provider)
   189          assert name is not None, 'invalid configuration for provider {}: please use a valid provider label'.format(
   190              provider)
   191  
   192          components_file = p.get('componentsFile')
   193          assert components_file is not None, 'invalid configuration for provider {}: please provide componentsFile value'.format(
   194              provider)
   195  
   196          if len(sys.argv) > 1:
   197              url = "{}/{}".format(sys.argv[1], components_file)
   198              components_yaml = urllib.request.urlopen(url).read()
   199          else:
   200              components_yaml = execCmd(['./hack/tools/bin/kustomize', 'build', os.path.join(repo, config_folder)])
   201  
   202          components_path = write_local_repository(provider, next_version, components_file, components_yaml,
   203                                                       metadata_file)
   204  
   205          yield name, type, next_version, components_path
   206  
   207  
   208  def injectLatest(path):
   209      head, tail = os.path.split(path)
   210      return '{}/latest/{}'.format(head, tail)
   211  
   212  
   213  def create_dev_config(repos):
   214      yaml = "providers:\n"
   215      for name, type, next_version, components_path in repos:
   216          yaml += "- name: \"{}\"\n".format(name)
   217          yaml += "  type: \"{}\"\n".format(type)
   218          yaml += "  url: \"{}\"\n".format(components_path)
   219      yaml += "overridesFolder: \"{}/overrides\"\n".format(get_repository_folder())
   220  
   221      try:
   222          repository_folder = get_repository_folder()
   223          config_path = os.path.join(repository_folder, "config.yaml")
   224          f = open(config_path, 'w')
   225          f.write(yaml)
   226          f.close()
   227          return components_path
   228      except Exception as e:
   229          raise Exception('failed to write {}: {}'.format(config_path, e))
   230  
   231  
   232  def splitNameAndType(provider):
   233      if provider == 'cluster-api':
   234          return 'cluster-api', 'CoreProvider'
   235      if provider.startswith('bootstrap-'):
   236          return provider[len('bootstrap-'):], 'BootstrapProvider'
   237      if provider.startswith('control-plane-'):
   238          return provider[len('control-plane-'):], 'ControlPlaneProvider'
   239      if provider.startswith('infrastructure-'):
   240          return provider[len('infrastructure-'):], 'InfrastructureProvider'
   241      if provider.startswith('ipam-'):
   242          return provider[len('ipam-'):], 'IPAMProvider'
   243      if provider.startswith('runtime-extension-'):
   244          return provider[len('runtime-extension-'):], 'RuntimeExtensionProvider'
   245      if provider.startswith('addon-'):
   246          return provider[len('addon-'):], 'AddonProvider'
   247      return None, None
   248  
   249  
   250  def CoreProviderFlag():
   251      return '--core'
   252  
   253  
   254  def BootstrapProviderFlag():
   255      return '--bootstrap'
   256  
   257  
   258  def ControlPlaneProviderFlag():
   259      return '--control-plane'
   260  
   261  
   262  def InfrastructureProviderFlag():
   263      return '--infrastructure'
   264  
   265  
   266  def IPAMProviderFlag():
   267      return '--ipam'
   268  
   269  
   270  def RuntimeExtensionProviderFlag():
   271      return '--runtime-extension'
   272  
   273  
   274  def AddonProviderFlag():
   275      return '--addon'
   276  
   277  
   278  def type_to_flag(type):
   279      switcher = {
   280          'CoreProvider': CoreProviderFlag,
   281          'BootstrapProvider': BootstrapProviderFlag,
   282          'ControlPlaneProvider': ControlPlaneProviderFlag,
   283          'InfrastructureProvider': InfrastructureProviderFlag,
   284          'IPAMProvider': IPAMProviderFlag,
   285          'RuntimeExtensionProvider': RuntimeExtensionProviderFlag,
   286          'AddonProvider': AddonProviderFlag
   287      }
   288      func = switcher.get(type, lambda: 'Invalid type')
   289      return func()
   290  
   291  
   292  def print_instructions(repos):
   293      providerList = settings.get('providers', [])
   294      print('clusterctl local overrides generated from local repositories for the {} providers.'.format(
   295          ', '.join(providerList)))
   296      print('in order to use them, please run:')
   297      print
   298      cmd = "clusterctl init \\\n"
   299      for name, type, next_version, components_path in repos:
   300          cmd += "   {} {}:{} \\\n".format(type_to_flag(type), name, next_version)
   301      config_dir = os.getenv("XDG_CONFIG_HOME", "")
   302      if config_dir != "":
   303          cmd += "   --config $XDG_CONFIG_HOME/cluster-api/dev-repository/config.yaml"
   304      else:
   305          cmd += "   --config $HOME/.config/cluster-api/dev-repository/config.yaml"
   306      print(cmd)
   307      print
   308      if 'infrastructure-docker' in providerList:
   309          print('please check the documentation for additional steps required for using the docker provider')
   310          print
   311      if 'infrastructure-in-memory' in providerList:
   312          print ('please check the documentation for additional steps required for using the in-memory provider')
   313          print
   314  
   315  
   316  load_settings()
   317  
   318  load_providers()
   319  
   320  repos = list(create_local_repositories())
   321  
   322  create_dev_config(repos)
   323  
   324  print_instructions(repos)