Compare commits

...

63 Commits

Author SHA1 Message Date
Brian Waldon
a398ce82f7 chore(release): Bump version to v0.6.1 2014-05-14 10:52:22 -07:00
Brian Waldon
e814b37839 Merge pull request #107 from bcwaldon/locksmith-no-etc
fix(coreos-cloudinit): Ensure /etc/coreos exists before writing to it
2014-05-14 10:49:23 -07:00
Brian Waldon
cb4d9e81a4 fix(coreos-cloudinit): Ensure /etc/coreos exists before writing to it 2014-05-14 10:47:18 -07:00
Jonathan Boulle
b87a4628e6 Merge pull request #99 from jonboulle/simple
chore(cloudinit): remove superfluous check
2014-05-12 10:51:51 -07:00
Jonathan Boulle
b22fdd5ac9 Merge pull request #104 from jonboulle/tests
feat(tests): add coverage script
2014-05-12 10:51:38 -07:00
Jonathan Boulle
6939fc2ddc feat(tests): add cover script 2014-05-10 01:42:57 -07:00
Jonathan Boulle
e3117269cb chore(cloudinit): remove superfluous check 2014-05-09 20:32:51 -07:00
Brandon Philips
3bb3a683a4 Merge pull request #98 from philips/remove-oem-from-default
chore(Documentation): move OEM into its own doc
2014-05-08 09:41:42 -07:00
Brandon Philips
e1033c979e chore(Documentation): move OEM into its own doc
People are customizing the OEM needlessly. Just move it into its own
doc.
2014-05-08 09:32:21 -07:00
Jonathan Boulle
9a4d24826f Merge pull request #80 from jonboulle/master
users[i].primary-group option seems invalid
2014-05-07 21:12:45 -07:00
Jonathan Boulle
7bed1307e1 fix(user): user correct primary group flag for useradd 2014-05-07 14:06:51 -07:00
Brandon Philips
47b536532d chore(coreos-cloudinit): version +git 2014-05-06 21:09:40 -07:00
Brandon Philips
7df5cf761e chore(coreos-cloudinit): bump to 0.6.0
The major feature in this release is coreos.update.reboot-strategy
2014-05-06 21:05:42 -07:00
Brandon Philips
799c02865c Merge pull request #96 from philips/locksmith-support
Add locksmith support v2
2014-05-06 21:00:44 -07:00
Brandon Philips
9f38792d43 fix(initialize): use REBOOT_STRATEGY in update.conf
Change from STRATEGY to REBOOT_STRATEGY and update the function names to
reflect that this is a config now.
2014-05-06 20:57:29 -07:00
Alex Polvi
7e4fa423e4 feat(initialize): add locksmith configuration
configure locksmith strategy based on the cloud config.
2014-05-06 20:57:28 -07:00
Brandon Philips
c3f17bd07b feat(system): add MaskUnit to systemd 2014-05-06 17:46:16 -07:00
Brandon Philips
85a473d972 Merge pull request #95 from philips/various-code-cleanups
chore(initialize): code cleanups and gofmt
2014-05-06 16:19:35 -07:00
Brandon Philips
aea5ca5252 chore(initialize): code cleanups and gofmt 2014-05-06 16:13:21 -07:00
Michael Marineau
4e84180ad5 chore(release): Bump version to v0.5.2+git 2014-05-05 14:09:08 -07:00
Michael Marineau
0f1717bf26 chore(release): Bump version to v0.5.2 2014-05-05 14:07:50 -07:00
Michael Marineau
6a9aa60a8d Merge pull request #93 from marineam/reload
Revert "fix(units): Drop automatic daemon-reload"
2014-05-05 14:02:16 -07:00
Michael Marineau
7cacb2e127 Revert "fix(units): Drop automatic daemon-reload"
daemon-reload should be fixed now and the latest CoreOS with locksmith
is causing the etcd unit to get lazy-loaded before all the cloudinit
processes have finished configuring etcd via dropin files. In short,
the luck we were relying on to get by without daemon-reload has
officially run out. Cross your fingers!

This reverts commit 580460ff3f.
2014-05-05 13:16:07 -07:00
Brian Waldon
1f688dcdca Merge pull request #92 from bcwaldon/crlf-test
test(crlf): Add test that parses user-data with carriage returns
2014-05-05 10:50:25 -07:00
Brian Waldon
f6d8190e8f test(crlf): Add test that parses user-data with carriage returns 2014-05-05 10:49:02 -07:00
Brandon Philips
3263816cf5 Merge https://github.com/coreos/template-project 2014-05-05 09:44:59 -07:00
Michael Marineau
96e1cb5a7a Merge pull request #89 from robszumski/doc-write-files
feat(docs): include write_files example
2014-04-29 11:26:44 -07:00
Rob Szumski
cf556d2a81 feat(docs): include write_files example 2014-04-29 11:17:22 -07:00
Jonathan Boulle
62bda8e6cc Merge pull request #88 from robszumski/master
fix(docs): start the example unit
2014-04-29 12:15:44 -06:00
Rob Szumski
0d1d1f77be fix(docs): start the example unit 2014-04-28 10:57:11 -07:00
Michael Marineau
a7e21747fa Merge pull request #87 from marineam/hack
fix(configdrive): Always run after OEM and ec2 metadata.
2014-04-23 14:54:19 -07:00
Michael Marineau
26b54534d6 fix(configdrive): Always run after OEM and ec2 metadata.
A workaround for https://github.com/coreos/coreos-cloudinit/issues/86

