Skip to content
This repository has been archived by the owner on Jun 5, 2023. It is now read-only.

Dynamic patch update #2469

Merged
merged 52 commits into from
Jan 8, 2019
Merged

Dynamic patch update #2469

merged 52 commits into from
Jan 8, 2019

Conversation

osandoval011
Copy link
Contributor

Thanks for opening a Pull Request!

Here's a handy checklist to ensure your PR goes smoothly.

  • I signed Google's Contributor License Agreement
  • My code conforms to Google's python style.
  • My PR at a minimum doesn't decrease unit-test coverage (if applicable).
  • My PR has been functionally tested.
  • All of the unit-tests still pass.
  • Running pylint --rcfile=pylintrc passes.

These guidelines and more can be found in our contributing guidelines.

fixes #2431

With this change, client and server vms that are installed on a version denoting tag will download and start forseti from latest patches matching that version upon vm restarts

@codecov
Copy link

codecov bot commented Jan 4, 2019

Codecov Report

Merging #2469 into dev will decrease coverage by 0.13%.
The diff coverage is n/a.

@@            Coverage Diff             @@
##              dev    #2469      +/-   ##
==========================================
- Coverage   88.71%   88.57%   -0.14%     
==========================================
  Files         180      180              
  Lines       14227    14103     -124     
==========================================
- Hits        12621    12492     -129     
- Misses       1606     1611       +5
Impacted Files Coverage Δ
...le/cloud/forseti/enforcer/gce_firewall_enforcer.py 94.82% <0%> (-1.26%) ⬇️
google/cloud/forseti/services/dao.py 87.97% <0%> (-1.11%) ⬇️
.../cloud/forseti/services/model/importer/importer.py 91.66% <0%> (-1%) ⬇️
...gle/cloud/forseti/common/data_access/csv_writer.py 96.29% <0%> (-0.48%) ⬇️
google/cloud/forseti/services/base/config.py 89.47% <0%> (-0.24%) ⬇️
...d/forseti/scanner/audit/ke_version_rules_engine.py 88.35% <0%> (-0.13%) ⬇️
.../scanner/audit/service_account_key_rules_engine.py 86.25% <0%> (-0.11%) ⬇️
google/cloud/forseti/services/inventory/storage.py 78.2% <0%> (-0.09%) ⬇️
.../cloud/forseti/notifier/notifiers/cscc_notifier.py 55.68% <0%> (ø) ⬆️
...cloud/forseti/common/gcp_type/instance_template.py 100% <0%> (ø) ⬆️
... and 13 more

1 similar comment
@codecov
Copy link

codecov bot commented Jan 4, 2019

Codecov Report

Merging #2469 into dev will decrease coverage by 0.13%.
The diff coverage is n/a.

@@            Coverage Diff             @@
##              dev    #2469      +/-   ##
==========================================
- Coverage   88.71%   88.57%   -0.14%     
==========================================
  Files         180      180              
  Lines       14227    14103     -124     
==========================================
- Hits        12621    12492     -129     
- Misses       1606     1611       +5
Impacted Files Coverage Δ
...le/cloud/forseti/enforcer/gce_firewall_enforcer.py 94.82% <0%> (-1.26%) ⬇️
google/cloud/forseti/services/dao.py 87.97% <0%> (-1.11%) ⬇️
.../cloud/forseti/services/model/importer/importer.py 91.66% <0%> (-1%) ⬇️
...gle/cloud/forseti/common/data_access/csv_writer.py 96.29% <0%> (-0.48%) ⬇️
google/cloud/forseti/services/base/config.py 89.47% <0%> (-0.24%) ⬇️
...d/forseti/scanner/audit/ke_version_rules_engine.py 88.35% <0%> (-0.13%) ⬇️
.../scanner/audit/service_account_key_rules_engine.py 86.25% <0%> (-0.11%) ⬇️
google/cloud/forseti/services/inventory/storage.py 78.2% <0%> (-0.09%) ⬇️
.../cloud/forseti/notifier/notifiers/cscc_notifier.py 55.68% <0%> (ø) ⬆️
...cloud/forseti/common/gcp_type/instance_template.py 100% <0%> (ø) ⬆️
... and 13 more

@osandoval011
Copy link
Contributor Author

