Merge ~smoser/cloud-init:feature/puppet_4 into ~rski/cloud-init:puppet_4

Proposed by Scott Moser
Status: Merged
Merge reported by: Scott Moser
Merged at revision: aad1ca8ba85926c065696d0554684bbf51b5ef16
Proposed branch: ~smoser/cloud-init:feature/puppet_4
Merge into: ~rski/cloud-init:puppet_4
Diff against target: 61263 lines (+39982/-7468) (has conflicts)
571 files modified
.gitignore (+6/-0)
.pylintrc (+60/-0)
ChangeLog (+656/-0)
HACKING.rst (+11/-0)
LICENSE (+22/-674)
LICENSE-Apache2.0 (+202/-0)
LICENSE-GPLv3 (+674/-0)
MANIFEST.in (+10/-1)
Makefile (+42/-15)
cloudinit/__init__.py (+0/-21)
cloudinit/analyze/__init__.py (+0/-0)
cloudinit/analyze/__main__.py (+157/-0)
cloudinit/analyze/dump.py (+170/-0)
cloudinit/analyze/show.py (+207/-0)
cloudinit/analyze/tests/test_dump.py (+210/-0)
cloudinit/apport.py (+105/-0)
cloudinit/atomic_helper.py (+14/-3)
cloudinit/cloud.py (+8/-21)
cloudinit/cmd/__init__.py (+0/-21)
cloudinit/cmd/clean.py (+103/-0)
cloudinit/cmd/devel/__init__.py (+0/-0)
cloudinit/cmd/devel/logs.py (+101/-0)
cloudinit/cmd/devel/parser.py (+26/-0)
cloudinit/cmd/devel/tests/__init__.py (+0/-0)
cloudinit/cmd/devel/tests/test_logs.py (+120/-0)
cloudinit/cmd/main.py (+260/-92)
cloudinit/cmd/status.py (+163/-0)
cloudinit/cmd/tests/__init__.py (+0/-0)
cloudinit/cmd/tests/test_clean.py (+177/-0)
cloudinit/cmd/tests/test_status.py (+390/-0)
cloudinit/config/__init__.py (+8/-19)
cloudinit/config/cc_apt_configure.py (+41/-32)
cloudinit/config/cc_apt_pipelining.py (+5/-15)
cloudinit/config/cc_bootcmd.py (+67/-47)
cloudinit/config/cc_byobu.py (+7/-17)
cloudinit/config/cc_ca_certs.py (+4/-14)
cloudinit/config/cc_chef.py (+41/-29)
cloudinit/config/cc_debug.py (+4/-14)
cloudinit/config/cc_disable_ec2_metadata.py (+7/-17)
cloudinit/config/cc_disk_setup.py (+125/-83)
cloudinit/config/cc_emit_upstart.py (+7/-17)
cloudinit/config/cc_fan.py (+7/-17)
cloudinit/config/cc_final_message.py (+7/-17)
cloudinit/config/cc_foo.py (+7/-17)
cloudinit/config/cc_growpart.py (+22/-19)
cloudinit/config/cc_grub_dpkg.py (+7/-17)
cloudinit/config/cc_keys_to_console.py (+7/-17)
cloudinit/config/cc_landscape.py (+13/-23)
cloudinit/config/cc_locale.py (+7/-17)
cloudinit/config/cc_lxd.py (+6/-16)
cloudinit/config/cc_mcollective.py (+8/-18)
cloudinit/config/cc_migrator.py (+5/-15)
cloudinit/config/cc_mounts.py (+57/-41)
cloudinit/config/cc_ntp.py (+160/-75)
cloudinit/config/cc_package_update_upgrade_install.py (+5/-15)
cloudinit/config/cc_phone_home.py (+7/-17)
cloudinit/config/cc_power_state_change.py (+7/-16)
cloudinit/config/cc_puppet.py (+59/-28)
cloudinit/config/cc_resizefs.py (+178/-102)
cloudinit/config/cc_resolv_conf.py (+9/-19)
cloudinit/config/cc_rh_subscription.py (+35/-36)
cloudinit/config/cc_rightscale_userdata.py (+7/-17)
cloudinit/config/cc_rsyslog.py (+17/-25)
cloudinit/config/cc_runcmd.py (+64/-44)
cloudinit/config/cc_salt_minion.py (+4/-14)
cloudinit/config/cc_scripts_per_boot.py (+7/-17)
cloudinit/config/cc_scripts_per_instance.py (+7/-17)
cloudinit/config/cc_scripts_per_once.py (+7/-17)
cloudinit/config/cc_scripts_user.py (+7/-17)
cloudinit/config/cc_scripts_vendor.py (+5/-15)
cloudinit/config/cc_seed_random.py (+10/-19)
cloudinit/config/cc_set_hostname.py (+8/-18)
cloudinit/config/cc_set_passwords.py (+67/-35)
cloudinit/config/cc_snap_config.py (+12/-19)
cloudinit/config/cc_snappy.py (+8/-18)
cloudinit/config/cc_spacewalk.py (+3/-13)
cloudinit/config/cc_ssh.py (+7/-17)
cloudinit/config/cc_ssh_authkey_fingerprints.py (+7/-17)
cloudinit/config/cc_ssh_import_id.py (+7/-17)
cloudinit/config/cc_timezone.py (+7/-17)
cloudinit/config/cc_update_etc_hosts.py (+9/-19)
cloudinit/config/cc_update_hostname.py (+7/-17)
cloudinit/config/cc_users_groups.py (+47/-35)
cloudinit/config/cc_write_files.py (+27/-26)
cloudinit/config/cc_yum_add_repo.py (+20/-24)
cloudinit/config/cc_zypper_add_repo.py (+218/-0)
cloudinit/config/schema.py (+360/-0)
cloudinit/cs_utils.py (+6/-15)
cloudinit/dhclient_hook.py (+3/-2)
cloudinit/distros/__init__.py (+81/-58)
cloudinit/distros/arch.py (+69/-49)
cloudinit/distros/centos.py (+12/-0)
cloudinit/distros/debian.py (+114/-48)
cloudinit/distros/fedora.py (+9/-19)
cloudinit/distros/freebsd.py (+265/-35)
cloudinit/distros/gentoo.py (+14/-23)
cloudinit/distros/net_util.py (+9/-19)
cloudinit/distros/opensuse.py (+212/-0)
cloudinit/distros/parsers/__init__.py (+5/-15)
cloudinit/distros/parsers/hostname.py (+5/-15)
cloudinit/distros/parsers/hosts.py (+7/-17)
cloudinit/distros/parsers/networkmanager_conf.py (+23/-0)
cloudinit/distros/parsers/resolv_conf.py (+12/-19)
cloudinit/distros/parsers/sys_conf.py (+5/-15)
cloudinit/distros/rhel.py (+24/-34)
cloudinit/distros/rhel_util.py (+9/-20)
cloudinit/distros/sles.py (+7/-169)
cloudinit/distros/ubuntu.py (+10/-20)
cloudinit/distros/ug_util.py (+18/-28)
cloudinit/ec2_utils.py (+45/-30)
cloudinit/filters/__init__.py (+0/-21)
cloudinit/filters/launch_index.py (+9/-19)
cloudinit/gpg.py (+9/-18)
cloudinit/handlers/__init__.py (+10/-20)
cloudinit/handlers/boot_hook.py (+9/-19)
cloudinit/handlers/cloud_config.py (+9/-19)
cloudinit/handlers/shell_script.py (+9/-19)
cloudinit/handlers/upstart_job.py (+9/-20)
cloudinit/helpers.py (+25/-33)
cloudinit/importer.py (+9/-19)
cloudinit/log.py (+14/-19)
cloudinit/mergers/__init__.py (+5/-15)
cloudinit/mergers/m_dict.py (+5/-15)
cloudinit/mergers/m_list.py (+5/-15)
cloudinit/mergers/m_str.py (+3/-14)
cloudinit/net/__init__.py (+450/-145)
cloudinit/net/cmdline.py (+23/-52)
cloudinit/net/dhcp.py (+224/-0)
cloudinit/net/eni.py (+64/-63)
cloudinit/net/netplan.py (+399/-0)
cloudinit/net/network_state.py (+579/-57)
cloudinit/net/renderer.py (+23/-16)
cloudinit/net/renderers.py (+53/-0)
cloudinit/net/sysconfig.py (+329/-101)
cloudinit/net/tests/__init__.py (+0/-0)
cloudinit/net/tests/test_dhcp.py (+323/-0)
cloudinit/net/tests/test_init.py (+613/-0)
cloudinit/net/udev.py (+8/-16)
cloudinit/netinfo.py (+18/-27)
cloudinit/patcher.py (+7/-17)
cloudinit/registry.py (+5/-3)
cloudinit/reporting/__init__.py (+3/-2)
cloudinit/reporting/events.py (+4/-3)
cloudinit/reporting/handlers.py (+5/-3)
cloudinit/safeyaml.py (+5/-15)
cloudinit/serial.py (+3/-14)
cloudinit/settings.py (+15/-20)
cloudinit/signal_handler.py (+7/-17)
cloudinit/simpletable.py (+62/-0)
cloudinit/sources/DataSourceAliYun.py (+26/-4)
cloudinit/sources/DataSourceAltCloud.py (+18/-26)
cloudinit/sources/DataSourceAzure.py (+515/-158)
cloudinit/sources/DataSourceBigstep.py (+10/-5)
cloudinit/sources/DataSourceCloudSigma.py (+12/-18)
cloudinit/sources/DataSourceCloudStack.py (+58/-41)
cloudinit/sources/DataSourceConfigDrive.py (+23/-27)
cloudinit/sources/DataSourceDigitalOcean.py (+10/-17)
cloudinit/sources/DataSourceEc2.py (+396/-49)
cloudinit/sources/DataSourceGCE.py (+214/-93)
cloudinit/sources/DataSourceMAAS.py (+56/-37)
cloudinit/sources/DataSourceNoCloud.py (+27/-22)
cloudinit/sources/DataSourceNone.py (+9/-16)
cloudinit/sources/DataSourceOVF.py (+292/-90)
cloudinit/sources/DataSourceOpenNebula.py (+83/-83)
cloudinit/sources/DataSourceOpenStack.py (+22/-20)
cloudinit/sources/DataSourceScaleway.py (+236/-0)
cloudinit/sources/DataSourceSmartOS.py (+11/-19)
cloudinit/sources/__init__.py (+154/-39)
cloudinit/sources/helpers/__init__.py (+0/-13)
cloudinit/sources/helpers/azure.py (+45/-17)
cloudinit/sources/helpers/digitalocean.py (+38/-44)
cloudinit/sources/helpers/openstack.py (+23/-20)
cloudinit/sources/helpers/vmware/__init__.py (+0/-13)
cloudinit/sources/helpers/vmware/imc/__init__.py (+0/-13)
cloudinit/sources/helpers/vmware/imc/boot_proto.py (+6/-16)
cloudinit/sources/helpers/vmware/imc/config.py (+31/-19)
cloudinit/sources/helpers/vmware/imc/config_custom_script.py (+153/-0)
cloudinit/sources/helpers/vmware/imc/config_file.py (+10/-20)
cloudinit/sources/helpers/vmware/imc/config_namespace.py (+6/-16)
cloudinit/sources/helpers/vmware/imc/config_nic.py (+141/-104)
cloudinit/sources/helpers/vmware/imc/config_passwd.py (+67/-0)
cloudinit/sources/helpers/vmware/imc/config_source.py (+6/-16)
cloudinit/sources/helpers/vmware/imc/guestcust_error.py (+6/-16)
cloudinit/sources/helpers/vmware/imc/guestcust_event.py (+6/-16)
cloudinit/sources/helpers/vmware/imc/guestcust_state.py (+6/-16)
cloudinit/sources/helpers/vmware/imc/guestcust_util.py (+13/-21)
cloudinit/sources/helpers/vmware/imc/ipv4_mode.py (+6/-16)
cloudinit/sources/helpers/vmware/imc/nic.py (+6/-16)
cloudinit/sources/helpers/vmware/imc/nic_base.py (+6/-16)
cloudinit/sources/tests/__init__.py (+0/-0)
cloudinit/sources/tests/test_init.py (+202/-0)
cloudinit/ssh_util.py (+27/-28)
cloudinit/stages.py (+75/-67)
cloudinit/temp_utils.py (+106/-0)
cloudinit/templater.py (+15/-25)
cloudinit/tests/__init__.py (+0/-0)
cloudinit/tests/helpers.py (+170/-54)
cloudinit/tests/test_netinfo.py (+106/-0)
cloudinit/tests/test_simpletable.py (+106/-0)
cloudinit/tests/test_temp_utils.py (+101/-0)
cloudinit/tests/test_url_helper.py (+40/-0)
cloudinit/tests/test_util.py (+46/-0)
cloudinit/type_utils.py (+9/-19)
cloudinit/url_helper.py (+49/-37)
cloudinit/user_data.py (+35/-31)
cloudinit/util.py (+420/-199)
cloudinit/version.py (+13/-16)
cloudinit/warnings.py (+139/-0)
config/cloud.cfg.tmpl (+86/-5)
dev/null (+0/-171)
doc/examples/cloud-config-apt.txt (+2/-2)
doc/examples/cloud-config-chef.txt (+49/-41)
doc/examples/cloud-config-disk-setup.txt (+7/-7)
doc/examples/cloud-config-gluster.txt (+2/-2)
doc/examples/cloud-config-mount-points.txt (+4/-4)
doc/examples/cloud-config-resolv-conf.txt (+2/-2)
doc/examples/cloud-config-update-apt.txt (+4/-3)
doc/examples/cloud-config-user-groups.txt (+3/-3)
doc/examples/cloud-config.txt (+12/-5)
doc/rtd/conf.py (+10/-0)
doc/rtd/index.rst (+3/-0)
doc/rtd/topics/boot.rst (+10/-3)
doc/rtd/topics/capabilities.rst (+202/-3)
doc/rtd/topics/datasources.rst (+14/-13)
doc/rtd/topics/datasources/altcloud.rst (+4/-2)
doc/rtd/topics/datasources/azure.rst (+3/-1)
doc/rtd/topics/datasources/cloudsigma.rst (+2/-0)
doc/rtd/topics/datasources/cloudstack.rst (+2/-0)
doc/rtd/topics/datasources/configdrive.rst (+6/-4)
doc/rtd/topics/datasources/digitalocean.rst (+2/-0)
doc/rtd/topics/datasources/ec2.rst (+2/-0)
doc/rtd/topics/datasources/fallback.rst (+2/-0)
doc/rtd/topics/datasources/gce.rst (+20/-0)
doc/rtd/topics/datasources/maas.rst (+2/-0)
doc/rtd/topics/datasources/nocloud.rst (+71/-0)
doc/rtd/topics/datasources/opennebula.rst (+2/-0)
doc/rtd/topics/datasources/openstack.rst (+37/-1)
doc/rtd/topics/datasources/ovf.rst (+2/-0)
doc/rtd/topics/datasources/smartos.rst (+2/-0)
doc/rtd/topics/debugging.rst (+147/-0)
doc/rtd/topics/dir_layout.rst (+7/-7)
doc/rtd/topics/examples.rst (+29/-1)
doc/rtd/topics/format.rst (+8/-6)
doc/rtd/topics/merging.rst (+199/-1)
doc/rtd/topics/modules.rst (+3/-1)
doc/rtd/topics/network-config-format-eni.rst (+20/-0)
doc/rtd/topics/network-config-format-v1.rst (+563/-0)
doc/rtd/topics/network-config-format-v2.rst (+503/-0)
doc/rtd/topics/network-config.rst (+254/-0)
doc/rtd/topics/tests.rst (+702/-0)
doc/rtd/topics/vendordata.rst (+2/-2)
integration-requirements.txt (+20/-0)
packages/bddeb (+38/-85)
packages/brpm (+14/-31)
packages/debian/control.in (+2/-13)
packages/debian/copyright (+25/-14)
packages/debian/dirs (+0/-1)
packages/debian/rules.in (+4/-1)
packages/pkg-deps.json (+85/-0)
packages/redhat/cloud-init.spec.in (+92/-84)
packages/suse/cloud-init.spec.in (+21/-33)
requirements.txt (+3/-6)
setup.py (+145/-87)
snapcraft.yaml (+20/-0)
systemd/cloud-config.service.tmpl (+1/-0)
systemd/cloud-final.service.tmpl (+8/-1)
systemd/cloud-init-generator (+37/-2)
systemd/cloud-init-local.service.tmpl (+5/-0)
systemd/cloud-init.service.tmpl (+20/-1)
systemd/cloud-init.target (+1/-1)
sysvinit/freebsd/cloudconfig (+0/-10)
sysvinit/freebsd/cloudfinal (+0/-10)
sysvinit/freebsd/cloudinit (+0/-10)
sysvinit/freebsd/cloudinitlocal (+2/-12)
sysvinit/gentoo/cloud-config (+0/-0)
sysvinit/gentoo/cloud-final (+0/-0)
sysvinit/gentoo/cloud-init (+0/-0)
sysvinit/gentoo/cloud-init-local (+0/-0)
sysvinit/redhat/cloud-config (+3/-16)
sysvinit/redhat/cloud-final (+3/-16)
sysvinit/redhat/cloud-init (+3/-16)
sysvinit/redhat/cloud-init-local (+3/-16)
sysvinit/suse/cloud-config (+113/-0)
sysvinit/suse/cloud-final (+113/-0)
sysvinit/suse/cloud-init (+114/-0)
sysvinit/suse/cloud-init-local (+113/-0)
templates/hosts.debian.tmpl (+2/-2)
templates/hosts.suse.tmpl (+8/-2)
templates/ntp.conf.opensuse.tmpl (+88/-0)
templates/ntp.conf.sles.tmpl (+0/-12)
templates/sources.list.debian.tmpl (+8/-10)
templates/timesyncd.conf.tmpl (+8/-0)
test-requirements.txt (+0/-6)
tests/cloud_tests/__init__.py (+38/-0)
tests/cloud_tests/__main__.py (+71/-0)
tests/cloud_tests/args.py (+301/-0)
tests/cloud_tests/bddeb.py (+119/-0)
tests/cloud_tests/collect.py (+205/-0)
tests/cloud_tests/config.py (+165/-0)
tests/cloud_tests/manage.py (+74/-0)
tests/cloud_tests/platforms.yaml (+70/-0)
tests/cloud_tests/platforms/__init__.py (+39/-0)
tests/cloud_tests/platforms/ec2/image.py (+99/-0)
tests/cloud_tests/platforms/ec2/instance.py (+132/-0)
tests/cloud_tests/platforms/ec2/platform.py (+258/-0)
tests/cloud_tests/platforms/ec2/snapshot.py (+66/-0)
tests/cloud_tests/platforms/images.py (+57/-0)
tests/cloud_tests/platforms/instances.py (+145/-0)
tests/cloud_tests/platforms/lxd/image.py (+193/-0)
tests/cloud_tests/platforms/lxd/instance.py (+232/-0)
tests/cloud_tests/platforms/lxd/platform.py (+108/-0)
tests/cloud_tests/platforms/lxd/snapshot.py (+53/-0)
tests/cloud_tests/platforms/nocloudkvm/image.py (+79/-0)
tests/cloud_tests/platforms/nocloudkvm/instance.py (+192/-0)
tests/cloud_tests/platforms/nocloudkvm/platform.py (+93/-0)
tests/cloud_tests/platforms/nocloudkvm/snapshot.py (+59/-0)
tests/cloud_tests/platforms/platforms.py (+96/-0)
tests/cloud_tests/platforms/snapshots.py (+45/-0)
tests/cloud_tests/releases.yaml (+261/-0)
tests/cloud_tests/run_funcs.py (+75/-0)
tests/cloud_tests/setup_image.py (+229/-0)
tests/cloud_tests/stage.py (+107/-0)
tests/cloud_tests/testcases.yaml (+43/-0)
tests/cloud_tests/testcases/__init__.py (+56/-0)
tests/cloud_tests/testcases/base.py (+137/-0)
tests/cloud_tests/testcases/bugs/README.md (+13/-0)
tests/cloud_tests/testcases/bugs/__init__.py (+8/-0)
tests/cloud_tests/testcases/bugs/lp1511485.py (+15/-0)
tests/cloud_tests/testcases/bugs/lp1511485.yaml (+11/-0)
tests/cloud_tests/testcases/bugs/lp1611074.yaml (+8/-0)
tests/cloud_tests/testcases/bugs/lp1628337.py (+23/-0)
tests/cloud_tests/testcases/bugs/lp1628337.yaml (+23/-0)
tests/cloud_tests/testcases/examples/README.md (+12/-0)
tests/cloud_tests/testcases/examples/TODO.md (+15/-0)
tests/cloud_tests/testcases/examples/__init__.py (+8/-0)
tests/cloud_tests/testcases/examples/add_apt_repositories.py (+20/-0)
tests/cloud_tests/testcases/examples/add_apt_repositories.yaml (+23/-0)
tests/cloud_tests/testcases/examples/alter_completion_message.py (+40/-0)
tests/cloud_tests/testcases/examples/alter_completion_message.yaml (+16/-0)
tests/cloud_tests/testcases/examples/configure_instance_trusted_ca_certificates.py (+27/-0)
tests/cloud_tests/testcases/examples/configure_instance_trusted_ca_certificates.yaml (+41/-0)
tests/cloud_tests/testcases/examples/configure_instances_ssh_keys.py (+31/-0)
tests/cloud_tests/testcases/examples/configure_instances_ssh_keys.yaml (+63/-0)
tests/cloud_tests/testcases/examples/including_user_groups.py (+49/-0)
tests/cloud_tests/testcases/examples/including_user_groups.yaml (+56/-0)
tests/cloud_tests/testcases/examples/install_arbitrary_packages.py (+20/-0)
tests/cloud_tests/testcases/examples/install_arbitrary_packages.yaml (+20/-0)
tests/cloud_tests/testcases/examples/install_run_chef_recipes.py (+17/-0)
tests/cloud_tests/testcases/examples/install_run_chef_recipes.yaml (+103/-0)
tests/cloud_tests/testcases/examples/run_apt_upgrade.py (+19/-0)
tests/cloud_tests/testcases/examples/run_apt_upgrade.yaml (+11/-0)
tests/cloud_tests/testcases/examples/run_commands.py (+15/-0)
tests/cloud_tests/testcases/examples/run_commands.yaml (+16/-0)
tests/cloud_tests/testcases/examples/run_commands_first_boot.py (+15/-0)
tests/cloud_tests/testcases/examples/run_commands_first_boot.yaml (+16/-0)
tests/cloud_tests/testcases/examples/setup_run_puppet.yaml (+55/-0)
tests/cloud_tests/testcases/examples/writing_out_arbitrary_files.py (+30/-0)
tests/cloud_tests/testcases/examples/writing_out_arbitrary_files.yaml (+45/-0)
tests/cloud_tests/testcases/main/README.md (+11/-0)
tests/cloud_tests/testcases/main/__init__.py (+8/-0)
tests/cloud_tests/testcases/main/command_output_simple.py (+34/-0)
tests/cloud_tests/testcases/main/command_output_simple.yaml (+13/-0)
tests/cloud_tests/testcases/modules/README.md (+12/-0)
tests/cloud_tests/testcases/modules/TODO.md (+98/-0)
tests/cloud_tests/testcases/modules/__init__.py (+8/-0)
tests/cloud_tests/testcases/modules/apt_configure_conf.py (+20/-0)
tests/cloud_tests/testcases/modules/apt_configure_conf.yaml (+21/-0)
tests/cloud_tests/testcases/modules/apt_configure_disable_suites.py (+15/-0)
tests/cloud_tests/testcases/modules/apt_configure_disable_suites.yaml (+20/-0)
tests/cloud_tests/testcases/modules/apt_configure_primary.py (+20/-0)
tests/cloud_tests/testcases/modules/apt_configure_primary.yaml (+26/-0)
tests/cloud_tests/testcases/modules/apt_configure_proxy.py (+22/-0)
tests/cloud_tests/testcases/modules/apt_configure_proxy.yaml (+18/-0)
tests/cloud_tests/testcases/modules/apt_configure_security.py (+15/-0)
tests/cloud_tests/testcases/modules/apt_configure_security.yaml (+18/-0)
tests/cloud_tests/testcases/modules/apt_configure_sources_key.py (+23/-0)
tests/cloud_tests/testcases/modules/apt_configure_sources_key.yaml (+50/-0)
tests/cloud_tests/testcases/modules/apt_configure_sources_keyserver.py (+23/-0)
tests/cloud_tests/testcases/modules/apt_configure_sources_keyserver.yaml (+23/-0)
tests/cloud_tests/testcases/modules/apt_configure_sources_list.py (+31/-0)
tests/cloud_tests/testcases/modules/apt_configure_sources_list.yaml (+28/-0)
tests/cloud_tests/testcases/modules/apt_configure_sources_ppa.py (+23/-0)
tests/cloud_tests/testcases/modules/apt_configure_sources_ppa.yaml (+29/-0)
tests/cloud_tests/testcases/modules/apt_pipelining_disable.py (+15/-0)
tests/cloud_tests/testcases/modules/apt_pipelining_disable.yaml (+15/-0)
tests/cloud_tests/testcases/modules/apt_pipelining_os.py (+15/-0)
tests/cloud_tests/testcases/modules/apt_pipelining_os.yaml (+15/-0)
tests/cloud_tests/testcases/modules/bootcmd.py (+15/-0)
tests/cloud_tests/testcases/modules/bootcmd.yaml (+13/-0)
tests/cloud_tests/testcases/modules/byobu.py (+25/-0)
tests/cloud_tests/testcases/modules/byobu.yaml (+20/-0)
tests/cloud_tests/testcases/modules/ca_certs.py (+20/-0)
tests/cloud_tests/testcases/modules/ca_certs.yaml (+52/-0)
tests/cloud_tests/testcases/modules/debug_disable.py (+16/-0)
tests/cloud_tests/testcases/modules/debug_disable.yaml (+9/-0)
tests/cloud_tests/testcases/modules/debug_enable.py (+15/-0)
tests/cloud_tests/testcases/modules/debug_enable.yaml (+9/-0)
tests/cloud_tests/testcases/modules/final_message.py (+40/-0)
tests/cloud_tests/testcases/modules/final_message.yaml (+13/-0)
tests/cloud_tests/testcases/modules/keys_to_console.py (+22/-0)
tests/cloud_tests/testcases/modules/keys_to_console.yaml (+15/-0)
tests/cloud_tests/testcases/modules/landscape.yaml (+28/-0)
tests/cloud_tests/testcases/modules/locale.py (+30/-0)
tests/cloud_tests/testcases/modules/locale.yaml (+22/-0)
tests/cloud_tests/testcases/modules/lxd_bridge.py (+26/-0)
tests/cloud_tests/testcases/modules/lxd_bridge.yaml (+32/-0)
tests/cloud_tests/testcases/modules/lxd_dir.py (+20/-0)
tests/cloud_tests/testcases/modules/lxd_dir.yaml (+19/-0)
tests/cloud_tests/testcases/modules/ntp.py (+25/-0)
tests/cloud_tests/testcases/modules/ntp.yaml (+21/-0)
tests/cloud_tests/testcases/modules/ntp_pools.py (+34/-0)
tests/cloud_tests/testcases/modules/ntp_pools.yaml (+31/-0)
tests/cloud_tests/testcases/modules/ntp_servers.py (+34/-0)
tests/cloud_tests/testcases/modules/ntp_servers.yaml (+27/-0)
tests/cloud_tests/testcases/modules/package_update_upgrade_install.py (+38/-0)
tests/cloud_tests/testcases/modules/package_update_upgrade_install.yaml (+33/-0)
tests/cloud_tests/testcases/modules/runcmd.py (+15/-0)
tests/cloud_tests/testcases/modules/runcmd.yaml (+13/-0)
tests/cloud_tests/testcases/modules/salt_minion.py (+29/-0)
tests/cloud_tests/testcases/modules/salt_minion.yaml (+34/-0)
tests/cloud_tests/testcases/modules/seed_random_command.yaml (+18/-0)
tests/cloud_tests/testcases/modules/seed_random_data.py (+15/-0)
tests/cloud_tests/testcases/modules/seed_random_data.yaml (+15/-0)
tests/cloud_tests/testcases/modules/set_hostname.py (+17/-0)
tests/cloud_tests/testcases/modules/set_hostname.yaml (+21/-0)
tests/cloud_tests/testcases/modules/set_hostname_fqdn.py (+31/-0)
tests/cloud_tests/testcases/modules/set_hostname_fqdn.yaml (+23/-0)
tests/cloud_tests/testcases/modules/set_password.py (+22/-0)
tests/cloud_tests/testcases/modules/set_password.yaml (+19/-0)
tests/cloud_tests/testcases/modules/set_password_expire.py (+23/-0)
tests/cloud_tests/testcases/modules/set_password_expire.yaml (+32/-0)
tests/cloud_tests/testcases/modules/set_password_list.py (+12/-0)
tests/cloud_tests/testcases/modules/set_password_list.yaml (+41/-0)
tests/cloud_tests/testcases/modules/set_password_list_string.py (+12/-0)
tests/cloud_tests/testcases/modules/set_password_list_string.yaml (+41/-0)
tests/cloud_tests/testcases/modules/snappy.py (+15/-0)
tests/cloud_tests/testcases/modules/snappy.yaml (+15/-0)
tests/cloud_tests/testcases/modules/ssh_auth_key_fingerprints_disable.py (+16/-0)
tests/cloud_tests/testcases/modules/ssh_auth_key_fingerprints_disable.yaml (+14/-0)
tests/cloud_tests/testcases/modules/ssh_auth_key_fingerprints_enable.py (+18/-0)
tests/cloud_tests/testcases/modules/ssh_auth_key_fingerprints_enable.yaml (+21/-0)
tests/cloud_tests/testcases/modules/ssh_import_id.py (+17/-0)
tests/cloud_tests/testcases/modules/ssh_import_id.yaml (+17/-0)
tests/cloud_tests/testcases/modules/ssh_keys_generate.py (+52/-0)
tests/cloud_tests/testcases/modules/ssh_keys_generate.yaml (+38/-0)
tests/cloud_tests/testcases/modules/ssh_keys_provided.py (+58/-0)
tests/cloud_tests/testcases/modules/ssh_keys_provided.yaml (+99/-0)
tests/cloud_tests/testcases/modules/timezone.py (+15/-0)
tests/cloud_tests/testcases/modules/timezone.yaml (+16/-0)
tests/cloud_tests/testcases/modules/user_groups.py (+49/-0)
tests/cloud_tests/testcases/modules/user_groups.yaml (+55/-0)
tests/cloud_tests/testcases/modules/write_files.py (+30/-0)
tests/cloud_tests/testcases/modules/write_files.yaml (+46/-0)
tests/cloud_tests/util.py (+494/-0)
tests/cloud_tests/verify.py (+96/-0)
tests/data/merge_sources/expected8.yaml (+1/-1)
tests/data/merge_sources/source8-1.yaml (+1/-1)
tests/unittests/__init__.py (+2/-0)
tests/unittests/test__init__.py (+61/-33)
tests/unittests/test_atomic_helper.py (+6/-2)
tests/unittests/test_builtin_handlers.py (+5/-1)
tests/unittests/test_cli.py (+244/-4)
tests/unittests/test_cs_util.py (+6/-1)
tests/unittests/test_data.py (+127/-1)
tests/unittests/test_datasource/test_aliyun.py (+76/-8)
tests/unittests/test_datasource/test_altcloud.py (+79/-91)
tests/unittests/test_datasource/test_azure.py (+698/-89)
tests/unittests/test_datasource/test_azure_helper.py (+103/-52)
tests/unittests/test_datasource/test_cloudsigma.py (+30/-7)
tests/unittests/test_datasource/test_cloudstack.py (+106/-9)
tests/unittests/test_datasource/test_common.py (+79/-0)
tests/unittests/test_datasource/test_configdrive.py (+31/-40)
tests/unittests/test_datasource/test_digitalocean.py (+77/-37)
tests/unittests/test_datasource/test_ec2.py (+638/-0)
tests/unittests/test_datasource/test_gce.py (+199/-38)
tests/unittests/test_datasource/test_maas.py (+49/-8)
tests/unittests/test_datasource/test_nocloud.py (+8/-8)
tests/unittests/test_datasource/test_opennebula.py (+190/-54)
tests/unittests/test_datasource/test_openstack.py (+24/-28)
tests/unittests/test_datasource/test_ovf.py (+275/-18)
tests/unittests/test_datasource/test_scaleway.py (+267/-0)
tests/unittests/test_datasource/test_smartos.py (+15/-23)
tests/unittests/test_distros/__init__.py (+21/-0)
tests/unittests/test_distros/test_arch.py (+45/-0)
tests/unittests/test_distros/test_create_users.py (+148/-0)
tests/unittests/test_distros/test_debian.py (+100/-0)
tests/unittests/test_distros/test_generic.py (+19/-1)
tests/unittests/test_distros/test_hostname.py (+4/-0)
tests/unittests/test_distros/test_hosts.py (+4/-0)
tests/unittests/test_distros/test_netconfig.py (+444/-8)
tests/unittests/test_distros/test_opensuse.py (+12/-0)
tests/unittests/test_distros/test_resolv.py (+7/-3)
tests/unittests/test_distros/test_sles.py (+12/-0)
tests/unittests/test_distros/test_sysconfig.py (+5/-1)
tests/unittests/test_distros/test_user_data_normalize.py (+5/-1)
tests/unittests/test_ds_identify.py (+564/-0)
tests/unittests/test_ec2_util.py (+52/-3)
tests/unittests/test_filters/test_launch_index.py (+5/-1)
tests/unittests/test_handler/test_handler_apt_conf_v1.py (+3/-1)
tests/unittests/test_handler/test_handler_apt_configure_sources_list_v1.py (+3/-1)
tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py (+76/-22)
tests/unittests/test_handler/test_handler_apt_source_v1.py (+3/-1)
tests/unittests/test_handler/test_handler_apt_source_v3.py (+3/-1)
tests/unittests/test_handler/test_handler_bootcmd.py (+146/-0)
tests/unittests/test_handler/test_handler_ca_certs.py (+5/-1)
tests/unittests/test_handler/test_handler_chef.py (+80/-12)
tests/unittests/test_handler/test_handler_debug.py (+11/-18)
tests/unittests/test_handler/test_handler_disk_setup.py (+125/-2)
tests/unittests/test_handler/test_handler_etc_hosts.py (+69/-0)
tests/unittests/test_handler/test_handler_growpart.py (+3/-1)
tests/unittests/test_handler/test_handler_landscape.py (+130/-0)
tests/unittests/test_handler/test_handler_locale.py (+62/-18)
tests/unittests/test_handler/test_handler_lxd.py (+13/-12)
tests/unittests/test_handler/test_handler_mcollective.py (+5/-1)
tests/unittests/test_handler/test_handler_mounts.py (+5/-1)
tests/unittests/test_handler/test_handler_ntp.py (+390/-204)
tests/unittests/test_handler/test_handler_power_state.py (+8/-7)
tests/unittests/test_handler/test_handler_puppet.py (+142/-0)
tests/unittests/test_handler/test_handler_resizefs.py (+317/-0)
tests/unittests/test_handler/test_handler_rsyslog.py (+5/-1)
tests/unittests/test_handler/test_handler_runcmd.py (+108/-0)
tests/unittests/test_handler/test_handler_seed_random.py (+6/-12)
tests/unittests/test_handler/test_handler_set_hostname.py (+8/-3)
tests/unittests/test_handler/test_handler_snappy.py (+8/-4)
tests/unittests/test_handler/test_handler_spacewalk.py (+5/-1)
tests/unittests/test_handler/test_handler_timezone.py (+6/-16)
tests/unittests/test_handler/test_handler_write_files.py (+34/-5)
tests/unittests/test_handler/test_handler_yum_add_repo.py (+49/-7)
tests/unittests/test_handler/test_handler_zypper_add_repo.py (+232/-0)
tests/unittests/test_handler/test_schema.py (+404/-0)
tests/unittests/test_helpers.py (+6/-2)
tests/unittests/test_log.py (+58/-0)
tests/unittests/test_merging.py (+5/-1)
tests/unittests/test_net.py (+2280/-93)
tests/unittests/test_pathprefix2dict.py (+3/-1)
tests/unittests/test_registry.py (+5/-1)
tests/unittests/test_reporting.py (+5/-4)
tests/unittests/test_rh_subscription.py (+21/-12)
tests/unittests/test_runs/test_merge_run.py (+6/-1)
tests/unittests/test_runs/test_simple_run.py (+111/-39)
tests/unittests/test_sshutil.py (+69/-2)
tests/unittests/test_templating.py (+7/-17)
tests/unittests/test_util.py (+229/-14)
tests/unittests/test_version.py (+14/-0)
tests/unittests/test_vmware/__init__.py (+0/-0)
tests/unittests/test_vmware/test_custom_script.py (+99/-0)
tests/unittests/test_vmware_config_file.py (+261/-18)
tools/21-cloudinit.conf (+1/-1)
tools/Z99-cloud-locale-test.sh (+77/-75)
tools/Z99-cloudinit-warnings.sh (+30/-0)
tools/build-on-freebsd (+2/-7)
tools/cloud-init-per (+1/-0)
tools/cloudconfig-schema (+35/-0)
tools/ds-identify (+1397/-0)
tools/hook-dhclient (+3/-0)
tools/hook-network-manager (+2/-0)
tools/hook-rhel.sh (+2/-0)
tools/make-mime.py (+4/-2)
tools/make-tarball (+1/-1)
tools/mock-meta.py (+49/-37)
tools/motd-hook (+4/-16)
tools/net-convert.py (+84/-0)
tools/read-dependencies (+253/-24)
tools/read-version (+17/-2)
tools/render-cloudcfg (+44/-0)
tools/run-centos (+352/-0)
tools/uncloud-init (+2/-1)
tools/validate-yaml.py (+5/-3)
tools/write-ssh-key-fingerprints (+1/-0)
tools/xkvm (+664/-0)
tox.ini (+75/-20)
Conflict in cloudinit/config/cc_puppet.py
Reviewer Review Type Date Requested Status
Chad Smith (community) Approve
Romanos Skiadas Pending
Review via email: mp+339373@code.launchpad.net

