...

Text file src/github.com/GoogleCloudPlatform/k8s-config-connector/scripts/generate-field-diffs-tf-upgrade/generate_field_changes.py

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/scripts/generate-field-diffs-tf-upgrade

     1# Copyright 2023 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
    15import git
    16import yaml
    17import os
    18from deepdiff import DeepDiff
    19
    20# Truncate the field path returned by deepdiff.
    21def process_field_path(path, root):
    22    return path.replace("['", ".").replace("']", "").replace(".properties", "").replace("root", root)
    23
    24# Extract the kind name from the CRD file content.
    25# Line #18 in the CRD file is usually (if not always) in the format of:
    26# `singular: [KindName]`.
    27def extract_kind_name(filename):
    28    with open(filename, 'r') as fp:
    29        row = fp.readlines()[17:18]
    30        return row[0].split(": ")[1].rstrip()
    31
    32# has_description_changes_only returns true if there are description changes only.
    33def has_description_changes_only(deep_diff_obj):
    34    for item in deep_diff_obj.get('dictionary_item_added', []):
    35        if "description" not in item:
    36            return False
    37    for item in deep_diff_obj.get('iterable_item_added', []):
    38        if "description" not in item:
    39            return False
    40    for item in deep_diff_obj.get('dictionary_item_removed', []):
    41        if "description" not in item:
    42            return False
    43    for item in deep_diff_obj.get('iterable_item_removed', []):
    44       if "description" not in item:
    45            return False
    46    for key, _ in deep_diff_obj.get('values_changed', {}).items():
    47       if "description" not in key:
    48            return False
    49    return True
    50
    51# print_diffs prints out diffs excluding description of CRDs in a readable format.
    52def print_diffs(deep_diff_obj, root):
    53    for item in deep_diff_obj.get('dictionary_item_added', []):
    54        field_path =  process_field_path(item, root)
    55        if "description" not in field_path:
    56            print_addition(field_path)
    57    for item in deep_diff_obj.get('iterable_item_added', []):
    58        field_path =  process_field_path(item, root)
    59        if "description" not in field_path:
    60            print_addition(field_path)
    61    for item in deep_diff_obj.get('dictionary_item_removed', []):
    62        field_path =  process_field_path(item, root)
    63        if "description" not in field_path:
    64            print_removal(field_path)
    65    for item in deep_diff_obj.get('iterable_item_removed', []):
    66        field_path =  process_field_path(item, root)
    67        if "description" not in field_path:
    68            print_removal(field_path)
    69    for key, value in deep_diff_obj.get('values_changed', {}).items():
    70        field_path =  process_field_path(key, root)
    71        if "description" not in field_path:
    72            print("  * Changed " + field_path + " from " + value['old_value'] + " to " + value['new_value'])
    73
    74def print_addition(field_path):
    75    print("  * Added `" + field_path + "` field.")
    76
    77def print_removal(field_path):
    78    print("  * Removed `" + field_path + "` field.")
    79
    80# Create a git Repo object.
    81repo_path = '.'
    82repo = git.Repo(repo_path)
    83
    84# Get the diff between the working tree and the last commit.
    85diff = repo.head.commit.diff(None)
    86latest_commit = repo.head.commit
    87
    88# Loop through the diff to find the changed YAML files under the config/crds/resources directory.
    89for file_diff in diff:
    90    if file_diff.a_path.startswith('config/crds/resources') and file_diff.a_path.endswith('.yaml'):
    91        # Open the YAML file and parse its content.
    92        file_path = os.path.join(repo_path, file_diff.a_path)
    93        with open(file_path, 'r') as yaml_file:
    94            new_yaml_data = yaml.safe_load(yaml_file)
    95            new_spec = new_yaml_data["spec"]["versions"][0]["schema"]["openAPIV3Schema"]["properties"]["spec"]["properties"]
    96            new_status = new_yaml_data["spec"]["versions"][0]["schema"]["openAPIV3Schema"]["properties"]["status"]["properties"]
    97        # Read the YAML file from the latest commit and parse its content.
    98        old_file_content = latest_commit.tree / file_diff.a_path
    99        old_yaml_data =  yaml.safe_load(old_file_content.data_stream.read())
   100        old_spec = old_yaml_data["spec"]["versions"][0]["schema"]["openAPIV3Schema"]["properties"]["spec"]["properties"]
   101        old_status = old_yaml_data["spec"]["versions"][0]["schema"]["openAPIV3Schema"]["properties"]["status"]["properties"]
   102        # Compute the field differences between the old and new spec.
   103        spec_differences = DeepDiff(old_spec, new_spec, ignore_order=True)
   104        # Compute the field differences between the old and new status.
   105        status_differences = DeepDiff(old_status, new_status, ignore_order=True)
   106        # Print out the diffs in a readable format.
   107        if has_description_changes_only(spec_differences) and has_description_changes_only(status_differences):
   108            continue
   109        version = old_yaml_data["spec"]["versions"][0]["name"]
   110        print("* Resource " + extract_kind_name(file_path) +"(" + version + "):")
   111        print_diffs(spec_differences, "spec")
   112        print_diffs(status_differences, "status")

View as plain text