scripts/packergen.py (93 lines of code) (raw):

#!/usr/bin/env python3 # # Copyright 2018 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import json import sys # Install chef-solo via deb package. INSTALL_CHEF_SOLO = r""" declare -r VERSION=18.5.0 declare -r SHA256=1918e72eebeea0dd2f7680b08f1362d699b37570431ebca3c1b4fbe40cfc2abb curl "https://packages.chef.io/files/stable/chef/${VERSION}/debian/11/chef_${VERSION}-1_amd64.deb" -o chef-solo.deb \ && echo "${SHA256} chef-solo.deb" | sha256sum -c \ && sudo dpkg --install chef-solo.deb """ # GCE VM shutdown-script to clean up the image. SHUTDOWN_SCRIPT = r"""#!/bin/bash -eu readonly BUCKET="$(curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/attributes/shutdown-script-log-bucket)" readonly LOGNAME="$(curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/attributes/shutdown-script-log-name)" # Execute inlined script and redirect output to a GCS file. /bin/bash -eu <(cat << 'EOF' echo "--> Cleaning /etc/ssh/ssh_host_* ..." for ssh_host in /etc/ssh/ssh_host_*; do cat /dev/null > $ssh_host && echo " > $ssh_host: DONE" done echo "--> Checking for existence of Root authorized_keys..." if [[ -f /root/.ssh/authorized_keys ]]; then echo " > Removing Root authorized_keys..." rm /root/.ssh/authorized_keys fi echo "--> Cleaning home directories..." for user in $(cat /var/lib/google/google_users | grep -v "root"); do pkill -u $user || echo " > $user: NO PROCESS TO KILL..." while pgrep -u $user > /dev/null; do sleep 1; echo "> Waiting for $user processes to terminate..."; done userdel -r $user && echo " > $user: REMOVED" done echo "--> Removing google_users file..." rm /var/lib/google/google_users echo "--> Cleaning all log files..." find /var/log/ -type f -delete echo "SHUTDOWN SCRIPT COMPLETED" EOF ) 2>&1 | gsutil -h "Content-Type:text/plain" cp - "gs://$BUCKET/$LOGNAME" # Cleaning /root/ ... # Clean all files except: # - .profile # - .bashrc find /root/ -mindepth 1 ! -name .profile ! -name .bashrc -delete """ STOP_SERVICES_SCRIPT = r""" echo "--> Stopping syslog service..." # Syslog is restarted on failure by default. Needed actions: # - disable # - stop # - enable (syslog is still stopped till next restart) systemctl disable rsyslog.service systemctl stop rsyslog.service systemctl enable rsyslog.service echo "--> Stopping Google services..." # We need to stop google services to avoid accidental creation of # users by the account daemon # Reference to google-guest-agent # https://github.com/GoogleCloudPlatform/compute-image-packages/tree/master/packages/python-google-compute-engine systemctl stop google-guest-agent.service """ VERIFY_SHUTDOWN_SCRIPT = r""" # Download and display log via streaming transfers gsutil cp gs://{{ user `log_bucket` }}/{{ user `shutdown_log_name` }} - # Verify shutdown script gsutil cat gs://{{ user `log_bucket` }}/{{ user `shutdown_log_name` }} \ | egrep -q '^SHUTDOWN SCRIPT COMPLETED$' echo "--> Shutdown script execution verified!" echo "--> Deleting log file..." gsutil rm gs://{{ user `log_bucket` }}/{{ user `shutdown_log_name` }} """ PACKER_OVERRIDABLE_CONFIG = [ "disk_size" "image_family", "machine_type", "source_image_family", "source_image_project_id", ] def _inline_format(command): return command.strip().split('\n') def _sudo_shell(command): return { 'type': 'shell', 'execute_command': ( 'chmod +x {{ .Path }}; ' 'sudo /bin/bash -eu -c \'{{ .Vars }} {{ .Path }}\'' ), 'inline_shebang': '/bin/bash -eu', 'inline': _inline_format(command) } def _purge_chef(): return _sudo_shell('apt-get purge -y --auto-remove chef') def _verify_shutdown_script(): """Post-processor to verify that shutdown script has executed.""" return { 'type': 'shell-local', 'inline': _inline_format(VERIFY_SHUTDOWN_SCRIPT), } def main(): parser = argparse.ArgumentParser() parser.add_argument('input', help='Input JSON') args = parser.parse_args() with open(args.input, 'r') as f: data = json.load(f) builder = { 'type': 'googlecompute', 'machine_type': 'e2-standard-2', 'account_file': '{{ user `keyfile` }}', 'service_account_email': '{{ user `service_account_email` }}', 'project_id': '{{ user `project` }}', 'zone': '{{ user `zone` }}', 'ssh_username': '{{ user `ssh_username` }}', 'image_name': '{{ user `imagename` }}', 'use_internal_ip': '{{ user `use_internal_ip` }}', 'instance_name': 'imagebuilder-{{uuid}}', 'metadata': { 'block-project-ssh-keys': 'true', 'shutdown-script': SHUTDOWN_SCRIPT, 'shutdown-script-log-name': '{{ user `shutdown_log_name` }}', 'shutdown-script-log-bucket': '{{ user `log_bucket` }}', }, 'tags': ['imagebuilder-workers'] } # Overrides the allowed attributes builder.update( {key: data.get(key) for key in PACKER_OVERRIDABLE_CONFIG if key in data} ) content = { 'variables': { 'chefdir': None, 'project': None, 'zone': None, 'imagename': None, 'use_internal_ip': 'true', 'shutdown_log_name': 'shutdown-log-{{ uuid }}.txt', 'log_bucket': None, 'ssh_username': None, }, 'builders': [builder], 'provisioners': [ { 'type': 'chef-solo', 'install_command': INSTALL_CHEF_SOLO, 'cookbook_paths': ['{{ user `chefdir` }}/cookbooks/'], 'run_list': data['chef']['run_list'], 'chef_license': 'accept' }, _purge_chef(), _sudo_shell(STOP_SERVICES_SCRIPT), _sudo_shell('apt-get clean') ], 'post-processors': [ _verify_shutdown_script() ], } print(json.dumps(content, sort_keys=True, indent=2)) return 0 if __name__ == '__main__': sys.exit(main())