Description of the change

Rebase onto master

    Things changed:
     * replace/join DEFAULT_ variables with those that are now in
       trunk named PUPPET_
     * allow configuration of conf_file rather than conf_dir.

Also, shorten some verticle white space.

To post a comment you must log in.
Revision history for this message
Scott Moser (smoser) wrote :

Romanos,
This was mostly straight forward,
the only question was one I raised in the original review at
 https://code.launchpad.net/~rski/cloud-init/+git/cloud-init/+merge/312284

You made the config *dir* configurable, but assumed file name 'puppet.conf'
inside that. Trunk had previously mad a constant for the config file path.

Was there a reason you chose to allow config of the directory but not the filename?

Revision history for this message
Scott Moser (smoser) wrote :
Revision history for this message
Chad Smith (chad.smith) wrote :

+1 on this changeset. Thanks Romanos for the initial post and Scott for the rebase cleanup.

Validated on bionic ubuntu which delivers puppet v4, but repackages config directories to live in the same place as v3 on ubuntu.

I'd like use to update RTD content, but we can file a wishlist bug for this content (I can address that when adding json schema definitions to puppet module

review: Approve
Revision history for this message
Romanos Skiadas (rski) wrote :

>You made the config *dir* configurable, but assumed file name 'puppet.conf'
>inside that. Trunk had previously mad a constant for the config file path.

>Was there a reason you chose to allow config of the directory but not the filename?
If I recall correctly, it was because the filename was the same, but the directory was what changed, but practically either one could have been a variable.

Thanks for rebasing and merging this!