@blueandgold I don't know if creating 2 remote branches to facilitate a newly created test in https://docs.google.com/spreadsheets/d/117QLTjAbvzfrdnhFWgIh22A-M0MiK2YvUwU7EJgTQag/edit#gid=1172914269 is feasible. When a new release comes out for this feature to be tested forseti must be installed with that new releases code. Hence creating a static branch 88.0.0 to install from and see if 88.0.1 is what gets installed on vm won't work, because 88.0.0 will always be using the same code rather than the new release's

Copy link
Contributor

@blueandgold blueandgold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for getting this working! First initial pass, I have some questions and suggestions to better understand how this works.

end_const = segments[2][2:]
else:
end_const = segments[2][1:]
return "%s.{[0-9],[0-9][0-9]}%s" % (start_const, end_const)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get why end_const is needed. Also, why we are slicing parts from segment[2].

The example that you showed me was:

*88.0.{[0-9], [0-9][0-9]}*

I must have missed something, but I can't retrieve anything with how it's used.

git tag -l *88.0.{[0-9], [0-9][0-9]}*

@@ -106,6 +106,37 @@ def _print_banner(border_symbol, edge_symbol, corner_symbol,
print(border)
print('')

def get_latest_patch_query():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming of this method can be better, to reflect what it is doing. I suggest:

def get_all_patches_glob():
def get_all_patches_glob_in_current_version():
def get_glob_for_all_patches_in_current_version():

#1: you are finding all patches, not just the latest patch
#2: glob is more clear than query, it matches the documentation and when we discuss verbally

@@ -91,6 +91,7 @@ def get_deployment_values(self):
'BUCKET_LOCATION': self.config.bucket_location,
'GCP_CLIENT_SERVICE_ACCOUNT': self.gcp_service_acct_email,
'FORSETI_VERSION': self.version,
'MATCHING_PATCHES_QUERY': self.matching_patches_query,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming here would be more readable as:

GLOB_FOR_ALL_PATCHES_IN_CURRENT_VERSION

end_const = segments[2][2:]
else:
end_const = segments[2][1:]
return "%s.{[0-9],[0-9][0-9]}%s" % (start_const, end_const)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should write a unit test for this method. All the return results should be verified.

def glob_expr_matching_patches(forseti_version):
#TODO update in server/forseti-instance-server if update here
"""Returns a glob expression matching all patches to the version of passed in parameter"""
segments = forseti_version[6:].split('.')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of slicing this by [6:], how about doing a string replace of tags/v, which would be more readable.

#TODO update in server/forseti-instance-server if update here
"""Returns a glob expression matching all patches to the version of passed in parameter"""
segments = forseti_version[6:].split('.')
if forseti_version[:6] == "tags/v" and all(segment.isdigit() for segment in segments):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can decompose this for better readability and debugging.

if forseti_version[:6] != 'tags/v':
    return None

for segment in segments:
    if not segment.isdigit():
        return None

return  "v{}.{}.{{[0-9],[0-9][0-9]}}".format(segments[0], segments[1])

#TODO update in server/forseti-instance-server if update here
"""Returns a glob expression matching all patches to the version of passed in parameter"""
segments = forseti_version[6:].split('.')
if forseti_version[:6] == "tags/v" and all(segment.isdigit() for segment in segments):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems nicer to move the tags/v check to the beginning of the method.

"""Returns a glob expression matching all patches to the version of passed in parameter"""
segments = forseti_version[6:].split('.')
if forseti_version[:6] == "tags/v" and all(segment.isdigit() for segment in segments):
return "v{}.{}.{{[0-9],[0-9][0-9]}}".format(segments[0], segments[1])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use only single-quotes, not double-quotes. Please change everywhere.

FORSETI_VERSION = (
"git checkout {forseti_version}".format(
forseti_version=context.properties['forseti-version']))
patch_search_expression = glob_expr_matching_patches(context.properties['forseti-version'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For better readability, you can write like this:

patch_search_expression = get_patch_search_expression(context.properties['forseti-version'])

@@ -150,8 +178,8 @@ def GenerateConfig(context):
# Install Forseti.
download_forseti=DOWNLOAD_FORSETI,

# Checkout Forseti version.
checkout_forseti_version=FORSETI_VERSION,
# if on tag, checkout latest patch to that version, else checkout current branch version
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use proper sentence, capitalize first letter and give it a period. Also keep it to 80 character length. You can set the margin in pycharm.

@@ -14,6 +14,15 @@

"""Creates a GCE instance template for Forseti Security."""

def glob_expr_matching_patches(forseti_version):
#TODO update in server/forseti-instance-server if update here
"""Returns a glob expression matching all patches to the version of passed in parameter"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document the arg and the return.

patch=${{segments[2]}}
patch=${{patch: 0: 2}}
patch=$(echo $patch | sed 's/[^0-9]*//g')
if !((${{#latest_version[@]}})) || ((patch > ${{latest_version[1]}}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this bash code do in this line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sets latest version to the version currently being iterated if latest version hasnt yet been instantiated or if the version being iterated on is greater than latest version. Essentially
if latest_version is None or patch > latest_version[1]
where latest_version is an array [full_version, patch_number]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I was thrown off by latest_version is an array [full_version, patch_number]. Can you please make a comment to make this easier to read? Or else, rename this to something more clear.

#  latest_version is an array [full_version, patch_number]

Copy link
Contributor

@blueandgold blueandgold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, I like how streamlined everything looks. Just a few more nits.

Shame that we can't centralize a lot of the code here.

def get_patch_search_expression(forseti_version):
"""Returns a glob expression matching all patches to the version of passed in parameter

#TODO update in server/forseti-instance-server if update here
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside the doc comment, we don't need # again. And make it a proper sentence.

TODO: Update in server/forseti-instance-server if update here.


Args:
forseti_version (str): Installed forseti version. Should start with
'tags/v' if patches are to be updated automatically
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line continuation has hanging indent of 4 spaces. Add period at the end.

forseti_version (str): Installed forseti version.  Should start with
    'tags/v' if patches are to be updated automatically.

patch_search_expression = glob_expr_matching_patches(context.properties['forseti-version'])
if patch_search_expression:
CHECKOUT_FORSETI_VERSION = (
"""matches=$(git tag -l {patch_search_expression})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking for change here like this:

 """versions=$(git tag -l {patch_search_expression})

CHECKOUT_FORSETI_VERSION = (
"""matches=$(git tag -l {patch_search_expression})
matches=(${{matches//;/ }})
for match in "${{matches[@]}}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you make the change above, this would become

for version in "${{versions[@]}}"

patch=${{segments[2]}}
patch=${{patch: 0: 2}}
patch=$(echo $patch | sed 's/[^0-9]*//g')
if !((${{#latest_version[@]}})) || ((patch > ${{latest_version[1]}}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I was thrown off by latest_version is an array [full_version, patch_number]. Can you please make a comment to make this easier to read? Or else, rename this to something more clear.

#  latest_version is an array [full_version, patch_number]

"""
if forseti_version[:6] != 'tags/v':
return None
segments = forseti_version.replace('tags/v', '''''').split('.')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line break here.

if forseti_version[:6] != 'tags/v':
return None
segments = forseti_version.replace('tags/v', '''''').split('.')

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please propagate the changes from above here.

patch=${{segments[2]}}
patch=${{patch: 0: 2}}
patch=$(echo $patch | sed 's/[^0-9]*//g')
if !((${{#latest_version[@]}})) || ((patch > ${{latest_version[1]}}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment here for latest_version would be super nice.

Copy link
Contributor

@blueandgold blueandgold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few more nits.

fi
done
git checkout ${{latest_version[0]}}"""
# latest_version is an array [full_version, patch_number]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you document this at line 63, I think that's where this comment would really help.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how can I put a comment in a string?

@@ -15,6 +15,31 @@
"""Creates a GCE instance template for Forseti Security."""


def get_patch_search_expression(forseti_version):
"""Returns a glob expression matching all patches to the version of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first line of a doc string needs to be one sentence within 80 characters (for generating nice-looking pydocs).
So, please change this.

"""Returns a glob expression matching all patches of the given version.

Copy link
Contributor

@blueandgold blueandgold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few more comments.

patch=${{segments[2]}}
patch=${{patch: 0: 2}}
patch=$(echo $patch | sed 's/[^0-9]*//g')
# latest_version is an array [full_version, patch_number]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One space only after #

done
git checkout ${{latest_version[0]}}"""

.format(patch_search_expression=patch_search_expression))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to append this .format()directly to the end of the bash string?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no it works

@@ -124,6 +170,7 @@ def GenerateConfig(context):
# Download Forseti source code
{download_forseti}
cd forseti-security
git fetch --tags
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does git fetch --all also get all the tags?


if forseti_version[:6] != 'tags/v':
return None
segments = forseti_version.replace('tags/v', '').split('.')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add new line here.

Copy link
Contributor

@blueandgold blueandgold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

VM should automatically pick up the latest patch of the current version
4 participants