Longer term cloudinit needs to be fixed to not corrupt the system when
multiple config sources are being used. We've pretty much gotten this
far without this coming up because most configs don't conflict so badly.
2014-04-23 14:38:54 -07:00
Brian Waldon
8201d75115 chore(release): Bump version to v0.5.1+git 2014-04-22 18:22:35 -07:00
Brian Waldon
1d024af4c1 chore(release): Bump version to v0.5.1 2014-04-22 18:22:24 -07:00
Brian Waldon
09c690cbe7 Merge pull request #85 from bcwaldon/pxe-unit
feat(proc-cmdline): Add proc-cmdline unit
2014-04-22 18:21:51 -07:00
Brian Waldon
49adf19081 feat(proc-cmdline): Add proc-cmdline unit
This unit will always be started, but will only do anything if
a `cloud-config-url=<url>` token is provided in /proc/cmdline.
2014-04-22 17:56:52 -07:00
Brian Waldon
46b046c82e chore(release): Bump version to v0.5.0+git 2014-04-22 16:48:32 -07:00
Brian Waldon
e64b61b312 chore(release): Bump version to v0.5.0 2014-04-22 16:48:21 -07:00
Brian Waldon
d72e10125a Merge pull request #84 from bcwaldon/proc-cmdline
feat(proc-cmdline): Parse /proc/cmdline for cloud-config-url
2014-04-22 16:43:05 -07:00
Brian Waldon
3de3d2c050 feat(proc-cmdline): Parse /proc/cmdline for cloud-config-url
If the --from-proc-cmdline flag is given to coreos-cloudinit, the local
/proc/cmdline file will be parsed for a cloud-config-url
2014-04-22 16:38:01 -07:00
Brian Waldon
2ff0762b0c Merge pull request #83 from robszumski/correct-headers
docs(cloud-config): correct headers
2014-04-21 19:15:50 -07:00
Rob Szumski
d6bacb24bc docs(cloud-config): correct headers 2014-04-21 17:56:35 -07:00
Brian Waldon
926eb4dbb7 Merge pull request #77 from chexxor/master
Update cloud-config.md to include expected file format
2014-04-21 14:27:22 -07:00
Brian Waldon
e7599fea58 Merge pull request #82 from bcwaldon/fix-68
fix(userdata): Strip \r when checking header
2014-04-21 14:26:31 -07:00
Brian Waldon
e98c58c656 fix(userdata): Strip \r when checking header
Fix #68
2014-04-21 13:40:26 -07:00
Alex Berg
ae350a3b34 Update cloud-config.md - use "you" 2014-04-18 11:45:02 -05:00
Alex Berg
c3b53f24cf Update cloud-config.md to use "parameter", not "option" 2014-04-18 11:45:01 -05:00
Alex Berg
8bee85e63d Update cloud-config.md based on feedback 2014-04-18 11:43:54 -05:00
Alex Berg
4c02e99bc8 Update cloud-config.md option descriptions
Re-word a few more things to look more like docs.
2014-04-18 11:43:53 -05:00
Alex Berg
0fb5291dd0 Update cloud-config.md to include expected file format
Clarify root-level keys. Use page structure to indicate expected values.
2014-04-18 11:43:53 -05:00
Brian Waldon
7f55876378 Merge pull request #79 from robszumski/note-config-drive
feat(docs): note config-drive
2014-04-17 09:36:57 -07:00
Brian Waldon
eb51a89f78 Merge pull request #72 from bcwaldon/unit-enable
Address unit enabling issues
2014-04-17 09:32:47 -07:00
Rob Szumski
588ff4c26c feat(docs): note config-drive 2014-04-16 22:35:39 -07:00
Michael Marineau
5472de8821 Merge pull request #78 from robszumski/update-user-group
fix(docs): use better group example
2014-04-16 16:49:50 -07:00
Rob Szumski
e6b632f817 fix(docs): use better group example 2014-04-16 16:48:04 -07:00
Brian Waldon
61bb63b6e6 feat(unit): Allow units to be enabled even if contents not provided 2014-04-15 09:00:53 -07:00
Brian Waldon
476761cf62 refactor(unit): Separate UnitDestination from PlaceUnit 2014-04-15 09:00:53 -07:00
Brian Waldon
5981e12ac0 feat(unit): Allow user to control enabling units
Fix #69 - A user may provide an `enable` attribute of a unit in their
cloud config document. If true, coreos-cloudinit will instruct systemd
to enable the associated unit. If false, the unit will not be enabled.

Fix #71 - The default enable behavior has been changed from on to off.
2014-04-15 09:00:52 -07:00
Jonathan Boulle
e01a1f70c3 Merge pull request #2 from jonboulle/master
Clean up CONTRIBUTING.md and other bits of template-project
2014-04-04 10:41:40 -07:00
Jonathan Boulle
2e4ea503b0 chore(contributing): clean up CONTRIBUTING.md and split out DCO 2014-04-04 10:40:37 -07:00
Brandon Philips
c7aef5fdf2 Merge pull request #1 from bcwaldon/fix-case
fix(CONTRIBUTING.md): Fix title case
2014-02-05 15:52:24 -08:00
Brian Waldon
c4605160c5 fix(CONTRIBUTING.md): Fix title case 2014-02-05 15:51:24 -08:00
Brandon Philips
054de85da2 feat(*): initial commit 2014-01-19 12:25:11 -08:00
30 changed files with 1069 additions and 175 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
*.swp
bin/
coverage/
pkg/