There was an error fetching revisions from git servers. Please try again in a few minutes. If the problem persists, contact Launchpad support.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.gitignore b/.gitignore
2index 865cac1..75565ed 100644
3--- a/.gitignore
4+++ b/.gitignore
5@@ -5,3 +5,9 @@ dist
6 __pycache__
7 .tox
8 .coverage
9+doc/rtd_html
10+parts
11+prime
12+stage
13+*.snap
14+*.cover
15diff --git a/.pylintrc b/.pylintrc
16new file mode 100644
17index 0000000..05a086d
18--- /dev/null
19+++ b/.pylintrc
20@@ -0,0 +1,60 @@
21+[MASTER]
22+
23+# --go-faster, use multiple processes to speed up Pylint
24+jobs=4
25+
26+
27+[MESSAGES CONTROL]
28+
29+# Errors and warings with some filtered:
30+# W0105(pointless-string-statement)
31+# W0107(unnecessary-pass)
32+# W0201(attribute-defined-outside-init)
33+# W0212(protected-access)
34+# W0221(arguments-differ)
35+# W0222(signature-differs)
36+# W0223(abstract-method)
37+# W0231(super-init-not-called)
38+# W0311(bad-indentation)
39+# W0511(fixme)
40+# W0602(global-variable-not-assigned)
41+# W0603(global-statement)
42+# W0611(unused-import)
43+# W0612(unused-variable)
44+# W0613(unused-argument)
45+# W0621(redefined-outer-name)
46+# W0622(redefined-builtin)
47+# W0631(undefined-loop-variable)
48+# W0703(broad-except)
49+# W1401(anomalous-backslash-in-string)
50+
51+disable=C, F, I, R, W0105, W0107, W0201, W0212, W0221, W0222, W0223, W0231, W0311, W0511, W0602, W0603, W0611, W0612, W0613, W0621, W0622, W0631, W0703, W1401
52+
53+
54+[REPORTS]
55+
56+# Set the output format. Available formats are text, parseable, colorized, msvs
57+output-format=parseable
58+
59+# Just the errors please, no full report
60+reports=no
61+
62+
63+[TYPECHECK]
64+
65+# List of module names for which member attributes should not be checked
66+# (useful for modules/projects where namespaces are manipulated during runtime
67+# and thus existing member attributes cannot be deduced by static analysis. It
68+# supports qualified module names, as well as Unix pattern matching.
69+ignored-modules=six.moves,pkg_resources,httplib,http.client,paramiko,simplestreams
70+
71+# List of class names for which member attributes should not be checked (useful
72+# for classes with dynamically set attributes). This supports the use of
73+# qualified names.
74+ignored-classes=optparse.Values,thread._local
75+
76+# List of members which are set dynamically and missed by pylint inference
77+# system, and so shouldn't trigger E1101 when accessed. Python regular
78+# expressions are accepted.
79+generated-members=types,http.client,command_handlers,m_.*
80+
81diff --git a/ChangeLog b/ChangeLog
82index 71df7ad..be4c357 100644
83--- a/ChangeLog
84+++ b/ChangeLog
85@@ -1,3 +1,659 @@
86+18.1:
87+ - OVF: Fix VMware support for 64-bit platforms. [Sankar Tanguturi]
88+ - ds-identify: Fix searching for iso9660 OVF cdroms. (LP: #1749980)
89+ - SUSE: Fix groups used for ownership of cloud-init.log [Robert Schweikert]
90+ - ds-identify: check /writable/system-data/ for nocloud seed.
91+ (LP: #1747070)
92+ - tests: run nosetests in cloudinit/ directory, fix py26 fallout.
93+ - tools: run-centos: git clone rather than tar.
94+ - tests: add support for logs with lxd from snap and future lxd 3.
95+ (LP: #1745663)
96+ - EC2: Fix get_instance_id called against cached datasource pickle.
97+ (LP: #1748354)
98+ - cli: fix cloud-init status to report running when before result.json
99+ (LP: #1747965)
100+ - net: accept network-config in netplan format for renaming interfaces
101+ (LP: #1709715)
102+ - Fix ssh keys validation in ssh_util [Tatiana Kholkina]
103+ - docs: Update RTD content for cloud-init subcommands.
104+ - OVF: Extend well-known labels to include OVFENV. (LP: #1698669)
105+ - Fix potential cases of uninitialized variables. (LP: #1744796)
106+ - tests: Collect script output as binary, collect systemd journal, fix lxd.
107+ - HACKING.rst: mention setting user name and email via git config.
108+ - Azure VM Preprovisioning support. [Douglas Jordan] (LP: #1734991)
109+ - tools/read-version: Fix read-version when in a git worktree.
110+ - docs: Fix typos in docs and one debug message. [Florian Grignon]
111+ - btrfs: support resizing if root is mounted ro.
112+ [Robert Schweikert] (LP: #1734787)
113+ - OpenNebula: Improve network configuration support.
114+ [Akihiko Ota] (LP: #1719157, #1716397, #1736750)
115+ - tests: Fix EC2 Platform to return console output as bytes.
116+ - tests: Fix attempted use of /run in a test case.
117+ - GCE: Improvements and changes to ssh key behavior for default user.
118+ [Max Illfelder] (LP: #1670456, #1707033, #1707037, #1707039)
119+ - subp: make ProcessExecutionError have expected types in stderr, stdout.
120+ - tests: when querying ntp server, do not do dns resolution.
121+ - Recognize uppercase vfat disk labels [James Penick] (LP: #1598783)
122+ - tests: remove zesty as supported OS to test [Joshua Powers]
123+ - Do not log warning on config files that represent None. (LP: #1742479)
124+ - tests: Use git hash pip dependency format for pylxd.
125+ - tests: add integration requirements text file [Joshua Powers]
126+ - MAAS: add check_instance_id based off oauth tokens. (LP: #1712680)
127+ - tests: update apt sources list test [Joshua Powers]
128+ - tests: clean up image properties [Joshua Powers]
129+ - tests: rename test ssh keys to avoid appearance of leaking private keys.
130+ [Joshua Powers]
131+ - tests: Enable AWS EC2 Integration Testing [Joshua Powers]
132+ - cli: cloud-init clean handles symlinks (LP: #1741093)
133+ - SUSE: Add a basic test of network config rendering. [Robert Schweikert]
134+ - Azure: Only bounce network when necessary. (LP: #1722668)
135+ - lint: Fix lints seen by pylint version 1.8.1.
136+ - cli: Fix error in cloud-init modules --mode=init. (LP: #1736600)
137+
138+17.2:
139+ - ds-identify: failure in NoCloud due to unset variable usage.
140+ (LP: #1737704)
141+ - tests: fix collect_console when not implemented [Joshua Powers]
142+ - ec2: Use instance-identity doc for region and instance-id
143+ [Andrew Jorgensen]
144+ - tests: remove leaked tmp files in config drive tests.
145+ - setup.py: Do not include rendered files in SOURCES.txt
146+ - SUSE: remove delta in systemd local template for SUSE [Robert Schweikert]
147+ - tests: move to using tox 1.7.5
148+ - OVF: improve ds-identify to support finding OVF iso transport.
149+ (LP: #1731868)
150+ - VMware: Support for user provided pre and post-customization scripts
151+ [Maitreyee Saikia]
152+ - citest: In NoCloudKVM provide keys via metadata not userdata.
153+ - pylint: Update pylint to 1.7.1, run on tests/ and tools and fix
154+ complaints.
155+ - Datasources: Formalize DataSource get_data and related properties.
156+ - cli: Add clean and status subcommands
157+ - tests: consolidate platforms into specific dirs
158+ - ec2: Fix sandboxed dhclient background process cleanup. (LP: #1735331)
159+ - tests: NoCloudKVMImage do not modify the original local cache image.
160+ - tests: Enable bionic in integration tests. [Joshua Powers]
161+ - tests: Use apt-get to install a deb so that depends get resolved.
162+ - sysconfig: Correctly render dns and dns search info.
163+ [Ryan McCabe] (LP: #1705804)
164+ - integration test: replace curtin test ppa with cloud-init test ppa.
165+ - EC2: Fix bug using fallback_nic and metadata when restoring from cache.
166+ (LP: #1732917)
167+ - EC2: Kill dhclient process used in sandbox dhclient. (LP: #1732964)
168+ - ntp: fix configuration template rendering for openSUSE and SLES
169+ (LP: #1726572)
170+ - centos: Provide the failed #include url in error messages
171+ - Catch UrlError when #include'ing URLs [Andrew Jorgensen]
172+ - hosts: Fix openSUSE and SLES setup for /etc/hosts and clarify docs.
173+ [Robert Schweikert] (LP: #1731022)
174+ - rh_subscription: Perform null checks for enabled and disabled repos.
175+ [Dave Mulford]
176+ - Improve warning message when a template is not found.
177+ [Robert Schweikert] (LP: #1731035)
178+ - Replace the temporary i9n.brickies.net with i9n.cloud-init.io.
179+ - Azure: don't generate network configuration for SRIOV devices
180+ (LP: #1721579)
181+ - tests: address some minor feedback missed in last merge.
182+ - tests: integration test cleanup and full pass of nocloud-kvm.
183+ - Gentoo: chmod +x on all files in sysvinit/gentoo/
184+ [ckonstanski] (LP: #1727126)
185+ - EC2: Limit network config to fallback nic, fix local-ipv4 only
186+ instances. (LP: #1728152)
187+ - Gentoo: Use "rc-service" rather than "service".
188+ [Carlos Konstanski] (LP: #1727121)
189+ - resizefs: Fix regression when system booted with root=PARTUUID=
190+ (LP: #1725067)
191+ - tools: make yum package installation more reliable
192+ - citest: fix remaining warnings raised by integration tests.
193+ - citest: show the class actual class name in results.
194+ - ntp: fix config module schema to allow empty ntp config (LP: #1724951)
195+ - tools: disable fastestmirror if using proxy [Joshua Powers]
196+ - schema: Log debug instead of warning when jsonschema is not available.
197+ (LP: #1724354)
198+ - simpletable: Fix get_string method to return table-formatted string
199+ (LP: #1722566)
200+ - net: Handle bridge stp values of 0 and convert to boolean type
201+ - tools: Give specific --abbrev=8 to "git describe"
202+ - network: bridge_stp value not always correct (LP: #1721157)
203+ - tests: re-enable tox with nocloud-kvm support [Joshua Powers]
204+ - systemd: remove limit on tasks created by cloud-init-final.service.
205+ [Robert Schweikert] (LP: #1717969)
206+ - suse: Support addition of zypper repos via cloud-config.
207+ [Robert Schweikert] (LP: #1718675)
208+ - tests: Combine integration configs and testcases [Joshua Powers]
209+ - Azure, CloudStack: Support reading dhcp options from systemd-networkd.
210+ [Dimitri John Ledkov] (LP: #1718029)
211+ - packages/debian/copyright: remove mention of boto and MIT license
212+ - systemd: only mention Before=apt-daily.service on debian based distros.
213+ [Robert Schweikert]
214+ - Add missing simpletable and simpletable tests for failed merge
215+ - Remove prettytable dependency, introduce simpletable [Andrew Jorgensen]
216+ - debian/copyright: dep5 updates, reorganize, add Apache 2.0 license.
217+ [Joshua Powers] (LP: #1718681)
218+ - tests: remove dependency on shlex [Joshua Powers]
219+ - AltCloud: Trust PATH for udevadm and modprobe.
220+ - DataSourceOVF: use util.find_devs_with(TYPE=iso9660) (LP: #1718287)
221+ - tests: remove a temp file used in bootcmd tests.
222+
223+17.1:
224+ - doc: document GCE datasource. [Arnd Hannemann]
225+ - suse: updates to templates to support openSUSE and SLES.
226+ [Robert Schweikert] (LP: #1718640)
227+ - suse: Copy sysvinit files from redhat with slight changes.
228+ [Robert Schweikert] (LP: #1718649)
229+ - docs: fix sphinx module schema documentation [Chad Smith]
230+ - tests: Add cloudinit package to all test targets [Chad Smith]
231+ - Makefile: No longer look for yaml files in obsolete ./bin/.
232+ - tests: fix ds-identify unit tests to set EC2_STRICT_ID_DEFAULT.
233+ - ec2: Fix maybe_perform_dhcp_discovery to use /var/tmp as a tmpdir
234+ [Chad Smith] (LP: #1717627)
235+ - Azure: wait longer for SSH pub keys to arrive.
236+ [Paul Meyer] (LP: #1717611)
237+ - GCE: Fix usage of user-data. (LP: #1717598)
238+ - cmdline: add collect-logs subcommand. [Chad Smith] (LP: #1607345)
239+ - CloudStack: consider dhclient lease files named with a hyphen.
240+ (LP: #1717147)
241+ - resizefs: Drop check for read-only device file, do not warn on
242+ overlayroot. [Chad Smith]
243+ - Do not provide systemd-fsck drop-in which could cause ordering cycles.
244+ [Balint Reczey] (LP: #1717477)
245+ - tests: Enable the NoCloud KVM platform [Joshua Powers]
246+ - resizefs: pass mount point to xfs_growfs [Dusty Mabe]
247+ - vmware: Enable nics before sending the SUCCESS event. [Sankar Tanguturi]
248+ - cloud-config modules: honor distros definitions in each module
249+ [Chad Smith] (LP: #1715738, #1715690)
250+ - chef: Add option to pin chef omnibus install version
251+ [Ethan Apodaca] (LP: #1462693)
252+ - tests: execute: support command as string [Joshua Powers]
253+ - schema and docs: Add jsonschema to resizefs and bootcmd modules
254+ [Chad Smith]
255+ - tools: Add xkvm script, wrapper around qemu-system [Joshua Powers]
256+ - vmware customization: return network config format
257+ [Sankar Tanguturi] (LP: #1675063)
258+ - Ec2: only attempt to operate at local mode on known platforms.
259+ (LP: #1715128)
260+ - Use /run/cloud-init for tempfile operations. (LP: #1707222)
261+ - ds-identify: Make OpenStack return maybe on arch other than intel.
262+ (LP: #1715241)
263+ - tests: mock missed openstack metadata uri network_data.json
264+ [Chad Smith] (LP: #1714376)
265+ - relocate tests/unittests/helpers.py to cloudinit/tests
266+ [Lars Kellogg-Stedman]
267+ - tox: add nose timer output [Joshua Powers]
268+ - upstart: do not package upstart jobs, drop ubuntu-init-switch module.
269+ - tests: Stop leaking calls through unmocked metadata addresses
270+ [Chad Smith] (LP: #1714117)
271+ - distro: allow distro to specify a default locale [Ryan Harper]
272+ - tests: fix two recently added tests for sles distro.
273+ - url_helper: dynamically import oauthlib import from inside oauth_headers
274+ [Chad Smith]
275+ - tox: make xenial environment run with python3.6
276+ - suse: Add support for openSUSE and return SLES to a working state.
277+ [Robert Schweikert]
278+ - GCE: Add a main to the GCE Datasource.
279+ - ec2: Add IPv6 dhcp support to Ec2DataSource. [Chad Smith] (LP: #1639030)
280+ - url_helper: fail gracefully if oauthlib is not available
281+ [Lars Kellogg-Stedman] (LP: #1713760)
282+ - cloud-init analyze: fix issues running under python 2. [Andrew Jorgensen]
283+ - Configure logging module to always use UTC time.
284+ [Ryan Harper] (LP: #1713158)
285+ - Log a helpful message if a user script does not include shebang.
286+ [Andrew Jorgensen]
287+ - cli: Fix command line parsing of coniditionally loaded subcommands.
288+ [Chad Smith] (LP: #1712676)
289+ - doc: Explain error behavior in user data include file format.
290+ [Jason Butz]
291+ - cc_landscape & cc_puppet: Fix six.StringIO use in writing configs
292+ [Chad Smith] (LP: #1699282, #1710932)
293+ - schema cli: Add schema subcommand to cloud-init cli and cc_runcmd schema
294+ [Chad Smith]
295+ - Debian: Remove non-free repositories from apt sources template.
296+ [Joonas Kylmälä] (LP: #1700091)
297+ - tools: Add tooling for basic cloud-init performance analysis.
298+ [Chad Smith] (LP: #1709761)
299+ - network: add v2 passthrough and fix parsing v2 config with bonds/bridge
300+ params [Ryan Harper] (LP: #1709180)
301+ - doc: update capabilities with features available, link doc reference,
302+ cli example [Ryan Harper]
303+ - vcloud directory: Guest Customization support for passwords
304+ [Maitreyee Saikia]
305+ - ec2: Allow Ec2 to run in init-local using dhclient in a sandbox.
306+ [Chad Smith] (LP: #1709772)
307+ - cc_ntp: fallback on timesyncd configuration if ntp is not installable
308+ [Ryan Harper] (LP: #1686485)
309+ - net: Reduce duplicate code. Have get_interfaces_by_mac use
310+ get_interfaces.
311+ - tests: Fix build tree integration tests [Joshua Powers]
312+ - sysconfig: Dont repeat header when rendering resolv.conf
313+ [Ryan Harper] (LP: #1701420)
314+ - archlinux: Fix bug with empty dns, do not render 'lo' devices.
315+ (LP: #1663045, #1706593)
316+ - cloudinit.net: add initialize_network_device function and tests
317+ [Chad Smith]
318+ - makefile: fix ci-deps-ubuntu target [Chad Smith]
319+ - tests: adjust locale integration test to parse default locale.
320+ - tests: remove 'yakkety' from releases as it is EOL.
321+ - tests: Add initial tests for EC2 and improve a docstring.
322+ - locale: Do not re-run locale-gen if provided locale is system default.
323+ - archlinux: fix set hostname usage of write_file.
324+ [Joshua Powers] (LP: #1705306)
325+ - sysconfig: support subnet type of 'manual'.
326+ - tools/run-centos: make running with no argument show help.
327+ - Drop rand_str() usage in DNS redirection detection
328+ [Bob Aman] (LP: #1088611)
329+ - sysconfig: use MACADDR on bonds/bridges to configure mac_address
330+ [Ryan Harper] (LP: #1701417)
331+ - net: eni route rendering missed ipv6 default route config
332+ [Ryan Harper] (LP: #1701097)
333+ - sysconfig: enable mtu set per subnet, including ipv6 mtu
334+ [Ryan Harper] (LP: #1702513)
335+ - sysconfig: handle manual type subnets [Ryan Harper] (LP: #1687725)
336+ - sysconfig: fix ipv6 gateway routes [Ryan Harper] (LP: #1694801)
337+ - sysconfig: fix rendering of bond, bridge and vlan types.
338+ [Ryan Harper] (LP: #1695092)
339+ - Templatize systemd unit files for cross distro deltas. [Ryan Harper]
340+ - sysconfig: ipv6 and default gateway fixes. [Ryan Harper] (LP: #1704872)
341+ - net: fix renaming of nics to support mac addresses written in upper
342+ case. (LP: #1705147)
343+ - tests: fixes for issues uncovered when moving to python 3.6.
344+ (LP: #1703697)
345+ - sysconfig: include GATEWAY value if set in subnet
346+ [Ryan Harper] (LP: #1686856)
347+ - Scaleway: add datasource with user and vendor data for Scaleway.
348+ [Julien Castets]
349+ - Support comments in content read by load_shell_content.
350+ - cloudinitlocal fail to run during boot [Hongjiang Zhang]
351+ - doc: fix disk setup example table_type options
352+ [Sandor Zeestraten] (LP: #1703789)
353+ - tools: Fix exception handling. [Joonas Kylmälä] (LP: #1701527)
354+ - tests: fix usage of mock in GCE test.
355+ - test_gce: Fix invalid mock of platform_reports_gce to return False
356+ [Chad Smith]
357+ - test: fix incorrect keyid for apt repository.
358+ [Joshua Powers] (LP: #1702717)
359+ - tests: Update version of pylxd [Joshua Powers]
360+ - write_files: Remove log from helper function signatures.
361+ [Andrew Jorgensen]
362+ - doc: document the cmdline options to NoCloud [Brian Candler]
363+ - read_dmi_data: always return None when inside a container. (LP: #1701325)
364+ - requirements.txt: remove trailing white space.
365+ - Azure: Add network-config, Refactor net layer to handle duplicate macs.
366+ [Ryan Harper]
367+ - Tests: Simplify the check on ssh-import-id [Joshua Powers]
368+ - tests: update ntp tests after sntp added [Joshua Powers]
369+ - FreeBSD: Make freebsd a variant, fix unittests and
370+ tools/build-on-freebsd.
371+ - FreeBSD: fix test failure
372+ - FreeBSD: replace ifdown/ifup with "ifconfig down" and "ifconfig up".
373+ [Hongjiang Zhang] (LP: #1697815)
374+ - FreeBSD: fix cdrom mounting failure if /mnt/cdrom/secure did not exist.
375+ [Hongjiang Zhang] (LP: #1696295)
376+ - main: Don't use templater to format the welcome message
377+ [Andrew Jorgensen]
378+ - docs: Automatically generate module docs form schema if present.
379+ [Chad Smith]
380+ - debian: fix path comment in /etc/hosts template.
381+ [Jens Sandmann] (LP: #1606406)
382+ - suse: add hostname and fully qualified domain to template.
383+ [Jens Sandmann]
384+ - write_file(s): Print permissions as octal, not decimal [Andrew Jorgensen]
385+ - ci deps: Add --test-distro to read-dependencies to install all deps
386+ [Chad Smith]
387+ - tools/run-centos: cleanups and move to using read-dependencies
388+ - pkg build ci: Add make ci-deps-<distro> target to install pkgs
389+ [Chad Smith]
390+ - systemd: make cloud-final.service run before apt daily services.
391+ (LP: #1693361)
392+ - selinux: Allow restorecon to be non-fatal. [Ryan Harper] (LP: #1686751)
393+ - net: Allow netinfo subprocesses to return 0 or 1.
394+ [Ryan Harper] (LP: #1686751)
395+ - net: Allow for NetworkManager configuration [Ryan McCabe] (LP: #1693251)
396+ - Use distro release version to determine if we use systemd in redhat spec
397+ [Ryan Harper]
398+ - net: normalize data in network_state object
399+ - Integration Testing: tox env, pyxld 2.2.3, and revamp framework
400+ [Wesley Wiedenmeier]
401+ - Chef: Update omnibus url to chef.io, minor doc changes. [JJ Asghar]
402+ - tools: add centos scripts to build and test [Joshua Powers]
403+ - Drop cheetah python module as it is not needed by trunk [Ryan Harper]
404+ - rhel/centos spec cleanups.
405+ - cloud.cfg: move to a template. setup.py changes along the way.
406+ - Makefile: add deb-src and srpm targets. use PYVER more places.
407+ - makefile: fix python 2/3 detection in the Makefile [Chad Smith]
408+ - snap: Removing snapcraft plug line [Joshua Powers] (LP: #1695333)
409+ - RHEL/CentOS: Fix default routes for IPv4/IPv6 configuration.
410+ [Andreas Karis] (LP: #1696176)
411+ - test: Fix pyflakes complaint of unused import.
412+ [Joshua Powers] (LP: #1695918)
413+ - NoCloud: support seed of nocloud from smbios information
414+ [Vladimir Pouzanov] (LP: #1691772)
415+ - net: when selecting a network device, use natural sort order
416+ [Marc-Aurèle Brothier]
417+ - fix typos and remove whitespace in various docs [Stephan Telling]
418+ - systemd: Fix typo in comment in cloud-init.target. [Chen-Han Hsiao]
419+ - Tests: Skip jsonschema related unit tests when dependency is absent.
420+ [Chad Smith] (LP: #1695318)
421+ - azure: remove accidental duplicate line in merge.
422+ - azure: identify platform by well known value in chassis asset tag.
423+ [Chad Smith] (LP: #1693939)
424+ - tools/net-convert.py: support old cloudinit versions by using kwargs.
425+ - ntp: Add schema definition and passive schema validation.
426+ [Chad Smith] (LP: #1692916)
427+ - Fix eni rendering for bridge params that require repeated key for
428+ values. [Ryan Harper]
429+ - net: remove systemd link file writing from eni renderer [Ryan Harper]
430+ - AliYun: Enable platform identification and enable by default.
431+ [Junjie Wang] (LP: #1638931)
432+ - net: fix reading and rendering addresses in cidr format.
433+ [Dimitri John Ledkov] (LP: #1689346, #1684349)
434+ - disk_setup: udev settle before attempting partitioning or fs creation.
435+ (LP: #1692093)
436+ - GCE: Update the attribute used to find instance SSH keys.
437+ [Daniel Watkins] (LP: #1693582)
438+ - nplan: For bonds, allow dashed or underscore names of keys.
439+ [Dimitri John Ledkov] (LP: #1690480)
440+ - python2.6: fix unit tests usage of assertNone and format.
441+ - test: update docstring on test_configured_list_with_none
442+ - fix tools/ds-identify to not write None twice.
443+ - tox/build: do not package depend on style requirements.
444+ - cc_ntp: Restructure cc_ntp unit tests. [Chad Smith] (LP: #1692794)
445+ - flake8: move the pinned version of flake8 up to 3.3.0
446+ - tests: Apply workaround for snapd bug in test case. [Joshua Powers]
447+ - RHEL/CentOS: Fix dual stack IPv4/IPv6 configuration.
448+ [Andreas Karis] (LP: #1679817, #1685534, #1685532)
449+ - disk_setup: fix several issues with gpt disk partitions. (LP: #1692087)
450+ - function spelling & docstring update [Joshua Powers]
451+ - Fixing wrong file name regression. [Joshua Powers]
452+ - tox: move pylint target to 1.7.1
453+ - Fix get_interfaces_by_mac for empty macs (LP: #1692028)
454+ - DigitalOcean: remove routes except for the public interface.
455+ [Ben Howard] (LP: #1681531.)
456+ - netplan: pass macaddress, when specified, for vlans
457+ [Dimitri John Ledkov] (LP: #1690388)
458+ - doc: various improvements for the docs on cc_users_groups.
459+ [Felix Dreissig]
460+ - cc_ntp: write template before installing and add service restart
461+ [Ryan Harper] (LP: #1645644)
462+ - cloudstack: fix tests to avoid accessing /var/lib/NetworkManager
463+ [Lars Kellogg-Stedman]
464+ - tests: fix hardcoded path to mkfs.ext4 [Joshua Powers] (LP: #1691517)
465+ - Actually skip warnings when .skip file is present.
466+ [Chris Brinker] (LP: #1691551)
467+ - netplan: fix netplan render_network_state signature.
468+ [Dimitri John Ledkov] (LP: #1685944)
469+ - Azure: fix reformatting of ephemeral disks on resize to large types.
470+ (LP: #1686514)
471+ - Revert "tools/net-convert: fix argument order for render_network_state"
472+ - make deb: Add devscripts dependency for make deb. Cleanup
473+ packages/bddeb. [Chad Smith] (LP: #1685935)
474+ - tools/net-convert: fix argument order for render_network_state
475+ [Ryan Harper] (LP: #1685944)
476+ - openstack: fix log message copy/paste typo in _get_url_settings
477+ [Lars Kellogg-Stedman]
478+ - unittests: fix unittests run on centos [Joshua Powers]
479+ - Improve detection of snappy to include os-release and kernel cmdline.
480+ (LP: #1689944)
481+ - Add address to config entry generated by _klibc_to_config_entry.
482+ [Julien Castets] (LP: #1691135)
483+ - sysconfig: Raise ValueError when multiple default gateways are present.
484+ [Chad Smith] (LP: #1687485)
485+ - FreeBSD: improvements and fixes for use on Azure
486+ [Hongjiang Zhang] (LP: #1636345)
487+ - Add unit tests for ds-identify, fix Ec2 bug found.
488+ - fs_setup: if cmd is specified, use shell interpretation.
489+ [Paul Meyer] (LP: #1687712)
490+ - doc: document network configuration defaults policy and formats.
491+ [Ryan Harper]
492+ - Fix name of "uri" key in docs for "cc_apt_configure" module
493+ [Felix Dreissig]
494+ - tests: Enable artful [Joshua Powers]
495+ - nova-lxd: read product_name from environment, not platform.
496+ (LP: #1685810)
497+ - Fix yum repo config where keys contain array values
498+ [Dylan Perry] (LP: #1592150)
499+ - template: Update debian backports template [Joshua Powers] (LP: #1627293)
500+ - rsyslog: replace ~ with stop [Joshua Powers] (LP: #1367899)
501+ - Doc: add additional RTD examples [Joshua Powers] (LP: #1459604)
502+ - Fix growpart for some cases when booted with root=PARTUUID.
503+ (LP: #1684869)
504+ - pylint: update output style to parseable [Joshua Powers]
505+ - pylint: fix all logging warnings [Joshua Powers]
506+ - CloudStack: Add NetworkManager to list of supported DHCP lease dirs.
507+ [Syed]
508+ - net: kernel lies about vlans not stealing mac addresses, when they do
509+ [Dimitri John Ledkov] (LP: #1682871)
510+ - ds-identify: Check correct path for "latest" config drive
511+ [Daniel Watkins] (LP: #1673637)
512+ - doc: Fix example for resolve.conf configuration.
513+ [Jon Grimm] (LP: #1531582)
514+ - Fix examples that reference upstream chef repository.
515+ [Jon Grimm] (LP: #1678145)
516+ - doc: correct grammar and improve clarity in merging documentation.
517+ [David Tagatac]
518+ - doc: Add missing doc link to snap-config module. [Ryan Harper]
519+ - snap: allows for creating cloud-init snap [Joshua Powers]
520+ - DigitalOcean: assign IPv4ll address to lowest indexed interface.
521+ [Ben Howard]
522+ - DigitalOcean: configure all NICs presented in meta-data. [Ben Howard]
523+ - Remove (and/or fix) URL shortener references [Jon Grimm] (LP: #1669727)
524+ - HACKING.rst: more info on filling out contributors agreement.
525+ - util: teach write_file about copy_mode option
526+ [Lars Kellogg-Stedman] (LP: #1644064)
527+ - DigitalOcean: bind resolvers to loopback interface. [Ben Howard]
528+ - tests: fix AltCloud tests to not rely on blkid (LP: #1636531)
529+ - OpenStack: add 'dvs' to the list of physical link types. (LP: #1674946)
530+ - Fix bug that resulted in an attempt to rename bonds or vlans.
531+ (LP: #1669860)
532+ - tests: update OpenNebula and Digital Ocean to not rely on host
533+ interfaces.
534+ - net: in netplan renderer delete known image-builtin content.
535+ (LP: #1675576)
536+ - doc: correct grammar in capabilities.rst [David Tagatac]
537+ - ds-identify: fix detecting of maas datasource. (LP: #1677710)
538+ - netplan: remove debugging prints, add debug logging [Ryan Harper]
539+ - ds-identify: do not write None twice to datasource_list.
540+ - support resizing partition and rootfs on system booted without
541+ initramfs. [Steve Langasek] (LP: #1677376)
542+ - apt_configure: run only when needed. (LP: #1675185)
543+ - OpenStack: identify OpenStack by product 'OpenStack Compute'.
544+ (LP: #1675349)
545+ - GCE: Search GCE in ds-identify, consider serial number in check.
546+ (LP: #1674861)
547+ - Add support for setting hashed passwords [Tore S. Lonoy] (LP: #1570325)
548+ - Fix filesystem creation when using "partition: auto"
549+ [Jonathan Ballet] (LP: #1634678)
550+ - ConfigDrive: support reading config drive data from /config-drive.
551+ (LP: #1673411)
552+ - ds-identify: fix detection of Bigstep datasource. (LP: #1674766)
553+ - test: add running of pylint [Joshua Powers]
554+ - ds-identify: fix bug where filename expansion was left on.
555+ - advertise network config v2 support (NETWORK_CONFIG_V2) in features.
556+ - Bigstep: fix bug when executing in python3. [root]
557+ - Fix unit test when running in a system deployed with cloud-init.
558+ - Bounce network interface for Azure when using the built-in path.
559+ [Brent Baude] (LP: #1674685)
560+ - cloudinit.net: add network config v2 parsing and rendering [Ryan Harper]
561+ - net: Fix incorrect call to isfile [Joshua Powers] (LP: #1674317)
562+ - net: add renderers for automatically selecting the renderer.
563+ - doc: fix config drive doc with regard to unpartitioned disks.
564+ (LP: #1673818)
565+ - test: Adding integratiron test for password as list [Joshua Powers]
566+ - render_network_state: switch arguments around, do not require target
567+ - support 'loopback' as a device type.
568+ - Integration Testing: improve testcase subclassing [Wesley Wiedenmeier]
569+ - gitignore: adding doc/rtd_html [Joshua Powers]
570+ - doc: add instructions for running integration tests via tox.
571+ [Joshua Powers]
572+ - test: avoid differences in 'date' output due to daylight savings.
573+ - Fix chef config module in omnibus install. [Jeremy Melvin] (LP: #1583837)
574+ - Add feature flags to cloudinit.version. [Wesley Wiedenmeier]
575+ - tox: add a citest environment
576+ - Further fix regression to support 'password' for default user.
577+ - fix regression when no chpasswd/list was provided.
578+ - Support chpasswd/list being a list in addition to a string.
579+ [Sergio Lystopad] (LP: #1665694)
580+ - doc: Fix configuration example for cc_set_passwords module.
581+ [Sergio Lystopad] (LP: #1665773)
582+ - net: support both ipv4 and ipv6 gateways in sysconfig.
583+ [Lars Kellogg-Stedman] (LP: #1669504)
584+ - net: do not raise exception for > 3 nameservers
585+ [Lars Kellogg-Stedman] (LP: #1670052)
586+ - ds-identify: report cleanups for config and exit value. (LP: #1669949)
587+ - ds-identify: move default setting for Ec2/strict_id to a global.
588+ - ds-identify: record not found in cloud.cfg and always add None.
589+ - Support warning if the used datasource is not in ds-identify's list.
590+ - tools/ds-identify: make report mode write namespaced results.
591+ - Move warning functionality to cloudinit/warnings.py
592+ - Add profile.d script for showing warnings on login.
593+ - Z99-cloud-locale-test.sh: install and make consistent.
594+ - tools/ds-identify: look at cloud.cfg when looking for ec2 strict_id.
595+ - tools/ds-identify: disable vmware_guest_customization by default.
596+ - tools/ds-identify: ovf identify vmware guest customization.
597+ - Identify Brightbox as an Ec2 datasource user. (LP: #1661693)
598+ - DatasourceEc2: add warning message when not on AWS.
599+ - ds-identify: add reading of datasource/Ec2/strict_id
600+ - tools/ds-identify: add support for found or maybe contributing config.
601+ - tools/ds-identify: read the seed directory on Ec2
602+ - tools/ds-identify: use quotes in local declarations.
603+ - tools/ds-identify: fix documentation of policy setting in a comment.
604+ - ds-identify: only run once per boot unless --force is given.
605+ - flake8: fix flake8 complaints in previous commit.
606+ - net: correct errors in cloudinit/net/sysconfig.py
607+ [Lars Kellogg-Stedman] (LP: #1665441)
608+ - ec2_utils: fix MetadataLeafDecoder that returned bytes on empty
609+ - apply the runtime configuration written by ds-identify.
610+ - ds-identify: fix checking for filesystem label (LP: #1663735)
611+ - ds-identify: read ds=nocloud properly (LP: #1663723)
612+ - support nova-lxd by reading platform from environment of pid 1.
613+ (LP: #1661797)
614+ - ds-identify: change aarch64 to use the default for non-dmi systems.
615+ - Remove style checking during build and add latest style checks to tox
616+ [Joshua Powers] (LP: #1652329)
617+ - code-style: make master pass pycodestyle (2.3.1) cleanly, currently:
618+ [Joshua Powers]
619+ - manual_cache_clean: When manually cleaning touch a file in instance dir.
620+ - Add tools/ds-identify to identify datasources available.
621+ - Fix small typo and change iso-filename for consistency [Robin Naundorf]
622+ - Fix eni rendering of multiple IPs per interface
623+ [Ryan Harper] (LP: #1657940)
624+ - tools/mock-meta: support python2 or python3 and ipv6 in both.
625+ - tests: remove executable bit on test_net, so it runs, and fix it.
626+ - tests: No longer monkey patch httpretty for python 3.4.2
627+ - Add 3 ecdsa-sha2-nistp* ssh key types now that they are standardized
628+ [Lars Kellogg-Stedman] (LP: #1658174)
629+ - reset httppretty for each test [Lars Kellogg-Stedman] (LP: #1658200)
630+ - build: fix running Make on a branch with tags other than master
631+ - EC2: Do not cache security credentials on disk
632+ [Andrew Jorgensen] (LP: #1638312)
633+ - doc: Fix typos and clarify some aspects of the part-handler
634+ [Erik M. Bray]
635+ - doc: add some documentation on OpenStack datasource.
636+ - OpenStack: Use timeout and retries from config in get_data.
637+ [Lars Kellogg-Stedman] (LP: #1657130)
638+ - Fixed Misc issues related to VMware customization. [Sankar Tanguturi]
639+ - Fix minor docs typo: perserve > preserve [Jeremy Bicha]
640+ - Use dnf instead of yum when available
641+ [Lars Kellogg-Stedman] (LP: #1647118)
642+ - validate-yaml: use python rather than explicitly python3
643+ - Get early logging logged, including failures of cmdline url.
644+
645+0.7.9:
646+ - doc: adjust headers in tests documentation for consistency.
647+ - pep8: fix issue found in zesty build with pycodestyle.
648+ - integration test: initial commit of integration test framework
649+ [Wesley Wiedenmeier]
650+ - LICENSE: Allow dual licensing GPL-3 or Apache 2.0 [Jon Grimm]
651+ - Fix config order of precedence, putting kernel command line over system.
652+ [Wesley Wiedenmeier] (LP: #1582323)
653+ - pep8: whitespace fix
654+ - Update the list of valid ssh keys. [Michael Felt]
655+ - network: add ENI unit test for statically rendered routes.
656+ - set_hostname: avoid erroneously appending domain to fqdn
657+ [Lars Kellogg-Stedman] (LP: #1647910)
658+ - doc: change 'nobootwait' to 'nofail' in docs [Anhad Jai Singh]
659+ - Replace an expired bit.ly link in code comment.
660+ - user-groups: fix bug when groups was provided as string and had spaces
661+ (LP: #1354694)
662+ - mounts: use mount -a again to accomplish mounts (LP: #1647708)
663+ - CloudSigma: Fix bug where datasource was not loaded in local search.
664+ (LP: #1648380)
665+ - when adding a user, strip whitespace from group list [Lars Kellogg-Stedman]
666+ (LP: #1354694)
667+ - fix decoding of utf-8 chars in yaml test
668+ - Replace usage of sys_netdev_info with read_sys_net (LP: #1625766)
669+ - fix problems found in python2.6 test.
670+ - OpenStack: extend physical types to include hyperv, hw_veb, vhost_user.
671+ (LP: #1642679)
672+ - tests: fix assumptions that expected no eth0 in system. (LP: #1644043)
673+ - net/cmdline: Consider ip= or ip6= on command line not only ip=
674+ (LP: #1639930)
675+ - Just use file logging by default (LP: #1643990)
676+ - Improve formatting for ProcessExecutionError [Wesley Wiedenmeier]
677+ - flake8: fix trailing white space
678+ - Doc: various documentation fixes [Sean Bright]
679+ - cloudinit/config/cc_rh_subscription.py: Remove repos before adding
680+ [Brent Baude]
681+ - packages/redhat: fix rpm spec file.
682+ - main: set TZ in environment if not already set. [Ryan Harper]
683+ - Azure: No longer rely on walinux agent. (LP: #1538522)
684+ - disk_setup: Use sectors as unit when formatting MBR disks with sfdisk.
685+ [Daniel Watkins] (LP: #1460715)
686+ - Add activate_datasource, for datasource specific code paths. (LP: #1611074)
687+ - systemd: cloud-init-local use RequiresMountsFor=/var/lib/cloud
688+ (LP: #1642062)
689+ - systemd: cloud-init remove After=systemd-networkd-wait-online
690+ - systemd: cloud-init-local change Before basic to sysinit
691+ - pep8: fix style errors reported by pycodestyle 2.1.0
692+ - systemd: drop both Wants and After local-fs.target
693+ - systemd: networking service adjustments. (LP: #1636912)
694+ - systemd: replace Before=basic.target, dbus.target with sysinit.target
695+ (LP: #1629797)
696+ - doc: Add documentation on stages of boot.
697+ - doc: make the RST files consistently formated and other improvements.
698+ - Ec2: fix syntax and tox in previous commit.
699+ - Ec2: protect against non-dictionary in block-device-mapping.
700+ - doc: fixed example to not overwrite /etc/hosts [Chris Glass]
701+ - Doc: fix spelling / typos in ca_certs and scripts_vendor.
702+ - pyflakes: fix issue with pyflakes 1.3 found in ubuntu zesty-proposed.
703+ - net/cmdline: Further adjustments to ipv6 support [LaMont Jones]
704+ (LP: #1621615)
705+ - Add coverage dependency to bddeb to fix package build.
706+ - doc: improve HACKING.rst file
707+ - dmidecode: Allow dmidecode to be used on aarch64 [Robert Schweikert]
708+ - AliYun: Add new datasource for Ali-Cloud ECS [kaihuan.pkh]
709+ - Add coverage collection to tox unit tests. [Joshua Powers]
710+ - cc_users_groups: fix remaing call to ds.normalize_user_groups [Ryan Harper]
711+ - disk-config: udev settle after partitioning in gpt format. (LP: #1626243)
712+ - unittests: do not read system /etc/cloud/cloud.cfg.d (LP: #1635350)
713+ - Add documentation for logging features. [Wesley Wiedenmeier]
714+ - Add support for snap create-user on Ubuntu Core images. [Ryan Harper]
715+ - Fix sshd restarts for rhel distros. [Jim Gorz]
716+ - OpenNebula: replace 'ip' parsing with cloudinit.net usage.
717+ - Fix python2.6 things found running in centos 6.
718+ - Move user/group functions to new ug_util file
719+ - DigitalOcean: enable usage of data source by default.
720+ - update Gentoo initscripts to run in the correct order [Matthew Thode]
721+ - MAAS: improve the main of datasource to look at kernel cmdline config.
722+ - tests: silence the Cheetah UserWarning about NameMapper C version.
723+ - systemd: Run cloud-init.service Before dbus.socket not dbus.target
724+ [Daniel Watkins] (LP: #1629797)
725+ - systemd: run cloud-init.service Before dbus.service (LP: #1629797)
726+ - unittests: fix use of mock 2.0 'assert_called' when running make check
727+ [Ryan Harper]
728+ - Improve module documentation and doc cleanup. [Wesley Wiedenmeier]
729+ - lxd: Update network config for LXD 2.3 [Stéphane Graber]
730+ - DigitalOcean: use meta-data for network configruation [Ben Howard]
731+ - ntp: move to run after apt configuration (LP: #1628337)
732+ - Decode unicode types in decode_binary [Robert Schweikert]
733+ - systemd: Ensure that cloud-init-local happens before NetworkManager
734+ - Allow ephemeral drive to be unpartitioned [Paul Meyer]
735+ - subp: add 'update_env' argument
736+ - net: support reading ipv6 dhcp config from initramfs [LaMont Jones]
737+ (LP: #1621615, #1621507)
738+ - Adjust mounts and disk configuration for systemd. (LP: #1611074)
739+ - dmidecode: run dmidecode only on i?86 or x86_64 arch. [Robert Schweikert]
740+ - systemd: put cloud-init.target After multi-user.target (LP: #1623868)
741+
742 0.7.8:
743 - SmartOS: more improvements for network configuration
744 - add ntp config module [Ryan Harper]
745diff --git a/HACKING.rst b/HACKING.rst
746index caee7ac..3bb555c 100644
747--- a/HACKING.rst
748+++ b/HACKING.rst
749@@ -13,6 +13,17 @@ Do these things once
750
751 If you have already signed it as an individual, your Launchpad user will be listed in the `contributor-agreement-canonical`_ group. Unfortunately there is no easy way to check if an organization or company you are doing work for has signed. If you are unsure or have questions, email `Scott Moser <mailto:scott.moser@canonical.com>`_ or ping smoser in ``#cloud-init`` channel via freenode.
752
753+ When prompted for 'Project contact' or 'Canonical Project Manager' enter
754+ 'Scott Moser'.
755+
756+* Configure git with your email and name for commit messages.
757+
758+ Your name will appear in commit messages and will also be used in
759+ changelogs or release notes. Give yourself credit!::
760+
761+ git config user.name "Your Name"
762+ git config user.email "Your Email"
763+
764 * Clone the upstream `repository`_ on Launchpad::
765
766 git clone https://git.launchpad.net/cloud-init
767diff --git a/LICENSE b/LICENSE
768index 94a9ed0..8a23b4d 100644
769--- a/LICENSE
770+++ b/LICENSE
771@@ -1,674 +1,22 @@
772- GNU GENERAL PUBLIC LICENSE
773- Version 3, 29 June 2007
774-
775- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
776- Everyone is permitted to copy and distribute verbatim copies
777- of this license document, but changing it is not allowed.
778-
779- Preamble
780-
781- The GNU General Public License is a free, copyleft license for
782-software and other kinds of works.
783-
784- The licenses for most software and other practical works are designed
785-to take away your freedom to share and change the works. By contrast,
786-the GNU General Public License is intended to guarantee your freedom to
787-share and change all versions of a program--to make sure it remains free
788-software for all its users. We, the Free Software Foundation, use the
789-GNU General Public License for most of our software; it applies also to
790-any other work released this way by its authors. You can apply it to
791-your programs, too.
792-
793- When we speak of free software, we are referring to freedom, not
794-price. Our General Public Licenses are designed to make sure that you
795-have the freedom to distribute copies of free software (and charge for
796-them if you wish), that you receive source code or can get it if you
797-want it, that you can change the software or use pieces of it in new
798-free programs, and that you know you can do these things.
799-
800- To protect your rights, we need to prevent others from denying you
801-these rights or asking you to surrender the rights. Therefore, you have
802-certain responsibilities if you distribute copies of the software, or if
803-you modify it: responsibilities to respect the freedom of others.
804-
805- For example, if you distribute copies of such a program, whether
806-gratis or for a fee, you must pass on to the recipients the same
807-freedoms that you received. You must make sure that they, too, receive
808-or can get the source code. And you must show them these terms so they
809-know their rights.
810-
811- Developers that use the GNU GPL protect your rights with two steps:
812-(1) assert copyright on the software, and (2) offer you this License
813-giving you legal permission to copy, distribute and/or modify it.
814-
815- For the developers' and authors' protection, the GPL clearly explains
816-that there is no warranty for this free software. For both users' and
817-authors' sake, the GPL requires that modified versions be marked as
818-changed, so that their problems will not be attributed erroneously to
819-authors of previous versions.
820-
821- Some devices are designed to deny users access to install or run
822-modified versions of the software inside them, although the manufacturer
823-can do so. This is fundamentally incompatible with the aim of
824-protecting users' freedom to change the software. The systematic
825-pattern of such abuse occurs in the area of products for individuals to
826-use, which is precisely where it is most unacceptable. Therefore, we
827-have designed this version of the GPL to prohibit the practice for those
828-products. If such problems arise substantially in other domains, we
829-stand ready to extend this provision to those domains in future versions
830-of the GPL, as needed to protect the freedom of users.
831-
832- Finally, every program is threatened constantly by software patents.
833-States should not allow patents to restrict development and use of
834-software on general-purpose computers, but in those that do, we wish to
835-avoid the special danger that patents applied to a free program could
836-make it effectively proprietary. To prevent this, the GPL assures that
837-patents cannot be used to render the program non-free.
838-
839- The precise terms and conditions for copying, distribution and
840-modification follow.
841-
842- TERMS AND CONDITIONS
843-
844- 0. Definitions.
845-
846- "This License" refers to version 3 of the GNU General Public License.
847-
848- "Copyright" also means copyright-like laws that apply to other kinds of
849-works, such as semiconductor masks.
850-
851- "The Program" refers to any copyrightable work licensed under this
852-License. Each licensee is addressed as "you". "Licensees" and
853-"recipients" may be individuals or organizations.
854-
855- To "modify" a work means to copy from or adapt all or part of the work
856-in a fashion requiring copyright permission, other than the making of an
857-exact copy. The resulting work is called a "modified version" of the
858-earlier work or a work "based on" the earlier work.
859-
860- A "covered work" means either the unmodified Program or a work based
861-on the Program.
862-
863- To "propagate" a work means to do anything with it that, without
864-permission, would make you directly or secondarily liable for
865-infringement under applicable copyright law, except executing it on a
866-computer or modifying a private copy. Propagation includes copying,
867-distribution (with or without modification), making available to the
868-public, and in some countries other activities as well.
869-
870- To "convey" a work means any kind of propagation that enables other
871-parties to make or receive copies. Mere interaction with a user through
872-a computer network, with no transfer of a copy, is not conveying.
873-
874- An interactive user interface displays "Appropriate Legal Notices"
875-to the extent that it includes a convenient and prominently visible
876-feature that (1) displays an appropriate copyright notice, and (2)
877-tells the user that there is no warranty for the work (except to the
878-extent that warranties are provided), that licensees may convey the
879-work under this License, and how to view a copy of this License. If
880-the interface presents a list of user commands or options, such as a
881-menu, a prominent item in the list meets this criterion.
882-
883- 1. Source Code.
884-
885- The "source code" for a work means the preferred form of the work
886-for making modifications to it. "Object code" means any non-source
887-form of a work.
888-
889- A "Standard Interface" means an interface that either is an official
890-standard defined by a recognized standards body, or, in the case of
891-interfaces specified for a particular programming language, one that
892-is widely used among developers working in that language.
893-
894- The "System Libraries" of an executable work include anything, other
895-than the work as a whole, that (a) is included in the normal form of
896-packaging a Major Component, but which is not part of that Major
897-Component, and (b) serves only to enable use of the work with that
898-Major Component, or to implement a Standard Interface for which an
899-implementation is available to the public in source code form. A
900-"Major Component", in this context, means a major essential component
901-(kernel, window system, and so on) of the specific operating system
902-(if any) on which the executable work runs, or a compiler used to
903-produce the work, or an object code interpreter used to run it.
904-
905- The "Corresponding Source" for a work in object code form means all
906-the source code needed to generate, install, and (for an executable
907-work) run the object code and to modify the work, including scripts to
908-control those activities. However, it does not include the work's
909-System Libraries, or general-purpose tools or generally available free
910-programs which are used unmodified in performing those activities but
911-which are not part of the work. For example, Corresponding Source
912-includes interface definition files associated with source files for
913-the work, and the source code for shared libraries and dynamically
914-linked subprograms that the work is specifically designed to require,
915-such as by intimate data communication or control flow between those
916-subprograms and other parts of the work.
917-
918- The Corresponding Source need not include anything that users
919-can regenerate automatically from other parts of the Corresponding
920-Source.
921-
922- The Corresponding Source for a work in source code form is that
923-same work.
924-
925- 2. Basic Permissions.
926-
927- All rights granted under this License are granted for the term of
928-copyright on the Program, and are irrevocable provided the stated
929-conditions are met. This License explicitly affirms your unlimited
930-permission to run the unmodified Program. The output from running a
931-covered work is covered by this License only if the output, given its
932-content, constitutes a covered work. This License acknowledges your
933-rights of fair use or other equivalent, as provided by copyright law.
934-
935- You may make, run and propagate covered works that you do not
936-convey, without conditions so long as your license otherwise remains
937-in force. You may convey covered works to others for the sole purpose
938-of having them make modifications exclusively for you, or provide you
939-with facilities for running those works, provided that you comply with
940-the terms of this License in conveying all material for which you do
941-not control copyright. Those thus making or running the covered works
942-for you must do so exclusively on your behalf, under your direction
943-and control, on terms that prohibit them from making any copies of
944-your copyrighted material outside their relationship with you.
945-
946- Conveying under any other circumstances is permitted solely under
947-the conditions stated below. Sublicensing is not allowed; section 10
948-makes it unnecessary.
949-
950- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
951-
952- No covered work shall be deemed part of an effective technological
953-measure under any applicable law fulfilling obligations under article
954-11 of the WIPO copyright treaty adopted on 20 December 1996, or
955-similar laws prohibiting or restricting circumvention of such
956-measures.
957-
958- When you convey a covered work, you waive any legal power to forbid
959-circumvention of technological measures to the extent such circumvention
960-is effected by exercising rights under this License with respect to
961-the covered work, and you disclaim any intention to limit operation or
962-modification of the work as a means of enforcing, against the work's
963-users, your or third parties' legal rights to forbid circumvention of
964-technological measures.
965-
966- 4. Conveying Verbatim Copies.
967-
968- You may convey verbatim copies of the Program's source code as you
969-receive it, in any medium, provided that you conspicuously and
970-appropriately publish on each copy an appropriate copyright notice;
971-keep intact all notices stating that this License and any
972-non-permissive terms added in accord with section 7 apply to the code;
973-keep intact all notices of the absence of any warranty; and give all
974-recipients a copy of this License along with the Program.
975-
976- You may charge any price or no price for each copy that you convey,
977-and you may offer support or warranty protection for a fee.
978-
979- 5. Conveying Modified Source Versions.
980-
981- You may convey a work based on the Program, or the modifications to
982-produce it from the Program, in the form of source code under the
983-terms of section 4, provided that you also meet all of these conditions:
984-
985- a) The work must carry prominent notices stating that you modified
986- it, and giving a relevant date.
987-
988- b) The work must carry prominent notices stating that it is
989- released under this License and any conditions added under section
990- 7. This requirement modifies the requirement in section 4 to
991- "keep intact all notices".
992-
993- c) You must license the entire work, as a whole, under this
994- License to anyone who comes into possession of a copy. This
995- License will therefore apply, along with any applicable section 7
996- additional terms, to the whole of the work, and all its parts,
997- regardless of how they are packaged. This License gives no
998- permission to license the work in any other way, but it does not
999- invalidate such permission if you have separately received it.
1000-
1001- d) If the work has interactive user interfaces, each must display
1002- Appropriate Legal Notices; however, if the Program has interactive
1003- interfaces that do not display Appropriate Legal Notices, your
1004- work need not make them do so.
1005-
1006- A compilation of a covered work with other separate and independent
1007-works, which are not by their nature extensions of the covered work,
1008-and which are not combined with it such as to form a larger program,
1009-in or on a volume of a storage or distribution medium, is called an
1010-"aggregate" if the compilation and its resulting copyright are not
1011-used to limit the access or legal rights of the compilation's users
1012-beyond what the individual works permit. Inclusion of a covered work
1013-in an aggregate does not cause this License to apply to the other
1014-parts of the aggregate.
1015-
1016- 6. Conveying Non-Source Forms.
1017-
1018- You may convey a covered work in object code form under the terms
1019-of sections 4 and 5, provided that you also convey the
1020-machine-readable Corresponding Source under the terms of this License,
1021-in one of these ways:
1022-
1023- a) Convey the object code in, or embodied in, a physical product
1024- (including a physical distribution medium), accompanied by the
1025- Corresponding Source fixed on a durable physical medium
1026- customarily used for software interchange.
1027-
1028- b) Convey the object code in, or embodied in, a physical product
1029- (including a physical distribution medium), accompanied by a
1030- written offer, valid for at least three years and valid for as
1031- long as you offer spare parts or customer support for that product
1032- model, to give anyone who possesses the object code either (1) a
1033- copy of the Corresponding Source for all the software in the
1034- product that is covered by this License, on a durable physical
1035- medium customarily used for software interchange, for a price no
1036- more than your reasonable cost of physically performing this
1037- conveying of source, or (2) access to copy the
1038- Corresponding Source from a network server at no charge.
1039-
1040- c) Convey individual copies of the object code with a copy of the
1041- written offer to provide the Corresponding Source. This
1042- alternative is allowed only occasionally and noncommercially, and
1043- only if you received the object code with such an offer, in accord
1044- with subsection 6b.
1045-
1046- d) Convey the object code by offering access from a designated
1047- place (gratis or for a charge), and offer equivalent access to the
1048- Corresponding Source in the same way through the same place at no
1049- further charge. You need not require recipients to copy the
1050- Corresponding Source along with the object code. If the place to
1051- copy the object code is a network server, the Corresponding Source
1052- may be on a different server (operated by you or a third party)
1053- that supports equivalent copying facilities, provided you maintain
1054- clear directions next to the object code saying where to find the
1055- Corresponding Source. Regardless of what server hosts the
1056- Corresponding Source, you remain obligated to ensure that it is
1057- available for as long as needed to satisfy these requirements.
1058-
1059- e) Convey the object code using peer-to-peer transmission, provided
1060- you inform other peers where the object code and Corresponding
1061- Source of the work are being offered to the general public at no
1062- charge under subsection 6d.
1063-
1064- A separable portion of the object code, whose source code is excluded
1065-from the Corresponding Source as a System Library, need not be
1066-included in conveying the object code work.
1067-
1068- A "User Product" is either (1) a "consumer product", which means any
1069-tangible personal property which is normally used for personal, family,
1070-or household purposes, or (2) anything designed or sold for incorporation
1071-into a dwelling. In determining whether a product is a consumer product,
1072-doubtful cases shall be resolved in favor of coverage. For a particular
1073-product received by a particular user, "normally used" refers to a
1074-typical or common use of that class of product, regardless of the status
1075-of the particular user or of the way in which the particular user
1076-actually uses, or expects or is expected to use, the product. A product
1077-is a consumer product regardless of whether the product has substantial
1078-commercial, industrial or non-consumer uses, unless such uses represent
1079-the only significant mode of use of the product.
1080-
1081- "Installation Information" for a User Product means any methods,
1082-procedures, authorization keys, or other information required to install
1083-and execute modified versions of a covered work in that User Product from
1084-a modified version of its Corresponding Source. The information must
1085-suffice to ensure that the continued functioning of the modified object
1086-code is in no case prevented or interfered with solely because
1087-modification has been made.
1088-
1089- If you convey an object code work under this section in, or with, or
1090-specifically for use in, a User Product, and the conveying occurs as
1091-part of a transaction in which the right of possession and use of the
1092-User Product is transferred to the recipient in perpetuity or for a
1093-fixed term (regardless of how the transaction is characterized), the
1094-Corresponding Source conveyed under this section must be accompanied
1095-by the Installation Information. But this requirement does not apply
1096-if neither you nor any third party retains the ability to install
1097-modified object code on the User Product (for example, the work has
1098-been installed in ROM).
1099-
1100- The requirement to provide Installation Information does not include a
1101-requirement to continue to provide support service, warranty, or updates
1102-for a work that has been modified or installed by the recipient, or for
1103-the User Product in which it has been modified or installed. Access to a
1104-network may be denied when the modification itself materially and
1105-adversely affects the operation of the network or violates the rules and
1106-protocols for communication across the network.
1107-
1108- Corresponding Source conveyed, and Installation Information provided,
1109-in accord with this section must be in a format that is publicly
1110-documented (and with an implementation available to the public in
1111-source code form), and must require no special password or key for
1112-unpacking, reading or copying.
1113-
1114- 7. Additional Terms.
1115-
1116- "Additional permissions" are terms that supplement the terms of this
1117-License by making exceptions from one or more of its conditions.
1118-Additional permissions that are applicable to the entire Program shall
1119-be treated as though they were included in this License, to the extent
1120-that they are valid under applicable law. If additional permissions
1121-apply only to part of the Program, that part may be used separately
1122-under those permissions, but the entire Program remains governed by
1123-this License without regard to the additional permissions.
1124-
1125- When you convey a copy of a covered work, you may at your option
1126-remove any additional permissions from that copy, or from any part of
1127-it. (Additional permissions may be written to require their own
1128-removal in certain cases when you modify the work.) You may place
1129-additional permissions on material, added by you to a covered work,
1130-for which you have or can give appropriate copyright permission.
1131-
1132- Notwithstanding any other provision of this License, for material you
1133-add to a covered work, you may (if authorized by the copyright holders of
1134-that material) supplement the terms of this License with terms:
1135-
1136- a) Disclaiming warranty or limiting liability differently from the
1137- terms of sections 15 and 16 of this License; or
1138-
1139- b) Requiring preservation of specified reasonable legal notices or
1140- author attributions in that material or in the Appropriate Legal
1141- Notices displayed by works containing it; or
1142-
1143- c) Prohibiting misrepresentation of the origin of that material, or
1144- requiring that modified versions of such material be marked in
1145- reasonable ways as different from the original version; or
1146-
1147- d) Limiting the use for publicity purposes of names of licensors or
1148- authors of the material; or
1149-
1150- e) Declining to grant rights under trademark law for use of some
1151- trade names, trademarks, or service marks; or
1152-
1153- f) Requiring indemnification of licensors and authors of that
1154- material by anyone who conveys the material (or modified versions of
1155- it) with contractual assumptions of liability to the recipient, for
1156- any liability that these contractual assumptions directly impose on
1157- those licensors and authors.
1158-
1159- All other non-permissive additional terms are considered "further
1160-restrictions" within the meaning of section 10. If the Program as you
1161-received it, or any part of it, contains a notice stating that it is
1162-governed by this License along with a term that is a further
1163-restriction, you may remove that term. If a license document contains
1164-a further restriction but permits relicensing or conveying under this
1165-License, you may add to a covered work material governed by the terms
1166-of that license document, provided that the further restriction does
1167-not survive such relicensing or conveying.
1168-
1169- If you add terms to a covered work in accord with this section, you
1170-must place, in the relevant source files, a statement of the
1171-additional terms that apply to those files, or a notice indicating
1172-where to find the applicable terms.
1173-
1174- Additional terms, permissive or non-permissive, may be stated in the
1175-form of a separately written license, or stated as exceptions;
1176-the above requirements apply either way.
1177-
1178- 8. Termination.
1179-
1180- You may not propagate or modify a covered work except as expressly
1181-provided under this License. Any attempt otherwise to propagate or
1182-modify it is void, and will automatically terminate your rights under
1183-this License (including any patent licenses granted under the third
1184-paragraph of section 11).
1185-
1186- However, if you cease all violation of this License, then your
1187-license from a particular copyright holder is reinstated (a)
1188-provisionally, unless and until the copyright holder explicitly and
1189-finally terminates your license, and (b) permanently, if the copyright
1190-holder fails to notify you of the violation by some reasonable means
1191-prior to 60 days after the cessation.
1192-
1193- Moreover, your license from a particular copyright holder is
1194-reinstated permanently if the copyright holder notifies you of the
1195-violation by some reasonable means, this is the first time you have
1196-received notice of violation of this License (for any work) from that
1197-copyright holder, and you cure the violation prior to 30 days after
1198-your receipt of the notice.
1199-
1200- Termination of your rights under this section does not terminate the
1201-licenses of parties who have received copies or rights from you under
1202-this License. If your rights have been terminated and not permanently
1203-reinstated, you do not qualify to receive new licenses for the same
1204-material under section 10.
1205-
1206- 9. Acceptance Not Required for Having Copies.
1207-
1208- You are not required to accept this License in order to receive or
1209-run a copy of the Program. Ancillary propagation of a covered work
1210-occurring solely as a consequence of using peer-to-peer transmission
1211-to receive a copy likewise does not require acceptance. However,
1212-nothing other than this License grants you permission to propagate or
1213-modify any covered work. These actions infringe copyright if you do
1214-not accept this License. Therefore, by modifying or propagating a
1215-covered work, you indicate your acceptance of this License to do so.
1216-
1217- 10. Automatic Licensing of Downstream Recipients.
1218-
1219- Each time you convey a covered work, the recipient automatically
1220-receives a license from the original licensors, to run, modify and
1221-propagate that work, subject to this License. You are not responsible
1222-for enforcing compliance by third parties with this License.
1223-
1224- An "entity transaction" is a transaction transferring control of an
1225-organization, or substantially all assets of one, or subdividing an
1226-organization, or merging organizations. If propagation of a covered
1227-work results from an entity transaction, each party to that
1228-transaction who receives a copy of the work also receives whatever
1229-licenses to the work the party's predecessor in interest had or could
1230-give under the previous paragraph, plus a right to possession of the
1231-Corresponding Source of the work from the predecessor in interest, if
1232-the predecessor has it or can get it with reasonable efforts.
1233-
1234- You may not impose any further restrictions on the exercise of the
1235-rights granted or affirmed under this License. For example, you may
1236-not impose a license fee, royalty, or other charge for exercise of
1237-rights granted under this License, and you may not initiate litigation
1238-(including a cross-claim or counterclaim in a lawsuit) alleging that
1239-any patent claim is infringed by making, using, selling, offering for
1240-sale, or importing the Program or any portion of it.
1241-
1242- 11. Patents.
1243-
1244- A "contributor" is a copyright holder who authorizes use under this
1245-License of the Program or a work on which the Program is based. The
1246-work thus licensed is called the contributor's "contributor version".
1247-
1248- A contributor's "essential patent claims" are all patent claims
1249-owned or controlled by the contributor, whether already acquired or
1250-hereafter acquired, that would be infringed by some manner, permitted
1251-by this License, of making, using, or selling its contributor version,
1252-but do not include claims that would be infringed only as a
1253-consequence of further modification of the contributor version. For
1254-purposes of this definition, "control" includes the right to grant
1255-patent sublicenses in a manner consistent with the requirements of
1256-this License.
1257-
1258- Each contributor grants you a non-exclusive, worldwide, royalty-free
1259-patent license under the contributor's essential patent claims, to
1260-make, use, sell, offer for sale, import and otherwise run, modify and
1261-propagate the contents of its contributor version.
1262-
1263- In the following three paragraphs, a "patent license" is any express
1264-agreement or commitment, however denominated, not to enforce a patent
1265-(such as an express permission to practice a patent or covenant not to
1266-sue for patent infringement). To "grant" such a patent license to a
1267-party means to make such an agreement or commitment not to enforce a
1268-patent against the party.
1269-
1270- If you convey a covered work, knowingly relying on a patent license,
1271-and the Corresponding Source of the work is not available for anyone
1272-to copy, free of charge and under the terms of this License, through a
1273-publicly available network server or other readily accessible means,
1274-then you must either (1) cause the Corresponding Source to be so
1275-available, or (2) arrange to deprive yourself of the benefit of the
1276-patent license for this particular work, or (3) arrange, in a manner
1277-consistent with the requirements of this License, to extend the patent
1278-license to downstream recipients. "Knowingly relying" means you have
1279-actual knowledge that, but for the patent license, your conveying the
1280-covered work in a country, or your recipient's use of the covered work
1281-in a country, would infringe one or more identifiable patents in that
1282-country that you have reason to believe are valid.
1283-
1284- If, pursuant to or in connection with a single transaction or
1285-arrangement, you convey, or propagate by procuring conveyance of, a
1286-covered work, and grant a patent license to some of the parties
1287-receiving the covered work authorizing them to use, propagate, modify
1288-or convey a specific copy of the covered work, then the patent license
1289-you grant is automatically extended to all recipients of the covered
1290-work and works based on it.
1291-
1292- A patent license is "discriminatory" if it does not include within
1293-the scope of its coverage, prohibits the exercise of, or is
1294-conditioned on the non-exercise of one or more of the rights that are
1295-specifically granted under this License. You may not convey a covered
1296-work if you are a party to an arrangement with a third party that is
1297-in the business of distributing software, under which you make payment
1298-to the third party based on the extent of your activity of conveying
1299-the work, and under which the third party grants, to any of the
1300-parties who would receive the covered work from you, a discriminatory
1301-patent license (a) in connection with copies of the covered work
1302-conveyed by you (or copies made from those copies), or (b) primarily
1303-for and in connection with specific products or compilations that
1304-contain the covered work, unless you entered into that arrangement,
1305-or that patent license was granted, prior to 28 March 2007.
1306-
1307- Nothing in this License shall be construed as excluding or limiting
1308-any implied license or other defenses to infringement that may
1309-otherwise be available to you under applicable patent law.
1310-
1311- 12. No Surrender of Others' Freedom.
1312-
1313- If conditions are imposed on you (whether by court order, agreement or
1314-otherwise) that contradict the conditions of this License, they do not
1315-excuse you from the conditions of this License. If you cannot convey a
1316-covered work so as to satisfy simultaneously your obligations under this
1317-License and any other pertinent obligations, then as a consequence you may
1318-not convey it at all. For example, if you agree to terms that obligate you
1319-to collect a royalty for further conveying from those to whom you convey
1320-the Program, the only way you could satisfy both those terms and this
1321-License would be to refrain entirely from conveying the Program.
1322-
1323- 13. Use with the GNU Affero General Public License.
1324-
1325- Notwithstanding any other provision of this License, you have
1326-permission to link or combine any covered work with a work licensed
1327-under version 3 of the GNU Affero General Public License into a single
1328-combined work, and to convey the resulting work. The terms of this
1329-License will continue to apply to the part which is the covered work,
1330-but the special requirements of the GNU Affero General Public License,
1331-section 13, concerning interaction through a network will apply to the
1332-combination as such.
1333-
1334- 14. Revised Versions of this License.
1335-
1336- The Free Software Foundation may publish revised and/or new versions of
1337-the GNU General Public License from time to time. Such new versions will
1338-be similar in spirit to the present version, but may differ in detail to
1339-address new problems or concerns.
1340-
1341- Each version is given a distinguishing version number. If the
1342-Program specifies that a certain numbered version of the GNU General
1343-Public License "or any later version" applies to it, you have the
1344-option of following the terms and conditions either of that numbered
1345-version or of any later version published by the Free Software
1346-Foundation. If the Program does not specify a version number of the
1347-GNU General Public License, you may choose any version ever published
1348-by the Free Software Foundation.
1349-
1350- If the Program specifies that a proxy can decide which future
1351-versions of the GNU General Public License can be used, that proxy's
1352-public statement of acceptance of a version permanently authorizes you
1353-to choose that version for the Program.
1354-
1355- Later license versions may give you additional or different
1356-permissions. However, no additional obligations are imposed on any
1357-author or copyright holder as a result of your choosing to follow a
1358-later version.
1359-
1360- 15. Disclaimer of Warranty.
1361-
1362- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
1363-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
1364-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
1365-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
1366-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
1367-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
1368-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
1369-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
1370-
1371- 16. Limitation of Liability.
1372-
1373- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
1374-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
1375-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
1376-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
1377-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
1378-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
1379-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
1380-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
1381-SUCH DAMAGES.
1382-
1383- 17. Interpretation of Sections 15 and 16.
1384-
1385- If the disclaimer of warranty and limitation of liability provided
1386-above cannot be given local legal effect according to their terms,
1387-reviewing courts shall apply local law that most closely approximates
1388-an absolute waiver of all civil liability in connection with the
1389-Program, unless a warranty or assumption of liability accompanies a
1390-copy of the Program in return for a fee.
1391-
1392- END OF TERMS AND CONDITIONS
1393-
1394- How to Apply These Terms to Your New Programs
1395-
1396- If you develop a new program, and you want it to be of the greatest
1397-possible use to the public, the best way to achieve this is to make it
1398-free software which everyone can redistribute and change under these terms.
1399-
1400- To do so, attach the following notices to the program. It is safest
1401-to attach them to the start of each source file to most effectively
1402-state the exclusion of warranty; and each file should have at least
1403-the "copyright" line and a pointer to where the full notice is found.
1404-
1405- <one line to give the program's name and a brief idea of what it does.>
1406- Copyright (C) <year> <name of author>
1407-
1408- This program is free software: you can redistribute it and/or modify
1409- it under the terms of the GNU General Public License as published by
1410- the Free Software Foundation, either version 3 of the License, or
1411- (at your option) any later version.
1412-
1413- This program is distributed in the hope that it will be useful,
1414- but WITHOUT ANY WARRANTY; without even the implied warranty of
1415- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1416- GNU General Public License for more details.
1417-
1418- You should have received a copy of the GNU General Public License
1419- along with this program. If not, see <http://www.gnu.org/licenses/>.
1420-
1421-Also add information on how to contact you by electronic and paper mail.
1422-
1423- If the program does terminal interaction, make it output a short
1424-notice like this when it starts in an interactive mode:
1425-
1426- <program> Copyright (C) <year> <name of author>
1427- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
1428- This is free software, and you are welcome to redistribute it
1429- under certain conditions; type `show c' for details.
1430-
1431-The hypothetical commands `show w' and `show c' should show the appropriate
1432-parts of the General Public License. Of course, your program's commands
1433-might be different; for a GUI interface, you would use an "about box".
1434-
1435- You should also get your employer (if you work as a programmer) or school,
1436-if any, to sign a "copyright disclaimer" for the program, if necessary.
1437-For more information on this, and how to apply and follow the GNU GPL, see
1438-<http://www.gnu.org/licenses/>.
1439-
1440- The GNU General Public License does not permit incorporating your program
1441-into proprietary programs. If your program is a subroutine library, you
1442-may consider it more useful to permit linking proprietary applications with
1443-the library. If this is what you want to do, use the GNU Lesser General
1444-Public License instead of this License. But first, please read
1445-<http://www.gnu.org/philosophy/why-not-lgpl.html>.
1446+Copyright 2015 Canonical Ltd.
1447+
1448+This program is free software: you can redistribute it and/or modify it under
1449+the terms of the GNU General Public License version 3, as published by the
1450+Free Software Foundation.
1451+
1452+This program is distributed in the hope that it will be useful, but WITHOUT
1453+ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1454+SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1455+General Public License for more details.
1456+
1457+You should have received a copy of the GNU General Public License along with
1458+this program. If not, see <http://www.gnu.org/licenses/>
1459+
1460+Alternatively, this program may be used under the terms of the Apache License,
1461+Version 2.0, in which case the provisions of that license are applicable
1462+instead of those above. If you wish to allow use of your version of this
1463+program under the terms of the Apache License, Version 2.0 only, indicate
1464+your decision by deleting the provisions above and replace them with the notice
1465+and other provisions required by the Apache License, Version 2.0. If you do not
1466+delete the provisions above, a recipient may use your version of this file
1467+under the terms of either the GPLv3 or the Apache License, Version 2.0.
1468diff --git a/LICENSE-Apache2.0 b/LICENSE-Apache2.0
1469new file mode 100644
1470index 0000000..d645695
1471--- /dev/null
1472+++ b/LICENSE-Apache2.0
1473@@ -0,0 +1,202 @@
1474+
1475+ Apache License
1476+ Version 2.0, January 2004
1477+ http://www.apache.org/licenses/
1478+
1479+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1480+
1481+ 1. Definitions.
1482+
1483+ "License" shall mean the terms and conditions for use, reproduction,
1484+ and distribution as defined by Sections 1 through 9 of this document.
1485+
1486+ "Licensor" shall mean the copyright owner or entity authorized by
1487+ the copyright owner that is granting the License.
1488+
1489+ "Legal Entity" shall mean the union of the acting entity and all
1490+ other entities that control, are controlled by, or are under common
1491+ control with that entity. For the purposes of this definition,
1492+ "control" means (i) the power, direct or indirect, to cause the
1493+ direction or management of such entity, whether by contract or
1494+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
1495+ outstanding shares, or (iii) beneficial ownership of such entity.
1496+
1497+ "You" (or "Your") shall mean an individual or Legal Entity
1498+ exercising permissions granted by this License.
1499+
1500+ "Source" form shall mean the preferred form for making modifications,
1501+ including but not limited to software source code, documentation
1502+ source, and configuration files.
1503+
1504+ "Object" form shall mean any form resulting from mechanical
1505+ transformation or translation of a Source form, including but
1506+ not limited to compiled object code, generated documentation,
1507+ and conversions to other media types.
1508+
1509+ "Work" shall mean the work of authorship, whether in Source or
1510+ Object form, made available under the License, as indicated by a
1511+ copyright notice that is included in or attached to the work
1512+ (an example is provided in the Appendix below).
1513+
1514+ "Derivative Works" shall mean any work, whether in Source or Object
1515+ form, that is based on (or derived from) the Work and for which the
1516+ editorial revisions, annotations, elaborations, or other modifications
1517+ represent, as a whole, an original work of authorship. For the purposes
1518+ of this License, Derivative Works shall not include works that remain
1519+ separable from, or merely link (or bind by name) to the interfaces of,
1520+ the Work and Derivative Works thereof.
1521+
1522+ "Contribution" shall mean any work of authorship, including
1523+ the original version of the Work and any modifications or additions
1524+ to that Work or Derivative Works thereof, that is intentionally
1525+ submitted to Licensor for inclusion in the Work by the copyright owner
1526+ or by an individual or Legal Entity authorized to submit on behalf of
1527+ the copyright owner. For the purposes of this definition, "submitted"
1528+ means any form of electronic, verbal, or written communication sent
1529+ to the Licensor or its representatives, including but not limited to
1530+ communication on electronic mailing lists, source code control systems,
1531+ and issue tracking systems that are managed by, or on behalf of, the
1532+ Licensor for the purpose of discussing and improving the Work, but
1533+ excluding communication that is conspicuously marked or otherwise
1534+ designated in writing by the copyright owner as "Not a Contribution."
1535+
1536+ "Contributor" shall mean Licensor and any individual or Legal Entity
1537+ on behalf of whom a Contribution has been received by Licensor and
1538+ subsequently incorporated within the Work.
1539+
1540+ 2. Grant of Copyright License. Subject to the terms and conditions of
1541+ this License, each Contributor hereby grants to You a perpetual,
1542+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
1543+ copyright license to reproduce, prepare Derivative Works of,
1544+ publicly display, publicly perform, sublicense, and distribute the
1545+ Work and such Derivative Works in Source or Object form.
1546+
1547+ 3. Grant of Patent License. Subject to the terms and conditions of
1548+ this License, each Contributor hereby grants to You a perpetual,
1549+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
1550+ (except as stated in this section) patent license to make, have made,
1551+ use, offer to sell, sell, import, and otherwise transfer the Work,
1552+ where such license applies only to those patent claims licensable
1553+ by such Contributor that are necessarily infringed by their
1554+ Contribution(s) alone or by combination of their Contribution(s)
1555+ with the Work to which such Contribution(s) was submitted. If You
1556+ institute patent litigation against any entity (including a
1557+ cross-claim or counterclaim in a lawsuit) alleging that the Work
1558+ or a Contribution incorporated within the Work constitutes direct
1559+ or contributory patent infringement, then any patent licenses
1560+ granted to You under this License for that Work shall terminate
1561+ as of the date such litigation is filed.
1562+
1563+ 4. Redistribution. You may reproduce and distribute copies of the
1564+ Work or Derivative Works thereof in any medium, with or without
1565+ modifications, and in Source or Object form, provided that You
1566+ meet the following conditions:
1567+
1568+ (a) You must give any other recipients of the Work or
1569+ Derivative Works a copy of this License; and
1570+
1571+ (b) You must cause any modified files to carry prominent notices
1572+ stating that You changed the files; and
1573+
1574+ (c) You must retain, in the Source form of any Derivative Works
1575+ that You distribute, all copyright, patent, trademark, and
1576+ attribution notices from the Source form of the Work,
1577+ excluding those notices that do not pertain to any part of
1578+ the Derivative Works; and
1579+
1580+ (d) If the Work includes a "NOTICE" text file as part of its
1581+ distribution, then any Derivative Works that You distribute must
1582+ include a readable copy of the attribution notices contained
1583+ within such NOTICE file, excluding those notices that do not
1584+ pertain to any part of the Derivative Works, in at least one
1585+ of the following places: within a NOTICE text file distributed
1586+ as part of the Derivative Works; within the Source form or
1587+ documentation, if provided along with the Derivative Works; or,
1588+ within a display generated by the Derivative Works, if and
1589+ wherever such third-party notices normally appear. The contents
1590+ of the NOTICE file are for informational purposes only and
1591+ do not modify the License. You may add Your own attribution
1592+ notices within Derivative Works that You distribute, alongside
1593+ or as an addendum to the NOTICE text from the Work, provided
1594+ that such additional attribution notices cannot be construed
1595+ as modifying the License.
1596+
1597+ You may add Your own copyright statement to Your modifications and
1598+ may provide additional or different license terms and conditions
1599+ for use, reproduction, or distribution of Your modifications, or
1600+ for any such Derivative Works as a whole, provided Your use,
1601+ reproduction, and distribution of the Work otherwise complies with
1602+ the conditions stated in this License.
1603+
1604+ 5. Submission of Contributions. Unless You explicitly state otherwise,
1605+ any Contribution intentionally submitted for inclusion in the Work
1606+ by You to the Licensor shall be under the terms and conditions of
1607+ this License, without any additional terms or conditions.
1608+ Notwithstanding the above, nothing herein shall supersede or modify
1609+ the terms of any separate license agreement you may have executed
1610+ with Licensor regarding such Contributions.
1611+
1612+ 6. Trademarks. This License does not grant permission to use the trade
1613+ names, trademarks, service marks, or product names of the Licensor,
1614+ except as required for reasonable and customary use in describing the
1615+ origin of the Work and reproducing the content of the NOTICE file.
1616+
1617+ 7. Disclaimer of Warranty. Unless required by applicable law or
1618+ agreed to in writing, Licensor provides the Work (and each
1619+ Contributor provides its Contributions) on an "AS IS" BASIS,
1620+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
1621+ implied, including, without limitation, any warranties or conditions
1622+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
1623+ PARTICULAR PURPOSE. You are solely responsible for determining the
1624+ appropriateness of using or redistributing the Work and assume any
1625+ risks associated with Your exercise of permissions under this License.
1626+
1627+ 8. Limitation of Liability. In no event and under no legal theory,
1628+ whether in tort (including negligence), contract, or otherwise,
1629+ unless required by applicable law (such as deliberate and grossly
1630+ negligent acts) or agreed to in writing, shall any Contributor be
1631+ liable to You for damages, including any direct, indirect, special,
1632+ incidental, or consequential damages of any character arising as a
1633+ result of this License or out of the use or inability to use the
1634+ Work (including but not limited to damages for loss of goodwill,
1635+ work stoppage, computer failure or malfunction, or any and all
1636+ other commercial damages or losses), even if such Contributor
1637+ has been advised of the possibility of such damages.
1638+
1639+ 9. Accepting Warranty or Additional Liability. While redistributing
1640+ the Work or Derivative Works thereof, You may choose to offer,
1641+ and charge a fee for, acceptance of support, warranty, indemnity,
1642+ or other liability obligations and/or rights consistent with this
1643+ License. However, in accepting such obligations, You may act only
1644+ on Your own behalf and on Your sole responsibility, not on behalf
1645+ of any other Contributor, and only if You agree to indemnify,
1646+ defend, and hold each Contributor harmless for any liability
1647+ incurred by, or claims asserted against, such Contributor by reason
1648+ of your accepting any such warranty or additional liability.
1649+
1650+ END OF TERMS AND CONDITIONS
1651+
1652+ APPENDIX: How to apply the Apache License to your work.
1653+
1654+ To apply the Apache License to your work, attach the following
1655+ boilerplate notice, with the fields enclosed by brackets "[]"
1656+ replaced with your own identifying information. (Don't include
1657+ the brackets!) The text should be enclosed in the appropriate
1658+ comment syntax for the file format. We also recommend that a
1659+ file or class name and description of purpose be included on the
1660+ same "printed page" as the copyright notice for easier
1661+ identification within third-party archives.
1662+
1663+ Copyright [yyyy] [name of copyright owner]
1664+
1665+ Licensed under the Apache License, Version 2.0 (the "License");
1666+ you may not use this file except in compliance with the License.
1667+ You may obtain a copy of the License at
1668+
1669+ http://www.apache.org/licenses/LICENSE-2.0
1670+
1671+ Unless required by applicable law or agreed to in writing, software
1672+ distributed under the License is distributed on an "AS IS" BASIS,
1673+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1674+ See the License for the specific language governing permissions and
1675+ limitations under the License.
1676diff --git a/LICENSE-GPLv3 b/LICENSE-GPLv3
1677new file mode 100644
1678index 0000000..94a9ed0
1679--- /dev/null
1680+++ b/LICENSE-GPLv3
1681@@ -0,0 +1,674 @@
1682+ GNU GENERAL PUBLIC LICENSE
1683+ Version 3, 29 June 2007
1684+
1685+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
1686+ Everyone is permitted to copy and distribute verbatim copies
1687+ of this license document, but changing it is not allowed.
1688+
1689+ Preamble
1690+
1691+ The GNU General Public License is a free, copyleft license for
1692+software and other kinds of works.
1693+
1694+ The licenses for most software and other practical works are designed
1695+to take away your freedom to share and change the works. By contrast,
1696+the GNU General Public License is intended to guarantee your freedom to
1697+share and change all versions of a program--to make sure it remains free
1698+software for all its users. We, the Free Software Foundation, use the
1699+GNU General Public License for most of our software; it applies also to
1700+any other work released this way by its authors. You can apply it to
1701+your programs, too.
1702+
1703+ When we speak of free software, we are referring to freedom, not
1704+price. Our General Public Licenses are designed to make sure that you
1705+have the freedom to distribute copies of free software (and charge for
1706+them if you wish), that you receive source code or can get it if you
1707+want it, that you can change the software or use pieces of it in new
1708+free programs, and that you know you can do these things.
1709+
1710+ To protect your rights, we need to prevent others from denying you
1711+these rights or asking you to surrender the rights. Therefore, you have
1712+certain responsibilities if you distribute copies of the software, or if
1713+you modify it: responsibilities to respect the freedom of others.
1714+
1715+ For example, if you distribute copies of such a program, whether
1716+gratis or for a fee, you must pass on to the recipients the same
1717+freedoms that you received. You must make sure that they, too, receive
1718+or can get the source code. And you must show them these terms so they
1719+know their rights.
1720+
1721+ Developers that use the GNU GPL protect your rights with two steps:
1722+(1) assert copyright on the software, and (2) offer you this License
1723+giving you legal permission to copy, distribute and/or modify it.
1724+
1725+ For the developers' and authors' protection, the GPL clearly explains
1726+that there is no warranty for this free software. For both users' and
1727+authors' sake, the GPL requires that modified versions be marked as
1728+changed, so that their problems will not be attributed erroneously to
1729+authors of previous versions.
1730+
1731+ Some devices are designed to deny users access to install or run
1732+modified versions of the software inside them, although the manufacturer
1733+can do so. This is fundamentally incompatible with the aim of
1734+protecting users' freedom to change the software. The systematic
1735+pattern of such abuse occurs in the area of products for individuals to
1736+use, which is precisely where it is most unacceptable. Therefore, we
1737+have designed this version of the GPL to prohibit the practice for those
1738+products. If such problems arise substantially in other domains, we
1739+stand ready to extend this provision to those domains in future versions
1740+of the GPL, as needed to protect the freedom of users.
1741+
1742+ Finally, every program is threatened constantly by software patents.
1743+States should not allow patents to restrict development and use of
1744+software on general-purpose computers, but in those that do, we wish to
1745+avoid the special danger that patents applied to a free program could
1746+make it effectively proprietary. To prevent this, the GPL assures that
1747+patents cannot be used to render the program non-free.
1748+
1749+ The precise terms and conditions for copying, distribution and
1750+modification follow.
1751+
1752+ TERMS AND CONDITIONS
1753+
1754+ 0. Definitions.
1755+
1756+ "This License" refers to version 3 of the GNU General Public License.
1757+
1758+ "Copyright" also means copyright-like laws that apply to other kinds of
1759+works, such as semiconductor masks.
1760+
1761+ "The Program" refers to any copyrightable work licensed under this
1762+License. Each licensee is addressed as "you". "Licensees" and
1763+"recipients" may be individuals or organizations.
1764+
1765+ To "modify" a work means to copy from or adapt all or part of the work
1766+in a fashion requiring copyright permission, other than the making of an
1767+exact copy. The resulting work is called a "modified version" of the
1768+earlier work or a work "based on" the earlier work.
1769+
1770+ A "covered work" means either the unmodified Program or a work based
1771+on the Program.
1772+
1773+ To "propagate" a work means to do anything with it that, without
1774+permission, would make you directly or secondarily liable for
1775+infringement under applicable copyright law, except executing it on a
1776+computer or modifying a private copy. Propagation includes copying,
1777+distribution (with or without modification), making available to the
1778+public, and in some countries other activities as well.
1779+
1780+ To "convey" a work means any kind of propagation that enables other
1781+parties to make or receive copies. Mere interaction with a user through
1782+a computer network, with no transfer of a copy, is not conveying.
1783+
1784+ An interactive user interface displays "Appropriate Legal Notices"
1785+to the extent that it includes a convenient and prominently visible
1786+feature that (1) displays an appropriate copyright notice, and (2)
1787+tells the user that there is no warranty for the work (except to the
1788+extent that warranties are provided), that licensees may convey the
1789+work under this License, and how to view a copy of this License. If
1790+the interface presents a list of user commands or options, such as a
1791+menu, a prominent item in the list meets this criterion.
1792+
1793+ 1. Source Code.
1794+
1795+ The "source code" for a work means the preferred form of the work
1796+for making modifications to it. "Object code" means any non-source
1797+form of a work.
1798+
1799+ A "Standard Interface" means an interface that either is an official
1800+standard defined by a recognized standards body, or, in the case of
1801+interfaces specified for a particular programming language, one that
1802+is widely used among developers working in that language.
1803+
1804+ The "System Libraries" of an executable work include anything, other
1805+than the work as a whole, that (a) is included in the normal form of
1806+packaging a Major Component, but which is not part of that Major
1807+Component, and (b) serves only to enable use of the work with that
1808+Major Component, or to implement a Standard Interface for which an
1809+implementation is available to the public in source code form. A
1810+"Major Component", in this context, means a major essential component
1811+(kernel, window system, and so on) of the specific operating system
1812+(if any) on which the executable work runs, or a compiler used to
1813+produce the work, or an object code interpreter used to run it.
1814+
1815+ The "Corresponding Source" for a work in object code form means all
1816+the source code needed to generate, install, and (for an executable
1817+work) run the object code and to modify the work, including scripts to
1818+control those activities. However, it does not include the work's
1819+System Libraries, or general-purpose tools or generally available free
1820+programs which are used unmodified in performing those activities but
1821+which are not part of the work. For example, Corresponding Source
1822+includes interface definition files associated with source files for
1823+the work, and the source code for shared libraries and dynamically
1824+linked subprograms that the work is specifically designed to require,
1825+such as by intimate data communication or control flow between those
1826+subprograms and other parts of the work.
1827+
1828+ The Corresponding Source need not include anything that users
1829+can regenerate automatically from other parts of the Corresponding
1830+Source.
1831+
1832+ The Corresponding Source for a work in source code form is that
1833+same work.
1834+
1835+ 2. Basic Permissions.
1836+
1837+ All rights granted under this License are granted for the term of
1838+copyright on the Program, and are irrevocable provided the stated
1839+conditions are met. This License explicitly affirms your unlimited
1840+permission to run the unmodified Program. The output from running a
1841+covered work is covered by this License only if the output, given its
1842+content, constitutes a covered work. This License acknowledges your
1843+rights of fair use or other equivalent, as provided by copyright law.
1844+
1845+ You may make, run and propagate covered works that you do not
1846+convey, without conditions so long as your license otherwise remains
1847+in force. You may convey covered works to others for the sole purpose
1848+of having them make modifications exclusively for you, or provide you
1849+with facilities for running those works, provided that you comply with
1850+the terms of this License in conveying all material for which you do
1851+not control copyright. Those thus making or running the covered works
1852+for you must do so exclusively on your behalf, under your direction
1853+and control, on terms that prohibit them from making any copies of
1854+your copyrighted material outside their relationship with you.
1855+
1856+ Conveying under any other circumstances is permitted solely under
1857+the conditions stated below. Sublicensing is not allowed; section 10
1858+makes it unnecessary.
1859+
1860+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
1861+
1862+ No covered work shall be deemed part of an effective technological
1863+measure under any applicable law fulfilling obligations under article
1864+11 of the WIPO copyright treaty adopted on 20 December 1996, or
1865+similar laws prohibiting or restricting circumvention of such
1866+measures.
1867+
1868+ When you convey a covered work, you waive any legal power to forbid
1869+circumvention of technological measures to the extent such circumvention
1870+is effected by exercising rights under this License with respect to
1871+the covered work, and you disclaim any intention to limit operation or
1872+modification of the work as a means of enforcing, against the work's
1873+users, your or third parties' legal rights to forbid circumvention of
1874+technological measures.
1875+
1876+ 4. Conveying Verbatim Copies.
1877+
1878+ You may convey verbatim copies of the Program's source code as you
1879+receive it, in any medium, provided that you conspicuously and
1880+appropriately publish on each copy an appropriate copyright notice;
1881+keep intact all notices stating that this License and any
1882+non-permissive terms added in accord with section 7 apply to the code;
1883+keep intact all notices of the absence of any warranty; and give all
1884+recipients a copy of this License along with the Program.
1885+
1886+ You may charge any price or no price for each copy that you convey,
1887+and you may offer support or warranty protection for a fee.
1888+
1889+ 5. Conveying Modified Source Versions.
1890+
1891+ You may convey a work based on the Program, or the modifications to
1892+produce it from the Program, in the form of source code under the
1893+terms of section 4, provided that you also meet all of these conditions:
1894+
1895+ a) The work must carry prominent notices stating that you modified
1896+ it, and giving a relevant date.
1897+
1898+ b) The work must carry prominent notices stating that it is
1899+ released under this License and any conditions added under section
1900+ 7. This requirement modifies the requirement in section 4 to
1901+ "keep intact all notices".
1902+
1903+ c) You must license the entire work, as a whole, under this
1904+ License to anyone who comes into possession of a copy. This
1905+ License will therefore apply, along with any applicable section 7
1906+ additional terms, to the whole of the work, and all its parts,
1907+ regardless of how they are packaged. This License gives no
1908+ permission to license the work in any other way, but it does not
1909+ invalidate such permission if you have separately received it.
1910+
1911+ d) If the work has interactive user interfaces, each must display
1912+ Appropriate Legal Notices; however, if the Program has interactive
1913+ interfaces that do not display Appropriate Legal Notices, your
1914+ work need not make them do so.
1915+
1916+ A compilation of a covered work with other separate and independent
1917+works, which are not by their nature extensions of the covered work,
1918+and which are not combined with it such as to form a larger program,
1919+in or on a volume of a storage or distribution medium, is called an
1920+"aggregate" if the compilation and its resulting copyright are not
1921+used to limit the access or legal rights of the compilation's users
1922+beyond what the individual works permit. Inclusion of a covered work
1923+in an aggregate does not cause this License to apply to the other
1924+parts of the aggregate.
1925+
1926+ 6. Conveying Non-Source Forms.
1927+
1928+ You may convey a covered work in object code form under the terms
1929+of sections 4 and 5, provided that you also convey the
1930+machine-readable Corresponding Source under the terms of this License,
1931+in one of these ways:
1932+
1933+ a) Convey the object code in, or embodied in, a physical product
1934+ (including a physical distribution medium), accompanied by the
1935+ Corresponding Source fixed on a durable physical medium
1936+ customarily used for software interchange.
1937+
1938+ b) Convey the object code in, or embodied in, a physical product
1939+ (including a physical distribution medium), accompanied by a
1940+ written offer, valid for at least three years and valid for as
1941+ long as you offer spare parts or customer support for that product
1942+ model, to give anyone who possesses the object code either (1) a
1943+ copy of the Corresponding Source for all the software in the
1944+ product that is covered by this License, on a durable physical
1945+ medium customarily used for software interchange, for a price no
1946+ more than your reasonable cost of physically performing this
1947+ conveying of source, or (2) access to copy the
1948+ Corresponding Source from a network server at no charge.
1949+
1950+ c) Convey individual copies of the object code with a copy of the
1951+ written offer to provide the Corresponding Source. This
1952+ alternative is allowed only occasionally and noncommercially, and
1953+ only if you received the object code with such an offer, in accord
1954+ with subsection 6b.
1955+
1956+ d) Convey the object code by offering access from a designated
1957+ place (gratis or for a charge), and offer equivalent access to the
1958+ Corresponding Source in the same way through the same place at no
1959+ further charge. You need not require recipients to copy the
1960+ Corresponding Source along with the object code. If the place to
1961+ copy the object code is a network server, the Corresponding Source
1962+ may be on a different server (operated by you or a third party)
1963+ that supports equivalent copying facilities, provided you maintain
1964+ clear directions next to the object code saying where to find the
1965+ Corresponding Source. Regardless of what server hosts the
1966+ Corresponding Source, you remain obligated to ensure that it is
1967+ available for as long as needed to satisfy these requirements.
1968+
1969+ e) Convey the object code using peer-to-peer transmission, provided
1970+ you inform other peers where the object code and Corresponding
1971+ Source of the work are being offered to the general public at no
1972+ charge under subsection 6d.
1973+
1974+ A separable portion of the object code, whose source code is excluded
1975+from the Corresponding Source as a System Library, need not be
1976+included in conveying the object code work.
1977+
1978+ A "User Product" is either (1) a "consumer product", which means any
1979+tangible personal property which is normally used for personal, family,
1980+or household purposes, or (2) anything designed or sold for incorporation
1981+into a dwelling. In determining whether a product is a consumer product,
1982+doubtful cases shall be resolved in favor of coverage. For a particular
1983+product received by a particular user, "normally used" refers to a
1984+typical or common use of that class of product, regardless of the status
1985+of the particular user or of the way in which the particular user
1986+actually uses, or expects or is expected to use, the product. A product
1987+is a consumer product regardless of whether the product has substantial
1988+commercial, industrial or non-consumer uses, unless such uses represent
1989+the only significant mode of use of the product.
1990+
1991+ "Installation Information" for a User Product means any methods,
1992+procedures, authorization keys, or other information required to install
1993+and execute modified versions of a covered work in that User Product from
1994+a modified version of its Corresponding Source. The information must
1995+suffice to ensure that the continued functioning of the modified object
1996+code is in no case prevented or interfered with solely because
1997+modification has been made.
1998+
1999+ If you convey an object code work under this section in, or with, or
2000+specifically for use in, a User Product, and the conveying occurs as
2001+part of a transaction in which the right of possession and use of the
2002+User Product is transferred to the recipient in perpetuity or for a
2003+fixed term (regardless of how the transaction is characterized), the
2004+Corresponding Source conveyed under this section must be accompanied
2005+by the Installation Information. But this requirement does not apply
2006+if neither you nor any third party retains the ability to install
2007+modified object code on the User Product (for example, the work has
2008+been installed in ROM).
2009+
2010+ The requirement to provide Installation Information does not include a
2011+requirement to continue to provide support service, warranty, or updates
2012+for a work that has been modified or installed by the recipient, or for
2013+the User Product in which it has been modified or installed. Access to a
2014+network may be denied when the modification itself materially and
2015+adversely affects the operation of the network or violates the rules and
2016+protocols for communication across the network.
2017+
2018+ Corresponding Source conveyed, and Installation Information provided,
2019+in accord with this section must be in a format that is publicly
2020+documented (and with an implementation available to the public in
2021+source code form), and must require no special password or key for
2022+unpacking, reading or copying.
2023+
2024+ 7. Additional Terms.
2025+
2026+ "Additional permissions" are terms that supplement the terms of this
2027+License by making exceptions from one or more of its conditions.
2028+Additional permissions that are applicable to the entire Program shall
2029+be treated as though they were included in this License, to the extent
2030+that they are valid under applicable law. If additional permissions
2031+apply only to part of the Program, that part may be used separately
2032+under those permissions, but the entire Program remains governed by
2033+this License without regard to the additional permissions.
2034+
2035+ When you convey a copy of a covered work, you may at your option
2036+remove any additional permissions from that copy, or from any part of
2037+it. (Additional permissions may be written to require their own
2038+removal in certain cases when you modify the work.) You may place
2039+additional permissions on material, added by you to a covered work,
2040+for which you have or can give appropriate copyright permission.
2041+
2042+ Notwithstanding any other provision of this License, for material you
2043+add to a covered work, you may (if authorized by the copyright holders of
2044+that material) supplement the terms of this License with terms:
2045+
2046+ a) Disclaiming warranty or limiting liability differently from the
2047+ terms of sections 15 and 16 of this License; or
2048+
2049+ b) Requiring preservation of specified reasonable legal notices or
2050+ author attributions in that material or in the Appropriate Legal
2051+ Notices displayed by works containing it; or
2052+
2053+ c) Prohibiting misrepresentation of the origin of that material, or
2054+ requiring that modified versions of such material be marked in
2055+ reasonable ways as different from the original version; or
2056+
2057+ d) Limiting the use for publicity purposes of names of licensors or
2058+ authors of the material; or
2059+
2060+ e) Declining to grant rights under trademark law for use of some
2061+ trade names, trademarks, or service marks; or
2062+
2063+ f) Requiring indemnification of licensors and authors of that
2064+ material by anyone who conveys the material (or modified versions of
2065+ it) with contractual assumptions of liability to the recipient, for
2066+ any liability that these contractual assumptions directly impose on
2067+ those licensors and authors.
2068+
2069+ All other non-permissive additional terms are considered "further
2070+restrictions" within the meaning of section 10. If the Program as you
2071+received it, or any part of it, contains a notice stating that it is
2072+governed by this License along with a term that is a further
2073+restriction, you may remove that term. If a license document contains
2074+a further restriction but permits relicensing or conveying under this
2075+License, you may add to a covered work material governed by the terms
2076+of that license document, provided that the further restriction does
2077+not survive such relicensing or conveying.
2078+
2079+ If you add terms to a covered work in accord with this section, you
2080+must place, in the relevant source files, a statement of the
2081+additional terms that apply to those files, or a notice indicating
2082+where to find the applicable terms.
2083+
2084+ Additional terms, permissive or non-permissive, may be stated in the
2085+form of a separately written license, or stated as exceptions;
2086+the above requirements apply either way.
2087+
2088+ 8. Termination.
2089+
2090+ You may not propagate or modify a covered work except as expressly
2091+provided under this License. Any attempt otherwise to propagate or
2092+modify it is void, and will automatically terminate your rights under
2093+this License (including any patent licenses granted under the third
2094+paragraph of section 11).
2095+
2096+ However, if you cease all violation of this License, then your
2097+license from a particular copyright holder is reinstated (a)
2098+provisionally, unless and until the copyright holder explicitly and
2099+finally terminates your license, and (b) permanently, if the copyright
2100+holder fails to notify you of the violation by some reasonable means
2101+prior to 60 days after the cessation.
2102+
2103+ Moreover, your license from a particular copyright holder is
2104+reinstated permanently if the copyright holder notifies you of the
2105+violation by some reasonable means, this is the first time you have
2106+received notice of violation of this License (for any work) from that
2107+copyright holder, and you cure the violation prior to 30 days after
2108+your receipt of the notice.
2109+
2110+ Termination of your rights under this section does not terminate the
2111+licenses of parties who have received copies or rights from you under
2112+this License. If your rights have been terminated and not permanently
2113+reinstated, you do not qualify to receive new licenses for the same
2114+material under section 10.
2115+
2116+ 9. Acceptance Not Required for Having Copies.
2117+
2118+ You are not required to accept this License in order to receive or
2119+run a copy of the Program. Ancillary propagation of a covered work
2120+occurring solely as a consequence of using peer-to-peer transmission
2121+to receive a copy likewise does not require acceptance. However,
2122+nothing other than this License grants you permission to propagate or
2123+modify any covered work. These actions infringe copyright if you do
2124+not accept this License. Therefore, by modifying or propagating a
2125+covered work, you indicate your acceptance of this License to do so.
2126+
2127+ 10. Automatic Licensing of Downstream Recipients.
2128+
2129+ Each time you convey a covered work, the recipient automatically
2130+receives a license from the original licensors, to run, modify and
2131+propagate that work, subject to this License. You are not responsible
2132+for enforcing compliance by third parties with this License.
2133+
2134+ An "entity transaction" is a transaction transferring control of an
2135+organization, or substantially all assets of one, or subdividing an
2136+organization, or merging organizations. If propagation of a covered
2137+work results from an entity transaction, each party to that
2138+transaction who receives a copy of the work also receives whatever
2139+licenses to the work the party's predecessor in interest had or could
2140+give under the previous paragraph, plus a right to possession of the
2141+Corresponding Source of the work from the predecessor in interest, if
2142+the predecessor has it or can get it with reasonable efforts.
2143+
2144+ You may not impose any further restrictions on the exercise of the
2145+rights granted or affirmed under this License. For example, you may
2146+not impose a license fee, royalty, or other charge for exercise of
2147+rights granted under this License, and you may not initiate litigation
2148+(including a cross-claim or counterclaim in a lawsuit) alleging that
2149+any patent claim is infringed by making, using, selling, offering for
2150+sale, or importing the Program or any portion of it.
2151+
2152+ 11. Patents.
2153+
2154+ A "contributor" is a copyright holder who authorizes use under this
2155+License of the Program or a work on which the Program is based. The
2156+work thus licensed is called the contributor's "contributor version".
2157+
2158+ A contributor's "essential patent claims" are all patent claims
2159+owned or controlled by the contributor, whether already acquired or
2160+hereafter acquired, that would be infringed by some manner, permitted
2161+by this License, of making, using, or selling its contributor version,
2162+but do not include claims that would be infringed only as a
2163+consequence of further modification of the contributor version. For
2164+purposes of this definition, "control" includes the right to grant
2165+patent sublicenses in a manner consistent with the requirements of
2166+this License.
2167+
2168+ Each contributor grants you a non-exclusive, worldwide, royalty-free
2169+patent license under the contributor's essential patent claims, to
2170+make, use, sell, offer for sale, import and otherwise run, modify and
2171+propagate the contents of its contributor version.
2172+
2173+ In the following three paragraphs, a "patent license" is any express
2174+agreement or commitment, however denominated, not to enforce a patent
2175+(such as an express permission to practice a patent or covenant not to
2176+sue for patent infringement). To "grant" such a patent license to a
2177+party means to make such an agreement or commitment not to enforce a
2178+patent against the party.
2179+
2180+ If you convey a covered work, knowingly relying on a patent license,
2181+and the Corresponding Source of the work is not available for anyone
2182+to copy, free of charge and under the terms of this License, through a
2183+publicly available network server or other readily accessible means,
2184+then you must either (1) cause the Corresponding Source to be so
2185+available, or (2) arrange to deprive yourself of the benefit of the
2186+patent license for this particular work, or (3) arrange, in a manner
2187+consistent with the requirements of this License, to extend the patent
2188+license to downstream recipients. "Knowingly relying" means you have
2189+actual knowledge that, but for the patent license, your conveying the
2190+covered work in a country, or your recipient's use of the covered work
2191+in a country, would infringe one or more identifiable patents in that
2192+country that you have reason to believe are valid.
2193+
2194+ If, pursuant to or in connection with a single transaction or
2195+arrangement, you convey, or propagate by procuring conveyance of, a
2196+covered work, and grant a patent license to some of the parties
2197+receiving the covered work authorizing them to use, propagate, modify
2198+or convey a specific copy of the covered work, then the patent license
2199+you grant is automatically extended to all recipients of the covered
2200+work and works based on it.
2201+
2202+ A patent license is "discriminatory" if it does not include within
2203+the scope of its coverage, prohibits the exercise of, or is
2204+conditioned on the non-exercise of one or more of the rights that are
2205+specifically granted under this License. You may not convey a covered
2206+work if you are a party to an arrangement with a third party that is
2207+in the business of distributing software, under which you make payment
2208+to the third party based on the extent of your activity of conveying
2209+the work, and under which the third party grants, to any of the
2210+parties who would receive the covered work from you, a discriminatory
2211+patent license (a) in connection with copies of the covered work
2212+conveyed by you (or copies made from those copies), or (b) primarily
2213+for and in connection with specific products or compilations that
2214+contain the covered work, unless you entered into that arrangement,
2215+or that patent license was granted, prior to 28 March 2007.
2216+
2217+ Nothing in this License shall be construed as excluding or limiting
2218+any implied license or other defenses to infringement that may
2219+otherwise be available to you under applicable patent law.
2220+
2221+ 12. No Surrender of Others' Freedom.
2222+
2223+ If conditions are imposed on you (whether by court order, agreement or
2224+otherwise) that contradict the conditions of this License, they do not
2225+excuse you from the conditions of this License. If you cannot convey a
2226+covered work so as to satisfy simultaneously your obligations under this
2227+License and any other pertinent obligations, then as a consequence you may
2228+not convey it at all. For example, if you agree to terms that obligate you
2229+to collect a royalty for further conveying from those to whom you convey
2230+the Program, the only way you could satisfy both those terms and this
2231+License would be to refrain entirely from conveying the Program.
2232+
2233+ 13. Use with the GNU Affero General Public License.
2234+
2235+ Notwithstanding any other provision of this License, you have
2236+permission to link or combine any covered work with a work licensed
2237+under version 3 of the GNU Affero General Public License into a single
2238+combined work, and to convey the resulting work. The terms of this
2239+License will continue to apply to the part which is the covered work,
2240+but the special requirements of the GNU Affero General Public License,
2241+section 13, concerning interaction through a network will apply to the
2242+combination as such.
2243+
2244+ 14. Revised Versions of this License.
2245+
2246+ The Free Software Foundation may publish revised and/or new versions of
2247+the GNU General Public License from time to time. Such new versions will
2248+be similar in spirit to the present version, but may differ in detail to
2249+address new problems or concerns.
2250+
2251+ Each version is given a distinguishing version number. If the
2252+Program specifies that a certain numbered version of the GNU General
2253+Public License "or any later version" applies to it, you have the
2254+option of following the terms and conditions either of that numbered
2255+version or of any later version published by the Free Software
2256+Foundation. If the Program does not specify a version number of the
2257+GNU General Public License, you may choose any version ever published
2258+by the Free Software Foundation.
2259+
2260+ If the Program specifies that a proxy can decide which future
2261+versions of the GNU General Public License can be used, that proxy's
2262+public statement of acceptance of a version permanently authorizes you
2263+to choose that version for the Program.
2264+
2265+ Later license versions may give you additional or different
2266+permissions. However, no additional obligations are imposed on any
2267+author or copyright holder as a result of your choosing to follow a
2268+later version.
2269+
2270+ 15. Disclaimer of Warranty.
2271+
2272+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
2273+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
2274+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
2275+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
2276+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2277+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
2278+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
2279+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
2280+
2281+ 16. Limitation of Liability.
2282+
2283+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
2284+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
2285+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
2286+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
2287+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
2288+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
2289+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
2290+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
2291+SUCH DAMAGES.
2292+
2293+ 17. Interpretation of Sections 15 and 16.
2294+
2295+ If the disclaimer of warranty and limitation of liability provided
2296+above cannot be given local legal effect according to their terms,
2297+reviewing courts shall apply local law that most closely approximates
2298+an absolute waiver of all civil liability in connection with the
2299+Program, unless a warranty or assumption of liability accompanies a
2300+copy of the Program in return for a fee.
2301+
2302+ END OF TERMS AND CONDITIONS
2303+
2304+ How to Apply These Terms to Your New Programs
2305+
2306+ If you develop a new program, and you want it to be of the greatest
2307+possible use to the public, the best way to achieve this is to make it
2308+free software which everyone can redistribute and change under these terms.
2309+
2310+ To do so, attach the following notices to the program. It is safest
2311+to attach them to the start of each source file to most effectively
2312+state the exclusion of warranty; and each file should have at least
2313+the "copyright" line and a pointer to where the full notice is found.
2314+
2315+ <one line to give the program's name and a brief idea of what it does.>
2316+ Copyright (C) <year> <name of author>
2317+
2318+ This program is free software: you can redistribute it and/or modify
2319+ it under the terms of the GNU General Public License as published by
2320+ the Free Software Foundation, either version 3 of the License, or
2321+ (at your option) any later version.
2322+
2323+ This program is distributed in the hope that it will be useful,
2324+ but WITHOUT ANY WARRANTY; without even the implied warranty of
2325+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2326+ GNU General Public License for more details.
2327+
2328+ You should have received a copy of the GNU General Public License
2329+ along with this program. If not, see <http://www.gnu.org/licenses/>.
2330+
2331+Also add information on how to contact you by electronic and paper mail.
2332+
2333+ If the program does terminal interaction, make it output a short
2334+notice like this when it starts in an interactive mode:
2335+
2336+ <program> Copyright (C) <year> <name of author>
2337+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
2338+ This is free software, and you are welcome to redistribute it
2339+ under certain conditions; type `show c' for details.
2340+
2341+The hypothetical commands `show w' and `show c' should show the appropriate
2342+parts of the General Public License. Of course, your program's commands
2343+might be different; for a GUI interface, you would use an "about box".
2344+
2345+ You should also get your employer (if you work as a programmer) or school,
2346+if any, to sign a "copyright disclaimer" for the program, if necessary.
2347+For more information on this, and how to apply and follow the GNU GPL, see
2348+<http://www.gnu.org/licenses/>.
2349+
2350+ The GNU General Public License does not permit incorporating your program
2351+into proprietary programs. If your program is a subroutine library, you
2352+may consider it more useful to permit linking proprietary applications with
2353+the library. If this is what you want to do, use the GNU Lesser General
2354+Public License instead of this License. But first, please read
2355+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
2356diff --git a/MANIFEST.in b/MANIFEST.in
2357index 9426464..1a4d771 100644
2358--- a/MANIFEST.in
2359+++ b/MANIFEST.in
2360@@ -1,6 +1,15 @@
2361-include *.py MANIFEST.in ChangeLog
2362+include *.py MANIFEST.in LICENSE* ChangeLog
2363 global-include *.txt *.rst *.ini *.in *.conf *.cfg *.sh
2364+graft config
2365+graft doc
2366+graft packages
2367+graft systemd
2368+graft sysvinit
2369+graft templates
2370+graft tests
2371 graft tools
2372+graft udev
2373+graft upstart
2374 prune build
2375 prune dist
2376 prune .tox
2377diff --git a/Makefile b/Makefile
2378index 5d35dcc..4ace227 100644
2379--- a/Makefile
2380+++ b/Makefile
2381@@ -1,10 +1,10 @@
2382 CWD=$(shell pwd)
2383 PYVER ?= $(shell for p in python3 python2; do \
2384- out=$(which $$p 2>&1) && echo $$p && exit; done; \
2385- exit 1)
2386+ out=$$(command -v $$p 2>&1) && echo $$p && exit; done; exit 1)
2387+
2388 noseopts ?= -v
2389
2390-YAML_FILES=$(shell find cloudinit bin tests tools -name "*.yaml" -type f )
2391+YAML_FILES=$(shell find cloudinit tests tools -name "*.yaml" -type f )
2392 YAML_FILES+=$(shell find doc/examples -name "cloud-config*.txt" -type f )
2393
2394 PIP_INSTALL := pip install
2395@@ -27,13 +27,16 @@ ifeq ($(distro),)
2396 distro = redhat
2397 endif
2398
2399-READ_VERSION=$(shell $(PYVER) $(CWD)/tools/read-version)
2400+READ_VERSION=$(shell $(PYVER) $(CWD)/tools/read-version || \
2401+ echo read-version-failed)
2402 CODE_VERSION=$(shell $(PYVER) -c "from cloudinit import version; print(version.version_string())")
2403
2404
2405 all: check
2406
2407-check: check_version pep8 $(pyflakes) test $(yaml)
2408+check: check_version test $(yaml)
2409+
2410+style-check: pep8 $(pyflakes)
2411
2412 pep8:
2413 @$(CWD)/tools/run-pep8
2414@@ -43,12 +46,18 @@ pyflakes:
2415
2416 pyflakes3:
2417 @$(CWD)/tools/run-pyflakes3
2418-
2419+
2420 unittest: clean_pyc
2421- nosetests $(noseopts) tests/unittests
2422+ nosetests $(noseopts) tests/unittests cloudinit
2423
2424 unittest3: clean_pyc
2425- nosetests3 $(noseopts) tests/unittests
2426+ nosetests3 $(noseopts) tests/unittests cloudinit
2427+
2428+ci-deps-ubuntu:
2429+ @$(PYVER) $(CWD)/tools/read-dependencies --distro ubuntu --test-distro
2430+
2431+ci-deps-centos:
2432+ @$(PYVER) $(CWD)/tools/read-dependencies --distro centos --test-distro
2433
2434 pip-requirements:
2435 @echo "Installing cloud-init dependencies..."
2436@@ -62,10 +71,13 @@ test: $(unittests)
2437
2438 check_version:
2439 @if [ "$(READ_VERSION)" != "$(CODE_VERSION)" ]; then \
2440- echo "Error: read-version version $(READ_VERSION)" \
2441- "not equal to code version $(CODE_VERSION)"; exit 2; \
2442+ echo "Error: read-version version '$(READ_VERSION)'" \
2443+ "not equal to code version '$(CODE_VERSION)'"; exit 2; \
2444 else true; fi
2445
2446+config/cloud.cfg:
2447+ $(PYVER) ./tools/render-cloudcfg config/cloud.cfg.tmpl config/cloud.cfg
2448+
2449 clean_pyc:
2450 @find . -type f -name "*.pyc" -delete
2451
2452@@ -73,13 +85,28 @@ clean: clean_pyc
2453 rm -rf /var/log/cloud-init.log /var/lib/cloud/
2454
2455 yaml:
2456- @$(CWD)/tools/validate-yaml.py $(YAML_FILES)
2457+ @$(PYVER) $(CWD)/tools/validate-yaml.py $(YAML_FILES)
2458
2459 rpm:
2460- ./packages/brpm --distro $(distro)
2461+ $(PYVER) ./packages/brpm --distro=$(distro)
2462+
2463+srpm:
2464+ $(PYVER) ./packages/brpm --srpm --distro=$(distro)
2465
2466 deb:
2467- ./packages/bddeb
2468+ @which debuild || \
2469+ { echo "Missing devscripts dependency. Install with:"; \
2470+ echo sudo apt-get install devscripts; exit 1; }
2471+
2472+ $(PYVER) ./packages/bddeb
2473+
2474+deb-src:
2475+ @which debuild || \
2476+ { echo "Missing devscripts dependency. Install with:"; \
2477+ echo sudo apt-get install devscripts; exit 1; }
2478+ $(PYVER) ./packages/bddeb -S -d
2479+
2480
2481-.PHONY: test pyflakes pyflakes3 clean pep8 rpm deb yaml check_version
2482-.PHONY: pip-test-requirements pip-requirements clean_pyc unittest unittest3
2483+.PHONY: test pyflakes pyflakes3 clean pep8 rpm srpm deb deb-src yaml
2484+.PHONY: check_version pip-test-requirements pip-requirements clean_pyc
2485+.PHONY: unittest unittest3 style-check
2486diff --git a/cloudinit/__init__.py b/cloudinit/__init__.py
2487index da12464..e69de29 100644
2488--- a/cloudinit/__init__.py
2489+++ b/cloudinit/__init__.py
2490@@ -1,21 +0,0 @@
2491-# vi: ts=4 expandtab
2492-#
2493-# Copyright (C) 2012 Canonical Ltd.
2494-# Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
2495-# Copyright (C) 2012 Yahoo! Inc.
2496-#
2497-# Author: Scott Moser <scott.moser@canonical.com>
2498-# Author: Juerg Haefliger <juerg.haefliger@hp.com>
2499-# Author: Joshua Harlow <harlowja@yahoo-inc.com>
2500-#
2501-# This program is free software: you can redistribute it and/or modify
2502-# it under the terms of the GNU General Public License version 3, as
2503-# published by the Free Software Foundation.
2504-#
2505-# This program is distributed in the hope that it will be useful,
2506-# but WITHOUT ANY WARRANTY; without even the implied warranty of
2507-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2508-# GNU General Public License for more details.
2509-#
2510-# You should have received a copy of the GNU General Public License
2511-# along with this program. If not, see <http://www.gnu.org/licenses/>.
2512diff --git a/cloudinit/analyze/__init__.py b/cloudinit/analyze/__init__.py
2513new file mode 100644
2514index 0000000..e69de29
2515--- /dev/null
2516+++ b/cloudinit/analyze/__init__.py
2517diff --git a/cloudinit/analyze/__main__.py b/cloudinit/analyze/__main__.py
2518new file mode 100644
2519index 0000000..3ba5903
2520--- /dev/null
2521+++ b/cloudinit/analyze/__main__.py
2522@@ -0,0 +1,157 @@
2523+# Copyright (C) 2017 Canonical Ltd.
2524+#
2525+# This file is part of cloud-init. See LICENSE file for license information.
2526+
2527+import argparse
2528+import re
2529+import sys
2530+
2531+from cloudinit.util import json_dumps
2532+
2533+from . import dump
2534+from . import show
2535+
2536+
2537+def get_parser(parser=None):
2538+ if not parser:
2539+ parser = argparse.ArgumentParser(
2540+ prog='cloudinit-analyze',
2541+ description='Devel tool: Analyze cloud-init logs and data')
2542+ subparsers = parser.add_subparsers(title='Subcommands', dest='subcommand')
2543+ subparsers.required = True
2544+
2545+ parser_blame = subparsers.add_parser(
2546+ 'blame', help='Print list of executed stages ordered by time to init')
2547+ parser_blame.add_argument(
2548+ '-i', '--infile', action='store', dest='infile',
2549+ default='/var/log/cloud-init.log',
2550+ help='specify where to read input.')
2551+ parser_blame.add_argument(
2552+ '-o', '--outfile', action='store', dest='outfile', default='-',
2553+ help='specify where to write output. ')
2554+ parser_blame.set_defaults(action=('blame', analyze_blame))
2555+
2556+ parser_show = subparsers.add_parser(
2557+ 'show', help='Print list of in-order events during execution')
2558+ parser_show.add_argument('-f', '--format', action='store',
2559+ dest='print_format', default='%I%D @%Es +%ds',
2560+ help='specify formatting of output.')
2561+ parser_show.add_argument('-i', '--infile', action='store',
2562+ dest='infile', default='/var/log/cloud-init.log',
2563+ help='specify where to read input.')
2564+ parser_show.add_argument('-o', '--outfile', action='store',
2565+ dest='outfile', default='-',
2566+ help='specify where to write output.')
2567+ parser_show.set_defaults(action=('show', analyze_show))
2568+ parser_dump = subparsers.add_parser(
2569+ 'dump', help='Dump cloud-init events in JSON format')
2570+ parser_dump.add_argument('-i', '--infile', action='store',
2571+ dest='infile', default='/var/log/cloud-init.log',
2572+ help='specify where to read input. ')
2573+ parser_dump.add_argument('-o', '--outfile', action='store',
2574+ dest='outfile', default='-',
2575+ help='specify where to write output. ')
2576+ parser_dump.set_defaults(action=('dump', analyze_dump))
2577+ return parser
2578+
2579+
2580+def analyze_blame(name, args):
2581+ """Report a list of records sorted by largest time delta.
2582+
2583+ For example:
2584+ 30.210s (init-local) searching for datasource
2585+ 8.706s (init-network) reading and applying user-data
2586+ 166ms (modules-config) ....
2587+ 807us (modules-final) ...
2588+
2589+ We generate event records parsing cloud-init logs, formatting the output
2590+ and sorting by record data ('delta')
2591+ """
2592+ (infh, outfh) = configure_io(args)
2593+ blame_format = ' %ds (%n)'
2594+ r = re.compile('(^\s+\d+\.\d+)', re.MULTILINE)
2595+ for idx, record in enumerate(show.show_events(_get_events(infh),
2596+ blame_format)):
2597+ srecs = sorted(filter(r.match, record), reverse=True)
2598+ outfh.write('-- Boot Record %02d --\n' % (idx + 1))
2599+ outfh.write('\n'.join(srecs) + '\n')
2600+ outfh.write('\n')
2601+ outfh.write('%d boot records analyzed\n' % (idx + 1))
2602+
2603+
2604+def analyze_show(name, args):
2605+ """Generate output records using the 'standard' format to printing events.
2606+
2607+ Example output follows:
2608+ Starting stage: (init-local)
2609+ ...
2610+ Finished stage: (init-local) 0.105195 seconds
2611+
2612+ Starting stage: (init-network)
2613+ ...
2614+ Finished stage: (init-network) 0.339024 seconds
2615+
2616+ Starting stage: (modules-config)
2617+ ...
2618+ Finished stage: (modules-config) 0.NNN seconds
2619+
2620+ Starting stage: (modules-final)
2621+ ...
2622+ Finished stage: (modules-final) 0.NNN seconds
2623+ """
2624+ (infh, outfh) = configure_io(args)
2625+ for idx, record in enumerate(show.show_events(_get_events(infh),
2626+ args.print_format)):
2627+ outfh.write('-- Boot Record %02d --\n' % (idx + 1))
2628+ outfh.write('The total time elapsed since completing an event is'
2629+ ' printed after the "@" character.\n')
2630+ outfh.write('The time the event takes is printed after the "+" '
2631+ 'character.\n\n')
2632+ outfh.write('\n'.join(record) + '\n')
2633+ outfh.write('%d boot records analyzed\n' % (idx + 1))
2634+
2635+
2636+def analyze_dump(name, args):
2637+ """Dump cloud-init events in json format"""
2638+ (infh, outfh) = configure_io(args)
2639+ outfh.write(json_dumps(_get_events(infh)) + '\n')
2640+
2641+
2642+def _get_events(infile):
2643+ rawdata = None
2644+ events, rawdata = show.load_events(infile, None)
2645+ if not events:
2646+ events, _ = dump.dump_events(rawdata=rawdata)
2647+ return events
2648+
2649+
2650+def configure_io(args):
2651+ """Common parsing and setup of input/output files"""
2652+ if args.infile == '-':
2653+ infh = sys.stdin
2654+ else:
2655+ try:
2656+ infh = open(args.infile, 'r')
2657+ except OSError:
2658+ sys.stderr.write('Cannot open file %s\n' % args.infile)
2659+ sys.exit(1)
2660+
2661+ if args.outfile == '-':
2662+ outfh = sys.stdout
2663+ else:
2664+ try:
2665+ outfh = open(args.outfile, 'w')
2666+ except OSError:
2667+ sys.stderr.write('Cannot open file %s\n' % args.outfile)
2668+ sys.exit(1)
2669+
2670+ return (infh, outfh)
2671+
2672+
2673+if __name__ == '__main__':
2674+ parser = get_parser()
2675+ args = parser.parse_args()
2676+ (name, action_functor) = args.action
2677+ action_functor(name, args)
2678+
2679+# vi: ts=4 expandtab
2680diff --git a/cloudinit/analyze/dump.py b/cloudinit/analyze/dump.py
2681new file mode 100644
2682index 0000000..b071aa1
2683--- /dev/null
2684+++ b/cloudinit/analyze/dump.py
2685@@ -0,0 +1,170 @@
2686+# This file is part of cloud-init. See LICENSE file for license information.
2687+
2688+import calendar
2689+from datetime import datetime
2690+import sys
2691+
2692+from cloudinit import util
2693+
2694+stage_to_description = {
2695+ 'finished': 'finished running cloud-init',
2696+ 'init-local': 'starting search for local datasources',
2697+ 'init-network': 'searching for network datasources',
2698+ 'init': 'searching for network datasources',
2699+ 'modules-config': 'running config modules',
2700+ 'modules-final': 'finalizing modules',
2701+ 'modules': 'running modules for',
2702+ 'single': 'running single module ',
2703+}
2704+
2705+# logger's asctime format
2706+CLOUD_INIT_ASCTIME_FMT = "%Y-%m-%d %H:%M:%S,%f"
2707+
2708+# journctl -o short-precise
2709+CLOUD_INIT_JOURNALCTL_FMT = "%b %d %H:%M:%S.%f %Y"
2710+
2711+# other
2712+DEFAULT_FMT = "%b %d %H:%M:%S %Y"
2713+
2714+
2715+def parse_timestamp(timestampstr):
2716+ # default syslog time does not include the current year
2717+ months = [calendar.month_abbr[m] for m in range(1, 13)]
2718+ if timestampstr.split()[0] in months:
2719+ # Aug 29 22:55:26
2720+ FMT = DEFAULT_FMT
2721+ if '.' in timestampstr:
2722+ FMT = CLOUD_INIT_JOURNALCTL_FMT
2723+ dt = datetime.strptime(timestampstr + " " +
2724+ str(datetime.now().year),
2725+ FMT)
2726+ timestamp = dt.strftime("%s.%f")
2727+ elif "," in timestampstr:
2728+ # 2016-09-12 14:39:20,839
2729+ dt = datetime.strptime(timestampstr, CLOUD_INIT_ASCTIME_FMT)
2730+ timestamp = dt.strftime("%s.%f")
2731+ else:
2732+ # allow date(1) to handle other formats we don't expect
2733+ timestamp = parse_timestamp_from_date(timestampstr)
2734+
2735+ return float(timestamp)
2736+
2737+
2738+def parse_timestamp_from_date(timestampstr):
2739+ out, _ = util.subp(['date', '+%s.%3N', '-d', timestampstr])
2740+ timestamp = out.strip()
2741+ return float(timestamp)
2742+
2743+
2744+def parse_ci_logline(line):
2745+ # Stage Starts:
2746+ # Cloud-init v. 0.7.7 running 'init-local' at \
2747+ # Fri, 02 Sep 2016 19:28:07 +0000. Up 1.0 seconds.
2748+ # Cloud-init v. 0.7.7 running 'init' at \
2749+ # Fri, 02 Sep 2016 19:28:08 +0000. Up 2.0 seconds.
2750+ # Cloud-init v. 0.7.7 finished at
2751+ # Aug 29 22:55:26 test1 [CLOUDINIT] handlers.py[DEBUG]: \
2752+ # finish: modules-final: SUCCESS: running modules for final
2753+ # 2016-08-30T21:53:25.972325+00:00 y1 [CLOUDINIT] handlers.py[DEBUG]: \
2754+ # finish: modules-final: SUCCESS: running modules for final
2755+ #
2756+ # Nov 03 06:51:06.074410 x2 cloud-init[106]: [CLOUDINIT] util.py[DEBUG]: \
2757+ # Cloud-init v. 0.7.8 running 'init-local' at \
2758+ # Thu, 03 Nov 2016 06:51:06 +0000. Up 1.0 seconds.
2759+ #
2760+ # 2017-05-22 18:02:01,088 - util.py[DEBUG]: Cloud-init v. 0.7.9 running \
2761+ # 'init-local' at Mon, 22 May 2017 18:02:01 +0000. Up 2.0 seconds.
2762+
2763+ separators = [' - ', ' [CLOUDINIT] ']
2764+ found = False
2765+ for sep in separators:
2766+ if sep in line:
2767+ found = True
2768+ break
2769+
2770+ if not found:
2771+ return None
2772+
2773+ (timehost, eventstr) = line.split(sep)
2774+
2775+ # journalctl -o short-precise
2776+ if timehost.endswith(":"):
2777+ timehost = " ".join(timehost.split()[0:-1])
2778+
2779+ if "," in timehost:
2780+ timestampstr, extra = timehost.split(",")
2781+ timestampstr += ",%s" % extra.split()[0]
2782+ if ' ' in extra:
2783+ hostname = extra.split()[-1]
2784+ else:
2785+ hostname = timehost.split()[-1]
2786+ timestampstr = timehost.split(hostname)[0].strip()
2787+ if 'Cloud-init v.' in eventstr:
2788+ event_type = 'start'
2789+ if 'running' in eventstr:
2790+ stage_and_timestamp = eventstr.split('running')[1].lstrip()
2791+ event_name, _ = stage_and_timestamp.split(' at ')
2792+ event_name = event_name.replace("'", "").replace(":", "-")
2793+ if event_name == "init":
2794+ event_name = "init-network"
2795+ else:
2796+ # don't generate a start for the 'finished at' banner
2797+ return None
2798+ event_description = stage_to_description[event_name]
2799+ else:
2800+ (pymodloglvl, event_type, event_name) = eventstr.split()[0:3]
2801+ event_description = eventstr.split(event_name)[1].strip()
2802+
2803+ event = {
2804+ 'name': event_name.rstrip(":"),
2805+ 'description': event_description,
2806+ 'timestamp': parse_timestamp(timestampstr),
2807+ 'origin': 'cloudinit',
2808+ 'event_type': event_type.rstrip(":"),
2809+ }
2810+ if event['event_type'] == "finish":
2811+ result = event_description.split(":")[0]
2812+ desc = event_description.split(result)[1].lstrip(':').strip()
2813+ event['result'] = result
2814+ event['description'] = desc.strip()
2815+
2816+ return event
2817+
2818+
2819+def dump_events(cisource=None, rawdata=None):
2820+ events = []
2821+ event = None
2822+ CI_EVENT_MATCHES = ['start:', 'finish:', 'Cloud-init v.']
2823+
2824+ if not any([cisource, rawdata]):
2825+ raise ValueError('Either cisource or rawdata parameters are required')
2826+
2827+ if rawdata:
2828+ data = rawdata.splitlines()
2829+ else:
2830+ data = cisource.readlines()
2831+
2832+ for line in data:
2833+ for match in CI_EVENT_MATCHES:
2834+ if match in line:
2835+ try:
2836+ event = parse_ci_logline(line)
2837+ except ValueError:
2838+ sys.stderr.write('Skipping invalid entry\n')
2839+ if event:
2840+ events.append(event)
2841+
2842+ return events, data
2843+
2844+
2845+def main():
2846+ if len(sys.argv) > 1:
2847+ cisource = open(sys.argv[1])
2848+ else:
2849+ cisource = sys.stdin
2850+
2851+ return util.json_dumps(dump_events(cisource))
2852+
2853+
2854+if __name__ == "__main__":
2855+ print(main())
2856diff --git a/cloudinit/analyze/show.py b/cloudinit/analyze/show.py
2857new file mode 100644
2858index 0000000..3e778b8
2859--- /dev/null
2860+++ b/cloudinit/analyze/show.py
2861@@ -0,0 +1,207 @@
2862+# Copyright (C) 2016 Canonical Ltd.
2863+#
2864+# Author: Ryan Harper <ryan.harper@canonical.com>
2865+#
2866+# This file is part of cloud-init. See LICENSE file for license information.
2867+
2868+import base64
2869+import datetime
2870+import json
2871+import os
2872+
2873+from cloudinit import util
2874+
2875+# An event:
2876+'''
2877+{
2878+ "description": "executing late commands",
2879+ "event_type": "start",
2880+ "level": "INFO",
2881+ "name": "cmd-install/stage-late"
2882+ "origin": "cloudinit",
2883+ "timestamp": 1461164249.1590767,
2884+},
2885+
2886+ {
2887+ "description": "executing late commands",
2888+ "event_type": "finish",
2889+ "level": "INFO",
2890+ "name": "cmd-install/stage-late",
2891+ "origin": "cloudinit",
2892+ "result": "SUCCESS",
2893+ "timestamp": 1461164249.1590767
2894+ }
2895+
2896+'''
2897+format_key = {
2898+ '%d': 'delta',
2899+ '%D': 'description',
2900+ '%E': 'elapsed',
2901+ '%e': 'event_type',
2902+ '%I': 'indent',
2903+ '%l': 'level',
2904+ '%n': 'name',
2905+ '%o': 'origin',
2906+ '%r': 'result',
2907+ '%t': 'timestamp',
2908+ '%T': 'total_time',
2909+}
2910+
2911+formatting_help = " ".join(["{0}: {1}".format(k.replace('%', '%%'), v)
2912+ for k, v in format_key.items()])
2913+
2914+
2915+def format_record(msg, event):
2916+ for i, j in format_key.items():
2917+ if i in msg:
2918+ # ensure consistent formatting of time values
2919+ if j in ['delta', 'elapsed', 'timestamp']:
2920+ msg = msg.replace(i, "{%s:08.5f}" % j)
2921+ else:
2922+ msg = msg.replace(i, "{%s}" % j)
2923+ return msg.format(**event)
2924+
2925+
2926+def dump_event_files(event):
2927+ content = dict((k, v) for k, v in event.items() if k not in ['content'])
2928+ files = content['files']
2929+ saved = []
2930+ for f in files:
2931+ fname = f['path']
2932+ fn_local = os.path.basename(fname)
2933+ fcontent = base64.b64decode(f['content']).decode('ascii')
2934+ util.write_file(fn_local, fcontent)
2935+ saved.append(fn_local)
2936+
2937+ return saved
2938+
2939+
2940+def event_name(event):
2941+ if event:
2942+ return event.get('name')
2943+ return None
2944+
2945+
2946+def event_type(event):
2947+ if event:
2948+ return event.get('event_type')
2949+ return None
2950+
2951+
2952+def event_parent(event):
2953+ if event:
2954+ return event_name(event).split("/")[0]
2955+ return None
2956+
2957+
2958+def event_timestamp(event):
2959+ return float(event.get('timestamp'))
2960+
2961+
2962+def event_datetime(event):
2963+ return datetime.datetime.utcfromtimestamp(event_timestamp(event))
2964+
2965+
2966+def delta_seconds(t1, t2):
2967+ return (t2 - t1).total_seconds()
2968+
2969+
2970+def event_duration(start, finish):
2971+ return delta_seconds(event_datetime(start), event_datetime(finish))
2972+
2973+
2974+def event_record(start_time, start, finish):
2975+ record = finish.copy()
2976+ record.update({
2977+ 'delta': event_duration(start, finish),
2978+ 'elapsed': delta_seconds(start_time, event_datetime(start)),
2979+ 'indent': '|' + ' ' * (event_name(start).count('/') - 1) + '`->',
2980+ })
2981+
2982+ return record
2983+
2984+
2985+def total_time_record(total_time):
2986+ return 'Total Time: %3.5f seconds\n' % total_time
2987+
2988+
2989+def generate_records(events, blame_sort=False,
2990+ print_format="(%n) %d seconds in %I%D",
2991+ dump_files=False, log_datafiles=False):
2992+
2993+ sorted_events = sorted(events, key=lambda x: x['timestamp'])
2994+ records = []
2995+ start_time = None
2996+ total_time = 0.0
2997+ stage_start_time = {}
2998+ stages_seen = []
2999+ boot_records = []
3000+
3001+ unprocessed = []
3002+ for e in range(0, len(sorted_events)):
3003+ event = events[e]
3004+ try:
3005+ next_evt = events[e + 1]
3006+ except IndexError:
3007+ next_evt = None
3008+
3009+ if event_type(event) == 'start':
3010+ if event.get('name') in stages_seen:
3011+ records.append(total_time_record(total_time))
3012+ boot_records.append(records)
3013+ records = []
3014+ start_time = None
3015+ total_time = 0.0
3016+
3017+ if start_time is None:
3018+ stages_seen = []
3019+ start_time = event_datetime(event)
3020+ stage_start_time[event_parent(event)] = start_time
3021+
3022+ # see if we have a pair
3023+ if event_name(event) == event_name(next_evt):
3024+ if event_type(next_evt) == 'finish':
3025+ records.append(format_record(print_format,
3026+ event_record(start_time,
3027+ event,
3028+ next_evt)))
3029+ else:
3030+ # This is a parent event
3031+ records.append("Starting stage: %s" % event.get('name'))
3032+ unprocessed.append(event)
3033+ stages_seen.append(event.get('name'))
3034+ continue
3035+ else:
3036+ prev_evt = unprocessed.pop()
3037+ if event_name(event) == event_name(prev_evt):
3038+ record = event_record(start_time, prev_evt, event)
3039+ records.append(format_record("Finished stage: "
3040+ "(%n) %d seconds ",
3041+ record) + "\n")
3042+ total_time += record.get('delta')
3043+ else:
3044+ # not a match, put it back
3045+ unprocessed.append(prev_evt)
3046+
3047+ records.append(total_time_record(total_time))
3048+ boot_records.append(records)
3049+ return boot_records
3050+
3051+
3052+def show_events(events, print_format):
3053+ return generate_records(events, print_format=print_format)
3054+
3055+
3056+def load_events(infile, rawdata=None):
3057+ if rawdata:
3058+ data = rawdata.read()
3059+ else:
3060+ data = infile.read()
3061+
3062+ j = None
3063+ try:
3064+ j = json.loads(data)
3065+ except ValueError:
3066+ pass
3067+
3068+ return j, data
3069diff --git a/cloudinit/analyze/tests/test_dump.py b/cloudinit/analyze/tests/test_dump.py
3070new file mode 100644
3071index 0000000..f4c4284
3072--- /dev/null
3073+++ b/cloudinit/analyze/tests/test_dump.py
3074@@ -0,0 +1,210 @@
3075+# This file is part of cloud-init. See LICENSE file for license information.
3076+
3077+from datetime import datetime
3078+from textwrap import dedent
3079+
3080+from cloudinit.analyze.dump import (
3081+ dump_events, parse_ci_logline, parse_timestamp)
3082+from cloudinit.util import subp, write_file
3083+from cloudinit.tests.helpers import CiTestCase
3084+
3085+
3086+class TestParseTimestamp(CiTestCase):
3087+
3088+ def test_parse_timestamp_handles_cloud_init_default_format(self):
3089+ """Logs with cloud-init detailed formats will be properly parsed."""
3090+ trusty_fmt = '%Y-%m-%d %H:%M:%S,%f'
3091+ trusty_stamp = '2016-09-12 14:39:20,839'
3092+
3093+ parsed = parse_timestamp(trusty_stamp)
3094+
3095+ # convert ourselves
3096+ dt = datetime.strptime(trusty_stamp, trusty_fmt)
3097+ expected = float(dt.strftime('%s.%f'))
3098+
3099+ # use date(1)
3100+ out, _err = subp(['date', '+%s.%3N', '-d', trusty_stamp])
3101+ timestamp = out.strip()
3102+ date_ts = float(timestamp)
3103+
3104+ self.assertEqual(expected, parsed)
3105+ self.assertEqual(expected, date_ts)
3106+ self.assertEqual(date_ts, parsed)
3107+
3108+ def test_parse_timestamp_handles_syslog_adding_year(self):
3109+ """Syslog timestamps lack a year. Add year and properly parse."""
3110+ syslog_fmt = '%b %d %H:%M:%S %Y'
3111+ syslog_stamp = 'Aug 08 15:12:51'
3112+
3113+ # convert stamp ourselves by adding the missing year value
3114+ year = datetime.now().year
3115+ dt = datetime.strptime(syslog_stamp + " " + str(year), syslog_fmt)
3116+ expected = float(dt.strftime('%s.%f'))
3117+ parsed = parse_timestamp(syslog_stamp)
3118+
3119+ # use date(1)
3120+ out, _ = subp(['date', '+%s.%3N', '-d', syslog_stamp])
3121+ timestamp = out.strip()
3122+ date_ts = float(timestamp)
3123+
3124+ self.assertEqual(expected, parsed)
3125+ self.assertEqual(expected, date_ts)
3126+ self.assertEqual(date_ts, parsed)
3127+
3128+ def test_parse_timestamp_handles_journalctl_format_adding_year(self):
3129+ """Journalctl precise timestamps lack a year. Add year and parse."""
3130+ journal_fmt = '%b %d %H:%M:%S.%f %Y'
3131+ journal_stamp = 'Aug 08 17:15:50.606811'
3132+
3133+ # convert stamp ourselves by adding the missing year value
3134+ year = datetime.now().year
3135+ dt = datetime.strptime(journal_stamp + " " + str(year), journal_fmt)
3136+ expected = float(dt.strftime('%s.%f'))
3137+ parsed = parse_timestamp(journal_stamp)
3138+
3139+ # use date(1)
3140+ out, _ = subp(['date', '+%s.%6N', '-d', journal_stamp])
3141+ timestamp = out.strip()
3142+ date_ts = float(timestamp)
3143+
3144+ self.assertEqual(expected, parsed)
3145+ self.assertEqual(expected, date_ts)
3146+ self.assertEqual(date_ts, parsed)
3147+
3148+ def test_parse_unexpected_timestamp_format_with_date_command(self):
3149+ """Dump sends unexpected timestamp formats to data for processing."""
3150+ new_fmt = '%H:%M %m/%d %Y'
3151+ new_stamp = '17:15 08/08'
3152+
3153+ # convert stamp ourselves by adding the missing year value
3154+ year = datetime.now().year
3155+ dt = datetime.strptime(new_stamp + " " + str(year), new_fmt)
3156+ expected = float(dt.strftime('%s.%f'))
3157+ parsed = parse_timestamp(new_stamp)
3158+
3159+ # use date(1)
3160+ out, _ = subp(['date', '+%s.%6N', '-d', new_stamp])
3161+ timestamp = out.strip()
3162+ date_ts = float(timestamp)
3163+
3164+ self.assertEqual(expected, parsed)
3165+ self.assertEqual(expected, date_ts)
3166+ self.assertEqual(date_ts, parsed)
3167+
3168+
3169+class TestParseCILogLine(CiTestCase):
3170+
3171+ def test_parse_logline_returns_none_without_separators(self):
3172+ """When no separators are found, parse_ci_logline returns None."""
3173+ expected_parse_ignores = [
3174+ '', '-', 'adsf-asdf', '2017-05-22 18:02:01,088', 'CLOUDINIT']
3175+ for parse_ignores in expected_parse_ignores:
3176+ self.assertIsNone(parse_ci_logline(parse_ignores))
3177+
3178+ def test_parse_logline_returns_event_for_cloud_init_logs(self):
3179+ """parse_ci_logline returns an event parse from cloud-init format."""
3180+ line = (
3181+ "2017-08-08 20:05:07,147 - util.py[DEBUG]: Cloud-init v. 0.7.9"
3182+ " running 'init-local' at Tue, 08 Aug 2017 20:05:07 +0000. Up"
3183+ " 6.26 seconds.")
3184+ dt = datetime.strptime(
3185+ '2017-08-08 20:05:07,147', '%Y-%m-%d %H:%M:%S,%f')
3186+ timestamp = float(dt.strftime('%s.%f'))
3187+ expected = {
3188+ 'description': 'starting search for local datasources',
3189+ 'event_type': 'start',
3190+ 'name': 'init-local',
3191+ 'origin': 'cloudinit',
3192+ 'timestamp': timestamp}
3193+ self.assertEqual(expected, parse_ci_logline(line))
3194+
3195+ def test_parse_logline_returns_event_for_journalctl_logs(self):
3196+ """parse_ci_logline returns an event parse from journalctl format."""
3197+ line = ("Nov 03 06:51:06.074410 x2 cloud-init[106]: [CLOUDINIT]"
3198+ " util.py[DEBUG]: Cloud-init v. 0.7.8 running 'init-local' at"
3199+ " Thu, 03 Nov 2016 06:51:06 +0000. Up 1.0 seconds.")
3200+ year = datetime.now().year
3201+ dt = datetime.strptime(
3202+ 'Nov 03 06:51:06.074410 %d' % year, '%b %d %H:%M:%S.%f %Y')
3203+ timestamp = float(dt.strftime('%s.%f'))
3204+ expected = {
3205+ 'description': 'starting search for local datasources',
3206+ 'event_type': 'start',
3207+ 'name': 'init-local',
3208+ 'origin': 'cloudinit',
3209+ 'timestamp': timestamp}
3210+ self.assertEqual(expected, parse_ci_logline(line))
3211+
3212+ def test_parse_logline_returns_event_for_finish_events(self):
3213+ """parse_ci_logline returns a finish event for a parsed log line."""
3214+ line = ('2016-08-30 21:53:25.972325+00:00 y1 [CLOUDINIT]'
3215+ ' handlers.py[DEBUG]: finish: modules-final: SUCCESS: running'
3216+ ' modules for final')
3217+ expected = {
3218+ 'description': 'running modules for final',
3219+ 'event_type': 'finish',
3220+ 'name': 'modules-final',
3221+ 'origin': 'cloudinit',
3222+ 'result': 'SUCCESS',
3223+ 'timestamp': 1472594005.972}
3224+ self.assertEqual(expected, parse_ci_logline(line))
3225+
3226+
3227+SAMPLE_LOGS = dedent("""\
3228+Nov 03 06:51:06.074410 x2 cloud-init[106]: [CLOUDINIT] util.py[DEBUG]:\
3229+ Cloud-init v. 0.7.8 running 'init-local' at Thu, 03 Nov 2016\
3230+ 06:51:06 +0000. Up 1.0 seconds.
3231+2016-08-30 21:53:25.972325+00:00 y1 [CLOUDINIT] handlers.py[DEBUG]: finish:\
3232+ modules-final: SUCCESS: running modules for final
3233+""")
3234+
3235+
3236+class TestDumpEvents(CiTestCase):
3237+ maxDiff = None
3238+
3239+ def test_dump_events_with_rawdata(self):
3240+ """Rawdata is split and parsed into a tuple of events and data"""
3241+ events, data = dump_events(rawdata=SAMPLE_LOGS)
3242+ expected_data = SAMPLE_LOGS.splitlines()
3243+ year = datetime.now().year
3244+ dt1 = datetime.strptime(
3245+ 'Nov 03 06:51:06.074410 %d' % year, '%b %d %H:%M:%S.%f %Y')
3246+ timestamp1 = float(dt1.strftime('%s.%f'))
3247+ expected_events = [{
3248+ 'description': 'starting search for local datasources',
3249+ 'event_type': 'start',
3250+ 'name': 'init-local',
3251+ 'origin': 'cloudinit',
3252+ 'timestamp': timestamp1}, {
3253+ 'description': 'running modules for final',
3254+ 'event_type': 'finish',
3255+ 'name': 'modules-final',
3256+ 'origin': 'cloudinit',
3257+ 'result': 'SUCCESS',
3258+ 'timestamp': 1472594005.972}]
3259+ self.assertEqual(expected_events, events)
3260+ self.assertEqual(expected_data, data)
3261+
3262+ def test_dump_events_with_cisource(self):
3263+ """Cisource file is read and parsed into a tuple of events and data."""
3264+ tmpfile = self.tmp_path('logfile')
3265+ write_file(tmpfile, SAMPLE_LOGS)
3266+ events, data = dump_events(cisource=open(tmpfile))
3267+ year = datetime.now().year
3268+ dt1 = datetime.strptime(
3269+ 'Nov 03 06:51:06.074410 %d' % year, '%b %d %H:%M:%S.%f %Y')
3270+ timestamp1 = float(dt1.strftime('%s.%f'))
3271+ expected_events = [{
3272+ 'description': 'starting search for local datasources',
3273+ 'event_type': 'start',
3274+ 'name': 'init-local',
3275+ 'origin': 'cloudinit',
3276+ 'timestamp': timestamp1}, {
3277+ 'description': 'running modules for final',
3278+ 'event_type': 'finish',
3279+ 'name': 'modules-final',
3280+ 'origin': 'cloudinit',
3281+ 'result': 'SUCCESS',
3282+ 'timestamp': 1472594005.972}]
3283+ self.assertEqual(expected_events, events)
3284+ self.assertEqual(SAMPLE_LOGS.splitlines(), [d.strip() for d in data])
3285diff --git a/cloudinit/apport.py b/cloudinit/apport.py
3286new file mode 100644
3287index 0000000..221f341
3288--- /dev/null
3289+++ b/cloudinit/apport.py
3290@@ -0,0 +1,105 @@
3291+# Copyright (C) 2017 Canonical Ltd.
3292+#
3293+# This file is part of cloud-init. See LICENSE file for license information.
3294+
3295+'''Cloud-init apport interface'''
3296+
3297+try:
3298+ from apport.hookutils import (
3299+ attach_file, attach_root_command_outputs, root_command_output)
3300+ has_apport = True
3301+except ImportError:
3302+ has_apport = False
3303+
3304+
3305+KNOWN_CLOUD_NAMES = [
3306+ 'Amazon - Ec2', 'AliYun', 'AltCloud', 'Azure', 'Bigstep', 'CloudSigma',
3307+ 'CloudStack', 'DigitalOcean', 'GCE - Google Compute Engine', 'MAAS',
3308+ 'NoCloud', 'OpenNebula', 'OpenStack', 'OVF', 'Scaleway', 'SmartOS',
3309+ 'VMware', 'Other']
3310+
3311+# Potentially clear text collected logs
3312+CLOUDINIT_LOG = '/var/log/cloud-init.log'
3313+CLOUDINIT_OUTPUT_LOG = '/var/log/cloud-init-output.log'
3314+USER_DATA_FILE = '/var/lib/cloud/instance/user-data.txt' # Optional
3315+
3316+
3317+def attach_cloud_init_logs(report, ui=None):
3318+ '''Attach cloud-init logs and tarfile from 'cloud-init collect-logs'.'''
3319+ attach_root_command_outputs(report, {
3320+ 'cloud-init-log-warnings':
3321+ 'egrep -i "warn|error" /var/log/cloud-init.log',
3322+ 'cloud-init-output.log.txt': 'cat /var/log/cloud-init-output.log'})
3323+ root_command_output(
3324+ ['cloud-init', 'collect-logs', '-t', '/tmp/cloud-init-logs.tgz'])
3325+ attach_file(report, '/tmp/cloud-init-logs.tgz', 'logs.tgz')
3326+
3327+
3328+def attach_hwinfo(report, ui=None):
3329+ '''Optionally attach hardware info from lshw.'''
3330+ prompt = (
3331+ 'Your device details (lshw) may be useful to developers when'
3332+ ' addressing this bug, but gathering it requires admin privileges.'
3333+ ' Would you like to include this info?')
3334+ if ui and ui.yesno(prompt):
3335+ attach_root_command_outputs(report, {'lshw.txt': 'lshw'})
3336+
3337+
3338+def attach_cloud_info(report, ui=None):
3339+ '''Prompt for cloud details if available.'''
3340+ if ui:
3341+ prompt = 'Is this machine running in a cloud environment?'
3342+ response = ui.yesno(prompt)
3343+ if response is None:
3344+ raise StopIteration # User cancelled
3345+ if response:
3346+ prompt = ('Please select the cloud vendor or environment in which'
3347+ ' this instance is running')
3348+ response = ui.choice(prompt, KNOWN_CLOUD_NAMES)
3349+ if response:
3350+ report['CloudName'] = KNOWN_CLOUD_NAMES[response[0]]
3351+ else:
3352+ report['CloudName'] = 'None'
3353+
3354+
3355+def attach_user_data(report, ui=None):
3356+ '''Optionally provide user-data if desired.'''
3357+ if ui:
3358+ prompt = (
3359+ 'Your user-data or cloud-config file can optionally be provided'
3360+ ' from {0} and could be useful to developers when addressing this'
3361+ ' bug. Do you wish to attach user-data to this bug?'.format(
3362+ USER_DATA_FILE))
3363+ response = ui.yesno(prompt)
3364+ if response is None:
3365+ raise StopIteration # User cancelled
3366+ if response:
3367+ attach_file(report, USER_DATA_FILE, 'user_data.txt')
3368+
3369+
3370+def add_bug_tags(report):
3371+ '''Add any appropriate tags to the bug.'''
3372+ if 'JournalErrors' in report.keys():
3373+ errors = report['JournalErrors']
3374+ if 'Breaking ordering cycle' in errors:
3375+ report['Tags'] = 'systemd-ordering'
3376+
3377+
3378+def add_info(report, ui):
3379+ '''This is an entry point to run cloud-init's apport functionality.
3380+
3381+ Distros which want apport support will have a cloud-init package-hook at
3382+ /usr/share/apport/package-hooks/cloud-init.py which defines an add_info
3383+ function and returns the result of cloudinit.apport.add_info(report, ui).
3384+ '''
3385+ if not has_apport:
3386+ raise RuntimeError(
3387+ 'No apport imports discovered. Apport functionality disabled')
3388+ attach_cloud_init_logs(report, ui)
3389+ attach_hwinfo(report, ui)
3390+ attach_cloud_info(report, ui)
3391+ attach_user_data(report, ui)
3392+ add_bug_tags(report)
3393+ return True
3394+
3395+# vi: ts=4 expandtab
3396diff --git a/cloudinit/atomic_helper.py b/cloudinit/atomic_helper.py
3397index a3cfd94..587b994 100644
3398--- a/cloudinit/atomic_helper.py
3399+++ b/cloudinit/atomic_helper.py
3400@@ -1,15 +1,24 @@
3401-#!/usr/bin/python
3402-# vi: ts=4 expandtab
3403+# This file is part of cloud-init. See LICENSE file for license information.
3404
3405 import json
3406 import os
3407+import stat
3408 import tempfile
3409
3410 _DEF_PERMS = 0o644
3411
3412
3413-def write_file(filename, content, mode=_DEF_PERMS, omode="wb"):
3414+def write_file(filename, content, mode=_DEF_PERMS,
3415+ omode="wb", copy_mode=False):
3416 # open filename in mode 'omode', write content, set permissions to 'mode'
3417+
3418+ if copy_mode:
3419+ try:
3420+ file_stat = os.stat(filename)
3421+ mode = stat.S_IMODE(file_stat.st_mode)
3422+ except OSError:
3423+ pass
3424+
3425 tf = None
3426 try:
3427 tf = tempfile.NamedTemporaryFile(dir=os.path.dirname(filename),
3428@@ -29,3 +38,5 @@ def write_json(filename, data, mode=_DEF_PERMS):
3429 return write_file(
3430 filename, json.dumps(data, indent=1, sort_keys=True) + "\n",
3431 omode="w", mode=mode)
3432+
3433+# vi: ts=4 expandtab
3434diff --git a/cloudinit/cloud.py b/cloudinit/cloud.py
3435index 3e6be20..ba61678 100644
3436--- a/cloudinit/cloud.py
3437+++ b/cloudinit/cloud.py
3438@@ -1,24 +1,8 @@
3439-# vi: ts=4 expandtab
3440-#
3441-# Copyright (C) 2012 Canonical Ltd.
3442-# Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
3443-# Copyright (C) 2012 Yahoo! Inc.
3444-#
3445-# Author: Scott Moser <scott.moser@canonical.com>
3446-# Author: Juerg Haefliger <juerg.haefliger@hp.com>
3447-# Author: Joshua Harlow <harlowja@yahoo-inc.com>
3448-#
3449-# This program is free software: you can redistribute it and/or modify
3450-# it under the terms of the GNU General Public License version 3, as
3451-# published by the Free Software Foundation.
3452+# Copyright (C) 2012 Canonical Ltd.
3453+# Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
3454+# Copyright (C) 2012 Yahoo! Inc.
3455 #
3456-# This program is distributed in the hope that it will be useful,
3457-# but WITHOUT ANY WARRANTY; without even the implied warranty of
3458-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3459-# GNU General Public License for more details.
3460-#
3461-# You should have received a copy of the GNU General Public License
3462-# along with this program. If not, see <http://www.gnu.org/licenses/>.
3463+# This file is part of cloud-init. See LICENSE file for license information.
3464
3465 import copy
3466 import os
3467@@ -72,7 +56,8 @@ class Cloud(object):
3468 def get_template_filename(self, name):
3469 fn = self.paths.template_tpl % (name)
3470 if not os.path.isfile(fn):
3471- LOG.warn("No template found at %s for template named %s", fn, name)
3472+ LOG.warning("No template found in %s for template named %s",
3473+ os.path.dirname(fn), name)
3474 return None
3475 return fn
3476
3477@@ -107,3 +92,5 @@ class Cloud(object):
3478
3479 def get_ipath(self, name=None):
3480 return self.paths.get_ipath(name)
3481+
3482+# vi: ts=4 expandtab
3483diff --git a/cloudinit/cmd/__init__.py b/cloudinit/cmd/__init__.py
3484index da12464..e69de29 100644
3485--- a/cloudinit/cmd/__init__.py
3486+++ b/cloudinit/cmd/__init__.py
3487@@ -1,21 +0,0 @@
3488-# vi: ts=4 expandtab
3489-#
3490-# Copyright (C) 2012 Canonical Ltd.
3491-# Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
3492-# Copyright (C) 2012 Yahoo! Inc.
3493-#
3494-# Author: Scott Moser <scott.moser@canonical.com>
3495-# Author: Juerg Haefliger <juerg.haefliger@hp.com>
3496-# Author: Joshua Harlow <harlowja@yahoo-inc.com>
3497-#
3498-# This program is free software: you can redistribute it and/or modify
3499-# it under the terms of the GNU General Public License version 3, as
3500-# published by the Free Software Foundation.
3501-#
3502-# This program is distributed in the hope that it will be useful,
3503-# but WITHOUT ANY WARRANTY; without even the implied warranty of
3504-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3505-# GNU General Public License for more details.
3506-#
3507-# You should have received a copy of the GNU General Public License
3508-# along with this program. If not, see <http://www.gnu.org/licenses/>.
3509diff --git a/cloudinit/cmd/clean.py b/cloudinit/cmd/clean.py
3510new file mode 100644
3511index 0000000..de22f7f
3512--- /dev/null
3513+++ b/cloudinit/cmd/clean.py
3514@@ -0,0 +1,103 @@
3515+# Copyright (C) 2017 Canonical Ltd.
3516+#
3517+# This file is part of cloud-init. See LICENSE file for license information.
3518+
3519+"""Define 'clean' utility and handler as part of cloud-init commandline."""
3520+
3521+import argparse
3522+import os
3523+import sys
3524+
3525+from cloudinit.stages import Init
3526+from cloudinit.util import (
3527+ ProcessExecutionError, chdir, del_dir, del_file, get_config_logfiles,
3528+ is_link, subp)
3529+
3530+
3531+def error(msg):
3532+ sys.stderr.write("ERROR: " + msg + "\n")
3533+
3534+
3535+def get_parser(parser=None):
3536+ """Build or extend an arg parser for clean utility.
3537+
3538+ @param parser: Optional existing ArgumentParser instance representing the
3539+ clean subcommand which will be extended to support the args of
3540+ this utility.
3541+
3542+ @returns: ArgumentParser with proper argument configuration.
3543+ """
3544+ if not parser:
3545+ parser = argparse.ArgumentParser(
3546+ prog='clean',
3547+ description=('Remove logs and artifacts so cloud-init re-runs on '
3548+ 'a clean system'))
3549+ parser.add_argument(
3550+ '-l', '--logs', action='store_true', default=False, dest='remove_logs',
3551+ help='Remove cloud-init logs.')
3552+ parser.add_argument(
3553+ '-r', '--reboot', action='store_true', default=False,
3554+ help='Reboot system after logs are cleaned so cloud-init re-runs.')
3555+ parser.add_argument(
3556+ '-s', '--seed', action='store_true', default=False, dest='remove_seed',
3557+ help='Remove cloud-init seed directory /var/lib/cloud/seed.')
3558+ return parser
3559+
3560+
3561+def remove_artifacts(remove_logs, remove_seed=False):
3562+ """Helper which removes artifacts dir and optionally log files.
3563+
3564+ @param: remove_logs: Boolean. Set True to delete the cloud_dir path. False
3565+ preserves them.
3566+ @param: remove_seed: Boolean. Set True to also delete seed subdir in
3567+ paths.cloud_dir.
3568+ @returns: 0 on success, 1 otherwise.
3569+ """
3570+ init = Init(ds_deps=[])
3571+ init.read_cfg()
3572+ if remove_logs:
3573+ for log_file in get_config_logfiles(init.cfg):
3574+ del_file(log_file)
3575+
3576+ if not os.path.isdir(init.paths.cloud_dir):
3577+ return 0 # Artifacts dir already cleaned
3578+ with chdir(init.paths.cloud_dir):
3579+ for path in os.listdir('.'):
3580+ if path == 'seed' and not remove_seed:
3581+ continue
3582+ try:
3583+ if os.path.isdir(path) and not is_link(path):
3584+ del_dir(path)
3585+ else:
3586+ del_file(path)
3587+ except OSError as e:
3588+ error('Could not remove {0}: {1}'.format(path, str(e)))
3589+ return 1
3590+ return 0
3591+
3592+
3593+def handle_clean_args(name, args):
3594+ """Handle calls to 'cloud-init clean' as a subcommand."""
3595+ exit_code = remove_artifacts(args.remove_logs, args.remove_seed)
3596+ if exit_code == 0 and args.reboot:
3597+ cmd = ['shutdown', '-r', 'now']
3598+ try:
3599+ subp(cmd, capture=False)
3600+ except ProcessExecutionError as e:
3601+ error(
3602+ 'Could not reboot this system using "{0}": {1}'.format(
3603+ cmd, str(e)))
3604+ exit_code = 1
3605+ return exit_code
3606+
3607+
3608+def main():
3609+ """Tool to collect and tar all cloud-init related logs."""
3610+ parser = get_parser()
3611+ sys.exit(handle_clean_args('clean', parser.parse_args()))
3612+
3613+
3614+if __name__ == '__main__':
3615+ main()
3616+
3617+# vi: ts=4 expandtab
3618diff --git a/cloudinit/cmd/devel/__init__.py b/cloudinit/cmd/devel/__init__.py
3619new file mode 100644
3620index 0000000..e69de29
3621--- /dev/null
3622+++ b/cloudinit/cmd/devel/__init__.py
3623diff --git a/cloudinit/cmd/devel/logs.py b/cloudinit/cmd/devel/logs.py
3624new file mode 100644
3625index 0000000..35ca478
3626--- /dev/null
3627+++ b/cloudinit/cmd/devel/logs.py
3628@@ -0,0 +1,101 @@
3629+# Copyright (C) 2017 Canonical Ltd.
3630+#
3631+# This file is part of cloud-init. See LICENSE file for license information.
3632+
3633+"""Define 'collect-logs' utility and handler to include in cloud-init cmd."""
3634+
3635+import argparse
3636+from cloudinit.util import (
3637+ ProcessExecutionError, chdir, copy, ensure_dir, subp, write_file)
3638+from cloudinit.temp_utils import tempdir
3639+from datetime import datetime
3640+import os
3641+import shutil
3642+
3643+
3644+CLOUDINIT_LOGS = ['/var/log/cloud-init.log', '/var/log/cloud-init-output.log']
3645+CLOUDINIT_RUN_DIR = '/run/cloud-init'
3646+USER_DATA_FILE = '/var/lib/cloud/instance/user-data.txt' # Optional
3647+
3648+
3649+def get_parser(parser=None):
3650+ """Build or extend and arg parser for collect-logs utility.
3651+
3652+ @param parser: Optional existing ArgumentParser instance representing the
3653+ collect-logs subcommand which will be extended to support the args of
3654+ this utility.
3655+
3656+ @returns: ArgumentParser with proper argument configuration.
3657+ """
3658+ if not parser:
3659+ parser = argparse.ArgumentParser(
3660+ prog='collect-logs',
3661+ description='Collect and tar all cloud-init debug info')
3662+ parser.add_argument(
3663+ "--tarfile", '-t', default='cloud-init.tar.gz',
3664+ help=('The tarfile to create containing all collected logs.'
3665+ ' Default: cloud-init.tar.gz'))
3666+ parser.add_argument(
3667+ "--include-userdata", '-u', default=False, action='store_true',
3668+ dest='userdata', help=(
3669+ 'Optionally include user-data from {0} which could contain'
3670+ ' sensitive information.'.format(USER_DATA_FILE)))
3671+ return parser
3672+
3673+
3674+def _write_command_output_to_file(cmd, filename):
3675+ """Helper which runs a command and writes output or error to filename."""
3676+ try:
3677+ out, _ = subp(cmd)
3678+ except ProcessExecutionError as e:
3679+ write_file(filename, str(e))
3680+ else:
3681+ write_file(filename, out)
3682+
3683+
3684+def collect_logs(tarfile, include_userdata):
3685+ """Collect all cloud-init logs and tar them up into the provided tarfile.
3686+
3687+ @param tarfile: The path of the tar-gzipped file to create.
3688+ @param include_userdata: Boolean, true means include user-data.
3689+ """
3690+ tarfile = os.path.abspath(tarfile)
3691+ date = datetime.utcnow().date().strftime('%Y-%m-%d')
3692+ log_dir = 'cloud-init-logs-{0}'.format(date)
3693+ with tempdir(dir='/tmp') as tmp_dir:
3694+ log_dir = os.path.join(tmp_dir, log_dir)
3695+ _write_command_output_to_file(
3696+ ['dpkg-query', '--show', "-f=${Version}\n", 'cloud-init'],
3697+ os.path.join(log_dir, 'version'))
3698+ _write_command_output_to_file(
3699+ ['dmesg'], os.path.join(log_dir, 'dmesg.txt'))
3700+ _write_command_output_to_file(
3701+ ['journalctl', '-o', 'short-precise'],
3702+ os.path.join(log_dir, 'journal.txt'))
3703+ for log in CLOUDINIT_LOGS:
3704+ copy(log, log_dir)
3705+ if include_userdata:
3706+ copy(USER_DATA_FILE, log_dir)
3707+ run_dir = os.path.join(log_dir, 'run')
3708+ ensure_dir(run_dir)
3709+ shutil.copytree(CLOUDINIT_RUN_DIR, os.path.join(run_dir, 'cloud-init'))
3710+ with chdir(tmp_dir):
3711+ subp(['tar', 'czvf', tarfile, log_dir.replace(tmp_dir + '/', '')])
3712+
3713+
3714+def handle_collect_logs_args(name, args):
3715+ """Handle calls to 'cloud-init collect-logs' as a subcommand."""
3716+ collect_logs(args.tarfile, args.userdata)
3717+
3718+
3719+def main():
3720+ """Tool to collect and tar all cloud-init related logs."""
3721+ parser = get_parser()
3722+ handle_collect_logs_args('collect-logs', parser.parse_args())
3723+ return 0
3724+
3725+
3726+if __name__ == '__main__':
3727+ main()
3728+
3729+# vi: ts=4 expandtab
3730diff --git a/cloudinit/cmd/devel/parser.py b/cloudinit/cmd/devel/parser.py
3731new file mode 100644
3732index 0000000..acacc4e
3733--- /dev/null
3734+++ b/cloudinit/cmd/devel/parser.py
3735@@ -0,0 +1,26 @@
3736+# Copyright (C) 2017 Canonical Ltd.
3737+#
3738+# This file is part of cloud-init. See LICENSE file for license information.
3739+
3740+"""Define 'devel' subcommand argument parsers to include in cloud-init cmd."""
3741+
3742+import argparse
3743+from cloudinit.config.schema import (
3744+ get_parser as schema_parser, handle_schema_args)
3745+
3746+
3747+def get_parser(parser=None):
3748+ if not parser:
3749+ parser = argparse.ArgumentParser(
3750+ prog='cloudinit-devel',
3751+ description='Run development cloud-init tools')
3752+ subparsers = parser.add_subparsers(title='Subcommands', dest='subcommand')
3753+ subparsers.required = True
3754+
3755+ parser_schema = subparsers.add_parser(
3756+ 'schema', help='Validate cloud-config files or document schema')
3757+ # Construct schema subcommand parser
3758+ schema_parser(parser_schema)
3759+ parser_schema.set_defaults(action=('schema', handle_schema_args))
3760+
3761+ return parser
3762diff --git a/cloudinit/cmd/devel/tests/__init__.py b/cloudinit/cmd/devel/tests/__init__.py
3763new file mode 100644
3764index 0000000..e69de29
3765--- /dev/null
3766+++ b/cloudinit/cmd/devel/tests/__init__.py
3767diff --git a/cloudinit/cmd/devel/tests/test_logs.py b/cloudinit/cmd/devel/tests/test_logs.py
3768new file mode 100644
3769index 0000000..dc4947c
3770--- /dev/null
3771+++ b/cloudinit/cmd/devel/tests/test_logs.py
3772@@ -0,0 +1,120 @@
3773+# This file is part of cloud-init. See LICENSE file for license information.
3774+
3775+from cloudinit.cmd.devel import logs
3776+from cloudinit.util import ensure_dir, load_file, subp, write_file
3777+from cloudinit.tests.helpers import FilesystemMockingTestCase, wrap_and_call
3778+from datetime import datetime
3779+import os
3780+
3781+
3782+class TestCollectLogs(FilesystemMockingTestCase):
3783+
3784+ def setUp(self):
3785+ super(TestCollectLogs, self).setUp()
3786+ self.new_root = self.tmp_dir()
3787+ self.run_dir = self.tmp_path('run', self.new_root)
3788+
3789+ def test_collect_logs_creates_tarfile(self):
3790+ """collect-logs creates a tarfile with all related cloud-init info."""
3791+ log1 = self.tmp_path('cloud-init.log', self.new_root)
3792+ write_file(log1, 'cloud-init-log')
3793+ log2 = self.tmp_path('cloud-init-output.log', self.new_root)
3794+ write_file(log2, 'cloud-init-output-log')
3795+ ensure_dir(self.run_dir)
3796+ write_file(self.tmp_path('results.json', self.run_dir), 'results')
3797+ output_tarfile = self.tmp_path('logs.tgz')
3798+
3799+ date = datetime.utcnow().date().strftime('%Y-%m-%d')
3800+ date_logdir = 'cloud-init-logs-{0}'.format(date)
3801+
3802+ expected_subp = {
3803+ ('dpkg-query', '--show', "-f=${Version}\n", 'cloud-init'):
3804+ '0.7fake\n',
3805+ ('dmesg',): 'dmesg-out\n',
3806+ ('journalctl', '-o', 'short-precise'): 'journal-out\n',
3807+ ('tar', 'czvf', output_tarfile, date_logdir): ''
3808+ }
3809+
3810+ def fake_subp(cmd):
3811+ cmd_tuple = tuple(cmd)
3812+ if cmd_tuple not in expected_subp:
3813+ raise AssertionError(
3814+ 'Unexpected command provided to subp: {0}'.format(cmd))
3815+ if cmd == ['tar', 'czvf', output_tarfile, date_logdir]:
3816+ subp(cmd) # Pass through tar cmd so we can check output
3817+ return expected_subp[cmd_tuple], ''
3818+
3819+ wrap_and_call(
3820+ 'cloudinit.cmd.devel.logs',
3821+ {'subp': {'side_effect': fake_subp},
3822+ 'CLOUDINIT_LOGS': {'new': [log1, log2]},
3823+ 'CLOUDINIT_RUN_DIR': {'new': self.run_dir}},
3824+ logs.collect_logs, output_tarfile, include_userdata=False)
3825+ # unpack the tarfile and check file contents
3826+ subp(['tar', 'zxvf', output_tarfile, '-C', self.new_root])
3827+ out_logdir = self.tmp_path(date_logdir, self.new_root)
3828+ self.assertEqual(
3829+ '0.7fake\n',
3830+ load_file(os.path.join(out_logdir, 'version')))
3831+ self.assertEqual(
3832+ 'cloud-init-log',
3833+ load_file(os.path.join(out_logdir, 'cloud-init.log')))
3834+ self.assertEqual(
3835+ 'cloud-init-output-log',
3836+ load_file(os.path.join(out_logdir, 'cloud-init-output.log')))
3837+ self.assertEqual(
3838+ 'dmesg-out\n',
3839+ load_file(os.path.join(out_logdir, 'dmesg.txt')))
3840+ self.assertEqual(
3841+ 'journal-out\n',
3842+ load_file(os.path.join(out_logdir, 'journal.txt')))
3843+ self.assertEqual(
3844+ 'results',
3845+ load_file(
3846+ os.path.join(out_logdir, 'run', 'cloud-init', 'results.json')))
3847+
3848+ def test_collect_logs_includes_optional_userdata(self):
3849+ """collect-logs include userdata when --include-userdata is set."""
3850+ log1 = self.tmp_path('cloud-init.log', self.new_root)
3851+ write_file(log1, 'cloud-init-log')
3852+ log2 = self.tmp_path('cloud-init-output.log', self.new_root)
3853+ write_file(log2, 'cloud-init-output-log')
3854+ userdata = self.tmp_path('user-data.txt', self.new_root)
3855+ write_file(userdata, 'user-data')
3856+ ensure_dir(self.run_dir)
3857+ write_file(self.tmp_path('results.json', self.run_dir), 'results')
3858+ output_tarfile = self.tmp_path('logs.tgz')
3859+
3860+ date = datetime.utcnow().date().strftime('%Y-%m-%d')
3861+ date_logdir = 'cloud-init-logs-{0}'.format(date)
3862+
3863+ expected_subp = {
3864+ ('dpkg-query', '--show', "-f=${Version}\n", 'cloud-init'):
3865+ '0.7fake',
3866+ ('dmesg',): 'dmesg-out\n',
3867+ ('journalctl', '-o', 'short-precise'): 'journal-out\n',
3868+ ('tar', 'czvf', output_tarfile, date_logdir): ''
3869+ }
3870+
3871+ def fake_subp(cmd):
3872+ cmd_tuple = tuple(cmd)
3873+ if cmd_tuple not in expected_subp:
3874+ raise AssertionError(
3875+ 'Unexpected command provided to subp: {0}'.format(cmd))
3876+ if cmd == ['tar', 'czvf', output_tarfile, date_logdir]:
3877+ subp(cmd) # Pass through tar cmd so we can check output
3878+ return expected_subp[cmd_tuple], ''
3879+
3880+ wrap_and_call(
3881+ 'cloudinit.cmd.devel.logs',
3882+ {'subp': {'side_effect': fake_subp},
3883+ 'CLOUDINIT_LOGS': {'new': [log1, log2]},
3884+ 'CLOUDINIT_RUN_DIR': {'new': self.run_dir},
3885+ 'USER_DATA_FILE': {'new': userdata}},
3886+ logs.collect_logs, output_tarfile, include_userdata=True)
3887+ # unpack the tarfile and check file contents
3888+ subp(['tar', 'zxvf', output_tarfile, '-C', self.new_root])
3889+ out_logdir = self.tmp_path(date_logdir, self.new_root)
3890+ self.assertEqual(
3891+ 'user-data',
3892+ load_file(os.path.join(out_logdir, 'user-data.txt')))
3893diff --git a/cloudinit/cmd/main.py b/cloudinit/cmd/main.py
3894index 26c0240..d2f1b77 100644
3895--- a/cloudinit/cmd/main.py
3896+++ b/cloudinit/cmd/main.py
3897@@ -1,25 +1,16 @@
3898 #!/usr/bin/python
3899-# vi: ts=4 expandtab
3900-#
3901-# Copyright (C) 2012 Canonical Ltd.
3902-# Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
3903-# Copyright (C) 2012 Yahoo! Inc.
3904 #
3905-# Author: Scott Moser <scott.moser@canonical.com>
3906-# Author: Juerg Haefliger <juerg.haefliger@hp.com>
3907-# Author: Joshua Harlow <harlowja@yahoo-inc.com>
3908+# Copyright (C) 2012 Canonical Ltd.
3909+# Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
3910+# Copyright (C) 2012 Yahoo! Inc.
3911+# Copyright (C) 2017 Amazon.com, Inc. or its affiliates
3912 #
3913-# This program is free software: you can redistribute it and/or modify
3914-# it under the terms of the GNU General Public License version 3, as
3915-# published by the Free Software Foundation.
3916+# Author: Scott Moser <scott.moser@canonical.com>
3917+# Author: Juerg Haefliger <juerg.haefliger@hp.com>
3918+# Author: Joshua Harlow <harlowja@yahoo-inc.com>
3919+# Author: Andrew Jorgensen <ajorgens@amazon.com>
3920 #
3921-# This program is distributed in the hope that it will be useful,
3922-# but WITHOUT ANY WARRANTY; without even the implied warranty of
3923-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3924-# GNU General Public License for more details.
3925-#
3926-# You should have received a copy of the GNU General Public License
3927-# along with this program. If not, see <http://www.gnu.org/licenses/>.
3928+# This file is part of cloud-init. See LICENSE file for license information.
3929
3930 import argparse
3931 import json
3932@@ -36,9 +27,10 @@ from cloudinit import netinfo
3933 from cloudinit import signal_handler
3934 from cloudinit import sources
3935 from cloudinit import stages
3936-from cloudinit import templater
3937+from cloudinit import url_helper
3938 from cloudinit import util
3939 from cloudinit import version
3940+from cloudinit import warnings
3941
3942 from cloudinit import reporting
3943 from cloudinit.reporting import events
3944@@ -51,20 +43,13 @@ from cloudinit import atomic_helper
3945 from cloudinit.dhclient_hook import LogDhclient
3946
3947
3948-# Pretty little cheetah formatted welcome message template
3949-WELCOME_MSG_TPL = ("Cloud-init v. ${version} running '${action}' at "
3950- "${timestamp}. Up ${uptime} seconds.")
3951+# Welcome message template
3952+WELCOME_MSG_TPL = ("Cloud-init v. {version} running '{action}' at "
3953+ "{timestamp}. Up {uptime} seconds.")
3954
3955 # Module section template
3956 MOD_SECTION_TPL = "cloud_%s_modules"
3957
3958-# Things u can query on
3959-QUERY_DATA_TYPES = [
3960- 'data',
3961- 'data_raw',
3962- 'instance_id',
3963-]
3964-
3965 # Frequency shortname to full name
3966 # (so users don't have to remember the full name...)
3967 FREQ_SHORT_NAMES = {
3968@@ -97,13 +82,11 @@ def welcome(action, msg=None):
3969
3970
3971 def welcome_format(action):
3972- tpl_params = {
3973- 'version': version.version_string(),
3974- 'uptime': util.uptime(),
3975- 'timestamp': util.time_rfc2822(),
3976- 'action': action,
3977- }
3978- return templater.render_string(WELCOME_MSG_TPL, tpl_params)
3979+ return WELCOME_MSG_TPL.format(
3980+ version=version.version_string(),
3981+ uptime=util.uptime(),
3982+ timestamp=util.time_rfc2822(),
3983+ action=action)
3984
3985
3986 def extract_fns(args):
3987@@ -140,23 +123,104 @@ def apply_reporting_cfg(cfg):
3988 reporting.update_configuration(cfg.get('reporting'))
3989
3990
3991+def parse_cmdline_url(cmdline, names=('cloud-config-url', 'url')):
3992+ data = util.keyval_str_to_dict(cmdline)
3993+ for key in names:
3994+ if key in data:
3995+ return key, data[key]
3996+ raise KeyError("No keys (%s) found in string '%s'" %
3997+ (cmdline, names))
3998+
3999+
4000+def attempt_cmdline_url(path, network=True, cmdline=None):
4001+ """Write data from url referenced in command line to path.
4002+
4003+ path: a file to write content to if downloaded.
4004+ network: should network access be assumed.
4005+ cmdline: the cmdline to parse for cloud-config-url.
4006+
4007+ This is used in MAAS datasource, in "ephemeral" (read-only root)
4008+ environment where the instance netboots to iscsi ro root.
4009+ and the entity that controls the pxe config has to configure
4010+ the maas datasource.
4011+
4012+ An attempt is made on network urls even in local datasource
4013+ for case of network set up in initramfs.
4014+
4015+ Return value is a tuple of a logger function (logging.DEBUG)
4016+ and a message indicating what happened.
4017+ """
4018+
4019+ if cmdline is None:
4020+ cmdline = util.get_cmdline()
4021+
4022+ try:
4023+ cmdline_name, url = parse_cmdline_url(cmdline)
4024+ except KeyError:
4025+ return (logging.DEBUG, "No kernel command line url found.")
4026+
4027+ path_is_local = url.startswith("file://") or url.startswith("/")
4028+
4029+ if path_is_local and os.path.exists(path):
4030+ if network:
4031+ m = ("file '%s' existed, possibly from local stage download"
4032+ " of command line url '%s'. Not re-writing." % (path, url))
4033+ level = logging.INFO
4034+ if path_is_local:
4035+ level = logging.DEBUG
4036+ else:
4037+ m = ("file '%s' existed, possibly from previous boot download"
4038+ " of command line url '%s'. Not re-writing." % (path, url))
4039+ level = logging.WARN
4040+
4041+ return (level, m)
4042+
4043+ kwargs = {'url': url, 'timeout': 10, 'retries': 2}
4044+ if network or path_is_local:
4045+ level = logging.WARN
4046+ kwargs['sec_between'] = 1
4047+ else:
4048+ level = logging.DEBUG
4049+ kwargs['sec_between'] = .1
4050+
4051+ data = None
4052+ header = b'#cloud-config'
4053+ try:
4054+ resp = util.read_file_or_url(**kwargs)
4055+ if resp.ok():
4056+ data = resp.contents
4057+ if not resp.contents.startswith(header):
4058+ if cmdline_name == 'cloud-config-url':
4059+ level = logging.WARN
4060+ else:
4061+ level = logging.INFO
4062+ return (
4063+ level,
4064+ "contents of '%s' did not start with %s" % (url, header))
4065+ else:
4066+ return (level,
4067+ "url '%s' returned code %s. Ignoring." % (url, resp.code))
4068+
4069+ except url_helper.UrlError as e:
4070+ return (level, "retrieving url '%s' failed: %s" % (url, e))
4071+
4072+ util.write_file(path, data, mode=0o600)
4073+ return (logging.INFO,
4074+ "wrote cloud-config data from %s='%s' to %s" %
4075+ (cmdline_name, url, path))
4076+
4077+
4078 def main_init(name, args):
4079 deps = [sources.DEP_FILESYSTEM, sources.DEP_NETWORK]
4080 if args.local:
4081 deps = [sources.DEP_FILESYSTEM]
4082
4083- if not args.local:
4084- # See doc/kernel-cmdline.txt
4085- #
4086- # This is used in maas datasource, in "ephemeral" (read-only root)
4087- # environment where the instance netboots to iscsi ro root.
4088- # and the entity that controls the pxe config has to configure
4089- # the maas datasource.
4090- #
4091- # Could be used elsewhere, only works on network based (not local).
4092- root_name = "%s.d" % (CLOUD_CONFIG)
4093- target_fn = os.path.join(root_name, "91_kernel_cmdline_url.cfg")
4094- util.read_write_cmdline_url(target_fn)
4095+ early_logs = []
4096+ early_logs.append(
4097+ attempt_cmdline_url(
4098+ path=os.path.join("%s.d" % CLOUD_CONFIG,
4099+ "91_kernel_cmdline_url.cfg"),
4100+ network=not args.local))
4101
4102 # Cloud-init 'init' stage is broken up into the following sub-stages
4103 # 1. Ensure that the init object fetches its config without errors
4104@@ -182,12 +246,14 @@ def main_init(name, args):
4105 outfmt = None
4106 errfmt = None
4107 try:
4108- LOG.debug("Closing stdin")
4109+ early_logs.append((logging.DEBUG, "Closing stdin."))
4110 util.close_stdin()
4111 (outfmt, errfmt) = util.fixup_output(init.cfg, name)
4112 except Exception:
4113- util.logexc(LOG, "Failed to setup output redirection!")
4114- print_exc("Failed to setup output redirection!")
4115+ msg = "Failed to setup output redirection!"
4116+ util.logexc(LOG, msg)
4117+ print_exc(msg)
4118+ early_logs.append((logging.WARN, msg))
4119 if args.debug:
4120 # Reset so that all the debug handlers are closed out
4121 LOG.debug(("Logging being reset, this logger may no"
4122@@ -201,6 +267,10 @@ def main_init(name, args):
4123 # been redirected and log now configured.
4124 welcome(name, msg=w_msg)
4125
4126+ # re-play early log messages before logging was setup
4127+ for lvl, msg in early_logs:
4128+ LOG.log(lvl, msg)
4129+
4130 # Stage 3
4131 try:
4132 init.initialize()
4133@@ -235,8 +305,15 @@ def main_init(name, args):
4134 " would allow us to stop early.")
4135 else:
4136 existing = "check"
4137- if util.get_cfg_option_bool(init.cfg, 'manual_cache_clean', False):
4138+ mcfg = util.get_cfg_option_bool(init.cfg, 'manual_cache_clean', False)
4139+ if mcfg:
4140+ LOG.debug("manual cache clean set from config")
4141 existing = "trust"
4142+ else:
4143+ mfile = path_helper.get_ipath_cur("manual_clean_marker")
4144+ if os.path.exists(mfile):
4145+ LOG.debug("manual cache clean found from marker: %s", mfile)
4146+ existing = "trust"
4147
4148 init.purge_cache()
4149 # Delete the non-net file as well
4150@@ -288,6 +365,9 @@ def main_init(name, args):
4151 LOG.debug("[%s] %s is in local mode, will apply init modules now.",
4152 mode, init.datasource)
4153
4154+ # Give the datasource a chance to use network resources.
4155+ # This is used on Azure to communicate with the fabric over network.
4156+ init.setup_datasource()
4157 # update fully realizes user-data (pulling in #include if necessary)
4158 init.update()
4159 # Stage 7
4160@@ -320,7 +400,8 @@ def main_init(name, args):
4161 errfmt_orig = errfmt
4162 (outfmt, errfmt) = util.get_output_cfg(mods.cfg, name)
4163 if outfmt_orig != outfmt or errfmt_orig != errfmt:
4164- LOG.warn("Stdout, stderr changing to (%s, %s)", outfmt, errfmt)
4165+ LOG.warning("Stdout, stderr changing to (%s, %s)",
4166+ outfmt, errfmt)
4167 (outfmt, errfmt) = util.fixup_output(mods.cfg, name)
4168 except Exception:
4169 util.logexc(LOG, "Failed to re-adjust output redirection!")
4170@@ -329,10 +410,54 @@ def main_init(name, args):
4171 # give the activated datasource a chance to adjust
4172 init.activate_datasource()
4173
4174+ di_report_warn(datasource=init.datasource, cfg=init.cfg)
4175+
4176 # Stage 10
4177 return (init.datasource, run_module_section(mods, name, name))
4178
4179
4180+def di_report_warn(datasource, cfg):
4181+ if 'di_report' not in cfg:
4182+ LOG.debug("no di_report found in config.")
4183+ return
4184+
4185+ dicfg = cfg['di_report']
4186+ if dicfg is None:
4187+ # ds-identify may write 'di_report:\n #comment\n'
4188+ # which reads as {'di_report': None}
4189+ LOG.debug("di_report was None.")
4190+ return
4191+
4192+ if not isinstance(dicfg, dict):
4193+ LOG.warning("di_report config not a dictionary: %s", dicfg)
4194+ return
4195+
4196+ dslist = dicfg.get('datasource_list')
4197+ if dslist is None:
4198+ LOG.warning("no 'datasource_list' found in di_report.")
4199+ return
4200+ elif not isinstance(dslist, list):
4201+ LOG.warning("di_report/datasource_list not a list: %s", dslist)
4202+ return
4203+
4204+ # ds.__module__ is like cloudinit.sources.DataSourceName
4205+ # where Name is the thing that shows up in datasource_list.
4206+ modname = datasource.__module__.rpartition(".")[2]
4207+ if modname.startswith(sources.DS_PREFIX):
4208+ modname = modname[len(sources.DS_PREFIX):]
4209+ else:
4210+ LOG.warning("Datasource '%s' came from unexpected module '%s'.",
4211+ datasource, modname)
4212+
4213+ if modname in dslist:
4214+ LOG.debug("used datasource '%s' from '%s' was in di_report's list: %s",
4215+ datasource, modname, dslist)
4216+ return
4217+
4218+ warnings.show_warning('dsid_missing_source', cfg,
4219+ source=modname, dslist=str(dslist))
4220+
4221+
4222 def main_modules(action_name, args):
4223 name = args.mode
4224 # Cloud-init 'modules' stages are broken up into the following sub-stages
4225@@ -384,11 +509,6 @@ def main_modules(action_name, args):
4226 return run_module_section(mods, name, name)
4227
4228
4229-def main_query(name, _args):
4230- raise NotImplementedError(("Action '%s' is not"
4231- " currently implemented") % (name))
4232-
4233-
4234 def main_single(name, args):
4235 # Cloud-init single stage is broken up into the following sub-stages
4236 # 1. Ensure that the init object fetches its config without errors
4237@@ -448,10 +568,10 @@ def main_single(name, args):
4238 mod_args,
4239 mod_freq)
4240 if failures:
4241- LOG.warn("Ran %s but it failed!", mod_name)
4242+ LOG.warning("Ran %s but it failed!", mod_name)
4243 return 1
4244 elif not which_ran:
4245- LOG.warn("Did not run %s, does it exist?", mod_name)
4246+ LOG.warning("Did not run %s, does it exist?", mod_name)
4247 return 1
4248 else:
4249 # Guess it worked
4250@@ -489,7 +609,11 @@ def status_wrapper(name, args, data_d=None, link_d=None):
4251 else:
4252 raise ValueError("unknown name: %s" % name)
4253
4254- modes = ('init', 'init-local', 'modules-config', 'modules-final')
4255+ modes = ('init', 'init-local', 'modules-init', 'modules-config',
4256+ 'modules-final')
4257+ if mode not in modes:
4258+ raise ValueError(
4259+ "Invalid cloud init mode specified '{0}'".format(mode))
4260
4261 status = None
4262 if mode == 'init-local':
4263@@ -501,16 +625,18 @@ def status_wrapper(name, args, data_d=None, link_d=None):
4264 except Exception:
4265 pass
4266
4267+ nullstatus = {
4268+ 'errors': [],
4269+ 'start': None,
4270+ 'finished': None,
4271+ }
4272 if status is None:
4273- nullstatus = {
4274- 'errors': [],
4275- 'start': None,
4276- 'finished': None,
4277- }
4278 status = {'v1': {}}
4279 for m in modes:
4280 status['v1'][m] = nullstatus.copy()
4281 status['v1']['datasource'] = None
4282+ elif mode not in status['v1']:
4283+ status['v1'][mode] = nullstatus.copy()
4284
4285 v1 = status['v1']
4286 v1['stage'] = mode
4287@@ -557,12 +683,15 @@ def status_wrapper(name, args, data_d=None, link_d=None):
4288 return len(v1[mode]['errors'])
4289
4290
4291+def main_features(name, args):
4292+ sys.stdout.write('\n'.join(sorted(version.FEATURES)) + '\n')
4293+
4294+
4295 def main(sysv_args=None):
4296- if sysv_args is not None:
4297- parser = argparse.ArgumentParser(prog=sysv_args[0])
4298- sysv_args = sysv_args[1:]
4299- else:
4300- parser = argparse.ArgumentParser()
4301+ if not sysv_args:
4302+ sysv_args = sys.argv
4303+ parser = argparse.ArgumentParser(prog=sysv_args[0])
4304+ sysv_args = sysv_args[1:]
4305
4306 # Top level args
4307 parser.add_argument('--version', '-v', action='version',
4308@@ -583,7 +712,8 @@ def main(sysv_args=None):
4309 default=False)
4310
4311 parser.set_defaults(reporter=None)
4312- subparsers = parser.add_subparsers()
4313+ subparsers = parser.add_subparsers(title='Subcommands', dest='subcommand')
4314+ subparsers.required = True
4315
4316 # Each action and its sub-options (if any)
4317 parser_init = subparsers.add_parser('init',
4318@@ -607,17 +737,6 @@ def main(sysv_args=None):
4319 choices=('init', 'config', 'final'))
4320 parser_mod.set_defaults(action=('modules', main_modules))
4321
4322- # These settings are used when you want to query information
4323- # stored in the cloud-init data objects/directories/files
4324- parser_query = subparsers.add_parser('query',
4325- help=('query information stored '
4326- 'in cloud-init'))
4327- parser_query.add_argument("--name", '-n', action="store",
4328- help="item name to query on",
4329- required=True,
4330- choices=QUERY_DATA_TYPES)
4331- parser_query.set_defaults(action=('query', main_query))
4332-
4333 # This subcommand allows you to run a single module
4334 parser_single = subparsers.add_parser('single',
4335 help=('run a single module '))
4336@@ -647,15 +766,61 @@ def main(sysv_args=None):
4337 ' upon'))
4338 parser_dhclient.set_defaults(action=('dhclient_hook', dhclient_hook))
4339
4340+ parser_features = subparsers.add_parser('features',
4341+ help=('list defined features'))
4342+ parser_features.set_defaults(action=('features', main_features))
4343+
4344+ parser_analyze = subparsers.add_parser(
4345+ 'analyze', help='Devel tool: Analyze cloud-init logs and data')
4346+
4347+ parser_devel = subparsers.add_parser(
4348+ 'devel', help='Run development tools')
4349+
4350+ parser_collect_logs = subparsers.add_parser(
4351+ 'collect-logs', help='Collect and tar all cloud-init debug info')
4352+
4353+ parser_clean = subparsers.add_parser(
4354+ 'clean', help='Remove logs and artifacts so cloud-init can re-run.')
4355+
4356+ parser_status = subparsers.add_parser(
4357+ 'status', help='Report cloud-init status or wait on completion.')
4358+
4359+ if sysv_args:
4360+ # Only load subparsers if subcommand is specified to avoid load cost
4361+ if sysv_args[0] == 'analyze':
4362+ from cloudinit.analyze.__main__ import get_parser as analyze_parser
4363+ # Construct analyze subcommand parser
4364+ analyze_parser(parser_analyze)
4365+ elif sysv_args[0] == 'devel':
4366+ from cloudinit.cmd.devel.parser import get_parser as devel_parser
4367+ # Construct devel subcommand parser
4368+ devel_parser(parser_devel)
4369+ elif sysv_args[0] == 'collect-logs':
4370+ from cloudinit.cmd.devel.logs import (
4371+ get_parser as logs_parser, handle_collect_logs_args)
4372+ logs_parser(parser_collect_logs)
4373+ parser_collect_logs.set_defaults(
4374+ action=('collect-logs', handle_collect_logs_args))
4375+ elif sysv_args[0] == 'clean':
4376+ from cloudinit.cmd.clean import (
4377+ get_parser as clean_parser, handle_clean_args)
4378+ clean_parser(parser_clean)
4379+ parser_clean.set_defaults(
4380+ action=('clean', handle_clean_args))
4381+ elif sysv_args[0] == 'status':
4382+ from cloudinit.cmd.status import (
4383+ get_parser as status_parser, handle_status_args)
4384+ status_parser(parser_status)
4385+ parser_status.set_defaults(
4386+ action=('status', handle_status_args))
4387+
4388 args = parser.parse_args(args=sysv_args)
4389
4390- try:
4391- (name, functor) = args.action
4392- except AttributeError:
4393- parser.error('too few arguments')
4394+ # Subparsers.required = True and each subparser sets action=(name, functor)
4395+ (name, functor) = args.action
4396
4397 # Setup basic logging to start (until reinitialized)
4398- # iff in debug mode...
4399+ # iff in debug mode.
4400 if args.debug:
4401 logging.setupBasicLogging()
4402
4403@@ -665,6 +830,7 @@ def main(sysv_args=None):
4404 if name in ("modules", "init"):
4405 functor = status_wrapper
4406
4407+ rname = None
4408 report_on = True
4409 if name == "init":
4410 if args.local:
4411@@ -679,10 +845,10 @@ def main(sysv_args=None):
4412 rname, rdesc = ("single/%s" % args.name,
4413 "running single module %s" % args.name)
4414 report_on = args.report
4415-
4416- elif name == 'dhclient_hook':
4417- rname, rdesc = ("dhclient-hook",
4418- "running dhclient-hook module")
4419+ else:
4420+ rname = name
4421+ rdesc = "running 'cloud-init %s'" % name
4422+ report_on = False
4423
4424 args.reporter = events.ReportEventStack(
4425 rname, rdesc, reporting_enabled=report_on)
4426@@ -697,3 +863,5 @@ if __name__ == '__main__':
4427 if 'TZ' not in os.environ:
4428 os.environ['TZ'] = ":/etc/localtime"
4429 main(sys.argv)
4430+
4431+# vi: ts=4 expandtab
4432diff --git a/cloudinit/cmd/status.py b/cloudinit/cmd/status.py
4433new file mode 100644
4434index 0000000..ea79a85
4435--- /dev/null
4436+++ b/cloudinit/cmd/status.py
4437@@ -0,0 +1,163 @@
4438+# Copyright (C) 2017 Canonical Ltd.
4439+#
4440+# This file is part of cloud-init. See LICENSE file for license information.
4441+
4442+"""Define 'status' utility and handler as part of cloud-init commandline."""
4443+
4444+import argparse
4445+import os
4446+import sys
4447+from time import gmtime, strftime, sleep
4448+
4449+from cloudinit.distros import uses_systemd
4450+from cloudinit.stages import Init
4451+from cloudinit.util import get_cmdline, load_file, load_json
4452+
4453+CLOUDINIT_DISABLED_FILE = '/etc/cloud/cloud-init.disabled'
4454+
4455+# customer visible status messages
4456+STATUS_ENABLED_NOT_RUN = 'not run'
4457+STATUS_RUNNING = 'running'
4458+STATUS_DONE = 'done'
4459+STATUS_ERROR = 'error'
4460+STATUS_DISABLED = 'disabled'
4461+
4462+
4463+def get_parser(parser=None):
4464+ """Build or extend an arg parser for status utility.
4465+
4466+ @param parser: Optional existing ArgumentParser instance representing the
4467+ status subcommand which will be extended to support the args of
4468+ this utility.
4469+
4470+ @returns: ArgumentParser with proper argument configuration.
4471+ """
4472+ if not parser:
4473+ parser = argparse.ArgumentParser(
4474+ prog='status',
4475+ description='Report run status of cloud init')
4476+ parser.add_argument(
4477+ '-l', '--long', action='store_true', default=False,
4478+ help=('Report long format of statuses including run stage name and'
4479+ ' error messages'))
4480+ parser.add_argument(
4481+ '-w', '--wait', action='store_true', default=False,
4482+ help='Block waiting on cloud-init to complete')
4483+ return parser
4484+
4485+
4486+def handle_status_args(name, args):
4487+ """Handle calls to 'cloud-init status' as a subcommand."""
4488+ # Read configured paths
4489+ init = Init(ds_deps=[])
4490+ init.read_cfg()
4491+
4492+ status, status_detail, time = _get_status_details(init.paths)
4493+ if args.wait:
4494+ while status in (STATUS_ENABLED_NOT_RUN, STATUS_RUNNING):
4495+ sys.stdout.write('.')
4496+ sys.stdout.flush()
4497+ status, status_detail, time = _get_status_details(init.paths)
4498+ sleep(0.25)
4499+ sys.stdout.write('\n')
4500+ if args.long:
4501+ print('status: {0}'.format(status))
4502+ if time:
4503+ print('time: {0}'.format(time))
4504+ print('detail:\n{0}'.format(status_detail))
4505+ else:
4506+ print('status: {0}'.format(status))
4507+ return 1 if status == STATUS_ERROR else 0
4508+
4509+
4510+def _is_cloudinit_disabled(disable_file, paths):
4511+ """Report whether cloud-init is disabled.
4512+
4513+ @param disable_file: The path to the cloud-init disable file.
4514+ @param paths: An initialized cloudinit.helpers.Paths object.
4515+ @returns: A tuple containing (bool, reason) about cloud-init's status and
4516+ why.
4517+ """
4518+ is_disabled = False
4519+ cmdline_parts = get_cmdline().split()
4520+ if not uses_systemd():
4521+ reason = 'Cloud-init enabled on sysvinit'
4522+ elif 'cloud-init=enabled' in cmdline_parts:
4523+ reason = 'Cloud-init enabled by kernel command line cloud-init=enabled'
4524+ elif os.path.exists(disable_file):
4525+ is_disabled = True
4526+ reason = 'Cloud-init disabled by {0}'.format(disable_file)
4527+ elif 'cloud-init=disabled' in cmdline_parts:
4528+ is_disabled = True
4529+ reason = 'Cloud-init disabled by kernel parameter cloud-init=disabled'
4530+ elif not os.path.exists(os.path.join(paths.run_dir, 'enabled')):
4531+ is_disabled = True
4532+ reason = 'Cloud-init disabled by cloud-init-generator'
4533+ else:
4534+ reason = 'Cloud-init enabled by systemd cloud-init-generator'
4535+ return (is_disabled, reason)
4536+
4537+
4538+def _get_status_details(paths):
4539+ """Return a 3-tuple of status, status_details and time of last event.
4540+
4541+ @param paths: An initialized cloudinit.helpers.paths object.
4542+
4543+ Values are obtained from parsing paths.run_dir/status.json.
4544+ """
4545+ status = STATUS_ENABLED_NOT_RUN
4546+ status_detail = ''
4547+ status_v1 = {}
4548+
4549+ status_file = os.path.join(paths.run_dir, 'status.json')
4550+ result_file = os.path.join(paths.run_dir, 'result.json')
4551+
4552+ (is_disabled, reason) = _is_cloudinit_disabled(
4553+ CLOUDINIT_DISABLED_FILE, paths)
4554+ if is_disabled:
4555+ status = STATUS_DISABLED
4556+ status_detail = reason
4557+ if os.path.exists(status_file):
4558+ if not os.path.exists(result_file):
4559+ status = STATUS_RUNNING
4560+ status_v1 = load_json(load_file(status_file)).get('v1', {})
4561+ errors = []
4562+ latest_event = 0
4563+ for key, value in sorted(status_v1.items()):
4564+ if key == 'stage':
4565+ if value:
4566+ status = STATUS_RUNNING
4567+ status_detail = 'Running in stage: {0}'.format(value)
4568+ elif key == 'datasource':
4569+ status_detail = value
4570+ elif isinstance(value, dict):
4571+ errors.extend(value.get('errors', []))
4572+ start = value.get('start') or 0
4573+ finished = value.get('finished') or 0
4574+ if finished == 0 and start != 0:
4575+ status = STATUS_RUNNING
4576+ event_time = max(start, finished)
4577+ if event_time > latest_event:
4578+ latest_event = event_time
4579+ if errors:
4580+ status = STATUS_ERROR
4581+ status_detail = '\n'.join(errors)
4582+ elif status == STATUS_ENABLED_NOT_RUN and latest_event > 0:
4583+ status = STATUS_DONE
4584+ if latest_event:
4585+ time = strftime('%a, %d %b %Y %H:%M:%S %z', gmtime(latest_event))
4586+ else:
4587+ time = ''
4588+ return status, status_detail, time
4589+
4590+
4591+def main():
4592+ """Tool to report status of cloud-init."""
4593+ parser = get_parser()
4594+ sys.exit(handle_status_args('status', parser.parse_args()))
4595+
4596+
4597+if __name__ == '__main__':
4598+ main()
4599+
4600+# vi: ts=4 expandtab
4601diff --git a/cloudinit/cmd/tests/__init__.py b/cloudinit/cmd/tests/__init__.py
4602new file mode 100644
4603index 0000000..e69de29
4604--- /dev/null
4605+++ b/cloudinit/cmd/tests/__init__.py
4606diff --git a/cloudinit/cmd/tests/test_clean.py b/cloudinit/cmd/tests/test_clean.py
4607new file mode 100644
4608index 0000000..5a3ec3b
4609--- /dev/null
4610+++ b/cloudinit/cmd/tests/test_clean.py
4611@@ -0,0 +1,177 @@
4612+# This file is part of cloud-init. See LICENSE file for license information.
4613+
4614+from cloudinit.cmd import clean
4615+from cloudinit.util import ensure_dir, sym_link, write_file
4616+from cloudinit.tests.helpers import CiTestCase, wrap_and_call, mock
4617+from collections import namedtuple
4618+import os
4619+from six import StringIO
4620+
4621+mypaths = namedtuple('MyPaths', 'cloud_dir')
4622+
4623+
4624+class TestClean(CiTestCase):
4625+
4626+ def setUp(self):
4627+ super(TestClean, self).setUp()
4628+ self.new_root = self.tmp_dir()
4629+ self.artifact_dir = self.tmp_path('artifacts', self.new_root)
4630+ self.log1 = self.tmp_path('cloud-init.log', self.new_root)
4631+ self.log2 = self.tmp_path('cloud-init-output.log', self.new_root)
4632+
4633+ class FakeInit(object):
4634+ cfg = {'def_log_file': self.log1,
4635+ 'output': {'all': '|tee -a {0}'.format(self.log2)}}
4636+ paths = mypaths(cloud_dir=self.artifact_dir)
4637+
4638+ def __init__(self, ds_deps):
4639+ pass
4640+
4641+ def read_cfg(self):
4642+ pass
4643+
4644+ self.init_class = FakeInit
4645+
4646+ def test_remove_artifacts_removes_logs(self):
4647+ """remove_artifacts removes logs when remove_logs is True."""
4648+ write_file(self.log1, 'cloud-init-log')
4649+ write_file(self.log2, 'cloud-init-output-log')
4650+
4651+ self.assertFalse(
4652+ os.path.exists(self.artifact_dir), 'Unexpected artifacts dir')
4653+ retcode = wrap_and_call(
4654+ 'cloudinit.cmd.clean',
4655+ {'Init': {'side_effect': self.init_class}},
4656+ clean.remove_artifacts, remove_logs=True)
4657+ self.assertFalse(os.path.exists(self.log1), 'Unexpected file')
4658+ self.assertFalse(os.path.exists(self.log2), 'Unexpected file')
4659+ self.assertEqual(0, retcode)
4660+
4661+ def test_remove_artifacts_preserves_logs(self):
4662+ """remove_artifacts leaves logs when remove_logs is False."""
4663+ write_file(self.log1, 'cloud-init-log')
4664+ write_file(self.log2, 'cloud-init-output-log')
4665+
4666+ retcode = wrap_and_call(
4667+ 'cloudinit.cmd.clean',
4668+ {'Init': {'side_effect': self.init_class}},
4669+ clean.remove_artifacts, remove_logs=False)
4670+ self.assertTrue(os.path.exists(self.log1), 'Missing expected file')
4671+ self.assertTrue(os.path.exists(self.log2), 'Missing expected file')
4672+ self.assertEqual(0, retcode)
4673+
4674+ def test_remove_artifacts_removes_unlinks_symlinks(self):
4675+ """remove_artifacts cleans artifacts dir unlinking any symlinks."""
4676+ dir1 = os.path.join(self.artifact_dir, 'dir1')
4677+ ensure_dir(dir1)
4678+ symlink = os.path.join(self.artifact_dir, 'mylink')
4679+ sym_link(dir1, symlink)
4680+
4681+ retcode = wrap_and_call(
4682+ 'cloudinit.cmd.clean',
4683+ {'Init': {'side_effect': self.init_class}},
4684+ clean.remove_artifacts, remove_logs=False)
4685+ self.assertEqual(0, retcode)
4686+ for path in (dir1, symlink):
4687+ self.assertFalse(
4688+ os.path.exists(path),
4689+ 'Unexpected {0} dir'.format(path))
4690+
4691+ def test_remove_artifacts_removes_artifacts_skipping_seed(self):
4692+ """remove_artifacts cleans artifacts dir with exception of seed dir."""
4693+ dirs = [
4694+ self.artifact_dir,
4695+ os.path.join(self.artifact_dir, 'seed'),
4696+ os.path.join(self.artifact_dir, 'dir1'),
4697+ os.path.join(self.artifact_dir, 'dir2')]
4698+ for _dir in dirs:
4699+ ensure_dir(_dir)
4700+
4701+ retcode = wrap_and_call(
4702+ 'cloudinit.cmd.clean',
4703+ {'Init': {'side_effect': self.init_class}},
4704+ clean.remove_artifacts, remove_logs=False)
4705+ self.assertEqual(0, retcode)
4706+ for expected_dir in dirs[:2]:
4707+ self.assertTrue(
4708+ os.path.exists(expected_dir),
4709+ 'Missing {0} dir'.format(expected_dir))
4710+ for deleted_dir in dirs[2:]:
4711+ self.assertFalse(
4712+ os.path.exists(deleted_dir),
4713+ 'Unexpected {0} dir'.format(deleted_dir))
4714+
4715+ def test_remove_artifacts_removes_artifacts_removes_seed(self):
4716+ """remove_artifacts removes seed dir when remove_seed is True."""
4717+ dirs = [
4718+ self.artifact_dir,
4719+ os.path.join(self.artifact_dir, 'seed'),
4720+ os.path.join(self.artifact_dir, 'dir1'),
4721+ os.path.join(self.artifact_dir, 'dir2')]
4722+ for _dir in dirs:
4723+ ensure_dir(_dir)
4724+
4725+ retcode = wrap_and_call(
4726+ 'cloudinit.cmd.clean',
4727+ {'Init': {'side_effect': self.init_class}},
4728+ clean.remove_artifacts, remove_logs=False, remove_seed=True)
4729+ self.assertEqual(0, retcode)
4730+ self.assertTrue(
4731+ os.path.exists(self.artifact_dir), 'Missing artifact dir')
4732+ for deleted_dir in dirs[1:]:
4733+ self.assertFalse(
4734+ os.path.exists(deleted_dir),
4735+ 'Unexpected {0} dir'.format(deleted_dir))
4736+
4737+ def test_remove_artifacts_returns_one_on_errors(self):
4738+ """remove_artifacts returns non-zero on failure and prints an error."""
4739+ ensure_dir(self.artifact_dir)
4740+ ensure_dir(os.path.join(self.artifact_dir, 'dir1'))
4741+
4742+ with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:
4743+ retcode = wrap_and_call(
4744+ 'cloudinit.cmd.clean',
4745+ {'del_dir': {'side_effect': OSError('oops')},
4746+ 'Init': {'side_effect': self.init_class}},
4747+ clean.remove_artifacts, remove_logs=False)
4748+ self.assertEqual(1, retcode)
4749+ self.assertEqual(
4750+ 'ERROR: Could not remove dir1: oops\n', m_stderr.getvalue())
4751+
4752+ def test_handle_clean_args_reboots(self):
4753+ """handle_clean_args_reboots when reboot arg is provided."""
4754+
4755+ called_cmds = []
4756+
4757+ def fake_subp(cmd, capture):
4758+ called_cmds.append((cmd, capture))
4759+ return '', ''
4760+
4761+ myargs = namedtuple('MyArgs', 'remove_logs remove_seed reboot')
4762+ cmdargs = myargs(remove_logs=False, remove_seed=False, reboot=True)
4763+ retcode = wrap_and_call(
4764+ 'cloudinit.cmd.clean',
4765+ {'subp': {'side_effect': fake_subp},
4766+ 'Init': {'side_effect': self.init_class}},
4767+ clean.handle_clean_args, name='does not matter', args=cmdargs)
4768+ self.assertEqual(0, retcode)
4769+ self.assertEqual(
4770+ [(['shutdown', '-r', 'now'], False)], called_cmds)
4771+
4772+ def test_status_main(self):
4773+ '''clean.main can be run as a standalone script.'''
4774+ write_file(self.log1, 'cloud-init-log')
4775+ with self.assertRaises(SystemExit) as context_manager:
4776+ wrap_and_call(
4777+ 'cloudinit.cmd.clean',
4778+ {'Init': {'side_effect': self.init_class},
4779+ 'sys.exit': {'side_effect': self.sys_exit},
4780+ 'sys.argv': {'new': ['clean', '--logs']}},
4781+ clean.main)
4782+
4783+ self.assertEqual(0, context_manager.exception.code)
4784+ self.assertFalse(
4785+ os.path.exists(self.log1), 'Unexpected log {0}'.format(self.log1))
4786+
4787+
4788+# vi: ts=4 expandtab syntax=python
4789diff --git a/cloudinit/cmd/tests/test_status.py b/cloudinit/cmd/tests/test_status.py
4790new file mode 100644
4791index 0000000..37a8993
4792--- /dev/null
4793+++ b/cloudinit/cmd/tests/test_status.py
4794@@ -0,0 +1,390 @@
4795+# This file is part of cloud-init. See LICENSE file for license information.
4796+
4797+from collections import namedtuple
4798+import os
4799+from six import StringIO
4800+from textwrap import dedent
4801+
4802+from cloudinit.atomic_helper import write_json
4803+from cloudinit.cmd import status
4804+from cloudinit.util import ensure_file
4805+from cloudinit.tests.helpers import CiTestCase, wrap_and_call, mock
4806+
4807+mypaths = namedtuple('MyPaths', 'run_dir')
4808+myargs = namedtuple('MyArgs', 'long wait')
4809+
4810+
4811+class TestStatus(CiTestCase):
4812+
4813+ def setUp(self):
4814+ super(TestStatus, self).setUp()
4815+ self.new_root = self.tmp_dir()
4816+ self.status_file = self.tmp_path('status.json', self.new_root)
4817+ self.disable_file = self.tmp_path('cloudinit-disable', self.new_root)
4818+ self.paths = mypaths(run_dir=self.new_root)
4819+
4820+ class FakeInit(object):
4821+ paths = self.paths
4822+
4823+ def __init__(self, ds_deps):
4824+ pass
4825+
4826+ def read_cfg(self):
4827+ pass
4828+
4829+ self.init_class = FakeInit
4830+
4831+ def test__is_cloudinit_disabled_false_on_sysvinit(self):
4832+ '''When not in an environment using systemd, return False.'''
4833+ ensure_file(self.disable_file) # Create the ignored disable file
4834+ (is_disabled, reason) = wrap_and_call(
4835+ 'cloudinit.cmd.status',
4836+ {'uses_systemd': False},
4837+ status._is_cloudinit_disabled, self.disable_file, self.paths)
4838+ self.assertFalse(
4839+ is_disabled, 'expected enabled cloud-init on sysvinit')
4840+ self.assertEqual('Cloud-init enabled on sysvinit', reason)
4841+
4842+ def test__is_cloudinit_disabled_true_on_disable_file(self):
4843+ '''When using systemd and disable_file is present return disabled.'''
4844+ ensure_file(self.disable_file) # Create observed disable file
4845+ (is_disabled, reason) = wrap_and_call(
4846+ 'cloudinit.cmd.status',
4847+ {'uses_systemd': True},
4848+ status._is_cloudinit_disabled, self.disable_file, self.paths)
4849+ self.assertTrue(is_disabled, 'expected disabled cloud-init')
4850+ self.assertEqual(
4851+ 'Cloud-init disabled by {0}'.format(self.disable_file), reason)
4852+
4853+ def test__is_cloudinit_disabled_false_on_kernel_cmdline_enable(self):
4854+ '''Not disabled when using systemd and enabled via commandline.'''
4855+ ensure_file(self.disable_file) # Create ignored disable file
4856+ (is_disabled, reason) = wrap_and_call(
4857+ 'cloudinit.cmd.status',
4858+ {'uses_systemd': True,
4859+ 'get_cmdline': 'something cloud-init=enabled else'},
4860+ status._is_cloudinit_disabled, self.disable_file, self.paths)
4861+ self.assertFalse(is_disabled, 'expected enabled cloud-init')
4862+ self.assertEqual(
4863+ 'Cloud-init enabled by kernel command line cloud-init=enabled',
4864+ reason)
4865+
4866+ def test__is_cloudinit_disabled_true_on_kernel_cmdline(self):
4867+ '''When using systemd and disable_file is present return disabled.'''
4868+ (is_disabled, reason) = wrap_and_call(
4869+ 'cloudinit.cmd.status',
4870+ {'uses_systemd': True,
4871+ 'get_cmdline': 'something cloud-init=disabled else'},
4872+ status._is_cloudinit_disabled, self.disable_file, self.paths)
4873+ self.assertTrue(is_disabled, 'expected disabled cloud-init')
4874+ self.assertEqual(
4875+ 'Cloud-init disabled by kernel parameter cloud-init=disabled',
4876+ reason)
4877+
4878+ def test__is_cloudinit_disabled_true_when_generator_disables(self):
4879+ '''When cloud-init-generator doesn't write enabled file return True.'''
4880+ enabled_file = os.path.join(self.paths.run_dir, 'enabled')
4881+ self.assertFalse(os.path.exists(enabled_file))
4882+ (is_disabled, reason) = wrap_and_call(
4883+ 'cloudinit.cmd.status',
4884+ {'uses_systemd': True,
4885+ 'get_cmdline': 'something'},
4886+ status._is_cloudinit_disabled, self.disable_file, self.paths)
4887+ self.assertTrue(is_disabled, 'expected disabled cloud-init')
4888+ self.assertEqual('Cloud-init disabled by cloud-init-generator', reason)
4889+
4890+ def test__is_cloudinit_disabled_false_when_enabled_in_systemd(self):
4891+ '''Report enabled when systemd generator creates the enabled file.'''
4892+ enabled_file = os.path.join(self.paths.run_dir, 'enabled')
4893+ ensure_file(enabled_file)
4894+ (is_disabled, reason) = wrap_and_call(
4895+ 'cloudinit.cmd.status',
4896+ {'uses_systemd': True,
4897+ 'get_cmdline': 'something ignored'},
4898+ status._is_cloudinit_disabled, self.disable_file, self.paths)
4899+ self.assertFalse(is_disabled, 'expected enabled cloud-init')
4900+ self.assertEqual(
4901+ 'Cloud-init enabled by systemd cloud-init-generator', reason)
4902+
4903+ def test_status_returns_not_run(self):
4904+ '''When status.json does not exist yet, return 'not run'.'''
4905+ self.assertFalse(
4906+ os.path.exists(self.status_file), 'Unexpected status.json found')
4907+ cmdargs = myargs(long=False, wait=False)
4908+ with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
4909+ retcode = wrap_and_call(
4910+ 'cloudinit.cmd.status',
4911+ {'_is_cloudinit_disabled': (False, ''),
4912+ 'Init': {'side_effect': self.init_class}},
4913+ status.handle_status_args, 'ignored', cmdargs)
4914+ self.assertEqual(0, retcode)
4915+ self.assertEqual('status: not run\n', m_stdout.getvalue())
4916+
4917+ def test_status_returns_disabled_long_on_presence_of_disable_file(self):
4918+ '''When cloudinit is disabled, return disabled reason.'''
4919+
4920+ checked_files = []
4921+
4922+ def fakeexists(filepath):
4923+ checked_files.append(filepath)
4924+ status_file = os.path.join(self.paths.run_dir, 'status.json')
4925+ return bool(not filepath == status_file)
4926+
4927+ cmdargs = myargs(long=True, wait=False)
4928+ with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
4929+ retcode = wrap_and_call(
4930+ 'cloudinit.cmd.status',
4931+ {'os.path.exists': {'side_effect': fakeexists},
4932+ '_is_cloudinit_disabled': (True, 'disabled for some reason'),
4933+ 'Init': {'side_effect': self.init_class}},
4934+ status.handle_status_args, 'ignored', cmdargs)
4935+ self.assertEqual(0, retcode)
4936+ self.assertEqual(
4937+ [os.path.join(self.paths.run_dir, 'status.json')],
4938+ checked_files)
4939+ expected = dedent('''\
4940+ status: disabled
4941+ detail:
4942+ disabled for some reason
4943+ ''')
4944+ self.assertEqual(expected, m_stdout.getvalue())
4945+
4946+ def test_status_returns_running_on_no_results_json(self):
4947+ '''Report running when status.json exists but result.json does not.'''
4948+ result_file = self.tmp_path('result.json', self.new_root)
4949+ write_json(self.status_file, {})
4950+ self.assertFalse(
4951+ os.path.exists(result_file), 'Unexpected result.json found')
4952+ cmdargs = myargs(long=False, wait=False)
4953+ with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
4954+ retcode = wrap_and_call(
4955+ 'cloudinit.cmd.status',
4956+ {'_is_cloudinit_disabled': (False, ''),
4957+ 'Init': {'side_effect': self.init_class}},
4958+ status.handle_status_args, 'ignored', cmdargs)
4959+ self.assertEqual(0, retcode)
4960+ self.assertEqual('status: running\n', m_stdout.getvalue())
4961+
4962+ def test_status_returns_running(self):
4963+ '''Report running when status exists with an unfinished stage.'''
4964+ ensure_file(self.tmp_path('result.json', self.new_root))
4965+ write_json(self.status_file,
4966+ {'v1': {'init': {'start': 1, 'finished': None}}})
4967+ cmdargs = myargs(long=False, wait=False)
4968+ with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
4969+ retcode = wrap_and_call(
4970+ 'cloudinit.cmd.status',
4971+ {'_is_cloudinit_disabled': (False, ''),
4972+ 'Init': {'side_effect': self.init_class}},
4973+ status.handle_status_args, 'ignored', cmdargs)
4974+ self.assertEqual(0, retcode)
4975+ self.assertEqual('status: running\n', m_stdout.getvalue())
4976+
4977+ def test_status_returns_done(self):
4978+ '''Report done results.json exists no stages are unfinished.'''
4979+ ensure_file(self.tmp_path('result.json', self.new_root))
4980+ write_json(
4981+ self.status_file,
4982+ {'v1': {'stage': None, # No current stage running
4983+ 'datasource': (
4984+ 'DataSourceNoCloud [seed=/var/.../seed/nocloud-net]'
4985+ '[dsmode=net]'),
4986+ 'blah': {'finished': 123.456},
4987+ 'init': {'errors': [], 'start': 124.567,
4988+ 'finished': 125.678},
4989+ 'init-local': {'start': 123.45, 'finished': 123.46}}})
4990+ cmdargs = myargs(long=False, wait=False)
4991+ with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout:
4992+ retcode = wrap_and_call(
4993+ 'cloudinit.cmd.status',
4994+ {'_is_cloudinit_disabled': (False, ''),
4995+ 'Init': {'side_effect': self.init_class}},
4996+ status.handle_status_args, 'ignored', cmdargs)
4997+ self.assertEqual(0, retcode)
4998+ self.assertEqual('status: done\n', m_stdout.getvalue())
4999+
5000+ def test_status_returns_done_long(self):
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: