├── tests ├── __init__.py ├── unittests │ ├── __init__.py │ ├── test_runs │ │ ├── __init__.py │ │ └── test_merge_run.py │ ├── test_datasource │ │ ├── __init__.py │ │ └── test_cloudsigma.py │ ├── test_distros │ │ ├── __init__.py │ │ ├── test_hostname.py │ │ ├── test_hosts.py │ │ └── test_resolv.py │ ├── test_filters │ │ └── __init__.py │ ├── test_handler │ │ ├── __init__.py │ │ ├── test_handler_locale.py │ │ ├── test_handler_set_hostname.py │ │ └── test_handler_timezone.py │ ├── test_pathprefix2dict.py │ ├── test_cs_util.py │ └── test_builtin_handlers.py ├── data │ ├── merge_sources │ │ ├── expected1.yaml │ │ ├── expected3.yaml │ │ ├── expected4.yaml │ │ ├── source11-2.yaml │ │ ├── source11-3.yaml │ │ ├── source4-1.yaml │ │ ├── expected2.yaml │ │ ├── source1-1.yaml │ │ ├── source3-1.yaml │ │ ├── expected11.yaml │ │ ├── expected12.yaml │ │ ├── source11-1.yaml │ │ ├── source12-2.yaml │ │ ├── source2-2.yaml │ │ ├── source6-1.yaml │ │ ├── source2-1.yaml │ │ ├── source5-1.yaml │ │ ├── expected5.yaml │ │ ├── source12-1.yaml │ │ ├── source10-1.yaml │ │ ├── source1-2.yaml │ │ ├── source3-2.yaml │ │ ├── source4-2.yaml │ │ ├── source9-2.yaml │ │ ├── expected6.yaml │ │ ├── source5-2.yaml │ │ ├── expected10.yaml │ │ ├── source10-2.yaml │ │ ├── source6-2.yaml │ │ ├── source9-1.yaml │ │ ├── expected9.yaml │ │ ├── source8-2.yaml │ │ ├── expected8.yaml │ │ ├── source8-1.yaml │ │ ├── source7-2.yaml │ │ ├── source7-1.yaml │ │ └── expected7.yaml │ ├── roots │ │ └── simple_ubuntu │ │ │ └── etc │ │ │ └── networks │ │ │ └── interfaces │ ├── filter_cloud_multipart_1.email │ ├── filter_cloud_multipart_header.email │ ├── user_data.1.txt │ ├── filter_cloud_multipart_2.email │ ├── filter_cloud_multipart.yaml │ ├── mountinfo_raring_btrfs.txt │ └── mountinfo_precise_ext4.txt └── configs │ └── sample1.yaml ├── packages └── debian │ ├── compat │ ├── pycompat │ ├── dirs │ ├── watch │ ├── changelog.in │ ├── rules │ ├── control.in │ └── copyright ├── doc ├── rtd │ ├── topics │ │ ├── hacking.rst │ │ ├── modules.rst │ │ ├── merging.rst │ │ ├── moreinfo.rst │ │ ├── availability.rst │ │ └── capabilities.rst │ ├── static │ │ └── logo.png │ ├── index.rst │ └── conf.py ├── examples │ ├── plain-ignored.txt │ ├── seed │ │ ├── user-data │ │ ├── README │ │ └── meta-data │ ├── user-script.txt │ ├── cloud-config-update-apt.txt │ ├── cloud-config-update-packages.txt │ ├── include.txt │ ├── upstart-cloud-config.txt │ ├── upstart-rclocal.txt │ ├── cloud-config-final-message.txt │ ├── cloud-config-archive.txt │ ├── include-once.txt │ ├── cloud-config-phone-home.txt │ ├── cloud-config-install-packages.txt │ ├── cloud-config-gluster.txt │ ├── cloud-config-resolv-conf.txt │ ├── cloud-config-vendor-data.txt │ ├── cloud-config-launch-index.txt │ ├── cloud-config-boot-cmds.txt │ ├── cloud-config-yum-repo.txt │ ├── cloud-config-run-cmds.txt │ ├── cloud-config-landscape.txt │ ├── kernel-cmdline.txt │ ├── part-handler.txt │ ├── cloud-config-archive-launch-index.txt │ ├── cloud-config-growpart.txt │ ├── cloud-config-ca-certs.txt │ ├── cloud-config-write-files.txt │ ├── cloud-config-power-state.txt │ ├── cloud-config-add-apt-repos.txt │ ├── cloud-config-mount-points.txt │ ├── part-handler-v2.txt │ ├── cloud-config-puppet.txt │ ├── cloud-config-datasources.txt │ ├── cloud-config-salt-minion.txt │ └── cloud-config-mcollective.txt ├── README ├── sources │ ├── ovf │ │ ├── user-data │ │ ├── ovf-env.xml.tmpl │ │ ├── ovfdemo.pem │ │ └── example │ │ │ └── ovf-env.xml │ ├── cloudsigma │ │ └── README.rst │ └── kernel-cmdline.txt ├── var-lib-cloud.txt └── vendordata.txt ├── test-requirements.txt ├── config └── cloud.cfg.d │ ├── README │ └── 05_logging.cfg ├── upstart ├── cloud-init-local.conf ├── cloud-init.conf ├── cloud-config.conf ├── cloud-final.conf ├── cloud-log-shutdown.conf ├── cloud-init-container.conf └── cloud-init-nonet.conf ├── tools ├── 21-cloudinit.conf ├── make-dist-tarball ├── run-pylint ├── validate-yaml.py ├── read-dependencies ├── read-version ├── make-tarball ├── write-ssh-key-fingerprints ├── run-pep8 ├── motd-hook ├── cloud-init-per └── make-mime.py ├── systemd ├── cloud-init-local.service ├── cloud-config.service ├── cloud-final.service ├── cloud-init.service └── cloud-config.target ├── pylintrc ├── cloudinit ├── sources │ ├── helpers │ │ └── __init__.py │ └── DataSourceNone.py ├── version.py ├── __init__.py ├── filters │ └── __init__.py ├── safeyaml.py ├── distros │ ├── fedora.py │ ├── parsers │ │ └── __init__.py │ └── ubuntu.py ├── type_utils.py ├── config │ ├── cc_users_groups.py │ ├── cc_disable_ec2_metadata.py │ ├── cc_timezone.py │ ├── cc_runcmd.py │ ├── cc_locale.py │ ├── cc_scripts_per_once.py │ ├── cc_scripts_per_boot.py │ ├── cc_scripts_per_instance.py │ ├── cc_scripts_user.py │ ├── cc_scripts_vendor.py │ ├── cc_set_hostname.py │ ├── cc_update_hostname.py │ ├── cc_emit_upstart.py │ ├── cc_bootcmd.py │ ├── cc_keys_to_console.py │ ├── __init__.py │ ├── cc_seed_random.py │ ├── cc_apt_pipelining.py │ ├── cc_salt_minion.py │ ├── cc_final_message.py │ ├── cc_grub_dpkg.py │ ├── cc_update_etc_hosts.py │ └── cc_foo.py ├── templater.py ├── mergers │ └── m_str.py ├── patcher.py ├── handlers │ └── shell_script.py ├── settings.py ├── importer.py └── signal_handler.py ├── templates ├── hosts.suse.tmpl ├── resolv.conf.tmpl ├── chef_client.rb.tmpl ├── hosts.redhat.tmpl ├── hosts.debian.tmpl └── sources.list.debian.tmpl ├── requirements.txt ├── HACKING.rst └── Makefile /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/debian/compat: -------------------------------------------------------------------------------- 1 | 5 2 | -------------------------------------------------------------------------------- /tests/unittests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/debian/pycompat: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /tests/unittests/test_runs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unittests/test_datasource/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unittests/test_distros/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unittests/test_filters/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unittests/test_handler/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/rtd/topics/hacking.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../../HACKING.rst 2 | -------------------------------------------------------------------------------- /doc/rtd/topics/modules.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Modules 3 | ========= 4 | -------------------------------------------------------------------------------- /tests/data/merge_sources/expected1.yaml: -------------------------------------------------------------------------------- 1 | Blah: ['blah2', 'b'] 2 | -------------------------------------------------------------------------------- /tests/data/merge_sources/expected3.yaml: -------------------------------------------------------------------------------- 1 | Blah: [blah2, 'blah1'] 2 | -------------------------------------------------------------------------------- /tests/data/merge_sources/expected4.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | Blah: {} 3 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source11-2.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | b: 4 4 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source11-3.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | a: 22 4 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source4-1.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | Blah: 3 | b: 1 4 | -------------------------------------------------------------------------------- /tests/data/merge_sources/expected2.yaml: -------------------------------------------------------------------------------- 1 | Blah: 3 2 | Blah2: 2 3 | Blah3: [1] 4 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source1-1.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | Blah: ['blah2'] 3 | 4 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source3-1.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | Blah: ['blah1'] 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/data/merge_sources/expected11.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | a: 22 4 | b: 4 5 | c: 3 6 | -------------------------------------------------------------------------------- /tests/data/merge_sources/expected12.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | a: 4 | e: 5 | y: 2 6 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source11-1.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | a: 1 4 | b: 2 5 | c: 3 6 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source12-2.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | a: 4 | e: 5 | y: 2 6 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | httpretty>=0.7.1 2 | mocker 3 | nose 4 | pep8 5 | pyflakes 6 | pylint 7 | -------------------------------------------------------------------------------- /tests/data/roots/simple_ubuntu/etc/networks/interfaces: -------------------------------------------------------------------------------- 1 | auto lo 2 | iface lo inet loopback 3 | 4 | -------------------------------------------------------------------------------- /doc/rtd/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudsigma/cloud-init/HEAD/doc/rtd/static/logo.png -------------------------------------------------------------------------------- /packages/debian/dirs: -------------------------------------------------------------------------------- 1 | var/lib/cloud 2 | usr/bin 3 | etc/init 4 | usr/share/doc/cloud 5 | etc/cloud 6 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source2-2.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | Blah: 3 4 | Blah2: 2 5 | Blah3: [1] 6 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source6-1.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | run_cmds: 4 | - bash 5 | - top 6 | -------------------------------------------------------------------------------- /doc/examples/plain-ignored.txt: -------------------------------------------------------------------------------- 1 | #ignored 2 | Nothing will be done with this part by the UserDataHandler 3 | -------------------------------------------------------------------------------- /doc/rtd/topics/merging.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Merging 3 | ========= 4 | 5 | .. include:: ../../merging.rst 6 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source2-1.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | 4 | Blah: 1 5 | Blah2: 2 6 | Blah3: 3 7 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source5-1.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | 4 | Blah: 1 5 | Blah2: 2 6 | Blah3: 3 7 | -------------------------------------------------------------------------------- /tests/data/merge_sources/expected5.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | Blah: 3 4 | Blah2: 2 5 | Blah3: [1] 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/debian/watch: -------------------------------------------------------------------------------- 1 | version=3 2 | https://launchpad.net/cloud-init/+download .*/\+download/cloud-init-(.+)\.tar.gz 3 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source12-1.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | a: 4 | c: 1 5 | d: 2 6 | e: 7 | z: a 8 | y: b 9 | -------------------------------------------------------------------------------- /doc/examples/seed/user-data: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | runcmd: 3 | - [ sh, -c, 'echo ==== $(date) ====; echo HI WORLD; echo =======' ] 4 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source10-1.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | power_state: 4 | delay: 30 5 | mode: poweroff 6 | message: [Bye, Bye] 7 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source1-2.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | Blah: ['b'] 4 | 5 | merge_how: 'dict(recurse_array,no_replace)+list(append)' 6 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source3-2.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | Blah: ['blah2'] 3 | 4 | merge_how: 'dict(recurse_array,no_replace)+list(prepend)' 5 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source4-2.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | Blah: 3 | b: null 4 | 5 | 6 | merge_how: 'dict(allow_delete,no_replace)+list()' 7 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source9-2.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | phone_home: 4 | url: $BLAH_BLAH 5 | 6 | merge_how: 'dict(recurse_str)+str(append)' 7 | -------------------------------------------------------------------------------- /tests/data/merge_sources/expected6.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | run_cmds: 4 | - bash 5 | - top 6 | - ps 7 | - vi 8 | - emacs 9 | 10 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source5-2.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | Blah: 3 4 | Blah2: 2 5 | Blah3: [1] 6 | 7 | 8 | merge_how: 'dict(replace)+list(append)' 9 | -------------------------------------------------------------------------------- /doc/README: -------------------------------------------------------------------------------- 1 | This project is cloud-init it is hosted on launchpad at 2 | https://launchpad.net/cloud-init 3 | 4 | The package was previously named ec2-init. 5 | -------------------------------------------------------------------------------- /tests/data/merge_sources/expected10.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | power_state: 4 | delay: 30 5 | mode: poweroff 6 | message: [Bye, Bye, Pew, Pew] 7 | 8 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source10-2.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | power_state: 4 | message: [Pew, Pew] 5 | 6 | merge_how: 'dict(recurse_list)+list(append)' 7 | -------------------------------------------------------------------------------- /config/cloud.cfg.d/README: -------------------------------------------------------------------------------- 1 | # All files in this directory will be read by cloud-init 2 | # They are read in lexical order. Later files overwrite values in 3 | # earlier files. 4 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source6-2.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | run_cmds: 4 | - ps 5 | - vi 6 | - emacs 7 | 8 | merge_type: 'list(append)+dict(recurse_array)+str()' 9 | -------------------------------------------------------------------------------- /doc/examples/user-script.txt: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat < Fri, 16 Dec 2011 11:50:25 -0500 7 | -------------------------------------------------------------------------------- /tests/data/merge_sources/expected8.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | mounts: 4 | - [ ephemeral22, /mnt, auto, "defaults,noexec" ] 5 | - [ sdc, /opt/data ] 6 | - [ xvdh, /opt/data, "auto", "defaults,nobootwait", "0", "0" ] 7 | - [ dd, /dev/zero ] 8 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source8-1.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | mounts: 4 | - [ ephemeral0, /mnt, auto, "defaults,noexec" ] 5 | - [ sdc, /opt/data ] 6 | - [ xvdh, /opt/data, "auto", "defaults,nobootwait", "0", "0" ] 7 | - [ dd, /dev/zero ] 8 | -------------------------------------------------------------------------------- /upstart/cloud-init-local.conf: -------------------------------------------------------------------------------- 1 | # cloud-init - the initial cloud-init job 2 | # crawls metadata service, emits cloud-config 3 | start on mounted MOUNTPOINT=/ 4 | 5 | task 6 | 7 | console output 8 | 9 | exec /usr/bin/cloud-init init --local 10 | -------------------------------------------------------------------------------- /tests/data/filter_cloud_multipart_1.email: -------------------------------------------------------------------------------- 1 | From nobody Fri Aug 31 17:17:00 2012 2 | Content-Type: text/plain; charset="us-ascii" 3 | MIME-Version: 1.0 4 | Content-Transfer-Encoding: 7bit 5 | 6 | 7 | #cloud-config 8 | b: c 9 | launch-index: 2 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/data/filter_cloud_multipart_header.email: -------------------------------------------------------------------------------- 1 | From nobody Fri Aug 31 17:17:00 2012 2 | Content-Type: text/plain; charset="us-ascii" 3 | MIME-Version: 1.0 4 | Launch-Index: 5 5 | Content-Transfer-Encoding: 7bit 6 | 7 | 8 | #cloud-config 9 | b: c 10 | 11 | 12 | -------------------------------------------------------------------------------- /upstart/cloud-init.conf: -------------------------------------------------------------------------------- 1 | # cloud-init - the initial cloud-init job 2 | # crawls metadata service, emits cloud-config 3 | start on mounted MOUNTPOINT=/ and stopped cloud-init-nonet 4 | 5 | task 6 | 7 | console output 8 | 9 | exec /usr/bin/cloud-init init 10 | -------------------------------------------------------------------------------- /doc/examples/include.txt: -------------------------------------------------------------------------------- 1 | #include 2 | # entries are one url per line. comment lines beginning with '#' are allowed 3 | # urls are passed to urllib.urlopen, so the format must be supported there 4 | http://www.ubuntu.com/robots.txt 5 | http://www.w3schools.com/html/lastpage.htm 6 | -------------------------------------------------------------------------------- /doc/examples/upstart-cloud-config.txt: -------------------------------------------------------------------------------- 1 | #upstart-job 2 | description "My test job" 3 | 4 | start on cloud-config 5 | console output 6 | task 7 | 8 | script 9 | echo "====BEGIN=======" 10 | echo "HELLO WORLD: $UPSTART_JOB" 11 | echo "=====END========" 12 | end script 13 | -------------------------------------------------------------------------------- /tools/21-cloudinit.conf: -------------------------------------------------------------------------------- 1 | # Log cloudinit generated log messages to file 2 | :syslogtag, isequal, "[CLOUDINIT]" /var/log/cloud-init.log 3 | 4 | # comment out the following line to allow CLOUDINIT messages through. 5 | # Doing so means you'll also get CLOUDINIT messages in /var/log/syslog 6 | & ~ 7 | -------------------------------------------------------------------------------- /upstart/cloud-config.conf: -------------------------------------------------------------------------------- 1 | # cloud-config - Handle applying the settings specified in cloud-config 2 | description "Handle applying cloud-config" 3 | emits cloud-config 4 | 5 | start on (filesystem and started rsyslog) 6 | console output 7 | task 8 | 9 | exec cloud-init modules --mode=config 10 | -------------------------------------------------------------------------------- /doc/examples/upstart-rclocal.txt: -------------------------------------------------------------------------------- 1 | #upstart-job 2 | description "a test upstart job" 3 | 4 | start on stopped rc RUNLEVEL=[2345] 5 | console output 6 | task 7 | 8 | script 9 | echo "====BEGIN=======" 10 | echo "HELLO RC.LOCAL LIKE WORLD: $UPSTART_JOB" 11 | echo "=====END========" 12 | end script 13 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-final-message.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | # final_message 4 | # default: cloud-init boot finished at $TIMESTAMP. Up $UPTIME seconds 5 | # this message is written by cloud-final when the system is finished 6 | # its first boot 7 | final_message: "The system is finally up, after $UPTIME seconds" 8 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-archive.txt: -------------------------------------------------------------------------------- 1 | #cloud-config-archive 2 | - type: foo/wark 3 | filename: bar 4 | content: | 5 | This is my payload 6 | hello 7 | - this is also payload 8 | - | 9 | multi line payload 10 | here 11 | - 12 | type: text/upstart-job 13 | filename: my-upstart.conf 14 | content: | 15 | whats this, yo? 16 | 17 | -------------------------------------------------------------------------------- /upstart/cloud-final.conf: -------------------------------------------------------------------------------- 1 | # cloud-final.conf - run "final" jobs 2 | # this runs around traditional "rc.local" time. 3 | # and after all cloud-config jobs are run 4 | description "execute cloud user/final scripts" 5 | 6 | start on (stopped rc RUNLEVEL=[2345] and stopped cloud-config) 7 | console output 8 | task 9 | 10 | exec cloud-init modules --mode=final 11 | -------------------------------------------------------------------------------- /doc/examples/include-once.txt: -------------------------------------------------------------------------------- 1 | #include-once 2 | # entries are one url per line. comment lines beginning with '#' are allowed 3 | # urls are passed to urllib.urlopen, so the format must be supported there 4 | # This entries will just be processed ONE TIME by cloud-init, any further 5 | # iterations won't process this file 6 | http://www.ubuntu.com/robots.txt 7 | http://www.w3schools.com/html/lastpage.htm 8 | -------------------------------------------------------------------------------- /doc/rtd/topics/moreinfo.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | More information 3 | ========= 4 | 5 | Useful external references 6 | ------------------------- 7 | 8 | - `The beauty of cloudinit`_ 9 | - `Introduction to cloud-init`_ (video) 10 | 11 | .. _Introduction to cloud-init: http://www.youtube.com/watch?v=-zL3BdbKyGY 12 | .. _The beauty of cloudinit: http://brandon.fuller.name/archives/2011/05/02/06.40.57/ 13 | -------------------------------------------------------------------------------- /tests/data/user_data.1.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | write_files: 3 | - content: blah 4 | path: /etc/blah.ini 5 | permissions: 493 6 | 7 | system_info: 8 | package_mirrors: 9 | - arches: [i386, amd64, blah] 10 | failsafe: 11 | primary: http://my.archive.mydomain.com/ubuntu 12 | security: http://my.security.mydomain.com/ubuntu 13 | search: 14 | primary: [] 15 | security: [] 16 | -------------------------------------------------------------------------------- /systemd/cloud-init-local.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Initial cloud-init job (pre-networking) 3 | Wants=local-fs.target 4 | After=local-fs.target 5 | 6 | [Service] 7 | Type=oneshot 8 | ExecStart=/usr/bin/cloud-init init --local 9 | RemainAfterExit=yes 10 | TimeoutSec=0 11 | 12 | # Output needs to appear in instance console output 13 | StandardOutput=journal+console 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-phone-home.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | # phone_home: if this dictionary is present, then the phone_home 4 | # cloud-config module will post specified data back to the given 5 | # url 6 | # default: none 7 | # phone_home: 8 | # url: http://my.foo.bar/$INSTANCE/ 9 | # post: all 10 | # tries: 10 11 | # 12 | phone_home: 13 | url: http://my.example.com/$INSTANCE_ID/ 14 | post: [ pub_key_dsa, pub_key_rsa, pub_key_ecdsa, instance_id ] 15 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-install-packages.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | # Install additional packages on first boot 4 | # 5 | # Default: none 6 | # 7 | # if packages are specified, this apt_update will be set to true 8 | # 9 | # packages may be supplied as a single package name or as a list 10 | # with the format [, ] wherein the specifc 11 | # package version will be installed. 12 | packages: 13 | - pwgen 14 | - pastebinit 15 | - [libpython2.7, 2.7.3-0ubuntu3.1] 16 | -------------------------------------------------------------------------------- /systemd/cloud-config.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Apply the settings specified in cloud-config 3 | After=network.target syslog.target cloud-config.target 4 | Requires=cloud-config.target 5 | Wants=network.target 6 | 7 | [Service] 8 | Type=oneshot 9 | ExecStart=/usr/bin/cloud-init modules --mode=config 10 | RemainAfterExit=yes 11 | TimeoutSec=0 12 | 13 | # Output needs to appear in instance console output 14 | StandardOutput=journal+console 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /systemd/cloud-final.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Execute cloud user/final scripts 3 | After=network.target syslog.target cloud-config.service rc-local.service 4 | Requires=cloud-config.target 5 | Wants=network.target 6 | 7 | [Service] 8 | Type=oneshot 9 | ExecStart=/usr/bin/cloud-init modules --mode=final 10 | RemainAfterExit=yes 11 | TimeoutSec=0 12 | 13 | # Output needs to appear in instance console output 14 | StandardOutput=journal+console 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /tools/make-dist-tarball: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | Usage() { 4 | cat <&2 ; exit 1; } 16 | 17 | out="${topdir}/cloud-init-${tag}.tar.gz" 18 | 19 | bzr export --format=tgz --root="cloud-init-$tag" \ 20 | "--revision=tag:${tag}" "$out" "$topdir" && 21 | echo "Wrote ${out}" 22 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source7-2.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | users: 4 | - bob 5 | - joe 6 | - sue 7 | - name: foobar_jr 8 | gecos: Foo B. Bar Jr 9 | primary-group: foobar 10 | groups: users 11 | selinux-user: staff_u 12 | expiredate: 2012-09-01 13 | ssh-import-id: foobar 14 | lock-passwd: false 15 | passwd: $6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/ 16 | 17 | merge_how: "dict(recurse_array)+list(append)" 18 | -------------------------------------------------------------------------------- /systemd/cloud-init.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Initial cloud-init job (metadata service crawler) 3 | After=local-fs.target network.target cloud-init-local.service 4 | Requires=network.target 5 | Wants=local-fs.target cloud-init-local.service 6 | 7 | [Service] 8 | Type=oneshot 9 | ExecStart=/usr/bin/cloud-init init 10 | RemainAfterExit=yes 11 | TimeoutSec=0 12 | 13 | # Output needs to appear in instance console output 14 | StandardOutput=journal+console 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /tools/run-pylint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# -eq 0 ]; then 4 | files=( bin/cloud-init $(find * -name "*.py" -type f) ) 5 | else 6 | files=( "$@" ); 7 | fi 8 | 9 | RC_FILE="pylintrc" 10 | if [ ! -f $RC_FILE ]; then 11 | RC_FILE="../pylintrc" 12 | fi 13 | 14 | cmd=( 15 | pylint 16 | --rcfile=$RC_FILE 17 | --disable=R 18 | --disable=I 19 | --dummy-variables-rgx="_" 20 | "${files[@]}" 21 | ) 22 | 23 | echo -e "\nRunning pylint:" 24 | echo "${cmd[@]}" 25 | "${cmd[@]}" 26 | 27 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [General] 2 | init-hook='import sys; sys.path.append("tests/")' 3 | 4 | [MESSAGES CONTROL] 5 | # See: http://pylint-messages.wikidot.com/all-codes 6 | # W0142: *args and **kwargs are fine. 7 | # W0511: TODOs in code comments are fine. 8 | # W0702: No exception type(s) specified 9 | # W0703: Catch "Exception" 10 | # C0103: Invalid name 11 | # C0111: Missing docstring 12 | disable=W0142,W0511,W0702,W0703,C0103,C0111 13 | 14 | [REPORTS] 15 | reports=no 16 | include-ids=yes 17 | 18 | [FORMAT] 19 | max-line-length=79 20 | -------------------------------------------------------------------------------- /doc/sources/ovf/user-data: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | password: passw0rd 3 | chpasswd: { expire: False } 4 | ssh_pwauth: True 5 | 6 | ssh_authorized_keys: 7 | - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDVmr/XVGTL+4d1sol7CYS0MAEahKMgXKOhjThPl0VhQ3CLQHoqbtvzliWqaL/UqZHhm+L752PXRee6yhav9mu6/zmEeKUxn0ajMvZEPqNT8Joj6sFCQpJpH8J2YQcFtGKarpPYT1yEyEljSM3N57ElIpVHdB4t1tFnrgftAZMentSWh269L1XABZylhsaBwznjV9ugUCIk4sgIzVQ8fjHlYOROr/rJ4SVKZ6ITaUe0RIeHIwk5IMHdhnmHDX7htjexH7S9ndVBbOmfUvCDVfcBkDzJ/5myYxkeFMV4KCWx3yOUseRa52HmYkGQK7A+9FCTWATmBKdMa2LRNSosis1H ubuntu@ovfdemo 8 | -------------------------------------------------------------------------------- /systemd/cloud-config.target: -------------------------------------------------------------------------------- 1 | # cloud-init normally emits a "cloud-config" upstart event to inform third 2 | # parties that cloud-config is available, which does us no good when we're 3 | # using systemd. cloud-config.target serves as this synchronization point 4 | # instead. Services that would "start on cloud-config" with upstart can 5 | # instead use "After=cloud-config.target" and "Wants=cloud-config.target" 6 | # as appropriate. 7 | 8 | [Unit] 9 | Description=Cloud-config availability 10 | Requires=cloud-init-local.service cloud-init.service 11 | -------------------------------------------------------------------------------- /packages/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | DEB_PYTHON2_MODULE_PACKAGES = cloud-init 4 | INIT_SYSTEM ?= upstart 5 | 6 | binary-install/cloud-init::cloud-init-fixups 7 | 8 | include /usr/share/cdbs/1/rules/debhelper.mk 9 | include /usr/share/cdbs/1/class/python-distutils.mk 10 | 11 | DEB_PYTHON_INSTALL_ARGS_ALL += --init-system=$(INIT_SYSTEM) 12 | 13 | DEB_DH_INSTALL_SOURCEDIR := debian/tmp 14 | 15 | cloud-init-fixups: 16 | install -d $(DEB_DESTDIR)/etc/rsyslog.d 17 | cp tools/21-cloudinit.conf $(DEB_DESTDIR)/etc/rsyslog.d/21-cloudinit.conf 18 | 19 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-gluster.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | # vim: syntax=yaml 3 | # Mounts volfile exported by glusterfsd running on 4 | # "volfile-server-hostname" onto the local mount point '/mnt/data' 5 | # 6 | # In reality, replace 'volfile-server-hostname' with one of your nodes 7 | # running glusterfsd. 8 | # 9 | packages: 10 | - glusterfs-client 11 | 12 | mounts: 13 | - [ 'volfile-server-hostname:6996', /mnt/data, glusterfs, "defaults,nobootwait", "0", "2" ] 14 | 15 | runcmd: 16 | - [ modprobe, fuse ] 17 | - [ mkdir, '-p', /mnt/data ] 18 | - [ mount, '-a' ] 19 | -------------------------------------------------------------------------------- /doc/rtd/topics/availability.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Availability 3 | ============ 4 | 5 | It is currently installed in the `Ubuntu Cloud Images`_ and also in the official `Ubuntu`_ images available on EC2. 6 | 7 | Versions for other systems can be (or have been) created for the following distributions: 8 | 9 | - Ubuntu 10 | - Fedora 11 | - Debian 12 | - RHEL 13 | - CentOS 14 | - *and more...* 15 | 16 | So ask your distribution provider where you can obtain an image with it built-in if one is not already available ☺ 17 | 18 | 19 | .. _Ubuntu Cloud Images: http://cloud-images.ubuntu.com/ 20 | .. _Ubuntu: http://www.ubuntu.com/ 21 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-resolv-conf.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | # 3 | # This is an example file to automatically configure resolv.conf when the 4 | # instance boots for the first time. 5 | # 6 | # Ensure that your yaml is valid and pass this as user-data when starting 7 | # the instance. Also be sure that your cloud.cfg file includes this 8 | # configuration module in the appropirate section. 9 | # 10 | manage-resolv-conf: true 11 | 12 | resolv_conf: 13 | nameservers: ['8.8.4.4', '8.8.8.8'] 14 | searchdomains: 15 | - foo.example.com 16 | - bar.example.com 17 | domain: example.com 18 | options: 19 | rotate: true 20 | timeout: 1 21 | -------------------------------------------------------------------------------- /tools/validate-yaml.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Try to read a YAML file and report any errors. 4 | """ 5 | 6 | import sys 7 | 8 | import yaml 9 | 10 | 11 | if __name__ == "__main__": 12 | bads = 0 13 | for fn in sys.argv[1:]: 14 | sys.stdout.write("%s" % (fn)) 15 | try: 16 | fh = open(fn, 'r') 17 | yaml.safe_load(fh.read()) 18 | fh.close() 19 | sys.stdout.write(" - ok\n") 20 | except Exception, e: 21 | sys.stdout.write(" - bad (%s)\n" % (e)) 22 | bads += 1 23 | if bads > 0: 24 | sys.exit(1) 25 | else: 26 | sys.exit(0) 27 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-vendor-data.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | # 3 | # This explains how to control vendordata via a cloud-config 4 | # 5 | # On select Datasources, vendors have a channel for the consumptions 6 | # of all support user-data types via a special channel called 7 | # vendordata. Users of the end system are given ultimate control. 8 | # 9 | vendor_data: 10 | enabled: True 11 | prefix: /usr/bin/ltrace 12 | 13 | # enabled: whether it is enabled or not 14 | # prefix: the command to run before any vendor scripts. 15 | # Note: this is a fairly weak method of containment. It should 16 | # be used to profile a script, not to prevent its run 17 | -------------------------------------------------------------------------------- /upstart/cloud-log-shutdown.conf: -------------------------------------------------------------------------------- 1 | # log shutdowns and reboots to the console (/dev/console) 2 | # this is useful for correlating logs 3 | start on runlevel PREVLEVEL=2 4 | 5 | task 6 | console output 7 | 8 | script 9 | # runlevel(7) says INIT_HALT will be set to HALT or POWEROFF 10 | date=$(date --utc) 11 | case "$RUNLEVEL:$INIT_HALT" in 12 | 6:*) mode="reboot";; 13 | 0:HALT) mode="halt";; 14 | 0:POWEROFF) mode="poweroff";; 15 | 0:*) mode="shutdown-unknown";; 16 | esac 17 | { read seconds idle < /proc/uptime; } 2>/dev/null || : 18 | echo "$date: shutting down for $mode${seconds:+ [up ${seconds%.*}s]}." 19 | end script 20 | -------------------------------------------------------------------------------- /cloudinit/sources/helpers/__init__.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License version 3, as 5 | # published by the Free Software Foundation. 6 | # 7 | # This program is distributed in the hope that it will be useful, 8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | # GNU General Public License for more details. 11 | # 12 | # You should have received a copy of the GNU General Public License 13 | # along with this program. If not, see . 14 | 15 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-launch-index.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | # vim: syntax=yaml 3 | 4 | # 5 | # This is the configuration syntax that can be provided to have 6 | # a given set of cloud config data show up on a certain launch 7 | # index (and not other launches) by provided a key here which 8 | # will act as a filter on the instances userdata. When 9 | # this key is left out (or non-integer) then the content 10 | # of this file will always be used for all launch-indexes 11 | # (ie the previous behavior). 12 | launch-index: 5 13 | 14 | # Upgrade the instance on first boot 15 | # (ie run apt-get upgrade) 16 | # 17 | # Default: false 18 | # 19 | apt_upgrade: true 20 | 21 | # Other yaml keys below... 22 | # ....... 23 | # ....... 24 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-boot-cmds.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | # boot commands 4 | # default: none 5 | # this is very similar to runcmd, but commands run very early 6 | # in the boot process, only slightly after a 'boothook' would run. 7 | # bootcmd should really only be used for things that could not be 8 | # done later in the boot process. bootcmd is very much like 9 | # boothook, but possibly with more friendly. 10 | # * bootcmd will run on every boot 11 | # * the INSTANCE_ID variable will be set to the current instance id. 12 | # * you can use 'cloud-init-boot-per' command to help only run once 13 | bootcmd: 14 | - echo 192.168.1.130 us.archive.ubuntu.com > /etc/hosts 15 | - [ cloud-init-per, once, mymkfs, mkfs, /dev/vdb ] 16 | -------------------------------------------------------------------------------- /doc/rtd/index.rst: -------------------------------------------------------------------------------- 1 | .. _index: 2 | 3 | ===================== 4 | Documentation 5 | ===================== 6 | 7 | .. rubric:: Everything about cloud-init, a set of **python** scripts and utilities to make your cloud images be all they can be! 8 | 9 | Summary 10 | ----------------- 11 | 12 | `Cloud-init`_ is the *defacto* multi-distribution package that handles early initialization of a cloud instance. 13 | 14 | 15 | ---- 16 | 17 | .. toctree:: 18 | :maxdepth: 2 19 | 20 | topics/capabilities 21 | topics/availability 22 | topics/format 23 | topics/dir_layout 24 | topics/examples 25 | topics/datasources 26 | topics/modules 27 | topics/merging 28 | topics/moreinfo 29 | topics/hacking 30 | 31 | .. _Cloud-init: https://launchpad.net/cloud-init 32 | -------------------------------------------------------------------------------- /tests/data/merge_sources/source7-1.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | users: 4 | - default 5 | - name: foobar 6 | gecos: Foo B. Bar 7 | primary-group: foobar 8 | groups: users 9 | selinux-user: staff_u 10 | expiredate: 2012-09-01 11 | ssh-import-id: foobar 12 | lock-passwd: false 13 | passwd: $6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/ 14 | - name: barfoo 15 | gecos: Bar B. Foo 16 | sudo: ALL=(ALL) NOPASSWD:ALL 17 | groups: users, admin 18 | ssh-import-id: None 19 | lock-passwd: true 20 | ssh-authorized-keys: 21 | - 22 | - 23 | - name: cloudy 24 | gecos: Magic Cloud App Daemon User 25 | inactive: true 26 | system: true 27 | 28 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-yum-repo.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | # vim: syntax=yaml 3 | # 4 | # Add yum repository configuration to the system 5 | # 6 | # The following example adds the file /etc/yum.repos.d/epel_testing.repo 7 | # which can then subsequently be used by yum for later operations. 8 | yum_repos: 9 | # The name of the repository 10 | epel-testing: 11 | # Any repository configuration options 12 | # See: man yum.conf 13 | # 14 | # This one is required! 15 | baseurl: http://download.fedoraproject.org/pub/epel/testing/5/$basearch 16 | enabled: false 17 | failovermethod: priority 18 | gpgcheck: true 19 | gpgkey: file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL 20 | name: Extra Packages for Enterprise Linux 5 - Testing 21 | -------------------------------------------------------------------------------- /doc/examples/seed/README: -------------------------------------------------------------------------------- 1 | This directory is an example of a 'seed' directory. 2 | 3 | 4 | copying these files inside an instance's 5 | /var/lib/cloud/seed/nocloud 6 | or 7 | /var/lib/cloud/seed/nocloud-net 8 | 9 | will cause the 'DataSourceNoCloud' and 'DataSourceNoCloudNet' modules 10 | to enable and read the given data. 11 | 12 | The directory must have both files. 13 | 14 | - user-data: 15 | This is the user data, as would be consumed from ec2's metadata service 16 | see examples in doc/examples. 17 | - meta-data: 18 | This file is yaml formated data similar to what is in the ec2 metadata 19 | service under meta-data/. See the example, or, on an ec2 instance, 20 | run: 21 | python -c 'import boto.utils, yaml; print( 22 | yaml.dump(boto.utils.get_instance_metadata()))' 23 | -------------------------------------------------------------------------------- /tools/read-dependencies: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | 6 | if 'CLOUD_INIT_TOP_D' in os.environ: 7 | topd = os.path.realpath(os.environ.get('CLOUD_INIT_TOP_D')) 8 | else: 9 | topd = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 10 | 11 | for fname in ("setup.py", "requirements.txt"): 12 | if not os.path.isfile(os.path.join(topd, fname)): 13 | sys.stderr.write("Unable to locate '%s' file that should " 14 | "exist in cloud-init root directory." % fname) 15 | sys.exit(1) 16 | 17 | with open(os.path.join(topd, "requirements.txt"), "r") as fp: 18 | for line in fp: 19 | if not line.strip() or line.startswith("#"): 20 | continue 21 | sys.stdout.write(line) 22 | 23 | sys.exit(0) 24 | -------------------------------------------------------------------------------- /tools/read-version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import re 5 | import sys 6 | 7 | if 'CLOUD_INIT_TOP_D' in os.environ: 8 | topd = os.path.realpath(os.environ.get('CLOUD_INIT_TOP_D')) 9 | else: 10 | topd = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 11 | 12 | for fname in ("setup.py", "ChangeLog"): 13 | if not os.path.isfile(os.path.join(topd, fname)): 14 | sys.stderr.write("Unable to locate '%s' file that should " 15 | "exist in cloud-init root directory." % fname) 16 | sys.exit(1) 17 | 18 | vermatch = re.compile(r"^[0-9]+[.][0-9]+[.][0-9]+:$") 19 | 20 | with open(os.path.join(topd, "ChangeLog"), "r") as fp: 21 | for line in fp: 22 | if vermatch.match(line): 23 | sys.stdout.write(line.strip()[:-1] + "\n") 24 | break 25 | 26 | sys.exit(0) 27 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-run-cmds.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | # run commands 4 | # default: none 5 | # runcmd contains a list of either lists or a string 6 | # each item will be executed in order at rc.local like level with 7 | # output to the console 8 | # - if the item is a list, the items will be properly executed as if 9 | # passed to execve(3) (with the first arg as the command). 10 | # - if the item is a string, it will be simply written to the file and 11 | # will be interpreted by 'sh' 12 | # 13 | # Note, that the list has to be proper yaml, so you have to escape 14 | # any characters yaml would eat (':' can be problematic) 15 | runcmd: 16 | - [ ls, -l, / ] 17 | - [ sh, -xc, "echo $(date) ': hello world!'" ] 18 | - [ sh, -c, echo "=========hello world'=========" ] 19 | - ls -l /root 20 | - [ wget, "http://slashdot.org", -O, /tmp/index.html ] 21 | 22 | -------------------------------------------------------------------------------- /doc/rtd/topics/capabilities.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Capabilities 3 | ===================== 4 | 5 | - Setting a default locale 6 | - Setting a instance hostname 7 | - Generating instance ssh private keys 8 | - Adding ssh keys to a users ``.ssh/authorized_keys`` so they can log in 9 | - Setting up ephemeral mount points 10 | 11 | User configurability 12 | -------------------- 13 | 14 | `Cloud-init`_ 's behavior can be configured via user-data. 15 | 16 | User-data can be given by the user at instance launch time. 17 | 18 | This is done via the ``--user-data`` or ``--user-data-file`` argument to ec2-run-instances for example. 19 | 20 | * Check your local clients documentation for how to provide a `user-data` string 21 | or `user-data` file for usage by cloud-init on instance creation. 22 | 23 | 24 | .. _Cloud-init: https://launchpad.net/cloud-init 25 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-landscape.txt: -------------------------------------------------------------------------------- 1 | # Landscape-client configuration 2 | # 3 | # Anything under the top 'landscape: client' entry 4 | # will be basically rendered into a ConfigObj formated file 5 | # under the '[client]' section of /etc/landscape/client.conf 6 | # 7 | # Note: 'tags' should be specified as a comma delimited string 8 | # rather than a list. 9 | # 10 | # You can get example key/values by running 'landscape-config', 11 | # answer question, then look at /etc/landscape/client.config 12 | landscape: 13 | client: 14 | url: "https://landscape.canonical.com/message-system" 15 | ping_url: "http://landscape.canonical.com/ping" 16 | data_path: "/var/lib/landscape/client" 17 | http_proxy: "http://my.proxy.com/foobar" 18 | tags: "server,cloud" 19 | computer_title: footitle 20 | https_proxy: fooproxy 21 | registration_key: fookey 22 | account_name: fooaccount 23 | -------------------------------------------------------------------------------- /tools/make-tarball: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | find_root() { 5 | local topd 6 | if [ -z "${CLOUD_INIT_TOP_D}" ]; then 7 | topd=$(cd "$(dirname "${0}")" && cd .. && pwd) 8 | else 9 | topd=$(cd "${CLOUD_INIT_TOP_D}" && pwd) 10 | fi 11 | [ $? -eq 0 -a -f "${topd}/setup.py" ] || return 12 | ROOT_DIR="$topd" 13 | } 14 | 15 | if ! find_root; then 16 | echo "Unable to locate 'setup.py' file that should" \ 17 | "exist in the cloud-init root directory." 1>&2 18 | exit 1; 19 | fi 20 | 21 | REVNO=$(bzr revno "$ROOT_DIR") 22 | 23 | if [ ! -z "$1" ]; then 24 | ARCHIVE_FN="$1" 25 | else 26 | VERSION=$("$ROOT_DIR/tools/read-version") 27 | ARCHIVE_FN="$PWD/cloud-init-$VERSION~bzr$REVNO.tar.gz" 28 | fi 29 | 30 | bzr export --format=tgz --root="cloud-init-$VERSION~bzr$REVNO" \ 31 | "--revision=${REVNO}" "${ARCHIVE_FN}" "$ROOT_DIR" 32 | 33 | echo "$ARCHIVE_FN" 34 | -------------------------------------------------------------------------------- /doc/examples/kernel-cmdline.txt: -------------------------------------------------------------------------------- 1 | cloud-config can be provided via the kernel command line. 2 | configuration that comes from the kernel command line has higher priority 3 | than configuration in /etc/cloud/cloud.cfg 4 | 5 | The format is: 6 | cc: [end_cc] 7 | 8 | cloud-config will consider any content after 'cc:' to be cloud-config 9 | data. If an 'end_cc' string is present, then it will stop reading there. 10 | otherwise it considers everthing after 'cc:' to be cloud-config content. 11 | 12 | In order to allow carriage returns, you must enter '\\n', literally, 13 | on the command line two backslashes followed by a letter 'n'. 14 | 15 | Here are some examples: 16 | root=/dev/sda1 cc: ssh_import_id: [smoser, kirkland]\\n 17 | root=LABEL=uec-rootfs cc: ssh_import_id: [smoser, bob]\\nruncmd: [ [ ls, -l ], echo hi ] end_cc 18 | cc:ssh_import_id: [smoser] end_cc cc:runcmd: [ [ ls, -l ] ] end_cc root=/dev/sda1 19 | -------------------------------------------------------------------------------- /doc/examples/part-handler.txt: -------------------------------------------------------------------------------- 1 | #part-handler 2 | # vi: syntax=python ts=4 3 | 4 | def list_types(): 5 | # return a list of mime-types that are handled by this module 6 | return(["text/plain", "text/go-cubs-go"]) 7 | 8 | def handle_part(data,ctype,filename,payload): 9 | # data: the cloudinit object 10 | # ctype: '__begin__', '__end__', or the specific mime-type of the part 11 | # filename: the filename for the part, or dynamically generated part if 12 | # no filename is given attribute is present 13 | # payload: the content of the part (empty for begin or end) 14 | if ctype == "__begin__": 15 | print "my handler is beginning" 16 | return 17 | if ctype == "__end__": 18 | print "my handler is ending" 19 | return 20 | 21 | print "==== received ctype=%s filename=%s ====" % (ctype,filename) 22 | print payload 23 | print "==== end ctype=%s filename=%s" % (ctype, filename) 24 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-archive-launch-index.txt: -------------------------------------------------------------------------------- 1 | #cloud-config-archive 2 | 3 | # This is an example of a cloud archive 4 | # format which includes a set of launch indexes 5 | # that will be filtered on (thus only showing 6 | # up in instances with that launch index), this 7 | # is done by adding the 'launch-index' key which 8 | # maps to the integer 'launch-index' that the 9 | # corresponding content should be used with. 10 | # 11 | # It is possible to leave this value out which 12 | # will mean that the content will be applicable 13 | # for all instances 14 | 15 | - type: foo/wark 16 | filename: bar 17 | content: | 18 | This is my payload 19 | hello 20 | launch-index: 1 # I will only be used on launch-index 1 21 | - this is also payload 22 | - | 23 | multi line payload 24 | here 25 | - 26 | type: text/upstart-job 27 | filename: my-upstart.conf 28 | content: | 29 | whats this, yo? 30 | launch-index: 0 # I will only be used on launch-index 0 31 | -------------------------------------------------------------------------------- /tests/data/filter_cloud_multipart_2.email: -------------------------------------------------------------------------------- 1 | From nobody Fri Aug 31 17:43:04 2012 2 | Content-Type: multipart/mixed; boundary="===============1668325974==" 3 | MIME-Version: 1.0 4 | 5 | --===============1668325974== 6 | Content-Type: text/cloud-config; charset="us-ascii" 7 | MIME-Version: 1.0 8 | Content-Transfer-Encoding: 7bit 9 | 10 | 11 | #cloud-config 12 | b: c 13 | launch-index: 2 14 | 15 | 16 | --===============1668325974== 17 | Content-Type: text/plain; charset="us-ascii" 18 | MIME-Version: 1.0 19 | Content-Transfer-Encoding: 7bit 20 | 21 | 22 | #cloud-config-archive 23 | - content: The quick brown fox jumps over the lazy dog 24 | filename: b3.txt 25 | launch-index: 3 26 | type: plain/text 27 | 28 | --===============1668325974== 29 | Content-Type: text/plain; charset="us-ascii" 30 | MIME-Version: 1.0 31 | Content-Transfer-Encoding: 7bit 32 | 33 | 34 | #cloud-config 35 | b: c 36 | launch-index: 2 37 | 38 | 39 | --===============1668325974==-- 40 | -------------------------------------------------------------------------------- /templates/hosts.suse.tmpl: -------------------------------------------------------------------------------- 1 | #* 2 | This file /etc/cloud/templates/hosts.suse.tmpl is only utilized 3 | if enabled in cloud-config. Specifically, in order to enable it 4 | you need to add the following to config: 5 | manage_etc_hosts: True 6 | *# 7 | # Your system has configured 'manage_etc_hosts' as True. 8 | # As a result, if you wish for changes to this file to persist 9 | # then you will need to either 10 | # a.) make changes to the master file in /etc/cloud/templates/hosts.suse.tmpl 11 | # b.) change or remove the value of 'manage_etc_hosts' in 12 | # /etc/cloud/cloud.cfg or cloud-config from user-data 13 | # 14 | # The following lines are desirable for IPv4 capable hosts 15 | 127.0.0.1 localhost 16 | 17 | # The following lines are desirable for IPv6 capable hosts 18 | ::1 localhost ipv6-localhost ipv6-loopback 19 | fe00::0 ipv6-localnet 20 | 21 | ff00::0 ipv6-mcastprefix 22 | ff02::1 ipv6-allnodes 23 | ff02::2 ipv6-allrouters 24 | ff02::3 ipv6-allhosts 25 | -------------------------------------------------------------------------------- /templates/resolv.conf.tmpl: -------------------------------------------------------------------------------- 1 | # 2 | # Your system has been configured with 'manage-resolv-conf' set to true. 3 | # As a result, cloud-init has written this file with configuration data 4 | # that it has been provided. Cloud-init, by default, will write this file 5 | # a single time (PER_ONCE). 6 | # 7 | 8 | #if $varExists('nameservers') 9 | #for $server in $nameservers 10 | nameserver $server 11 | #end for 12 | #end if 13 | #if $varExists('searchdomains') 14 | search #slurp 15 | #for $search in $searchdomains 16 | $search #slurp 17 | #end for 18 | 19 | #end if 20 | #if $varExists('domain') 21 | domain $domain 22 | #end if 23 | #if $varExists('sortlist') 24 | sortlist #slurp 25 | #for $sort in $sortlist 26 | $sort #slurp 27 | #end for 28 | 29 | #end if 30 | #if $varExists('options') or $varExists('flags') 31 | options #slurp 32 | #for $flag in $flags 33 | $flag #slurp 34 | #end for 35 | #for $key, $value in $options.items() 36 | $key:$value #slurp 37 | #end for 38 | 39 | #end if 40 | -------------------------------------------------------------------------------- /cloudinit/version.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2012 Yahoo! Inc. 4 | # 5 | # Author: Joshua Harlow 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License version 3, as 9 | # published by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | from distutils import version as vr 20 | 21 | 22 | def version(): 23 | return vr.StrictVersion("0.7.5") 24 | 25 | 26 | def version_string(): 27 | return str(version()) 28 | -------------------------------------------------------------------------------- /templates/chef_client.rb.tmpl: -------------------------------------------------------------------------------- 1 | #* 2 | This file is only utilized if the module 'cc_chef' is enabled in 3 | cloud-config. Specifically, in order to enable it 4 | you need to add the following to config: 5 | chef: 6 | validation_key: XYZ 7 | validation_cert: XYZ 8 | validation_name: XYZ 9 | server_url: XYZ 10 | *# 11 | log_level :info 12 | log_location "/var/log/chef/client.log" 13 | ssl_verify_mode :verify_none 14 | validation_client_name "$validation_name" 15 | validation_key "/etc/chef/validation.pem" 16 | client_key "/etc/chef/client.pem" 17 | chef_server_url "$server_url" 18 | environment "$environment" 19 | node_name "$node_name" 20 | json_attribs "/etc/chef/firstboot.json" 21 | file_cache_path "/var/cache/chef" 22 | file_backup_path "/var/backups/chef" 23 | pid_file "/var/run/chef/client.pid" 24 | Chef::Log::Formatter.show_time = true 25 | 26 | -------------------------------------------------------------------------------- /cloudinit/__init__.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2012 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # Copyright (C) 2012 Yahoo! Inc. 6 | # 7 | # Author: Scott Moser 8 | # Author: Juerg Haefliger 9 | # Author: Joshua Harlow 10 | # 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License version 3, as 13 | # published by the Free Software Foundation. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | -------------------------------------------------------------------------------- /templates/hosts.redhat.tmpl: -------------------------------------------------------------------------------- 1 | #* 2 | This file /etc/cloud/templates/hosts.redhat.tmpl is only utilized 3 | if enabled in cloud-config. Specifically, in order to enable it 4 | you need to add the following to config: 5 | manage_etc_hosts: True 6 | *# 7 | # Your system has configured 'manage_etc_hosts' as True. 8 | # As a result, if you wish for changes to this file to persist 9 | # then you will need to either 10 | # a.) make changes to the master file in /etc/cloud/templates/hosts.redhat.tmpl 11 | # b.) change or remove the value of 'manage_etc_hosts' in 12 | # /etc/cloud/cloud.cfg or cloud-config from user-data 13 | # 14 | # The following lines are desirable for IPv4 capable hosts 15 | 127.0.0.1 ${fqdn} ${hostname} 16 | 127.0.0.1 localhost.localdomain localhost 17 | 127.0.0.1 localhost4.localdomain4 localhost4 18 | 19 | # The following lines are desirable for IPv6 capable hosts 20 | ::1 ${fqdn} ${hostname} 21 | ::1 localhost.localdomain localhost 22 | ::1 localhost6.localdomain6 localhost6 23 | 24 | -------------------------------------------------------------------------------- /cloudinit/filters/__init__.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2012 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # Copyright (C) 2012 Yahoo! Inc. 6 | # 7 | # Author: Scott Moser 8 | # Author: Juerg Haefliger 9 | # Author: Joshua Harlow 10 | # 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License version 3, as 13 | # published by the Free Software Foundation. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | -------------------------------------------------------------------------------- /tests/data/filter_cloud_multipart.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config-archive 2 | --- 3 | - content: "\n blah: true\n launch-index: 3\n" 4 | type: text/cloud-config 5 | - content: "\n blah: true\n launch-index: 4\n" 6 | type: text/cloud-config 7 | - content: The quick brown fox jumps over the lazy dog 8 | filename: b0.txt 9 | launch-index: 0 10 | type: plain/text 11 | - content: The quick brown fox jumps over the lazy dog 12 | filename: b3.txt 13 | launch-index: 3 14 | type: plain/text 15 | - content: The quick brown fox jumps over the lazy dog 16 | filename: b2.txt 17 | launch-index: 2 18 | type: plain/text 19 | - content: '#!/bin/bash \n echo "stuff"' 20 | filename: b2.txt 21 | launch-index: 2 22 | - content: '#!/bin/bash \n echo "stuff"' 23 | filename: b2.txt 24 | launch-index: 1 25 | - content: '#!/bin/bash \n echo "stuff"' 26 | filename: b2.txt 27 | # Use a string to see if conversion works 28 | launch-index: "1" 29 | ... 30 | 31 | -------------------------------------------------------------------------------- /tests/data/mountinfo_raring_btrfs.txt: -------------------------------------------------------------------------------- 1 | 15 20 0:14 / /sys rw,nosuid,nodev,noexec,relatime - sysfs sysfs rw 2 | 16 20 0:3 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw 3 | 17 20 0:5 / /dev rw,relatime - devtmpfs udev rw,size=865556k,nr_inodes=216389,mode=755 4 | 18 17 0:11 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000 5 | 19 20 0:15 / /run rw,nosuid,relatime - tmpfs tmpfs rw,size=348196k,mode=755 6 | 20 1 0:16 /@ / rw,relatime - btrfs /dev/vda1 rw,compress=lzo,space_cache 7 | 21 15 0:19 / /sys/fs/fuse/connections rw,relatime - fusectl none rw 8 | 22 15 0:6 / /sys/kernel/debug rw,relatime - debugfs none rw 9 | 23 15 0:10 / /sys/kernel/security rw,relatime - securityfs none rw 10 | 24 19 0:20 / /run/lock rw,nosuid,nodev,noexec,relatime - tmpfs none rw,size=5120k 11 | 25 19 0:21 / /run/shm rw,nosuid,nodev,relatime - tmpfs none rw 12 | 26 19 0:22 / /run/user rw,nosuid,nodev,noexec,relatime - tmpfs none rw,size=102400k,mode=755 13 | 27 20 0:16 /@home /home rw,relatime - btrfs /dev/vda1 rw,compress=lzo,space_cache 14 | -------------------------------------------------------------------------------- /templates/hosts.debian.tmpl: -------------------------------------------------------------------------------- 1 | ## This file (/etc/cloud/templates/hosts.tmpl) is only utilized 2 | ## if enabled in cloud-config. Specifically, in order to enable it 3 | ## you need to add the following to config: 4 | ## manage_etc_hosts: True 5 | ## 6 | ## Note, double-hash commented lines will not appear in /etc/hosts 7 | # 8 | # Your system has configured 'manage_etc_hosts' as True. 9 | # As a result, if you wish for changes to this file to persist 10 | # then you will need to either 11 | # a.) make changes to the master file in /etc/cloud/templates/hosts.tmpl 12 | # b.) change or remove the value of 'manage_etc_hosts' in 13 | # /etc/cloud/cloud.cfg or cloud-config from user-data 14 | # 15 | ## The value '$hostname' will be replaced with the local-hostname 16 | 127.0.1.1 $fqdn $hostname 17 | 127.0.0.1 localhost 18 | 19 | # The following lines are desirable for IPv6 capable hosts 20 | ::1 ip6-localhost ip6-loopback 21 | fe00::0 ip6-localnet 22 | ff00::0 ip6-mcastprefix 23 | ff02::1 ip6-allnodes 24 | ff02::2 ip6-allrouters 25 | ff02::3 ip6-allhosts 26 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Pypi requirements for cloud-init to work 2 | 3 | # Used for untemplating any files or strings with parameters. 4 | cheetah 5 | 6 | # This is used for any pretty printing of tabular data. 7 | PrettyTable 8 | 9 | # This one is currently only used by the MAAS datasource. If that 10 | # datasource is removed, this is no longer needed 11 | oauth 12 | 13 | # This one is currently used only by the CloudSigma and SmartOS datasources. 14 | # If these datasources are removed, this is no longer needed 15 | pyserial 16 | 17 | # This is only needed for places where we need to support configs in a manner 18 | # that the built-in config parser is not sufficent (ie 19 | # when we need to preserve comments, or do not have a top-level 20 | # section)... 21 | configobj 22 | 23 | # All new style configurations are in the yaml format 24 | pyyaml 25 | 26 | # The new main entrypoint uses argparse instead of optparse 27 | argparse 28 | 29 | # Requests handles ssl correctly! 30 | requests 31 | 32 | # For patching pieces of cloud-config together 33 | jsonpatch 34 | -------------------------------------------------------------------------------- /packages/debian/control.in: -------------------------------------------------------------------------------- 1 | ## This is a cheetah template 2 | Source: cloud-init 3 | Section: admin 4 | Priority: extra 5 | Maintainer: Scott Moser 6 | Build-Depends: cdbs, 7 | debhelper (>= 5.0.38), 8 | python (>= 2.6.6-3~), 9 | python-nose, 10 | pyflakes, 11 | pylint, 12 | python-setuptools, 13 | python-cheetah, 14 | python-mocker, 15 | python-setuptools 16 | XS-Python-Version: all 17 | Standards-Version: 3.9.3 18 | 19 | Package: cloud-init 20 | Architecture: all 21 | Depends: procps, 22 | python, 23 | #for $r in $requires 24 | ${r}, 25 | #end for 26 | python-software-properties | software-properties-common, 27 | \${misc:Depends}, 28 | Recommends: sudo 29 | XB-Python-Version: \${python:Versions} 30 | Description: Init scripts for cloud instances 31 | Cloud instances need special scripts to run during initialisation 32 | to retrieve and install ssh keys and to let the user run various scripts. 33 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-growpart.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | # 3 | # growpart entry is a dict, if it is not present at all 4 | # in config, then the default is used ({'mode': 'auto', 'devices': ['/']}) 5 | # 6 | # mode: 7 | # values: 8 | # * auto: use any option possible (any available) 9 | # if none are available, do not warn, but debug. 10 | # * growpart: use growpart to grow partitions 11 | # if growpart is not available, this is an error. 12 | # * off, false 13 | # 14 | # devices: 15 | # a list of things to resize. 16 | # items can be filesystem paths or devices (in /dev) 17 | # examples: 18 | # devices: [/, /dev/vdb1] 19 | # 20 | # ignore_growroot_disabled: 21 | # a boolean, default is false. 22 | # if the file /etc/growroot-disabled exists, then cloud-init will not grow 23 | # the root partition. This is to allow a single file to disable both 24 | # cloud-initramfs-growroot and cloud-init's growroot support. 25 | # 26 | # true indicates that /etc/growroot-disabled should be ignored 27 | # 28 | growpart: 29 | mode: auto 30 | devices: ['/'] 31 | ignore_growroot_disabled: false 32 | -------------------------------------------------------------------------------- /tests/data/merge_sources/expected7.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | users: 4 | - default 5 | - name: foobar 6 | gecos: Foo B. Bar 7 | primary-group: foobar 8 | groups: users 9 | selinux-user: staff_u 10 | expiredate: 2012-09-01 11 | ssh-import-id: foobar 12 | lock-passwd: false 13 | passwd: $6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/ 14 | - name: barfoo 15 | gecos: Bar B. Foo 16 | sudo: ALL=(ALL) NOPASSWD:ALL 17 | groups: users, admin 18 | ssh-import-id: None 19 | lock-passwd: true 20 | ssh-authorized-keys: 21 | - 22 | - 23 | - name: cloudy 24 | gecos: Magic Cloud App Daemon User 25 | inactive: true 26 | system: true 27 | - bob 28 | - joe 29 | - sue 30 | - name: foobar_jr 31 | gecos: Foo B. Bar Jr 32 | primary-group: foobar 33 | groups: users 34 | selinux-user: staff_u 35 | expiredate: 2012-09-01 36 | ssh-import-id: foobar 37 | lock-passwd: false 38 | passwd: $6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/ 39 | -------------------------------------------------------------------------------- /tests/unittests/test_distros/test_hostname.py: -------------------------------------------------------------------------------- 1 | from mocker import MockerTestCase 2 | 3 | from cloudinit.distros.parsers import hostname 4 | 5 | 6 | BASE_HOSTNAME = ''' 7 | # My super-duper-hostname 8 | 9 | blahblah 10 | 11 | ''' 12 | BASE_HOSTNAME = BASE_HOSTNAME.strip() 13 | 14 | 15 | class TestHostnameHelper(MockerTestCase): 16 | def test_parse_same(self): 17 | hn = hostname.HostnameConf(BASE_HOSTNAME) 18 | self.assertEquals(str(hn).strip(), BASE_HOSTNAME) 19 | self.assertEquals(hn.hostname, 'blahblah') 20 | 21 | def test_no_adjust_hostname(self): 22 | hn = hostname.HostnameConf(BASE_HOSTNAME) 23 | prev_name = hn.hostname 24 | hn.set_hostname("") 25 | self.assertEquals(hn.hostname, prev_name) 26 | 27 | def test_adjust_hostname(self): 28 | hn = hostname.HostnameConf(BASE_HOSTNAME) 29 | prev_name = hn.hostname 30 | self.assertEquals(prev_name, 'blahblah') 31 | hn.set_hostname("bbbbd") 32 | self.assertEquals(hn.hostname, 'bbbbd') 33 | expected_out = ''' 34 | # My super-duper-hostname 35 | 36 | bbbbd 37 | ''' 38 | self.assertEquals(str(hn).strip(), expected_out.strip()) 39 | -------------------------------------------------------------------------------- /cloudinit/safeyaml.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2012 Canonical Ltd. 4 | # 5 | # Author: Scott Moser 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License version 3, as 9 | # published by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import yaml 20 | 21 | 22 | class _CustomSafeLoader(yaml.SafeLoader): 23 | def construct_python_unicode(self, node): 24 | return self.construct_scalar(node) 25 | 26 | _CustomSafeLoader.add_constructor( 27 | u'tag:yaml.org,2002:python/unicode', 28 | _CustomSafeLoader.construct_python_unicode) 29 | 30 | 31 | def load(blob): 32 | return(yaml.load(blob, Loader=_CustomSafeLoader)) 33 | -------------------------------------------------------------------------------- /cloudinit/distros/fedora.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2012 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # Copyright (C) 2012 Yahoo! Inc. 6 | # 7 | # Author: Scott Moser 8 | # Author: Juerg Haefliger 9 | # Author: Joshua Harlow 10 | # 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License version 3, as 13 | # published by the Free Software Foundation. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | 23 | from cloudinit.distros import rhel 24 | 25 | from cloudinit import log as logging 26 | 27 | LOG = logging.getLogger(__name__) 28 | 29 | 30 | class Distro(rhel.Distro): 31 | pass 32 | -------------------------------------------------------------------------------- /cloudinit/distros/parsers/__init__.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2012 Yahoo! Inc. 4 | # 5 | # Author: Joshua Harlow 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License version 3, as 9 | # published by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | 20 | def chop_comment(text, comment_chars): 21 | comment_locations = [text.find(c) for c in comment_chars] 22 | comment_locations = [c for c in comment_locations if c != -1] 23 | if not comment_locations: 24 | return (text, '') 25 | min_comment = min(comment_locations) 26 | before_comment = text[0:min_comment] 27 | comment = text[min_comment:] 28 | return (before_comment, comment) 29 | -------------------------------------------------------------------------------- /tools/write-ssh-key-fingerprints: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | logger_opts="-p user.info -t ec2" 4 | 5 | # rhels' version of logger_opts does not support long 6 | # for of -s (--stderr), so use short form. 7 | logger_opts="$logger_opts -s" 8 | 9 | # Redirect stderr to stdout 10 | exec 2>&1 11 | 12 | fp_blist=",${1}," 13 | key_blist=",${2}," 14 | { 15 | echo 16 | echo "#############################################################" 17 | echo "-----BEGIN SSH HOST KEY FINGERPRINTS-----" 18 | for f in /etc/ssh/ssh_host_*key.pub; do 19 | [ -f "$f" ] || continue 20 | read ktype line < "$f" 21 | # skip the key if its type is in the blacklist 22 | [ "${fp_blist#*,$ktype,}" = "${fp_blist}" ] || continue 23 | ssh-keygen -l -f "$f" 24 | done 25 | echo "-----END SSH HOST KEY FINGERPRINTS-----" 26 | echo "#############################################################" 27 | 28 | } | logger $logger_opts 29 | 30 | echo "-----BEGIN SSH HOST KEY KEYS-----" 31 | for f in /etc/ssh/ssh_host_*key.pub; do 32 | [ -f "$f" ] || continue 33 | read ktype line < "$f" 34 | # skip the key if its type is in the blacklist 35 | [ "${key_blist#*,$ktype,}" = "${key_blist}" ] || continue 36 | cat $f 37 | done 38 | echo "-----END SSH HOST KEY KEYS-----" 39 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-ca-certs.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | # 3 | # This is an example file to configure an instance's trusted CA certificates 4 | # system-wide for SSL/TLS trust establishment when the instance boots for the 5 | # first time. 6 | # 7 | # Make sure that this file is valid yaml before starting instances. 8 | # It should be passed as user-data when starting the instance. 9 | 10 | ca-certs: 11 | # If present and set to True, the 'remove-defaults' parameter will remove 12 | # all the default trusted CA certificates that are normally shipped with 13 | # Ubuntu. 14 | # This is mainly for paranoid admins - most users will not need this 15 | # functionality. 16 | remove-defaults: true 17 | 18 | # If present, the 'trusted' parameter should contain a certificate (or list 19 | # of certificates) to add to the system as trusted CA certificates. 20 | # Pay close attention to the YAML multiline list syntax. The example shown 21 | # here is for a list of multiline certificates. 22 | trusted: 23 | - | 24 | -----BEGIN CERTIFICATE----- 25 | YOUR-ORGS-TRUSTED-CA-CERT-HERE 26 | -----END CERTIFICATE----- 27 | - | 28 | -----BEGIN CERTIFICATE----- 29 | YOUR-ORGS-TRUSTED-CA-CERT-HERE 30 | -----END CERTIFICATE----- 31 | 32 | -------------------------------------------------------------------------------- /tools/run-pep8: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# -eq 0 ]; then 4 | files=( bin/cloud-init $(find * -name "*.py" -type f) ) 5 | else 6 | files=( "$@" ); 7 | fi 8 | 9 | if [ -f 'hacking.py' ] 10 | then 11 | base=`pwd` 12 | else 13 | base=`pwd`/tools/ 14 | fi 15 | 16 | IGNORE="E501" # Line too long (these are caught by pylint) 17 | 18 | # King Arthur: Be quiet! ... Be Quiet! I Order You to Be Quiet. 19 | IGNORE="$IGNORE,E121" # Continuation line indentation is not a multiple of four 20 | IGNORE="$IGNORE,E123" # Closing bracket does not match indentation of opening bracket's line 21 | IGNORE="$IGNORE,E124" # Closing bracket missing visual indentation 22 | IGNORE="$IGNORE,E125" # Continuation line does not distinguish itself from next logical line 23 | IGNORE="$IGNORE,E126" # Continuation line over-indented for hanging indent 24 | IGNORE="$IGNORE,E127" # Continuation line over-indented for visual indent 25 | IGNORE="$IGNORE,E128" # Continuation line under-indented for visual indent 26 | IGNORE="$IGNORE,E502" # The backslash is redundant between brackets 27 | 28 | cmd=( 29 | ${base}/hacking.py 30 | 31 | --ignore="$IGNORE" 32 | 33 | "${files[@]}" 34 | ) 35 | 36 | echo -e "\nRunning 'cloudinit' pep8:" 37 | echo "${cmd[@]}" 38 | "${cmd[@]}" 39 | -------------------------------------------------------------------------------- /cloudinit/distros/ubuntu.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2012 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # Copyright (C) 2012 Yahoo! Inc. 6 | # 7 | # Author: Scott Moser 8 | # Author: Juerg Haefliger 9 | # Author: Joshua Harlow 10 | # Author: Ben Howard 11 | # 12 | # This program is free software: you can redistribute it and/or modify 13 | # it under the terms of the GNU General Public License version 3, as 14 | # published by the Free Software Foundation. 15 | # 16 | # This program is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program. If not, see . 23 | 24 | from cloudinit.distros import debian 25 | from cloudinit import log as logging 26 | 27 | LOG = logging.getLogger(__name__) 28 | 29 | 30 | class Distro(debian.Distro): 31 | pass 32 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-write-files.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | # vim: syntax=yaml 3 | # 4 | # This is the configuration syntax that the write_files module 5 | # will know how to understand. encoding can be given b64 or gzip or (gz+b64). 6 | # The content will be decoded accordingly and then written to the path that is 7 | # provided. 8 | # 9 | # Note: Content strings here are truncated for example purposes. 10 | write_files: 11 | - encoding: b64 12 | content: CiMgVGhpcyBmaWxlIGNvbnRyb2xzIHRoZSBzdGF0ZSBvZiBTRUxpbnV4... 13 | owner: root:root 14 | path: /etc/sysconfig/selinux 15 | permissions: '0644' 16 | - content: | 17 | # My new /etc/sysconfig/samba file 18 | 19 | SMBDOPTIONS="-D" 20 | path: /etc/sysconfig/samba 21 | - content: !!binary | 22 | f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAwARAAAAAAABAAAAAAAAAAJAVAAAAAAAAAAAAAEAAOAAI 23 | AEAAHgAdAAYAAAAFAAAAQAAAAAAAAABAAEAAAAAAAEAAQAAAAAAAwAEAAAAAAADAAQAAAAAAAAgA 24 | AAAAAAAAAwAAAAQAAAAAAgAAAAAAAAACQAAAAAAAAAJAAAAAAAAcAAAAAAAAABwAAAAAAAAAAQAA 25 | .... 26 | path: /bin/arch 27 | permissions: '0555' 28 | - encoding: gzip 29 | content: !!binary | 30 | H4sIAIDb/U8C/1NW1E/KzNMvzuBKTc7IV8hIzcnJVyjPL8pJ4QIA6N+MVxsAAAA= 31 | path: /usr/bin/hello 32 | permissions: '0755' 33 | 34 | -------------------------------------------------------------------------------- /doc/sources/ovf/ovf-env.xml.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | 14 | ESX Server 15 | 3.0.1 16 | VMware, Inc. 17 | en_US 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /packages/debian/copyright: -------------------------------------------------------------------------------- 1 | Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135 2 | Name: cloud-init 3 | Maintainer: Scott Moser 4 | Source: https://launchpad.net/cloud-init 5 | 6 | This package was debianized by Soren Hansen on 7 | Thu, 04 Sep 2008 12:49:15 +0200 as ec2-init. It was later renamed to 8 | cloud-utils by Scott Moser 9 | 10 | Upstream Author: Scott Moser 11 | Soren Hansen 12 | Chuck Short 13 | 14 | Copyright: 2010, Canonical Ltd. 15 | License: GPL-3 16 | This program is free software: you can redistribute it and/or modify 17 | it under the terms of the GNU General Public License version 3, as 18 | published by the Free Software Foundation. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | You should have received a copy of the GNU General Public License 26 | along with this program. If not, see . 27 | 28 | The complete text of the GPL version 3 can be seen in 29 | /usr/share/common-licenses/GPL-3. 30 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-power-state.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | ## poweroff or reboot system after finished 4 | # default: none 5 | # 6 | # power_state can be used to make the system shutdown, reboot or 7 | # halt after boot is finished. This same thing can be acheived by 8 | # user-data scripts or by runcmd by simply invoking 'shutdown'. 9 | # 10 | # Doing it this way ensures that cloud-init is entirely finished with 11 | # modules that would be executed, and avoids any error/log messages 12 | # that may go to the console as a result of system services like 13 | # syslog being taken down while cloud-init is running. 14 | # 15 | # If you delay '+5' (5 minutes) and have a timeout of 16 | # 120 (2 minutes), then the max time until shutdown will be 7 minutes. 17 | # cloud-init will invoke 'shutdown +5' after the process finishes, or 18 | # when 'timeout' seconds have elapsed. 19 | # 20 | # delay: form accepted by shutdown. default is 'now'. other format 21 | # accepted is +m (m in minutes) 22 | # mode: required. must be one of 'poweroff', 'halt', 'reboot' 23 | # message: provided as the message argument to 'shutdown'. default is none. 24 | # timeout: the amount of time to give the cloud-init process to finish 25 | # before executing shutdown. 26 | # 27 | power_state: 28 | delay: "+30" 29 | mode: poweroff 30 | message: Bye Bye 31 | timeout: 30 32 | -------------------------------------------------------------------------------- /cloudinit/type_utils.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2012 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # Copyright (C) 2012 Yahoo! Inc. 6 | # 7 | # Author: Scott Moser 8 | # Author: Juerg Haefliger 9 | # Author: Joshua Harlow 10 | # 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License version 3, as 13 | # published by the Free Software Foundation. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | # 23 | # pylint: disable=C0302 24 | 25 | import types 26 | 27 | 28 | def obj_name(obj): 29 | if isinstance(obj, (types.TypeType, 30 | types.ModuleType, 31 | types.FunctionType, 32 | types.LambdaType)): 33 | return str(obj.__name__) 34 | return obj_name(obj.__class__) 35 | -------------------------------------------------------------------------------- /tools/motd-hook: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # 92-ec2-upgrade-available - update-motd script 4 | # 5 | # Copyright (C) 2010 Canonical Ltd. 6 | # 7 | # Authors: Scott Moser 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation, version 3 of the License. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | 22 | # Determining if updates are available is possibly slow. 23 | # a cronjob runs occasioinally and updates a file with information 24 | # on the latest available release (if newer than current) 25 | 26 | BUILD_FILE=/var/lib/cloud/data/available.build 27 | 28 | [ -s "${BUILD_FILE}" ] || exit 0 29 | 30 | read suite build_name name serial other < "${BUILD_FILE}" 31 | 32 | cat < 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License version 3, as 9 | # published by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | # Ensure this is aliased to a name not 'distros' 20 | # since the module attribute 'distros' 21 | # is a list of distros that are supported, not a sub-module 22 | from cloudinit import distros as ds 23 | 24 | from cloudinit.settings import PER_INSTANCE 25 | 26 | frequency = PER_INSTANCE 27 | 28 | 29 | def handle(name, cfg, cloud, _log, _args): 30 | (users, groups) = ds.normalize_users_groups(cfg, cloud.distro) 31 | for (name, members) in groups.items(): 32 | cloud.distro.create_group(name, members) 33 | for (user, config) in users.items(): 34 | cloud.distro.create_user(user, **config) 35 | -------------------------------------------------------------------------------- /tests/unittests/test_pathprefix2dict.py: -------------------------------------------------------------------------------- 1 | from cloudinit import util 2 | 3 | from mocker import MockerTestCase 4 | from tests.unittests.helpers import populate_dir 5 | 6 | 7 | class TestPathPrefix2Dict(MockerTestCase): 8 | 9 | def setUp(self): 10 | self.tmp = self.makeDir() 11 | 12 | def test_required_only(self): 13 | dirdata = {'f1': 'f1content', 'f2': 'f2content'} 14 | populate_dir(self.tmp, dirdata) 15 | 16 | ret = util.pathprefix2dict(self.tmp, required=['f1', 'f2']) 17 | self.assertEqual(dirdata, ret) 18 | 19 | def test_required_missing(self): 20 | dirdata = {'f1': 'f1content'} 21 | populate_dir(self.tmp, dirdata) 22 | kwargs = {'required': ['f1', 'f2']} 23 | self.assertRaises(ValueError, util.pathprefix2dict, self.tmp, **kwargs) 24 | 25 | def test_no_required_and_optional(self): 26 | dirdata = {'f1': 'f1c', 'f2': 'f2c'} 27 | populate_dir(self.tmp, dirdata) 28 | 29 | ret = util.pathprefix2dict(self.tmp, required=None, 30 | optional=['f1', 'f2']) 31 | self.assertEqual(dirdata, ret) 32 | 33 | def test_required_and_optional(self): 34 | dirdata = {'f1': 'f1c', 'f2': 'f2c'} 35 | populate_dir(self.tmp, dirdata) 36 | 37 | ret = util.pathprefix2dict(self.tmp, required=['f1'], optional=['f2']) 38 | self.assertEqual(dirdata, ret) 39 | 40 | # vi: ts=4 expandtab 41 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-add-apt-repos.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | # Add apt repositories 4 | # 5 | # Default: auto select based on cloud metadata 6 | # in ec2, the default is .archive.ubuntu.com 7 | # apt_mirror: 8 | # use the provided mirror 9 | # apt_mirror_search: 10 | # search the list for the first mirror. 11 | # this is currently very limited, only verifying that 12 | # the mirror is dns resolvable or an IP address 13 | # 14 | # if neither apt_mirror nor apt_mirror search is set (the default) 15 | # then use the mirror provided by the DataSource found. 16 | # In EC2, that means using .ec2.archive.ubuntu.com 17 | # 18 | # if no mirror is provided by the DataSource, and 'apt_mirror_search_dns' is 19 | # true, then search for dns names '-mirror' in each of 20 | # - fqdn of this host per cloud metadata 21 | # - localdomain 22 | # - no domain (which would search domains listed in /etc/resolv.conf) 23 | # If there is a dns entry for -mirror, then it is assumed that there 24 | # is a distro mirror at http://-mirror./ 25 | # 26 | # That gives the cloud provider the opportunity to set mirrors of a distro 27 | # up and expose them only by creating dns entries. 28 | # 29 | # if none of that is found, then the default distro mirror is used 30 | apt_mirror: http://us.archive.ubuntu.com/ubuntu/ 31 | apt_mirror_search: 32 | - http://local-mirror.mydomain 33 | - http://archive.ubuntu.com 34 | apt_mirror_search_dns: False 35 | -------------------------------------------------------------------------------- /cloudinit/config/cc_disable_ec2_metadata.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2009-2010 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from cloudinit import util 22 | 23 | from cloudinit.settings import PER_ALWAYS 24 | 25 | frequency = PER_ALWAYS 26 | 27 | REJECT_CMD = ['route', 'add', '-host', '169.254.169.254', 'reject'] 28 | 29 | 30 | def handle(name, cfg, _cloud, log, _args): 31 | disabled = util.get_cfg_option_bool(cfg, "disable_ec2_metadata", False) 32 | if disabled: 33 | util.subp(REJECT_CMD, capture=False) 34 | else: 35 | log.debug(("Skipping module named %s," 36 | " disabling the ec2 route not enabled"), name) 37 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-mount-points.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | # set up mount points 4 | # 'mounts' contains a list of lists 5 | # the inner list are entries for an /etc/fstab line 6 | # ie : [ fs_spec, fs_file, fs_vfstype, fs_mntops, fs-freq, fs_passno ] 7 | # 8 | # default: 9 | # mounts: 10 | # - [ ephemeral0, /mnt ] 11 | # - [ swap, none, swap, sw, 0, 0 ] 12 | # 13 | # in order to remove a previously listed mount (ie, one from defaults) 14 | # list only the fs_spec. For example, to override the default, of 15 | # mounting swap: 16 | # - [ swap ] 17 | # or 18 | # - [ swap, null ] 19 | # 20 | # - if a device does not exist at the time, an entry will still be 21 | # written to /etc/fstab. 22 | # - '/dev' can be ommitted for device names that begin with: xvd, sd, hd, vd 23 | # - if an entry does not have all 6 fields, they will be filled in 24 | # with values from 'mount_default_fields' below. 25 | # 26 | # Note, that you should set 'nobootwait' (see man fstab) for volumes that may 27 | # not be attached at instance boot (or reboot) 28 | # 29 | mounts: 30 | - [ ephemeral0, /mnt, auto, "defaults,noexec" ] 31 | - [ sdc, /opt/data ] 32 | - [ xvdh, /opt/data, "auto", "defaults,nobootwait", "0", "0" ] 33 | - [ dd, /dev/zero ] 34 | 35 | # mount_default_fields 36 | # These values are used to fill in any entries in 'mounts' that are not 37 | # complete. This must be an array, and must have 7 fields. 38 | mount_default_fields: [ None, None, "auto", "defaults,nobootwait", "0", "2" ] 39 | 40 | -------------------------------------------------------------------------------- /cloudinit/config/cc_timezone.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2009-2010 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from cloudinit import util 22 | 23 | from cloudinit.settings import PER_INSTANCE 24 | 25 | frequency = PER_INSTANCE 26 | 27 | 28 | def handle(name, cfg, cloud, log, args): 29 | if len(args) != 0: 30 | timezone = args[0] 31 | else: 32 | timezone = util.get_cfg_option_str(cfg, "timezone", False) 33 | 34 | if not timezone: 35 | log.debug("Skipping module named %s, no 'timezone' specified", name) 36 | return 37 | 38 | # Let the distro handle settings its timezone 39 | cloud.distro.set_timezone(timezone) 40 | -------------------------------------------------------------------------------- /tests/configs/sample1.yaml: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | #apt_update: false 3 | #apt_upgrade: true 4 | packages: [ bzr, pastebinit, ubuntu-dev-tools, ccache, bzr-builddeb, vim-nox, git-core, lftp ] 5 | 6 | #apt_sources: 7 | # - source: ppa:smoser/ppa 8 | 9 | #disable_root: False 10 | 11 | # mounts: 12 | # - [ ephemeral0, /mnt ] 13 | # - [ swap, none, swap, sw, 0, 0 ] 14 | 15 | ssh_import_id: [smoser ] 16 | 17 | #!/bin/sh 18 | 19 | output: {all: '| tee -a /var/log/cloud-init-output.log'} 20 | 21 | sm_misc: 22 | - &user_setup | 23 | set -x; exec > ~/user_setup.log 2>&1 24 | echo "starting at $(date -R)" 25 | echo "set -o vi" >> ~/.bashrc 26 | cat >> ~/.profile <<"EOF" 27 | export EDITOR=vi 28 | export DEB_BUILD_OPTIONS=parallel=4 29 | export PATH=/usr/lib/ccache:$PATH 30 | EOF 31 | 32 | mkdir ~/bin 33 | chmod 755 ~/bin 34 | cat > ~/bin/mdebuild <<"EOF" 35 | #!/bin/sh 36 | exec debuild --prepend-path /usr/lib/ccache "$@" 37 | EOF 38 | chmod 755 ~/bin/* 39 | 40 | #byobu-launcher-install 41 | byobu-ctrl-a screen 2>&1 || : 42 | 43 | echo "pinging 8.8.8.8" 44 | ping -c 4 8.8.8.8 45 | 46 | runcmd: 47 | - [ sudo, -Hu, ubuntu, sh, -c, '[ -e /var/log/cloud-init.log ] || exit 0; grep "cloud-init.*running" /var/log/cloud-init.log > ~/runcmd.log' ] 48 | - [ sudo, -Hu, ubuntu, sh, -c, 'read up sleep < /proc/uptime; echo $(date): runcmd up at $up | tee -a ~/runcmd.log' ] 49 | - [ sudo, -Hu, ubuntu, sh, -c, *user_setup ] 50 | 51 | 52 | byobu_by_default: user 53 | -------------------------------------------------------------------------------- /cloudinit/config/cc_runcmd.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2009-2010 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import os 22 | 23 | from cloudinit import util 24 | 25 | 26 | def handle(name, cfg, cloud, log, _args): 27 | if "runcmd" not in cfg: 28 | log.debug(("Skipping module named %s," 29 | " no 'runcmd' key in configuration"), name) 30 | return 31 | 32 | out_fn = os.path.join(cloud.get_ipath('scripts'), "runcmd") 33 | cmd = cfg["runcmd"] 34 | try: 35 | content = util.shellify(cmd) 36 | util.write_file(out_fn, content, 0700) 37 | except: 38 | util.logexc(log, "Failed to shellify %s into file %s", cmd, out_fn) 39 | -------------------------------------------------------------------------------- /cloudinit/config/cc_locale.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2011 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from cloudinit import util 22 | 23 | 24 | def handle(name, cfg, cloud, log, args): 25 | if len(args) != 0: 26 | locale = args[0] 27 | else: 28 | locale = util.get_cfg_option_str(cfg, "locale", cloud.get_locale()) 29 | 30 | if not locale: 31 | log.debug(("Skipping module named %s, " 32 | "no 'locale' configuration found"), name) 33 | return 34 | 35 | log.debug("Setting locale to %s", locale) 36 | locale_cfgfile = util.get_cfg_option_str(cfg, "locale_configfile") 37 | cloud.distro.apply_locale(locale, locale_cfgfile) 38 | -------------------------------------------------------------------------------- /tests/unittests/test_distros/test_hosts.py: -------------------------------------------------------------------------------- 1 | from mocker import MockerTestCase 2 | 3 | from cloudinit.distros.parsers import hosts 4 | 5 | 6 | BASE_ETC = ''' 7 | # Example 8 | 127.0.0.1 localhost 9 | 192.168.1.10 foo.mydomain.org foo 10 | 192.168.1.10 bar.mydomain.org bar 11 | 146.82.138.7 master.debian.org master 12 | 209.237.226.90 www.opensource.org 13 | ''' 14 | BASE_ETC = BASE_ETC.strip() 15 | 16 | 17 | class TestHostsHelper(MockerTestCase): 18 | def test_parse(self): 19 | eh = hosts.HostsConf(BASE_ETC) 20 | self.assertEquals(eh.get_entry('127.0.0.1'), [['localhost']]) 21 | self.assertEquals(eh.get_entry('192.168.1.10'), 22 | [['foo.mydomain.org', 'foo'], 23 | ['bar.mydomain.org', 'bar']]) 24 | eh = str(eh) 25 | self.assertTrue(eh.startswith('# Example')) 26 | 27 | def test_add(self): 28 | eh = hosts.HostsConf(BASE_ETC) 29 | eh.add_entry('127.0.0.0', 'blah') 30 | self.assertEquals(eh.get_entry('127.0.0.0'), [['blah']]) 31 | eh.add_entry('127.0.0.3', 'blah', 'blah2', 'blah3') 32 | self.assertEquals(eh.get_entry('127.0.0.3'), 33 | [['blah', 'blah2', 'blah3']]) 34 | 35 | def test_del(self): 36 | eh = hosts.HostsConf(BASE_ETC) 37 | eh.add_entry('127.0.0.0', 'blah') 38 | self.assertEquals(eh.get_entry('127.0.0.0'), [['blah']]) 39 | 40 | eh.del_entries('127.0.0.0') 41 | self.assertEquals(eh.get_entry('127.0.0.0'), []) 42 | -------------------------------------------------------------------------------- /doc/examples/seed/meta-data: -------------------------------------------------------------------------------- 1 | # this is yaml formated data 2 | # it is expected to be roughly what you would get from running the following 3 | # on an ec2 instance: 4 | # python -c 'import boto.utils, yaml; print(yaml.dump(boto.utils.get_instance_metadata()))' 5 | ami-id: ami-fd4aa494 6 | ami-launch-index: '0' 7 | ami-manifest-path: ubuntu-images-us/ubuntu-lucid-10.04-amd64-server-20100427.1.manifest.xml 8 | block-device-mapping: {ami: sda1, ephemeral0: sdb, ephemeral1: sdc, root: /dev/sda1} 9 | hostname: domU-12-31-38-07-19-44.compute-1.internal 10 | instance-action: none 11 | instance-id: i-87018aed 12 | instance-type: m1.large 13 | kernel-id: aki-c8b258a1 14 | local-hostname: domU-12-31-38-07-19-44.compute-1.internal 15 | local-ipv4: 10.223.26.178 16 | placement: {availability-zone: us-east-1d} 17 | public-hostname: ec2-184-72-174-120.compute-1.amazonaws.com 18 | public-ipv4: 184.72.174.120 19 | public-keys: 20 | ec2-keypair.us-east-1: [ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCD9dlT00vOUC8Ttq6YH8RzUCVqPQl6HaSfWSTKYnZiVCpTBj1CaRZPLRLmkSB9Nziy4aRJa/LZMbBHXytQKnB1psvNknqC2UNlrXXMk+Vx5S4vg21MXYYimK4uZEY0Qz29QUiTyNsx18jpAaF4ocUpTpRhxPEBCcSCDmMbc27MU2XuTbasM2NjW/w0bBF3ZFhdH68dZICXdTxS2jUrtrCnc1D/QXVZ5kQO3jsmSyJg8E0nE+6Onpx2YRoVRSwjpGzVZ+BlXPnN5xBREBG8XxzhNFHJbek+RgK5TfL+k4yD4XhnVZuZu53cBAFhj+xPKhtisSd+YmaEq+Jt9uS0Ekd5 21 | ec2-keypair.us-east-1, ''] 22 | reservation-id: r-e2225889 23 | security-groups: default 24 | 25 | # of the fields above: 26 | # required: 27 | # instance-id 28 | # suggested: 29 | # local-hostname 30 | # public-keys 31 | -------------------------------------------------------------------------------- /cloudinit/templater.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2012 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # Copyright (C) 2012 Yahoo! Inc. 6 | # 7 | # Author: Scott Moser 8 | # Author: Juerg Haefliger 9 | # Author: Joshua Harlow 10 | # 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License version 3, as 13 | # published by the Free Software Foundation. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | 23 | from Cheetah.Template import Template 24 | 25 | from cloudinit import util 26 | 27 | 28 | def render_from_file(fn, params): 29 | return render_string(util.load_file(fn), params) 30 | 31 | 32 | def render_to_file(fn, outfn, params, mode=0644): 33 | contents = render_from_file(fn, params) 34 | util.write_file(outfn, contents, mode=mode) 35 | 36 | 37 | def render_string(content, params): 38 | if not params: 39 | params = {} 40 | return Template(content, searchList=[params]).respond() 41 | -------------------------------------------------------------------------------- /cloudinit/config/cc_scripts_per_once.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2011 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import os 22 | 23 | from cloudinit import util 24 | 25 | from cloudinit.settings import PER_ONCE 26 | 27 | frequency = PER_ONCE 28 | 29 | SCRIPT_SUBDIR = 'per-once' 30 | 31 | 32 | def handle(name, _cfg, cloud, log, _args): 33 | # Comes from the following: 34 | # https://forums.aws.amazon.com/thread.jspa?threadID=96918 35 | runparts_path = os.path.join(cloud.get_cpath(), 'scripts', SCRIPT_SUBDIR) 36 | try: 37 | util.runparts(runparts_path) 38 | except: 39 | log.warn("Failed to run module %s (%s in %s)", 40 | name, SCRIPT_SUBDIR, runparts_path) 41 | raise 42 | -------------------------------------------------------------------------------- /cloudinit/config/cc_scripts_per_boot.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2011 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import os 22 | 23 | from cloudinit import util 24 | 25 | from cloudinit.settings import PER_ALWAYS 26 | 27 | frequency = PER_ALWAYS 28 | 29 | SCRIPT_SUBDIR = 'per-boot' 30 | 31 | 32 | def handle(name, _cfg, cloud, log, _args): 33 | # Comes from the following: 34 | # https://forums.aws.amazon.com/thread.jspa?threadID=96918 35 | runparts_path = os.path.join(cloud.get_cpath(), 'scripts', SCRIPT_SUBDIR) 36 | try: 37 | util.runparts(runparts_path) 38 | except: 39 | log.warn("Failed to run module %s (%s in %s)", 40 | name, SCRIPT_SUBDIR, runparts_path) 41 | raise 42 | -------------------------------------------------------------------------------- /cloudinit/config/cc_scripts_per_instance.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2011 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import os 22 | 23 | from cloudinit import util 24 | 25 | from cloudinit.settings import PER_INSTANCE 26 | 27 | frequency = PER_INSTANCE 28 | 29 | SCRIPT_SUBDIR = 'per-instance' 30 | 31 | 32 | def handle(name, _cfg, cloud, log, _args): 33 | # Comes from the following: 34 | # https://forums.aws.amazon.com/thread.jspa?threadID=96918 35 | runparts_path = os.path.join(cloud.get_cpath(), 'scripts', SCRIPT_SUBDIR) 36 | try: 37 | util.runparts(runparts_path) 38 | except: 39 | log.warn("Failed to run module %s (%s in %s)", 40 | name, SCRIPT_SUBDIR, runparts_path) 41 | raise 42 | -------------------------------------------------------------------------------- /templates/sources.list.debian.tmpl: -------------------------------------------------------------------------------- 1 | \## Note, this file is written by cloud-init on first boot of an instance 2 | \## modifications made here will not survive a re-bundle. 3 | \## if you wish to make changes you can: 4 | \## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg 5 | \## or do the same in user-data 6 | \## b.) add sources in /etc/apt/sources.list.d 7 | \## c.) make changes to template file /etc/cloud/templates/sources.list.debian.tmpl 8 | \### 9 | 10 | # See http://www.debian.org/releases/stable/i386/release-notes/ch-upgrading.html 11 | # for how to upgrade to newer versions of the distribution. 12 | deb $mirror $codename main contrib non-free 13 | deb-src $mirror $codename main contrib non-free 14 | 15 | \## Major bug fix updates produced after the final release of the 16 | \## distribution. 17 | deb $security $codename/updates main contrib non-free 18 | deb-src $security $codename/updates main contrib non-free 19 | deb $mirror $codename-updates main contrib non-free 20 | deb-src $mirror $codename-updates main contrib non-free 21 | 22 | \## Uncomment the following two lines to add software from the 'backports' 23 | \## repository. 24 | \## N.B. software from this repository may not have been tested as 25 | \## extensively as that contained in the main release, although it includes 26 | \## newer versions of some applications which may provide useful features. 27 | # deb http://backports.debian.org/debian-backports $codename-backports main contrib non-free 28 | # deb-src http://backports.debian.org/debian-backports $codename-backports main contrib non-free 29 | -------------------------------------------------------------------------------- /cloudinit/config/cc_scripts_user.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2011 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import os 22 | 23 | from cloudinit import util 24 | 25 | from cloudinit.settings import PER_INSTANCE 26 | 27 | frequency = PER_INSTANCE 28 | 29 | SCRIPT_SUBDIR = 'scripts' 30 | 31 | 32 | def handle(name, _cfg, cloud, log, _args): 33 | # This is written to by the user data handlers 34 | # Ie, any custom shell scripts that come down 35 | # go here... 36 | runparts_path = os.path.join(cloud.get_ipath_cur(), SCRIPT_SUBDIR) 37 | try: 38 | util.runparts(runparts_path) 39 | except: 40 | log.warn("Failed to run module %s (%s in %s)", 41 | name, SCRIPT_SUBDIR, runparts_path) 42 | raise 43 | -------------------------------------------------------------------------------- /cloudinit/config/cc_scripts_vendor.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2014 Canonical Ltd. 4 | # 5 | # Author: Ben Howard 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License version 3, as 9 | # published by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import os 20 | 21 | from cloudinit import util 22 | 23 | from cloudinit.settings import PER_INSTANCE 24 | 25 | frequency = PER_INSTANCE 26 | 27 | SCRIPT_SUBDIR = 'vendor' 28 | 29 | 30 | def handle(name, cfg, cloud, log, _args): 31 | # This is written to by the vendor data handlers 32 | # any vendor data shell scripts get placed in runparts_path 33 | runparts_path = os.path.join(cloud.get_ipath_cur(), 'scripts', 34 | SCRIPT_SUBDIR) 35 | 36 | prefix = util.get_cfg_by_path(cfg, ('vendor_data', 'prefix'), []) 37 | 38 | try: 39 | util.runparts(runparts_path, exe_prefix=prefix) 40 | except: 41 | log.warn("Failed to run module %s (%s in %s)", 42 | name, SCRIPT_SUBDIR, runparts_path) 43 | raise 44 | -------------------------------------------------------------------------------- /cloudinit/config/cc_set_hostname.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2011 Canonical Ltd. 4 | # Copyright (C) 2012, 2013 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from cloudinit import util 22 | 23 | 24 | def handle(name, cfg, cloud, log, _args): 25 | if util.get_cfg_option_bool(cfg, "preserve_hostname", False): 26 | log.debug(("Configuration option 'preserve_hostname' is set," 27 | " not setting the hostname in module %s"), name) 28 | return 29 | 30 | (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud) 31 | try: 32 | log.debug("Setting the hostname to %s (%s)", fqdn, hostname) 33 | cloud.distro.set_hostname(hostname, fqdn) 34 | except Exception: 35 | util.logexc(log, "Failed to set the hostname to %s (%s)", fqdn, 36 | hostname) 37 | raise 38 | -------------------------------------------------------------------------------- /doc/examples/part-handler-v2.txt: -------------------------------------------------------------------------------- 1 | #part-handler 2 | # vi: syntax=python ts=4 3 | # this is an example of a version 2 part handler. 4 | # the differences between the initial part-handler version 5 | # and v2 is: 6 | # * handle_part receives a 5th argument, 'frequency' 7 | # frequency will be either 'always' or 'per-instance' 8 | # * handler_version must be set 9 | # 10 | # A handler declaring version 2 will be called on all instance boots, with a 11 | # different 'frequency' argument. 12 | 13 | handler_version = 2 14 | 15 | def list_types(): 16 | # return a list of mime-types that are handled by this module 17 | return(["text/plain", "text/go-cubs-go"]) 18 | 19 | def handle_part(data,ctype,filename,payload,frequency): 20 | # data: the cloudinit object 21 | # ctype: '__begin__', '__end__', or the specific mime-type of the part 22 | # filename: the filename for the part, or dynamically generated part if 23 | # no filename is given attribute is present 24 | # payload: the content of the part (empty for begin or end) 25 | # frequency: the frequency that this cloud-init run is running for 26 | # this is either 'per-instance' or 'always'. 'per-instance' 27 | # will be invoked only on the first boot. 'always' will 28 | # will be called on subsequent boots. 29 | if ctype == "__begin__": 30 | print "my handler is beginning, frequency=%s" % frequency 31 | return 32 | if ctype == "__end__": 33 | print "my handler is ending, frequency=%s" % frequency 34 | return 35 | 36 | print "==== received ctype=%s filename=%s ====" % (ctype,filename) 37 | print payload 38 | print "==== end ctype=%s filename=%s" % (ctype, filename) 39 | -------------------------------------------------------------------------------- /doc/sources/cloudsigma/README.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | CloudSigma Datasource 3 | ===================== 4 | 5 | This datasource finds metadata and user-data from the `CloudSigma`_ cloud platform. 6 | Data transfer occurs through a virtual serial port of the `CloudSigma`_'s VM and the 7 | presence of network adapter is **NOT** a requirement, 8 | 9 | See `server context`_ in the public documentation for more information. 10 | 11 | 12 | Setting a hostname 13 | ~~~~~~~~~~~~~~~~~~ 14 | 15 | By default the name of the server will be applied as a hostname on the first boot. 16 | 17 | 18 | Providing user-data 19 | ~~~~~~~~~~~~~~~~~~~ 20 | 21 | You can provide user-data to the VM using the dedicated `meta field`_ in the `server context`_ 22 | ``cloudinit-user-data``. By default *cloud-config* format is expected there and the ``#cloud-config`` 23 | header could be omitted. However since this is a raw-text field you could provide any of the valid 24 | `config formats`_. 25 | 26 | You have the option to encode your user-data using Base64. In order to do that you have to add the 27 | ``cloudinit-user-data`` field to the ``base64_fields``. The latter is a comma-separated field with 28 | all the meta fields whit base64 encoded values. 29 | 30 | If your user-data does not need an internet connection you can create a 31 | `meta field`_ in the `server context`_ ``cloudinit-dsmode`` and set "local" as value. 32 | If this field does not exist the default value is "net". 33 | 34 | 35 | .. _CloudSigma: http://cloudsigma.com/ 36 | .. _server context: http://cloudsigma-docs.readthedocs.org/en/latest/server_context.html 37 | .. _meta field: http://cloudsigma-docs.readthedocs.org/en/latest/meta.html 38 | .. _config formats: http://cloudinit.readthedocs.org/en/latest/topics/format.html 39 | -------------------------------------------------------------------------------- /doc/sources/ovf/ovfdemo.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA1Zq/11Rky/uHdbKJewmEtDABGoSjIFyjoY04T5dFYUNwi0B6 3 | Km7b85Ylqmi/1KmR4Zvi++dj10XnusoWr/Zruv85hHilMZ9GozL2RD6jU/CaI+rB 4 | QkKSaR/CdmEHBbRimq6T2E9chMhJY0jNzeexJSKVR3QeLdbRZ64H7QGTHp7Ulodu 5 | vS9VwAWcpYbGgcM541fboFAiJOLICM1UPH4x5WDkTq/6yeElSmeiE2lHtESHhyMJ 6 | OSDB3YZ5hw1+4bY3sR+0vZ3VQWzpn1Lwg1X3AZA8yf+ZsmMZHhTFeCglsd8jlLHk 7 | Wudh5mJBkCuwPvRQk1gE5gSnTGti0TUqLIrNRwIDAQABAoIBAGZMrdIXxgp3VWHF 8 | 9tfpMBgH4Y9stJ98HpXxh2V+4ih53v2iDKAj5c1cPH/HmQ/lgktVmDjikct43El2 9 | HbV6RBATyd0q1prUWEUy1ATNJvW9hmTrOlFchrg4EK8XOwC9angAYig3oeyp65PU 10 | O1SAwTMyw+GruARmHHYWQA9/MJF5yexrjBw00w7hnCsqjezU5YIYsXwgcz0Zw+Ix 11 | fDJcZFXF9X3Al7H3ZILW3PpfhcVl7WzkL47TIX4oB/ab2kltaTE90SZMXKVcLvTI 12 | 6To2xJAnMUyasRfcGmvE8m0SqWqp66POAUDF2I8qu78inKH2u0rNtLQjyx5btF5K 13 | A39bPnkCgYEA8Joba3QFrbd0zPTP/DawRtTXzdIQcNjj4XEefxBN3Cw7MlCsfgDc 14 | xiAR703zqQ/IDkF00XrU5w7rmDga3Pv66JRzFDwvRVtGb6QV+lg7Ypd/6NI1G5AS 15 | 0Qzneer2JytEpHoTqGH/vWcXzJRH2BfaPK/vEF4qhAXBqouz2DXn3EUCgYEA40ZU 16 | eDc4MmHOSuqoggSEDJ5NITgPbdkwOta0BmnBZ36M5vgqN8EfAZISKocLNlERDrRG 17 | MpBlQCulq3rpU7WYkx8hGE21f1YBo+vKkffI56ptO2lAp5iLflkSOypdiVN6OELW 18 | 5SzkViohDnxKc6eshVycnNoxh6MqE6ugWSd6ahsCgYEA6t0kQwIgwPDCfYfEt2kT 19 | LjF675lNHzs5R8pKgLKDrpcmufjySJXC7UxE9ZrcbX3QRcozpIEI7vwrko3B+1Gm 20 | Hf87TtdpNYTh/vznz1btsVI+NCFuYheDprm4A9UOsDGWchAQvF/dayAFpVhhwVmX 21 | WYJMFWg2jGWqJTb2Oep1CRkCgYEAqzdkk1wmPe5o1w+I+sokIM1xFcGB/iNMrkbp 22 | QJuTVECGLcpvI6mdjjVY8ijiTX0s+ILfD2CwpnM7T8A83w9DbjJZYFHKla9ZdQBB 23 | j024UK6Xs9ZLGvdUv06i6We1J6t3u8K+2c/EBRWf6aXBAPgkhCOM6K2H+sL1A/Sb 24 | zA5trlkCgYArqJCk999mXQuMjNv6UTwzB0iYDjAFNgJdFmPMXlogD51r0HlGeCgD 25 | OEyup4FdIvX1ZYOCkKyieSngmPmY/P4lZBgQbM23FMp+oUkA+FlVW+WNVoXagUrh 26 | abatKtbZ+WZHHmgSoC8sAo5KnxM9O0R6fWlpoIhJTVoihkZYdmnpMg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /HACKING.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Hacking on cloud-init 3 | ===================== 4 | 5 | To get changes into cloud-init, the process to follow is: 6 | 7 | * If you have not already, be sure to sign the CCA: 8 | 9 | - `Canonical Contributor Agreement`_ 10 | 11 | * Get your changes into a local bzr branch. 12 | Initialize a repo, and checkout trunk (init repo is to share bzr info across multiple checkouts, its different than git): 13 | 14 | - ``bzr init-repo cloud-init`` 15 | - ``bzr branch lp:cloud-init trunk.dist`` 16 | - ``bzr branch trunk.dist my-topic-branch`` 17 | 18 | * Commit your changes (note, you can make multiple commits, fixes, more commits.): 19 | 20 | - ``bzr commit`` 21 | 22 | * Check pylint and pep8 and test, and address any issues: 23 | 24 | - ``make test pylint pep8`` 25 | 26 | * Push to launchpad to a personal branch: 27 | 28 | - ``bzr push lp:~/cloud-init/`` 29 | 30 | * Propose that for a merge into lp:cloud-init via web browser. 31 | 32 | - Open the branch in `Launchpad`_ 33 | 34 | - It will typically be at ``https://code.launchpad.net///`` 35 | - ie. https://code.launchpad.net/~smoser/cloud-init/mybranch 36 | 37 | * Click 'Propose for merging' 38 | * Select 'lp:cloud-init' as the target branch 39 | 40 | Then, someone on cloud-init-dev (currently `Scott Moser`_ and `Joshua Harlow`_) will 41 | review your changes and follow up in the merge request. 42 | 43 | Feel free to ping and/or join #cloud-init on freenode (irc) if you have any questions. 44 | 45 | .. _Launchpad: https://launchpad.net 46 | .. _Canonical Contributor Agreement: http://www.canonical.com/contributors 47 | .. _Scott Moser: https://launchpad.net/~smoser 48 | .. _Joshua Harlow: https://launchpad.net/~harlowja 49 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CWD=$(shell pwd) 2 | PY_FILES=$(shell find cloudinit bin tests tools -name "*.py" -type f ) 3 | PY_FILES+="bin/cloud-init" 4 | 5 | YAML_FILES=$(shell find cloudinit bin tests tools -name "*.yaml" -type f ) 6 | YAML_FILES+=$(shell find doc/examples -name "cloud-config*.txt" -type f ) 7 | 8 | CHANGELOG_VERSION=$(shell $(CWD)/tools/read-version) 9 | CODE_VERSION=$(shell python -c "from cloudinit import version; print version.version_string()") 10 | 11 | PIP_INSTALL := pip install 12 | 13 | ifeq ($(distro),) 14 | distro = redhat 15 | endif 16 | 17 | all: test check_version 18 | 19 | pep8: 20 | @$(CWD)/tools/run-pep8 $(PY_FILES) 21 | 22 | pylint: 23 | @$(CWD)/tools/run-pylint $(PY_FILES) 24 | 25 | pyflakes: 26 | pyflakes $(PY_FILES) 27 | 28 | pip-requirements: 29 | @echo "Installing cloud-init dependencies..." 30 | $(PIP_INSTALL) -r "$@.txt" -q 31 | 32 | pip-test-requirements: 33 | @echo "Installing cloud-init test dependencies..." 34 | $(PIP_INSTALL) -r "$@.txt" -q 35 | 36 | test: clean_pyc 37 | @echo "Running tests..." 38 | @nosetests $(noseopts) tests/ 39 | 40 | check_version: 41 | @if [ "$(CHANGELOG_VERSION)" != "$(CODE_VERSION)" ]; then \ 42 | echo "Error: ChangeLog version $(CHANGELOG_VERSION)" \ 43 | "not equal to code version $(CODE_VERSION)"; exit 2; \ 44 | else true; fi 45 | 46 | clean_pyc: 47 | @find . -type f -name "*.pyc" -delete 48 | 49 | 2to3: 50 | 2to3 $(PY_FILES) 51 | 52 | clean: clean_pyc 53 | rm -rf /var/log/cloud-init.log /var/lib/cloud/ 54 | 55 | yaml: 56 | @$(CWD)/tools/validate-yaml.py $(YAML_FILES) 57 | 58 | rpm: 59 | ./packages/brpm --distro $(distro) 60 | 61 | deb: 62 | ./packages/bddeb 63 | 64 | .PHONY: test pylint pyflakes 2to3 clean pep8 rpm deb yaml check_version 65 | .PHONY: pip-test-requirements pip-requirements clean_pyc 66 | -------------------------------------------------------------------------------- /cloudinit/mergers/m_str.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vi: ts=4 expandtab 3 | # 4 | # Copyright (C) 2012 Yahoo! Inc. 5 | # 6 | # Author: Joshua Harlow 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License version 3, as 10 | # published by the Free Software Foundation. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | 21 | class Merger(object): 22 | def __init__(self, _merger, opts): 23 | self._append = 'append' in opts 24 | 25 | def __str__(self): 26 | return 'StringMerger: (append=%s)' % (self._append) 27 | 28 | # On encountering a unicode object to merge value with 29 | # we will for now just proxy into the string method to let it handle it. 30 | def _on_unicode(self, value, merge_with): 31 | return self._on_str(value, merge_with) 32 | 33 | # On encountering a string object to merge with we will 34 | # perform the following action, if appending we will 35 | # merge them together, otherwise we will just return value. 36 | def _on_str(self, value, merge_with): 37 | if not isinstance(value, (basestring)): 38 | return merge_with 39 | if not self._append: 40 | return merge_with 41 | if isinstance(value, unicode): 42 | return value + unicode(merge_with) 43 | else: 44 | return value + str(merge_with) 45 | -------------------------------------------------------------------------------- /cloudinit/config/cc_update_hostname.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2011 Canonical Ltd. 4 | # Copyright (C) 2012, 2013 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import os 22 | 23 | from cloudinit.settings import PER_ALWAYS 24 | from cloudinit import util 25 | 26 | frequency = PER_ALWAYS 27 | 28 | 29 | def handle(name, cfg, cloud, log, _args): 30 | if util.get_cfg_option_bool(cfg, "preserve_hostname", False): 31 | log.debug(("Configuration option 'preserve_hostname' is set," 32 | " not updating the hostname in module %s"), name) 33 | return 34 | 35 | (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud) 36 | try: 37 | prev_fn = os.path.join(cloud.get_cpath('data'), "previous-hostname") 38 | log.debug("Updating hostname to %s (%s)", fqdn, hostname) 39 | cloud.distro.update_hostname(hostname, fqdn, prev_fn) 40 | except Exception: 41 | util.logexc(log, "Failed to update the hostname to %s (%s)", fqdn, 42 | hostname) 43 | raise 44 | -------------------------------------------------------------------------------- /cloudinit/config/cc_emit_upstart.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2009-2011 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import os 22 | 23 | from cloudinit.settings import PER_ALWAYS 24 | from cloudinit import util 25 | 26 | frequency = PER_ALWAYS 27 | 28 | distros = ['ubuntu', 'debian'] 29 | 30 | 31 | def handle(name, _cfg, cloud, log, args): 32 | event_names = args 33 | if not event_names: 34 | # Default to the 'cloud-config' 35 | # event for backwards compat. 36 | event_names = ['cloud-config'] 37 | if not os.path.isfile("/sbin/initctl"): 38 | log.debug(("Skipping module named %s," 39 | " no /sbin/initctl located"), name) 40 | return 41 | cfgpath = cloud.paths.get_ipath_cur("cloud_config") 42 | for n in event_names: 43 | cmd = ['initctl', 'emit', str(n), 'CLOUD_CFG=%s' % cfgpath] 44 | try: 45 | util.subp(cmd) 46 | except Exception as e: 47 | # TODO(harlowja), use log exception from utils?? 48 | log.warn("Emission of upstart event %s failed due to: %s", n, e) 49 | -------------------------------------------------------------------------------- /tests/data/mountinfo_precise_ext4.txt: -------------------------------------------------------------------------------- 1 | 15 20 0:14 / /sys rw,nosuid,nodev,noexec,relatime - sysfs sysfs rw 2 | 16 20 0:3 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw 3 | 17 20 0:5 / /dev rw,relatime - devtmpfs udev rw,size=16422216k,nr_inodes=4105554,mode=755 4 | 18 17 0:11 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000 5 | 19 20 0:15 / /run rw,nosuid,relatime - tmpfs tmpfs rw,size=6572812k,mode=755 6 | 20 1 252:1 / / rw,relatime - ext4 /dev/mapper/vg0-root rw,errors=remount-ro,data=ordered 7 | 21 15 0:16 / /sys/fs/cgroup rw,relatime - tmpfs cgroup rw,mode=755 8 | 22 15 0:17 / /sys/fs/fuse/connections rw,relatime - fusectl none rw 9 | 23 15 0:6 / /sys/kernel/debug rw,relatime - debugfs none rw 10 | 25 15 0:10 / /sys/kernel/security rw,relatime - securityfs none rw 11 | 26 19 0:19 / /run/lock rw,nosuid,nodev,noexec,relatime - tmpfs none rw,size=5120k 12 | 27 19 0:20 / /run/shm rw,nosuid,nodev,relatime - tmpfs none rw 13 | 28 19 0:21 / /run/user rw,nosuid,nodev,noexec,relatime - tmpfs none rw,size=102400k,mode=755 14 | 24 21 0:18 / /sys/fs/cgroup/cpuset rw,relatime - cgroup cgroup rw,cpuset 15 | 29 21 0:22 / /sys/fs/cgroup/cpu rw,relatime - cgroup cgroup rw,cpu 16 | 30 21 0:23 / /sys/fs/cgroup/cpuacct rw,relatime - cgroup cgroup rw,cpuacct 17 | 31 21 0:24 / /sys/fs/cgroup/memory rw,relatime - cgroup cgroup rw,memory 18 | 32 21 0:25 / /sys/fs/cgroup/devices rw,relatime - cgroup cgroup rw,devices 19 | 33 21 0:26 / /sys/fs/cgroup/freezer rw,relatime - cgroup cgroup rw,freezer 20 | 34 21 0:27 / /sys/fs/cgroup/blkio rw,relatime - cgroup cgroup rw,blkio 21 | 35 21 0:28 / /sys/fs/cgroup/perf_event rw,relatime - cgroup cgroup rw,perf_event 22 | 36 20 9:0 / /boot rw,relatime - ext4 /dev/md0 rw,data=ordered 23 | 37 16 0:29 / /proc/sys/fs/binfmt_misc rw,nosuid,nodev,noexec,relatime - binfmt_misc binfmt_misc rw 24 | 39 28 0:30 / /run/user/foobar/gvfs rw,nosuid,nodev,relatime - fuse.gvfsd-fuse gvfsd-fuse rw,user_id=1000,group_id=1000 25 | -------------------------------------------------------------------------------- /tools/cloud-init-per: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DATA_PRE="/var/lib/cloud/sem/bootper" 4 | INST_PRE="/var/lib/cloud/instance/sem/bootper" 5 | 6 | Usage() { 7 | cat <&2; } 22 | fail() { [ $# -eq 0 ] || error "$@"; exit 1; } 23 | 24 | # support the old 'cloud-init-run-module freq name "execute" cmd arg1' 25 | # if < 3 arguments, it will fail below on usage. 26 | if [ "${0##*/}" = "cloud-init-run-module" ]; then 27 | if [ $# -le 2 -o "$3" = "execute" ]; then 28 | error "Warning: ${0##*/} is deprecated. Please use cloud-init-per." 29 | freq=$1; name=$2; 30 | [ $# -le 2 ] || shift 3; 31 | set -- "$freq" "$name" "$@" 32 | else 33 | fail "legacy cloud-init-run-module only supported with module 'execute'" 34 | fi 35 | fi 36 | 37 | [ "$1" = "-h" -o "$1" = "--help" ] && { Usage ; exit 0; } 38 | [ $# -ge 3 ] || { Usage 1>&2; exit 1; } 39 | freq=$1 40 | name=$2 41 | shift 2; 42 | 43 | [ "${name#*/}" = "${name}" ] || fail "name cannot contain a /" 44 | [ "$(id -u)" = "0" ] || fail "must be root" 45 | 46 | case "$freq" in 47 | once|always) sem="${DATA_PRE}.$name.$freq";; 48 | instance) sem="${INST_PRE}.$name.$freq";; 49 | *) Usage 1>&2; fail "invalid frequency: $freq";; 50 | esac 51 | 52 | [ -d "${sem%/*}" ] || mkdir -p "${sem%/*}" || 53 | fail "failed to make directory for ${sem}" 54 | 55 | [ "$freq" != "always" -a -e "$sem" ] && exit 0 56 | "$@" 57 | ret=$? 58 | printf "%s\t%s\n" "$ret" "$(date +%s)" > "$sem" || 59 | fail "failed to write to $sem" 60 | exit $ret 61 | -------------------------------------------------------------------------------- /cloudinit/config/cc_bootcmd.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2009-2011 Canonical Ltd. 4 | # Copyright (C) 2012, 2013 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import os 22 | 23 | from cloudinit.settings import PER_ALWAYS 24 | from cloudinit import util 25 | 26 | frequency = PER_ALWAYS 27 | 28 | 29 | def handle(name, cfg, cloud, log, _args): 30 | 31 | if "bootcmd" not in cfg: 32 | log.debug(("Skipping module named %s," 33 | " no 'bootcmd' key in configuration"), name) 34 | return 35 | 36 | with util.ExtendedTemporaryFile(suffix=".sh") as tmpf: 37 | try: 38 | content = util.shellify(cfg["bootcmd"]) 39 | tmpf.write(content) 40 | tmpf.flush() 41 | except: 42 | util.logexc(log, "Failed to shellify bootcmd") 43 | raise 44 | 45 | try: 46 | env = os.environ.copy() 47 | iid = cloud.get_instance_id() 48 | if iid: 49 | env['INSTANCE_ID'] = str(iid) 50 | cmd = ['/bin/sh', tmpf.name] 51 | util.subp(cmd, env=env, capture=False) 52 | except: 53 | util.logexc(log, "Failed to run bootcmd module %s", name) 54 | raise 55 | -------------------------------------------------------------------------------- /cloudinit/patcher.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2012 Canonical Ltd. 4 | # Copyright (C) 2012 Yahoo! Inc. 5 | # 6 | # Author: Scott Moser 7 | # Author: Joshua Harlow 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import imp 22 | import logging 23 | import sys 24 | 25 | # Default fallback format 26 | FALL_FORMAT = ('FALLBACK: %(asctime)s - %(filename)s[%(levelname)s]: ' + 27 | '%(message)s') 28 | 29 | 30 | class QuietStreamHandler(logging.StreamHandler): 31 | def handleError(self, record): 32 | pass 33 | 34 | 35 | def _patch_logging(): 36 | # Replace 'handleError' with one that will be more 37 | # tolerant of errors in that it can avoid 38 | # re-notifying on exceptions and when errors 39 | # do occur, it can at least try to write to 40 | # sys.stderr using a fallback logger 41 | fallback_handler = QuietStreamHandler(sys.stderr) 42 | fallback_handler.setFormatter(logging.Formatter(FALL_FORMAT)) 43 | 44 | def handleError(self, record): # pylint: disable=W0613 45 | try: 46 | fallback_handler.handle(record) 47 | fallback_handler.flush() 48 | except IOError: 49 | pass 50 | setattr(logging.Handler, 'handleError', handleError) 51 | 52 | 53 | def patch(): 54 | imp.acquire_lock() 55 | try: 56 | _patch_logging() 57 | finally: 58 | imp.release_lock() 59 | -------------------------------------------------------------------------------- /cloudinit/sources/DataSourceNone.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2012 Yahoo! Inc. 4 | # 5 | # Author: Joshua Harlow 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License version 3, as 9 | # published by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | from cloudinit import log as logging 20 | from cloudinit import sources 21 | 22 | LOG = logging.getLogger(__name__) 23 | 24 | 25 | class DataSourceNone(sources.DataSource): 26 | def __init__(self, sys_cfg, distro, paths, ud_proc=None): 27 | sources.DataSource.__init__(self, sys_cfg, distro, paths, ud_proc) 28 | self.metadata = {} 29 | self.userdata_raw = '' 30 | 31 | def get_data(self): 32 | # If the datasource config has any provided 'fallback' 33 | # userdata or metadata, use it... 34 | if 'userdata_raw' in self.ds_cfg: 35 | self.userdata_raw = self.ds_cfg['userdata_raw'] 36 | if 'metadata' in self.ds_cfg: 37 | self.metadata = self.ds_cfg['metadata'] 38 | return True 39 | 40 | def get_instance_id(self): 41 | return 'iid-datasource-none' 42 | 43 | @property 44 | def is_disconnected(self): 45 | return True 46 | 47 | 48 | # Used to match classes to dependencies 49 | datasources = [ 50 | (DataSourceNone, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)), 51 | (DataSourceNone, []), 52 | ] 53 | 54 | 55 | # Return a list of data sources that match this set of dependencies 56 | def get_datasource_list(depends): 57 | return sources.list_from_depends(depends, datasources) 58 | -------------------------------------------------------------------------------- /tests/unittests/test_runs/test_merge_run.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from tests.unittests import helpers 4 | 5 | from cloudinit.settings import (PER_INSTANCE) 6 | from cloudinit import stages 7 | from cloudinit import util 8 | 9 | 10 | class TestMergeRun(helpers.FilesystemMockingTestCase): 11 | def _patchIn(self, root): 12 | self.restore() 13 | self.patchOS(root) 14 | self.patchUtils(root) 15 | 16 | def test_none_ds(self): 17 | new_root = self.makeDir() 18 | self.replicateTestRoot('simple_ubuntu', new_root) 19 | cfg = { 20 | 'datasource_list': ['None'], 21 | 'cloud_init_modules': ['write-files'], 22 | } 23 | ud = self.readResource('user_data.1.txt') 24 | cloud_cfg = util.yaml_dumps(cfg) 25 | util.ensure_dir(os.path.join(new_root, 'etc', 'cloud')) 26 | util.write_file(os.path.join(new_root, 'etc', 27 | 'cloud', 'cloud.cfg'), cloud_cfg) 28 | self._patchIn(new_root) 29 | 30 | # Now start verifying whats created 31 | initer = stages.Init() 32 | initer.read_cfg() 33 | initer.initialize() 34 | initer.fetch() 35 | initer.datasource.userdata_raw = ud 36 | _iid = initer.instancify() 37 | initer.update() 38 | initer.cloudify().run('consume_data', 39 | initer.consume_data, 40 | args=[PER_INSTANCE], 41 | freq=PER_INSTANCE) 42 | mirrors = initer.distro.get_option('package_mirrors') 43 | self.assertEquals(1, len(mirrors)) 44 | mirror = mirrors[0] 45 | self.assertEquals(mirror['arches'], ['i386', 'amd64', 'blah']) 46 | mods = stages.Modules(initer) 47 | (which_ran, failures) = mods.run_section('cloud_init_modules') 48 | self.assertTrue(len(failures) == 0) 49 | self.assertTrue(os.path.exists('/etc/blah.ini')) 50 | self.assertIn('write-files', which_ran) 51 | contents = util.load_file('/etc/blah.ini') 52 | self.assertEquals(contents, 'blah') 53 | -------------------------------------------------------------------------------- /doc/sources/kernel-cmdline.txt: -------------------------------------------------------------------------------- 1 | In order to allow an ephemeral, or otherwise pristine image to 2 | receive some configuration, cloud-init will read a url directed by 3 | the kernel command line and proceed as if its data had previously existed. 4 | 5 | This allows for configuring a meta-data service, or some other data. 6 | 7 | Note, that usage of the kernel command line is somewhat of a last resort, 8 | as it requires knowing in advance the correct command line or modifying 9 | the boot loader to append data. 10 | 11 | For example, when 'cloud-init start' runs, it will check to 12 | see if if one of 'cloud-config-url' or 'url' appear in key/value fashion 13 | in the kernel command line as in: 14 | root=/dev/sda ro url=http://foo.bar.zee/abcde 15 | 16 | Cloud-init will then read the contents of the given url. 17 | If the content starts with '#cloud-config', it will store 18 | that data to the local filesystem in a static filename 19 | '/etc/cloud/cloud.cfg.d/91_kernel_cmdline_url.cfg', and consider it as 20 | part of the config from that point forward. 21 | 22 | If that file exists already, it will not be overwritten, and the url parameters 23 | completely ignored. 24 | 25 | Then, when the DataSource runs, it will find that config already available. 26 | 27 | So, in able to configure the MAAS DataSource by controlling the kernel 28 | command line from outside the image, you can append: 29 | url=http://your.url.here/abcdefg 30 | or 31 | cloud-config-url=http://your.url.here/abcdefg 32 | 33 | Then, have the following content at that url: 34 | #cloud-config 35 | datasource: 36 | MAAS: 37 | metadata_url: http://mass-host.localdomain/source 38 | consumer_key: Xh234sdkljf 39 | token_key: kjfhgb3n 40 | token_secret: 24uysdfx1w4 41 | 42 | Notes: 43 | * Because 'url=' is so very generic, in order to avoid false positives, 44 | cloud-init requires the content to start with '#cloud-config' in order 45 | for it to be considered. 46 | * The url= is un-authed http GET, and contains credentials 47 | It could be set up to be randomly generated and also check source 48 | address in order to be more secure 49 | -------------------------------------------------------------------------------- /doc/var-lib-cloud.txt: -------------------------------------------------------------------------------- 1 | /var/lib/cloud has the following structure: 2 | - scripts/ 3 | per-instance/ 4 | per-boot/ 5 | per-once/ 6 | 7 | files in these directories will be run by 'run-parts' once per 8 | instance, once per boot, and once per *ever*. 9 | 10 | - seed/ 11 | / 12 | sys-user-data 13 | user-data 14 | meta-data 15 | 16 | The 'seed/' directory allows you to seed a specific datasource 17 | For example, to seed the 'nocloud' datasource you would need to 18 | populate 19 | seed/nocloud/user-data 20 | seed/nocloud/meta-data 21 | 22 | - instance -> instances/i-abcde 23 | This is a symlink to the current instance/ directory 24 | created/updated on boot 25 | - instances/ 26 | i-abcdefgh/ 27 | scripts/ # all scripts in scripts are per-instance 28 | sem/ 29 | config-puppet 30 | config-ssh 31 | set-hostname 32 | cloud-config.txt 33 | user-data.txt 34 | user-data.txt.i 35 | obj.pkl 36 | handlers/ 37 | data/ # just a per-instance data location to be used 38 | boot-finished 39 | # this file indicates when "boot" is finished 40 | # it is created by the 'final_message' cloud-config 41 | datasource # a file containing the class and string of datasource 42 | 43 | - sem/ 44 | scripts.once 45 | These are the cloud-specific semaphores. The only thing that 46 | would go here are files to mark that a "per-once" script 47 | has run. 48 | 49 | - handlers/ 50 | "persistent" handlers (not per-instance). Same as handlers 51 | from user-data, just will be cross-instance id 52 | 53 | - data/ 54 | this is a persistent data location. cloud-init won't really 55 | use it, but something else (a handler or script could) 56 | previous-datasource 57 | previous-instance-id 58 | previous-hostname 59 | 60 | to clear out the current instance's data as if to force a "new run" on reboot 61 | do: 62 | ( cd /var/lib/cloud/instance && sudo rm -Rf * ) 63 | 64 | -------------------------------------------------------------------------------- /cloudinit/config/cc_keys_to_console.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2011 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import os 22 | 23 | from cloudinit.settings import PER_INSTANCE 24 | from cloudinit import util 25 | 26 | frequency = PER_INSTANCE 27 | 28 | # This is a tool that cloud init provides 29 | HELPER_TOOL = '/usr/lib/cloud-init/write-ssh-key-fingerprints' 30 | 31 | 32 | def handle(name, cfg, _cloud, log, _args): 33 | if not os.path.exists(HELPER_TOOL): 34 | log.warn(("Unable to activate module %s," 35 | " helper tool not found at %s"), name, HELPER_TOOL) 36 | return 37 | 38 | fp_blacklist = util.get_cfg_option_list(cfg, 39 | "ssh_fp_console_blacklist", []) 40 | key_blacklist = util.get_cfg_option_list(cfg, 41 | "ssh_key_console_blacklist", 42 | ["ssh-dss"]) 43 | 44 | try: 45 | cmd = [HELPER_TOOL] 46 | cmd.append(','.join(fp_blacklist)) 47 | cmd.append(','.join(key_blacklist)) 48 | (stdout, _stderr) = util.subp(cmd) 49 | util.multi_log("%s\n" % (stdout.strip()), 50 | stderr=False, console=True) 51 | except: 52 | log.warn("Writing keys to the system console failed!") 53 | raise 54 | -------------------------------------------------------------------------------- /tools/make-mime.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import argparse 4 | import sys 5 | 6 | from email.mime.multipart import MIMEMultipart 7 | from email.mime.text import MIMEText 8 | 9 | KNOWN_CONTENT_TYPES = [ 10 | 'text/x-include-once-url', 11 | 'text/x-include-url', 12 | 'text/cloud-config-archive', 13 | 'text/upstart-job', 14 | 'text/cloud-config', 15 | 'text/part-handler', 16 | 'text/x-shellscript', 17 | 'text/cloud-boothook', 18 | ] 19 | 20 | 21 | def file_content_type(text): 22 | try: 23 | filename, content_type = text.split(":", 1) 24 | return (open(filename, 'r'), filename, content_type.strip()) 25 | except: 26 | raise argparse.ArgumentError("Invalid value for %r" % (text)) 27 | 28 | 29 | def main(): 30 | parser = argparse.ArgumentParser() 31 | parser.add_argument("-a", "--attach", 32 | dest="files", 33 | type=file_content_type, 34 | action='append', 35 | default=[], 36 | required=True, 37 | metavar=":", 38 | help="attach the given file in the specified " 39 | "content type") 40 | args = parser.parse_args() 41 | sub_messages = [] 42 | for i, (fh, filename, format_type) in enumerate(args.files): 43 | contents = fh.read() 44 | sub_message = MIMEText(contents, format_type, sys.getdefaultencoding()) 45 | sub_message.add_header('Content-Disposition', 46 | 'attachment; filename="%s"' % (filename)) 47 | content_type = sub_message.get_content_type().lower() 48 | if content_type not in KNOWN_CONTENT_TYPES: 49 | sys.stderr.write(("WARNING: content type %r for attachment %s " 50 | "may be incorrect!\n") % (content_type, i + 1)) 51 | sub_messages.append(sub_message) 52 | combined_message = MIMEMultipart() 53 | for msg in sub_messages: 54 | combined_message.attach(msg) 55 | print(combined_message) 56 | return 0 57 | 58 | 59 | if __name__ == '__main__': 60 | sys.exit(main()) 61 | -------------------------------------------------------------------------------- /config/cloud.cfg.d/05_logging.cfg: -------------------------------------------------------------------------------- 1 | ## This yaml formated config file handles setting 2 | ## logger information. The values that are necessary to be set 3 | ## are seen at the bottom. The top '_log' are only used to remove 4 | ## redundency in a syslog and fallback-to-file case. 5 | ## 6 | ## The 'log_cfgs' entry defines a list of logger configs 7 | ## Each entry in the list is tried, and the first one that 8 | ## works is used. If a log_cfg list entry is an array, it will 9 | ## be joined with '\n'. 10 | _log: 11 | - &log_base | 12 | [loggers] 13 | keys=root,cloudinit 14 | 15 | [handlers] 16 | keys=consoleHandler,cloudLogHandler 17 | 18 | [formatters] 19 | keys=simpleFormatter,arg0Formatter 20 | 21 | [logger_root] 22 | level=DEBUG 23 | handlers=consoleHandler,cloudLogHandler 24 | 25 | [logger_cloudinit] 26 | level=DEBUG 27 | qualname=cloudinit 28 | handlers= 29 | propagate=1 30 | 31 | [handler_consoleHandler] 32 | class=StreamHandler 33 | level=WARNING 34 | formatter=arg0Formatter 35 | args=(sys.stderr,) 36 | 37 | [formatter_arg0Formatter] 38 | format=%(asctime)s - %(filename)s[%(levelname)s]: %(message)s 39 | 40 | [formatter_simpleFormatter] 41 | format=[CLOUDINIT] %(filename)s[%(levelname)s]: %(message)s 42 | - &log_file | 43 | [handler_cloudLogHandler] 44 | class=FileHandler 45 | level=DEBUG 46 | formatter=arg0Formatter 47 | args=('/var/log/cloud-init.log',) 48 | - &log_syslog | 49 | [handler_cloudLogHandler] 50 | class=handlers.SysLogHandler 51 | level=DEBUG 52 | formatter=simpleFormatter 53 | args=("/dev/log", handlers.SysLogHandler.LOG_USER) 54 | 55 | log_cfgs: 56 | # These will be joined into a string that defines the configuration 57 | - [ *log_base, *log_syslog ] 58 | # These will be joined into a string that defines the configuration 59 | - [ *log_base, *log_file ] 60 | # A file path can also be used 61 | # - /etc/log.conf 62 | 63 | # this tells cloud-init to redirect its stdout and stderr to 64 | # 'tee -a /var/log/cloud-init-output.log' so the user can see output 65 | # there without needing to look on the console. 66 | output: {all: '| tee -a /var/log/cloud-init-output.log'} 67 | -------------------------------------------------------------------------------- /cloudinit/config/__init__.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2008-2010 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Chuck Short 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | # 21 | 22 | from cloudinit.settings import (PER_INSTANCE, FREQUENCIES) 23 | 24 | from cloudinit import log as logging 25 | 26 | LOG = logging.getLogger(__name__) 27 | 28 | # This prefix is used to make it less 29 | # of a chance that when importing 30 | # we will not find something else with the same 31 | # name in the lookup path... 32 | MOD_PREFIX = "cc_" 33 | 34 | 35 | def form_module_name(name): 36 | canon_name = name.replace("-", "_") 37 | if canon_name.lower().endswith(".py"): 38 | canon_name = canon_name[0:(len(canon_name) - 3)] 39 | canon_name = canon_name.strip() 40 | if not canon_name: 41 | return None 42 | if not canon_name.startswith(MOD_PREFIX): 43 | canon_name = '%s%s' % (MOD_PREFIX, canon_name) 44 | return canon_name 45 | 46 | 47 | def fixup_module(mod, def_freq=PER_INSTANCE): 48 | if not hasattr(mod, 'frequency'): 49 | setattr(mod, 'frequency', def_freq) 50 | else: 51 | freq = mod.frequency 52 | if freq and freq not in FREQUENCIES: 53 | LOG.warn("Module %s has an unknown frequency %s", mod, freq) 54 | if not hasattr(mod, 'distros'): 55 | setattr(mod, 'distros', []) 56 | if not hasattr(mod, 'osfamilies'): 57 | setattr(mod, 'osfamilies', []) 58 | return mod 59 | -------------------------------------------------------------------------------- /cloudinit/handlers/shell_script.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2012 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # Copyright (C) 2012 Yahoo! Inc. 6 | # 7 | # Author: Scott Moser 8 | # Author: Juerg Haefliger 9 | # Author: Joshua Harlow 10 | # 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License version 3, as 13 | # published by the Free Software Foundation. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | 23 | import os 24 | 25 | from cloudinit import handlers 26 | from cloudinit import log as logging 27 | from cloudinit import util 28 | 29 | from cloudinit.settings import (PER_ALWAYS) 30 | 31 | LOG = logging.getLogger(__name__) 32 | SHELL_PREFIX = "#!" 33 | 34 | 35 | class ShellScriptPartHandler(handlers.Handler): 36 | def __init__(self, paths, **_kwargs): 37 | handlers.Handler.__init__(self, PER_ALWAYS) 38 | self.script_dir = paths.get_ipath_cur('scripts') 39 | if 'script_path' in _kwargs: 40 | self.script_dir = paths.get_ipath_cur(_kwargs['script_path']) 41 | 42 | def list_types(self): 43 | return [ 44 | handlers.type_from_starts_with(SHELL_PREFIX), 45 | ] 46 | 47 | def handle_part(self, _data, ctype, filename, # pylint: disable=W0221 48 | payload, frequency): # pylint: disable=W0613 49 | if ctype in handlers.CONTENT_SIGNALS: 50 | # TODO(harlowja): maybe delete existing things here 51 | return 52 | 53 | filename = util.clean_filename(filename) 54 | payload = util.dos2unix(payload) 55 | path = os.path.join(self.script_dir, filename) 56 | util.write_file(path, payload, 0700) 57 | -------------------------------------------------------------------------------- /upstart/cloud-init-container.conf: -------------------------------------------------------------------------------- 1 | # in a lxc container, events for network interfaces do not 2 | # get created or may be missed. This helps cloud-init-nonet along 3 | # by emitting those events if they have not been emitted. 4 | 5 | start on container 6 | stop on static-network-up 7 | task 8 | 9 | emits net-device-added 10 | 11 | console output 12 | 13 | script 14 | # if we are inside a container, then we may have to emit the ifup 15 | # events for 'auto' network devices. 16 | set -f 17 | 18 | # from /etc/network/if-up.d/upstart 19 | MARK_DEV_PREFIX="/run/network/ifup." 20 | MARK_STATIC_NETWORK_EMITTED="/run/network/static-network-up-emitted" 21 | # if the all static network interfaces are already up, nothing to do 22 | [ -f "$MARK_STATIC_NETWORK_EMITTED" ] && exit 0 23 | 24 | # ifquery will exit failure if there is no /run/network directory. 25 | # normally that would get created by one of network-interface.conf 26 | # or networking.conf. But, it is possible that we're running 27 | # before either of those have. 28 | mkdir -p /run/network 29 | 30 | # get list of all 'auto' interfaces. if there are none, nothing to do. 31 | auto_list=$(ifquery --list --allow auto 2>/dev/null) || : 32 | [ -z "$auto_list" ] && exit 0 33 | set -- ${auto_list} 34 | [ "$*" = "lo" ] && exit 0 35 | 36 | # we only want to emit for interfaces that do not exist, so filter 37 | # out anything that does not exist. 38 | for iface in "$@"; do 39 | [ "$iface" = "lo" ] && continue 40 | # skip interfaces that are already up 41 | [ -f "${MARK_DEV_PREFIX}${iface}" ] && continue 42 | 43 | if [ -d /sys/net ]; then 44 | # if /sys is mounted, and there is no /sys/net/iface, then no device 45 | [ -e "/sys/net/$iface" ] && continue 46 | else 47 | # sys wasn't mounted, so just check via 'ifconfig' 48 | ifconfig "$iface" >/dev/null 2>&1 || continue 49 | fi 50 | initctl emit --no-wait net-device-added "INTERFACE=$iface" && 51 | emitted="$emitted $iface" || 52 | echo "warn: ${UPSTART_JOB} failed to emit net-device-added INTERFACE=$iface" 53 | done 54 | 55 | [ -z "${emitted# }" ] || 56 | echo "${UPSTART_JOB}: emitted ifup for ${emitted# }" 57 | end script 58 | -------------------------------------------------------------------------------- /tests/unittests/test_handler/test_handler_locale.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Author: Juerg Haefliger 4 | # 5 | # Based on test_handler_set_hostname.py 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License version 3, as 9 | # published by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | from cloudinit.config import cc_locale 20 | 21 | from cloudinit import cloud 22 | from cloudinit import distros 23 | from cloudinit import helpers 24 | from cloudinit import util 25 | 26 | from cloudinit.sources import DataSourceNoCloud 27 | 28 | from tests.unittests import helpers as t_help 29 | 30 | from configobj import ConfigObj 31 | 32 | from StringIO import StringIO 33 | 34 | import logging 35 | 36 | LOG = logging.getLogger(__name__) 37 | 38 | 39 | class TestLocale(t_help.FilesystemMockingTestCase): 40 | def setUp(self): 41 | super(TestLocale, self).setUp() 42 | self.new_root = self.makeDir(prefix="unittest_") 43 | 44 | def _get_cloud(self, distro): 45 | self.patchUtils(self.new_root) 46 | paths = helpers.Paths({}) 47 | 48 | cls = distros.fetch(distro) 49 | d = cls(distro, {}, paths) 50 | ds = DataSourceNoCloud.DataSourceNoCloud({}, d, paths) 51 | cc = cloud.Cloud(ds, paths, {}, d, None) 52 | return cc 53 | 54 | def test_set_locale_sles(self): 55 | 56 | cfg = { 57 | 'locale': 'My.Locale', 58 | } 59 | cc = self._get_cloud('sles') 60 | cc_locale.handle('cc_locale', cfg, cc, LOG, []) 61 | 62 | contents = util.load_file('/etc/sysconfig/language') 63 | n_cfg = ConfigObj(StringIO(contents)) 64 | self.assertEquals({'RC_LANG': cfg['locale']}, dict(n_cfg)) 65 | -------------------------------------------------------------------------------- /upstart/cloud-init-nonet.conf: -------------------------------------------------------------------------------- 1 | # cloud-init-no-net 2 | # the purpose of this job is 3 | # * to block running of cloud-init until all network interfaces 4 | # configured in /etc/network/interfaces are up 5 | # * timeout if they all do not come up in a reasonable amount of time 6 | start on mounted MOUNTPOINT=/ and stopped cloud-init-local 7 | stop on static-network-up 8 | task 9 | 10 | console output 11 | 12 | script 13 | set +e # you cannot trap TERM reliably with 'set -e' 14 | SLEEP_CHILD="" 15 | 16 | static_network_up() { 17 | local emitted="/run/network/static-network-up-emitted" 18 | # /run/network/static-network-up-emitted is written by 19 | # upstart (via /etc/network/if-up.d/upstart). its presense would 20 | # indicate that static-network-up has already fired. 21 | [ -e "$emitted" -o -e "/var/$emitted" ] 22 | } 23 | msg() { 24 | local uptime="" idle="" 25 | if [ -r /proc/uptime ]; then 26 | read uptime idle < /proc/uptime 27 | fi 28 | echo "$UPSTART_JOB${uptime:+[${uptime}]}:" "$1" 29 | } 30 | 31 | handle_sigterm() { 32 | # if we received sigterm and static networking is up then it probably 33 | # came from upstart as a result of 'stop on static-network-up' 34 | if [ -n "$SLEEP_CHILD" ]; then 35 | if ! kill $SLEEP_CHILD 2>/dev/null; then 36 | [ ! -d "/proc/$SLEEP_CHILD" ] || 37 | msg "hm.. failed to kill sleep pid $SLEEP_CHILD" 38 | fi 39 | fi 40 | if static_network_up; then 41 | msg "static networking is now up" 42 | exit 0 43 | fi 44 | msg "recieved SIGTERM, networking not up" 45 | exit 2 46 | } 47 | 48 | dowait() { 49 | msg "waiting $1 seconds for network device" 50 | sleep "$1" & 51 | SLEEP_CHILD=$! 52 | wait $SLEEP_CHILD 53 | SLEEP_CHILD="" 54 | } 55 | 56 | trap handle_sigterm TERM 57 | 58 | # static_network_up already occurred 59 | static_network_up && exit 0 60 | 61 | # obj.pkl comes from cloud-init-local (or previous boot and 62 | # manual_cache_clean) 63 | [ -f /var/lib/cloud/instance/obj.pkl ] && exit 0 64 | 65 | dowait 10 66 | dowait 120 67 | msg "gave up waiting for a network device." 68 | : > /var/lib/cloud/data/no-net 69 | end script 70 | -------------------------------------------------------------------------------- /cloudinit/config/cc_seed_random.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2013 Yahoo! Inc. 4 | # 5 | # Author: Joshua Harlow 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License version 3, as 9 | # published by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import base64 20 | from StringIO import StringIO 21 | 22 | from cloudinit.settings import PER_INSTANCE 23 | from cloudinit import util 24 | 25 | frequency = PER_INSTANCE 26 | 27 | 28 | def _decode(data, encoding=None): 29 | if not data: 30 | return '' 31 | if not encoding or encoding.lower() in ['raw']: 32 | return data 33 | elif encoding.lower() in ['base64', 'b64']: 34 | return base64.b64decode(data) 35 | elif encoding.lower() in ['gzip', 'gz']: 36 | return util.decomp_gzip(data, quiet=False) 37 | else: 38 | raise IOError("Unknown random_seed encoding: %s" % (encoding)) 39 | 40 | 41 | def handle(name, cfg, cloud, log, _args): 42 | if not cfg or "random_seed" not in cfg: 43 | log.debug(("Skipping module named %s, " 44 | "no 'random_seed' configuration found"), name) 45 | return 46 | 47 | my_cfg = cfg['random_seed'] 48 | seed_path = my_cfg.get('file', '/dev/urandom') 49 | seed_buf = StringIO() 50 | seed_buf.write(_decode(my_cfg.get('data', ''), 51 | encoding=my_cfg.get('encoding'))) 52 | 53 | metadata = cloud.datasource.metadata 54 | if metadata and 'random_seed' in metadata: 55 | seed_buf.write(metadata['random_seed']) 56 | 57 | seed_data = seed_buf.getvalue() 58 | if len(seed_data): 59 | log.debug("%s: adding %s bytes of random seed entrophy to %s", name, 60 | len(seed_data), seed_path) 61 | util.append_file(seed_path, seed_data) 62 | -------------------------------------------------------------------------------- /tests/unittests/test_cs_util.py: -------------------------------------------------------------------------------- 1 | from mocker import MockerTestCase 2 | 3 | from cloudinit.cs_utils import Cepko 4 | 5 | 6 | SERVER_CONTEXT = { 7 | "cpu": 1000, 8 | "cpus_instead_of_cores": False, 9 | "global_context": {"some_global_key": "some_global_val"}, 10 | "mem": 1073741824, 11 | "meta": {"ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2E.../hQ5D5 john@doe"}, 12 | "name": "test_server", 13 | "requirements": [], 14 | "smp": 1, 15 | "tags": ["much server", "very performance"], 16 | "uuid": "65b2fb23-8c03-4187-a3ba-8b7c919e889", 17 | "vnc_password": "9e84d6cb49e46379" 18 | } 19 | 20 | 21 | class CepkoMock(Cepko): 22 | def all(self): 23 | return SERVER_CONTEXT 24 | 25 | def get(self, key="", request_pattern=None): 26 | return SERVER_CONTEXT['tags'] 27 | 28 | 29 | class CepkoResultTests(MockerTestCase): 30 | def setUp(self): 31 | self.mocked = self.mocker.replace("cloudinit.cs_utils.Cepko", 32 | spec=CepkoMock, 33 | count=False, 34 | passthrough=False) 35 | self.mocked() 36 | self.mocker.result(CepkoMock()) 37 | self.mocker.replay() 38 | self.c = Cepko() 39 | 40 | def test_getitem(self): 41 | result = self.c.all() 42 | self.assertEqual("65b2fb23-8c03-4187-a3ba-8b7c919e889", result['uuid']) 43 | self.assertEqual([], result['requirements']) 44 | self.assertEqual("much server", result['tags'][0]) 45 | self.assertEqual(1, result['smp']) 46 | 47 | def test_len(self): 48 | self.assertEqual(len(SERVER_CONTEXT), len(self.c.all())) 49 | 50 | def test_contains(self): 51 | result = self.c.all() 52 | self.assertTrue('uuid' in result) 53 | self.assertFalse('uid' in result) 54 | self.assertTrue('meta' in result) 55 | self.assertFalse('ssh_public_key' in result) 56 | 57 | def test_iter(self): 58 | self.assertEqual(sorted(SERVER_CONTEXT.keys()), 59 | sorted([key for key in self.c.all()])) 60 | 61 | def test_with_list_as_result(self): 62 | result = self.c.get('tags') 63 | self.assertEqual('much server', result[0]) 64 | self.assertTrue('very performance' in result) 65 | self.assertEqual(2, len(result)) 66 | -------------------------------------------------------------------------------- /cloudinit/config/cc_apt_pipelining.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2011 Canonical Ltd. 4 | # 5 | # Author: Ben Howard 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License version 3, as 9 | # published by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | from cloudinit.settings import PER_INSTANCE 20 | from cloudinit import util 21 | 22 | frequency = PER_INSTANCE 23 | 24 | distros = ['ubuntu', 'debian'] 25 | 26 | DEFAULT_FILE = "/etc/apt/apt.conf.d/90cloud-init-pipelining" 27 | 28 | APT_PIPE_TPL = ("//Written by cloud-init per 'apt_pipelining'\n" 29 | 'Acquire::http::Pipeline-Depth "%s";\n') 30 | 31 | # Acquire::http::Pipeline-Depth can be a value 32 | # from 0 to 5 indicating how many outstanding requests APT should send. 33 | # A value of zero MUST be specified if the remote host does not properly linger 34 | # on TCP connections - otherwise data corruption will occur. 35 | 36 | 37 | def handle(_name, cfg, _cloud, log, _args): 38 | 39 | apt_pipe_value = util.get_cfg_option_str(cfg, "apt_pipelining", False) 40 | apt_pipe_value_s = str(apt_pipe_value).lower().strip() 41 | 42 | if apt_pipe_value_s == "false": 43 | write_apt_snippet("0", log, DEFAULT_FILE) 44 | elif apt_pipe_value_s in ("none", "unchanged", "os"): 45 | return 46 | elif apt_pipe_value_s in [str(b) for b in xrange(0, 6)]: 47 | write_apt_snippet(apt_pipe_value_s, log, DEFAULT_FILE) 48 | else: 49 | log.warn("Invalid option for apt_pipeling: %s", apt_pipe_value) 50 | 51 | 52 | def write_apt_snippet(setting, log, f_name): 53 | """Writes f_name with apt pipeline depth 'setting'.""" 54 | 55 | file_contents = APT_PIPE_TPL % (setting) 56 | util.write_file(f_name, file_contents) 57 | log.debug("Wrote %s with apt pipeline depth setting %s", f_name, setting) 58 | -------------------------------------------------------------------------------- /cloudinit/settings.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2012 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # Copyright (C) 2012 Yahoo! Inc. 6 | # 7 | # Author: Scott Moser 8 | # Author: Juerg Haefliger 9 | # Author: Joshua Harlow 10 | # 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License version 3, as 13 | # published by the Free Software Foundation. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | 23 | # Set and read for determining the cloud config file location 24 | CFG_ENV_NAME = "CLOUD_CFG" 25 | 26 | # This is expected to be a yaml formatted file 27 | CLOUD_CONFIG = '/etc/cloud/cloud.cfg' 28 | 29 | # What u get if no config is provided 30 | CFG_BUILTIN = { 31 | 'datasource_list': [ 32 | 'NoCloud', 33 | 'ConfigDrive', 34 | 'OpenNebula', 35 | 'Azure', 36 | 'AltCloud', 37 | 'OVF', 38 | 'MAAS', 39 | 'GCE', 40 | 'OpenStack' 41 | 'Ec2', 42 | 'CloudSigma', 43 | 'CloudStack', 44 | 'SmartOS', 45 | # At the end to act as a 'catch' when none of the above work... 46 | 'None', 47 | ], 48 | 'def_log_file': '/var/log/cloud-init.log', 49 | 'log_cfgs': [], 50 | 'syslog_fix_perms': 'syslog:adm', 51 | 'system_info': { 52 | 'paths': { 53 | 'cloud_dir': '/var/lib/cloud', 54 | 'templates_dir': '/etc/cloud/templates/', 55 | }, 56 | 'distro': 'ubuntu', 57 | }, 58 | 'vendor_data': {'enabled': True, 'prefix': []}, 59 | } 60 | 61 | # Valid frequencies of handlers/modules 62 | PER_INSTANCE = "once-per-instance" 63 | PER_ALWAYS = "always" 64 | PER_ONCE = "once" 65 | 66 | # Used to sanity check incoming handlers/modules frequencies 67 | FREQUENCIES = [PER_INSTANCE, PER_ALWAYS, PER_ONCE] 68 | -------------------------------------------------------------------------------- /tests/unittests/test_builtin_handlers.py: -------------------------------------------------------------------------------- 1 | """Tests of the built-in user data handlers.""" 2 | 3 | import os 4 | 5 | from tests.unittests import helpers as test_helpers 6 | 7 | from cloudinit import handlers 8 | from cloudinit import helpers 9 | from cloudinit import util 10 | 11 | from cloudinit.handlers import upstart_job 12 | 13 | from cloudinit.settings import (PER_ALWAYS, PER_INSTANCE) 14 | 15 | 16 | class TestBuiltins(test_helpers.FilesystemMockingTestCase): 17 | 18 | def test_upstart_frequency_no_out(self): 19 | c_root = self.makeDir() 20 | up_root = self.makeDir() 21 | paths = helpers.Paths({ 22 | 'cloud_dir': c_root, 23 | 'upstart_dir': up_root, 24 | }) 25 | freq = PER_ALWAYS 26 | h = upstart_job.UpstartJobPartHandler(paths) 27 | # No files should be written out when 28 | # the frequency is ! per-instance 29 | h.handle_part('', handlers.CONTENT_START, 30 | None, None, None) 31 | h.handle_part('blah', 'text/upstart-job', 32 | 'test.conf', 'blah', freq) 33 | h.handle_part('', handlers.CONTENT_END, 34 | None, None, None) 35 | self.assertEquals(0, len(os.listdir(up_root))) 36 | 37 | def test_upstart_frequency_single(self): 38 | # files should be written out when frequency is ! per-instance 39 | new_root = self.makeDir() 40 | freq = PER_INSTANCE 41 | 42 | self.patchOS(new_root) 43 | self.patchUtils(new_root) 44 | paths = helpers.Paths({ 45 | 'upstart_dir': "/etc/upstart", 46 | }) 47 | 48 | upstart_job.SUITABLE_UPSTART = True 49 | util.ensure_dir("/run") 50 | util.ensure_dir("/etc/upstart") 51 | 52 | mock_subp = self.mocker.replace(util.subp, passthrough=False) 53 | mock_subp(["initctl", "reload-configuration"], capture=False) 54 | self.mocker.replay() 55 | 56 | h = upstart_job.UpstartJobPartHandler(paths) 57 | h.handle_part('', handlers.CONTENT_START, 58 | None, None, None) 59 | h.handle_part('blah', 'text/upstart-job', 60 | 'test.conf', 'blah', freq) 61 | h.handle_part('', handlers.CONTENT_END, 62 | None, None, None) 63 | 64 | self.assertEquals(1, len(os.listdir('/etc/upstart'))) 65 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-puppet.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | # 3 | # This is an example file to automatically setup and run puppetd 4 | # when the instance boots for the first time. 5 | # Make sure that this file is valid yaml before starting instances. 6 | # It should be passed as user-data when starting the instance. 7 | puppet: 8 | # Every key present in the conf object will be added to puppet.conf: 9 | # [name] 10 | # subkey=value 11 | # 12 | # For example the configuration below will have the following section 13 | # added to puppet.conf: 14 | # [puppetd] 15 | # server=puppetmaster.example.org 16 | # certname=i-0123456.ip-X-Y-Z.cloud.internal 17 | # 18 | # The puppmaster ca certificate will be available in 19 | # /var/lib/puppet/ssl/certs/ca.pem 20 | conf: 21 | agent: 22 | server: "puppetmaster.example.org" 23 | # certname supports substitutions at runtime: 24 | # %i: instanceid 25 | # Example: i-0123456 26 | # %f: fqdn of the machine 27 | # Example: ip-X-Y-Z.cloud.internal 28 | # 29 | # NB: the certname will automatically be lowercased as required by puppet 30 | certname: "%i.%f" 31 | # ca_cert is a special case. It won't be added to puppet.conf. 32 | # It holds the puppetmaster certificate in pem format. 33 | # It should be a multi-line string (using the | yaml notation for 34 | # multi-line strings). 35 | # The puppetmaster certificate is located in 36 | # /var/lib/puppet/ssl/ca/ca_crt.pem on the puppetmaster host. 37 | # 38 | ca_cert: | 39 | -----BEGIN CERTIFICATE----- 40 | MIICCTCCAXKgAwIBAgIBATANBgkqhkiG9w0BAQUFADANMQswCQYDVQQDDAJjYTAe 41 | Fw0xMDAyMTUxNzI5MjFaFw0xNTAyMTQxNzI5MjFaMA0xCzAJBgNVBAMMAmNhMIGf 42 | MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCu7Q40sm47/E1Pf+r8AYb/V/FWGPgc 43 | b014OmNoX7dgCxTDvps/h8Vw555PdAFsW5+QhsGr31IJNI3kSYprFQcYf7A8tNWu 44 | 1MASW2CfaEiOEi9F1R3R4Qlz4ix+iNoHiUDTjazw/tZwEdxaQXQVLwgTGRwVa+aA 45 | qbutJKi93MILLwIDAQABo3kwdzA4BglghkgBhvhCAQ0EKxYpUHVwcGV0IFJ1Ynkv 46 | T3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwDwYDVR0TAQH/BAUwAwEB/zAd 47 | BgNVHQ4EFgQUu4+jHB+GYE5Vxo+ol1OAhevspjAwCwYDVR0PBAQDAgEGMA0GCSqG 48 | SIb3DQEBBQUAA4GBAH/rxlUIjwNb3n7TXJcDJ6MMHUlwjr03BDJXKb34Ulndkpaf 49 | +GAlzPXWa7bO908M9I8RnPfvtKnteLbvgTK+h+zX1XCty+S2EQWk29i2AdoqOTxb 50 | hppiGMp0tT5Havu4aceCXiy2crVcudj3NFciy8X66SoECemW9UYDCb9T5D0d 51 | -----END CERTIFICATE----- 52 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-datasources.txt: -------------------------------------------------------------------------------- 1 | # Documentation on data sources configuration options 2 | datasource: 3 | # Ec2 4 | Ec2: 5 | # timeout: the timeout value for a request at metadata service 6 | timeout : 50 7 | # The length in seconds to wait before giving up on the metadata 8 | # service. The actual total wait could be up to 9 | # len(resolvable_metadata_urls)*timeout 10 | max_wait : 120 11 | 12 | #metadata_url: a list of URLs to check for metadata services 13 | metadata_urls: 14 | - http://169.254.169.254:80 15 | - http://instance-data:8773 16 | 17 | MAAS: 18 | timeout : 50 19 | max_wait : 120 20 | 21 | # there are no default values for metadata_url or oauth credentials 22 | # If no credentials are present, non-authed attempts will be made. 23 | metadata_url: http://mass-host.localdomain/source 24 | consumer_key: Xh234sdkljf 25 | token_key: kjfhgb3n 26 | token_secret: 24uysdfx1w4 27 | 28 | NoCloud: 29 | # default seedfrom is None 30 | # if found, then it should contain a url with: 31 | # /user-data and /meta-data 32 | # seedfrom: http://my.example.com/i-abcde 33 | seedfrom: None 34 | 35 | # fs_label: the label on filesystems to be searched for NoCloud source 36 | fs_label: cidata 37 | 38 | # these are optional, but allow you to basically provide a datasource 39 | # right here 40 | user-data: | 41 | # This is the user-data verbatum 42 | meta-data: 43 | instance-id: i-87018aed 44 | local-hostname: myhost.internal 45 | 46 | Azure: 47 | agent_command: [service, walinuxagent, start] 48 | set_hostname: True 49 | hostname_bounce: 50 | interface: eth0 51 | policy: on # [can be 'on', 'off' or 'force'] 52 | 53 | SmartOS: 54 | # Smart OS datasource works over a serial console interacting with 55 | # a server on the other end. By default, the second serial console is the 56 | # device. SmartOS also uses a serial timeout of 60 seconds. 57 | serial_device: /dev/ttyS1 58 | serial_timeout: 60 59 | 60 | # a list of keys that will not be base64 decoded even if base64_all 61 | no_base64_decode: ['root_authorized_keys', 'motd_sys_info', 62 | 'iptables_disable'] 63 | # a plaintext, comma delimited list of keys whose values are b64 encoded 64 | base64_keys: [] 65 | # a boolean indicating that all keys not in 'no_base64_decode' are encoded 66 | base64_all: False 67 | -------------------------------------------------------------------------------- /tests/unittests/test_distros/test_resolv.py: -------------------------------------------------------------------------------- 1 | from mocker import MockerTestCase 2 | 3 | from cloudinit.distros.parsers import resolv_conf 4 | 5 | import re 6 | 7 | 8 | BASE_RESOLVE = ''' 9 | ; generated by /sbin/dhclient-script 10 | search blah.yahoo.com yahoo.com 11 | nameserver 10.15.44.14 12 | nameserver 10.15.30.92 13 | ''' 14 | BASE_RESOLVE = BASE_RESOLVE.strip() 15 | 16 | 17 | class TestResolvHelper(MockerTestCase): 18 | def test_parse_same(self): 19 | rp = resolv_conf.ResolvConf(BASE_RESOLVE) 20 | rp_r = str(rp).strip() 21 | self.assertEquals(BASE_RESOLVE, rp_r) 22 | 23 | def test_local_domain(self): 24 | rp = resolv_conf.ResolvConf(BASE_RESOLVE) 25 | self.assertEquals(None, rp.local_domain) 26 | 27 | rp.local_domain = "bob" 28 | self.assertEquals('bob', rp.local_domain) 29 | self.assertIn('domain bob', str(rp)) 30 | 31 | def test_nameservers(self): 32 | rp = resolv_conf.ResolvConf(BASE_RESOLVE) 33 | self.assertIn('10.15.44.14', rp.nameservers) 34 | self.assertIn('10.15.30.92', rp.nameservers) 35 | rp.add_nameserver('10.2') 36 | self.assertIn('10.2', rp.nameservers) 37 | self.assertIn('nameserver 10.2', str(rp)) 38 | self.assertNotIn('10.3', rp.nameservers) 39 | self.assertEquals(len(rp.nameservers), 3) 40 | rp.add_nameserver('10.2') 41 | self.assertRaises(ValueError, rp.add_nameserver, '10.3') 42 | self.assertNotIn('10.3', rp.nameservers) 43 | 44 | def test_search_domains(self): 45 | rp = resolv_conf.ResolvConf(BASE_RESOLVE) 46 | self.assertIn('yahoo.com', rp.search_domains) 47 | self.assertIn('blah.yahoo.com', rp.search_domains) 48 | rp.add_search_domain('bbb.y.com') 49 | self.assertIn('bbb.y.com', rp.search_domains) 50 | self.assertTrue(re.search(r'search(.*)bbb.y.com(.*)', str(rp))) 51 | self.assertIn('bbb.y.com', rp.search_domains) 52 | rp.add_search_domain('bbb.y.com') 53 | self.assertEquals(len(rp.search_domains), 3) 54 | rp.add_search_domain('bbb2.y.com') 55 | self.assertEquals(len(rp.search_domains), 4) 56 | rp.add_search_domain('bbb3.y.com') 57 | self.assertEquals(len(rp.search_domains), 5) 58 | rp.add_search_domain('bbb4.y.com') 59 | self.assertEquals(len(rp.search_domains), 6) 60 | self.assertRaises(ValueError, rp.add_search_domain, 'bbb5.y.com') 61 | self.assertEquals(len(rp.search_domains), 6) 62 | -------------------------------------------------------------------------------- /tests/unittests/test_handler/test_handler_set_hostname.py: -------------------------------------------------------------------------------- 1 | from cloudinit.config import cc_set_hostname 2 | 3 | from cloudinit import cloud 4 | from cloudinit import distros 5 | from cloudinit import helpers 6 | from cloudinit import util 7 | 8 | from tests.unittests import helpers as t_help 9 | 10 | import logging 11 | 12 | from StringIO import StringIO 13 | 14 | from configobj import ConfigObj 15 | 16 | LOG = logging.getLogger(__name__) 17 | 18 | 19 | class TestHostname(t_help.FilesystemMockingTestCase): 20 | def setUp(self): 21 | super(TestHostname, self).setUp() 22 | self.tmp = self.makeDir(prefix="unittest_") 23 | 24 | def _fetch_distro(self, kind): 25 | cls = distros.fetch(kind) 26 | paths = helpers.Paths({}) 27 | return cls(kind, {}, paths) 28 | 29 | def test_write_hostname_rhel(self): 30 | cfg = { 31 | 'hostname': 'blah.blah.blah.yahoo.com', 32 | } 33 | distro = self._fetch_distro('rhel') 34 | paths = helpers.Paths({}) 35 | ds = None 36 | cc = cloud.Cloud(ds, paths, {}, distro, None) 37 | self.patchUtils(self.tmp) 38 | cc_set_hostname.handle('cc_set_hostname', 39 | cfg, cc, LOG, []) 40 | contents = util.load_file("/etc/sysconfig/network") 41 | n_cfg = ConfigObj(StringIO(contents)) 42 | self.assertEquals({'HOSTNAME': 'blah.blah.blah.yahoo.com'}, 43 | dict(n_cfg)) 44 | 45 | def test_write_hostname_debian(self): 46 | cfg = { 47 | 'hostname': 'blah.blah.blah.yahoo.com', 48 | } 49 | distro = self._fetch_distro('debian') 50 | paths = helpers.Paths({}) 51 | ds = None 52 | cc = cloud.Cloud(ds, paths, {}, distro, None) 53 | self.patchUtils(self.tmp) 54 | cc_set_hostname.handle('cc_set_hostname', 55 | cfg, cc, LOG, []) 56 | contents = util.load_file("/etc/hostname") 57 | self.assertEquals('blah', contents.strip()) 58 | 59 | def test_write_hostname_sles(self): 60 | cfg = { 61 | 'hostname': 'blah.blah.blah.suse.com', 62 | } 63 | distro = self._fetch_distro('sles') 64 | paths = helpers.Paths({}) 65 | ds = None 66 | cc = cloud.Cloud(ds, paths, {}, distro, None) 67 | self.patchUtils(self.tmp) 68 | cc_set_hostname.handle('cc_set_hostname', cfg, cc, LOG, []) 69 | contents = util.load_file("/etc/HOSTNAME") 70 | self.assertEquals('blah', contents.strip()) 71 | -------------------------------------------------------------------------------- /cloudinit/config/cc_salt_minion.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Author: Jeff Bauer 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 3, as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | import os 18 | 19 | from cloudinit import util 20 | 21 | # Note: see http://saltstack.org/topics/installation/ 22 | 23 | 24 | def handle(name, cfg, cloud, log, _args): 25 | # If there isn't a salt key in the configuration don't do anything 26 | if 'salt_minion' not in cfg: 27 | log.debug(("Skipping module named %s," 28 | " no 'salt_minion' key in configuration"), name) 29 | return 30 | 31 | salt_cfg = cfg['salt_minion'] 32 | 33 | # Start by installing the salt package ... 34 | cloud.distro.install_packages(('salt-minion',)) 35 | 36 | # Ensure we can configure files at the right dir 37 | config_dir = salt_cfg.get("config_dir", '/etc/salt') 38 | util.ensure_dir(config_dir) 39 | 40 | # ... and then update the salt configuration 41 | if 'conf' in salt_cfg: 42 | # Add all sections from the conf object to /etc/salt/minion 43 | minion_config = os.path.join(config_dir, 'minion') 44 | minion_data = util.yaml_dumps(salt_cfg.get('conf')) 45 | util.write_file(minion_config, minion_data) 46 | 47 | # ... copy the key pair if specified 48 | if 'public_key' in salt_cfg and 'private_key' in salt_cfg: 49 | pki_dir = salt_cfg.get('pki_dir', '/etc/salt/pki') 50 | with util.umask(077): 51 | util.ensure_dir(pki_dir) 52 | pub_name = os.path.join(pki_dir, 'minion.pub') 53 | pem_name = os.path.join(pki_dir, 'minion.pem') 54 | util.write_file(pub_name, salt_cfg['public_key']) 55 | util.write_file(pem_name, salt_cfg['private_key']) 56 | 57 | # restart salt-minion. 'service' will start even if not started. if it 58 | # was started, it needs to be restarted for config change. 59 | util.subp(['service', 'salt-minion', 'restart'], capture=False) 60 | -------------------------------------------------------------------------------- /doc/sources/ovf/example/ovf-env.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | 14 | ESX Server 15 | 3.0.1 16 | VMware, Inc. 17 | en_US 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 33 | 34 | 35 | 36 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /cloudinit/config/cc_final_message.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2011 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from cloudinit import templater 22 | from cloudinit import util 23 | from cloudinit import version 24 | 25 | from cloudinit.settings import PER_ALWAYS 26 | 27 | frequency = PER_ALWAYS 28 | 29 | # Cheetah formated default message 30 | FINAL_MESSAGE_DEF = ("Cloud-init v. ${version} finished at ${timestamp}." 31 | " Datasource ${datasource}. Up ${uptime} seconds") 32 | 33 | 34 | def handle(_name, cfg, cloud, log, args): 35 | 36 | msg_in = '' 37 | if len(args) != 0: 38 | msg_in = str(args[0]) 39 | else: 40 | msg_in = util.get_cfg_option_str(cfg, "final_message", "") 41 | 42 | msg_in = msg_in.strip() 43 | if not msg_in: 44 | msg_in = FINAL_MESSAGE_DEF 45 | 46 | uptime = util.uptime() 47 | ts = util.time_rfc2822() 48 | cver = version.version_string() 49 | try: 50 | subs = { 51 | 'uptime': uptime, 52 | 'timestamp': ts, 53 | 'version': cver, 54 | 'datasource': str(cloud.datasource), 55 | } 56 | util.multi_log("%s\n" % (templater.render_string(msg_in, subs)), 57 | console=False, stderr=True, log=log) 58 | except Exception: 59 | util.logexc(log, "Failed to render final message template") 60 | 61 | boot_fin_fn = cloud.paths.boot_finished 62 | try: 63 | contents = "%s - %s - v. %s\n" % (uptime, ts, cver) 64 | util.write_file(boot_fin_fn, contents) 65 | except: 66 | util.logexc(log, "Failed to write boot finished file %s", boot_fin_fn) 67 | 68 | if cloud.datasource.is_disconnected: 69 | log.warn("Used fallback datasource") 70 | -------------------------------------------------------------------------------- /cloudinit/importer.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2012 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # Copyright (C) 2012 Yahoo! Inc. 6 | # 7 | # Author: Scott Moser 8 | # Author: Juerg Haefliger 9 | # Author: Joshua Harlow 10 | # 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License version 3, as 13 | # published by the Free Software Foundation. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | 23 | import sys 24 | 25 | from cloudinit import log as logging 26 | 27 | LOG = logging.getLogger(__name__) 28 | 29 | 30 | def import_module(module_name): 31 | __import__(module_name) 32 | return sys.modules[module_name] 33 | 34 | 35 | def find_module(base_name, search_paths, required_attrs=None): 36 | found_places = [] 37 | if not required_attrs: 38 | required_attrs = [] 39 | # NOTE(harlowja): translate the search paths to include the base name. 40 | real_paths = [] 41 | for path in search_paths: 42 | real_path = [] 43 | if path: 44 | real_path.extend(path.split(".")) 45 | real_path.append(base_name) 46 | full_path = '.'.join(real_path) 47 | real_paths.append(full_path) 48 | LOG.debug("Looking for modules %s that have attributes %s", 49 | real_paths, required_attrs) 50 | for full_path in real_paths: 51 | mod = None 52 | try: 53 | mod = import_module(full_path) 54 | except ImportError as e: 55 | LOG.debug("Failed at attempted import of '%s' due to: %s", 56 | full_path, e) 57 | if not mod: 58 | continue 59 | found_attrs = 0 60 | for attr in required_attrs: 61 | if hasattr(mod, attr): 62 | found_attrs += 1 63 | if found_attrs == len(required_attrs): 64 | found_places.append(full_path) 65 | LOG.debug("Found %s with attributes %s in %s", base_name, 66 | required_attrs, found_places) 67 | return found_places 68 | -------------------------------------------------------------------------------- /cloudinit/config/cc_grub_dpkg.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2009-2010 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import os 22 | 23 | from cloudinit import util 24 | 25 | distros = ['ubuntu', 'debian'] 26 | 27 | 28 | def handle(_name, cfg, _cloud, log, _args): 29 | idevs = None 30 | idevs_empty = None 31 | 32 | if "grub-dpkg" in cfg: 33 | idevs = util.get_cfg_option_str(cfg["grub-dpkg"], 34 | "grub-pc/install_devices", None) 35 | idevs_empty = util.get_cfg_option_str(cfg["grub-dpkg"], 36 | "grub-pc/install_devices_empty", None) 37 | 38 | if ((os.path.exists("/dev/sda1") and not os.path.exists("/dev/sda")) or 39 | (os.path.exists("/dev/xvda1") and not os.path.exists("/dev/xvda"))): 40 | if idevs is None: 41 | idevs = "" 42 | if idevs_empty is None: 43 | idevs_empty = "true" 44 | else: 45 | if idevs_empty is None: 46 | idevs_empty = "false" 47 | if idevs is None: 48 | idevs = "/dev/sda" 49 | for dev in ("/dev/sda", "/dev/vda", "/dev/sda1", "/dev/vda1"): 50 | if os.path.exists(dev): 51 | idevs = dev 52 | break 53 | 54 | # now idevs and idevs_empty are set to determined values 55 | # or, those set by user 56 | 57 | dconf_sel = (("grub-pc grub-pc/install_devices string %s\n" 58 | "grub-pc grub-pc/install_devices_empty boolean %s\n") % 59 | (idevs, idevs_empty)) 60 | 61 | log.debug("Setting grub debconf-set-selections with '%s','%s'" % 62 | (idevs, idevs_empty)) 63 | 64 | try: 65 | util.subp(['debconf-set-selections'], dconf_sel) 66 | except: 67 | util.logexc(log, "Failed to run debconf-set-selections for grub-dpkg") 68 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-salt-minion.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | # 3 | # This is an example file to automatically setup and run a salt 4 | # minion when the instance boots for the first time. 5 | # Make sure that this file is valid yaml before starting instances. 6 | # It should be passed as user-data when starting the instance. 7 | 8 | salt_minion: 9 | # conf contains all the directives to be assigned in /etc/salt/minion. 10 | 11 | conf: 12 | # Set the location of the salt master server, if the master server cannot be 13 | # resolved, then the minion will fail to start. 14 | 15 | master: salt.example.com 16 | 17 | # Salt keys are manually generated by: salt-key --gen-keys=GEN_KEYS, 18 | # where GEN_KEYS is the name of the keypair, e.g. 'minion'. The keypair 19 | # will be copied to /etc/salt/pki on the minion instance. 20 | 21 | public_key: | 22 | -----BEGIN PUBLIC KEY----- 23 | MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAwI4yqk1Y12zVmu9Ejlua 24 | h2FD6kjrt+N9XfGqZUUVNeRb7CA0Sj5Q6NtgoaiXuIrSea2sLda6ivqAGmtxMMrP 25 | zpf3FwsYWxBUNF7D4YeLmYjvcTbfr3bCOIRnPNXZ+4isuvvEiM02u2cO0okZSgeb 26 | dofNa1NbTLYAQr9jZZb7GPKrTO4CKy0xzBih/A+sl6dL9PNDmqXQEjyJS6PXG1Vj 27 | PvD5jpSrxuIl5Ms/+2Ro3ALgvC8dgoY/3m3csnd06afumGKv5YOGtf+bnWLhc0bf 28 | 6Sk8Q6i5t0Bl+HAULSPr+B9x/I0rN76ZnPvTj1+hJ0zTof4d0hOLx/K5OQyt7AKo 29 | 4wIBAQ== 30 | -----END PUBLIC KEY----- 31 | 32 | private_key: | 33 | -----BEGIN RSA PRIVATE KEY----- 34 | Proc-Type: 4,ENCRYPTED 35 | DEK-Info: AES-128-CBC,ECE30DBBA56E2DF06B7BC415F8870994 36 | 37 | YQOE5HIsghqjRsxPQqiWMH/VHmyFH6xIpBcmzxzispEHwBojlvLXviwvR66YhgNw 38 | 7smwE10Ik4/cwwiHTZqCk++jPATPygBiqQkUijCWzcT9kfaxmqdP4PL+hu9g7kGC 39 | KrD2Bm8/oO08s957aThuHC1sABRcJ1V3FRzJT6Za4fwweyvHVYRnmgaDA6zH0qV8 40 | NqBSB2hnNXKEdh6UFz9QGcrQxnRjfdIaW64zoEX7jT7gYYL7FkGXBa3XdMOA4fnl 41 | adRwLFMs0jfilisZv8oUbPdZ6J6x3o8p8LVecCF8tdZt1zkcLSIXKnoDFpHSISGs 42 | BD9aqD+E4ejynM/tPaVFq4IHzT8viN6h6WcH8fbpClFZ66Iyy9XL3/CjAY7Jzhh9 43 | fnbc4Iq28cdbmO/vkR7JyVOgEMWe1BcSqtro70XoUNRY8uDJUPqohrhm/9AigFRA 44 | Pwyf3LqojxRnwXjHsZtGltUtEAPZzgh3fKJnx9MyRR7DPXBRig7TAHU7n2BFRhHA 45 | TYThy29bK6NkIc/cKc2kEQVo98Cr04PO8jVxZM332FlhiVlP0kpAp+tFj7aMzPTG 46 | sJumb9kPbMsgpEuTCONm3yyoufGEBFMrIJ+Po48M2RlYOh50VkO09pI+Eu7FPtVB 47 | H4gKzoJIpZZ/7vYXQ3djM8s9hc5gD5CVExTZV4drbsXt6ITiwHuxZ6CNHRBPL5AY 48 | wmF8QZz4oivv1afdSe6E6OGC3uVmX3Psn5CVq2pE8VlRDKFy1WqfU2enRAijSS2B 49 | rtJs263fOJ8ZntDzMVMPgiAlzzfA285KUletpAeUmz+peR1gNzkE0eKSG6THOCi0 50 | rfmR8SeEzyNvin0wQ3qgYiiHjHbbFhJIMAQxoX+0hDSooM7Wo5wkLREULpGuesTg 51 | A6Fe3CiOivMDraNGA7H6Yg== 52 | -----END RSA PRIVATE KEY----- 53 | 54 | -------------------------------------------------------------------------------- /doc/rtd/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | # If extensions (or modules to document with autodoc) are in another directory, 5 | # add these directories to sys.path here. If the directory is relative to the 6 | # documentation root, use os.path.abspath to make it absolute, like shown here. 7 | sys.path.insert(0, os.path.abspath('../../')) 8 | sys.path.insert(0, os.path.abspath('../')) 9 | sys.path.insert(0, os.path.abspath('./')) 10 | sys.path.insert(0, os.path.abspath('.')) 11 | 12 | from cloudinit import version 13 | 14 | # Supress warnings for docs that aren't used yet 15 | #unused_docs = [ 16 | #] 17 | 18 | # General information about the project. 19 | project = 'Cloud-Init' 20 | 21 | # -- General configuration ---------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be 27 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = [ 29 | 'sphinx.ext.intersphinx', 30 | ] 31 | 32 | intersphinx_mapping = { 33 | 'sphinx': ('http://sphinx.pocoo.org', None) 34 | } 35 | 36 | # The suffix of source filenames. 37 | source_suffix = '.rst' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # The version info for the project you're documenting, acts as replacement for 43 | # |version| and |release|, also used in various other places throughout the 44 | # built documents. 45 | version = version.version_string() 46 | release = version 47 | 48 | # Set the default Pygments syntax 49 | highlight_language = 'python' 50 | 51 | # List of patterns, relative to source directory, that match files and 52 | # directories to ignore when looking for source files. 53 | exclude_patterns = [] 54 | 55 | # If true, sectionauthor and moduleauthor directives will be shown in the 56 | # output. They are ignored by default. 57 | show_authors = False 58 | 59 | # -- Options for HTML output -------------------------------------------------- 60 | 61 | # The theme to use for HTML and HTML Help pages. See the documentation for 62 | # a list of builtin themes. 63 | html_theme = 'default' 64 | 65 | # Theme options are theme-specific and customize the look and feel of a theme 66 | # further. For a list of options available for each theme, see the 67 | # documentation. 68 | html_theme_options = { 69 | "bodyfont": "Arial, sans-serif", 70 | "headfont": "Arial, sans-serif" 71 | } 72 | 73 | # The name of an image file (relative to this directory) to place at the top 74 | # of the sidebar. 75 | html_logo = 'static/logo.png' 76 | -------------------------------------------------------------------------------- /cloudinit/config/cc_update_etc_hosts.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2011 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from cloudinit import templater 22 | from cloudinit import util 23 | 24 | from cloudinit.settings import PER_ALWAYS 25 | 26 | frequency = PER_ALWAYS 27 | 28 | 29 | def handle(name, cfg, cloud, log, _args): 30 | manage_hosts = util.get_cfg_option_str(cfg, "manage_etc_hosts", False) 31 | if util.translate_bool(manage_hosts, addons=['template']): 32 | (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud) 33 | if not hostname: 34 | log.warn(("Option 'manage_etc_hosts' was set," 35 | " but no hostname was found")) 36 | return 37 | 38 | # Render from a template file 39 | tpl_fn_name = cloud.get_template_filename("hosts.%s" % 40 | (cloud.distro.osfamily)) 41 | if not tpl_fn_name: 42 | raise RuntimeError(("No hosts template could be" 43 | " found for distro %s") % 44 | (cloud.distro.osfamily)) 45 | 46 | templater.render_to_file(tpl_fn_name, '/etc/hosts', 47 | {'hostname': hostname, 'fqdn': fqdn}) 48 | 49 | elif manage_hosts == "localhost": 50 | (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud) 51 | if not hostname: 52 | log.warn(("Option 'manage_etc_hosts' was set," 53 | " but no hostname was found")) 54 | return 55 | 56 | log.debug("Managing localhost in /etc/hosts") 57 | cloud.distro.update_etc_hosts(hostname, fqdn) 58 | else: 59 | log.debug(("Configuration option 'manage_etc_hosts' is not set," 60 | " not managing /etc/hosts in module %s"), name) 61 | -------------------------------------------------------------------------------- /cloudinit/config/cc_foo.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2009-2010 Canonical Ltd. 4 | # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Author: Scott Moser 7 | # Author: Juerg Haefliger 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from cloudinit.settings import PER_INSTANCE 22 | 23 | # Modules are expected to have the following attributes. 24 | # 1. A required 'handle' method which takes the following params. 25 | # a) The name will not be this files name, but instead 26 | # the name specified in configuration (which is the name 27 | # which will be used to find this module). 28 | # b) A configuration object that is the result of the merging 29 | # of cloud configs configuration with legacy configuration 30 | # as well as any datasource provided configuration 31 | # c) A cloud object that can be used to access various 32 | # datasource and paths for the given distro and data provided 33 | # by the various datasource instance types. 34 | # d) A argument list that may or may not be empty to this module. 35 | # Typically those are from module configuration where the module 36 | # is defined with some extra configuration that will eventually 37 | # be translated from yaml into arguments to this module. 38 | # 2. A optional 'frequency' that defines how often this module should be ran. 39 | # Typically one of PER_INSTANCE, PER_ALWAYS, PER_ONCE. If not 40 | # provided PER_INSTANCE will be assumed. 41 | # See settings.py for these constants. 42 | # 3. A optional 'distros' array/set/tuple that defines the known distros 43 | # this module will work with (if not all of them). This is used to write 44 | # a warning out if a module is being ran on a untested distribution for 45 | # informational purposes. If non existent all distros are assumed and 46 | # no warning occurs. 47 | 48 | frequency = PER_INSTANCE 49 | 50 | 51 | def handle(name, _cfg, _cloud, log, _args): 52 | log.debug("Hi from module %s", name) 53 | -------------------------------------------------------------------------------- /doc/vendordata.txt: -------------------------------------------------------------------------------- 1 | === Overview === 2 | Vendordata is data provided by the entity that launches an instance 3 | (for example, the cloud provider). This data can be used to 4 | customize the image to fit into the particular environment it is 5 | being run in. 6 | 7 | Vendordata follows the same rules as user-data, with the following 8 | caveats: 9 | 1. Users have ultimate control over vendordata. They can disable its 10 | execution or disable handling of specific parts of multipart input. 11 | 2. By default it only runs on first boot 12 | 3. Vendordata can be disabled by the user. If the use of vendordata is 13 | required for the instance to run, then vendordata should not be 14 | used. 15 | 4. user supplied cloud-config is merged over cloud-config from 16 | vendordata. 17 | 18 | Users providing cloud-config data can use the '#cloud-config-jsonp' method 19 | to more finely control their modifications to the vendor supplied 20 | cloud-config. For example, if both vendor and user have provided 21 | 'runcnmd' then the default merge handler will cause the user's runcmd to 22 | override the one provided by the vendor. To append to 'runcmd', the user 23 | could better provide multipart input with a cloud-config-jsonp part like: 24 | #cloud-config-jsonp 25 | [{ "op": "add", "path": "/runcmd", "value": ["my", "command", "here"]}] 26 | 27 | Further, we strongly advise vendors to not 'be evil'. By evil, we 28 | mean any action that could compromise a system. Since users trust 29 | you, please take care to make sure that any vendordata is safe, 30 | atomic, idempotent and does not put your users at risk. 31 | 32 | === Input Formats === 33 | cloud-init will download and cache to filesystem any vendor-data that it 34 | finds. Vendordata is handled exactly like user-data. That means that 35 | the vendor can supply multipart input and have those parts acted on 36 | in the same way as user-data. 37 | 38 | The only differences are: 39 | * user-scripts are stored in a different location than user-scripts (to 40 | avoid namespace collision) 41 | * user can disable part handlers by cloud-config settings. 42 | For example, to disable handling of 'part-handlers' in vendor-data, 43 | the user could provide user-data like this: 44 | #cloud-config 45 | vendordata: {excluded: 'text/part-handler'} 46 | 47 | === Examples === 48 | There are examples in the examples subdirectory. 49 | Additionally, the 'tools' directory contains 'write-mime-multipart', 50 | which can be used to easily generate mime-multi-part files from a list 51 | of input files. That data can then be given to an instance. 52 | 53 | See 'write-mime-multipart --help' for usage. 54 | -------------------------------------------------------------------------------- /cloudinit/signal_handler.py: -------------------------------------------------------------------------------- 1 | # vi: ts=4 expandtab 2 | # 3 | # Copyright (C) 2012 Canonical Ltd. 4 | # Copyright (C) 2012 Yahoo! Inc. 5 | # 6 | # Author: Scott Moser 7 | # Author: Joshua Harlow 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License version 3, as 11 | # published by the Free Software Foundation. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | import inspect 22 | import signal 23 | import sys 24 | 25 | from StringIO import StringIO 26 | 27 | from cloudinit import log as logging 28 | from cloudinit import util 29 | from cloudinit import version as vr 30 | 31 | LOG = logging.getLogger(__name__) 32 | 33 | 34 | BACK_FRAME_TRACE_DEPTH = 3 35 | EXIT_FOR = { 36 | signal.SIGINT: ('Cloud-init %(version)s received SIGINT, exiting...', 1), 37 | signal.SIGTERM: ('Cloud-init %(version)s received SIGTERM, exiting...', 1), 38 | # Can't be caught... 39 | # signal.SIGKILL: ('Cloud-init killed, exiting...', 1), 40 | signal.SIGABRT: ('Cloud-init %(version)s received SIGABRT, exiting...', 1), 41 | } 42 | 43 | 44 | def _pprint_frame(frame, depth, max_depth, contents): 45 | if depth > max_depth or not frame: 46 | return 47 | frame_info = inspect.getframeinfo(frame) 48 | prefix = " " * (depth * 2) 49 | contents.write("%sFilename: %s\n" % (prefix, frame_info.filename)) 50 | contents.write("%sFunction: %s\n" % (prefix, frame_info.function)) 51 | contents.write("%sLine number: %s\n" % (prefix, frame_info.lineno)) 52 | _pprint_frame(frame.f_back, depth + 1, max_depth, contents) 53 | 54 | 55 | def _handle_exit(signum, frame): 56 | (msg, rc) = EXIT_FOR[signum] 57 | msg = msg % ({'version': vr.version()}) 58 | contents = StringIO() 59 | contents.write("%s\n" % (msg)) 60 | _pprint_frame(frame, 1, BACK_FRAME_TRACE_DEPTH, contents) 61 | util.multi_log(contents.getvalue(), 62 | console=True, stderr=False, log=LOG) 63 | sys.exit(rc) 64 | 65 | 66 | def attach_handlers(): 67 | sigs_attached = 0 68 | for signum in EXIT_FOR.keys(): 69 | signal.signal(signum, _handle_exit) 70 | sigs_attached += len(EXIT_FOR) 71 | return sigs_attached 72 | -------------------------------------------------------------------------------- /tests/unittests/test_datasource/test_cloudsigma.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import copy 3 | from unittest import TestCase 4 | 5 | from cloudinit.cs_utils import Cepko 6 | from cloudinit.sources import DataSourceCloudSigma 7 | 8 | 9 | SERVER_CONTEXT = { 10 | "cpu": 1000, 11 | "cpus_instead_of_cores": False, 12 | "global_context": {"some_global_key": "some_global_val"}, 13 | "mem": 1073741824, 14 | "meta": { 15 | "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2E.../hQ5D5 john@doe", 16 | "cloudinit-user-data": "#cloud-config\n\n...", 17 | }, 18 | "name": "test_server", 19 | "requirements": [], 20 | "smp": 1, 21 | "tags": ["much server", "very performance"], 22 | "uuid": "65b2fb23-8c03-4187-a3ba-8b7c919e8890", 23 | "vnc_password": "9e84d6cb49e46379" 24 | } 25 | 26 | 27 | class CepkoMock(Cepko): 28 | def __init__(self, mocked_context): 29 | self.result = mocked_context 30 | 31 | def all(self): 32 | return self 33 | 34 | 35 | class DataSourceCloudSigmaTest(TestCase): 36 | def setUp(self): 37 | self.datasource = DataSourceCloudSigma.DataSourceCloudSigma("", "", "") 38 | self.datasource.cepko = CepkoMock(SERVER_CONTEXT) 39 | self.datasource.get_data() 40 | 41 | def test_get_hostname(self): 42 | self.assertEqual("test_server", self.datasource.get_hostname()) 43 | self.datasource.metadata['name'] = '' 44 | self.assertEqual("65b2fb23", self.datasource.get_hostname()) 45 | self.datasource.metadata['name'] = u'тест' 46 | self.assertEqual("65b2fb23", self.datasource.get_hostname()) 47 | 48 | def test_get_public_ssh_keys(self): 49 | self.assertEqual([SERVER_CONTEXT['meta']['ssh_public_key']], 50 | self.datasource.get_public_ssh_keys()) 51 | 52 | def test_get_instance_id(self): 53 | self.assertEqual(SERVER_CONTEXT['uuid'], 54 | self.datasource.get_instance_id()) 55 | 56 | def test_metadata(self): 57 | self.assertEqual(self.datasource.metadata, SERVER_CONTEXT) 58 | 59 | def test_user_data(self): 60 | self.assertEqual(self.datasource.userdata_raw, 61 | SERVER_CONTEXT['meta']['cloudinit-user-data']) 62 | 63 | def test_encoded_user_data(self): 64 | encoded_context = copy.deepcopy(SERVER_CONTEXT) 65 | encoded_context['meta']['base64_fields'] = 'cloudinit-user-data' 66 | encoded_context['meta']['cloudinit-user-data'] = 'aGkgd29ybGQK' 67 | self.datasource.cepko = CepkoMock(encoded_context) 68 | self.datasource.get_data() 69 | 70 | self.assertEqual(self.datasource.userdata_raw, b'hi world\n') 71 | -------------------------------------------------------------------------------- /tests/unittests/test_handler/test_handler_timezone.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Author: Juerg Haefliger 4 | # 5 | # Based on test_handler_set_hostname.py 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License version 3, as 9 | # published by the Free Software Foundation. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | from cloudinit.config import cc_timezone 20 | 21 | from cloudinit import cloud 22 | from cloudinit import distros 23 | from cloudinit import helpers 24 | from cloudinit import util 25 | 26 | from cloudinit.sources import DataSourceNoCloud 27 | 28 | from tests.unittests import helpers as t_help 29 | 30 | from configobj import ConfigObj 31 | 32 | from StringIO import StringIO 33 | 34 | import logging 35 | 36 | LOG = logging.getLogger(__name__) 37 | 38 | 39 | class TestTimezone(t_help.FilesystemMockingTestCase): 40 | def setUp(self): 41 | super(TestTimezone, self).setUp() 42 | self.new_root = self.makeDir(prefix="unittest_") 43 | 44 | def _get_cloud(self, distro): 45 | self.patchUtils(self.new_root) 46 | self.patchOS(self.new_root) 47 | 48 | paths = helpers.Paths({}) 49 | 50 | cls = distros.fetch(distro) 51 | d = cls(distro, {}, paths) 52 | ds = DataSourceNoCloud.DataSourceNoCloud({}, d, paths) 53 | cc = cloud.Cloud(ds, paths, {}, d, None) 54 | return cc 55 | 56 | def test_set_timezone_sles(self): 57 | 58 | cfg = { 59 | 'timezone': 'Tatooine/Bestine', 60 | } 61 | cc = self._get_cloud('sles') 62 | 63 | # Create a dummy timezone file 64 | dummy_contents = '0123456789abcdefgh' 65 | util.write_file('/usr/share/zoneinfo/%s' % cfg['timezone'], 66 | dummy_contents) 67 | 68 | cc_timezone.handle('cc_timezone', cfg, cc, LOG, []) 69 | 70 | contents = util.load_file('/etc/sysconfig/clock') 71 | n_cfg = ConfigObj(StringIO(contents)) 72 | self.assertEquals({'TIMEZONE': cfg['timezone']}, dict(n_cfg)) 73 | 74 | contents = util.load_file('/etc/localtime') 75 | self.assertEquals(dummy_contents, contents.strip()) 76 | -------------------------------------------------------------------------------- /doc/examples/cloud-config-mcollective.txt: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | # 3 | # This is an example file to automatically setup and run mcollective 4 | # when the instance boots for the first time. 5 | # Make sure that this file is valid yaml before starting instances. 6 | # It should be passed as user-data when starting the instance. 7 | mcollective: 8 | # Every key present in the conf object will be added to server.cfg: 9 | # key: value 10 | # 11 | # For example the configuration below will have the following key 12 | # added to server.cfg: 13 | # plugin.stomp.host: dbhost 14 | conf: 15 | plugin.stomp.host: dbhost 16 | # This will add ssl certs to mcollective 17 | # WARNING WARNING WARNING 18 | # The ec2 metadata service is a network service, and thus is readable 19 | # by non-root users on the system (ie: 'ec2metadata --user-data') 20 | # If you want security for this, please use include-once + SSL urls 21 | public-cert: | 22 | -----BEGIN CERTIFICATE----- 23 | MIICCTCCAXKgAwIBAgIBATANBgkqhkiG9w0BAQUFADANMQswCQYDVQQDDAJjYTAe 24 | Fw0xMDAyMTUxNzI5MjFaFw0xNTAyMTQxNzI5MjFaMA0xCzAJBgNVBAMMAmNhMIGf 25 | MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCu7Q40sm47/E1Pf+r8AYb/V/FWGPgc 26 | b014OmNoX7dgCxTDvps/h8Vw555PdAFsW5+QhsGr31IJNI3kSYprFQcYf7A8tNWu 27 | 1MASW2CfaEiOEi9F1R3R4Qlz4ix+iNoHiUDTjazw/tZwEdxaQXQVLwgTGRwVa+aA 28 | qbutJKi93MILLwIDAQABo3kwdzA4BglghkgBhvhCAQ0EKxYpUHVwcGV0IFJ1Ynkv 29 | T3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwDwYDVR0TAQH/BAUwAwEB/zAd 30 | BgNVHQ4EFgQUu4+jHB+GYE5Vxo+ol1OAhevspjAwCwYDVR0PBAQDAgEGMA0GCSqG 31 | SIb3DQEBBQUAA4GBAH/rxlUIjwNb3n7TXJcDJ6MMHUlwjr03BDJXKb34Ulndkpaf 32 | +GAlzPXWa7bO908M9I8RnPfvtKnteLbvgTK+h+zX1XCty+S2EQWk29i2AdoqOTxb 33 | hppiGMp0tT5Havu4aceCXiy2crVcudj3NFciy8X66SoECemW9UYDCb9T5D0d 34 | -----END CERTIFICATE----- 35 | private-cert: | 36 | -----BEGIN CERTIFICATE----- 37 | MIICCTCCAXKgAwIBAgIBATANBgkqhkiG9w0BAQUFADANMQswCQYDVQQDDAJjYTAe 38 | Fw0xMDAyMTUxNzI5MjFaFw0xNTAyMTQxNzI5MjFaMA0xCzAJBgNVBAMMAmNhMIGf 39 | MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCu7Q40sm47/E1Pf+r8AYb/V/FWGPgc 40 | b014OmNoX7dgCxTDvps/h8Vw555PdAFsW5+QhsGr31IJNI3kSYprFQcYf7A8tNWu 41 | 1MASW2CfaEiOEi9F1R3R4Qlz4ix+iNoHiUDTjazw/tZwEdxaQXQVLwgTGRwVa+aA 42 | qbutJKi93MILLwIDAQABo3kwdzA4BglghkgBhvhCAQ0EKxYpUHVwcGV0IFJ1Ynkv 43 | T3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwDwYDVR0TAQH/BAUwAwEB/zAd 44 | BgNVHQ4EFgQUu4+jHB+GYE5Vxo+ol1OAhevspjAwCwYDVR0PBAQDAgEGMA0GCSqG 45 | SIb3DQEBBQUAA4GBAH/rxlUIjwNb3n7TXJcDJ6MMHUlwjr03BDJXKb34Ulndkpaf 46 | +GAlzPXWa7bO908M9I8RnPfvtKnteLbvgTK+h+zX1XCty+S2EQWk29i2AdoqOTxb 47 | hppiGMp0tT5Havu4aceCXiy2crVcudj3NFciy8X66SoECemW9UYDCb9T5D0d 48 | -----END CERTIFICATE----- 49 | 50 | --------------------------------------------------------------------------------