87
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,87 @@
# How to Contribute
CoreOS projects are [Apache 2.0 licensed](LICENSE) and accept contributions via
GitHub pull requests. This document outlines some of the conventions on
development workflow, commit message formatting, contact points and other
resources to make it easier to get your contribution accepted.
# Certificate of Origin
By contributing to this project you agree to the Developer Certificate of
Origin (DCO). This document was created by the Linux Kernel community and is a
simple statement that you, as a contributor, have the legal right to make the
contribution. See the [DCO](DCO) file for details.
# Email and Chat
The project currently uses the general CoreOS email list and IRC channel:
- Email: [coreos-dev](https://groups.google.com/forum/#!forum/coreos-dev)
- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org
## Getting Started
- Fork the repository on GitHub
- Read the [README](README.md) for build and test instructions
- Play with the project, submit bugs, submit patches!
## Contribution Flow
This is a rough outline of what a contributor's workflow looks like:
- Create a topic branch from where you want to base your work (usually master).
- Make commits of logical units.
- Make sure your commit messages are in the proper format (see below).
- Push your changes to a topic branch in your fork of the repository.
- Make sure the tests pass, and add any new tests as appropriate.
- Submit a pull request to the original repository.
Thanks for your contributions!
### Format of the Commit Message
We follow a rough convention for commit messages borrowed from AngularJS. This
is an example of a commit:
```
feat(scripts/test-cluster): add a cluster test command
this uses tmux to setup a test cluster that you can easily kill and
start for debugging.
```
The format can be described more formally as follows:
```
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
The first line is the subject and should be no longer than 70 characters, the
second line is always blank, and other lines should be wrapped at 80 characters.
This allows the message to be easier to read on GitHub as well as in various
git tools.
#### Subject Line
The subject line contains a succinct description of the change.
#### Allowed `<type>`s
- *feat* (feature)
- *fix* (bug fix)
- *docs* (documentation)
- *style* (formatting, missing semi colons, …)
- *refactor*
- *test* (when adding missing tests)
- *chore* (maintain)
#### Allowed `<scope>`s
Scopes can anything specifying the place of the commit change in the code base -
for example, "api", "store", etc.
For more details on the commit format, see the [AngularJS commit style
guide](https://docs.google.com/a/coreos.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#).

36
DCO Normal file
View File

@@ -0,0 +1,36 @@
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.

View File

@@ -0,0 +1,37 @@
## OEM configuration
The `coreos.oem.*` parameters follow the [os-release spec][os-release], but have been repurposed as a way for coreos-cloudinit to know about the OEM partition on this machine. Customizing this section is only needed when generating a new OEM of CoreOS from the SDK. The fields include:
- **id**: Lowercase string identifying the OEM
- **name**: Human-friendly string representing the OEM
- **version-id**: Lowercase string identifying the version of the OEM
- **home-url**: Link to the homepage of the provider or OEM
- **bug-report-url**: Link to a place to file bug reports about this OEM
coreos-cloudinit renders these fields to `/etc/oem-release`.
If no **id** field is provided, coreos-cloudinit will ignore this section.
For example, the following cloud-config document...
```
#cloud-config
coreos:
oem:
id: rackspace
name: Rackspace Cloud Servers
version-id: 168.0.0
home-url: https://www.rackspace.com/cloud/servers/
bug-report-url: https://github.com/coreos/coreos-overlay
```
...would be rendered to the following `/etc/oem-release`:
```
ID=rackspace
NAME="Rackspace Cloud Servers"
VERSION_ID=168.0.0
HOME_URL="https://www.rackspace.com/cloud/servers/"
BUG_REPORT_URL="https://github.com/coreos/coreos-overlay"
```
[os-release]: http://www.freedesktop.org/software/systemd/man/os-release.html

View File

@@ -1,16 +1,45 @@
# Customize with Cloud-Config
# Using Cloud-Config
CoreOS allows you to configure networking, create users, launch systemd units on startup and more. We've designed our implementation to allow the same cloud-config file to work across all of our supported platforms.
CoreOS allows you to declaratively customize various OS-level items, such as network configuration, user accounts, and systemd units. This document describes the full list of items we can configure. The `coreos-cloudinit` program uses these files as it configures the OS after startup or during runtime.
Only a subset of [cloud-config functionality][cloud-config] is implemented. A set of custom parameters were added to the cloud-config format that are specific to CoreOS. An example file containing all available options can be found at the bottom of this page.
## Configuration File
The file used by this system initialization program is called a "cloud-config" file. It is inspired by the [cloud-init][cloud-init] project's [cloud-config][cloud-config] file. which is "the defacto multi-distribution package that handles early initialization of a cloud instance" ([cloud-init docs][cloud-init-docs]). Because the cloud-init project includes tools which aren't used by CoreOS, only the relevant subset of its configuration items will be implemented in our cloud-config file. In addition to those, we added a few CoreOS-specific items, such as etcd configuration, OEM definition, and systemd units.
We've designed our implementation to allow the same cloud-config file to work across all of our supported platforms.
[cloud-init]: https://launchpad.net/cloud-init
[cloud-init-docs]: http://cloudinit.readthedocs.org/en/latest/index.html
[cloud-config]: http://cloudinit.readthedocs.org/en/latest/topics/format.html#cloud-config-data
## CoreOS Parameters
### File Format
### coreos.etcd
The cloud-config file uses the [YAML][yaml] file format, which uses whitespace and new-lines to delimit lists, associative arrays, and values.
The `coreos.etcd.*` options are translated to a partial systemd unit acting as an etcd configuration file.
A cloud-config file should contain an associative array which has zero or more of the following keys:
- `coreos`
- `ssh_authorized_keys`
- `hostname`
- `users`
- `write_files`
- `manage_etc_hosts`
The expected values for these keys are defined in the rest of this document.
[yaml]: https://en.wikipedia.org/wiki/YAML
### Providing Cloud-Config with Config-Drive
CoreOS tries to conform to each platform's native method to provide user data. Each cloud provider tends to be unique, but this complexity has been abstracted by CoreOS. You can view each platform's instructions on their documentation pages. The most universal way to provide cloud-config is [via config-drive](https://github.com/coreos/coreos-cloudinit/blob/master/Documentation/config-drive.md), which attaches a read-only device to the machine, that contains your cloud-config file.
## Configuration Parameters
### coreos
#### etcd
The `coreos.etcd.*` parameters will be translated to a partial systemd unit acting as an etcd configuration file.
We can use the templating feature of coreos-cloudinit to automate etcd configuration with the `$private_ipv4` and `$public_ipv4` fields. For example, the following cloud-config document...
```
@@ -36,57 +65,35 @@ Environment="ETCD_ADDR=203.0.113.29:4001"
Environment="ETCD_PEER_ADDR=192.0.2.13:7001"
```
For more information about the available configuration options, see the [etcd documentation][etcd-config].
For more information about the available configuration parameters, see the [etcd documentation][etcd-config].
Note that hyphens in the coreos.etcd.* keys are mapped to underscores.
[etcd-config]: https://github.com/coreos/etcd/blob/master/Documentation/configuration.md
### coreos.oem
#### update
These fields are borrowed from the [os-release spec][os-release] and repurposed
as a way for coreos-cloudinit to know about the OEM partition on this machine:
The `coreos.update.*` parameters manipulate settings related to how CoreOS instances are updated.
- **id**: Lowercase string identifying the OEM
- **name**: Human-friendly string representing the OEM
- **version-id**: Lowercase string identifying the version of the OEM
- **home-url**: Link to the homepage of the provider or OEM
- **bug-report-url**: Link to a place to file bug reports about this OEM
coreos-cloudinit renders these fields to `/etc/oem-release`.
If no **id** field is provided, coreos-cloudinit will ignore this section.
For example, the following cloud-config document...
- **reboot-strategy**: One of "reboot", "etcd-lock", "best-effort" or "off" for controlling when reboots are issued after an update is performed.
- _reboot_: Reboot immediately after an update is applied.
- _etcd-lock_: Reboot after first taking a distributed lock in etcd, this guarantees that only one host will reboot concurrently and that the cluster will remain available during the update.
- _best-effort_ - If etcd is running, "etcd-lock", otherwise simply "reboot".
- _off_ - Disable rebooting after updates are applied (not recommended).
```
#cloud-config
coreos:
oem:
id: rackspace
name: Rackspace Cloud Servers
version-id: 168.0.0
home-url: https://www.rackspace.com/cloud/servers/
bug-report-url: https://github.com/coreos/coreos-overlay
update:
reboot-strategy: etcd-lock
```
...would be rendered to the following `/etc/oem-release`:
#### units
```
ID=rackspace
NAME="Rackspace Cloud Servers"
VERSION_ID=168.0.0
HOME_URL="https://www.rackspace.com/cloud/servers/"
BUG_REPORT_URL="https://github.com/coreos/coreos-overlay"
```
[os-release]: http://www.freedesktop.org/software/systemd/man/os-release.html
### coreos.units
Arbitrary systemd units may be provided in the `coreos.units` attribute.
`coreos.units` is a list of objects with the following fields:
The `coreos.units.*` parameters define a list of arbitrary systemd units to start. Each item is an object with the following fields:
- **name**: String representing unit's name. Required.
- **runtime**: Boolean indicating whether or not to persist the unit across reboots. This is analagous to the `--runtime` argument to `systemd enable`. Default value is false.
- **enable**: Boolean indicating whether or not to handle the [Install] section of the unit file. This is similar to running `systemctl enable <name>`. Default value is false.
- **content**: Plaintext string representing entire unit file. If no value is provided, the unit is assumed to exist already.
- **command**: Command to execute on unit: start, stop, reload, restart, try-restart, reload-or-restart, reload-or-try-restart. Default value is restart.
@@ -102,6 +109,7 @@ Write a unit to disk, automatically starting it.
coreos:
units:
- name: docker-redis.service
command: start
content: |
[Unit]
Description=Redis container
@@ -130,14 +138,12 @@ coreos:
command: start
```
## Cloud-Config Parameters
### ssh_authorized_keys
Provided public SSH keys will be authorized for the `core` user.
The `ssh_authorized_keys` parameter adds public SSH keys which will be authorized for the `core` user.
The keys will be named "coreos-cloudinit" by default.
Override this with the `--ssh-key-name` flag when calling `coreos-cloudinit`.
Override this by using the `--ssh-key-name` flag when calling `coreos-cloudinit`.
```
#cloud-config
@@ -148,7 +154,7 @@ ssh_authorized_keys:
### hostname
The provided value will be used to set the system's hostname.
The `hostname` parameter defines the system's hostname.
This is the local part of a fully-qualified domain name (i.e. `foo` in `foo.example.com`).
```
@@ -159,8 +165,7 @@ hostname: coreos1
### users
Add or modify users with the `users` directive by providing a list of user objects, each consisting of the following fields.
Each field is optional and of type string unless otherwise noted.
The `users` parameter adds or modifies the specified list of users. Each user is an object which consists of the following fields. Each field is optional and of type string unless otherwise noted.
All but the `passwd` and `ssh-authorized-keys` fields will be ignored if the user already exists.
- **name**: Required. Login name of user
@@ -192,7 +197,7 @@ users:
- name: elroy
passwd: $6$5s2u6/jR$un0AvWnqilcgaNB3Mkxd5yYv6mTlWfOoCYHZmfi3LDKVltj.E8XNKEcwWm...
groups:
- staff
- sudo
- docker
ssh-authorized-keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0g+ZTxC7weoIJLUafOgrm+h...
@@ -218,9 +223,11 @@ perl -e 'print crypt("password","\$6\$SALT\$") . "\n"'
Using a higher number of rounds will help create more secure passwords, but given enough time, password hashes can be reversed. On most RPM based distributions there is a tool called mkpasswd available in the `expect` package, but this does not handle "rounds" nor advanced hashing algorithms.
#### Retrieving ssh authorized keys from a GitHub user
#### Retrieving SSH Authorized Keys
Using the field `coreos-ssh-import-github` you can make coreos-cloudinit to add the public ssh keys from a GitHub user as authorized keys to a server.
##### From a GitHub User
Using the `coreos-ssh-import-github` field, we can import public SSH keys from a GitHub user to use as authorized keys to a server.
```
#cloud-config
@@ -230,10 +237,10 @@ users:
coreos-ssh-import-github: elroy
```
#### Retrieving ssh authorized keys from an http endpoint
##### From an HTTP Endpoint
coreos-cloudinit can also pull public SSH keys from any http endpoint that matches [GitHub's API response format](https://developer.github.com/v3/users/keys/#list-public-keys-for-a-user).
For example, if you have an installation of GitHub Enterprise, you can provide a complete url with an authentication token:
We can also pull public SSH keys from any HTTP endpoint which matches [GitHub's API response format](https://developer.github.com/v3/users/keys/#list-public-keys-for-a-user).
For example, if you have an installation of GitHub Enterprise, you can provide a complete URL with an authentication token:
```
#cloud-config
@@ -243,7 +250,7 @@ users:
coreos-ssh-import-url: https://token:<OAUTH-TOKEN>@github-enterprise.example.com/users/elroy/keys
```
You can also provide any url which response matches that json format for public keys:
You can also specify any URL whose response matches the JSON format for public keys:
```
#cloud-config
@@ -255,8 +262,7 @@ users:
### write_files
Inject an arbitrary set of files to the local filesystem.
Provide a list of objects with the following attributes:
The `write-file` parameter defines a list of files to create on the local filesystem. Each file is represented as an associative array which has the following keys:
- **path**: Absolute location on disk where contents should be written
- **content**: Data to write at the provided `path`
@@ -266,10 +272,20 @@ Provide a list of objects with the following attributes:
Explicitly not implemented is the **encoding** attribute.
The **content** field must represent exactly what should be written to disk.
```
#cloud-config
write_files:
- path: /etc/fleet/fleet.conf
permissions: 0644
content: |
verbosity=1
metadata="region=us-west,type=ssd"
```
### manage_etc_hosts
Have coreos-cloudinit manage your /etc/hosts file for local name resolution.
The only supported value is "localhost" which will cause your system's hostname
The `manage_etc_hosts` parameter configures the contents of the `/etc/hosts` file, which is used for local name resolution.
Currently, the only supported value is "localhost" which will cause your system's hostname
to resolve to "127.0.0.1". This is helpful when the host does not have DNS
infrastructure in place to resolve its own hostname, for example, when using Vagrant.

202
LICENSE Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

5
NOTICE Normal file
View File

@@ -0,0 +1,5 @@
CoreOS Project
Copyright 2014 CoreOS, Inc
This product includes software developed at CoreOS, Inc.
(http://www.coreos.com/).

5
build
View File

@@ -1,6 +1,9 @@
#!/bin/bash -e
ORG_PATH="github.com/coreos"
REPO_PATH="${ORG_PATH}/coreos-cloudinit"
export GOBIN=${PWD}/bin
export GOPATH=${PWD}
go build -o bin/coreos-cloudinit github.com/coreos/coreos-cloudinit
go build -o bin/coreos-cloudinit ${REPO_PATH}

View File

@@ -5,14 +5,13 @@ import (
"fmt"
"log"
"os"
"strings"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/initialize"
"github.com/coreos/coreos-cloudinit/system"
)
const version = "0.4.0+git"
const version = "0.6.1"
func main() {
var printVersion bool
@@ -27,6 +26,9 @@ func main() {
var url string
flag.StringVar(&url, "from-url", "", "Download user-data from provided url")
var useProcCmdline bool
flag.BoolVar(&useProcCmdline, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", datasource.ProcCmdlineLocation, datasource.ProcCmdlineCloudConfigFlag))
var workspace string
flag.StringVar(&workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
@@ -40,18 +42,15 @@ func main() {
os.Exit(0)
}
if file != "" && url != "" {
fmt.Println("Provide one of --from-file or --from-url")
os.Exit(1)
}
var ds datasource.Datasource
if file != "" {
ds = datasource.NewLocalFile(file)
} else if url != "" {
ds = datasource.NewMetadataService(url)
} else if useProcCmdline {
ds = datasource.NewProcCmdline()
} else {
fmt.Println("Provide one of --from-file or --from-url")
fmt.Println("Provide one of --from-file, --from-url or --from-proc-cmdline")
os.Exit(1)
}
@@ -76,7 +75,7 @@ func main() {
userdata := string(userdataBytes)
userdata = env.Apply(userdata)
parsed, err := ParseUserData(userdata)
parsed, err := initialize.ParseUserData(userdata)
if err != nil {
log.Printf("Failed parsing user-data: %v", err)
if ignoreFailure {
@@ -108,22 +107,3 @@ func main() {
log.Fatalf("Failed resolving user-data: %v", err)
}
}
func ParseUserData(contents string) (interface{}, error) {
header := strings.SplitN(contents, "\n", 2)[0]
if strings.HasPrefix(header, "#!") {
log.Printf("Parsing user-data as script")
return system.Script(contents), nil
} else if header == "#cloud-config" {
log.Printf("Parsing user-data as cloud-config")
cfg, err := initialize.NewCloudConfig(contents)
if err != nil {
log.Fatal(err.Error())
}
return *cfg, nil
} else {
return nil, fmt.Errorf("Unrecognized user-data header: %s", header)
}
}

27
cover Executable file
View File

@@ -0,0 +1,27 @@
#!/bin/bash -e
#
# Generate coverage HTML for a package
# e.g. PKG=./initialize ./cover
#
if [ -z "$PKG" ]; then
echo "cover only works with a single package, sorry"
exit 255
fi
COVEROUT="coverage"
if ! [ -d "$COVEROUT" ]; then
mkdir "$COVEROUT"
fi
# strip out slashes and dots
COVERPKG=${PKG//\//}
COVERPKG=${COVERPKG//./}
# generate arg for "go test"
export COVER="-coverprofile ${COVEROUT}/${COVERPKG}.out"
source ./test
go tool cover -html=${COVEROUT}/${COVERPKG}.out

View File

@@ -1,6 +1,31 @@
package datasource
import (
"io/ioutil"
"net/http"
)
type Datasource interface {
Fetch() ([]byte, error)
Type() string
}
func fetchURL(url string) ([]byte, error) {
client := http.Client{}
resp, err := client.Get(url)
if err != nil {
return []byte{}, err
}
defer resp.Body.Close()
if resp.StatusCode / 100 != 2 {
return []byte{}, nil
}
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return respBytes, nil
}

View File

@@ -1,36 +1,15 @@
package datasource
import (
"io/ioutil"
"net/http"
)
type metadataService struct {
url string
client http.Client
url string
}
func NewMetadataService(url string) *metadataService {
return &metadataService{url, http.Client{}}
return &metadataService{url}
}
func (ms *metadataService) Fetch() ([]byte, error) {
resp, err := ms.client.Get(ms.url)
if err != nil {
return []byte{}, err
}
defer resp.Body.Close()
if resp.StatusCode / 100 != 2 {
return []byte{}, nil
}
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return respBytes, nil
return fetchURL(ms.url)
}
func (ms *metadataService) Type() string {

View File

@@ -0,0 +1,66 @@
package datasource
import (
"errors"
"io/ioutil"
"log"
"strings"
)
const (
ProcCmdlineLocation = "/proc/cmdline"
ProcCmdlineCloudConfigFlag = "cloud-config-url"
)
type procCmdline struct{}
func NewProcCmdline() *procCmdline {
return &procCmdline{}
}
func (self *procCmdline) Fetch() ([]byte, error) {
cmdline, err := ioutil.ReadFile(ProcCmdlineLocation)
if err != nil {
return nil, err
}
url, err := findCloudConfigURL(string(cmdline))
if err != nil {
return nil, err
}
cfg, err := fetchURL(url)
if err != nil {
return nil, err
}
return cfg, nil
}
func (self *procCmdline) Type() string {
return "proc-cmdline"
}
func findCloudConfigURL(input string) (url string, err error) {
err = errors.New("cloud-config-url not found")
for _, token := range strings.Split(input, " ") {
parts := strings.SplitN(token, "=", 2)
key := parts[0]
key = strings.Replace(key, "_", "-", -1)
if key != "cloud-config-url" {
continue
}
if len(parts) != 2 {
log.Printf("Found cloud-config-url in /proc/cmdline with no value, ignoring.")
continue
}
url = parts[1]
err = nil
}
return
}

View File

@@ -0,0 +1,47 @@
package datasource
import (
"testing"
)
func TestParseCmdlineCloudConfigFound(t *testing.T) {
tests := []struct {
input string
expect string
}{
{
"cloud-config-url=example.com",
"example.com",
},
{
"cloud_config_url=example.com",
"example.com",
},
{
"cloud-config-url cloud-config-url=example.com",
"example.com",
},
{
"cloud-config-url= cloud-config-url=example.com",
"example.com",
},
{
"cloud-config-url=one.example.com cloud-config-url=two.example.com",
"two.example.com",
},
{
"foo=bar cloud-config-url=example.com ping=pong",
"example.com",
},
}
for i, tt := range tests {
output, err := findCloudConfigURL(tt.input)
if output != tt.expect {
t.Errorf("Test case %d failed: %s != %s", i, output, tt.expect)
}
if err != nil {
t.Errorf("Test case %d produced error: %v", i, err)
}
}
}

View File

@@ -13,9 +13,10 @@ import (
type CloudConfig struct {
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
Coreos struct {
Etcd EtcdEnvironment
Units []system.Unit
OEM OEMRelease
Etcd EtcdEnvironment
Update map[string]string
Units []system.Unit
OEM OEMRelease
}
WriteFiles []system.File `yaml:"write_files"`
Hostname string
@@ -128,18 +129,26 @@ func Apply(cfg CloudConfig, env *Environment) error {
log.Printf("Wrote etcd config file to filesystem")
}
if s, ok := cfg.Coreos.Update["reboot-strategy"]; ok {
if err := WriteLocksmithConfig(s, env.Root()); err != nil {
log.Fatalf("Failed to write locksmith config to filesystem: %v", err)
}
log.Printf("Wrote locksmith config file to filesystem")
}
if len(cfg.Coreos.Units) > 0 {
commands := make(map[string]string, 0)
for _, unit := range cfg.Coreos.Units {
dst := system.UnitDestination(&unit, env.Root())
if unit.Content != "" {
log.Printf("Writing unit %s to filesystem", unit.Name)
dst, err := system.PlaceUnit(&unit, env.Root())
if err != nil {
log.Printf("Writing unit %s to filesystem at path %s", unit.Name, dst)
if err := system.PlaceUnit(&unit, dst); err != nil {
return err
}
log.Printf("Placed unit %s at %s", unit.Name, dst)
}
if unit.Enable {
if unit.Group() != "network" {
log.Printf("Enabling unit file %s", dst)
if err := system.EnableUnitFile(dst, unit.Runtime); err != nil {
@@ -160,6 +169,10 @@ func Apply(cfg CloudConfig, env *Environment) error {
}
}
if err := system.DaemonReload(); err != nil {
log.Fatalf("Failed systemd daemon-reload: %v", err)
}
for unit, command := range commands {
log.Printf("Calling unit command '%s %s'", command, unit)
res, err := system.RunUnitCommand(command, unit)

View File

@@ -32,6 +32,8 @@ func TestCloudConfig(t *testing.T) {
coreos:
etcd:
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
update:
reboot-strategy: reboot
units:
- name: 50-eth0.network
runtime: yes
@@ -129,6 +131,9 @@ Address=10.209.171.177/19
if cfg.Hostname != "trontastic" {
t.Errorf("Failed to parse hostname")
}
if cfg.Coreos.Update["reboot-strategy"] != "reboot" {
t.Errorf("Failed to parse locksmith strategy")
}
}
// Assert that our interface conversion doesn't panic

View File

@@ -5,7 +5,6 @@ import (
"os"
"os/exec"
"path"
"syscall"
"testing"
)
@@ -60,15 +59,15 @@ Environment="ETCD_PEER_BIND_ADDR=127.0.0.1:7002"
func TestEtcdEnvironmentWrittenToDisk(t *testing.T) {
ec := EtcdEnvironment{
"name": "node001",
"discovery": "http://disco.example.com/foobar",
"name": "node001",
"discovery": "http://disco.example.com/foobar",
"peer-bind-addr": "127.0.0.1:7002",
}
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
if err != nil {
t.Fatalf("Unable to create tempdir: %v", err)
}
defer syscall.Rmdir(dir)
defer os.RemoveAll(dir)
if err := WriteEtcdEnvironment(ec, dir); err != nil {
t.Fatalf("Processing of EtcdEnvironment failed: %v", err)
@@ -106,7 +105,7 @@ func TestEtcdEnvironmentWrittenToDiskDefaultToMachineID(t *testing.T) {
if err != nil {
t.Fatalf("Unable to create tempdir: %v", err)
}
defer syscall.Rmdir(dir)
defer os.RemoveAll(dir)
os.Mkdir(path.Join(dir, "etc"), os.FileMode(0755))
err = ioutil.WriteFile(path.Join(dir, "etc", "machine-id"), []byte("node007"), os.FileMode(0444))
@@ -134,6 +133,6 @@ Environment="ETCD_NAME=node007"
}
func rmdir(path string) error {
cmd := exec.Command("rm", "-rf", path)
return cmd.Run()
cmd := exec.Command("rm", "-rf", path)
return cmd.Run()
}

85
initialize/locksmith.go Normal file
View File

@@ -0,0 +1,85 @@
package initialize
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"github.com/coreos/coreos-cloudinit/system"
)
const locksmithUnit = "locksmithd.service"
// addStrategy creates an `/etc/coreos/update.conf` file with the requested
// strategy via rewriting the file on disk or by starting from
// `/usr/share/coreos/update.conf`.
func addStrategy(strategy string, root string) error {
etcUpdate := path.Join(root, "etc", "coreos", "update.conf")
usrUpdate := path.Join(root, "usr", "share", "coreos", "update.conf")
// Ensure /etc/coreos/ exists before attempting to write a file in it
os.MkdirAll(path.Join(root, "etc", "coreos"), 0755)
tmp, err := ioutil.TempFile(path.Join(root, "etc", "coreos"), ".update.conf")
if err != nil {
return err
}
err = tmp.Chmod(0644)
if err != nil {
return err
}
conf, err := os.Open(etcUpdate)
if os.IsNotExist(err) {
conf, err = os.Open(usrUpdate)
if err != nil {
return err
}
}
scanner := bufio.NewScanner(conf)
sawStrat := false
stratLine := "REBOOT_STRATEGY="+strategy
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "REBOOT_STRATEGY=") {
line = stratLine
sawStrat = true
}
fmt.Fprintln(tmp, line)
if err := scanner.Err(); err != nil {
return err
}
}
if !sawStrat {
fmt.Fprintln(tmp, stratLine)
}
return os.Rename(tmp.Name(), etcUpdate)
}
// WriteLocksmithConfig updates the `update.conf` file with a REBOOT_STRATEGY for locksmith.
func WriteLocksmithConfig(strategy string, root string) error {
cmd := "restart"
if strategy == "off" {
err := system.MaskUnit(locksmithUnit, root)
if err != nil {
return err
}
cmd = "stop"
} else {
return addStrategy(strategy, root)
}
if err := system.DaemonReload(); err != nil {
return err
}
if _, err := system.RunUnitCommand(cmd, locksmithUnit); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,91 @@
package initialize
import (
"io/ioutil"
"os"
"path"
"testing"
)
const (
base = `SERVER=https://example.com
GROUP=thegroupc`
configured = base + `
REBOOT_STRATEGY=awesome
`
expected = base + `
REBOOT_STRATEGY=etcd-lock
`
)
func setupFixtures(dir string) {
os.MkdirAll(path.Join(dir, "usr", "share", "coreos"), 0755)
os.MkdirAll(path.Join(dir, "run", "systemd", "system"), 0755)
ioutil.WriteFile(path.Join(dir, "usr", "share", "coreos", "update.conf"), []byte(base), 0644)
}
func TestLocksmithEnvironmentWrittenToDisk(t *testing.T) {
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
if err != nil {
t.Fatalf("Unable to create tempdir: %v", err)
}
defer os.RemoveAll(dir)
setupFixtures(dir)
for i := 0; i < 2; i++ {
if i == 1 {
err = ioutil.WriteFile(path.Join(dir, "etc", "coreos", "update.conf"), []byte(configured), 0644)
if err != nil {
t.Fatal(err)
}
}
if err := WriteLocksmithConfig("etcd-lock", dir); err != nil {
t.Fatalf("Processing of LocksmithEnvironment failed: %v", err)
}
fullPath := path.Join(dir, "etc", "coreos", "update.conf")
fi, err := os.Stat(fullPath)
if err != nil {
t.Fatalf("Unable to stat file: %v", err)
}
if fi.Mode() != os.FileMode(0644) {
t.Errorf("File has incorrect mode: %v", fi.Mode())
}
contents, err := ioutil.ReadFile(fullPath)
if err != nil {
t.Fatalf("Unable to read expected file: %v", err)
}
if string(contents) != expected {
t.Fatalf("File has incorrect contents, got %v, wanted %v", string(contents), expected)
}
}
}
func TestLocksmithEnvironmentMasked(t *testing.T) {
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
if err != nil {
t.Fatalf("Unable to create tempdir: %v", err)
}
defer os.RemoveAll(dir)
setupFixtures(dir)
if err := WriteLocksmithConfig("off", dir); err != nil {
t.Fatalf("Processing of LocksmithEnvironment failed: %v", err)
}
fullPath := path.Join(dir, "etc", "systemd", "system", "locksmithd.service")
target, err := os.Readlink(fullPath)
if err != nil {
t.Fatalf("Unable to read link %v", err)
}
if target != "/dev/null" {
t.Fatalf("Locksmith not masked, unit target %v", target)
}
}

View File

@@ -4,7 +4,6 @@ import (
"io/ioutil"
"os"
"path"
"syscall"
"testing"
)
@@ -20,7 +19,7 @@ func TestOEMReleaseWrittenToDisk(t *testing.T) {
if err != nil {
t.Fatalf("Unable to create tempdir: %v", err)
}
defer syscall.Rmdir(dir)
defer os.RemoveAll(dir)
if err := WriteOEMRelease(&oem, dir); err != nil {
t.Fatalf("Processing of EtcdEnvironment failed: %v", err)

33
initialize/user_data.go Normal file
View File

@@ -0,0 +1,33 @@
package initialize
import (
"fmt"
"log"
"strings"
"github.com/coreos/coreos-cloudinit/system"
)
func ParseUserData(contents string) (interface{}, error) {
header := strings.SplitN(contents, "\n", 2)[0]
// Explicitly trim the header so we can handle user-data from
// non-unix operating systems. The rest of the file is parsed
// by goyaml, which correctly handles CRLF.
header = strings.TrimSpace(header)
if strings.HasPrefix(header, "#!") {
log.Printf("Parsing user-data as script")
return system.Script(contents), nil
} else if header == "#cloud-config" {
log.Printf("Parsing user-data as cloud-config")
cfg, err := NewCloudConfig(contents)
if err != nil {
log.Fatal(err.Error())
}
return *cfg, nil
} else {
return nil, fmt.Errorf("Unrecognized user-data header: %s", header)
}
}

View File

@@ -0,0 +1,49 @@
package initialize
import (
"testing"
)
func TestParseHeaderCRLF(t *testing.T) {
configs := []string{
"#cloud-config\nfoo: bar",
"#cloud-config\r\nfoo: bar",
}
for i, config := range configs {
_, err := ParseUserData(config)
if err != nil {
t.Errorf("Failed parsing config %d: %v", i, err)
}
}
scripts := []string{
"#!bin/bash\necho foo",
"#!bin/bash\r\necho foo",
}
for i, script := range scripts {
_, err := ParseUserData(script)
if err != nil {
t.Errorf("Failed parsing script %d: %v", i, err)
}
}
}
func TestParseConfigCRLF(t *testing.T) {
contents := "#cloud-config\r\nhostname: foo\r\nssh_authorized_keys:\r\n - foobar\r\n"
ud, err := ParseUserData(contents)
if err != nil {
t.Fatalf("Failed parsing config: %v", err)
}
cfg := ud.(CloudConfig)
if cfg.Hostname != "foo" {
t.Error("Failed parsing hostname from config")
}
if len(cfg.SSHAuthorizedKeys) != 1 {
t.Error("Parsed incorrect number of SSH keys")
}
}

View File

@@ -11,10 +11,10 @@ import (
)
type File struct {
Encoding string
Content string
Owner string
Path string
Encoding string
Content string
Owner string
Path string
RawFilePermissions string `yaml:"permissions"`
}
@@ -31,7 +31,6 @@ func (f *File) Permissions() (os.FileMode, error) {
return os.FileMode(perm), nil
}
func WriteFile(f *File) error {
if f.Encoding != "" {
return fmt.Errorf("Unable to write file with encoding %s", f.Encoding)

View File

@@ -19,6 +19,7 @@ const fakeMachineID = "42000000000000000000000000000042"
type Unit struct {
Name string
Enable bool
Runtime bool
Content string
Command string
@@ -41,33 +42,40 @@ func (u *Unit) Group() (group string) {
type Script []byte
func PlaceUnit(u *Unit, root string) (string, error) {
// UnitDestination builds the appropriate absolte file path for
// the given unit. The root argument indicates the effective base
// directory of the system (similar to a chroot).
func UnitDestination(u *Unit, root string) string {
dir := "etc"
if u.Runtime {
dir = "run"
}
dst := path.Join(root, dir, "systemd", u.Group())
if _, err := os.Stat(dst); os.IsNotExist(err) {
if err := os.MkdirAll(dst, os.FileMode(0755)); err != nil {
return "", err
return path.Join(root, dir, "systemd", u.Group(), u.Name)
}
// PlaceUnit writes a unit file at the provided destination, creating
// parent directories as necessary.
func PlaceUnit(u *Unit, dst string) error {
dir := filepath.Dir(dst)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil {
return err
}
}
dst = path.Join(dst, u.Name)
file := File{
Path: dst,
Content: u.Content,
Path: dst,
Content: u.Content,
RawFilePermissions: "0644",
}
err := WriteFile(&file)
if err != nil {
return "", err
return err
}
return dst, nil
return nil
}
func EnableUnitFile(file string, runtime bool) error {
@@ -157,3 +165,11 @@ func MachineID(root string) string {
return id
}
func MaskUnit(unit string, root string) error {
masked := path.Join(root, "etc", "systemd", "system", unit)
if err := os.MkdirAll(path.Dir(masked), os.FileMode(0755)); err != nil {
return err
}
return os.Symlink("/dev/null", masked)
}

View File

@@ -4,15 +4,14 @@ import (
"io/ioutil"
"os"
"path"
"syscall"
"testing"
)
func TestPlaceNetworkUnit(t *testing.T) {
u := Unit{
Name: "50-eth0.network",
Runtime: true,
Content: `[Match]
Name: "50-eth0.network",
Runtime: true,
Content: `[Match]
Name=eth47
[Network]
@@ -24,14 +23,19 @@ Address=10.209.171.177/19
if err != nil {
t.Fatalf("Unable to create tempdir: %v", err)
}
defer syscall.Rmdir(dir)
defer os.RemoveAll(dir)
if _, err := PlaceUnit(&u, dir); err != nil {
dst := UnitDestination(&u, dir)
expectDst := path.Join(dir, "run", "systemd", "network", "50-eth0.network")
if dst != expectDst {
t.Fatalf("UnitDestination returned %s, expected %s", dst, expectDst)
}
if err := PlaceUnit(&u, dst); err != nil {
t.Fatalf("PlaceUnit failed: %v", err)
}
fullPath := path.Join(dir, "run", "systemd", "network", "50-eth0.network")
fi, err := os.Stat(fullPath)
fi, err := os.Stat(dst)
if err != nil {
t.Fatalf("Unable to stat file: %v", err)
}
@@ -40,27 +44,27 @@ Address=10.209.171.177/19
t.Errorf("File has incorrect mode: %v", fi.Mode())
}
contents, err := ioutil.ReadFile(fullPath)
contents, err := ioutil.ReadFile(dst)
if err != nil {
t.Fatalf("Unable to read expected file: %v", err)
}
expect := `[Match]
expectContents := `[Match]
Name=eth47
[Network]
Address=10.209.171.177/19
`
if string(contents) != expect {
t.Fatalf("File has incorrect contents '%s'.\nExpected '%s'", string(contents), expect)
if string(contents) != expectContents {
t.Fatalf("File has incorrect contents '%s'.\nExpected '%s'", string(contents), expectContents)
}
}
func TestPlaceMountUnit(t *testing.T) {
u := Unit{
Name: "media-state.mount",
Runtime: false,
Content: `[Mount]
Name: "media-state.mount",
Runtime: false,
Content: `[Mount]
What=/dev/sdb1
Where=/media/state
`,
@@ -70,14 +74,19 @@ Where=/media/state
if err != nil {
t.Fatalf("Unable to create tempdir: %v", err)
}
defer syscall.Rmdir(dir)
defer os.RemoveAll(dir)
if _, err := PlaceUnit(&u, dir); err != nil {
dst := UnitDestination(&u, dir)
expectDst := path.Join(dir, "etc", "systemd", "system", "media-state.mount")
if dst != expectDst {
t.Fatalf("UnitDestination returned %s, expected %s", dst, expectDst)
}
if err := PlaceUnit(&u, dst); err != nil {
t.Fatalf("PlaceUnit failed: %v", err)
}
fullPath := path.Join(dir, "etc", "systemd", "system", "media-state.mount")
fi, err := os.Stat(fullPath)
fi, err := os.Stat(dst)
if err != nil {
t.Fatalf("Unable to stat file: %v", err)
}
@@ -86,17 +95,17 @@ Where=/media/state
t.Errorf("File has incorrect mode: %v", fi.Mode())
}
contents, err := ioutil.ReadFile(fullPath)
contents, err := ioutil.ReadFile(dst)
if err != nil {
t.Fatalf("Unable to read expected file: %v", err)
}
expect := `[Mount]
expectContents := `[Mount]
What=/dev/sdb1
Where=/media/state
`
if string(contents) != expect {
t.Fatalf("File has incorrect contents '%s'.\nExpected '%s'", string(contents), expect)
if string(contents) != expectContents {
t.Fatalf("File has incorrect contents '%s'.\nExpected '%s'", string(contents), expectContents)
}
}
@@ -105,7 +114,7 @@ func TestMachineID(t *testing.T) {
if err != nil {
t.Fatalf("Unable to create tempdir: %v", err)
}
defer syscall.Rmdir(dir)
defer os.RemoveAll(dir)
os.Mkdir(path.Join(dir, "etc"), os.FileMode(0755))
ioutil.WriteFile(path.Join(dir, "etc", "machine-id"), []byte("node007\n"), os.FileMode(0444))
@@ -114,3 +123,22 @@ func TestMachineID(t *testing.T) {
t.Fatalf("File has incorrect contents")
}
}
func TestMaskUnit(t *testing.T) {
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
if err != nil {
t.Fatalf("Unable to create tempdir: %v", err)
}
defer os.RemoveAll(dir)
if err := MaskUnit("foo.service", dir); err != nil {
t.Fatalf("Unable to mask unit: %v", err)
}
fullPath := path.Join(dir, "etc", "systemd", "system", "foo.service")
target, err := os.Readlink(fullPath)
if err != nil {
t.Fatalf("Unable to read link", err)
}
if target != "/dev/null" {
t.Fatalf("unit not masked, got unit target", target)
}
}

View File

@@ -53,7 +53,7 @@ func CreateUser(u *User) error {
}
if u.PrimaryGroup != "" {
args = append(args, "--primary-group", u.PrimaryGroup)
args = append(args, "--gid", u.PrimaryGroup)
}
if len(u.Groups) > 0 {

39
test
View File

@@ -1,10 +1,37 @@
#!/bin/bash -e
#
# Run all coreos-cloudinit tests
# ./test
# ./test -v
#
# Run tests for one package
# PKG=initialize ./test
#
echo "Building bin/coreos-cloudinit"
. build
# Invoke ./cover for HTML output
COVER=${COVER:-"-cover"}
source ./build
declare -a TESTPKGS=(initialize system datasource)
if [ -z "$PKG" ]; then
GOFMTPATH="$TESTPKGS coreos-cloudinit.go"
# prepend repo path to each package
TESTPKGS=${TESTPKGS[@]/#/${REPO_PATH}/}
else
GOFMTPATH="$TESTPKGS"
# strip out slashes and dots from PKG=./foo/
TESTPKGS=${PKG//\//}
TESTPKGS=${TESTPKGS//./}
TESTPKGS=${TESTPKGS/#/${REPO_PATH}/}
fi
echo "Running tests..."
for pkg in "./initialize ./system"; do
go test -i $pkg
go test -v $pkg
done
go test -i ${TESTPKGS}
go test ${COVER} $@ ${TESTPKGS}
echo "Checking gofmt..."
fmtRes=$(gofmt -l $GOFMTPATH)
echo "Success"

View File

@@ -0,0 +1,12 @@
[Unit]
Description=Load cloud-config from url defined in /proc/cmdline
Requires=coreos-setup-environment.service
After=coreos-setup-environment.service
Before=user-config.target
ConditionKernelCommandLine=cloud-config-url
[Service]
Type=oneshot
RemainAfterExit=yes
EnvironmentFile=-/etc/environment
ExecStart=/usr/bin/coreos-cloudinit --from-proc-cmdline

View File

@@ -0,0 +1,24 @@
[Unit]
Description=Load cloud-config from %f
Requires=coreos-setup-environment.service
After=coreos-setup-environment.service
Before=user-config.target
ConditionFileNotEmpty=%f
# HACK: work around ordering between config drive and ec2 metadata It is
# possible for OpenStack style systems to provide both the metadata service
# and config drive, to prevent the two from stomping on eachother force
# this to run after OEM and after metadata (if it exsts). I'm doing this
# here instead of in the ec2 service because the ec2 unit is not written
# to disk until the OEM cloud config is evaluated and I want to make sure
# systemd knows about the ordering as early as possible.
# coreos-cloudinit could implement a simple lock but that cannot be used
# until after the systemd dbus calls are made non-blocking.
After=system-cloudinit@usr-share-oem-cloud\x2dconfig.yml.service
After=ec2-cloudinit.service
[Service]
Type=oneshot
RemainAfterExit=yes
EnvironmentFile=-/etc/environment
ExecStart=/usr/bin/coreos-cloudinit --from-file=%f

View File

@@ -6,3 +6,6 @@ After=system-config.target
# Load user_data placed by coreos-install
Requires=user-cloudinit@var-lib-coreos\x2dinstall-user_data.service
After=user-cloudinit@var-lib-coreos\x2dinstall-user_data.service
Requires=user-cloudinit-proc-cmdline.service
After=user-cloudinit-proc-cmdline.service