Compare commits

..

297 Commits

Author SHA1 Message Date
e8f51fe59d fixup appending keys
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2017-11-28 00:34:57 +03:00
d7b5d86bdb fix lock and resize order
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2017-11-22 11:51:25 +03:00
99671182f1 fixup for nodev on /tmp
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2017-11-02 14:49:27 +03:00
3d8a829986 more simple go checking
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2016-09-27 09:09:55 +03:00
002446413d fix openstack metadata
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2016-09-27 09:05:53 +03:00
f33a90587f Merge branch 'upstream' 2015-11-19 11:22:48 +00:00
Jonathan Boulle
fa0178cd47 Merge pull request #400 from sethp/coreos-cloudinit/gzip-magic
gzip autodetection
2015-11-11 13:49:14 -08:00
Seth Pellegrino
778a47b957 userdata: gzip autodetection
look for the gzip magic number at the beginning of a data source, and,
if found, decompress the input before further processing.

Addresses coreos/bugs#741.
2015-11-11 08:21:00 -08:00
Seth Pellegrino
86909e5bcb test: added coreos-cloudinit_test to test script
Include the root project directory in packages
to be built/tested.
2015-11-11 08:06:36 -08:00
Josh Wood
0fd3cd2fae Merge pull request #406 from joshix/doubledash0
Vmware-guestinfo: Double hyphen long options.
2015-11-10 10:21:06 -08:00
f9f1f229ff fix script running when specify in user-data
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-11-10 13:28:25 +00:00
Josh Wood
ad81cf7f78 Vmware-guestinfo: Double hyphen long options.
In line with https://github.com/coreos/docs/issues/650,
revert a little bit of https://github.com/coreos/coreos-cloudinit/pull/404
to document `--longoption` with two hyphens in document and Usage.
2015-11-09 16:31:30 -08:00
6cad908751 always run commands in runcmd
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-11-09 18:29:30 +00:00
eb27f373ee fix build
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-11-09 09:39:24 +00:00
89181cbf0f add windows
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-11-09 09:37:23 +00:00
f24b0e886f fix build
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-11-09 09:18:51 +00:00
414957d985 fix build
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-11-09 09:08:44 +00:00
d407b82968 fix import
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-11-09 08:24:52 +00:00
668f322607 Merge branch 'upstream' 2015-11-09 08:15:44 +00:00
Josh Wood
0a500a19ff Merge pull request #405 from omkensey/doc-link-blob-fix
docs: fix links to specific blobs
2015-11-08 23:54:34 -08:00
30aa7a9acc use vendoring, build for netbsd/openbsd
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-11-09 07:23:54 +00:00
Joe Thompson
b9f34d93ad docs: fix links to specific blobs
Rather than link to specific blob hashes, now that things have been synced to 0.4.9 we can link to the 0.4 release for original etcd; also fixes a link that was to a blob hash of configuration.md instead of the head of master for etcd.
2015-11-09 02:05:37 -05:00
Josh Wood
2f5d8cc188 Merge pull request #404 from joshix/backdoorname
Docs/vmware-backdoor: Rename backdoor to guestinfo
2015-11-05 15:35:45 -08:00
Josh Wood
b56c0f5609 Docs/vmware-backdoor: Rename backdoor to guestinfo
Based on results of google searches on a few possible titles
to improve on the connotation of backdoor, Guestinfo Configuration
Interface (as part of the RPC API) seems recognizable in vmware
circles. Friends call it just guestinfo.

This changeset also makes docs and usage for this flag appear with
a single hypen (-flag) like most go programs do.
2015-11-05 15:27:33 -08:00
Alex Crawford
cd1994b007 Merge pull request #403 from mdlayher/digitalocean_anchor_gateway
network: do not assign gateway for DigitalOcean anchor IP address
2015-10-27 09:10:34 -07:00
Matt Layher
1fd780befc network: do not assign gateway for DigitalOcean anchor IP address 2015-10-27 10:49:05 -04:00
Rob Szumski
8847a471c5 Merge pull request #393 from endocode/kayrus/vmware
Updated vmware info / Fixed help help message
2015-10-21 09:20:59 -07:00
kayrus
c0c144bd56 Added additional vmware info, fixed cli help 2015-10-19 10:33:27 +02:00
Alex Crawford
1d962916b9 Merge pull request #402 from xiang90/etcd2_wal
config: add wal_dir into etcd2.go
2015-10-15 12:29:49 -07:00
Xiang Li
bda6668f00 config: add wal_dir into etcd2.go 2015-10-15 09:36:48 -07:00
Alex Crawford
0f828db9a3 Merge pull request #401 from mischief/update-fleet-options
config: sync fleet options
2015-10-14 11:05:17 -07:00
Nick Owens
5970000589 config: sync fleet options 2015-10-14 11:02:59 -07:00
Alex Crawford
7870fa8c9d Merge pull request #399 from mdlayher/digitalocean_floatingip
datasource, network: add support for DigitalOcean floating IPs
2015-10-13 11:59:46 -07:00
Matt Layher
b4d45306b2 datasource, network: add support for DigitalOcean floating IPs 2015-10-09 10:52:05 -04:00
Michael Marineau
3c2b5e6636 Merge pull request #398 from marineam/gomax
main: default to GOMAXPROCS=1
2015-09-30 20:43:03 -07:00
Michael Marineau
bf743b3060 main: default to GOMAXPROCS=1 2015-09-30 17:23:40 -07:00
Jonathan Boulle
3b98be7788 Merge pull request #396 from crawford/bugs
readme: add link to coreos/bugs
2015-09-24 19:44:48 -07:00
Alex Crawford
746685023f readme: add link to coreos/bugs 2015-09-24 17:50:06 -07:00
Alex Crawford
a0fcbb16d6 Merge pull request #395 from crawford/locksmith
config: add group and window options for locksmith
2015-09-23 23:21:19 -07:00
Alex Crawford
f63fa39a2d config: format license 2015-09-23 23:19:47 -07:00
Alex Crawford
0ae90f3b22 config: add group and window options for locksmith
The regular expression for RebootWindowLength comes from
https://golang.org/pkg/time/#ParseDuration.
2015-09-23 23:19:41 -07:00
Alex Crawford
dee67b964a Merge pull request #385 from endocode/kayrus/fleet
doc: added coreos-cloudinit options description
2015-09-21 10:32:20 -07:00
Michael Marineau
05062188f1 Merge pull request #391 from marineam/go-1.5.1
Fix and enable testing on Go 1.5
2015-09-16 17:34:43 -07:00
Michael Marineau
5405fc9d0d network: update error check for Go 1.5
Changed upstream:
055ecb7be5
2015-09-16 13:26:21 -07:00
Michael Marineau
c7f327bb89 travis: enable go 1.5 2015-09-16 13:02:45 -07:00
Alex Crawford
71e2b2bddb Merge pull request #389 from stresler/patch-1
cloudinit: Removing convert-netconf from Packet OEM
2015-09-11 09:13:10 -07:00
stresler
8fac253214 Removing convert-netconf from packet OEM
We still utilize the network code on first boot, so it should remain until we implement ignition, but we don't want it on subsequent boots, which is what this line would do.
2015-09-11 12:00:10 -04:00
Alex Crawford
e19fd09664 Merge pull request #386 from crawford/quotes
docs: quote everything
2015-09-08 18:45:52 -07:00
Alex Crawford
4a25948b53 docs: quote everything
I realize this is one of the selling points of YAML, but it causes far
too much confusion. Turns out typing is a good thing.
2015-09-08 18:40:21 -07:00
kayrus
f5cc75299a doc: added coreos-cloudinit options description 2015-09-08 12:23:10 +02:00
Michael Marineau
f816819c6d Merge pull request #380 from endocode/kayrus/mod_cloudinit_locations
Filled cloud providers cloud-config URLs
2015-09-07 10:41:49 -07:00
Alex Crawford
5f688a0a21 Merge pull request #379 from endocode/kayrus/trim_fix
cloudinit: trim trailing whitespaces in #cloud-config header
2015-09-07 10:09:42 -07:00
kayrus
f92dcb7968 cloudinit: trim trailing whitespaces in #cloud-config header 2015-09-07 18:30:02 +02:00
Alex Crawford
bb71f5e072 Merge pull request #383 from sigma/t/feature/vmware-config-url
datasource/vmware: introduce guestinfo.coreos.config.url
2015-09-04 09:20:42 -07:00
Yann Hodique
ed512c1cac datasource/vmware: introduce guestinfo.coreos.config.url
allow use of a URL instead of an inline document.
Inline document takes precedence.
2015-09-03 20:50:19 -07:00
Alex Crawford
94f8e00054 Merge pull request #382 from sigma/t/fix/assertion
datasource/vmware: fix assertion
2015-09-03 18:39:22 -07:00
Yann Hodique
b5cb942acb datasource/vmware: fix assertion
we definitely don't always want nil
2015-09-03 18:15:23 -07:00
Rob Szumski
de38ac5c98 Merge pull request #381 from endocode/kayrus/new_docs
docs: added cloud-config headers explanation
2015-09-02 08:54:28 -07:00
kayrus
dfc5f2627f Added cloud-config headers explanation 2015-09-02 15:12:27 +02:00
Alex Crawford
057e8094d5 Merge pull request #345 from crawford/vmware
datasource: add vmware backdoor
2015-09-01 15:41:01 -07:00
Alex Crawford
15b50d4712 doc: Add docs for VMware backdoor 2015-09-01 15:36:36 -07:00
Alex Crawford
dda5032296 cloudinit: add vmware backdoor 2015-09-01 15:36:36 -07:00
Alex Crawford
d675638776 datasource: add vmware 2015-09-01 15:36:35 -07:00
kayrus
61e3595520 Added table for cloud-config locations 2015-08-31 12:04:23 +02:00
kayrus
4f76283917 Filled cloud providers cloud-config URLs 2015-08-28 12:58:00 +02:00
Rob Szumski
9c4aca6c9a Merge pull request #366 from endocode/master
docs: add cloud-config locations
2015-08-26 10:06:26 -04:00
Alex Crawford
13dc11abf3 godeps: add github.com/sigma/vmw-guestinfo 2015-08-24 09:03:29 -07:00
Alex Crawford
9ba25550a1 datasource: use opaque object instead of string
Rather than serializing the network config into a string and then later
deserializing it, just pass the object through directly.
2015-08-24 09:03:29 -07:00
Alex Crawford
81ffa056bd log: use log.Print instead of fmt.Print 2015-08-23 19:30:03 -07:00
kayrus
8c804a1124 Removed {{site.baseurl}} 2015-08-18 11:31:52 +02:00
kayrus
0a46b32c88 Updated cloudconfig docs
* Fixed openstack config-drive link
* Added cloud-config-locations.md
2015-08-17 16:53:33 +02:00
Alex Crawford
fac805dc11 Merge pull request #375 from crawford/build
build: extract the version number from git
2015-08-12 11:09:29 -07:00
Alex Crawford
94ea0b99ea Merge pull request #374 from crawford/timeout
pkg/http: up the timeout to 10 seconds
2015-08-12 11:08:04 -07:00
Alex Crawford
56a80d84cf build: extract the version number from git
Update the tests as well.
2015-08-12 11:05:04 -07:00
Alex Crawford
00c9174da4 pkg/http: up the timeout to 10 seconds
Additionally, fix the units on that multiplication. This isn't
acceleration.
2015-08-11 10:42:41 -07:00
Alex Crawford
ec8742c9ba Merge pull request #371 from crawford/http-client
pkg: update HttpClient to use newer Go features
2015-08-07 20:03:01 -07:00
Alex Crawford
b3b09aeb19 pkg: update HttpClient to use newer Go features 2015-08-07 19:30:47 -07:00
Alex Crawford
481d98c0b5 Merge pull request #370 from crawford/etcd2
config: update flags for etcd 2.1
2015-08-07 17:03:43 -07:00
Alex Crawford
f30727a675 config: update flags for etcd 2.1 2015-08-07 16:20:36 -07:00
kayrus
fc4efb086b Merge https://github.com/coreos/coreos-cloudinit 2015-07-28 14:40:18 +02:00
kayrus
5383bd1f07 Fixed Cloud-Config examples 2015-07-27 18:29:21 +02:00
Alex Crawford
e1305937e6 Merge pull request #362 from bodgit/mkisofs
Add note for creating config-drive ISO on OS X
2015-07-23 18:42:37 -07:00
Alex Crawford
20c4653ecf Merge pull request #365 from cusspvz/flannel/add-public-ip-opt
Add PublicIP option on flannel
2015-07-23 13:41:06 -07:00
José Moreira
43c6da06a5 add public_ip opt on cloud-config.md 2015-07-23 17:04:37 +01:00
José Moreira
7ab84601c3 Add PublicIP opt 2015-07-22 22:54:03 +01:00
Alex Crawford
a24b23663c Merge pull request #364 from crawford/etcd
config: specific valid values for ETCD_PROXY
2015-07-21 22:21:25 -07:00
Alex Crawford
91fe744bd2 config: specific valid values for ETCD_PROXY 2015-07-21 14:20:10 -07:00
Matt Dainty
eb8fc045ee Add note for creating config-drive ISO on OS X 2015-07-20 10:27:02 +01:00
Alex Crawford
ba83b2871f coreos-cloudinit: bump to v1.5.0+git 2015-07-14 11:55:39 -07:00
Alex Crawford
f36821f7ce coreos-cloudinit: bump to v1.5.0 2015-07-14 11:54:56 -07:00
8c916a8c22 fix partition
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-07-14 15:08:12 +03:00
732ff09ccd fix for various fdisk versions
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-07-13 15:47:20 +03:00
Alex Crawford
97fe210760 Merge pull request #356 from crawford/ignition
config: recognize Ignition configs and no-op
2015-07-10 16:34:18 -07:00
Alex Crawford
c6400f7751 config: recognize Ignition configs and no-op 2015-07-10 16:32:57 -07:00
Alex Crawford
f6647634f0 Merge pull request #352 from packethost/packet-datasource
datasource: add packethost metadata
2015-07-10 12:25:37 -07:00
Sam Tresler
837d3d3622 datasource: add packethost metadata 2015-07-10 15:13:57 -04:00
Alex Crawford
1063a4b9ee Merge pull request #351 from packethost/bond-config-options
Bond options persisted to the generated netdev file.
2015-07-10 09:08:31 -07:00
28db10bbf3 rewrite resize
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-07-10 16:11:45 +03:00
52fc61f2d1 fix
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-06-30 16:04:41 +03:00
Sam Tresler
081f77a102 Persisting bond options to the netdev file, and updating the test. 2015-06-23 11:21:26 -04:00
Alex Crawford
41289286ca Merge pull request #354 from packethost/go-ci-versions
Go ci versions
2015-06-22 16:04:56 -07:00
Sam Tresler
d50a4069a6 Removing goland 1.3 and 1.2 from Travis testing 2015-06-22 18:43:27 -04:00
8e1ce09b0d unlock user only if it locked
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-06-04 14:11:39 +03:00
bafcbde165 remove external findmnt dep
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-06-02 23:54:05 +03:00
Alex Crawford
be0c9c56e4 Merge pull request #347 from crawford/import
doc: deprecate coreos-ssh-import-*
2015-05-28 13:45:02 -07:00
Alex Crawford
6467f06656 doc: deprecate coreos-ssh-import-* 2015-05-28 13:35:51 -07:00
Alex Crawford
7a05e63fcc coreos-cloudinit: bump to v1.4.1+git 2015-05-12 17:08:27 -07:00
Alex Crawford
ca6f97d050 coreos-cloudinit: bump to v1.4.1 2015-05-12 17:08:06 -07:00
Alex Crawford
d086bca9e4 Merge pull request #336 from crawford/doc
docs: fix typo with etcd2 env vars
2015-04-28 13:48:05 -07:00
Alex Crawford
d25f18776f docs: fix typo with etcd2 env vars 2015-04-27 18:03:13 -07:00
465cb76917 process runcmd
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-04-23 17:40:28 +03:00
Alex Crawford
c583b77cdb Merge pull request #335 from crawford/github
config: deprecate fetching SSH keys from remote endpoints
2015-04-20 11:29:38 -07:00
Alex Crawford
ed4d5fac4c config: deprecate fetching SSH keys from remote endpoints 2015-04-20 11:23:35 -07:00
Rob Szumski
40429204ba Merge pull request #333 from coreos/robszumski-patch-1
docs: add info about discovery size parameter
2015-04-16 12:04:49 -07:00
Rob Szumski
d72d54be59 docs: add info about discovery size parameter 2015-04-16 12:02:37 -07:00
Alex Crawford
373c7ecbd9 coreos-cloudinit: bump to v1.4.0+git 2015-04-09 17:48:36 -07:00
Alex Crawford
31c46c7051 coreos-cloudinit: bump to v1.4.0 2015-04-09 17:48:14 -07:00
0deecce2de fix resize
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-04-08 13:09:25 +03:00
d58264fc8c fix freebsd
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-04-08 12:00:41 +03:00
Alex Crawford
66ec7d805c Merge pull request #330 from crawford/etcd
config: add support for etcd2
2015-04-07 14:40:47 -07:00
Alex Crawford
2563896f89 docs: use etcd2 instead of etcd 2015-04-07 14:24:50 -07:00
81de7a1151 fix partition resize
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-04-05 17:03:28 +03:00
616b356754 add ubuntu workaround
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-04-05 01:16:48 +03:00
Alex Crawford
94a242cc58 config: add support for etcd2 2015-04-03 17:29:32 -07:00
20416969bd Merge branch 'master' of github.com:coreos/coreos-cloudinit
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-04-02 13:34:57 +03:00
Alex Crawford
5b159fcf56 coreos-cloudinit: bump to v1.3.4+git 2015-04-01 14:58:25 -07:00
Alex Crawford
a9e8940132 coreos-cloudinit: bump to v1.3.4 2015-04-01 14:58:09 -07:00
Alex Crawford
cf194ab85e Merge pull request #326 from richardmarshall/user_shell_config
config/system: add shell user attribute
2015-04-01 11:02:15 -07:00
Alex Crawford
33bc5fc63d validate: warn on deprecated keys 2015-03-30 13:52:57 -07:00
Alex Crawford
09f6a279ef config: deprecate etcd2 flags from etcd structure 2015-03-30 13:52:57 -07:00
e86ab7a185 remove debug
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-03-30 12:06:01 +03:00
2be9bc5c43 fix
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-03-30 11:55:46 +03:00
de2a74b621 fix
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-03-30 10:51:56 +03:00
da65c72ea4 fix
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-03-30 10:33:17 +03:00
dda4e55470 fix
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-03-30 10:29:18 +03:00
0c98d05ebf tst
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-03-28 20:08:30 +03:00
7449a4a5db update
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-03-28 20:04:09 +03:00
94d56f972a add 32 and 64 bit cloudinit
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-03-27 09:23:31 +03:00
ed4b3c90ff fix import
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-03-26 13:36:11 +03:00
1285e5da2d fix
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-03-26 12:26:16 +03:00
5bbc02c647 fix
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-03-26 11:53:54 +03:00
b8521294cd merge do
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-03-26 10:43:37 +03:00
0bc1edbd9d Merge branch 'master' into generic 2015-03-26 10:30:02 +03:00
993af2705a up
Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-03-26 10:23:27 +03:00
Richard Marshall
e8c8b811fe docs: add user shell field 2015-03-06 21:12:43 -08:00
Richard Marshall
f5ecc05d62 config/system: add shell user attribute
This adds support for specifying the login shell of created users.
2015-03-06 14:16:19 -08:00
Alex Crawford
66a2f00679 coreos-cloudinit: bump to v1.3.3+git 2015-02-24 12:25:00 -08:00
Alex Crawford
14cad6f7c3 coreos-cloudinit: bump to v1.3.3 2015-02-24 12:24:37 -08:00
Alex Crawford
6f188bd5d4 Merge pull request #319 from Vladimiroff/fix-cloudsigma-empty-ssh-keys
Make sure public ssh key is not empty from CloudSigma's server context
2015-02-23 10:58:17 -08:00
Alex Crawford
41832ab19e Merge pull request #320 from ibuildthecloud/typo-contents
Fix typo, "contents" should be "content"
2015-02-23 10:41:55 -08:00
Darren Shepherd
672e4c07af Fix typo, "contents" should be "content"
The validation of the encoding for write_files was looking
for a node named "contents" when the node name is "content"

Signed-off-by: Darren Shepherd <darren@rancher.com>
2015-02-23 09:17:21 -07:00
Kiril Vladimirov
be53013431 fix(datasource/CloudSigma): Add a test for an empty ssh key 2015-02-22 05:38:57 +02:00
Kiril Vladimirov
c30fc51b03 fix(datasource/CloudSigma): Make sure public ssh key is not empty
Even when public ssh key is not set by the user, CloudSigma's server
context has a key `meta.ssh_public_key` which is just an empty string.
So instead of just relying on the "comma ok" idiom I make sure the value
is not an empty string.
2015-02-21 19:31:01 +02:00
Rob Szumski
b429eaab84 docs: fix formatting 2015-02-18 15:05:28 -08:00
Alex Crawford
e0104e6d93 coreos-cloudinit: bump to v1.3.2+git 2015-02-18 11:13:34 -08:00
Alex Crawford
7bf9712724 coreos-cloudinit: bump to v1.3.2 2015-02-18 11:12:52 -08:00
Alex Crawford
78b0f82918 Merge pull request #318 from crawford/filesystem
configdrive: correct network config reading and improve tests
2015-02-17 13:40:05 -08:00
Alex Crawford
987aa21883 configdrive: check the network config path
Check to make sure that a network config path has been specified before
trying to read from it. Otherwise, it will end up trying to read a
directory.
2015-02-17 13:27:30 -08:00
Alex Crawford
47ac4f6931 test: add directory support to MockFilesystem 2015-02-17 13:27:30 -08:00
Alex Crawford
f8aa7a43b8 coreos-cloudinit: bump to v1.3.1+git 2015-02-13 10:25:01 -08:00
Alex Crawford
2fe0b0b2a8 coreos-cloudinit: bump to v1.3.1 2015-02-13 10:24:41 -08:00
Alex Crawford
19ce7ac849 Merge pull request #317 from crawford/json
configdrive: check metadata length before parsing
2015-02-13 10:22:57 -08:00
Alex Crawford
477053ffde configdrive: check metadata length before parsing
This was lost in some of the recent refactoring.
2015-02-13 10:20:08 -08:00
Alex Crawford
eb0d2dbfa3 coreos-cloudinit: bump to v1.3.0+git 2015-02-11 17:19:32 -08:00
Alex Crawford
18caa5bf07 coreos-cloudinit: bump to v1.3.0 2015-02-11 17:19:05 -08:00
Alex Crawford
a27bbb912f Merge pull request #315 from crawford/cloudsigma
oem: add CloudSigma OEM
2015-02-11 17:15:23 -08:00
Alex Crawford
3b2af743bd oem: add CloudSigma OEM 2015-02-11 17:03:24 -08:00
Alex Crawford
995bc63abe Merge pull request #313 from jimmycuadra/etcd-2.0-configuration
Add etcd 2.0 configuration support
2015-02-09 12:33:28 -08:00
Alex Crawford
a28f870302 Merge pull request #314 from robszumski/master
docs: link to CoreUpdate and Omaha
2015-02-06 14:15:39 -08:00
Rob Szumski
a3357c273c docs: update links 2015-02-06 13:43:06 -08:00
Rob Szumski
080c698ec2 docs: link to CoreUpdate and Omaha 2015-02-06 13:31:50 -08:00
Jimmy Cuadra
afbf1dbb3a Add etcd 2.0 configuration flag support. 2015-02-05 02:57:11 -08:00
Alex Crawford
a275e18533 Merge pull request #308 from demonbane/github-capitalization
Fix GitHub capitalization
2015-01-28 14:28:53 -08:00
Alex Malinovich
cf3baa8805 Fix GitHub capitalization 2015-01-28 14:19:08 -08:00
Jonathan Boulle
ed84bcef04 Merge pull request #307 from jonboulle/master
docs: fix typo
2015-01-28 10:25:15 -08:00
Jonathan Boulle
7d8b29e597 docs: fix typo 2015-01-28 10:19:16 -08:00
Alex Crawford
4eaaa5c927 Merge pull request #304 from crawford/metadata
datasource: improve metadata handling
2015-01-27 14:36:12 -08:00
Alex Crawford
536f8acf2a config: remove network config from CloudConfig 2015-01-26 17:35:08 -08:00
Alex Crawford
9605b5edf2 datasource: remove FetchNetworkConfig step
Its easier to let each datasource grab all metadata in the FetchMetadata
stage than to break it into multiple stages.
2015-01-26 16:08:26 -08:00
Alex Crawford
42153edbbc initialize: change env to process from metadata
We don't use general substitutions so just have env pick the values it
wants to substitute.
2015-01-26 16:08:26 -08:00
Alex Crawford
650a239fdb metadata: simplify merging of metadata
Add an internal field for CloudConfig to make it easier to distinguish.
Instead of creating two CloudConfigs and merging them, just merge the
metadata into the existing CloudConfig.
2015-01-26 16:08:26 -08:00
Alex Crawford
3e47c09b41 datasource: replace metadata map with struct
The loosely-typed metadata map is a load of crap. Make it a struct and
let the compiler help us out.
2015-01-26 15:57:57 -08:00
Alex Crawford
d4c617fc23 config: standardize interface a bit 2015-01-26 15:57:55 -08:00
Alex Crawford
9441586229 test: check all root golang files 2015-01-26 15:57:54 -08:00
Alex Crawford
be62a1df66 test: DRY out MockFilesystem 2015-01-26 15:57:51 -08:00
Jonathan Boulle
c093e44049 Merge pull request #305 from jonboulle/copyright
*: switch to line comments for copyright
2015-01-25 10:28:52 -08:00
Jonathan Boulle
be68a8e5cc *: switch to line comments for copyright
Build tags are not compatible with block comments. Also adds copyright
header to a few places it was missing.
2015-01-24 19:32:33 -08:00
Alex Crawford
58b4de8093 coreos-cloudinit: bump to v1.2.1+git 2015-01-21 14:29:51 -08:00
Alex Crawford
ae3676096c coreos-cloudinit: bump to v1.2.1 2015-01-21 14:29:25 -08:00
Alex Crawford
a548b557ed doc: add coreos-ssh-import-github-users 2015-01-21 14:28:43 -08:00
Alex Crawford
a9c132a706 coreos-cloudinit: bump to v1.2.0+git 2015-01-20 14:42:48 -08:00
Alex Crawford
c3c4b86a3b coreos-cloudinit: bump to v1.2.0 2015-01-20 14:42:18 -08:00
Alex Crawford
44142ff8af Merge pull request #301 from crawford/github
config: add support for multiple github users
2015-01-20 14:26:38 -08:00
Alex Crawford
e9529ede44 config: add support for multiple github users 2015-01-20 13:45:52 -08:00
Alex Crawford
4b5b801171 Merge pull request #295 from crawford/rules
config/validate: add some sanity checks
2015-01-15 16:25:37 -08:00
Alex Crawford
551cbb1e5d config/validate: add rule for file encoding 2015-01-15 15:01:44 -08:00
Alex Crawford
3c93938f8a decode: refactor file decoding into config package 2015-01-15 15:01:44 -08:00
Alex Crawford
f61c08c246 config/validate: add rule for coreos.write_files 2015-01-15 15:01:44 -08:00
Alex Crawford
571903cec6 config/validate: add rule for etcd discovery token 2015-01-15 15:01:44 -08:00
Alex Crawford
bdbd1930ed config/validate: add rule for file paths 2015-01-15 15:01:44 -08:00
Alex Crawford
cc75a943ba Merge pull request #300 from crawford/float
validate: allow promotion of int to float64
2015-01-15 13:17:44 -08:00
Alex Crawford
fc77ba6355 validate: allow promotion of int to float64 2015-01-14 17:54:01 -08:00
Eugene Yakubovich
7cfa0df7c4 Merge pull request #299 from eyakubovich/master
config: document and update flannel config values
2015-01-13 15:55:42 -08:00
Eugene Yakubovich
58f0dadaf9 config: document and update flannel config values
Fixes #297
2015-01-13 15:51:47 -08:00
Michael Marineau
1ab530f157 Merge pull request #293 from realestate-com-au/master
select first available hostname returned by EC2 metadata.
2015-01-05 12:47:27 -08:00
Kevin Yung
13e4b77130 ec2: allow spaces seperated hostname in metadata
AWS hostname metadata will return space seperated hostname and domain
names when DHCPOptionSet is using multiple domain names. This patch will
cater for this scenario.
2015-01-05 16:01:57 +11:00
Alex Crawford
54c62cbb70 coreos-cloudinit: bump to v1.1.0+git 2014-12-30 16:52:10 +01:00
Alex Crawford
c8e864fef5 coreos-cloudinit: bump to v1.1.0 2014-12-30 16:51:24 +01:00
Alex Crawford
60a3377e7c Merge pull request #290 from crawford/yaml
Improved YAML parsing
2014-12-30 16:24:12 +01:00
Alex Crawford
5527f09778 config: fix parsing of file permissions
These reintroduces the braindead '744' syntax for file permissions. Even
though this number isn't octal, it is assumed by convention to be. In
order to pull this off, coerceNodes() was introduced to try to
counteract the type inferrencing that occurs during the yaml
unmarshalling. The config is unmarshalled twice: once into an empty
interface and once into the CloudConfig structure. The two resulting
node structures are combined together. The nodes from the CloudConfig
process replace those from the interface{} when the types of the two
nodes are compatible. For example, with the input `0744`, yaml
interprets that as the integer 484 giving us the nodes '0744'(string)
and 484(int). Because the types string and int are compatible, we opt to
take the string node instead of the integer.
2014-12-30 16:20:21 +01:00
Alex Crawford
54a64454b9 validate: fix printing for non-string values 2014-12-30 16:20:21 +01:00
Alex Crawford
0e70d4f01f config: add validity check for file permissions 2014-12-30 16:20:21 +01:00
Alex Crawford
af8e590575 config: change valid tag to use regexp
A regular expression is much more useful than a list of strings.
2014-12-30 16:20:21 +01:00
Alex Crawford
40d943fb7a reboot-strategy: remove the 'false' value
Since we no longer do a two-stage unmarshal, the 'false' value will no
longer be necessary.
2014-12-30 16:20:21 +01:00
Alex Crawford
248536a5cd config: use a YAML transform to normalize keys
This removes the problematic two-pass unmarshalling.
2014-12-30 16:20:21 +01:00
Alex Crawford
4ed1d03c97 godeps: bump github.com/coreos/yaml 2014-12-30 16:20:20 +01:00
Alex Crawford
057ab37364 config: seperate the CoreOS type from CloudConfig
Renamed Coreos to CoreOS while I was at it.
2014-12-30 16:20:20 +01:00
Alex Crawford
182241c8d3 config: clean up and remove some tests
Small modification to make these align with our test-table-style tests.
Also removed TestCloudConfigInvalidKeys since it hasn't been a useful
test since d3294bcb86.
2014-12-30 16:19:00 +01:00
Michael Marineau
edced59fa6 Merge pull request #281 from thommay/flannel_env_file
Create an environment file for flannel
2014-12-29 15:07:08 -08:00
Thom May
9be836df31 Create an environment file for flannel
Rather than using a systemd overlay, allow docker to load the
environment file. This is due to coreos/coreos-overlay#1002
2014-12-29 10:27:22 +00:00
Jonathan Boulle
4e54447b8e Merge pull request #286 from jonboulle/master
Godeps: switch to coreos/yaml
2014-12-20 15:43:55 -08:00
Jonathan Boulle
999c38b09b Godeps: switch to coreos/yaml 2014-12-20 15:31:02 -08:00
Alex Crawford
06d13de5c3 coreos-cloudinit: bump to v1.0.2+git 2014-12-12 17:38:28 -08:00
Alex Crawford
5b0903d162 coreos-cloudinit: bump to v1.0.2 2014-12-12 17:37:39 -08:00
Alex Crawford
10669be7c0 Merge pull request #284 from crawford/travis
test: disable Travis sudo capability
2014-12-12 17:28:47 -08:00
Alex Crawford
2edae741e1 test: disable Travis sudo capability 2014-12-12 16:46:18 -08:00
Alex Crawford
ea90e553d1 Merge pull request #282 from crawford/network
network: write network units with user units
2014-12-12 15:12:50 -08:00
Alex Crawford
b0cfd86902 network: write network units with user units
This allows us to test the network unit generation as well as removing
some special-cased code.
2014-12-12 15:08:03 -08:00
Alex Crawford
565a9540c9 Merge pull request #283 from crawford/validate
validate: empty user_data is also valid
2014-12-12 15:05:51 -08:00
Alex Crawford
fd10e27b99 validate: empty user_data is also valid 2014-12-12 14:49:42 -08:00
Michael Marineau
39763d772c Merge pull request #280 from marineam/go1.4
travis: Add Go 1.4 as a test target
2014-12-11 16:34:00 -08:00
Michael Marineau
ee69b77bfb travis: Add Go 1.4 as a test target 2014-12-11 15:29:36 -08:00
Jonathan Boulle
353444e56d Merge pull request #279 from cnelson/write_files_encoding_support
Add support for the encoding key to write_files
2014-12-09 17:37:34 -08:00
cnelson
112ba1e31f Added support for the encoding key in write_files
Supported encodings are base64, gzip, and base64 encoded gzip
2014-12-09 17:35:33 -08:00
cnelson
9c3cd9e69c bumped version of yaml.v1 2014-12-09 07:49:59 -08:00
Alex Crawford
685d8317bc Merge pull request #275 from mwhooker/master
Enable configuration of locksmithd
2014-12-05 14:09:40 -08:00
Matthew Hooker
f42d102b26 Also fix wording of Flannel section 2014-12-04 23:55:26 -08:00
Matthew Hooker
c944e9ef94 Enable configuration of locksmithd 2014-12-04 23:53:31 -08:00
Alex Crawford
f10d6e8bef coreos-cloudinit: bump to v1.0.1+git 2014-12-04 16:35:20 -08:00
Alex Crawford
f3f3af79fd coreos-cloudinit: bump to v1.0.1 2014-12-04 16:34:57 -08:00
Alex Crawford
0e63aa0f6b Merge pull request #276 from crawford/networkd
initialize: restart networkd before other units
2014-12-04 16:33:02 -08:00
Alex Crawford
b254e17e89 Merge pull request #263 from robszumski/docs-validator
docs: link to validator
2014-12-04 16:28:21 -08:00
Alex Crawford
5c059b66f0 initialize: restart networkd before other units 2014-12-04 15:25:44 -08:00
Alex Crawford
c628bef666 Merge pull request #273 from crawford/networkd
initialize: only restart networkd once per config
2014-12-02 12:54:27 -08:00
Alex Crawford
2270db3f7a initialize: only restart networkd once per config
Regression introduced by 9fcf338bf3.

Networkd was erroneously being restarted once per network unit. It
should only restart once for the entire config.
2014-12-02 12:46:35 -08:00
Alex Crawford
d0d467813d Merge pull request #251 from Vladimiroff/master
metadata: Populate CloudSigma's IPs properly
2014-12-01 14:52:11 -08:00
Alex Crawford
123f111efe coreos-cloudinit: bump to 1.0.0+git 2014-11-26 14:19:29 -08:00
Alex Crawford
521ecfdab5 coreos-cloudinit: bump to 1.0.0 2014-11-26 14:19:13 -08:00
Alex Crawford
6d0fdf1a47 Merge pull request #268 from crawford/dropins
drop-in: add support for drop-ins
2014-11-26 14:14:49 -08:00
Alex Crawford
ffc54b028c drop-in: add support for drop-ins
This allows a list of drop-ins for a unit to be declared inline within a
cloud-config. For example:

  #cloud-config
  coreos:
    units:
      - name: docker.service
        drop-ins:
          - name: 50-insecure-registry.conf
            content: |
              [Service]
              Environment=DOCKER_OPTS='--insecure-registry="10.0.1.0/24"'
2014-11-26 14:09:35 -08:00
Alex Crawford
420f7cf202 system: clean up TestPlaceUnit() 2014-11-26 10:32:43 -08:00
Alex Crawford
624df676d0 config/unit: move Type() and Group() out of config 2014-11-26 10:32:43 -08:00
Alex Crawford
75ed8dacf9 initialize: clean up TestProcessUnits() 2014-11-26 10:32:43 -08:00
Alex Crawford
dcaabe4d4a system: clean up UnitManager interface 2014-11-26 10:32:43 -08:00
Alex Crawford
92c57423ba Merge pull request #269 from crawford/valid
validate: promote invalid values to an error
2014-11-26 10:32:27 -08:00
Alex Crawford
7447e133c9 validate: promote invalid values to an error 2014-11-26 10:29:09 -08:00
Eugene Yakubovich
4e466c12da Merge pull request #267 from thommay/flannel_unit
the flannel service is called flanneld
2014-11-25 12:30:58 -08:00
Thom May
333468dba3 the flannel service is called flanneld 2014-11-25 14:00:53 +00:00
Alex Crawford
55c3a793ad coreos-cloudinit: bump to 0.11.4+git 2014-11-21 20:11:54 -08:00
Alex Crawford
eca51031c8 coreos-cloudinit: bump to 0.11.4 2014-11-21 20:11:37 -08:00
Alex Crawford
19522bcb82 Merge pull request #266 from crawford/config
config: update configs to match etcd, fleet, and flannel
2014-11-21 20:10:34 -08:00
Alex Crawford
62248ea33d config/fleet: fix configs
Added EtcdKeyPrefix and fixed the types of EngineReconcileInterval and EtcdRequestTimeout.
2014-11-21 16:57:00 -08:00
Alex Crawford
d2a19cc86d config/flannel: correct - vs _ 2014-11-21 16:57:00 -08:00
Alex Crawford
08131ffab1 config/etcd: fix configs
This new table is pulled from the etcd codebase rather than the docs...

Added:
 GraphiteHost
 PeerElectionTimeout
 PeerHeartbeatInterval
 PeerKeyFile
 RetryInterval
 SnapshotCount
 StrTrace
 VeryVeryVerbose

Fixed types:
 ClusterActiveSize
 ClusterRemoveDelay
 ClusterSyncInterval
 HTTPReadTimeout
 HTTPWriteTimeout
 MaxResultBuffer
 MaxRetryAttempts
 Snapshot
 Verbose
 VeryVerbose

Renamed:
 Cors

Removed:
 MaxClusterSize
 CPUProfileFile
2014-11-21 16:57:00 -08:00
Alex Crawford
4a0019c669 config: add support for float64 2014-11-21 16:13:49 -08:00
Alex Crawford
3275ead1ec coreos-cloudinit: bump to 0.11.3+git 2014-11-21 12:25:26 -08:00
Alex Crawford
32b6a55724 coreos-cloudinit: bump to 0.11.3 2014-11-21 12:25:04 -08:00
Alex Crawford
6c43644369 Merge pull request #265 from crawford/update
config/update: add "off" as a valid strategy
2014-11-21 12:22:45 -08:00
Alex Crawford
e6593d49e6 config/update: add "off" as a valid strategy
It was assumed that the user would specify the reboot strategy as an
unquoted value. In the case that they turn off updates, `off` is
interpreted as a boolean and the normalization pass converts that to
`false`. In the event that the user uses `"off"`, it's interpreted as a
string and not modified.
2014-11-21 10:41:03 -08:00
Alex Crawford
ab752b239f coreos-cloudinit: bump to 0.11.2+git 2014-11-20 11:29:25 -08:00
Alex Crawford
0742e4d357 coreos-cloudinit: bump to 0.11.2 2014-11-20 11:29:12 -08:00
Alex Crawford
78f586ec9e Merge pull request #262 from crawford/permissions
config: fix parsing of file permissions
2014-11-20 11:28:11 -08:00
Alex Crawford
6f91b76d79 docs: correct type of permissions 2014-11-20 11:14:44 -08:00
Alex Crawford
5c80ccacc4 config: fix parsing of file permissions
The file permissions can be specified (unfortunately) as a string or an
octal integer. During the normalization step, every field is
unmarshalled into an interface{}. String types are kept in tact but
integers are converted to decimal integers. If the raw config
represented the permissions as an octal, it would be converted to
decimal _before_ it was saved to RawFilePermissions. Permissions() would
then try to convert it again, assuming it was an octal. The new behavior
doesn't assume the radix of the number, allowing decimal and octal
input.
2014-11-20 11:14:44 -08:00
Rob Szumski
44fdf95d99 docs: mention validate flag 2014-11-20 11:12:31 -08:00
Rob Szumski
0a62614eec docs: link to validator 2014-11-20 10:58:57 -08:00
Alex Crawford
97758b343b coreos-cloudinit: bump to 0.11.1+git 2014-11-18 12:14:34 -08:00
Alex Crawford
fb6f52b360 coreos-cloudinit: bump to 0.11.1 2014-11-18 12:14:29 -08:00
Alex Crawford
786cd2a539 Merge pull request #259 from crawford/hyphen
config/validate: disable - vs _ message for now
2014-11-18 12:12:26 -08:00
Alex Crawford
45793f1254 config/validate: disable - vs _ message for now 2014-11-18 12:11:50 -08:00
Alex Crawford
b621756d92 Merge pull request #258 from crawford/header
config/validate: fix line number for header check
2014-11-18 12:11:35 -08:00
Alex Crawford
a5b5c700a6 config/validate: fix line number for header check 2014-11-18 12:02:23 -08:00
Kiril Vladimirov
ea95920f31 fix(datasource/CloudSigma): Make sure DHCP has run 2014-11-17 15:35:10 +02:00
Alex Crawford
d7602f3c08 Merge pull request #244 from eyakubovich/master
flannel: added flannel support and helper to make dropins
2014-11-14 10:46:19 -08:00
Eugene Yakubovich
a20addd05e flannel: added flannel support and helper to make dropins
fleet, flannel, and etcd all generate dropins from config.
To reduce code duplication, factor out a helper to do that.
2014-11-14 10:45:23 -08:00
Alex Crawford
d9d89a6fa0 coreos-cloudinit: bump to 0.11.0+git 2014-11-14 10:42:00 -08:00
Alex Crawford
3c26376326 coreos-cloudinit: bump to 0.11.0 2014-11-14 10:41:47 -08:00
Alex Crawford
d3294bcb86 Merge pull request #254 from crawford/validator
config: add new validator
2014-11-12 17:40:16 -08:00
Alex Crawford
dda314b518 flags: add validate flag
This will allow the user to run a standalone validation.
2014-11-12 16:48:57 -08:00
Alex Crawford
055a3c339a config/validate: add new config validator
This validator is still experimental and is going to need new rules in the
future. This lays out the general framework.
2014-11-12 16:48:57 -08:00
Alex Crawford
51f37100a1 config: remove config validator 2014-11-07 10:18:08 -08:00
Alex Crawford
88e8265cd6 config: seperate AssertValid and AssertStructValid
Added an error structure to make it possible to get the specifics of the failure.
2014-11-07 10:14:34 -08:00
Alex Crawford
6e2db882e6 script: move Script into config package 2014-11-07 10:13:52 -08:00
Alex Crawford
3e2823df1b Merge pull request #256 from crawford/hyphen
config: deprecate - in favor of _ for key names
2014-11-03 14:54:23 -08:00
Alex Crawford
d02aa18839 config: deprecate - in favor of _ for key names
In all of the YAML tags, - has been replaced with _. normalizeConfig() and
normalizeKeys() have also been added to perform the normalization of the input
cloud-config.

As part of the normalization process, falsey values are converted to "false".
The "off" update strategy is no exception and as a result the "off" update
strategy has been changed to "false".
2014-11-03 12:09:52 -08:00
Kiril Vladimirov
b6062f0644 fix(datasource/CloudSigma): Populate local IPv4 address properly 2014-10-23 15:03:23 +03:00
Kiril Vladimirov
c5fada6e69 fix(datasource/CloudSigma): Populate public IPv4 address properly 2014-10-23 13:21:49 +03:00
351 changed files with 34567 additions and 13820 deletions

View File

@ -1,11 +1,12 @@
language: go language: go
go: sudo: false
- 1.3 matrix:
- 1.2 include:
- go: 1.4
install: install:
- go get code.google.com/p/go.tools/cmd/cover - go get golang.org/x/tools/cmd/cover
- go get code.google.com/p/go.tools/cmd/vet - go get golang.org/x/tools/cmd/vet
- go: 1.5
script: script:
- ./test - ./test

View File

@ -0,0 +1,38 @@
# Deprecated Cloud-Config Features
## Retrieving SSH Authorized Keys
### 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.
```yaml
#cloud-config
users:
- name: elroy
coreos-ssh-import-github: elroy
```
### From an HTTP Endpoint
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:
```yaml
#cloud-config
users:
- name: elroy
coreos-ssh-import-url: https://github-enterprise.example.com/api/v3/users/elroy/keys?access_token=<TOKEN>
```
You can also specify any URL whose response matches the JSON format for public keys:
```yaml
#cloud-config
users:
- name: elroy
coreos-ssh-import-url: https://example.com/public-keys
```

View File

@ -0,0 +1,26 @@
# Cloud-Config Locations
On every boot, coreos-cloudinit looks for a config file to configure your host. Here is a list of locations which are used by the Cloud-Config utility, depending on your CoreOS platform:
Location | Description
--- | --- | ---
|`/media/configvirtfs/openstack/latest/user_data`|`/media/configvirtfs` mount point with [config-2](/os/docs/latest/config-drive.html#contents-and-format) label. It should contain a `openstack/latest/user_data` relative path. Usually used by cloud providers or in VM installations.|
|`/media/configdrive/openstack/latest/user_data`|FAT or ISO9660 filesystem with [config-2](/os/docs/latest/config-drive.html#qemu-virtfs) label and `/media/configdrive/` mount point. It should also contain a `openstack/latest/user_data` relative path. Usually used in installations which are configured by USB Flash sticks or CDROM media.|
|Kernel command line: `cloud-config-url=http://example.com/user_data`.| You can find this string using this command `cat /proc/cmdline`. Usually used in [PXE](/os/docs/latest/booting-with-pxe.html) or [iPXE](/os/docs/latest/booting-with-ipxe.html) boots.|
|`/var/lib/coreos-install/user_data`| When you install CoreOS manually using the [coreos-install](/os/docs/latest/installing-to-disk.html) tool. Usually used in bare metal installations.|
|`/usr/share/oem/cloud-config.yml`| Path for OEM images.|
|`/var/lib/coreos-vagrant/vagrantfile-user-data`| Vagrant OEM scripts automatically store Cloud-Config into this path. |
|`/var/lib/waagent/CustomData`| Azure platform uses OEM path for first Cloud-Config initialization and then `/var/lib/waagent/CustomData` to apply your settings.|
|`http://169.254.169.254/metadata/v1/user-data` `http://169.254.169.254/2009-04-04/user-data` `https://metadata.packet.net/userdata`|DigitalOcean, EC2 and Packet cloud providers correspondingly use these URLs to download Cloud-Config.|
|`/usr/share/oem/bin/vmtoolsd --cmd "info-get guestinfo.coreos.config.data"`|Cloud-Config provided by [VMware Guestinfo][VMware Guestinfo]|
|`/usr/share/oem/bin/vmtoolsd --cmd "info-get guestinfo.coreos.config.url"`|Cloud-Config URL provided by [VMware Guestinfo][VMware Guestinfo]|
[VMware Guestinfo]: vmware-guestinfo.md
You can also run the `coreos-cloudinit` tool manually and provide a path to your custom Cloud-Config file:
```sh
sudo coreos-cloudinit --from-file=/home/core/cloud-config.yaml
```
This command will apply your custom cloud-config.

View File

@ -17,11 +17,11 @@ For example, the following cloud-config document...
#cloud-config #cloud-config
coreos: coreos:
oem: oem:
id: rackspace id: "rackspace"
name: Rackspace Cloud Servers name: "Rackspace Cloud Servers"
version-id: 168.0.0 version-id: "168.0.0"
home-url: https://www.rackspace.com/cloud/servers/ home-url: "https://www.rackspace.com/cloud/servers/"
bug-report-url: https://github.com/coreos/coreos-overlay bug-report-url: "https://github.com/coreos/coreos-overlay"
``` ```
...would be rendered to the following `/etc/oem-release`: ...would be rendered to the following `/etc/oem-release`:

View File

@ -1,6 +1,16 @@
# Using Cloud-Config # Using Cloud-Config
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. Your cloud-config is processed during each boot. 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.
Your cloud-config is processed during each boot. Invalid cloud-config won't be processed but will be logged in the journal. You can validate your cloud-config with the [CoreOS validator]({{site.url}}/validate) or by running `coreos-cloudinit -validate`.
In addition to `coreos-cloudinit -validate` command and https://coreos.com/validate/ online service you can debug `coreos-cloudinit` system output through the `journalctl` tool:
```sh
journalctl _EXE=/usr/bin/coreos-cloudinit
```
It will show `coreos-cloudinit` run output which was triggered by system boot.
## Configuration File ## Configuration File
@ -16,7 +26,7 @@ We've designed our implementation to allow the same cloud-config file to work ac
The cloud-config file uses the [YAML][yaml] file format, which uses whitespace and new-lines to delimit lists, associative arrays, and values. The cloud-config file uses the [YAML][yaml] file format, which uses whitespace and new-lines to delimit lists, associative arrays, and values.
A cloud-config file should contain `#cloud-config`, followed by an associative array which has zero or more of the following keys: A cloud-config file must contain a header: either `#cloud-config` for processing as cloud-config (suggested) or `#!` for processing as a shell script (advanced). If cloud-config has #cloud-config header, it should followed by an associative array which has zero or more of the following keys:
- `coreos` - `coreos`
- `ssh_authorized_keys` - `ssh_authorized_keys`
@ -27,6 +37,8 @@ A cloud-config file should contain `#cloud-config`, followed by an associative a
The expected values for these keys are defined in the rest of this document. The expected values for these keys are defined in the rest of this document.
If cloud-config header starts on `#!` then coreos-cloudinit will recognize it as shell script which is interpreted by bash and run it as transient systemd service.
[yaml]: https://en.wikipedia.org/wiki/YAML [yaml]: https://en.wikipedia.org/wiki/YAML
### Providing Cloud-Config with Config-Drive ### Providing Cloud-Config with Config-Drive
@ -37,7 +49,7 @@ CoreOS tries to conform to each platform's native method to provide user data. E
### coreos ### coreos
#### etcd #### etcd (deprecated. see etcd2)
The `coreos.etcd.*` parameters will be translated to a partial systemd unit acting as an etcd configuration file. The `coreos.etcd.*` parameters will be translated to a partial systemd unit acting as an etcd configuration file.
If the platform environment supports the templating feature of coreos-cloudinit it is possible to automate etcd configuration with the `$private_ipv4` and `$public_ipv4` fields. For example, the following cloud-config document... If the platform environment supports the templating feature of coreos-cloudinit it is possible to automate etcd configuration with the `$private_ipv4` and `$public_ipv4` fields. For example, the following cloud-config document...
@ -47,15 +59,15 @@ If the platform environment supports the templating feature of coreos-cloudinit
coreos: coreos:
etcd: etcd:
name: node001 name: "node001"
# generate a new token for each unique cluster from https://discovery.etcd.io/new # generate a new token for each unique cluster from https://discovery.etcd.io/new
discovery: https://discovery.etcd.io/<token> discovery: "https://discovery.etcd.io/<token>"
# multi-region and multi-cloud deployments need to use $public_ipv4 # multi-region and multi-cloud deployments need to use $public_ipv4
addr: $public_ipv4:4001 addr: "$public_ipv4:4001"
peer-addr: $private_ipv4:7001 peer-addr: "$private_ipv4:7001"
``` ```
...will generate a systemd unit drop-in like this: ...will generate a systemd unit drop-in for etcd.service with the following contents:
```yaml ```yaml
[Service] [Service]
@ -66,23 +78,62 @@ Environment="ETCD_PEER_ADDR=192.0.2.13:7001"
``` ```
For more information about the available configuration parameters, 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.
_Note: The `$private_ipv4` and `$public_ipv4` substitution variables referenced in other documents are only supported on Amazon EC2, Google Compute Engine, OpenStack, Rackspace, DigitalOcean, and Vagrant._ _Note: The `$private_ipv4` and `$public_ipv4` substitution variables referenced in other documents are only supported on Amazon EC2, Google Compute Engine, OpenStack, Rackspace, DigitalOcean, and Vagrant._
[etcd-config]: https://github.com/coreos/etcd/blob/master/Documentation/configuration.md [etcd-config]: https://github.com/coreos/etcd/blob/release-0.4/Documentation/configuration.md
#### etcd2
The `coreos.etcd2.*` parameters will be translated to a partial systemd unit acting as an etcd configuration file.
If the platform environment supports the templating feature of coreos-cloudinit it is possible to automate etcd configuration with the `$private_ipv4` and `$public_ipv4` fields. When generating a [discovery token](https://discovery.etcd.io/new?size=3), set the `size` parameter, since etcd uses this to determine if all members have joined the cluster. After the cluster is bootstrapped, it can grow or shrink from this configured size.
For example, the following cloud-config document...
```yaml
#cloud-config
coreos:
etcd2:
# generate a new token for each unique cluster from https://discovery.etcd.io/new?size=3
discovery: "https://discovery.etcd.io/<token>"
# multi-region and multi-cloud deployments need to use $public_ipv4
advertise-client-urls: "http://$public_ipv4:2379"
initial-advertise-peer-urls: "http://$private_ipv4:2380"
# listen on both the official ports and the legacy ports
# legacy ports can be omitted if your application doesn't depend on them
listen-client-urls: "http://0.0.0.0:2379,http://0.0.0.0:4001"
listen-peer-urls: "http://$private_ipv4:2380,http://$private_ipv4:7001"
```
...will generate a systemd unit drop-in for etcd2.service with the following contents:
```yaml
[Service]
Environment="ETCD_DISCOVERY=https://discovery.etcd.io/<token>"
Environment="ETCD_ADVERTISE_CLIENT_URLS=http://203.0.113.29:2379"
Environment="ETCD_INITIAL_ADVERTISE_PEER_URLS=http://192.0.2.13:2380"
Environment="ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379,http://0.0.0.0:4001"
Environment="ETCD_LISTEN_PEER_URLS=http://192.0.2.13:2380,http://192.0.2.13:7001"
```
For more information about the available configuration parameters, see the [etcd2 documentation][etcd2-config].
_Note: The `$private_ipv4` and `$public_ipv4` substitution variables referenced in other documents are only supported on Amazon EC2, Google Compute Engine, OpenStack, Rackspace, DigitalOcean, and Vagrant._
[etcd2-config]: https://github.com/coreos/etcd/blob/master/Documentation/configuration.md
#### fleet #### fleet
The `coreos.fleet.*` parameters work very similarly to `coreos.etcd.*`, and allow for the configuration of fleet through environment variables. For example, the following cloud-config document... The `coreos.fleet.*` parameters work very similarly to `coreos.etcd2.*`, and allow for the configuration of fleet through environment variables. For example, the following cloud-config document...
```yaml ```yaml
#cloud-config #cloud-config
coreos: coreos:
fleet: fleet:
public-ip: $public_ipv4 public-ip: "$public_ipv4"
metadata: region=us-west metadata: "region=us-west"
``` ```
...will generate a systemd unit drop-in like this: ...will generate a systemd unit drop-in like this:
@ -93,10 +144,92 @@ Environment="FLEET_PUBLIC_IP=203.0.113.29"
Environment="FLEET_METADATA=region=us-west" Environment="FLEET_METADATA=region=us-west"
``` ```
List of fleet configuration parameters:
- **agent_ttl**: An Agent will be considered dead if it exceeds this amount of time to communicate with the Registry
- **engine_reconcile_interval**: Interval in seconds at which the engine should reconcile the cluster schedule in etcd
- **etcd_cafile**: Path to CA file used for TLS communication with etcd
- **etcd_certfile**: Provide TLS configuration when SSL certificate authentication is enabled in etcd endpoints
- **etcd_keyfile**: Path to private key file used for TLS communication with etcd
- **etcd_key_prefix**: etcd prefix path to be used for fleet keys
- **etcd_request_timeout**: Amount of time in seconds to allow a single etcd request before considering it failed
- **etcd_servers**: Comma separated list of etcd endpoints
- **metadata**: Comma separated key/value pairs that are published with the local to the fleet registry
- **public_ip**: IP accessible by other nodes for inter-host communication
- **verbosity**: Enable debug logging by setting this to an integer value greater than zero
For more information on fleet configuration, see the [fleet documentation][fleet-config]. For more information on fleet configuration, see the [fleet documentation][fleet-config].
[fleet-config]: https://github.com/coreos/fleet/blob/master/Documentation/deployment-and-configuration.md#configuration [fleet-config]: https://github.com/coreos/fleet/blob/master/Documentation/deployment-and-configuration.md#configuration
#### flannel
The `coreos.flannel.*` parameters also work very similarly to `coreos.etcd2.*`
and `coreos.fleet.*`. They can be used to set environment variables for
flanneld. For example, the following cloud-config...
```yaml
#cloud-config
coreos:
flannel:
etcd_prefix: "/coreos.com/network2"
```
...will generate a systemd unit drop-in like so:
```
[Service]
Environment="FLANNELD_ETCD_PREFIX=/coreos.com/network2"
```
List of flannel configuration parameters:
- **etcd_endpoints**: Comma separated list of etcd endpoints
- **etcd_cafile**: Path to CA file used for TLS communication with etcd
- **etcd_certfile**: Path to certificate file used for TLS communication with etcd
- **etcd_keyfile**: Path to private key file used for TLS communication with etcd
- **etcd_prefix**: etcd prefix path to be used for flannel keys
- **ip_masq**: Install IP masquerade rules for traffic outside of flannel subnet
- **subnet_file**: Path to flannel subnet file to write out
- **interface**: Interface (name or IP) that should be used for inter-host communication
- **public_ip**: IP accessible by other nodes for inter-host communication
For more information on flannel configuration, see the [flannel documentation][flannel-readme].
[flannel-readme]: https://github.com/coreos/flannel/blob/master/README.md
#### locksmith
The `coreos.locksmith.*` parameters can be used to set environment variables
for locksmith. For example, the following cloud-config...
```yaml
#cloud-config
coreos:
locksmith:
endpoint: "http://example.com:2379"
```
...will generate a systemd unit drop-in like so:
```
[Service]
Environment="LOCKSMITHD_ENDPOINT=http://example.com:2379"
```
List of locksmith configuration parameters:
- **endpoint**: Comma separated list of etcd endpoints
- **etcd_cafile**: Path to CA file used for TLS communication with etcd
- **etcd_certfile**: Path to certificate file used for TLS communication with etcd
- **etcd_keyfile**: Path to private key file used for TLS communication with etcd
For the complete list of locksmith configuration parameters, see the [locksmith documentation][locksmith-readme].
[locksmith-readme]: https://github.com/coreos/locksmith/blob/master/README.md
#### update #### update
The `coreos.update.*` parameters manipulate settings related to how CoreOS instances are updated. The `coreos.update.*` parameters manipulate settings related to how CoreOS instances are updated.
@ -109,9 +242,12 @@ The `reboot-strategy` parameter also affects the behaviour of [locksmith](https:
- _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. - _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". - _best-effort_ - If etcd is running, "etcd-lock", otherwise simply "reboot".
- _off_ - Disable rebooting after updates are applied (not recommended). - _off_ - Disable rebooting after updates are applied (not recommended).
- **server**: is the omaha endpoint URL which will be queried for updates. - **server**: The location of the [CoreUpdate][coreupdate] server which will be queried for updates. Also known as the [omaha][omaha-docs] server endpoint.
- **group**: signifies the channel which should be used for automatic updates. This value defaults to the version of the image initially downloaded. (one of "master", "alpha", "beta", "stable") - **group**: signifies the channel which should be used for automatic updates. This value defaults to the version of the image initially downloaded. (one of "master", "alpha", "beta", "stable")
[coreupdate]: https://coreos.com/products/coreupdate
[omaha-docs]: https://coreos.com/docs/coreupdate/custom-apps/coreupdate-protocol/
*Note: cloudinit will only manipulate the locksmith unit file in the systemd runtime directory (`/run/systemd/system/locksmithd.service`). If any manual modifications are made to an overriding unit configuration file (e.g. `/etc/systemd/system/locksmithd.service`), cloudinit will no longer be able to control the locksmith service unit.* *Note: cloudinit will only manipulate the locksmith unit file in the systemd runtime directory (`/run/systemd/system/locksmithd.service`). If any manual modifications are made to an overriding unit configuration file (e.g. `/etc/systemd/system/locksmithd.service`), cloudinit will no longer be able to control the locksmith service unit.*
##### Example ##### Example
@ -120,7 +256,7 @@ The `reboot-strategy` parameter also affects the behaviour of [locksmith](https:
#cloud-config #cloud-config
coreos: coreos:
update: update:
reboot-strategy: etcd-lock reboot-strategy: "etcd-lock"
``` ```
#### units #### units
@ -135,6 +271,10 @@ Each item is an object with the following fields:
- **content**: Plaintext string representing entire unit file. If no value is provided, the unit is assumed to exist already. - **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. The default behavior is to not execute any commands. - **command**: Command to execute on unit: start, stop, reload, restart, try-restart, reload-or-restart, reload-or-try-restart. The default behavior is to not execute any commands.
- **mask**: Whether to mask the unit file by symlinking it to `/dev/null` (analogous to `systemctl mask <name>`). Note that unlike `systemctl mask`, **this will destructively remove any existing unit file** located at `/etc/systemd/system/<unit>`, to ensure that the mask succeeds. The default value is false. - **mask**: Whether to mask the unit file by symlinking it to `/dev/null` (analogous to `systemctl mask <name>`). Note that unlike `systemctl mask`, **this will destructively remove any existing unit file** located at `/etc/systemd/system/<unit>`, to ensure that the mask succeeds. The default value is false.
- **drop-ins**: A list of unit drop-ins with the following fields:
- **name**: String representing unit's name. Required.
- **content**: Plaintext string representing entire file. Required.
**NOTE:** The command field is ignored for all network, netdev, and link units. The systemd-networkd.service unit will be restarted in their place. **NOTE:** The command field is ignored for all network, netdev, and link units. The systemd-networkd.service unit will be restarted in their place.
@ -147,8 +287,8 @@ Write a unit to disk, automatically starting it.
coreos: coreos:
units: units:
- name: docker-redis.service - name: "docker-redis.service"
command: start command: "start"
content: | content: |
[Unit] [Unit]
Description=Redis container Description=Redis container
@ -161,17 +301,32 @@ coreos:
ExecStop=/usr/bin/docker stop -t 2 redis_server ExecStop=/usr/bin/docker stop -t 2 redis_server
``` ```
Start the built-in `etcd` and `fleet` services: Add the DOCKER_OPTS environment variable to docker.service.
```yaml ```yaml
#cloud-config #cloud-config
coreos: coreos:
units: units:
- name: etcd.service - name: "docker.service"
command: start drop-ins:
- name: fleet.service - name: "50-insecure-registry.conf"
command: start content: |
[Service]
Environment=DOCKER_OPTS='--insecure-registry="10.0.1.0/24"'
```
Start the built-in `etcd2` and `fleet` services:
```yaml
#cloud-config
coreos:
units:
- name: "etcd2.service"
command: "start"
- name: "fleet.service"
command: "start"
``` ```
### ssh_authorized_keys ### ssh_authorized_keys
@ -185,7 +340,7 @@ Override this by using the `--ssh-key-name` flag when calling `coreos-cloudinit`
#cloud-config #cloud-config
ssh_authorized_keys: ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0g+ZTxC7weoIJLUafOgrm+h... - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0g+ZTxC7weoIJLUafOgrm+h..."
``` ```
### hostname ### hostname
@ -196,7 +351,7 @@ This is the local part of a fully-qualified domain name (i.e. `foo` in `foo.exam
```yaml ```yaml
#cloud-config #cloud-config
hostname: coreos1 hostname: "coreos1"
``` ```
### users ### users
@ -213,10 +368,12 @@ All but the `passwd` and `ssh-authorized-keys` fields will be ignored if the use
- **groups**: Add user to these additional groups - **groups**: Add user to these additional groups
- **no-user-group**: Boolean. Skip default group creation. - **no-user-group**: Boolean. Skip default group creation.
- **ssh-authorized-keys**: List of public SSH keys to authorize for this user - **ssh-authorized-keys**: List of public SSH keys to authorize for this user
- **coreos-ssh-import-github**: Authorize SSH keys from Github user - **coreos-ssh-import-github** [DEPRECATED]: Authorize SSH keys from GitHub user
- **coreos-ssh-import-url**: Authorize SSH keys imported from a url endpoint. - **coreos-ssh-import-github-users** [DEPRECATED]: Authorize SSH keys from a list of GitHub users
- **coreos-ssh-import-url** [DEPRECATED]: Authorize SSH keys imported from a url endpoint.
- **system**: Create the user as a system user. No home directory will be created. - **system**: Create the user as a system user. No home directory will be created.
- **no-log-init**: Boolean. Skip initialization of lastlog and faillog databases. - **no-log-init**: Boolean. Skip initialization of lastlog and faillog databases.
- **shell**: User's login shell.
The following fields are not yet implemented: The following fields are not yet implemented:
@ -230,13 +387,13 @@ The following fields are not yet implemented:
#cloud-config #cloud-config
users: users:
- name: elroy - name: "elroy"
passwd: $6$5s2u6/jR$un0AvWnqilcgaNB3Mkxd5yYv6mTlWfOoCYHZmfi3LDKVltj.E8XNKEcwWm... passwd: "$6$5s2u6/jR$un0AvWnqilcgaNB3Mkxd5yYv6mTlWfOoCYHZmfi3LDKVltj.E8XNKEcwWm..."
groups: groups:
- sudo - "sudo"
- docker - "docker"
ssh-authorized-keys: ssh-authorized-keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0g+ZTxC7weoIJLUafOgrm+h... - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0g+ZTxC7weoIJLUafOgrm+h..."
``` ```
#### Generating a password hash #### Generating a password hash
@ -259,43 +416,6 @@ 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. 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
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.
```yaml
#cloud-config
users:
- name: elroy
coreos-ssh-import-github: elroy
```
##### From an HTTP Endpoint
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:
```yaml
#cloud-config
users:
- name: elroy
coreos-ssh-import-url: https://github-enterprise.example.com/api/v3/users/elroy/keys?access_token=<TOKEN>
```
You can also specify any URL whose response matches the JSON format for public keys:
```yaml
#cloud-config
users:
- name: elroy
coreos-ssh-import-url: https://example.com/public-keys
```
### write_files ### write_files
The `write_files` directive defines a set of files to create on the local filesystem. The `write_files` directive defines a set of files to create on the local filesystem.
@ -303,25 +423,45 @@ Each item in the list may have the following keys:
- **path**: Absolute location on disk where contents should be written - **path**: Absolute location on disk where contents should be written
- **content**: Data to write at the provided `path` - **content**: Data to write at the provided `path`
- **permissions**: String representing file permissions in octal notation (i.e. '0644') - **permissions**: Integer representing file permissions, typically in octal notation (i.e. 0644)
- **owner**: User and group that should own the file written to disk. This is equivalent to the `<user>:<group>` argument to `chown <user>:<group> <path>`. - **owner**: User and group that should own the file written to disk. This is equivalent to the `<user>:<group>` argument to `chown <user>:<group> <path>`.
- **encoding**: Optional. The encoding of the data in content. If not specified this defaults to the yaml document encoding (usually utf-8). Supported encoding types are:
- **b64, base64**: Base64 encoded content
- **gz, gzip**: gzip encoded content, for use with the !!binary tag
- **gz+b64, gz+base64, gzip+b64, gzip+base64**: Base64 encoded gzip content
Explicitly not implemented is the **encoding** attribute.
The **content** field must represent exactly what should be written to disk.
```yaml ```yaml
#cloud-config #cloud-config
write_files: write_files:
- path: /etc/resolv.conf - path: "/etc/resolv.conf"
permissions: 0644 permissions: "0644"
owner: root owner: "root"
content: | content: |
nameserver 8.8.8.8 nameserver 8.8.8.8
- path: /etc/motd - path: "/etc/motd"
permissions: 0644 permissions: "0644"
owner: root owner: "root"
content: | content: |
Good news, everyone! Good news, everyone!
- path: "/tmp/like_this"
permissions: "0644"
owner: "root"
encoding: "gzip"
content: !!binary |
H4sIAKgdh1QAAwtITM5WyK1USMqvUCjPLMlQSMssS1VIya9KzVPIySwszS9SyCpNLwYARQFQ5CcAAAA=
- path: "/tmp/or_like_this"
permissions: "0644"
owner: "root"
encoding: "gzip+base64"
content: |
H4sIAKgdh1QAAwtITM5WyK1USMqvUCjPLMlQSMssS1VIya9KzVPIySwszS9SyCpNLwYARQFQ5CcAAAA=
- path: "/tmp/todolist"
permissions: "0644"
owner: "root"
encoding: "base64"
content: |
UGFjayBteSBib3ggd2l0aCBmaXZlIGRvemVuIGxpcXVvciBqdWdz
``` ```
### manage_etc_hosts ### manage_etc_hosts
@ -334,5 +474,5 @@ infrastructure in place to resolve its own hostname, for example, when using Vag
```yaml ```yaml
#cloud-config #cloud-config
manage_etc_hosts: localhost manage_etc_hosts: "localhost"
``` ```

View File

@ -4,7 +4,7 @@ CoreOS supports providing configuration data via [config drive][config-drive]
disk images. Currently only providing a single script or cloud config file is disk images. Currently only providing a single script or cloud config file is
supported. supported.
[config-drive]: http://docs.openstack.org/user-guide/content/enable_config_drive.html#config_drive_contents [config-drive]: http://docs.openstack.org/user-guide/cli_config_drive.html
## Contents and Format ## Contents and Format
@ -21,6 +21,12 @@ mkisofs -R -V config-2 -o configdrive.iso /tmp/new-drive
rm -r /tmp/new-drive rm -r /tmp/new-drive
``` ```
If on OS X, replace the `mkisofs` invocation with:
```sh
hdiutil makehybrid -iso -joliet -default-volume-name config-2 -o configdrive.iso /tmp/new-drive
```
## QEMU virtfs ## QEMU virtfs
One exception to the above, when using QEMU it is possible to skip creating an One exception to the above, when using QEMU it is possible to skip creating an

View File

@ -0,0 +1,35 @@
# VMWare Guestinfo Interface
## Cloud-Config VMWare Guestinfo Variables
coreos-cloudinit accepts configuration from the VMware RPC API's *guestinfo*
facility. This datasource can be enabled with the `--from-vmware-guestinfo`
flag to coreos-cloudinit.
The following guestinfo variables are recognized and processed by cloudinit
when passed from the hypervisor to the virtual machine at boot time. Note that
property names are prefixed with `guestinfo.` in the VMX, e.g., `guestinfo.hostname`.
| guestinfo variable | type |
|:--------------------------------------|:--------------------------------|
| `hostname` | `hostname` |
| `interface.<n>.name` | `string` |
| `interface.<n>.mac` | `MAC address` |
| `interface.<n>.dhcp` | `{"yes", "no"}` |
| `interface.<n>.role` | `{"public", "private"}` |
| `interface.<n>.ip.<m>.address` | `CIDR IP address` |
| `interface.<n>.route.<l>.gateway` | `IP address` |
| `interface.<n>.route.<l>.destination` | `CIDR IP address` |
| `dns.server.<x>` | `IP address` |
| `coreos.config.data` | `string` |
| `coreos.config.data.encoding` | `{"", "base64", "gzip+base64"}` |
| `coreos.config.url` | `URL` |
Note: "n", "m", "l", and "x" are 0-indexed, incrementing integers. The
identifier for an `interface` does not correspond to anything outside of this
configuration; it serves only to distinguish between multiple `interface`s.
The guide to [booting on VMWare][bootvmware] is the starting point for more
information about configuring and running CoreOS on VMWare.
[bootvmware]: https://github.com/coreos/docs/blob/master/os/booting-on-vmware.md

34
Godeps/Godeps.json generated
View File

@ -1,34 +0,0 @@
{
"ImportPath": "github.com/coreos/coreos-cloudinit",
"GoVersion": "go1.3.1",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/cloudsigma/cepgo",
"Rev": "1bfc4895bf5c4d3b599f3f6ee142299488c8739b"
},
{
"ImportPath": "github.com/coreos/go-systemd/dbus",
"Rev": "4fbc5060a317b142e6c7bfbedb65596d5f0ab99b"
},
{
"ImportPath": "github.com/dotcloud/docker/pkg/netlink",
"Comment": "v0.11.1-359-g55d41c3e21e1",
"Rev": "55d41c3e21e1593b944c06196ffb2ac57ab7f653"
},
{
"ImportPath": "github.com/guelfey/go.dbus",
"Rev": "f6a3a2366cc39b8479cadc499d3c735fb10fbdda"
},
{
"ImportPath": "github.com/tarm/goserial",
"Rev": "cdabc8d44e8e84f58f18074ae44337e1f2f375b9"
},
{
"ImportPath": "gopkg.in/yaml.v1",
"Rev": "feb4ca79644e8e7e39c06095246ee54b1282c118"
}
]
}

5
Godeps/Readme generated
View File

@ -1,5 +0,0 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

2
Godeps/_workspace/.gitignore generated vendored
View File

@ -1,2 +0,0 @@
/pkg
/bin

View File

@ -1,43 +0,0 @@
cepgo
=====
Cepko implements easy-to-use communication with CloudSigma's VMs through a
virtual serial port without bothering with formatting the messages properly nor
parsing the output with the specific and sometimes confusing shell tools for
that purpose.
Having the server definition accessible by the VM can be useful in various
ways. For example it is possible to easily determine from within the VM, which
network interfaces are connected to public and which to private network.
Another use is to pass some data to initial VM setup scripts, like setting the
hostname to the VM name or passing ssh public keys through server meta.
Example usage:
package main
import (
"fmt"
"github.com/cloudsigma/cepgo"
)
func main() {
c := cepgo.NewCepgo()
result, err := c.Meta()
if err != nil {
panic(err)
}
fmt.Printf("%#v", result)
}
Output:
map[string]interface {}{
"optimize_for":"custom",
"ssh_public_key":"ssh-rsa AAA...",
"description":"[...]",
}
For more information take a look at the Server Context section of CloudSigma
API Docs: http://cloudsigma-docs.readthedocs.org/en/latest/server_context.html

View File

@ -1,186 +0,0 @@
// Cepko implements easy-to-use communication with CloudSigma's VMs through a
// virtual serial port without bothering with formatting the messages properly
// nor parsing the output with the specific and sometimes confusing shell tools
// for that purpose.
//
// Having the server definition accessible by the VM can be useful in various
// ways. For example it is possible to easily determine from within the VM,
// which network interfaces are connected to public and which to private
// network. Another use is to pass some data to initial VM setup scripts, like
// setting the hostname to the VM name or passing ssh public keys through
// server meta.
//
// Example usage:
//
// package main
//
// import (
// "fmt"
//
// "github.com/cloudsigma/cepgo"
// )
//
// func main() {
// c := cepgo.NewCepgo()
// result, err := c.Meta()
// if err != nil {
// panic(err)
// }
// fmt.Printf("%#v", result)
// }
//
// Output:
//
// map[string]string{
// "optimize_for":"custom",
// "ssh_public_key":"ssh-rsa AAA...",
// "description":"[...]",
// }
//
// For more information take a look at the Server Context section API Docs:
// http://cloudsigma-docs.readthedocs.org/en/latest/server_context.html
package cepgo
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"runtime"
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/tarm/goserial"
)
const (
requestPattern = "<\n%s\n>"
EOT = '\x04' // End Of Transmission
)
var (
SerialPort string = "/dev/ttyS1"
Baud int = 115200
)
// Sets the serial port. If the operating system is windows CloudSigma's server
// context is at COM2 port, otherwise (linux, freebsd, darwin) the port is
// being left to the default /dev/ttyS1.
func init() {
if runtime.GOOS == "windows" {
SerialPort = "COM2"
}
}
// The default fetcher makes the connection to the serial port,
// writes given query and reads until the EOT symbol.
func fetchViaSerialPort(key string) ([]byte, error) {
config := &serial.Config{Name: SerialPort, Baud: Baud}
connection, err := serial.OpenPort(config)
if err != nil {
return nil, err
}
query := fmt.Sprintf(requestPattern, key)
if _, err := connection.Write([]byte(query)); err != nil {
return nil, err
}
reader := bufio.NewReader(connection)
answer, err := reader.ReadBytes(EOT)
if err != nil {
return nil, err
}
return answer[0 : len(answer)-1], nil
}
// Queries to the serial port can be executed only from instance of this type.
// The result from each of them can be either interface{}, map[string]string or
// a single in case of single value is returned. There is also a public metod
// who directly calls the fetcher and returns raw []byte from the serial port.
type Cepgo struct {
fetcher func(string) ([]byte, error)
}
// Creates a Cepgo instance with the default serial port fetcher.
func NewCepgo() *Cepgo {
cepgo := new(Cepgo)
cepgo.fetcher = fetchViaSerialPort
return cepgo
}
// Creates a Cepgo instance with custom fetcher.
func NewCepgoFetcher(fetcher func(string) ([]byte, error)) *Cepgo {
cepgo := new(Cepgo)
cepgo.fetcher = fetcher
return cepgo
}
// Fetches raw []byte from the serial port using directly the fetcher member.
func (c *Cepgo) FetchRaw(key string) ([]byte, error) {
return c.fetcher(key)
}
// Fetches a single key and tries to unmarshal the result to json and returns
// it. If the unmarshalling fails it's safe to assume the result it's just a
// string and returns it.
func (c *Cepgo) Key(key string) (interface{}, error) {
var result interface{}
fetched, err := c.FetchRaw(key)
if err != nil {
return nil, err
}
err = json.Unmarshal(fetched, &result)
if err != nil {
return string(fetched), nil
}
return result, nil
}
// Fetches all the server context. Equivalent of c.Key("")
func (c *Cepgo) All() (interface{}, error) {
return c.Key("")
}
// Fetches only the object meta field and makes sure to return a proper
// map[string]string
func (c *Cepgo) Meta() (map[string]string, error) {
rawMeta, err := c.Key("/meta/")
if err != nil {
return nil, err
}
return typeAssertToMapOfStrings(rawMeta)
}
// Fetches only the global context and makes sure to return a proper
// map[string]string
func (c *Cepgo) GlobalContext() (map[string]string, error) {
rawContext, err := c.Key("/global_context/")
if err != nil {
return nil, err
}
return typeAssertToMapOfStrings(rawContext)
}
// Just a little helper function that uses type assertions in order to convert
// a interface{} to map[string]string if this is possible.
func typeAssertToMapOfStrings(raw interface{}) (map[string]string, error) {
result := make(map[string]string)
dictionary, ok := raw.(map[string]interface{})
if !ok {
return nil, errors.New("Received bytes are formatted badly")
}
for key, rawValue := range dictionary {
if value, ok := rawValue.(string); ok {
result[key] = value
} else {
return nil, errors.New("Server context metadata is formatted badly")
}
}
return result, nil
}

View File

@ -1,122 +0,0 @@
package cepgo
import (
"encoding/json"
"testing"
)
func fetchMock(key string) ([]byte, error) {
context := []byte(`{
"context": true,
"cpu": 4000,
"cpu_model": null,
"cpus_instead_of_cores": false,
"enable_numa": false,
"global_context": {
"some_global_key": "some_global_val"
},
"grantees": [],
"hv_relaxed": false,
"hv_tsc": false,
"jobs": [],
"mem": 4294967296,
"meta": {
"base64_fields": "cloudinit-user-data",
"cloudinit-user-data": "I2Nsb3VkLWNvbmZpZwoKaG9zdG5hbWU6IGNvcmVvczE=",
"ssh_public_key": "ssh-rsa AAAAB2NzaC1yc2E.../hQ5D5 john@doe"
},
"name": "coreos",
"nics": [
{
"runtime": {
"interface_type": "public",
"ip_v4": {
"uuid": "31.171.251.74"
},
"ip_v6": null
},
"vlan": null
}
],
"smp": 2,
"status": "running",
"uuid": "20a0059b-041e-4d0c-bcc6-9b2852de48b3"
}`)
if key == "" {
return context, nil
}
var marshalledContext map[string]interface{}
err := json.Unmarshal(context, &marshalledContext)
if err != nil {
return nil, err
}
if key[0] == '/' {
key = key[1:]
}
if key[len(key)-1] == '/' {
key = key[:len(key)-1]
}
return json.Marshal(marshalledContext[key])
}
func TestAll(t *testing.T) {
cepgo := NewCepgoFetcher(fetchMock)
result, err := cepgo.All()
if err != nil {
t.Error(err)
}
for _, key := range []string{"meta", "name", "uuid", "global_context"} {
if _, ok := result.(map[string]interface{})[key]; !ok {
t.Errorf("%s not in all keys", key)
}
}
}
func TestKey(t *testing.T) {
cepgo := NewCepgoFetcher(fetchMock)
result, err := cepgo.Key("uuid")
if err != nil {
t.Error(err)
}
if _, ok := result.(string); !ok {
t.Errorf("%#v\n", result)
t.Error("Fetching the uuid did not return a string")
}
}
func TestMeta(t *testing.T) {
cepgo := NewCepgoFetcher(fetchMock)
meta, err := cepgo.Meta()
if err != nil {
t.Errorf("%#v\n", meta)
t.Error(err)
}
if _, ok := meta["ssh_public_key"]; !ok {
t.Error("ssh_public_key is not in the meta")
}
}
func TestGlobalContext(t *testing.T) {
cepgo := NewCepgoFetcher(fetchMock)
result, err := cepgo.GlobalContext()
if err != nil {
t.Error(err)
}
if _, ok := result["some_global_key"]; !ok {
t.Error("some_global_key is not in the global context")
}
}

View File

@ -1,104 +0,0 @@
/*
Copyright 2013 CoreOS Inc.
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.
*/
// Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/
package dbus
import (
"os"
"strconv"
"strings"
"sync"
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
)
const signalBuffer = 100
// ObjectPath creates a dbus.ObjectPath using the rules that systemd uses for
// serializing special characters.
func ObjectPath(path string) dbus.ObjectPath {
path = strings.Replace(path, ".", "_2e", -1)
path = strings.Replace(path, "-", "_2d", -1)
path = strings.Replace(path, "@", "_40", -1)
return dbus.ObjectPath(path)
}
// Conn is a connection to systemds dbus endpoint.
type Conn struct {
sysconn *dbus.Conn
sysobj *dbus.Object
jobListener struct {
jobs map[dbus.ObjectPath]chan string
sync.Mutex
}
subscriber struct {
updateCh chan<- *SubStateUpdate
errCh chan<- error
sync.Mutex
ignore map[dbus.ObjectPath]int64
cleanIgnore int64
}
dispatch map[string]func(dbus.Signal)
}
// New() establishes a connection to the system bus and authenticates.
func New() (*Conn, error) {
c := new(Conn)
if err := c.initConnection(); err != nil {
return nil, err
}
c.initJobs()
return c, nil
}
func (c *Conn) initConnection() error {
var err error
c.sysconn, err = dbus.SystemBusPrivate()
if err != nil {
return err
}
// Only use EXTERNAL method, and hardcode the uid (not username)
// to avoid a username lookup (which requires a dynamically linked
// libc)
methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
err = c.sysconn.Auth(methods)
if err != nil {
c.sysconn.Close()
return err
}
err = c.sysconn.Hello()
if err != nil {
c.sysconn.Close()
return err
}
c.sysobj = c.sysconn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1"))
// Setup the listeners on jobs so that we can get completions
c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'")
c.initSubscription()
c.initDispatch()
return nil
}

View File

@ -1,41 +0,0 @@
/*
Copyright 2013 CoreOS Inc.
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.
*/
package dbus
import (
"testing"
)
// TestObjectPath ensures path encoding of the systemd rules works.
func TestObjectPath(t *testing.T) {
input := "/silly-path/to@a/unit..service"
output := ObjectPath(input)
expected := "/silly_2dpath/to_40a/unit_2e_2eservice"
if string(output) != expected {
t.Fatalf("Output '%s' did not match expected '%s'", output, expected)
}
}
// TestNew ensures that New() works without errors.
func TestNew(t *testing.T) {
_, err := New()
if err != nil {
t.Fatal(err)
}
}

View File

@ -1,354 +0,0 @@
/*
Copyright 2013 CoreOS Inc.
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.
*/
package dbus
import (
"errors"
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
)
func (c *Conn) initJobs() {
c.jobListener.jobs = make(map[dbus.ObjectPath]chan string)
}
func (c *Conn) jobComplete(signal *dbus.Signal) {
var id uint32
var job dbus.ObjectPath
var unit string
var result string
dbus.Store(signal.Body, &id, &job, &unit, &result)
c.jobListener.Lock()
out, ok := c.jobListener.jobs[job]
if ok {
out <- result
delete(c.jobListener.jobs, job)
}
c.jobListener.Unlock()
}
func (c *Conn) startJob(job string, args ...interface{}) (<-chan string, error) {
c.jobListener.Lock()
defer c.jobListener.Unlock()
ch := make(chan string, 1)
var path dbus.ObjectPath
err := c.sysobj.Call(job, 0, args...).Store(&path)
if err != nil {
return nil, err
}
c.jobListener.jobs[path] = ch
return ch, nil
}
func (c *Conn) runJob(job string, args ...interface{}) (string, error) {
respCh, err := c.startJob(job, args...)
if err != nil {
return "", err
}
return <-respCh, nil
}
// StartUnit enqeues a start job and depending jobs, if any (unless otherwise
// specified by the mode string).
//
// Takes the unit to activate, plus a mode string. The mode needs to be one of
// replace, fail, isolate, ignore-dependencies, ignore-requirements. If
// "replace" the call will start the unit and its dependencies, possibly
// replacing already queued jobs that conflict with this. If "fail" the call
// will start the unit and its dependencies, but will fail if this would change
// an already queued job. If "isolate" the call will start the unit in question
// and terminate all units that aren't dependencies of it. If
// "ignore-dependencies" it will start a unit but ignore all its dependencies.
// If "ignore-requirements" it will start a unit but only ignore the
// requirement dependencies. It is not recommended to make use of the latter
// two options.
//
// Result string: one of done, canceled, timeout, failed, dependency, skipped.
// done indicates successful execution of a job. canceled indicates that a job
// has been canceled before it finished execution. timeout indicates that the
// job timeout was reached. failed indicates that the job failed. dependency
// indicates that a job this job has been depending on failed and the job hence
// has been removed too. skipped indicates that a job was skipped because it
// didn't apply to the units current state.
func (c *Conn) StartUnit(name string, mode string) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.StartUnit", name, mode)
}
// StopUnit is similar to StartUnit but stops the specified unit rather
// than starting it.
func (c *Conn) StopUnit(name string, mode string) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.StopUnit", name, mode)
}
// ReloadUnit reloads a unit. Reloading is done only if the unit is already running and fails otherwise.
func (c *Conn) ReloadUnit(name string, mode string) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.ReloadUnit", name, mode)
}
// RestartUnit restarts a service. If a service is restarted that isn't
// running it will be started.
func (c *Conn) RestartUnit(name string, mode string) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.RestartUnit", name, mode)
}
// TryRestartUnit is like RestartUnit, except that a service that isn't running
// is not affected by the restart.
func (c *Conn) TryRestartUnit(name string, mode string) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode)
}
// ReloadOrRestart attempts a reload if the unit supports it and use a restart
// otherwise.
func (c *Conn) ReloadOrRestartUnit(name string, mode string) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode)
}
// ReloadOrTryRestart attempts a reload if the unit supports it and use a "Try"
// flavored restart otherwise.
func (c *Conn) ReloadOrTryRestartUnit(name string, mode string) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode)
}
// StartTransientUnit() may be used to create and start a transient unit, which
// will be released as soon as it is not running or referenced anymore or the
// system is rebooted. name is the unit name including suffix, and must be
// unique. mode is the same as in StartUnit(), properties contains properties
// of the unit.
func (c *Conn) StartTransientUnit(name string, mode string, properties ...Property) (string, error) {
return c.runJob("org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0))
}
// KillUnit takes the unit name and a UNIX signal number to send. All of the unit's
// processes are killed.
func (c *Conn) KillUnit(name string, signal int32) {
c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store()
}
// getProperties takes the unit name and returns all of its dbus object properties, for the given dbus interface
func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) {
var err error
var props map[string]dbus.Variant
path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit)
if !path.IsValid() {
return nil, errors.New("invalid unit name: " + unit)
}
obj := c.sysconn.Object("org.freedesktop.systemd1", path)
err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props)
if err != nil {
return nil, err
}
out := make(map[string]interface{}, len(props))
for k, v := range props {
out[k] = v.Value()
}
return out, nil
}
// GetUnitProperties takes the unit name and returns all of its dbus object properties.
func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
return c.getProperties(unit, "org.freedesktop.systemd1.Unit")
}
func (c *Conn) getProperty(unit string, dbusInterface string, propertyName string) (*Property, error) {
var err error
var prop dbus.Variant
path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit)
if !path.IsValid() {
return nil, errors.New("invalid unit name: " + unit)
}
obj := c.sysconn.Object("org.freedesktop.systemd1", path)
err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop)
if err != nil {
return nil, err
}
return &Property{Name: propertyName, Value: prop}, nil
}
func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) {
return c.getProperty(unit, "org.freedesktop.systemd1.Unit", propertyName)
}
// GetUnitTypeProperties returns the extra properties for a unit, specific to the unit type.
// Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope
// return "dbus.Error: Unknown interface" if the unitType is not the correct type of the unit
func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) {
return c.getProperties(unit, "org.freedesktop.systemd1."+unitType)
}
// SetUnitProperties() may be used to modify certain unit properties at runtime.
// Not all properties may be changed at runtime, but many resource management
// settings (primarily those in systemd.cgroup(5)) may. The changes are applied
// instantly, and stored on disk for future boots, unless runtime is true, in which
// case the settings only apply until the next reboot. name is the name of the unit
// to modify. properties are the settings to set, encoded as an array of property
// name and value pairs.
func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error {
return c.sysobj.Call("SetUnitProperties", 0, name, runtime, properties).Store()
}
func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) {
return c.getProperty(unit, "org.freedesktop.systemd1."+unitType, propertyName)
}
// ListUnits returns an array with all currently loaded units. Note that
// units may be known by multiple names at the same time, and hence there might
// be more unit names loaded than actual units behind them.
func (c *Conn) ListUnits() ([]UnitStatus, error) {
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0).Store(&result)
if err != nil {
return nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
status := make([]UnitStatus, len(result))
statusInterface := make([]interface{}, len(status))
for i := range status {
statusInterface[i] = &status[i]
}
err = dbus.Store(resultInterface, statusInterface...)
if err != nil {
return nil, err
}
return status, nil
}
type UnitStatus struct {
Name string // The primary unit name as string
Description string // The human readable description string
LoadState string // The load state (i.e. whether the unit file has been loaded successfully)
ActiveState string // The active state (i.e. whether the unit is currently started or not)
SubState string // The sub state (a more fine-grained version of the active state that is specific to the unit type, which the active state is not)
Followed string // A unit that is being followed in its state by this unit, if there is any, otherwise the empty string.
Path dbus.ObjectPath // The unit object path
JobId uint32 // If there is a job queued for the job unit the numeric job id, 0 otherwise
JobType string // The job type as string
JobPath dbus.ObjectPath // The job object path
}
// EnableUnitFiles() may be used to enable one or more units in the system (by
// creating symlinks to them in /etc or /run).
//
// It takes a list of unit files to enable (either just file names or full
// absolute paths if the unit files are residing outside the usual unit
// search paths), and two booleans: the first controls whether the unit shall
// be enabled for runtime only (true, /run), or persistently (false, /etc).
// The second one controls whether symlinks pointing to other units shall
// be replaced if necessary.
//
// This call returns one boolean and an array with the changes made. The
// boolean signals whether the unit files contained any enablement
// information (i.e. an [Install]) section. The changes list consists of
// structures with three strings: the type of the change (one of symlink
// or unlink), the file name of the symlink and the destination of the
// symlink.
func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) {
var carries_install_info bool
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result)
if err != nil {
return false, nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
changes := make([]EnableUnitFileChange, len(result))
changesInterface := make([]interface{}, len(changes))
for i := range changes {
changesInterface[i] = &changes[i]
}
err = dbus.Store(resultInterface, changesInterface...)
if err != nil {
return false, nil, err
}
return carries_install_info, changes, nil
}
type EnableUnitFileChange struct {
Type string // Type of the change (one of symlink or unlink)
Filename string // File name of the symlink
Destination string // Destination of the symlink
}
// DisableUnitFiles() may be used to disable one or more units in the system (by
// removing symlinks to them from /etc or /run).
//
// It takes a list of unit files to disable (either just file names or full
// absolute paths if the unit files are residing outside the usual unit
// search paths), and one boolean: whether the unit was enabled for runtime
// only (true, /run), or persistently (false, /etc).
//
// This call returns an array with the changes made. The changes list
// consists of structures with three strings: the type of the change (one of
// symlink or unlink), the file name of the symlink and the destination of the
// symlink.
func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) {
result := make([][]interface{}, 0)
err := c.sysobj.Call("DisableUnitFiles", 0, files, runtime).Store(&result)
if err != nil {
return nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
changes := make([]DisableUnitFileChange, len(result))
changesInterface := make([]interface{}, len(changes))
for i := range changes {
changesInterface[i] = &changes[i]
}
err = dbus.Store(resultInterface, changesInterface...)
if err != nil {
return nil, err
}
return changes, nil
}
type DisableUnitFileChange struct {
Type string // Type of the change (one of symlink or unlink)
Filename string // File name of the symlink
Destination string // Destination of the symlink
}
// Reload instructs systemd to scan for and reload unit files. This is
// equivalent to a 'systemctl daemon-reload'.
func (c *Conn) Reload() error {
return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store()
}

View File

@ -1,314 +0,0 @@
/*
Copyright 2013 CoreOS Inc.
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.
*/
package dbus
import (
"fmt"
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
"math/rand"
"os"
"path/filepath"
"reflect"
"testing"
)
func setupConn(t *testing.T) *Conn {
conn, err := New()
if err != nil {
t.Fatal(err)
}
return conn
}
func setupUnit(target string, conn *Conn, t *testing.T) {
// Blindly stop the unit in case it is running
conn.StopUnit(target, "replace")
// Blindly remove the symlink in case it exists
targetRun := filepath.Join("/run/systemd/system/", target)
err := os.Remove(targetRun)
// 1. Enable the unit
abs, err := filepath.Abs("../fixtures/" + target)
if err != nil {
t.Fatal(err)
}
fixture := []string{abs}
install, changes, err := conn.EnableUnitFiles(fixture, true, true)
if err != nil {
t.Fatal(err)
}
if install != false {
t.Fatal("Install was true")
}
if len(changes) < 1 {
t.Fatalf("Expected one change, got %v", changes)
}
if changes[0].Filename != targetRun {
t.Fatal("Unexpected target filename")
}
}
// Ensure that basic unit starting and stopping works.
func TestStartStopUnit(t *testing.T) {
target := "start-stop.service"
conn := setupConn(t)
setupUnit(target, conn, t)
// 2. Start the unit
job, err := conn.StartUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
if job != "done" {
t.Fatal("Job is not done, %v", job)
}
units, err := conn.ListUnits()
var unit *UnitStatus
for _, u := range units {
if u.Name == target {
unit = &u
}
}
if unit == nil {
t.Fatalf("Test unit not found in list")
}
if unit.ActiveState != "active" {
t.Fatalf("Test unit not active")
}
// 3. Stop the unit
job, err = conn.StopUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
units, err = conn.ListUnits()
unit = nil
for _, u := range units {
if u.Name == target {
unit = &u
}
}
if unit != nil {
t.Fatalf("Test unit found in list, should be stopped")
}
}
// Enables a unit and then immediately tears it down
func TestEnableDisableUnit(t *testing.T) {
target := "enable-disable.service"
conn := setupConn(t)
setupUnit(target, conn, t)
abs, err := filepath.Abs("../fixtures/" + target)
if err != nil {
t.Fatal(err)
}
path := filepath.Join("/run/systemd/system/", target)
// 2. Disable the unit
changes, err := conn.DisableUnitFiles([]string{abs}, true)
if err != nil {
t.Fatal(err)
}
if len(changes) != 1 {
t.Fatalf("Changes should include the path, %v", changes)
}
if changes[0].Filename != path {
t.Fatalf("Change should include correct filename, %+v", changes[0])
}
if changes[0].Destination != "" {
t.Fatalf("Change destination should be empty, %+v", changes[0])
}
}
// TestGetUnitProperties reads the `-.mount` which should exist on all systemd
// systems and ensures that one of its properties is valid.
func TestGetUnitProperties(t *testing.T) {
conn := setupConn(t)
unit := "-.mount"
info, err := conn.GetUnitProperties(unit)
if err != nil {
t.Fatal(err)
}
names := info["Wants"].([]string)
if len(names) < 1 {
t.Fatal("/ is unwanted")
}
if names[0] != "system.slice" {
t.Fatal("unexpected wants for /")
}
prop, err := conn.GetUnitProperty(unit, "Wants")
if err != nil {
t.Fatal(err)
}
if prop.Name != "Wants" {
t.Fatal("unexpected property name")
}
val := prop.Value.Value().([]string)
if !reflect.DeepEqual(val, names) {
t.Fatal("unexpected property value")
}
}
// TestGetUnitPropertiesRejectsInvalidName attempts to get the properties for a
// unit with an invalid name. This test should be run with --test.timeout set,
// as a fail will manifest as GetUnitProperties hanging indefinitely.
func TestGetUnitPropertiesRejectsInvalidName(t *testing.T) {
conn := setupConn(t)
unit := "//invalid#$^/"
_, err := conn.GetUnitProperties(unit)
if err == nil {
t.Fatal("Expected an error, got nil")
}
_, err = conn.GetUnitProperty(unit, "Wants")
if err == nil {
t.Fatal("Expected an error, got nil")
}
}
// TestSetUnitProperties changes a cgroup setting on the `tmp.mount`
// which should exist on all systemd systems and ensures that the
// property was set.
func TestSetUnitProperties(t *testing.T) {
conn := setupConn(t)
unit := "tmp.mount"
if err := conn.SetUnitProperties(unit, true, Property{"CPUShares", dbus.MakeVariant(uint64(1023))}); err != nil {
t.Fatal(err)
}
info, err := conn.GetUnitTypeProperties(unit, "Mount")
if err != nil {
t.Fatal(err)
}
value := info["CPUShares"].(uint64)
if value != 1023 {
t.Fatal("CPUShares of unit is not 1023, %s", value)
}
}
// Ensure that basic transient unit starting and stopping works.
func TestStartStopTransientUnit(t *testing.T) {
conn := setupConn(t)
props := []Property{
PropExecStart([]string{"/bin/sleep", "400"}, false),
}
target := fmt.Sprintf("testing-transient-%d.service", rand.Int())
// Start the unit
job, err := conn.StartTransientUnit(target, "replace", props...)
if err != nil {
t.Fatal(err)
}
if job != "done" {
t.Fatal("Job is not done, %v", job)
}
units, err := conn.ListUnits()
var unit *UnitStatus
for _, u := range units {
if u.Name == target {
unit = &u
}
}
if unit == nil {
t.Fatalf("Test unit not found in list")
}
if unit.ActiveState != "active" {
t.Fatalf("Test unit not active")
}
// 3. Stop the unit
job, err = conn.StopUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
units, err = conn.ListUnits()
unit = nil
for _, u := range units {
if u.Name == target {
unit = &u
}
}
if unit != nil {
t.Fatalf("Test unit found in list, should be stopped")
}
}
func TestConnJobListener(t *testing.T) {
target := "start-stop.service"
conn := setupConn(t)
setupUnit(target, conn, t)
jobSize := len(conn.jobListener.jobs)
_, err := conn.StartUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
_, err = conn.StopUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
currentJobSize := len(conn.jobListener.jobs)
if jobSize != currentJobSize {
t.Fatal("JobListener jobs leaked")
}
}

View File

@ -1,220 +0,0 @@
/*
Copyright 2013 CoreOS Inc.
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.
*/
package dbus
import (
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
)
// From the systemd docs:
//
// The properties array of StartTransientUnit() may take many of the settings
// that may also be configured in unit files. Not all parameters are currently
// accepted though, but we plan to cover more properties with future release.
// Currently you may set the Description, Slice and all dependency types of
// units, as well as RemainAfterExit, ExecStart for service units,
// TimeoutStopUSec and PIDs for scope units, and CPUAccounting, CPUShares,
// BlockIOAccounting, BlockIOWeight, BlockIOReadBandwidth,
// BlockIOWriteBandwidth, BlockIODeviceWeight, MemoryAccounting, MemoryLimit,
// DevicePolicy, DeviceAllow for services/scopes/slices. These fields map
// directly to their counterparts in unit files and as normal D-Bus object
// properties. The exception here is the PIDs field of scope units which is
// used for construction of the scope only and specifies the initial PIDs to
// add to the scope object.
type Property struct {
Name string
Value dbus.Variant
}
type PropertyCollection struct {
Name string
Properties []Property
}
type execStart struct {
Path string // the binary path to execute
Args []string // an array with all arguments to pass to the executed command, starting with argument 0
UncleanIsFailure bool // a boolean whether it should be considered a failure if the process exits uncleanly
}
// PropExecStart sets the ExecStart service property. The first argument is a
// slice with the binary path to execute followed by the arguments to pass to
// the executed command. See
// http://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart=
func PropExecStart(command []string, uncleanIsFailure bool) Property {
execStarts := []execStart{
execStart{
Path: command[0],
Args: command,
UncleanIsFailure: uncleanIsFailure,
},
}
return Property{
Name: "ExecStart",
Value: dbus.MakeVariant(execStarts),
}
}
// PropRemainAfterExit sets the RemainAfterExit service property. See
// http://www.freedesktop.org/software/systemd/man/systemd.service.html#RemainAfterExit=
func PropRemainAfterExit(b bool) Property {
return Property{
Name: "RemainAfterExit",
Value: dbus.MakeVariant(b),
}
}
// PropDescription sets the Description unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit#Description=
func PropDescription(desc string) Property {
return Property{
Name: "Description",
Value: dbus.MakeVariant(desc),
}
}
func propDependency(name string, units []string) Property {
return Property{
Name: name,
Value: dbus.MakeVariant(units),
}
}
// PropRequires sets the Requires unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requires=
func PropRequires(units ...string) Property {
return propDependency("Requires", units)
}
// PropRequiresOverridable sets the RequiresOverridable unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresOverridable=
func PropRequiresOverridable(units ...string) Property {
return propDependency("RequiresOverridable", units)
}
// PropRequisite sets the Requisite unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requisite=
func PropRequisite(units ...string) Property {
return propDependency("Requisite", units)
}
// PropRequisiteOverridable sets the RequisiteOverridable unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequisiteOverridable=
func PropRequisiteOverridable(units ...string) Property {
return propDependency("RequisiteOverridable", units)
}
// PropWants sets the Wants unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Wants=
func PropWants(units ...string) Property {
return propDependency("Wants", units)
}
// PropBindsTo sets the BindsTo unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#BindsTo=
func PropBindsTo(units ...string) Property {
return propDependency("BindsTo", units)
}
// PropRequiredBy sets the RequiredBy unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredBy=
func PropRequiredBy(units ...string) Property {
return propDependency("RequiredBy", units)
}
// PropRequiredByOverridable sets the RequiredByOverridable unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredByOverridable=
func PropRequiredByOverridable(units ...string) Property {
return propDependency("RequiredByOverridable", units)
}
// PropWantedBy sets the WantedBy unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#WantedBy=
func PropWantedBy(units ...string) Property {
return propDependency("WantedBy", units)
}
// PropBoundBy sets the BoundBy unit property. See
// http://www.freedesktop.org/software/systemd/main/systemd.unit.html#BoundBy=
func PropBoundBy(units ...string) Property {
return propDependency("BoundBy", units)
}
// PropConflicts sets the Conflicts unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Conflicts=
func PropConflicts(units ...string) Property {
return propDependency("Conflicts", units)
}
// PropConflictedBy sets the ConflictedBy unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#ConflictedBy=
func PropConflictedBy(units ...string) Property {
return propDependency("ConflictedBy", units)
}
// PropBefore sets the Before unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before=
func PropBefore(units ...string) Property {
return propDependency("Before", units)
}
// PropAfter sets the After unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#After=
func PropAfter(units ...string) Property {
return propDependency("After", units)
}
// PropOnFailure sets the OnFailure unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#OnFailure=
func PropOnFailure(units ...string) Property {
return propDependency("OnFailure", units)
}
// PropTriggers sets the Triggers unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Triggers=
func PropTriggers(units ...string) Property {
return propDependency("Triggers", units)
}
// PropTriggeredBy sets the TriggeredBy unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#TriggeredBy=
func PropTriggeredBy(units ...string) Property {
return propDependency("TriggeredBy", units)
}
// PropPropagatesReloadTo sets the PropagatesReloadTo unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#PropagatesReloadTo=
func PropPropagatesReloadTo(units ...string) Property {
return propDependency("PropagatesReloadTo", units)
}
// PropRequiresMountsFor sets the RequiresMountsFor unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresMountsFor=
func PropRequiresMountsFor(units ...string) Property {
return propDependency("RequiresMountsFor", units)
}
// PropSlice sets the Slice unit property. See
// http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#Slice=
func PropSlice(slice string) Property {
return Property{
Name: "Slice",
Value: dbus.MakeVariant(slice),
}
}

View File

@ -1,26 +0,0 @@
package dbus
type set struct {
data map[string]bool
}
func (s *set) Add(value string) {
s.data[value] = true
}
func (s *set) Remove(value string) {
delete(s.data, value)
}
func (s *set) Contains(value string) (exists bool) {
_, exists = s.data[value]
return
}
func (s *set) Length() (int) {
return len(s.data)
}
func newSet() (*set) {
return &set{make(map[string] bool)}
}

View File

@ -1,26 +0,0 @@
package dbus
import (
"testing"
)
// TestBasicSetActions asserts that Add & Remove behavior is correct
func TestBasicSetActions(t *testing.T) {
s := newSet()
if s.Contains("foo") {
t.Fatal("set should not contain 'foo'")
}
s.Add("foo")
if !s.Contains("foo") {
t.Fatal("set should contain 'foo'")
}
s.Remove("foo")
if s.Contains("foo") {
t.Fatal("set should not contain 'foo'")
}
}

View File

@ -1,249 +0,0 @@
/*
Copyright 2013 CoreOS Inc.
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.
*/
package dbus
import (
"errors"
"time"
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
)
const (
cleanIgnoreInterval = int64(10 * time.Second)
ignoreInterval = int64(30 * time.Millisecond)
)
// Subscribe sets up this connection to subscribe to all systemd dbus events.
// This is required before calling SubscribeUnits. When the connection closes
// systemd will automatically stop sending signals so there is no need to
// explicitly call Unsubscribe().
func (c *Conn) Subscribe() error {
c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'")
c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'")
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store()
if err != nil {
c.sysconn.Close()
return err
}
return nil
}
// Unsubscribe this connection from systemd dbus events.
func (c *Conn) Unsubscribe() error {
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store()
if err != nil {
c.sysconn.Close()
return err
}
return nil
}
func (c *Conn) initSubscription() {
c.subscriber.ignore = make(map[dbus.ObjectPath]int64)
}
func (c *Conn) initDispatch() {
ch := make(chan *dbus.Signal, signalBuffer)
c.sysconn.Signal(ch)
go func() {
for {
signal := <-ch
switch signal.Name {
case "org.freedesktop.systemd1.Manager.JobRemoved":
c.jobComplete(signal)
unitName := signal.Body[2].(string)
var unitPath dbus.ObjectPath
c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath)
if unitPath != dbus.ObjectPath("") {
c.sendSubStateUpdate(unitPath)
}
case "org.freedesktop.systemd1.Manager.UnitNew":
c.sendSubStateUpdate(signal.Body[1].(dbus.ObjectPath))
case "org.freedesktop.DBus.Properties.PropertiesChanged":
if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" {
// we only care about SubState updates, which are a Unit property
c.sendSubStateUpdate(signal.Path)
}
}
}
}()
}
// Returns two unbuffered channels which will receive all changed units every
// interval. Deleted units are sent as nil.
func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) {
return c.SubscribeUnitsCustom(interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil)
}
// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer
// size of the channels, the comparison function for detecting changes and a filter
// function for cutting down on the noise that your channel receives.
func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func(string) bool) (<-chan map[string]*UnitStatus, <-chan error) {
old := make(map[string]*UnitStatus)
statusChan := make(chan map[string]*UnitStatus, buffer)
errChan := make(chan error, buffer)
go func() {
for {
timerChan := time.After(interval)
units, err := c.ListUnits()
if err == nil {
cur := make(map[string]*UnitStatus)
for i := range units {
if filterUnit != nil && filterUnit(units[i].Name) {
continue
}
cur[units[i].Name] = &units[i]
}
// add all new or changed units
changed := make(map[string]*UnitStatus)
for n, u := range cur {
if oldU, ok := old[n]; !ok || isChanged(oldU, u) {
changed[n] = u
}
delete(old, n)
}
// add all deleted units
for oldN := range old {
changed[oldN] = nil
}
old = cur
if len(changed) != 0 {
statusChan <- changed
}
} else {
errChan <- err
}
<-timerChan
}
}()
return statusChan, errChan
}
type SubStateUpdate struct {
UnitName string
SubState string
}
// SetSubStateSubscriber writes to updateCh when any unit's substate changes.
// Although this writes to updateCh on every state change, the reported state
// may be more recent than the change that generated it (due to an unavoidable
// race in the systemd dbus interface). That is, this method provides a good
// way to keep a current view of all units' states, but is not guaranteed to
// show every state transition they go through. Furthermore, state changes
// will only be written to the channel with non-blocking writes. If updateCh
// is full, it attempts to write an error to errCh; if errCh is full, the error
// passes silently.
func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan<- error) {
c.subscriber.Lock()
defer c.subscriber.Unlock()
c.subscriber.updateCh = updateCh
c.subscriber.errCh = errCh
}
func (c *Conn) sendSubStateUpdate(path dbus.ObjectPath) {
c.subscriber.Lock()
defer c.subscriber.Unlock()
if c.subscriber.updateCh == nil {
return
}
if c.shouldIgnore(path) {
return
}
info, err := c.GetUnitProperties(string(path))
if err != nil {
select {
case c.subscriber.errCh <- err:
default:
}
}
name := info["Id"].(string)
substate := info["SubState"].(string)
update := &SubStateUpdate{name, substate}
select {
case c.subscriber.updateCh <- update:
default:
select {
case c.subscriber.errCh <- errors.New("update channel full!"):
default:
}
}
c.updateIgnore(path, info)
}
// The ignore functions work around a wart in the systemd dbus interface.
// Requesting the properties of an unloaded unit will cause systemd to send a
// pair of UnitNew/UnitRemoved signals. Because we need to get a unit's
// properties on UnitNew (as that's the only indication of a new unit coming up
// for the first time), we would enter an infinite loop if we did not attempt
// to detect and ignore these spurious signals. The signal themselves are
// indistinguishable from relevant ones, so we (somewhat hackishly) ignore an
// unloaded unit's signals for a short time after requesting its properties.
// This means that we will miss e.g. a transient unit being restarted
// *immediately* upon failure and also a transient unit being started
// immediately after requesting its status (with systemctl status, for example,
// because this causes a UnitNew signal to be sent which then causes us to fetch
// the properties).
func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool {
t, ok := c.subscriber.ignore[path]
return ok && t >= time.Now().UnixNano()
}
func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) {
c.cleanIgnore()
// unit is unloaded - it will trigger bad systemd dbus behavior
if info["LoadState"].(string) == "not-found" {
c.subscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval
}
}
// without this, ignore would grow unboundedly over time
func (c *Conn) cleanIgnore() {
now := time.Now().UnixNano()
if c.subscriber.cleanIgnore < now {
c.subscriber.cleanIgnore = now + cleanIgnoreInterval
for p, t := range c.subscriber.ignore {
if t < now {
delete(c.subscriber.ignore, p)
}
}
}
}

View File

@ -1,32 +0,0 @@
package dbus
import (
"time"
)
// SubscriptionSet returns a subscription set which is like conn.Subscribe but
// can filter to only return events for a set of units.
type SubscriptionSet struct {
*set
conn *Conn
}
func (s *SubscriptionSet) filter(unit string) bool {
return !s.Contains(unit)
}
// Subscribe starts listening for dbus events for all of the units in the set.
// Returns channels identical to conn.SubscribeUnits.
func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) {
// TODO: Make fully evented by using systemd 209 with properties changed values
return s.conn.SubscribeUnitsCustom(time.Second, 0,
func(u1, u2 *UnitStatus) bool { return *u1 != *u2 },
func(unit string) bool { return s.filter(unit) },
)
}
// NewSubscriptionSet returns a new subscription set.
func (conn *Conn) NewSubscriptionSet() (*SubscriptionSet) {
return &SubscriptionSet{newSet(), conn}
}

View File

@ -1,67 +0,0 @@
package dbus
import (
"testing"
"time"
)
// TestSubscribeUnit exercises the basics of subscription of a particular unit.
func TestSubscriptionSetUnit(t *testing.T) {
target := "subscribe-events-set.service"
conn, err := New()
if err != nil {
t.Fatal(err)
}
err = conn.Subscribe()
if err != nil {
t.Fatal(err)
}
subSet := conn.NewSubscriptionSet()
evChan, errChan := subSet.Subscribe()
subSet.Add(target)
setupUnit(target, conn, t)
job, err := conn.StartUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
if job != "done" {
t.Fatal("Couldn't start", target)
}
timeout := make(chan bool, 1)
go func() {
time.Sleep(3 * time.Second)
close(timeout)
}()
for {
select {
case changes := <-evChan:
tCh, ok := changes[target]
if !ok {
t.Fatal("Unexpected event %v", changes)
}
if tCh.ActiveState == "active" && tCh.Name == target {
goto success
}
case err = <-errChan:
t.Fatal(err)
case <-timeout:
t.Fatal("Reached timeout")
}
}
success:
return
}

View File

@ -1,90 +0,0 @@
package dbus
import (
"testing"
"time"
)
// TestSubscribe exercises the basics of subscription
func TestSubscribe(t *testing.T) {
conn, err := New()
if err != nil {
t.Fatal(err)
}
err = conn.Subscribe()
if err != nil {
t.Fatal(err)
}
err = conn.Unsubscribe()
if err != nil {
t.Fatal(err)
}
}
// TestSubscribeUnit exercises the basics of subscription of a particular unit.
func TestSubscribeUnit(t *testing.T) {
target := "subscribe-events.service"
conn, err := New()
if err != nil {
t.Fatal(err)
}
err = conn.Subscribe()
if err != nil {
t.Fatal(err)
}
err = conn.Unsubscribe()
if err != nil {
t.Fatal(err)
}
evChan, errChan := conn.SubscribeUnits(time.Second)
setupUnit(target, conn, t)
job, err := conn.StartUnit(target, "replace")
if err != nil {
t.Fatal(err)
}
if job != "done" {
t.Fatal("Couldn't start", target)
}
timeout := make(chan bool, 1)
go func() {
time.Sleep(3 * time.Second)
close(timeout)
}()
for {
select {
case changes := <-evChan:
tCh, ok := changes[target]
// Just continue until we see our event.
if !ok {
continue
}
if tCh.ActiveState == "active" && tCh.Name == target {
goto success
}
case err = <-errChan:
t.Fatal(err)
case <-timeout:
t.Fatal("Reached timeout")
}
}
success:
return
}

View File

@ -1,2 +0,0 @@
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
Guillaume J. Charmes <guillaume@docker.com> (@creack)

View File

@ -1,23 +0,0 @@
// Packet netlink provide access to low level Netlink sockets and messages.
//
// Actual implementations are in:
// netlink_linux.go
// netlink_darwin.go
package netlink
import (
"errors"
"net"
)
var (
ErrWrongSockType = errors.New("Wrong socket type")
ErrShortResponse = errors.New("Got short response from netlink")
)
// A Route is a subnet associated with the interface to reach it.
type Route struct {
*net.IPNet
Iface *net.Interface
Default bool
}

View File

@ -1,891 +0,0 @@
// +build amd64
package netlink
import (
"encoding/binary"
"fmt"
"math/rand"
"net"
"syscall"
"unsafe"
)
const (
IFNAMSIZ = 16
DEFAULT_CHANGE = 0xFFFFFFFF
IFLA_INFO_KIND = 1
IFLA_INFO_DATA = 2
VETH_INFO_PEER = 1
IFLA_NET_NS_FD = 28
SIOC_BRADDBR = 0x89a0
SIOC_BRADDIF = 0x89a2
)
var nextSeqNr int
type ifreqHwaddr struct {
IfrnName [16]byte
IfruHwaddr syscall.RawSockaddr
}
type ifreqIndex struct {
IfrnName [16]byte
IfruIndex int32
}
func nativeEndian() binary.ByteOrder {
var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
return binary.BigEndian
}
return binary.LittleEndian
}
func getSeq() int {
nextSeqNr = nextSeqNr + 1
return nextSeqNr
}
func getIpFamily(ip net.IP) int {
if len(ip) <= net.IPv4len {
return syscall.AF_INET
}
if ip.To4() != nil {
return syscall.AF_INET
}
return syscall.AF_INET6
}
type NetlinkRequestData interface {
Len() int
ToWireFormat() []byte
}
type IfInfomsg struct {
syscall.IfInfomsg
}
func newIfInfomsg(family int) *IfInfomsg {
return &IfInfomsg{
IfInfomsg: syscall.IfInfomsg{
Family: uint8(family),
},
}
}
func newIfInfomsgChild(parent *RtAttr, family int) *IfInfomsg {
msg := newIfInfomsg(family)
parent.children = append(parent.children, msg)
return msg
}
func (msg *IfInfomsg) ToWireFormat() []byte {
native := nativeEndian()
length := syscall.SizeofIfInfomsg
b := make([]byte, length)
b[0] = msg.Family
b[1] = 0
native.PutUint16(b[2:4], msg.Type)
native.PutUint32(b[4:8], uint32(msg.Index))
native.PutUint32(b[8:12], msg.Flags)
native.PutUint32(b[12:16], msg.Change)
return b
}
func (msg *IfInfomsg) Len() int {
return syscall.SizeofIfInfomsg
}
type IfAddrmsg struct {
syscall.IfAddrmsg
}
func newIfAddrmsg(family int) *IfAddrmsg {
return &IfAddrmsg{
IfAddrmsg: syscall.IfAddrmsg{
Family: uint8(family),
},
}
}
func (msg *IfAddrmsg) ToWireFormat() []byte {
native := nativeEndian()
length := syscall.SizeofIfAddrmsg
b := make([]byte, length)
b[0] = msg.Family
b[1] = msg.Prefixlen
b[2] = msg.Flags
b[3] = msg.Scope
native.PutUint32(b[4:8], msg.Index)
return b
}
func (msg *IfAddrmsg) Len() int {
return syscall.SizeofIfAddrmsg
}
type RtMsg struct {
syscall.RtMsg
}
func newRtMsg(family int) *RtMsg {
return &RtMsg{
RtMsg: syscall.RtMsg{
Family: uint8(family),
Table: syscall.RT_TABLE_MAIN,
Scope: syscall.RT_SCOPE_UNIVERSE,
Protocol: syscall.RTPROT_BOOT,
Type: syscall.RTN_UNICAST,
},
}
}
func (msg *RtMsg) ToWireFormat() []byte {
native := nativeEndian()
length := syscall.SizeofRtMsg
b := make([]byte, length)
b[0] = msg.Family
b[1] = msg.Dst_len
b[2] = msg.Src_len
b[3] = msg.Tos
b[4] = msg.Table
b[5] = msg.Protocol
b[6] = msg.Scope
b[7] = msg.Type
native.PutUint32(b[8:12], msg.Flags)
return b
}
func (msg *RtMsg) Len() int {
return syscall.SizeofRtMsg
}
func rtaAlignOf(attrlen int) int {
return (attrlen + syscall.RTA_ALIGNTO - 1) & ^(syscall.RTA_ALIGNTO - 1)
}
type RtAttr struct {
syscall.RtAttr
Data []byte
children []NetlinkRequestData
}
func newRtAttr(attrType int, data []byte) *RtAttr {
return &RtAttr{
RtAttr: syscall.RtAttr{
Type: uint16(attrType),
},
children: []NetlinkRequestData{},
Data: data,
}
}
func newRtAttrChild(parent *RtAttr, attrType int, data []byte) *RtAttr {
attr := newRtAttr(attrType, data)
parent.children = append(parent.children, attr)
return attr
}
func (a *RtAttr) Len() int {
l := 0
for _, child := range a.children {
l += child.Len() + syscall.SizeofRtAttr
}
if l == 0 {
l++
}
return rtaAlignOf(l + len(a.Data))
}
func (a *RtAttr) ToWireFormat() []byte {
native := nativeEndian()
length := a.Len()
buf := make([]byte, rtaAlignOf(length+syscall.SizeofRtAttr))
if a.Data != nil {
copy(buf[4:], a.Data)
} else {
next := 4
for _, child := range a.children {
childBuf := child.ToWireFormat()
copy(buf[next:], childBuf)
next += rtaAlignOf(len(childBuf))
}
}
if l := uint16(rtaAlignOf(length)); l != 0 {
native.PutUint16(buf[0:2], l+1)
}
native.PutUint16(buf[2:4], a.Type)
return buf
}
type NetlinkRequest struct {
syscall.NlMsghdr
Data []NetlinkRequestData
}
func (rr *NetlinkRequest) ToWireFormat() []byte {
native := nativeEndian()
length := rr.Len
dataBytes := make([][]byte, len(rr.Data))
for i, data := range rr.Data {
dataBytes[i] = data.ToWireFormat()
length += uint32(len(dataBytes[i]))
}
b := make([]byte, length)
native.PutUint32(b[0:4], length)
native.PutUint16(b[4:6], rr.Type)
native.PutUint16(b[6:8], rr.Flags)
native.PutUint32(b[8:12], rr.Seq)
native.PutUint32(b[12:16], rr.Pid)
next := 16
for _, data := range dataBytes {
copy(b[next:], data)
next += len(data)
}
return b
}
func (rr *NetlinkRequest) AddData(data NetlinkRequestData) {
if data != nil {
rr.Data = append(rr.Data, data)
}
}
func newNetlinkRequest(proto, flags int) *NetlinkRequest {
return &NetlinkRequest{
NlMsghdr: syscall.NlMsghdr{
Len: uint32(syscall.NLMSG_HDRLEN),
Type: uint16(proto),
Flags: syscall.NLM_F_REQUEST | uint16(flags),
Seq: uint32(getSeq()),
},
}
}
type NetlinkSocket struct {
fd int
lsa syscall.SockaddrNetlink
}
func getNetlinkSocket() (*NetlinkSocket, error) {
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_ROUTE)
if err != nil {
return nil, err
}
s := &NetlinkSocket{
fd: fd,
}
s.lsa.Family = syscall.AF_NETLINK
if err := syscall.Bind(fd, &s.lsa); err != nil {
syscall.Close(fd)
return nil, err
}
return s, nil
}
func (s *NetlinkSocket) Close() {
syscall.Close(s.fd)
}
func (s *NetlinkSocket) Send(request *NetlinkRequest) error {
if err := syscall.Sendto(s.fd, request.ToWireFormat(), 0, &s.lsa); err != nil {
return err
}
return nil
}
func (s *NetlinkSocket) Receive() ([]syscall.NetlinkMessage, error) {
rb := make([]byte, syscall.Getpagesize())
nr, _, err := syscall.Recvfrom(s.fd, rb, 0)
if err != nil {
return nil, err
}
if nr < syscall.NLMSG_HDRLEN {
return nil, ErrShortResponse
}
rb = rb[:nr]
return syscall.ParseNetlinkMessage(rb)
}
func (s *NetlinkSocket) GetPid() (uint32, error) {
lsa, err := syscall.Getsockname(s.fd)
if err != nil {
return 0, err
}
switch v := lsa.(type) {
case *syscall.SockaddrNetlink:
return v.Pid, nil
}
return 0, ErrWrongSockType
}
func (s *NetlinkSocket) HandleAck(seq uint32) error {
native := nativeEndian()
pid, err := s.GetPid()
if err != nil {
return err
}
done:
for {
msgs, err := s.Receive()
if err != nil {
return err
}
for _, m := range msgs {
if m.Header.Seq != seq {
return fmt.Errorf("Wrong Seq nr %d, expected %d", m.Header.Seq, seq)
}
if m.Header.Pid != pid {
return fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
}
if m.Header.Type == syscall.NLMSG_DONE {
break done
}
if m.Header.Type == syscall.NLMSG_ERROR {
error := int32(native.Uint32(m.Data[0:4]))
if error == 0 {
break done
}
return syscall.Errno(-error)
}
}
}
return nil
}
// Add a new default gateway. Identical to:
// ip route add default via $ip
func AddDefaultGw(ip net.IP) error {
s, err := getNetlinkSocket()
if err != nil {
return err
}
defer s.Close()
family := getIpFamily(ip)
wb := newNetlinkRequest(syscall.RTM_NEWROUTE, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
msg := newRtMsg(family)
wb.AddData(msg)
var ipData []byte
if family == syscall.AF_INET {
ipData = ip.To4()
} else {
ipData = ip.To16()
}
gateway := newRtAttr(syscall.RTA_GATEWAY, ipData)
wb.AddData(gateway)
if err := s.Send(wb); err != nil {
return err
}
return s.HandleAck(wb.Seq)
}
// Bring up a particular network interface
func NetworkLinkUp(iface *net.Interface) error {
s, err := getNetlinkSocket()
if err != nil {
return err
}
defer s.Close()
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
msg.Change = syscall.IFF_UP
msg.Flags = syscall.IFF_UP
msg.Index = int32(iface.Index)
wb.AddData(msg)
if err := s.Send(wb); err != nil {
return err
}
return s.HandleAck(wb.Seq)
}
func NetworkLinkDown(iface *net.Interface) error {
s, err := getNetlinkSocket()
if err != nil {
return err
}
defer s.Close()
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
msg.Change = syscall.IFF_UP
msg.Flags = 0 & ^syscall.IFF_UP
msg.Index = int32(iface.Index)
wb.AddData(msg)
if err := s.Send(wb); err != nil {
return err
}
return s.HandleAck(wb.Seq)
}
func NetworkSetMTU(iface *net.Interface, mtu int) error {
s, err := getNetlinkSocket()
if err != nil {
return err
}
defer s.Close()
wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
msg.Type = syscall.RTM_SETLINK
msg.Flags = syscall.NLM_F_REQUEST
msg.Index = int32(iface.Index)
msg.Change = DEFAULT_CHANGE
wb.AddData(msg)
var (
b = make([]byte, 4)
native = nativeEndian()
)
native.PutUint32(b, uint32(mtu))
data := newRtAttr(syscall.IFLA_MTU, b)
wb.AddData(data)
if err := s.Send(wb); err != nil {
return err
}
return s.HandleAck(wb.Seq)
}
// same as ip link set $name master $master
func NetworkSetMaster(iface, master *net.Interface) error {
s, err := getNetlinkSocket()
if err != nil {
return err
}
defer s.Close()
wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
msg.Type = syscall.RTM_SETLINK
msg.Flags = syscall.NLM_F_REQUEST
msg.Index = int32(iface.Index)
msg.Change = DEFAULT_CHANGE
wb.AddData(msg)
var (
b = make([]byte, 4)
native = nativeEndian()
)
native.PutUint32(b, uint32(master.Index))
data := newRtAttr(syscall.IFLA_MASTER, b)
wb.AddData(data)
if err := s.Send(wb); err != nil {
return err
}
return s.HandleAck(wb.Seq)
}
func NetworkSetNsPid(iface *net.Interface, nspid int) error {
s, err := getNetlinkSocket()
if err != nil {
return err
}
defer s.Close()
wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
msg.Type = syscall.RTM_SETLINK
msg.Flags = syscall.NLM_F_REQUEST
msg.Index = int32(iface.Index)
msg.Change = DEFAULT_CHANGE
wb.AddData(msg)
var (
b = make([]byte, 4)
native = nativeEndian()
)
native.PutUint32(b, uint32(nspid))
data := newRtAttr(syscall.IFLA_NET_NS_PID, b)
wb.AddData(data)
if err := s.Send(wb); err != nil {
return err
}
return s.HandleAck(wb.Seq)
}
func NetworkSetNsFd(iface *net.Interface, fd int) error {
s, err := getNetlinkSocket()
if err != nil {
return err
}
defer s.Close()
wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
msg.Type = syscall.RTM_SETLINK
msg.Flags = syscall.NLM_F_REQUEST
msg.Index = int32(iface.Index)
msg.Change = DEFAULT_CHANGE
wb.AddData(msg)
var (
b = make([]byte, 4)
native = nativeEndian()
)
native.PutUint32(b, uint32(fd))
data := newRtAttr(IFLA_NET_NS_FD, b)
wb.AddData(data)
if err := s.Send(wb); err != nil {
return err
}
return s.HandleAck(wb.Seq)
}
// Add an Ip address to an interface. This is identical to:
// ip addr add $ip/$ipNet dev $iface
func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
s, err := getNetlinkSocket()
if err != nil {
return err
}
defer s.Close()
family := getIpFamily(ip)
wb := newNetlinkRequest(syscall.RTM_NEWADDR, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
msg := newIfAddrmsg(family)
msg.Index = uint32(iface.Index)
prefixLen, _ := ipNet.Mask.Size()
msg.Prefixlen = uint8(prefixLen)
wb.AddData(msg)
var ipData []byte
if family == syscall.AF_INET {
ipData = ip.To4()
} else {
ipData = ip.To16()
}
localData := newRtAttr(syscall.IFA_LOCAL, ipData)
wb.AddData(localData)
addrData := newRtAttr(syscall.IFA_ADDRESS, ipData)
wb.AddData(addrData)
if err := s.Send(wb); err != nil {
return err
}
return s.HandleAck(wb.Seq)
}
func zeroTerminated(s string) []byte {
return []byte(s + "\000")
}
func nonZeroTerminated(s string) []byte {
return []byte(s)
}
// Add a new network link of a specified type. This is identical to
// running: ip add link $name type $linkType
func NetworkLinkAdd(name string, linkType string) error {
s, err := getNetlinkSocket()
if err != nil {
return err
}
defer s.Close()
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
wb.AddData(msg)
if name != "" {
nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name))
wb.AddData(nameData)
}
kindData := newRtAttr(IFLA_INFO_KIND, nonZeroTerminated(linkType))
infoData := newRtAttr(syscall.IFLA_LINKINFO, kindData.ToWireFormat())
wb.AddData(infoData)
if err := s.Send(wb); err != nil {
return err
}
return s.HandleAck(wb.Seq)
}
// Returns an array of IPNet for all the currently routed subnets on ipv4
// This is similar to the first column of "ip route" output
func NetworkGetRoutes() ([]Route, error) {
native := nativeEndian()
s, err := getNetlinkSocket()
if err != nil {
return nil, err
}
defer s.Close()
wb := newNetlinkRequest(syscall.RTM_GETROUTE, syscall.NLM_F_DUMP)
msg := newIfInfomsg(syscall.AF_UNSPEC)
wb.AddData(msg)
if err := s.Send(wb); err != nil {
return nil, err
}
pid, err := s.GetPid()
if err != nil {
return nil, err
}
res := make([]Route, 0)
done:
for {
msgs, err := s.Receive()
if err != nil {
return nil, err
}
for _, m := range msgs {
if m.Header.Seq != wb.Seq {
return nil, fmt.Errorf("Wrong Seq nr %d, expected 1", m.Header.Seq)
}
if m.Header.Pid != pid {
return nil, fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
}
if m.Header.Type == syscall.NLMSG_DONE {
break done
}
if m.Header.Type == syscall.NLMSG_ERROR {
error := int32(native.Uint32(m.Data[0:4]))
if error == 0 {
break done
}
return nil, syscall.Errno(-error)
}
if m.Header.Type != syscall.RTM_NEWROUTE {
continue
}
var r Route
msg := (*RtMsg)(unsafe.Pointer(&m.Data[0:syscall.SizeofRtMsg][0]))
if msg.Flags&syscall.RTM_F_CLONED != 0 {
// Ignore cloned routes
continue
}
if msg.Table != syscall.RT_TABLE_MAIN {
// Ignore non-main tables
continue
}
if msg.Family != syscall.AF_INET {
// Ignore non-ipv4 routes
continue
}
if msg.Dst_len == 0 {
// Default routes
r.Default = true
}
attrs, err := syscall.ParseNetlinkRouteAttr(&m)
if err != nil {
return nil, err
}
for _, attr := range attrs {
switch attr.Attr.Type {
case syscall.RTA_DST:
ip := attr.Value
r.IPNet = &net.IPNet{
IP: ip,
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(ip)),
}
case syscall.RTA_OIF:
index := int(native.Uint32(attr.Value[0:4]))
r.Iface, _ = net.InterfaceByIndex(index)
}
}
if r.Default || r.IPNet != nil {
res = append(res, r)
}
}
}
return res, nil
}
func getIfSocket() (fd int, err error) {
for _, socket := range []int{
syscall.AF_INET,
syscall.AF_PACKET,
syscall.AF_INET6,
} {
if fd, err = syscall.Socket(socket, syscall.SOCK_DGRAM, 0); err == nil {
break
}
}
if err == nil {
return fd, nil
}
return -1, err
}
func NetworkChangeName(iface *net.Interface, newName string) error {
fd, err := getIfSocket()
if err != nil {
return err
}
defer syscall.Close(fd)
data := [IFNAMSIZ * 2]byte{}
// the "-1"s here are very important for ensuring we get proper null
// termination of our new C strings
copy(data[:IFNAMSIZ-1], iface.Name)
copy(data[IFNAMSIZ:IFNAMSIZ*2-1], newName)
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&data[0]))); errno != 0 {
return errno
}
return nil
}
func NetworkCreateVethPair(name1, name2 string) error {
s, err := getNetlinkSocket()
if err != nil {
return err
}
defer s.Close()
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
msg := newIfInfomsg(syscall.AF_UNSPEC)
wb.AddData(msg)
nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name1))
wb.AddData(nameData)
nest1 := newRtAttr(syscall.IFLA_LINKINFO, nil)
newRtAttrChild(nest1, IFLA_INFO_KIND, zeroTerminated("veth"))
nest2 := newRtAttrChild(nest1, IFLA_INFO_DATA, nil)
nest3 := newRtAttrChild(nest2, VETH_INFO_PEER, nil)
newIfInfomsgChild(nest3, syscall.AF_UNSPEC)
newRtAttrChild(nest3, syscall.IFLA_IFNAME, zeroTerminated(name2))
wb.AddData(nest1)
if err := s.Send(wb); err != nil {
return err
}
return s.HandleAck(wb.Seq)
}
// Create the actual bridge device. This is more backward-compatible than
// netlink.NetworkLinkAdd and works on RHEL 6.
func CreateBridge(name string, setMacAddr bool) error {
s, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_IP)
if err != nil {
// ipv6 issue, creating with ipv4
s, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_IP)
if err != nil {
return err
}
}
defer syscall.Close(s)
nameBytePtr, err := syscall.BytePtrFromString(name)
if err != nil {
return err
}
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), SIOC_BRADDBR, uintptr(unsafe.Pointer(nameBytePtr))); err != 0 {
return err
}
if setMacAddr {
return setBridgeMacAddress(s, name)
}
return nil
}
// Add a slave to abridge device. This is more backward-compatible than
// netlink.NetworkSetMaster and works on RHEL 6.
func AddToBridge(iface, master *net.Interface) error {
s, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_IP)
if err != nil {
// ipv6 issue, creating with ipv4
s, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_IP)
if err != nil {
return err
}
}
defer syscall.Close(s)
ifr := ifreqIndex{}
copy(ifr.IfrnName[:], master.Name)
ifr.IfruIndex = int32(iface.Index)
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), SIOC_BRADDIF, uintptr(unsafe.Pointer(&ifr))); err != 0 {
return err
}
return nil
}
func setBridgeMacAddress(s int, name string) error {
ifr := ifreqHwaddr{}
ifr.IfruHwaddr.Family = syscall.ARPHRD_ETHER
copy(ifr.IfrnName[:], name)
for i := 0; i < 6; i++ {
ifr.IfruHwaddr.Data[i] = int8(rand.Intn(255))
}
ifr.IfruHwaddr.Data[0] &^= 0x1 // clear multicast bit
ifr.IfruHwaddr.Data[0] |= 0x2 // set local assignment bit (IEEE802)
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), syscall.SIOCSIFHWADDR, uintptr(unsafe.Pointer(&ifr))); err != 0 {
return err
}
return nil
}

View File

@ -1,69 +0,0 @@
// +build !linux !amd64
package netlink
import (
"errors"
"net"
)
var (
ErrNotImplemented = errors.New("not implemented")
)
func NetworkGetRoutes() ([]Route, error) {
return nil, ErrNotImplemented
}
func NetworkLinkAdd(name string, linkType string) error {
return ErrNotImplemented
}
func NetworkLinkUp(iface *net.Interface) error {
return ErrNotImplemented
}
func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
return ErrNotImplemented
}
func AddDefaultGw(ip net.IP) error {
return ErrNotImplemented
}
func NetworkSetMTU(iface *net.Interface, mtu int) error {
return ErrNotImplemented
}
func NetworkCreateVethPair(name1, name2 string) error {
return ErrNotImplemented
}
func NetworkChangeName(iface *net.Interface, newName string) error {
return ErrNotImplemented
}
func NetworkSetNsFd(iface *net.Interface, fd int) error {
return ErrNotImplemented
}
func NetworkSetNsPid(iface *net.Interface, nspid int) error {
return ErrNotImplemented
}
func NetworkSetMaster(iface, master *net.Interface) error {
return ErrNotImplemented
}
func NetworkLinkDown(iface *net.Interface) error {
return ErrNotImplemented
}
func CreateBridge(name string, setMacAddr bool) error {
return ErrNotImplemented
}
func AddToBridge(iface, master *net.Interface) error {
return ErrNotImplemented
}

View File

@ -1,25 +0,0 @@
Copyright (c) 2013, Georg Reinke (<guelfey at gmail dot com>)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,36 +0,0 @@
go.dbus
-------
go.dbus is a simple library that implements native Go client bindings for the
D-Bus message bus system.
### Features
* Complete native implementation of the D-Bus message protocol
* Go-like API (channels for signals / asynchronous method calls, Goroutine-safe connections)
* Subpackages that help with the introspection / property interfaces
### Installation
This packages requires Go 1.1. If you installed it and set up your GOPATH, just run:
```
go get github.com/guelfey/go.dbus
```
If you want to use the subpackages, you can install them the same way.
### Usage
The complete package documentation and some simple examples are available at
[godoc.org](http://godoc.org/github.com/guelfey/go.dbus). Also, the
[_examples](https://github.com/guelfey/go.dbus/tree/master/_examples) directory
gives a short overview over the basic usage.
Please note that the API is considered unstable for now and may change without
further notice.
### License
go.dbus is available under the Simplified BSD License; see LICENSE for the full
text.

View File

@ -1,255 +0,0 @@
package dbus
import (
"bufio"
"bytes"
"errors"
"io"
"os/user"
)
// AuthStatus represents the Status of an authentication mechanism.
type AuthStatus byte
const (
// AuthOk signals that authentication is finished; the next command
// from the server should be an OK.
AuthOk AuthStatus = iota
// AuthContinue signals that additional data is needed; the next command
// from the server should be a DATA.
AuthContinue
// AuthError signals an error; the server sent invalid data or some
// other unexpected thing happened and the current authentication
// process should be aborted.
AuthError
)
type authState byte
const (
waitingForData authState = iota
waitingForOk
waitingForReject
)
// Auth defines the behaviour of an authentication mechanism.
type Auth interface {
// Return the name of the mechnism, the argument to the first AUTH command
// and the next status.
FirstData() (name, resp []byte, status AuthStatus)
// Process the given DATA command, and return the argument to the DATA
// command and the next status. If len(resp) == 0, no DATA command is sent.
HandleData(data []byte) (resp []byte, status AuthStatus)
}
// Auth authenticates the connection, trying the given list of authentication
// mechanisms (in that order). If nil is passed, the EXTERNAL and
// DBUS_COOKIE_SHA1 mechanisms are tried for the current user. For private
// connections, this method must be called before sending any messages to the
// bus. Auth must not be called on shared connections.
func (conn *Conn) Auth(methods []Auth) error {
if methods == nil {
u, err := user.Current()
if err != nil {
return err
}
methods = []Auth{AuthExternal(u.Username), AuthCookieSha1(u.Username, u.HomeDir)}
}
in := bufio.NewReader(conn.transport)
err := conn.transport.SendNullByte()
if err != nil {
return err
}
err = authWriteLine(conn.transport, []byte("AUTH"))
if err != nil {
return err
}
s, err := authReadLine(in)
if err != nil {
return err
}
if len(s) < 2 || !bytes.Equal(s[0], []byte("REJECTED")) {
return errors.New("dbus: authentication protocol error")
}
s = s[1:]
for _, v := range s {
for _, m := range methods {
if name, data, status := m.FirstData(); bytes.Equal(v, name) {
var ok bool
err = authWriteLine(conn.transport, []byte("AUTH"), []byte(v), data)
if err != nil {
return err
}
switch status {
case AuthOk:
err, ok = conn.tryAuth(m, waitingForOk, in)
case AuthContinue:
err, ok = conn.tryAuth(m, waitingForData, in)
default:
panic("dbus: invalid authentication status")
}
if err != nil {
return err
}
if ok {
if conn.transport.SupportsUnixFDs() {
err = authWriteLine(conn, []byte("NEGOTIATE_UNIX_FD"))
if err != nil {
return err
}
line, err := authReadLine(in)
if err != nil {
return err
}
switch {
case bytes.Equal(line[0], []byte("AGREE_UNIX_FD")):
conn.EnableUnixFDs()
conn.unixFD = true
case bytes.Equal(line[0], []byte("ERROR")):
default:
return errors.New("dbus: authentication protocol error")
}
}
err = authWriteLine(conn.transport, []byte("BEGIN"))
if err != nil {
return err
}
go conn.inWorker()
go conn.outWorker()
return nil
}
}
}
}
return errors.New("dbus: authentication failed")
}
// tryAuth tries to authenticate with m as the mechanism, using state as the
// initial authState and in for reading input. It returns (nil, true) on
// success, (nil, false) on a REJECTED and (someErr, false) if some other
// error occured.
func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, bool) {
for {
s, err := authReadLine(in)
if err != nil {
return err, false
}
switch {
case state == waitingForData && string(s[0]) == "DATA":
if len(s) != 2 {
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
continue
}
data, status := m.HandleData(s[1])
switch status {
case AuthOk, AuthContinue:
if len(data) != 0 {
err = authWriteLine(conn.transport, []byte("DATA"), data)
if err != nil {
return err, false
}
}
if status == AuthOk {
state = waitingForOk
}
case AuthError:
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
}
case state == waitingForData && string(s[0]) == "REJECTED":
return nil, false
case state == waitingForData && string(s[0]) == "ERROR":
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
case state == waitingForData && string(s[0]) == "OK":
if len(s) != 2 {
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
}
conn.uuid = string(s[1])
return nil, true
case state == waitingForData:
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
case state == waitingForOk && string(s[0]) == "OK":
if len(s) != 2 {
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
}
conn.uuid = string(s[1])
return nil, true
case state == waitingForOk && string(s[0]) == "REJECTED":
return nil, false
case state == waitingForOk && (string(s[0]) == "DATA" ||
string(s[0]) == "ERROR"):
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
case state == waitingForOk:
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
case state == waitingForReject && string(s[0]) == "REJECTED":
return nil, false
case state == waitingForReject:
return errors.New("dbus: authentication protocol error"), false
default:
panic("dbus: invalid auth state")
}
}
}
// authReadLine reads a line and separates it into its fields.
func authReadLine(in *bufio.Reader) ([][]byte, error) {
data, err := in.ReadBytes('\n')
if err != nil {
return nil, err
}
data = bytes.TrimSuffix(data, []byte("\r\n"))
return bytes.Split(data, []byte{' '}), nil
}
// authWriteLine writes the given line in the authentication protocol format
// (elements of data separated by a " " and terminated by "\r\n").
func authWriteLine(out io.Writer, data ...[]byte) error {
buf := make([]byte, 0)
for i, v := range data {
buf = append(buf, v...)
if i != len(data)-1 {
buf = append(buf, ' ')
}
}
buf = append(buf, '\r')
buf = append(buf, '\n')
n, err := out.Write(buf)
if err != nil {
return err
}
if n != len(buf) {
return io.ErrUnexpectedEOF
}
return nil
}

View File

@ -1,26 +0,0 @@
package dbus
import (
"encoding/hex"
)
// AuthExternal returns an Auth that authenticates as the given user with the
// EXTERNAL mechanism.
func AuthExternal(user string) Auth {
return authExternal{user}
}
// AuthExternal implements the EXTERNAL authentication mechanism.
type authExternal struct {
user string
}
func (a authExternal) FirstData() ([]byte, []byte, AuthStatus) {
b := make([]byte, 2*len(a.user))
hex.Encode(b, []byte(a.user))
return []byte("EXTERNAL"), b, AuthOk
}
func (a authExternal) HandleData(b []byte) ([]byte, AuthStatus) {
return nil, AuthError
}

View File

@ -1,102 +0,0 @@
package dbus
import (
"bufio"
"bytes"
"crypto/rand"
"crypto/sha1"
"encoding/hex"
"os"
)
// AuthCookieSha1 returns an Auth that authenticates as the given user with the
// DBUS_COOKIE_SHA1 mechanism. The home parameter should specify the home
// directory of the user.
func AuthCookieSha1(user, home string) Auth {
return authCookieSha1{user, home}
}
type authCookieSha1 struct {
user, home string
}
func (a authCookieSha1) FirstData() ([]byte, []byte, AuthStatus) {
b := make([]byte, 2*len(a.user))
hex.Encode(b, []byte(a.user))
return []byte("DBUS_COOKIE_SHA1"), b, AuthContinue
}
func (a authCookieSha1) HandleData(data []byte) ([]byte, AuthStatus) {
challenge := make([]byte, len(data)/2)
_, err := hex.Decode(challenge, data)
if err != nil {
return nil, AuthError
}
b := bytes.Split(challenge, []byte{' '})
if len(b) != 3 {
return nil, AuthError
}
context := b[0]
id := b[1]
svchallenge := b[2]
cookie := a.getCookie(context, id)
if cookie == nil {
return nil, AuthError
}
clchallenge := a.generateChallenge()
if clchallenge == nil {
return nil, AuthError
}
hash := sha1.New()
hash.Write(bytes.Join([][]byte{svchallenge, clchallenge, cookie}, []byte{':'}))
hexhash := make([]byte, 2*hash.Size())
hex.Encode(hexhash, hash.Sum(nil))
data = append(clchallenge, ' ')
data = append(data, hexhash...)
resp := make([]byte, 2*len(data))
hex.Encode(resp, data)
return resp, AuthOk
}
// getCookie searches for the cookie identified by id in context and returns
// the cookie content or nil. (Since HandleData can't return a specific error,
// but only whether an error occured, this function also doesn't bother to
// return an error.)
func (a authCookieSha1) getCookie(context, id []byte) []byte {
file, err := os.Open(a.home + "/.dbus-keyrings/" + string(context))
if err != nil {
return nil
}
defer file.Close()
rd := bufio.NewReader(file)
for {
line, err := rd.ReadBytes('\n')
if err != nil {
return nil
}
line = line[:len(line)-1]
b := bytes.Split(line, []byte{' '})
if len(b) != 3 {
return nil
}
if bytes.Equal(b[0], id) {
return b[2]
}
}
}
// generateChallenge returns a random, hex-encoded challenge, or nil on error
// (see above).
func (a authCookieSha1) generateChallenge() []byte {
b := make([]byte, 16)
n, err := rand.Read(b)
if err != nil {
return nil
}
if n != 16 {
return nil
}
enc := make([]byte, 32)
hex.Encode(enc, b)
return enc
}

View File

@ -1,147 +0,0 @@
package dbus
import (
"errors"
"strings"
)
// Call represents a pending or completed method call.
type Call struct {
Destination string
Path ObjectPath
Method string
Args []interface{}
// Strobes when the call is complete.
Done chan *Call
// After completion, the error status. If this is non-nil, it may be an
// error message from the peer (with Error as its type) or some other error.
Err error
// Holds the response once the call is done.
Body []interface{}
}
var errSignature = errors.New("dbus: mismatched signature")
// Store stores the body of the reply into the provided pointers. It returns
// an error if the signatures of the body and retvalues don't match, or if
// the error status is not nil.
func (c *Call) Store(retvalues ...interface{}) error {
if c.Err != nil {
return c.Err
}
return Store(c.Body, retvalues...)
}
// Object represents a remote object on which methods can be invoked.
type Object struct {
conn *Conn
dest string
path ObjectPath
}
// Call calls a method with (*Object).Go and waits for its reply.
func (o *Object) Call(method string, flags Flags, args ...interface{}) *Call {
return <-o.Go(method, flags, make(chan *Call, 1), args...).Done
}
// GetProperty calls org.freedesktop.DBus.Properties.GetProperty on the given
// object. The property name must be given in interface.member notation.
func (o *Object) GetProperty(p string) (Variant, error) {
idx := strings.LastIndex(p, ".")
if idx == -1 || idx+1 == len(p) {
return Variant{}, errors.New("dbus: invalid property " + p)
}
iface := p[:idx]
prop := p[idx+1:]
result := Variant{}
err := o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).Store(&result)
if err != nil {
return Variant{}, err
}
return result, nil
}
// Go calls a method with the given arguments asynchronously. It returns a
// Call structure representing this method call. The passed channel will
// return the same value once the call is done. If ch is nil, a new channel
// will be allocated. Otherwise, ch has to be buffered or Go will panic.
//
// If the flags include FlagNoReplyExpected, ch is ignored and a Call structure
// is returned of which only the Err member is valid.
//
// If the method parameter contains a dot ('.'), the part before the last dot
// specifies the interface on which the method is called.
func (o *Object) Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
iface := ""
i := strings.LastIndex(method, ".")
if i != -1 {
iface = method[:i]
}
method = method[i+1:]
msg := new(Message)
msg.Type = TypeMethodCall
msg.serial = o.conn.getSerial()
msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected)
msg.Headers = make(map[HeaderField]Variant)
msg.Headers[FieldPath] = MakeVariant(o.path)
msg.Headers[FieldDestination] = MakeVariant(o.dest)
msg.Headers[FieldMember] = MakeVariant(method)
if iface != "" {
msg.Headers[FieldInterface] = MakeVariant(iface)
}
msg.Body = args
if len(args) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...))
}
if msg.Flags&FlagNoReplyExpected == 0 {
if ch == nil {
ch = make(chan *Call, 10)
} else if cap(ch) == 0 {
panic("dbus: unbuffered channel passed to (*Object).Go")
}
call := &Call{
Destination: o.dest,
Path: o.path,
Method: method,
Args: args,
Done: ch,
}
o.conn.callsLck.Lock()
o.conn.calls[msg.serial] = call
o.conn.callsLck.Unlock()
o.conn.outLck.RLock()
if o.conn.closed {
call.Err = ErrClosed
call.Done <- call
} else {
o.conn.out <- msg
}
o.conn.outLck.RUnlock()
return call
}
o.conn.outLck.RLock()
defer o.conn.outLck.RUnlock()
if o.conn.closed {
return &Call{Err: ErrClosed}
}
o.conn.out <- msg
return &Call{Err: nil}
}
// Destination returns the destination that calls on o are sent to.
func (o *Object) Destination() string {
return o.dest
}
// Path returns the path that calls on o are sent to.
func (o *Object) Path() ObjectPath {
return o.path
}

View File

@ -1,597 +0,0 @@
package dbus
import (
"errors"
"io"
"os"
"reflect"
"strings"
"sync"
)
const defaultSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket"
var (
systemBus *Conn
systemBusLck sync.Mutex
sessionBus *Conn
sessionBusLck sync.Mutex
)
// ErrClosed is the error returned by calls on a closed connection.
var ErrClosed = errors.New("dbus: connection closed by user")
// Conn represents a connection to a message bus (usually, the system or
// session bus).
//
// Connections are either shared or private. Shared connections
// are shared between calls to the functions that return them. As a result,
// the methods Close, Auth and Hello must not be called on them.
//
// Multiple goroutines may invoke methods on a connection simultaneously.
type Conn struct {
transport
busObj *Object
unixFD bool
uuid string
names []string
namesLck sync.RWMutex
serialLck sync.Mutex
nextSerial uint32
serialUsed map[uint32]bool
calls map[uint32]*Call
callsLck sync.RWMutex
handlers map[ObjectPath]map[string]interface{}
handlersLck sync.RWMutex
out chan *Message
closed bool
outLck sync.RWMutex
signals []chan<- *Signal
signalsLck sync.Mutex
eavesdropped chan<- *Message
eavesdroppedLck sync.Mutex
}
// SessionBus returns a shared connection to the session bus, connecting to it
// if not already done.
func SessionBus() (conn *Conn, err error) {
sessionBusLck.Lock()
defer sessionBusLck.Unlock()
if sessionBus != nil {
return sessionBus, nil
}
defer func() {
if conn != nil {
sessionBus = conn
}
}()
conn, err = SessionBusPrivate()
if err != nil {
return
}
if err = conn.Auth(nil); err != nil {
conn.Close()
conn = nil
return
}
if err = conn.Hello(); err != nil {
conn.Close()
conn = nil
}
return
}
// SessionBusPrivate returns a new private connection to the session bus.
func SessionBusPrivate() (*Conn, error) {
address := os.Getenv("DBUS_SESSION_BUS_ADDRESS")
if address != "" && address != "autolaunch:" {
return Dial(address)
}
return sessionBusPlatform()
}
// SystemBus returns a shared connection to the system bus, connecting to it if
// not already done.
func SystemBus() (conn *Conn, err error) {
systemBusLck.Lock()
defer systemBusLck.Unlock()
if systemBus != nil {
return systemBus, nil
}
defer func() {
if conn != nil {
systemBus = conn
}
}()
conn, err = SystemBusPrivate()
if err != nil {
return
}
if err = conn.Auth(nil); err != nil {
conn.Close()
conn = nil
return
}
if err = conn.Hello(); err != nil {
conn.Close()
conn = nil
}
return
}
// SystemBusPrivate returns a new private connection to the system bus.
func SystemBusPrivate() (*Conn, error) {
address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS")
if address != "" {
return Dial(address)
}
return Dial(defaultSystemBusAddress)
}
// Dial establishes a new private connection to the message bus specified by address.
func Dial(address string) (*Conn, error) {
tr, err := getTransport(address)
if err != nil {
return nil, err
}
return newConn(tr)
}
// NewConn creates a new private *Conn from an already established connection.
func NewConn(conn io.ReadWriteCloser) (*Conn, error) {
return newConn(genericTransport{conn})
}
// newConn creates a new *Conn from a transport.
func newConn(tr transport) (*Conn, error) {
conn := new(Conn)
conn.transport = tr
conn.calls = make(map[uint32]*Call)
conn.out = make(chan *Message, 10)
conn.handlers = make(map[ObjectPath]map[string]interface{})
conn.nextSerial = 1
conn.serialUsed = map[uint32]bool{0: true}
conn.busObj = conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")
return conn, nil
}
// BusObject returns the object owned by the bus daemon which handles
// administrative requests.
func (conn *Conn) BusObject() *Object {
return conn.busObj
}
// Close closes the connection. Any blocked operations will return with errors
// and the channels passed to Eavesdrop and Signal are closed. This method must
// not be called on shared connections.
func (conn *Conn) Close() error {
conn.outLck.Lock()
close(conn.out)
conn.closed = true
conn.outLck.Unlock()
conn.signalsLck.Lock()
for _, ch := range conn.signals {
close(ch)
}
conn.signalsLck.Unlock()
conn.eavesdroppedLck.Lock()
if conn.eavesdropped != nil {
close(conn.eavesdropped)
}
conn.eavesdroppedLck.Unlock()
return conn.transport.Close()
}
// Eavesdrop causes conn to send all incoming messages to the given channel
// without further processing. Method replies, errors and signals will not be
// sent to the appropiate channels and method calls will not be handled. If nil
// is passed, the normal behaviour is restored.
//
// The caller has to make sure that ch is sufficiently buffered;
// if a message arrives when a write to ch is not possible, the message is
// discarded.
func (conn *Conn) Eavesdrop(ch chan<- *Message) {
conn.eavesdroppedLck.Lock()
conn.eavesdropped = ch
conn.eavesdroppedLck.Unlock()
}
// getSerial returns an unused serial.
func (conn *Conn) getSerial() uint32 {
conn.serialLck.Lock()
defer conn.serialLck.Unlock()
n := conn.nextSerial
for conn.serialUsed[n] {
n++
}
conn.serialUsed[n] = true
conn.nextSerial = n + 1
return n
}
// Hello sends the initial org.freedesktop.DBus.Hello call. This method must be
// called after authentication, but before sending any other messages to the
// bus. Hello must not be called for shared connections.
func (conn *Conn) Hello() error {
var s string
err := conn.busObj.Call("org.freedesktop.DBus.Hello", 0).Store(&s)
if err != nil {
return err
}
conn.namesLck.Lock()
conn.names = make([]string, 1)
conn.names[0] = s
conn.namesLck.Unlock()
return nil
}
// inWorker runs in an own goroutine, reading incoming messages from the
// transport and dispatching them appropiately.
func (conn *Conn) inWorker() {
for {
msg, err := conn.ReadMessage()
if err == nil {
conn.eavesdroppedLck.Lock()
if conn.eavesdropped != nil {
select {
case conn.eavesdropped <- msg:
default:
}
conn.eavesdroppedLck.Unlock()
continue
}
conn.eavesdroppedLck.Unlock()
dest, _ := msg.Headers[FieldDestination].value.(string)
found := false
if dest == "" {
found = true
} else {
conn.namesLck.RLock()
if len(conn.names) == 0 {
found = true
}
for _, v := range conn.names {
if dest == v {
found = true
break
}
}
conn.namesLck.RUnlock()
}
if !found {
// Eavesdropped a message, but no channel for it is registered.
// Ignore it.
continue
}
switch msg.Type {
case TypeMethodReply, TypeError:
serial := msg.Headers[FieldReplySerial].value.(uint32)
conn.callsLck.Lock()
if c, ok := conn.calls[serial]; ok {
if msg.Type == TypeError {
name, _ := msg.Headers[FieldErrorName].value.(string)
c.Err = Error{name, msg.Body}
} else {
c.Body = msg.Body
}
c.Done <- c
conn.serialLck.Lock()
delete(conn.serialUsed, serial)
conn.serialLck.Unlock()
delete(conn.calls, serial)
}
conn.callsLck.Unlock()
case TypeSignal:
iface := msg.Headers[FieldInterface].value.(string)
member := msg.Headers[FieldMember].value.(string)
// as per http://dbus.freedesktop.org/doc/dbus-specification.html ,
// sender is optional for signals.
sender, _ := msg.Headers[FieldSender].value.(string)
if iface == "org.freedesktop.DBus" && member == "NameLost" &&
sender == "org.freedesktop.DBus" {
name, _ := msg.Body[0].(string)
conn.namesLck.Lock()
for i, v := range conn.names {
if v == name {
copy(conn.names[i:], conn.names[i+1:])
conn.names = conn.names[:len(conn.names)-1]
}
}
conn.namesLck.Unlock()
}
signal := &Signal{
Sender: sender,
Path: msg.Headers[FieldPath].value.(ObjectPath),
Name: iface + "." + member,
Body: msg.Body,
}
conn.signalsLck.Lock()
for _, ch := range conn.signals {
// don't block trying to send a signal
select {
case ch <- signal:
default:
}
}
conn.signalsLck.Unlock()
case TypeMethodCall:
go conn.handleCall(msg)
}
} else if _, ok := err.(InvalidMessageError); !ok {
// Some read error occured (usually EOF); we can't really do
// anything but to shut down all stuff and returns errors to all
// pending replies.
conn.Close()
conn.callsLck.RLock()
for _, v := range conn.calls {
v.Err = err
v.Done <- v
}
conn.callsLck.RUnlock()
return
}
// invalid messages are ignored
}
}
// Names returns the list of all names that are currently owned by this
// connection. The slice is always at least one element long, the first element
// being the unique name of the connection.
func (conn *Conn) Names() []string {
conn.namesLck.RLock()
// copy the slice so it can't be modified
s := make([]string, len(conn.names))
copy(s, conn.names)
conn.namesLck.RUnlock()
return s
}
// Object returns the object identified by the given destination name and path.
func (conn *Conn) Object(dest string, path ObjectPath) *Object {
return &Object{conn, dest, path}
}
// outWorker runs in an own goroutine, encoding and sending messages that are
// sent to conn.out.
func (conn *Conn) outWorker() {
for msg := range conn.out {
err := conn.SendMessage(msg)
conn.callsLck.RLock()
if err != nil {
if c := conn.calls[msg.serial]; c != nil {
c.Err = err
c.Done <- c
}
conn.serialLck.Lock()
delete(conn.serialUsed, msg.serial)
conn.serialLck.Unlock()
} else if msg.Type != TypeMethodCall {
conn.serialLck.Lock()
delete(conn.serialUsed, msg.serial)
conn.serialLck.Unlock()
}
conn.callsLck.RUnlock()
}
}
// Send sends the given message to the message bus. You usually don't need to
// use this; use the higher-level equivalents (Call / Go, Emit and Export)
// instead. If msg is a method call and NoReplyExpected is not set, a non-nil
// call is returned and the same value is sent to ch (which must be buffered)
// once the call is complete. Otherwise, ch is ignored and a Call structure is
// returned of which only the Err member is valid.
func (conn *Conn) Send(msg *Message, ch chan *Call) *Call {
var call *Call
msg.serial = conn.getSerial()
if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 {
if ch == nil {
ch = make(chan *Call, 5)
} else if cap(ch) == 0 {
panic("dbus: unbuffered channel passed to (*Conn).Send")
}
call = new(Call)
call.Destination, _ = msg.Headers[FieldDestination].value.(string)
call.Path, _ = msg.Headers[FieldPath].value.(ObjectPath)
iface, _ := msg.Headers[FieldInterface].value.(string)
member, _ := msg.Headers[FieldMember].value.(string)
call.Method = iface + "." + member
call.Args = msg.Body
call.Done = ch
conn.callsLck.Lock()
conn.calls[msg.serial] = call
conn.callsLck.Unlock()
conn.outLck.RLock()
if conn.closed {
call.Err = ErrClosed
call.Done <- call
} else {
conn.out <- msg
}
conn.outLck.RUnlock()
} else {
conn.outLck.RLock()
if conn.closed {
call = &Call{Err: ErrClosed}
} else {
conn.out <- msg
call = &Call{Err: nil}
}
conn.outLck.RUnlock()
}
return call
}
// sendError creates an error message corresponding to the parameters and sends
// it to conn.out.
func (conn *Conn) sendError(e Error, dest string, serial uint32) {
msg := new(Message)
msg.Type = TypeError
msg.serial = conn.getSerial()
msg.Headers = make(map[HeaderField]Variant)
msg.Headers[FieldDestination] = MakeVariant(dest)
msg.Headers[FieldErrorName] = MakeVariant(e.Name)
msg.Headers[FieldReplySerial] = MakeVariant(serial)
msg.Body = e.Body
if len(e.Body) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(e.Body...))
}
conn.outLck.RLock()
if !conn.closed {
conn.out <- msg
}
conn.outLck.RUnlock()
}
// sendReply creates a method reply message corresponding to the parameters and
// sends it to conn.out.
func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) {
msg := new(Message)
msg.Type = TypeMethodReply
msg.serial = conn.getSerial()
msg.Headers = make(map[HeaderField]Variant)
msg.Headers[FieldDestination] = MakeVariant(dest)
msg.Headers[FieldReplySerial] = MakeVariant(serial)
msg.Body = values
if len(values) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
}
conn.outLck.RLock()
if !conn.closed {
conn.out <- msg
}
conn.outLck.RUnlock()
}
// Signal registers the given channel to be passed all received signal messages.
// The caller has to make sure that ch is sufficiently buffered; if a message
// arrives when a write to c is not possible, it is discarded.
//
// Multiple of these channels can be registered at the same time. Passing a
// channel that already is registered will remove it from the list of the
// registered channels.
//
// These channels are "overwritten" by Eavesdrop; i.e., if there currently is a
// channel for eavesdropped messages, this channel receives all signals, and
// none of the channels passed to Signal will receive any signals.
func (conn *Conn) Signal(ch chan<- *Signal) {
conn.signalsLck.Lock()
conn.signals = append(conn.signals, ch)
conn.signalsLck.Unlock()
}
// SupportsUnixFDs returns whether the underlying transport supports passing of
// unix file descriptors. If this is false, method calls containing unix file
// descriptors will return an error and emitted signals containing them will
// not be sent.
func (conn *Conn) SupportsUnixFDs() bool {
return conn.unixFD
}
// Error represents a D-Bus message of type Error.
type Error struct {
Name string
Body []interface{}
}
func (e Error) Error() string {
if len(e.Body) >= 1 {
s, ok := e.Body[0].(string)
if ok {
return s
}
}
return e.Name
}
// Signal represents a D-Bus message of type Signal. The name member is given in
// "interface.member" notation, e.g. org.freedesktop.D-Bus.NameLost.
type Signal struct {
Sender string
Path ObjectPath
Name string
Body []interface{}
}
// transport is a D-Bus transport.
type transport interface {
// Read and Write raw data (for example, for the authentication protocol).
io.ReadWriteCloser
// Send the initial null byte used for the EXTERNAL mechanism.
SendNullByte() error
// Returns whether this transport supports passing Unix FDs.
SupportsUnixFDs() bool
// Signal the transport that Unix FD passing is enabled for this connection.
EnableUnixFDs()
// Read / send a message, handling things like Unix FDs.
ReadMessage() (*Message, error)
SendMessage(*Message) error
}
func getTransport(address string) (transport, error) {
var err error
var t transport
m := map[string]func(string) (transport, error){
"unix": newUnixTransport,
}
addresses := strings.Split(address, ";")
for _, v := range addresses {
i := strings.IndexRune(v, ':')
if i == -1 {
err = errors.New("dbus: invalid bus address (no transport)")
continue
}
f := m[v[:i]]
if f == nil {
err = errors.New("dbus: invalid bus address (invalid or unsupported transport)")
}
t, err = f(v[i+1:])
if err == nil {
return t, nil
}
}
return nil, err
}
// dereferenceAll returns a slice that, assuming that vs is a slice of pointers
// of arbitrary types, containes the values that are obtained from dereferencing
// all elements in vs.
func dereferenceAll(vs []interface{}) []interface{} {
for i := range vs {
v := reflect.ValueOf(vs[i])
v = v.Elem()
vs[i] = v.Interface()
}
return vs
}
// getKey gets a key from a the list of keys. Returns "" on error / not found...
func getKey(s, key string) string {
i := strings.Index(s, key)
if i == -1 {
return ""
}
if i+len(key)+1 >= len(s) || s[i+len(key)] != '=' {
return ""
}
j := strings.Index(s, ",")
if j == -1 {
j = len(s)
}
return s[i+len(key)+1 : j]
}

View File

@ -1,21 +0,0 @@
package dbus
import (
"errors"
"os/exec"
)
func sessionBusPlatform() (*Conn, error) {
cmd := exec.Command("launchctl", "getenv", "DBUS_LAUNCHD_SESSION_BUS_SOCKET")
b, err := cmd.CombinedOutput()
if err != nil {
return nil, err
}
if len(b) == 0 {
return nil, errors.New("dbus: couldn't determine address of session bus")
}
return Dial("unix:path=" + string(b[:len(b)-1]))
}

View File

@ -1,27 +0,0 @@
// +build !darwin
package dbus
import (
"bytes"
"errors"
"os/exec"
)
func sessionBusPlatform() (*Conn, error) {
cmd := exec.Command("dbus-launch")
b, err := cmd.CombinedOutput()
if err != nil {
return nil, err
}
i := bytes.IndexByte(b, '=')
j := bytes.IndexByte(b, '\n')
if i == -1 || j == -1 {
return nil, errors.New("dbus: couldn't determine address of session bus")
}
return Dial(string(b[i+1 : j]))
}

View File

@ -1,199 +0,0 @@
package dbus
import "testing"
func TestSessionBus(t *testing.T) {
_, err := SessionBus()
if err != nil {
t.Error(err)
}
}
func TestSystemBus(t *testing.T) {
_, err := SystemBus()
if err != nil {
t.Error(err)
}
}
func TestSend(t *testing.T) {
bus, err := SessionBus()
if err != nil {
t.Error(err)
}
ch := make(chan *Call, 1)
msg := &Message{
Type: TypeMethodCall,
Flags: 0,
Headers: map[HeaderField]Variant{
FieldDestination: MakeVariant(bus.Names()[0]),
FieldPath: MakeVariant(ObjectPath("/org/freedesktop/DBus")),
FieldInterface: MakeVariant("org.freedesktop.DBus.Peer"),
FieldMember: MakeVariant("Ping"),
},
}
call := bus.Send(msg, ch)
<-ch
if call.Err != nil {
t.Error(call.Err)
}
}
type server struct{}
func (server) Double(i int64) (int64, *Error) {
return 2 * i, nil
}
func BenchmarkCall(b *testing.B) {
b.StopTimer()
var s string
bus, err := SessionBus()
if err != nil {
b.Fatal(err)
}
name := bus.Names()[0]
obj := bus.BusObject()
b.StartTimer()
for i := 0; i < b.N; i++ {
err := obj.Call("org.freedesktop.DBus.GetNameOwner", 0, name).Store(&s)
if err != nil {
b.Fatal(err)
}
if s != name {
b.Errorf("got %s, wanted %s", s, name)
}
}
}
func BenchmarkCallAsync(b *testing.B) {
b.StopTimer()
bus, err := SessionBus()
if err != nil {
b.Fatal(err)
}
name := bus.Names()[0]
obj := bus.BusObject()
c := make(chan *Call, 50)
done := make(chan struct{})
go func() {
for i := 0; i < b.N; i++ {
v := <-c
if v.Err != nil {
b.Error(v.Err)
}
s := v.Body[0].(string)
if s != name {
b.Errorf("got %s, wanted %s", s, name)
}
}
close(done)
}()
b.StartTimer()
for i := 0; i < b.N; i++ {
obj.Go("org.freedesktop.DBus.GetNameOwner", 0, c, name)
}
<-done
}
func BenchmarkServe(b *testing.B) {
b.StopTimer()
srv, err := SessionBus()
if err != nil {
b.Fatal(err)
}
cli, err := SessionBusPrivate()
if err != nil {
b.Fatal(err)
}
if err = cli.Auth(nil); err != nil {
b.Fatal(err)
}
if err = cli.Hello(); err != nil {
b.Fatal(err)
}
benchmarkServe(b, srv, cli)
}
func BenchmarkServeAsync(b *testing.B) {
b.StopTimer()
srv, err := SessionBus()
if err != nil {
b.Fatal(err)
}
cli, err := SessionBusPrivate()
if err != nil {
b.Fatal(err)
}
if err = cli.Auth(nil); err != nil {
b.Fatal(err)
}
if err = cli.Hello(); err != nil {
b.Fatal(err)
}
benchmarkServeAsync(b, srv, cli)
}
func BenchmarkServeSameConn(b *testing.B) {
b.StopTimer()
bus, err := SessionBus()
if err != nil {
b.Fatal(err)
}
benchmarkServe(b, bus, bus)
}
func BenchmarkServeSameConnAsync(b *testing.B) {
b.StopTimer()
bus, err := SessionBus()
if err != nil {
b.Fatal(err)
}
benchmarkServeAsync(b, bus, bus)
}
func benchmarkServe(b *testing.B, srv, cli *Conn) {
var r int64
var err error
dest := srv.Names()[0]
srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test")
obj := cli.Object(dest, "/org/guelfey/DBus/Test")
b.StartTimer()
for i := 0; i < b.N; i++ {
err = obj.Call("org.guelfey.DBus.Test.Double", 0, int64(i)).Store(&r)
if err != nil {
b.Fatal(err)
}
if r != 2*int64(i) {
b.Errorf("got %d, wanted %d", r, 2*int64(i))
}
}
}
func benchmarkServeAsync(b *testing.B, srv, cli *Conn) {
dest := srv.Names()[0]
srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test")
obj := cli.Object(dest, "/org/guelfey/DBus/Test")
c := make(chan *Call, 50)
done := make(chan struct{})
go func() {
for i := 0; i < b.N; i++ {
v := <-c
if v.Err != nil {
b.Fatal(v.Err)
}
i, r := v.Args[0].(int64), v.Body[0].(int64)
if 2*i != r {
b.Errorf("got %d, wanted %d", r, 2*i)
}
}
close(done)
}()
b.StartTimer()
for i := 0; i < b.N; i++ {
obj.Go("org.guelfey.DBus.Test.Double", 0, c, int64(i))
}
<-done
}

View File

@ -1,258 +0,0 @@
package dbus
import (
"errors"
"reflect"
"strings"
)
var (
byteType = reflect.TypeOf(byte(0))
boolType = reflect.TypeOf(false)
uint8Type = reflect.TypeOf(uint8(0))
int16Type = reflect.TypeOf(int16(0))
uint16Type = reflect.TypeOf(uint16(0))
int32Type = reflect.TypeOf(int32(0))
uint32Type = reflect.TypeOf(uint32(0))
int64Type = reflect.TypeOf(int64(0))
uint64Type = reflect.TypeOf(uint64(0))
float64Type = reflect.TypeOf(float64(0))
stringType = reflect.TypeOf("")
signatureType = reflect.TypeOf(Signature{""})
objectPathType = reflect.TypeOf(ObjectPath(""))
variantType = reflect.TypeOf(Variant{Signature{""}, nil})
interfacesType = reflect.TypeOf([]interface{}{})
unixFDType = reflect.TypeOf(UnixFD(0))
unixFDIndexType = reflect.TypeOf(UnixFDIndex(0))
)
// An InvalidTypeError signals that a value which cannot be represented in the
// D-Bus wire format was passed to a function.
type InvalidTypeError struct {
Type reflect.Type
}
func (e InvalidTypeError) Error() string {
return "dbus: invalid type " + e.Type.String()
}
// Store copies the values contained in src to dest, which must be a slice of
// pointers. It converts slices of interfaces from src to corresponding structs
// in dest. An error is returned if the lengths of src and dest or the types of
// their elements don't match.
func Store(src []interface{}, dest ...interface{}) error {
if len(src) != len(dest) {
return errors.New("dbus.Store: length mismatch")
}
for i := range src {
if err := store(src[i], dest[i]); err != nil {
return err
}
}
return nil
}
func store(src, dest interface{}) error {
if reflect.TypeOf(dest).Elem() == reflect.TypeOf(src) {
reflect.ValueOf(dest).Elem().Set(reflect.ValueOf(src))
return nil
} else if hasStruct(dest) {
rv := reflect.ValueOf(dest).Elem()
switch rv.Kind() {
case reflect.Struct:
vs, ok := src.([]interface{})
if !ok {
return errors.New("dbus.Store: type mismatch")
}
t := rv.Type()
ndest := make([]interface{}, 0, rv.NumField())
for i := 0; i < rv.NumField(); i++ {
field := t.Field(i)
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
ndest = append(ndest, rv.Field(i).Addr().Interface())
}
}
if len(vs) != len(ndest) {
return errors.New("dbus.Store: type mismatch")
}
err := Store(vs, ndest...)
if err != nil {
return errors.New("dbus.Store: type mismatch")
}
case reflect.Slice:
sv := reflect.ValueOf(src)
if sv.Kind() != reflect.Slice {
return errors.New("dbus.Store: type mismatch")
}
rv.Set(reflect.MakeSlice(rv.Type(), sv.Len(), sv.Len()))
for i := 0; i < sv.Len(); i++ {
if err := store(sv.Index(i).Interface(), rv.Index(i).Addr().Interface()); err != nil {
return err
}
}
case reflect.Map:
sv := reflect.ValueOf(src)
if sv.Kind() != reflect.Map {
return errors.New("dbus.Store: type mismatch")
}
keys := sv.MapKeys()
rv.Set(reflect.MakeMap(sv.Type()))
for _, key := range keys {
v := reflect.New(sv.Type().Elem())
if err := store(v, sv.MapIndex(key).Interface()); err != nil {
return err
}
rv.SetMapIndex(key, v.Elem())
}
default:
return errors.New("dbus.Store: type mismatch")
}
return nil
} else {
return errors.New("dbus.Store: type mismatch")
}
}
func hasStruct(v interface{}) bool {
t := reflect.TypeOf(v)
for {
switch t.Kind() {
case reflect.Struct:
return true
case reflect.Slice, reflect.Ptr, reflect.Map:
t = t.Elem()
default:
return false
}
}
}
// An ObjectPath is an object path as defined by the D-Bus spec.
type ObjectPath string
// IsValid returns whether the object path is valid.
func (o ObjectPath) IsValid() bool {
s := string(o)
if len(s) == 0 {
return false
}
if s[0] != '/' {
return false
}
if s[len(s)-1] == '/' && len(s) != 1 {
return false
}
// probably not used, but technically possible
if s == "/" {
return true
}
split := strings.Split(s[1:], "/")
for _, v := range split {
if len(v) == 0 {
return false
}
for _, c := range v {
if !isMemberChar(c) {
return false
}
}
}
return true
}
// A UnixFD is a Unix file descriptor sent over the wire. See the package-level
// documentation for more information about Unix file descriptor passsing.
type UnixFD int32
// A UnixFDIndex is the representation of a Unix file descriptor in a message.
type UnixFDIndex uint32
// alignment returns the alignment of values of type t.
func alignment(t reflect.Type) int {
switch t {
case variantType:
return 1
case objectPathType:
return 4
case signatureType:
return 1
case interfacesType: // sometimes used for structs
return 8
}
switch t.Kind() {
case reflect.Uint8:
return 1
case reflect.Uint16, reflect.Int16:
return 2
case reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice, reflect.Map:
return 4
case reflect.Uint64, reflect.Int64, reflect.Float64, reflect.Struct:
return 8
case reflect.Ptr:
return alignment(t.Elem())
}
return 1
}
// isKeyType returns whether t is a valid type for a D-Bus dict.
func isKeyType(t reflect.Type) bool {
switch t.Kind() {
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64,
reflect.String:
return true
}
return false
}
// isValidInterface returns whether s is a valid name for an interface.
func isValidInterface(s string) bool {
if len(s) == 0 || len(s) > 255 || s[0] == '.' {
return false
}
elem := strings.Split(s, ".")
if len(elem) < 2 {
return false
}
for _, v := range elem {
if len(v) == 0 {
return false
}
if v[0] >= '0' && v[0] <= '9' {
return false
}
for _, c := range v {
if !isMemberChar(c) {
return false
}
}
}
return true
}
// isValidMember returns whether s is a valid name for a member.
func isValidMember(s string) bool {
if len(s) == 0 || len(s) > 255 {
return false
}
i := strings.Index(s, ".")
if i != -1 {
return false
}
if s[0] >= '0' && s[0] <= '9' {
return false
}
for _, c := range s {
if !isMemberChar(c) {
return false
}
}
return true
}
func isMemberChar(c rune) bool {
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') || c == '_'
}

View File

@ -1,228 +0,0 @@
package dbus
import (
"encoding/binary"
"io"
"reflect"
)
type decoder struct {
in io.Reader
order binary.ByteOrder
pos int
}
// newDecoder returns a new decoder that reads values from in. The input is
// expected to be in the given byte order.
func newDecoder(in io.Reader, order binary.ByteOrder) *decoder {
dec := new(decoder)
dec.in = in
dec.order = order
return dec
}
// align aligns the input to the given boundary and panics on error.
func (dec *decoder) align(n int) {
if dec.pos%n != 0 {
newpos := (dec.pos + n - 1) & ^(n - 1)
empty := make([]byte, newpos-dec.pos)
if _, err := io.ReadFull(dec.in, empty); err != nil {
panic(err)
}
dec.pos = newpos
}
}
// Calls binary.Read(dec.in, dec.order, v) and panics on read errors.
func (dec *decoder) binread(v interface{}) {
if err := binary.Read(dec.in, dec.order, v); err != nil {
panic(err)
}
}
func (dec *decoder) Decode(sig Signature) (vs []interface{}, err error) {
defer func() {
var ok bool
v := recover()
if err, ok = v.(error); ok {
if err == io.EOF || err == io.ErrUnexpectedEOF {
err = FormatError("unexpected EOF")
}
}
}()
vs = make([]interface{}, 0)
s := sig.str
for s != "" {
err, rem := validSingle(s, 0)
if err != nil {
return nil, err
}
v := dec.decode(s[:len(s)-len(rem)], 0)
vs = append(vs, v)
s = rem
}
return vs, nil
}
func (dec *decoder) decode(s string, depth int) interface{} {
dec.align(alignment(typeFor(s)))
switch s[0] {
case 'y':
var b [1]byte
if _, err := dec.in.Read(b[:]); err != nil {
panic(err)
}
dec.pos++
return b[0]
case 'b':
i := dec.decode("u", depth).(uint32)
switch {
case i == 0:
return false
case i == 1:
return true
default:
panic(FormatError("invalid value for boolean"))
}
case 'n':
var i int16
dec.binread(&i)
dec.pos += 2
return i
case 'i':
var i int32
dec.binread(&i)
dec.pos += 4
return i
case 'x':
var i int64
dec.binread(&i)
dec.pos += 8
return i
case 'q':
var i uint16
dec.binread(&i)
dec.pos += 2
return i
case 'u':
var i uint32
dec.binread(&i)
dec.pos += 4
return i
case 't':
var i uint64
dec.binread(&i)
dec.pos += 8
return i
case 'd':
var f float64
dec.binread(&f)
dec.pos += 8
return f
case 's':
length := dec.decode("u", depth).(uint32)
b := make([]byte, int(length)+1)
if _, err := io.ReadFull(dec.in, b); err != nil {
panic(err)
}
dec.pos += int(length) + 1
return string(b[:len(b)-1])
case 'o':
return ObjectPath(dec.decode("s", depth).(string))
case 'g':
length := dec.decode("y", depth).(byte)
b := make([]byte, int(length)+1)
if _, err := io.ReadFull(dec.in, b); err != nil {
panic(err)
}
dec.pos += int(length) + 1
sig, err := ParseSignature(string(b[:len(b)-1]))
if err != nil {
panic(err)
}
return sig
case 'v':
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
var variant Variant
sig := dec.decode("g", depth).(Signature)
if len(sig.str) == 0 {
panic(FormatError("variant signature is empty"))
}
err, rem := validSingle(sig.str, 0)
if err != nil {
panic(err)
}
if rem != "" {
panic(FormatError("variant signature has multiple types"))
}
variant.sig = sig
variant.value = dec.decode(sig.str, depth+1)
return variant
case 'h':
return UnixFDIndex(dec.decode("u", depth).(uint32))
case 'a':
if len(s) > 1 && s[1] == '{' {
ksig := s[2:3]
vsig := s[3 : len(s)-1]
v := reflect.MakeMap(reflect.MapOf(typeFor(ksig), typeFor(vsig)))
if depth >= 63 {
panic(FormatError("input exceeds container depth limit"))
}
length := dec.decode("u", depth).(uint32)
// Even for empty maps, the correct padding must be included
dec.align(8)
spos := dec.pos
for dec.pos < spos+int(length) {
dec.align(8)
if !isKeyType(v.Type().Key()) {
panic(InvalidTypeError{v.Type()})
}
kv := dec.decode(ksig, depth+2)
vv := dec.decode(vsig, depth+2)
v.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv))
}
return v.Interface()
}
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
length := dec.decode("u", depth).(uint32)
v := reflect.MakeSlice(reflect.SliceOf(typeFor(s[1:])), 0, int(length))
// Even for empty arrays, the correct padding must be included
dec.align(alignment(typeFor(s[1:])))
spos := dec.pos
for dec.pos < spos+int(length) {
ev := dec.decode(s[1:], depth+1)
v = reflect.Append(v, reflect.ValueOf(ev))
}
return v.Interface()
case '(':
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
dec.align(8)
v := make([]interface{}, 0)
s = s[1 : len(s)-1]
for s != "" {
err, rem := validSingle(s, 0)
if err != nil {
panic(err)
}
ev := dec.decode(s[:len(s)-len(rem)], depth+1)
v = append(v, ev)
s = rem
}
return v
default:
panic(SignatureError{Sig: s})
}
}
// A FormatError is an error in the wire format.
type FormatError string
func (e FormatError) Error() string {
return "dbus: wire format error: " + string(e)
}

View File

@ -1,63 +0,0 @@
/*
Package dbus implements bindings to the D-Bus message bus system.
To use the message bus API, you first need to connect to a bus (usually the
session or system bus). The acquired connection then can be used to call methods
on remote objects and emit or receive signals. Using the Export method, you can
arrange D-Bus methods calls to be directly translated to method calls on a Go
value.
Conversion Rules
For outgoing messages, Go types are automatically converted to the
corresponding D-Bus types. The following types are directly encoded as their
respective D-Bus equivalents:
Go type | D-Bus type
------------+-----------
byte | BYTE
bool | BOOLEAN
int16 | INT16
uint16 | UINT16
int32 | INT32
uint32 | UINT32
int64 | INT64
uint64 | UINT64
float64 | DOUBLE
string | STRING
ObjectPath | OBJECT_PATH
Signature | SIGNATURE
Variant | VARIANT
UnixFDIndex | UNIX_FD
Slices and arrays encode as ARRAYs of their element type.
Maps encode as DICTs, provided that their key type can be used as a key for
a DICT.
Structs other than Variant and Signature encode as a STRUCT containing their
exported fields. Fields whose tags contain `dbus:"-"` and unexported fields will
be skipped.
Pointers encode as the value they're pointed to.
Trying to encode any other type or a slice, map or struct containing an
unsupported type will result in an InvalidTypeError.
For incoming messages, the inverse of these rules are used, with the exception
of STRUCTs. Incoming STRUCTS are represented as a slice of empty interfaces
containing the struct fields in the correct order. The Store function can be
used to convert such values to Go structs.
Unix FD passing
Handling Unix file descriptors deserves special mention. To use them, you should
first check that they are supported on a connection by calling SupportsUnixFDs.
If it returns true, all method of Connection will translate messages containing
UnixFD's to messages that are accompanied by the given file descriptors with the
UnixFD values being substituted by the correct indices. Similarily, the indices
of incoming messages are automatically resolved. It shouldn't be necessary to use
UnixFDIndex.
*/
package dbus

View File

@ -1,179 +0,0 @@
package dbus
import (
"bytes"
"encoding/binary"
"io"
"reflect"
)
// An encoder encodes values to the D-Bus wire format.
type encoder struct {
out io.Writer
order binary.ByteOrder
pos int
}
// NewEncoder returns a new encoder that writes to out in the given byte order.
func newEncoder(out io.Writer, order binary.ByteOrder) *encoder {
enc := new(encoder)
enc.out = out
enc.order = order
return enc
}
// Aligns the next output to be on a multiple of n. Panics on write errors.
func (enc *encoder) align(n int) {
if enc.pos%n != 0 {
newpos := (enc.pos + n - 1) & ^(n - 1)
empty := make([]byte, newpos-enc.pos)
if _, err := enc.out.Write(empty); err != nil {
panic(err)
}
enc.pos = newpos
}
}
// Calls binary.Write(enc.out, enc.order, v) and panics on write errors.
func (enc *encoder) binwrite(v interface{}) {
if err := binary.Write(enc.out, enc.order, v); err != nil {
panic(err)
}
}
// Encode encodes the given values to the underyling reader. All written values
// are aligned properly as required by the D-Bus spec.
func (enc *encoder) Encode(vs ...interface{}) (err error) {
defer func() {
err, _ = recover().(error)
}()
for _, v := range vs {
enc.encode(reflect.ValueOf(v), 0)
}
return nil
}
// encode encodes the given value to the writer and panics on error. depth holds
// the depth of the container nesting.
func (enc *encoder) encode(v reflect.Value, depth int) {
enc.align(alignment(v.Type()))
switch v.Kind() {
case reflect.Uint8:
var b [1]byte
b[0] = byte(v.Uint())
if _, err := enc.out.Write(b[:]); err != nil {
panic(err)
}
enc.pos++
case reflect.Bool:
if v.Bool() {
enc.encode(reflect.ValueOf(uint32(1)), depth)
} else {
enc.encode(reflect.ValueOf(uint32(0)), depth)
}
case reflect.Int16:
enc.binwrite(int16(v.Int()))
enc.pos += 2
case reflect.Uint16:
enc.binwrite(uint16(v.Uint()))
enc.pos += 2
case reflect.Int32:
enc.binwrite(int32(v.Int()))
enc.pos += 4
case reflect.Uint32:
enc.binwrite(uint32(v.Uint()))
enc.pos += 4
case reflect.Int64:
enc.binwrite(v.Int())
enc.pos += 8
case reflect.Uint64:
enc.binwrite(v.Uint())
enc.pos += 8
case reflect.Float64:
enc.binwrite(v.Float())
enc.pos += 8
case reflect.String:
enc.encode(reflect.ValueOf(uint32(len(v.String()))), depth)
b := make([]byte, v.Len()+1)
copy(b, v.String())
b[len(b)-1] = 0
n, err := enc.out.Write(b)
if err != nil {
panic(err)
}
enc.pos += n
case reflect.Ptr:
enc.encode(v.Elem(), depth)
case reflect.Slice, reflect.Array:
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
var buf bytes.Buffer
bufenc := newEncoder(&buf, enc.order)
for i := 0; i < v.Len(); i++ {
bufenc.encode(v.Index(i), depth+1)
}
enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
length := buf.Len()
enc.align(alignment(v.Type().Elem()))
if _, err := buf.WriteTo(enc.out); err != nil {
panic(err)
}
enc.pos += length
case reflect.Struct:
if depth >= 64 && v.Type() != signatureType {
panic(FormatError("input exceeds container depth limit"))
}
switch t := v.Type(); t {
case signatureType:
str := v.Field(0)
enc.encode(reflect.ValueOf(byte(str.Len())), depth+1)
b := make([]byte, str.Len()+1)
copy(b, str.String())
b[len(b)-1] = 0
n, err := enc.out.Write(b)
if err != nil {
panic(err)
}
enc.pos += n
case variantType:
variant := v.Interface().(Variant)
enc.encode(reflect.ValueOf(variant.sig), depth+1)
enc.encode(reflect.ValueOf(variant.value), depth+1)
default:
for i := 0; i < v.Type().NumField(); i++ {
field := t.Field(i)
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
enc.encode(v.Field(i), depth+1)
}
}
}
case reflect.Map:
// Maps are arrays of structures, so they actually increase the depth by
// 2.
if depth >= 63 {
panic(FormatError("input exceeds container depth limit"))
}
if !isKeyType(v.Type().Key()) {
panic(InvalidTypeError{v.Type()})
}
keys := v.MapKeys()
var buf bytes.Buffer
bufenc := newEncoder(&buf, enc.order)
for _, k := range keys {
bufenc.align(8)
bufenc.encode(k, depth+2)
bufenc.encode(v.MapIndex(k), depth+2)
}
enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
length := buf.Len()
enc.align(8)
if _, err := buf.WriteTo(enc.out); err != nil {
panic(err)
}
enc.pos += length
default:
panic(InvalidTypeError{v.Type()})
}
}

View File

@ -1,50 +0,0 @@
package dbus
import "fmt"
func ExampleConn_Emit() {
conn, err := SessionBus()
if err != nil {
panic(err)
}
conn.Emit("/foo/bar", "foo.bar.Baz", uint32(0xDAEDBEEF))
}
func ExampleObject_Call() {
var list []string
conn, err := SessionBus()
if err != nil {
panic(err)
}
err = conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&list)
if err != nil {
panic(err)
}
for _, v := range list {
fmt.Println(v)
}
}
func ExampleObject_Go() {
conn, err := SessionBus()
if err != nil {
panic(err)
}
ch := make(chan *Call, 10)
conn.BusObject().Go("org.freedesktop.DBus.ListActivatableNames", 0, ch)
select {
case call := <-ch:
if call.Err != nil {
panic(err)
}
list := call.Body[0].([]string)
for _, v := range list {
fmt.Println(v)
}
// put some other cases here
}
}

View File

@ -1,300 +0,0 @@
package dbus
import (
"errors"
"reflect"
"strings"
"unicode"
)
var (
errmsgInvalidArg = Error{
"org.freedesktop.DBus.Error.InvalidArgs",
[]interface{}{"Invalid type / number of args"},
}
errmsgNoObject = Error{
"org.freedesktop.DBus.Error.NoSuchObject",
[]interface{}{"No such object"},
}
errmsgUnknownMethod = Error{
"org.freedesktop.DBus.Error.UnknownMethod",
[]interface{}{"Unknown / invalid method"},
}
)
// Sender is a type which can be used in exported methods to receive the message
// sender.
type Sender string
func exportedMethod(v interface{}, name string) reflect.Value {
if v == nil {
return reflect.Value{}
}
m := reflect.ValueOf(v).MethodByName(name)
if !m.IsValid() {
return reflect.Value{}
}
t := m.Type()
if t.NumOut() == 0 ||
t.Out(t.NumOut()-1) != reflect.TypeOf(&errmsgInvalidArg) {
return reflect.Value{}
}
return m
}
// handleCall handles the given method call (i.e. looks if it's one of the
// pre-implemented ones and searches for a corresponding handler if not).
func (conn *Conn) handleCall(msg *Message) {
name := msg.Headers[FieldMember].value.(string)
path := msg.Headers[FieldPath].value.(ObjectPath)
ifaceName, hasIface := msg.Headers[FieldInterface].value.(string)
sender := msg.Headers[FieldSender].value.(string)
serial := msg.serial
if ifaceName == "org.freedesktop.DBus.Peer" {
switch name {
case "Ping":
conn.sendReply(sender, serial)
case "GetMachineId":
conn.sendReply(sender, serial, conn.uuid)
default:
conn.sendError(errmsgUnknownMethod, sender, serial)
}
return
}
if len(name) == 0 || unicode.IsLower([]rune(name)[0]) {
conn.sendError(errmsgUnknownMethod, sender, serial)
}
var m reflect.Value
if hasIface {
conn.handlersLck.RLock()
obj, ok := conn.handlers[path]
if !ok {
conn.sendError(errmsgNoObject, sender, serial)
conn.handlersLck.RUnlock()
return
}
iface := obj[ifaceName]
conn.handlersLck.RUnlock()
m = exportedMethod(iface, name)
} else {
conn.handlersLck.RLock()
if _, ok := conn.handlers[path]; !ok {
conn.sendError(errmsgNoObject, sender, serial)
conn.handlersLck.RUnlock()
return
}
for _, v := range conn.handlers[path] {
m = exportedMethod(v, name)
if m.IsValid() {
break
}
}
conn.handlersLck.RUnlock()
}
if !m.IsValid() {
conn.sendError(errmsgUnknownMethod, sender, serial)
return
}
t := m.Type()
vs := msg.Body
pointers := make([]interface{}, t.NumIn())
decode := make([]interface{}, 0, len(vs))
for i := 0; i < t.NumIn(); i++ {
tp := t.In(i)
val := reflect.New(tp)
pointers[i] = val.Interface()
if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
val.Elem().SetString(sender)
} else {
decode = append(decode, pointers[i])
}
}
if len(decode) != len(vs) {
conn.sendError(errmsgInvalidArg, sender, serial)
return
}
if err := Store(vs, decode...); err != nil {
conn.sendError(errmsgInvalidArg, sender, serial)
return
}
params := make([]reflect.Value, len(pointers))
for i := 0; i < len(pointers); i++ {
params[i] = reflect.ValueOf(pointers[i]).Elem()
}
ret := m.Call(params)
if em := ret[t.NumOut()-1].Interface().(*Error); em != nil {
conn.sendError(*em, sender, serial)
return
}
if msg.Flags&FlagNoReplyExpected == 0 {
reply := new(Message)
reply.Type = TypeMethodReply
reply.serial = conn.getSerial()
reply.Headers = make(map[HeaderField]Variant)
reply.Headers[FieldDestination] = msg.Headers[FieldSender]
reply.Headers[FieldReplySerial] = MakeVariant(msg.serial)
reply.Body = make([]interface{}, len(ret)-1)
for i := 0; i < len(ret)-1; i++ {
reply.Body[i] = ret[i].Interface()
}
if len(ret) != 1 {
reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...))
}
conn.outLck.RLock()
if !conn.closed {
conn.out <- reply
}
conn.outLck.RUnlock()
}
}
// Emit emits the given signal on the message bus. The name parameter must be
// formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost".
func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) error {
if !path.IsValid() {
return errors.New("dbus: invalid object path")
}
i := strings.LastIndex(name, ".")
if i == -1 {
return errors.New("dbus: invalid method name")
}
iface := name[:i]
member := name[i+1:]
if !isValidMember(member) {
return errors.New("dbus: invalid method name")
}
if !isValidInterface(iface) {
return errors.New("dbus: invalid interface name")
}
msg := new(Message)
msg.Type = TypeSignal
msg.serial = conn.getSerial()
msg.Headers = make(map[HeaderField]Variant)
msg.Headers[FieldInterface] = MakeVariant(iface)
msg.Headers[FieldMember] = MakeVariant(member)
msg.Headers[FieldPath] = MakeVariant(path)
msg.Body = values
if len(values) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
}
conn.outLck.RLock()
defer conn.outLck.RUnlock()
if conn.closed {
return ErrClosed
}
conn.out <- msg
return nil
}
// Export registers the given value to be exported as an object on the
// message bus.
//
// If a method call on the given path and interface is received, an exported
// method with the same name is called with v as the receiver if the
// parameters match and the last return value is of type *Error. If this
// *Error is not nil, it is sent back to the caller as an error.
// Otherwise, a method reply is sent with the other return values as its body.
//
// Any parameters with the special type Sender are set to the sender of the
// dbus message when the method is called. Parameters of this type do not
// contribute to the dbus signature of the method (i.e. the method is exposed
// as if the parameters of type Sender were not there).
//
// Every method call is executed in a new goroutine, so the method may be called
// in multiple goroutines at once.
//
// Method calls on the interface org.freedesktop.DBus.Peer will be automatically
// handled for every object.
//
// Passing nil as the first parameter will cause conn to cease handling calls on
// the given combination of path and interface.
//
// Export returns an error if path is not a valid path name.
func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
if !path.IsValid() {
return errors.New("dbus: invalid path name")
}
conn.handlersLck.Lock()
if v == nil {
if _, ok := conn.handlers[path]; ok {
delete(conn.handlers[path], iface)
if len(conn.handlers[path]) == 0 {
delete(conn.handlers, path)
}
}
return nil
}
if _, ok := conn.handlers[path]; !ok {
conn.handlers[path] = make(map[string]interface{})
}
conn.handlers[path][iface] = v
conn.handlersLck.Unlock()
return nil
}
// ReleaseName calls org.freedesktop.DBus.ReleaseName. You should use only this
// method to release a name (see below).
func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) {
var r uint32
err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r)
if err != nil {
return 0, err
}
if r == uint32(ReleaseNameReplyReleased) {
conn.namesLck.Lock()
for i, v := range conn.names {
if v == name {
copy(conn.names[i:], conn.names[i+1:])
conn.names = conn.names[:len(conn.names)-1]
}
}
conn.namesLck.Unlock()
}
return ReleaseNameReply(r), nil
}
// RequestName calls org.freedesktop.DBus.RequestName. You should use only this
// method to request a name because package dbus needs to keep track of all
// names that the connection has.
func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) {
var r uint32
err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r)
if err != nil {
return 0, err
}
if r == uint32(RequestNameReplyPrimaryOwner) {
conn.namesLck.Lock()
conn.names = append(conn.names, name)
conn.namesLck.Unlock()
}
return RequestNameReply(r), nil
}
// ReleaseNameReply is the reply to a ReleaseName call.
type ReleaseNameReply uint32
const (
ReleaseNameReplyReleased ReleaseNameReply = 1 + iota
ReleaseNameReplyNonExistent
ReleaseNameReplyNotOwner
)
// RequestNameFlags represents the possible flags for a RequestName call.
type RequestNameFlags uint32
const (
NameFlagAllowReplacement RequestNameFlags = 1 << iota
NameFlagReplaceExisting
NameFlagDoNotQueue
)
// RequestNameReply is the reply to a RequestName call.
type RequestNameReply uint32
const (
RequestNameReplyPrimaryOwner RequestNameReply = 1 + iota
RequestNameReplyInQueue
RequestNameReplyExists
RequestNameReplyAlreadyOwner
)

View File

@ -1,27 +0,0 @@
package introspect
import (
"encoding/xml"
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
"strings"
)
// Call calls org.freedesktop.Introspectable.Introspect on a remote object
// and returns the introspection data.
func Call(o *dbus.Object) (*Node, error) {
var xmldata string
var node Node
err := o.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&xmldata)
if err != nil {
return nil, err
}
err = xml.NewDecoder(strings.NewReader(xmldata)).Decode(&node)
if err != nil {
return nil, err
}
if node.Name == "" {
node.Name = string(o.Path())
}
return &node, nil
}

View File

@ -1,80 +0,0 @@
// Package introspect provides some utilities for dealing with the DBus
// introspection format.
package introspect
import "encoding/xml"
// The introspection data for the org.freedesktop.DBus.Introspectable interface.
var IntrospectData = Interface{
Name: "org.freedesktop.DBus.Introspectable",
Methods: []Method{
{
Name: "Introspect",
Args: []Arg{
{"out", "s", "out"},
},
},
},
}
// The introspection data for the org.freedesktop.DBus.Introspectable interface,
// as a string.
const IntrospectDataString = `
<interface name="org.freedesktop.DBus.Introspectable">
<method name="Introspect">
<arg name="out" direction="out" type="s"/>
</method>
</interface>
`
// Node is the root element of an introspection.
type Node struct {
XMLName xml.Name `xml:"node"`
Name string `xml:"name,attr,omitempty"`
Interfaces []Interface `xml:"interface"`
Children []Node `xml:"node,omitempty"`
}
// Interface describes a DBus interface that is available on the message bus.
type Interface struct {
Name string `xml:"name,attr"`
Methods []Method `xml:"method"`
Signals []Signal `xml:"signal"`
Properties []Property `xml:"property"`
Annotations []Annotation `xml:"annotation"`
}
// Method describes a Method on an Interface as retured by an introspection.
type Method struct {
Name string `xml:"name,attr"`
Args []Arg `xml:"arg"`
Annotations []Annotation `xml:"annotation"`
}
// Signal describes a Signal emitted on an Interface.
type Signal struct {
Name string `xml:"name,attr"`
Args []Arg `xml:"arg"`
Annotations []Annotation `xml:"annotation"`
}
// Property describes a property of an Interface.
type Property struct {
Name string `xml:"name,attr"`
Type string `xml:"type,attr"`
Access string `xml:"access,attr"`
Annotations []Annotation `xml:"annotation"`
}
// Arg represents an argument of a method or a signal.
type Arg struct {
Name string `xml:"name,attr,omitempty"`
Type string `xml:"type,attr"`
Direction string `xml:"direction,attr,omitempty"`
}
// Annotation is an annotation in the introspection format.
type Annotation struct {
Name string `xml:"name,attr"`
Value string `xml:"value,attr"`
}

View File

@ -1,74 +0,0 @@
package introspect
import (
"encoding/xml"
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
"reflect"
)
// Introspectable implements org.freedesktop.Introspectable.
//
// You can create it by converting the XML-formatted introspection data from a
// string to an Introspectable or call NewIntrospectable with a Node. Then,
// export it as org.freedesktop.Introspectable on you object.
type Introspectable string
// NewIntrospectable returns an Introspectable that returns the introspection
// data that corresponds to the given Node. If n.Interfaces doesn't contain the
// data for org.freedesktop.DBus.Introspectable, it is added automatically.
func NewIntrospectable(n *Node) Introspectable {
found := false
for _, v := range n.Interfaces {
if v.Name == "org.freedesktop.DBus.Introspectable" {
found = true
break
}
}
if !found {
n.Interfaces = append(n.Interfaces, IntrospectData)
}
b, err := xml.Marshal(n)
if err != nil {
panic(err)
}
return Introspectable(b)
}
// Introspect implements org.freedesktop.Introspectable.Introspect.
func (i Introspectable) Introspect() (string, *dbus.Error) {
return string(i), nil
}
// Methods returns the description of the methods of v. This can be used to
// create a Node which can be passed to NewIntrospectable.
func Methods(v interface{}) []Method {
t := reflect.TypeOf(v)
ms := make([]Method, 0, t.NumMethod())
for i := 0; i < t.NumMethod(); i++ {
if t.Method(i).PkgPath != "" {
continue
}
mt := t.Method(i).Type
if mt.NumOut() == 0 ||
mt.Out(mt.NumOut()-1) != reflect.TypeOf(&dbus.Error{"", nil}) {
continue
}
var m Method
m.Name = t.Method(i).Name
m.Args = make([]Arg, 0, mt.NumIn()+mt.NumOut()-2)
for j := 1; j < mt.NumIn(); j++ {
if mt.In(j) != reflect.TypeOf((*dbus.Sender)(nil)).Elem() {
arg := Arg{"", dbus.SignatureOfType(mt.In(j)).String(), "in"}
m.Args = append(m.Args, arg)
}
}
for j := 0; j < mt.NumOut()-1; j++ {
arg := Arg{"", dbus.SignatureOfType(mt.Out(j)).String(), "out"}
m.Args = append(m.Args, arg)
}
m.Annotations = make([]Annotation, 0)
ms = append(ms, m)
}
return ms
}

View File

@ -1,346 +0,0 @@
package dbus
import (
"bytes"
"encoding/binary"
"errors"
"io"
"reflect"
"strconv"
)
const protoVersion byte = 1
// Flags represents the possible flags of a D-Bus message.
type Flags byte
const (
// FlagNoReplyExpected signals that the message is not expected to generate
// a reply. If this flag is set on outgoing messages, any possible reply
// will be discarded.
FlagNoReplyExpected Flags = 1 << iota
// FlagNoAutoStart signals that the message bus should not automatically
// start an application when handling this message.
FlagNoAutoStart
)
// Type represents the possible types of a D-Bus message.
type Type byte
const (
TypeMethodCall Type = 1 + iota
TypeMethodReply
TypeError
TypeSignal
typeMax
)
func (t Type) String() string {
switch t {
case TypeMethodCall:
return "method call"
case TypeMethodReply:
return "reply"
case TypeError:
return "error"
case TypeSignal:
return "signal"
}
return "invalid"
}
// HeaderField represents the possible byte codes for the headers
// of a D-Bus message.
type HeaderField byte
const (
FieldPath HeaderField = 1 + iota
FieldInterface
FieldMember
FieldErrorName
FieldReplySerial
FieldDestination
FieldSender
FieldSignature
FieldUnixFDs
fieldMax
)
// An InvalidMessageError describes the reason why a D-Bus message is regarded as
// invalid.
type InvalidMessageError string
func (e InvalidMessageError) Error() string {
return "dbus: invalid message: " + string(e)
}
// fieldType are the types of the various header fields.
var fieldTypes = [fieldMax]reflect.Type{
FieldPath: objectPathType,
FieldInterface: stringType,
FieldMember: stringType,
FieldErrorName: stringType,
FieldReplySerial: uint32Type,
FieldDestination: stringType,
FieldSender: stringType,
FieldSignature: signatureType,
FieldUnixFDs: uint32Type,
}
// requiredFields lists the header fields that are required by the different
// message types.
var requiredFields = [typeMax][]HeaderField{
TypeMethodCall: {FieldPath, FieldMember},
TypeMethodReply: {FieldReplySerial},
TypeError: {FieldErrorName, FieldReplySerial},
TypeSignal: {FieldPath, FieldInterface, FieldMember},
}
// Message represents a single D-Bus message.
type Message struct {
Type
Flags
Headers map[HeaderField]Variant
Body []interface{}
serial uint32
}
type header struct {
Field byte
Variant
}
// DecodeMessage tries to decode a single message in the D-Bus wire format
// from the given reader. The byte order is figured out from the first byte.
// The possibly returned error can be an error of the underlying reader, an
// InvalidMessageError or a FormatError.
func DecodeMessage(rd io.Reader) (msg *Message, err error) {
var order binary.ByteOrder
var hlength, length uint32
var typ, flags, proto byte
var headers []header
b := make([]byte, 1)
_, err = rd.Read(b)
if err != nil {
return
}
switch b[0] {
case 'l':
order = binary.LittleEndian
case 'B':
order = binary.BigEndian
default:
return nil, InvalidMessageError("invalid byte order")
}
dec := newDecoder(rd, order)
dec.pos = 1
msg = new(Message)
vs, err := dec.Decode(Signature{"yyyuu"})
if err != nil {
return nil, err
}
if err = Store(vs, &typ, &flags, &proto, &length, &msg.serial); err != nil {
return nil, err
}
msg.Type = Type(typ)
msg.Flags = Flags(flags)
// get the header length separately because we need it later
b = make([]byte, 4)
_, err = io.ReadFull(rd, b)
if err != nil {
return nil, err
}
binary.Read(bytes.NewBuffer(b), order, &hlength)
if hlength+length+16 > 1<<27 {
return nil, InvalidMessageError("message is too long")
}
dec = newDecoder(io.MultiReader(bytes.NewBuffer(b), rd), order)
dec.pos = 12
vs, err = dec.Decode(Signature{"a(yv)"})
if err != nil {
return nil, err
}
if err = Store(vs, &headers); err != nil {
return nil, err
}
msg.Headers = make(map[HeaderField]Variant)
for _, v := range headers {
msg.Headers[HeaderField(v.Field)] = v.Variant
}
dec.align(8)
body := make([]byte, int(length))
if length != 0 {
_, err := io.ReadFull(rd, body)
if err != nil {
return nil, err
}
}
if err = msg.IsValid(); err != nil {
return nil, err
}
sig, _ := msg.Headers[FieldSignature].value.(Signature)
if sig.str != "" {
buf := bytes.NewBuffer(body)
dec = newDecoder(buf, order)
vs, err := dec.Decode(sig)
if err != nil {
return nil, err
}
msg.Body = vs
}
return
}
// EncodeTo encodes and sends a message to the given writer. The byte order must
// be either binary.LittleEndian or binary.BigEndian. If the message is not
// valid or an error occurs when writing, an error is returned.
func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) error {
if err := msg.IsValid(); err != nil {
return err
}
var vs [7]interface{}
switch order {
case binary.LittleEndian:
vs[0] = byte('l')
case binary.BigEndian:
vs[0] = byte('B')
default:
return errors.New("dbus: invalid byte order")
}
body := new(bytes.Buffer)
enc := newEncoder(body, order)
if len(msg.Body) != 0 {
enc.Encode(msg.Body...)
}
vs[1] = msg.Type
vs[2] = msg.Flags
vs[3] = protoVersion
vs[4] = uint32(len(body.Bytes()))
vs[5] = msg.serial
headers := make([]header, 0, len(msg.Headers))
for k, v := range msg.Headers {
headers = append(headers, header{byte(k), v})
}
vs[6] = headers
var buf bytes.Buffer
enc = newEncoder(&buf, order)
enc.Encode(vs[:]...)
enc.align(8)
body.WriteTo(&buf)
if buf.Len() > 1<<27 {
return InvalidMessageError("message is too long")
}
if _, err := buf.WriteTo(out); err != nil {
return err
}
return nil
}
// IsValid checks whether msg is a valid message and returns an
// InvalidMessageError if it is not.
func (msg *Message) IsValid() error {
if msg.Flags & ^(FlagNoAutoStart|FlagNoReplyExpected) != 0 {
return InvalidMessageError("invalid flags")
}
if msg.Type == 0 || msg.Type >= typeMax {
return InvalidMessageError("invalid message type")
}
for k, v := range msg.Headers {
if k == 0 || k >= fieldMax {
return InvalidMessageError("invalid header")
}
if reflect.TypeOf(v.value) != fieldTypes[k] {
return InvalidMessageError("invalid type of header field")
}
}
for _, v := range requiredFields[msg.Type] {
if _, ok := msg.Headers[v]; !ok {
return InvalidMessageError("missing required header")
}
}
if path, ok := msg.Headers[FieldPath]; ok {
if !path.value.(ObjectPath).IsValid() {
return InvalidMessageError("invalid path name")
}
}
if iface, ok := msg.Headers[FieldInterface]; ok {
if !isValidInterface(iface.value.(string)) {
return InvalidMessageError("invalid interface name")
}
}
if member, ok := msg.Headers[FieldMember]; ok {
if !isValidMember(member.value.(string)) {
return InvalidMessageError("invalid member name")
}
}
if errname, ok := msg.Headers[FieldErrorName]; ok {
if !isValidInterface(errname.value.(string)) {
return InvalidMessageError("invalid error name")
}
}
if len(msg.Body) != 0 {
if _, ok := msg.Headers[FieldSignature]; !ok {
return InvalidMessageError("missing signature")
}
}
return nil
}
// Serial returns the message's serial number. The returned value is only valid
// for messages received by eavesdropping.
func (msg *Message) Serial() uint32 {
return msg.serial
}
// String returns a string representation of a message similar to the format of
// dbus-monitor.
func (msg *Message) String() string {
if err := msg.IsValid(); err != nil {
return "<invalid>"
}
s := msg.Type.String()
if v, ok := msg.Headers[FieldSender]; ok {
s += " from " + v.value.(string)
}
if v, ok := msg.Headers[FieldDestination]; ok {
s += " to " + v.value.(string)
}
s += " serial " + strconv.FormatUint(uint64(msg.serial), 10)
if v, ok := msg.Headers[FieldReplySerial]; ok {
s += " reply_serial " + strconv.FormatUint(uint64(v.value.(uint32)), 10)
}
if v, ok := msg.Headers[FieldUnixFDs]; ok {
s += " unixfds " + strconv.FormatUint(uint64(v.value.(uint32)), 10)
}
if v, ok := msg.Headers[FieldPath]; ok {
s += " path " + string(v.value.(ObjectPath))
}
if v, ok := msg.Headers[FieldInterface]; ok {
s += " interface " + v.value.(string)
}
if v, ok := msg.Headers[FieldErrorName]; ok {
s += " error " + v.value.(string)
}
if v, ok := msg.Headers[FieldMember]; ok {
s += " member " + v.value.(string)
}
if len(msg.Body) != 0 {
s += "\n"
}
for i, v := range msg.Body {
s += " " + MakeVariant(v).String()
if i != len(msg.Body)-1 {
s += "\n"
}
}
return s
}

View File

@ -1,264 +0,0 @@
// Package prop provides the Properties struct which can be used to implement
// org.freedesktop.DBus.Properties.
package prop
import (
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus/introspect"
"sync"
)
// EmitType controls how org.freedesktop.DBus.Properties.PropertiesChanged is
// emitted for a property. If it is EmitTrue, the signal is emitted. If it is
// EmitInvalidates, the signal is also emitted, but the new value of the property
// is not disclosed.
type EmitType byte
const (
EmitFalse EmitType = iota
EmitTrue
EmitInvalidates
)
// ErrIfaceNotFound is the error returned to peers who try to access properties
// on interfaces that aren't found.
var ErrIfaceNotFound = &dbus.Error{"org.freedesktop.DBus.Properties.Error.InterfaceNotFound", nil}
// ErrPropNotFound is the error returned to peers trying to access properties
// that aren't found.
var ErrPropNotFound = &dbus.Error{"org.freedesktop.DBus.Properties.Error.PropertyNotFound", nil}
// ErrReadOnly is the error returned to peers trying to set a read-only
// property.
var ErrReadOnly = &dbus.Error{"org.freedesktop.DBus.Properties.Error.ReadOnly", nil}
// ErrInvalidArg is returned to peers if the type of the property that is being
// changed and the argument don't match.
var ErrInvalidArg = &dbus.Error{"org.freedesktop.DBus.Properties.Error.InvalidArg", nil}
// The introspection data for the org.freedesktop.DBus.Properties interface.
var IntrospectData = introspect.Interface{
Name: "org.freedesktop.DBus.Properties",
Methods: []introspect.Method{
{
Name: "Get",
Args: []introspect.Arg{
{"interface", "s", "in"},
{"property", "s", "in"},
{"value", "v", "out"},
},
},
{
Name: "GetAll",
Args: []introspect.Arg{
{"interface", "s", "in"},
{"props", "a{sv}", "out"},
},
},
{
Name: "Set",
Args: []introspect.Arg{
{"interface", "s", "in"},
{"property", "s", "in"},
{"value", "v", "in"},
},
},
},
Signals: []introspect.Signal{
{
Name: "PropertiesChanged",
Args: []introspect.Arg{
{"interface", "s", "out"},
{"changed_properties", "a{sv}", "out"},
{"invalidates_properties", "as", "out"},
},
},
},
}
// The introspection data for the org.freedesktop.DBus.Properties interface, as
// a string.
const IntrospectDataString = `
<interface name="org.freedesktop.DBus.Introspectable">
<method name="Get">
<arg name="interface" direction="in" type="s"/>
<arg name="property" direction="in" type="s"/>
<arg name="value" direction="out" type="v"/>
</method>
<method name="GetAll">
<arg name="interface" direction="in" type="s"/>
<arg name="props" direction="out" type="a{sv}"/>
</method>
<method name="Set">
<arg name="interface" direction="in" type="s"/>
<arg name="property" direction="in" type="s"/>
<arg name="value" direction="in" type="v"/>
</method>
<signal name="PropertiesChanged">
<arg name="interface" type="s"/>
<arg name="changed_properties" type="a{sv}"/>
<arg name="invalidates_properties" type="as"/>
</signal>
</interface>
`
// Prop represents a single property. It is used for creating a Properties
// value.
type Prop struct {
// Initial value. Must be a DBus-representable type.
Value interface{}
// If true, the value can be modified by calls to Set.
Writable bool
// Controls how org.freedesktop.DBus.Properties.PropertiesChanged is
// emitted if this property changes.
Emit EmitType
// If not nil, anytime this property is changed by Set, this function is
// called with an appropiate Change as its argument. If the returned error
// is not nil, it is sent back to the caller of Set and the property is not
// changed.
Callback func(*Change) *dbus.Error
}
// Change represents a change of a property by a call to Set.
type Change struct {
Props *Properties
Iface string
Name string
Value interface{}
}
// Properties is a set of values that can be made available to the message bus
// using the org.freedesktop.DBus.Properties interface. It is safe for
// concurrent use by multiple goroutines.
type Properties struct {
m map[string]map[string]*Prop
mut sync.RWMutex
conn *dbus.Conn
path dbus.ObjectPath
}
// New returns a new Properties structure that manages the given properties.
// The key for the first-level map of props is the name of the interface; the
// second-level key is the name of the property. The returned structure will be
// exported as org.freedesktop.DBus.Properties on path.
func New(conn *dbus.Conn, path dbus.ObjectPath, props map[string]map[string]*Prop) *Properties {
p := &Properties{m: props, conn: conn, path: path}
conn.Export(p, path, "org.freedesktop.DBus.Properties")
return p
}
// Get implements org.freedesktop.DBus.Properties.Get.
func (p *Properties) Get(iface, property string) (dbus.Variant, *dbus.Error) {
p.mut.RLock()
defer p.mut.RUnlock()
m, ok := p.m[iface]
if !ok {
return dbus.Variant{}, ErrIfaceNotFound
}
prop, ok := m[property]
if !ok {
return dbus.Variant{}, ErrPropNotFound
}
return dbus.MakeVariant(prop.Value), nil
}
// GetAll implements org.freedesktop.DBus.Properties.GetAll.
func (p *Properties) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) {
p.mut.RLock()
defer p.mut.RUnlock()
m, ok := p.m[iface]
if !ok {
return nil, ErrIfaceNotFound
}
rm := make(map[string]dbus.Variant, len(m))
for k, v := range m {
rm[k] = dbus.MakeVariant(v.Value)
}
return rm, nil
}
// GetMust returns the value of the given property and panics if either the
// interface or the property name are invalid.
func (p *Properties) GetMust(iface, property string) interface{} {
p.mut.RLock()
defer p.mut.RUnlock()
return p.m[iface][property].Value
}
// Introspection returns the introspection data that represents the properties
// of iface.
func (p *Properties) Introspection(iface string) []introspect.Property {
p.mut.RLock()
defer p.mut.RUnlock()
m := p.m[iface]
s := make([]introspect.Property, 0, len(m))
for k, v := range m {
p := introspect.Property{Name: k, Type: dbus.SignatureOf(v.Value).String()}
if v.Writable {
p.Access = "readwrite"
} else {
p.Access = "read"
}
s = append(s, p)
}
return s
}
// set sets the given property and emits PropertyChanged if appropiate. p.mut
// must already be locked.
func (p *Properties) set(iface, property string, v interface{}) {
prop := p.m[iface][property]
prop.Value = v
switch prop.Emit {
case EmitFalse:
// do nothing
case EmitInvalidates:
p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged",
iface, map[string]dbus.Variant{}, []string{property})
case EmitTrue:
p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged",
iface, map[string]dbus.Variant{property: dbus.MakeVariant(v)},
[]string{})
default:
panic("invalid value for EmitType")
}
}
// Set implements org.freedesktop.Properties.Set.
func (p *Properties) Set(iface, property string, newv dbus.Variant) *dbus.Error {
p.mut.Lock()
defer p.mut.Unlock()
m, ok := p.m[iface]
if !ok {
return ErrIfaceNotFound
}
prop, ok := m[property]
if !ok {
return ErrPropNotFound
}
if !prop.Writable {
return ErrReadOnly
}
if newv.Signature() != dbus.SignatureOf(prop.Value) {
return ErrInvalidArg
}
if prop.Callback != nil {
err := prop.Callback(&Change{p, iface, property, newv.Value()})
if err != nil {
return err
}
}
p.set(iface, property, newv.Value())
return nil
}
// SetMust sets the value of the given property and panics if the interface or
// the property name are invalid.
func (p *Properties) SetMust(iface, property string, v interface{}) {
p.mut.Lock()
p.set(iface, property, v)
p.mut.Unlock()
}

View File

@ -1,369 +0,0 @@
package dbus
import (
"bytes"
"encoding/binary"
"io/ioutil"
"math"
"reflect"
"testing"
)
var protoTests = []struct {
vs []interface{}
bigEndian []byte
littleEndian []byte
}{
{
[]interface{}{int32(0)},
[]byte{0, 0, 0, 0},
[]byte{0, 0, 0, 0},
},
{
[]interface{}{true, false},
[]byte{0, 0, 0, 1, 0, 0, 0, 0},
[]byte{1, 0, 0, 0, 0, 0, 0, 0},
},
{
[]interface{}{byte(0), uint16(12), int16(32), uint32(43)},
[]byte{0, 0, 0, 12, 0, 32, 0, 0, 0, 0, 0, 43},
[]byte{0, 0, 12, 0, 32, 0, 0, 0, 43, 0, 0, 0},
},
{
[]interface{}{int64(-1), uint64(1<<64 - 1)},
bytes.Repeat([]byte{255}, 16),
bytes.Repeat([]byte{255}, 16),
},
{
[]interface{}{math.Inf(+1)},
[]byte{0x7f, 0xf0, 0, 0, 0, 0, 0, 0},
[]byte{0, 0, 0, 0, 0, 0, 0xf0, 0x7f},
},
{
[]interface{}{"foo"},
[]byte{0, 0, 0, 3, 'f', 'o', 'o', 0},
[]byte{3, 0, 0, 0, 'f', 'o', 'o', 0},
},
{
[]interface{}{Signature{"ai"}},
[]byte{2, 'a', 'i', 0},
[]byte{2, 'a', 'i', 0},
},
{
[]interface{}{[]int16{42, 256}},
[]byte{0, 0, 0, 4, 0, 42, 1, 0},
[]byte{4, 0, 0, 0, 42, 0, 0, 1},
},
{
[]interface{}{MakeVariant("foo")},
[]byte{1, 's', 0, 0, 0, 0, 0, 3, 'f', 'o', 'o', 0},
[]byte{1, 's', 0, 0, 3, 0, 0, 0, 'f', 'o', 'o', 0},
},
{
[]interface{}{MakeVariant(MakeVariant(Signature{"v"}))},
[]byte{1, 'v', 0, 1, 'g', 0, 1, 'v', 0},
[]byte{1, 'v', 0, 1, 'g', 0, 1, 'v', 0},
},
{
[]interface{}{map[int32]bool{42: true}},
[]byte{0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 1},
[]byte{8, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 1, 0, 0, 0},
},
{
[]interface{}{map[string]Variant{}, byte(42)},
[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
},
{
[]interface{}{[]uint64{}, byte(42)},
[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
},
}
func TestProto(t *testing.T) {
for i, v := range protoTests {
buf := new(bytes.Buffer)
bigEnc := newEncoder(buf, binary.BigEndian)
bigEnc.Encode(v.vs...)
marshalled := buf.Bytes()
if bytes.Compare(marshalled, v.bigEndian) != 0 {
t.Errorf("test %d (marshal be): got '%v', but expected '%v'\n", i+1, marshalled,
v.bigEndian)
}
buf.Reset()
litEnc := newEncoder(buf, binary.LittleEndian)
litEnc.Encode(v.vs...)
marshalled = buf.Bytes()
if bytes.Compare(marshalled, v.littleEndian) != 0 {
t.Errorf("test %d (marshal le): got '%v', but expected '%v'\n", i+1, marshalled,
v.littleEndian)
}
unmarshalled := reflect.MakeSlice(reflect.TypeOf(v.vs),
0, 0)
for i := range v.vs {
unmarshalled = reflect.Append(unmarshalled,
reflect.New(reflect.TypeOf(v.vs[i])))
}
bigDec := newDecoder(bytes.NewReader(v.bigEndian), binary.BigEndian)
vs, err := bigDec.Decode(SignatureOf(v.vs...))
if err != nil {
t.Errorf("test %d (unmarshal be): %s\n", i+1, err)
continue
}
if !reflect.DeepEqual(vs, v.vs) {
t.Errorf("test %d (unmarshal be): got %#v, but expected %#v\n", i+1, vs, v.vs)
}
litDec := newDecoder(bytes.NewReader(v.littleEndian), binary.LittleEndian)
vs, err = litDec.Decode(SignatureOf(v.vs...))
if err != nil {
t.Errorf("test %d (unmarshal le): %s\n", i+1, err)
continue
}
if !reflect.DeepEqual(vs, v.vs) {
t.Errorf("test %d (unmarshal le): got %#v, but expected %#v\n", i+1, vs, v.vs)
}
}
}
func TestProtoMap(t *testing.T) {
m := map[string]uint8{
"foo": 23,
"bar": 2,
}
var n map[string]uint8
buf := new(bytes.Buffer)
enc := newEncoder(buf, binary.LittleEndian)
enc.Encode(m)
dec := newDecoder(buf, binary.LittleEndian)
vs, err := dec.Decode(Signature{"a{sy}"})
if err != nil {
t.Fatal(err)
}
if err = Store(vs, &n); err != nil {
t.Fatal(err)
}
if len(n) != 2 || n["foo"] != 23 || n["bar"] != 2 {
t.Error("got", n)
}
}
func TestProtoVariantStruct(t *testing.T) {
var variant Variant
v := MakeVariant(struct {
A int32
B int16
}{1, 2})
buf := new(bytes.Buffer)
enc := newEncoder(buf, binary.LittleEndian)
enc.Encode(v)
dec := newDecoder(buf, binary.LittleEndian)
vs, err := dec.Decode(Signature{"v"})
if err != nil {
t.Fatal(err)
}
if err = Store(vs, &variant); err != nil {
t.Fatal(err)
}
sl := variant.Value().([]interface{})
v1, v2 := sl[0].(int32), sl[1].(int16)
if v1 != int32(1) {
t.Error("got", v1, "as first int")
}
if v2 != int16(2) {
t.Error("got", v2, "as second int")
}
}
func TestProtoStructTag(t *testing.T) {
type Bar struct {
A int32
B chan interface{} `dbus:"-"`
C int32
}
var bar1, bar2 Bar
bar1.A = 234
bar2.C = 345
buf := new(bytes.Buffer)
enc := newEncoder(buf, binary.LittleEndian)
enc.Encode(bar1)
dec := newDecoder(buf, binary.LittleEndian)
vs, err := dec.Decode(Signature{"(ii)"})
if err != nil {
t.Fatal(err)
}
if err = Store(vs, &bar2); err != nil {
t.Fatal(err)
}
if bar1 != bar2 {
t.Error("struct tag test: got", bar2)
}
}
func TestProtoStoreStruct(t *testing.T) {
var foo struct {
A int32
B string
c chan interface{}
D interface{} `dbus:"-"`
}
src := []interface{}{[]interface{}{int32(42), "foo"}}
err := Store(src, &foo)
if err != nil {
t.Fatal(err)
}
}
func TestProtoStoreNestedStruct(t *testing.T) {
var foo struct {
A int32
B struct {
C string
D float64
}
}
src := []interface{}{
[]interface{}{
int32(42),
[]interface{}{
"foo",
3.14,
},
},
}
err := Store(src, &foo)
if err != nil {
t.Fatal(err)
}
}
func TestMessage(t *testing.T) {
buf := new(bytes.Buffer)
message := new(Message)
message.Type = TypeMethodCall
message.serial = 32
message.Headers = map[HeaderField]Variant{
FieldPath: MakeVariant(ObjectPath("/org/foo/bar")),
FieldMember: MakeVariant("baz"),
}
message.Body = make([]interface{}, 0)
err := message.EncodeTo(buf, binary.LittleEndian)
if err != nil {
t.Error(err)
}
_, err = DecodeMessage(buf)
if err != nil {
t.Error(err)
}
}
func TestProtoStructInterfaces(t *testing.T) {
b := []byte{42}
vs, err := newDecoder(bytes.NewReader(b), binary.LittleEndian).Decode(Signature{"(y)"})
if err != nil {
t.Fatal(err)
}
if vs[0].([]interface{})[0].(byte) != 42 {
t.Errorf("wrongs results (got %v)", vs)
}
}
// ordinary org.freedesktop.DBus.Hello call
var smallMessage = &Message{
Type: TypeMethodCall,
serial: 1,
Headers: map[HeaderField]Variant{
FieldDestination: MakeVariant("org.freedesktop.DBus"),
FieldPath: MakeVariant(ObjectPath("/org/freedesktop/DBus")),
FieldInterface: MakeVariant("org.freedesktop.DBus"),
FieldMember: MakeVariant("Hello"),
},
}
// org.freedesktop.Notifications.Notify
var bigMessage = &Message{
Type: TypeMethodCall,
serial: 2,
Headers: map[HeaderField]Variant{
FieldDestination: MakeVariant("org.freedesktop.Notifications"),
FieldPath: MakeVariant(ObjectPath("/org/freedesktop/Notifications")),
FieldInterface: MakeVariant("org.freedesktop.Notifications"),
FieldMember: MakeVariant("Notify"),
FieldSignature: MakeVariant(Signature{"susssasa{sv}i"}),
},
Body: []interface{}{
"app_name",
uint32(0),
"dialog-information",
"Notification",
"This is the body of a notification",
[]string{"ok", "Ok"},
map[string]Variant{
"sound-name": MakeVariant("dialog-information"),
},
int32(-1),
},
}
func BenchmarkDecodeMessageSmall(b *testing.B) {
var err error
var rd *bytes.Reader
b.StopTimer()
buf := new(bytes.Buffer)
err = smallMessage.EncodeTo(buf, binary.LittleEndian)
if err != nil {
b.Fatal(err)
}
decoded := buf.Bytes()
b.StartTimer()
for i := 0; i < b.N; i++ {
rd = bytes.NewReader(decoded)
_, err = DecodeMessage(rd)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkDecodeMessageBig(b *testing.B) {
var err error
var rd *bytes.Reader
b.StopTimer()
buf := new(bytes.Buffer)
err = bigMessage.EncodeTo(buf, binary.LittleEndian)
if err != nil {
b.Fatal(err)
}
decoded := buf.Bytes()
b.StartTimer()
for i := 0; i < b.N; i++ {
rd = bytes.NewReader(decoded)
_, err = DecodeMessage(rd)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkEncodeMessageSmall(b *testing.B) {
var err error
for i := 0; i < b.N; i++ {
err = smallMessage.EncodeTo(ioutil.Discard, binary.LittleEndian)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkEncodeMessageBig(b *testing.B) {
var err error
for i := 0; i < b.N; i++ {
err = bigMessage.EncodeTo(ioutil.Discard, binary.LittleEndian)
if err != nil {
b.Fatal(err)
}
}
}

View File

@ -1,257 +0,0 @@
package dbus
import (
"fmt"
"reflect"
"strings"
)
var sigToType = map[byte]reflect.Type{
'y': byteType,
'b': boolType,
'n': int16Type,
'q': uint16Type,
'i': int32Type,
'u': uint32Type,
'x': int64Type,
't': uint64Type,
'd': float64Type,
's': stringType,
'g': signatureType,
'o': objectPathType,
'v': variantType,
'h': unixFDIndexType,
}
// Signature represents a correct type signature as specified by the D-Bus
// specification. The zero value represents the empty signature, "".
type Signature struct {
str string
}
// SignatureOf returns the concatenation of all the signatures of the given
// values. It panics if one of them is not representable in D-Bus.
func SignatureOf(vs ...interface{}) Signature {
var s string
for _, v := range vs {
s += getSignature(reflect.TypeOf(v))
}
return Signature{s}
}
// SignatureOfType returns the signature of the given type. It panics if the
// type is not representable in D-Bus.
func SignatureOfType(t reflect.Type) Signature {
return Signature{getSignature(t)}
}
// getSignature returns the signature of the given type and panics on unknown types.
func getSignature(t reflect.Type) string {
// handle simple types first
switch t.Kind() {
case reflect.Uint8:
return "y"
case reflect.Bool:
return "b"
case reflect.Int16:
return "n"
case reflect.Uint16:
return "q"
case reflect.Int32:
if t == unixFDType {
return "h"
}
return "i"
case reflect.Uint32:
if t == unixFDIndexType {
return "h"
}
return "u"
case reflect.Int64:
return "x"
case reflect.Uint64:
return "t"
case reflect.Float64:
return "d"
case reflect.Ptr:
return getSignature(t.Elem())
case reflect.String:
if t == objectPathType {
return "o"
}
return "s"
case reflect.Struct:
if t == variantType {
return "v"
} else if t == signatureType {
return "g"
}
var s string
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
s += getSignature(t.Field(i).Type)
}
}
return "(" + s + ")"
case reflect.Array, reflect.Slice:
return "a" + getSignature(t.Elem())
case reflect.Map:
if !isKeyType(t.Key()) {
panic(InvalidTypeError{t})
}
return "a{" + getSignature(t.Key()) + getSignature(t.Elem()) + "}"
}
panic(InvalidTypeError{t})
}
// ParseSignature returns the signature represented by this string, or a
// SignatureError if the string is not a valid signature.
func ParseSignature(s string) (sig Signature, err error) {
if len(s) == 0 {
return
}
if len(s) > 255 {
return Signature{""}, SignatureError{s, "too long"}
}
sig.str = s
for err == nil && len(s) != 0 {
err, s = validSingle(s, 0)
}
if err != nil {
sig = Signature{""}
}
return
}
// ParseSignatureMust behaves like ParseSignature, except that it panics if s
// is not valid.
func ParseSignatureMust(s string) Signature {
sig, err := ParseSignature(s)
if err != nil {
panic(err)
}
return sig
}
// Empty retruns whether the signature is the empty signature.
func (s Signature) Empty() bool {
return s.str == ""
}
// Single returns whether the signature represents a single, complete type.
func (s Signature) Single() bool {
err, r := validSingle(s.str, 0)
return err != nil && r == ""
}
// String returns the signature's string representation.
func (s Signature) String() string {
return s.str
}
// A SignatureError indicates that a signature passed to a function or received
// on a connection is not a valid signature.
type SignatureError struct {
Sig string
Reason string
}
func (e SignatureError) Error() string {
return fmt.Sprintf("dbus: invalid signature: %q (%s)", e.Sig, e.Reason)
}
// Try to read a single type from this string. If it was successfull, err is nil
// and rem is the remaining unparsed part. Otherwise, err is a non-nil
// SignatureError and rem is "". depth is the current recursion depth which may
// not be greater than 64 and should be given as 0 on the first call.
func validSingle(s string, depth int) (err error, rem string) {
if s == "" {
return SignatureError{Sig: s, Reason: "empty signature"}, ""
}
if depth > 64 {
return SignatureError{Sig: s, Reason: "container nesting too deep"}, ""
}
switch s[0] {
case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 's', 'g', 'o', 'v', 'h':
return nil, s[1:]
case 'a':
if len(s) > 1 && s[1] == '{' {
i := findMatching(s[1:], '{', '}')
if i == -1 {
return SignatureError{Sig: s, Reason: "unmatched '{'"}, ""
}
i++
rem = s[i+1:]
s = s[2:i]
if err, _ = validSingle(s[:1], depth+1); err != nil {
return err, ""
}
err, nr := validSingle(s[1:], depth+1)
if err != nil {
return err, ""
}
if nr != "" {
return SignatureError{Sig: s, Reason: "too many types in dict"}, ""
}
return nil, rem
}
return validSingle(s[1:], depth+1)
case '(':
i := findMatching(s, '(', ')')
if i == -1 {
return SignatureError{Sig: s, Reason: "unmatched ')'"}, ""
}
rem = s[i+1:]
s = s[1:i]
for err == nil && s != "" {
err, s = validSingle(s, depth+1)
}
if err != nil {
rem = ""
}
return
}
return SignatureError{Sig: s, Reason: "invalid type character"}, ""
}
func findMatching(s string, left, right rune) int {
n := 0
for i, v := range s {
if v == left {
n++
} else if v == right {
n--
}
if n == 0 {
return i
}
}
return -1
}
// typeFor returns the type of the given signature. It ignores any left over
// characters and panics if s doesn't start with a valid type signature.
func typeFor(s string) (t reflect.Type) {
err, _ := validSingle(s, 0)
if err != nil {
panic(err)
}
if t, ok := sigToType[s[0]]; ok {
return t
}
switch s[0] {
case 'a':
if s[1] == '{' {
i := strings.LastIndex(s, "}")
t = reflect.MapOf(sigToType[s[2]], typeFor(s[3:i]))
} else {
t = reflect.SliceOf(typeFor(s[1:]))
}
case '(':
t = interfacesType
}
return
}

View File

@ -1,70 +0,0 @@
package dbus
import (
"testing"
)
var sigTests = []struct {
vs []interface{}
sig Signature
}{
{
[]interface{}{new(int32)},
Signature{"i"},
},
{
[]interface{}{new(string)},
Signature{"s"},
},
{
[]interface{}{new(Signature)},
Signature{"g"},
},
{
[]interface{}{new([]int16)},
Signature{"an"},
},
{
[]interface{}{new(int16), new(uint32)},
Signature{"nu"},
},
{
[]interface{}{new(map[byte]Variant)},
Signature{"a{yv}"},
},
{
[]interface{}{new(Variant), new([]map[int32]string)},
Signature{"vaa{is}"},
},
}
func TestSig(t *testing.T) {
for i, v := range sigTests {
sig := SignatureOf(v.vs...)
if sig != v.sig {
t.Errorf("test %d: got %q, expected %q", i+1, sig.str, v.sig.str)
}
}
}
var getSigTest = []interface{}{
[]struct {
b byte
i int32
t uint64
s string
}{},
map[string]Variant{},
}
func BenchmarkGetSignatureSimple(b *testing.B) {
for i := 0; i < b.N; i++ {
SignatureOf("", int32(0))
}
}
func BenchmarkGetSignatureLong(b *testing.B) {
for i := 0; i < b.N; i++ {
SignatureOf(getSigTest...)
}
}

View File

@ -1,6 +0,0 @@
package dbus
func (t *unixTransport) SendNullByte() error {
_, err := t.Write([]byte{0})
return err
}

View File

@ -1,35 +0,0 @@
package dbus
import (
"encoding/binary"
"errors"
"io"
)
type genericTransport struct {
io.ReadWriteCloser
}
func (t genericTransport) SendNullByte() error {
_, err := t.Write([]byte{0})
return err
}
func (t genericTransport) SupportsUnixFDs() bool {
return false
}
func (t genericTransport) EnableUnixFDs() {}
func (t genericTransport) ReadMessage() (*Message, error) {
return DecodeMessage(t)
}
func (t genericTransport) SendMessage(msg *Message) error {
for _, v := range msg.Body {
if _, ok := v.(UnixFD); ok {
return errors.New("dbus: unix fd passing not enabled")
}
}
return msg.EncodeTo(t, binary.LittleEndian)
}

View File

@ -1,190 +0,0 @@
package dbus
import (
"bytes"
"encoding/binary"
"errors"
"io"
"net"
"syscall"
)
type oobReader struct {
conn *net.UnixConn
oob []byte
buf [4096]byte
}
func (o *oobReader) Read(b []byte) (n int, err error) {
n, oobn, flags, _, err := o.conn.ReadMsgUnix(b, o.buf[:])
if err != nil {
return n, err
}
if flags&syscall.MSG_CTRUNC != 0 {
return n, errors.New("dbus: control data truncated (too many fds received)")
}
o.oob = append(o.oob, o.buf[:oobn]...)
return n, nil
}
type unixTransport struct {
*net.UnixConn
hasUnixFDs bool
}
func newUnixTransport(keys string) (transport, error) {
var err error
t := new(unixTransport)
abstract := getKey(keys, "abstract")
path := getKey(keys, "path")
switch {
case abstract == "" && path == "":
return nil, errors.New("dbus: invalid address (neither path nor abstract set)")
case abstract != "" && path == "":
t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: "@" + abstract, Net: "unix"})
if err != nil {
return nil, err
}
return t, nil
case abstract == "" && path != "":
t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: path, Net: "unix"})
if err != nil {
return nil, err
}
return t, nil
default:
return nil, errors.New("dbus: invalid address (both path and abstract set)")
}
}
func (t *unixTransport) EnableUnixFDs() {
t.hasUnixFDs = true
}
func (t *unixTransport) ReadMessage() (*Message, error) {
var (
blen, hlen uint32
csheader [16]byte
headers []header
order binary.ByteOrder
unixfds uint32
)
// To be sure that all bytes of out-of-band data are read, we use a special
// reader that uses ReadUnix on the underlying connection instead of Read
// and gathers the out-of-band data in a buffer.
rd := &oobReader{conn: t.UnixConn}
// read the first 16 bytes (the part of the header that has a constant size),
// from which we can figure out the length of the rest of the message
if _, err := io.ReadFull(rd, csheader[:]); err != nil {
return nil, err
}
switch csheader[0] {
case 'l':
order = binary.LittleEndian
case 'B':
order = binary.BigEndian
default:
return nil, InvalidMessageError("invalid byte order")
}
// csheader[4:8] -> length of message body, csheader[12:16] -> length of
// header fields (without alignment)
binary.Read(bytes.NewBuffer(csheader[4:8]), order, &blen)
binary.Read(bytes.NewBuffer(csheader[12:]), order, &hlen)
if hlen%8 != 0 {
hlen += 8 - (hlen % 8)
}
// decode headers and look for unix fds
headerdata := make([]byte, hlen+4)
copy(headerdata, csheader[12:])
if _, err := io.ReadFull(t, headerdata[4:]); err != nil {
return nil, err
}
dec := newDecoder(bytes.NewBuffer(headerdata), order)
dec.pos = 12
vs, err := dec.Decode(Signature{"a(yv)"})
if err != nil {
return nil, err
}
Store(vs, &headers)
for _, v := range headers {
if v.Field == byte(FieldUnixFDs) {
unixfds, _ = v.Variant.value.(uint32)
}
}
all := make([]byte, 16+hlen+blen)
copy(all, csheader[:])
copy(all[16:], headerdata[4:])
if _, err := io.ReadFull(rd, all[16+hlen:]); err != nil {
return nil, err
}
if unixfds != 0 {
if !t.hasUnixFDs {
return nil, errors.New("dbus: got unix fds on unsupported transport")
}
// read the fds from the OOB data
scms, err := syscall.ParseSocketControlMessage(rd.oob)
if err != nil {
return nil, err
}
if len(scms) != 1 {
return nil, errors.New("dbus: received more than one socket control message")
}
fds, err := syscall.ParseUnixRights(&scms[0])
if err != nil {
return nil, err
}
msg, err := DecodeMessage(bytes.NewBuffer(all))
if err != nil {
return nil, err
}
// substitute the values in the message body (which are indices for the
// array receiver via OOB) with the actual values
for i, v := range msg.Body {
if j, ok := v.(UnixFDIndex); ok {
if uint32(j) >= unixfds {
return nil, InvalidMessageError("invalid index for unix fd")
}
msg.Body[i] = UnixFD(fds[j])
}
}
return msg, nil
}
return DecodeMessage(bytes.NewBuffer(all))
}
func (t *unixTransport) SendMessage(msg *Message) error {
fds := make([]int, 0)
for i, v := range msg.Body {
if fd, ok := v.(UnixFD); ok {
msg.Body[i] = UnixFDIndex(len(fds))
fds = append(fds, int(fd))
}
}
if len(fds) != 0 {
if !t.hasUnixFDs {
return errors.New("dbus: unix fd passing not enabled")
}
msg.Headers[FieldUnixFDs] = MakeVariant(uint32(len(fds)))
oob := syscall.UnixRights(fds...)
buf := new(bytes.Buffer)
msg.EncodeTo(buf, binary.LittleEndian)
n, oobn, err := t.UnixConn.WriteMsgUnix(buf.Bytes(), oob, nil)
if err != nil {
return err
}
if n != buf.Len() || oobn != len(oob) {
return io.ErrShortWrite
}
} else {
if err := msg.EncodeTo(t, binary.LittleEndian); err != nil {
return nil
}
}
return nil
}
func (t *unixTransport) SupportsUnixFDs() bool {
return true
}

View File

@ -1,49 +0,0 @@
package dbus
import (
"os"
"testing"
)
const testString = `This is a test!
This text should be read from the file that is created by this test.`
type unixFDTest struct{}
func (t unixFDTest) Test(fd UnixFD) (string, *Error) {
var b [4096]byte
file := os.NewFile(uintptr(fd), "testfile")
defer file.Close()
n, err := file.Read(b[:])
if err != nil {
return "", &Error{"com.github.guelfey.test.Error", nil}
}
return string(b[:n]), nil
}
func TestUnixFDs(t *testing.T) {
conn, err := SessionBus()
if err != nil {
t.Fatal(err)
}
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
defer w.Close()
if _, err := w.Write([]byte(testString)); err != nil {
t.Fatal(err)
}
name := conn.Names()[0]
test := unixFDTest{}
conn.Export(test, "/com/github/guelfey/test", "com.github.guelfey.test")
var s string
obj := conn.Object(name, "/com/github/guelfey/test")
err = obj.Call("com.github.guelfey.test.Test", 0, UnixFD(r.Fd())).Store(&s)
if err != nil {
t.Fatal(err)
}
if s != testString {
t.Fatal("got", s, "wanted", testString)
}
}

View File

@ -1,22 +0,0 @@
// +build !darwin
package dbus
import (
"io"
"os"
"syscall"
)
func (t *unixTransport) SendNullByte() error {
ucred := &syscall.Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
b := syscall.UnixCredentials(ucred)
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
if err != nil {
return err
}
if oobn != len(b) {
return io.ErrShortWrite
}
return nil
}

View File

@ -1,129 +0,0 @@
package dbus
import (
"bytes"
"fmt"
"reflect"
"strconv"
)
// Variant represents the D-Bus variant type.
type Variant struct {
sig Signature
value interface{}
}
// MakeVariant converts the given value to a Variant. It panics if v cannot be
// represented as a D-Bus type.
func MakeVariant(v interface{}) Variant {
return Variant{SignatureOf(v), v}
}
// ParseVariant parses the given string as a variant as described at
// https://developer.gnome.org/glib/unstable/gvariant-text.html. If sig is not
// empty, it is taken to be the expected signature for the variant.
func ParseVariant(s string, sig Signature) (Variant, error) {
tokens := varLex(s)
p := &varParser{tokens: tokens}
n, err := varMakeNode(p)
if err != nil {
return Variant{}, err
}
if sig.str == "" {
sig, err = varInfer(n)
if err != nil {
return Variant{}, err
}
}
v, err := n.Value(sig)
if err != nil {
return Variant{}, err
}
return MakeVariant(v), nil
}
// format returns a formatted version of v and whether this string can be parsed
// unambigously.
func (v Variant) format() (string, bool) {
switch v.sig.str[0] {
case 'b', 'i':
return fmt.Sprint(v.value), true
case 'n', 'q', 'u', 'x', 't', 'd', 'h':
return fmt.Sprint(v.value), false
case 's':
return strconv.Quote(v.value.(string)), true
case 'o':
return strconv.Quote(string(v.value.(ObjectPath))), false
case 'g':
return strconv.Quote(v.value.(Signature).str), false
case 'v':
s, unamb := v.value.(Variant).format()
if !unamb {
return "<@" + v.value.(Variant).sig.str + " " + s + ">", true
}
return "<" + s + ">", true
case 'y':
return fmt.Sprintf("%#x", v.value.(byte)), false
}
rv := reflect.ValueOf(v.value)
switch rv.Kind() {
case reflect.Slice:
if rv.Len() == 0 {
return "[]", false
}
unamb := true
buf := bytes.NewBuffer([]byte("["))
for i := 0; i < rv.Len(); i++ {
// TODO: slooow
s, b := MakeVariant(rv.Index(i).Interface()).format()
unamb = unamb && b
buf.WriteString(s)
if i != rv.Len()-1 {
buf.WriteString(", ")
}
}
buf.WriteByte(']')
return buf.String(), unamb
case reflect.Map:
if rv.Len() == 0 {
return "{}", false
}
unamb := true
buf := bytes.NewBuffer([]byte("{"))
for i, k := range rv.MapKeys() {
s, b := MakeVariant(k.Interface()).format()
unamb = unamb && b
buf.WriteString(s)
buf.WriteString(": ")
s, b = MakeVariant(rv.MapIndex(k).Interface()).format()
unamb = unamb && b
buf.WriteString(s)
if i != rv.Len()-1 {
buf.WriteString(", ")
}
}
buf.WriteByte('}')
return buf.String(), unamb
}
return `"INVALID"`, true
}
// Signature returns the D-Bus signature of the underlying value of v.
func (v Variant) Signature() Signature {
return v.sig
}
// String returns the string representation of the underlying value of v as
// described at https://developer.gnome.org/glib/unstable/gvariant-text.html.
func (v Variant) String() string {
s, unamb := v.format()
if !unamb {
return "@" + v.sig.str + " " + s
}
return s
}
// Value returns the underlying value of v.
func (v Variant) Value() interface{} {
return v.value
}

View File

@ -1,284 +0,0 @@
package dbus
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
// Heavily inspired by the lexer from text/template.
type varToken struct {
typ varTokenType
val string
}
type varTokenType byte
const (
tokEOF varTokenType = iota
tokError
tokNumber
tokString
tokBool
tokArrayStart
tokArrayEnd
tokDictStart
tokDictEnd
tokVariantStart
tokVariantEnd
tokComma
tokColon
tokType
tokByteString
)
type varLexer struct {
input string
start int
pos int
width int
tokens []varToken
}
type lexState func(*varLexer) lexState
func varLex(s string) []varToken {
l := &varLexer{input: s}
l.run()
return l.tokens
}
func (l *varLexer) accept(valid string) bool {
if strings.IndexRune(valid, l.next()) >= 0 {
return true
}
l.backup()
return false
}
func (l *varLexer) backup() {
l.pos -= l.width
}
func (l *varLexer) emit(t varTokenType) {
l.tokens = append(l.tokens, varToken{t, l.input[l.start:l.pos]})
l.start = l.pos
}
func (l *varLexer) errorf(format string, v ...interface{}) lexState {
l.tokens = append(l.tokens, varToken{
tokError,
fmt.Sprintf(format, v...),
})
return nil
}
func (l *varLexer) ignore() {
l.start = l.pos
}
func (l *varLexer) next() rune {
var r rune
if l.pos >= len(l.input) {
l.width = 0
return -1
}
r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
l.pos += l.width
return r
}
func (l *varLexer) run() {
for state := varLexNormal; state != nil; {
state = state(l)
}
}
func (l *varLexer) peek() rune {
r := l.next()
l.backup()
return r
}
func varLexNormal(l *varLexer) lexState {
for {
r := l.next()
switch {
case r == -1:
l.emit(tokEOF)
return nil
case r == '[':
l.emit(tokArrayStart)
case r == ']':
l.emit(tokArrayEnd)
case r == '{':
l.emit(tokDictStart)
case r == '}':
l.emit(tokDictEnd)
case r == '<':
l.emit(tokVariantStart)
case r == '>':
l.emit(tokVariantEnd)
case r == ':':
l.emit(tokColon)
case r == ',':
l.emit(tokComma)
case r == '\'' || r == '"':
l.backup()
return varLexString
case r == '@':
l.backup()
return varLexType
case unicode.IsSpace(r):
l.ignore()
case unicode.IsNumber(r) || r == '+' || r == '-':
l.backup()
return varLexNumber
case r == 'b':
pos := l.start
if n := l.peek(); n == '"' || n == '\'' {
return varLexByteString
}
// not a byte string; try to parse it as a type or bool below
l.pos = pos + 1
l.width = 1
fallthrough
default:
// either a bool or a type. Try bools first.
l.backup()
if l.pos+4 <= len(l.input) {
if l.input[l.pos:l.pos+4] == "true" {
l.pos += 4
l.emit(tokBool)
continue
}
}
if l.pos+5 <= len(l.input) {
if l.input[l.pos:l.pos+5] == "false" {
l.pos += 5
l.emit(tokBool)
continue
}
}
// must be a type.
return varLexType
}
}
}
var varTypeMap = map[string]string{
"boolean": "b",
"byte": "y",
"int16": "n",
"uint16": "q",
"int32": "i",
"uint32": "u",
"int64": "x",
"uint64": "t",
"double": "f",
"string": "s",
"objectpath": "o",
"signature": "g",
}
func varLexByteString(l *varLexer) lexState {
q := l.next()
Loop:
for {
switch l.next() {
case '\\':
if r := l.next(); r != -1 {
break
}
fallthrough
case -1:
return l.errorf("unterminated bytestring")
case q:
break Loop
}
}
l.emit(tokByteString)
return varLexNormal
}
func varLexNumber(l *varLexer) lexState {
l.accept("+-")
digits := "0123456789"
if l.accept("0") {
if l.accept("x") {
digits = "0123456789abcdefABCDEF"
} else {
digits = "01234567"
}
}
for strings.IndexRune(digits, l.next()) >= 0 {
}
l.backup()
if l.accept(".") {
for strings.IndexRune(digits, l.next()) >= 0 {
}
l.backup()
}
if l.accept("eE") {
l.accept("+-")
for strings.IndexRune("0123456789", l.next()) >= 0 {
}
l.backup()
}
if r := l.peek(); unicode.IsLetter(r) {
l.next()
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
}
l.emit(tokNumber)
return varLexNormal
}
func varLexString(l *varLexer) lexState {
q := l.next()
Loop:
for {
switch l.next() {
case '\\':
if r := l.next(); r != -1 {
break
}
fallthrough
case -1:
return l.errorf("unterminated string")
case q:
break Loop
}
}
l.emit(tokString)
return varLexNormal
}
func varLexType(l *varLexer) lexState {
at := l.accept("@")
for {
r := l.next()
if r == -1 {
break
}
if unicode.IsSpace(r) {
l.backup()
break
}
}
if at {
if _, err := ParseSignature(l.input[l.start+1 : l.pos]); err != nil {
return l.errorf("%s", err)
}
} else {
if _, ok := varTypeMap[l.input[l.start:l.pos]]; ok {
l.emit(tokType)
return varLexNormal
}
return l.errorf("unrecognized type %q", l.input[l.start:l.pos])
}
l.emit(tokType)
return varLexNormal
}

View File

@ -1,817 +0,0 @@
package dbus
import (
"bytes"
"errors"
"fmt"
"io"
"reflect"
"strconv"
"strings"
"unicode/utf8"
)
type varParser struct {
tokens []varToken
i int
}
func (p *varParser) backup() {
p.i--
}
func (p *varParser) next() varToken {
if p.i < len(p.tokens) {
t := p.tokens[p.i]
p.i++
return t
}
return varToken{typ: tokEOF}
}
type varNode interface {
Infer() (Signature, error)
String() string
Sigs() sigSet
Value(Signature) (interface{}, error)
}
func varMakeNode(p *varParser) (varNode, error) {
var sig Signature
for {
t := p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
case tokNumber:
return varMakeNumNode(t, sig)
case tokString:
return varMakeStringNode(t, sig)
case tokBool:
if sig.str != "" && sig.str != "b" {
return nil, varTypeError{t.val, sig}
}
b, err := strconv.ParseBool(t.val)
if err != nil {
return nil, err
}
return boolNode(b), nil
case tokArrayStart:
return varMakeArrayNode(p, sig)
case tokVariantStart:
return varMakeVariantNode(p, sig)
case tokDictStart:
return varMakeDictNode(p, sig)
case tokType:
if sig.str != "" {
return nil, errors.New("unexpected type annotation")
}
if t.val[0] == '@' {
sig.str = t.val[1:]
} else {
sig.str = varTypeMap[t.val]
}
case tokByteString:
if sig.str != "" && sig.str != "ay" {
return nil, varTypeError{t.val, sig}
}
b, err := varParseByteString(t.val)
if err != nil {
return nil, err
}
return byteStringNode(b), nil
default:
return nil, fmt.Errorf("unexpected %q", t.val)
}
}
}
type varTypeError struct {
val string
sig Signature
}
func (e varTypeError) Error() string {
return fmt.Sprintf("dbus: can't parse %q as type %q", e.val, e.sig.str)
}
type sigSet map[Signature]bool
func (s sigSet) Empty() bool {
return len(s) == 0
}
func (s sigSet) Intersect(s2 sigSet) sigSet {
r := make(sigSet)
for k := range s {
if s2[k] {
r[k] = true
}
}
return r
}
func (s sigSet) Single() (Signature, bool) {
if len(s) == 1 {
for k := range s {
return k, true
}
}
return Signature{}, false
}
func (s sigSet) ToArray() sigSet {
r := make(sigSet, len(s))
for k := range s {
r[Signature{"a" + k.str}] = true
}
return r
}
type numNode struct {
sig Signature
str string
val interface{}
}
var numSigSet = sigSet{
Signature{"y"}: true,
Signature{"n"}: true,
Signature{"q"}: true,
Signature{"i"}: true,
Signature{"u"}: true,
Signature{"x"}: true,
Signature{"t"}: true,
Signature{"d"}: true,
}
func (n numNode) Infer() (Signature, error) {
if strings.ContainsAny(n.str, ".e") {
return Signature{"d"}, nil
}
return Signature{"i"}, nil
}
func (n numNode) String() string {
return n.str
}
func (n numNode) Sigs() sigSet {
if n.sig.str != "" {
return sigSet{n.sig: true}
}
if strings.ContainsAny(n.str, ".e") {
return sigSet{Signature{"d"}: true}
}
return numSigSet
}
func (n numNode) Value(sig Signature) (interface{}, error) {
if n.sig.str != "" && n.sig != sig {
return nil, varTypeError{n.str, sig}
}
if n.val != nil {
return n.val, nil
}
return varNumAs(n.str, sig)
}
func varMakeNumNode(tok varToken, sig Signature) (varNode, error) {
if sig.str == "" {
return numNode{str: tok.val}, nil
}
num, err := varNumAs(tok.val, sig)
if err != nil {
return nil, err
}
return numNode{sig: sig, val: num}, nil
}
func varNumAs(s string, sig Signature) (interface{}, error) {
isUnsigned := false
size := 32
switch sig.str {
case "n":
size = 16
case "i":
case "x":
size = 64
case "y":
size = 8
isUnsigned = true
case "q":
size = 16
isUnsigned = true
case "u":
isUnsigned = true
case "t":
size = 64
isUnsigned = true
case "d":
d, err := strconv.ParseFloat(s, 64)
if err != nil {
return nil, err
}
return d, nil
default:
return nil, varTypeError{s, sig}
}
base := 10
if strings.HasPrefix(s, "0x") {
base = 16
s = s[2:]
}
if strings.HasPrefix(s, "0") && len(s) != 1 {
base = 8
s = s[1:]
}
if isUnsigned {
i, err := strconv.ParseUint(s, base, size)
if err != nil {
return nil, err
}
var v interface{} = i
switch sig.str {
case "y":
v = byte(i)
case "q":
v = uint16(i)
case "u":
v = uint32(i)
}
return v, nil
}
i, err := strconv.ParseInt(s, base, size)
if err != nil {
return nil, err
}
var v interface{} = i
switch sig.str {
case "n":
v = int16(i)
case "i":
v = int32(i)
}
return v, nil
}
type stringNode struct {
sig Signature
str string // parsed
val interface{} // has correct type
}
var stringSigSet = sigSet{
Signature{"s"}: true,
Signature{"g"}: true,
Signature{"o"}: true,
}
func (n stringNode) Infer() (Signature, error) {
return Signature{"s"}, nil
}
func (n stringNode) String() string {
return n.str
}
func (n stringNode) Sigs() sigSet {
if n.sig.str != "" {
return sigSet{n.sig: true}
}
return stringSigSet
}
func (n stringNode) Value(sig Signature) (interface{}, error) {
if n.sig.str != "" && n.sig != sig {
return nil, varTypeError{n.str, sig}
}
if n.val != nil {
return n.val, nil
}
switch {
case sig.str == "g":
return Signature{n.str}, nil
case sig.str == "o":
return ObjectPath(n.str), nil
case sig.str == "s":
return n.str, nil
default:
return nil, varTypeError{n.str, sig}
}
}
func varMakeStringNode(tok varToken, sig Signature) (varNode, error) {
if sig.str != "" && sig.str != "s" && sig.str != "g" && sig.str != "o" {
return nil, fmt.Errorf("invalid type %q for string", sig.str)
}
s, err := varParseString(tok.val)
if err != nil {
return nil, err
}
n := stringNode{str: s}
if sig.str == "" {
return stringNode{str: s}, nil
}
n.sig = sig
switch sig.str {
case "o":
n.val = ObjectPath(s)
case "g":
n.val = Signature{s}
case "s":
n.val = s
}
return n, nil
}
func varParseString(s string) (string, error) {
// quotes are guaranteed to be there
s = s[1 : len(s)-1]
buf := new(bytes.Buffer)
for len(s) != 0 {
r, size := utf8.DecodeRuneInString(s)
if r == utf8.RuneError && size == 1 {
return "", errors.New("invalid UTF-8")
}
s = s[size:]
if r != '\\' {
buf.WriteRune(r)
continue
}
r, size = utf8.DecodeRuneInString(s)
if r == utf8.RuneError && size == 1 {
return "", errors.New("invalid UTF-8")
}
s = s[size:]
switch r {
case 'a':
buf.WriteRune(0x7)
case 'b':
buf.WriteRune(0x8)
case 'f':
buf.WriteRune(0xc)
case 'n':
buf.WriteRune('\n')
case 'r':
buf.WriteRune('\r')
case 't':
buf.WriteRune('\t')
case '\n':
case 'u':
if len(s) < 4 {
return "", errors.New("short unicode escape")
}
r, err := strconv.ParseUint(s[:4], 16, 32)
if err != nil {
return "", err
}
buf.WriteRune(rune(r))
s = s[4:]
case 'U':
if len(s) < 8 {
return "", errors.New("short unicode escape")
}
r, err := strconv.ParseUint(s[:8], 16, 32)
if err != nil {
return "", err
}
buf.WriteRune(rune(r))
s = s[8:]
default:
buf.WriteRune(r)
}
}
return buf.String(), nil
}
var boolSigSet = sigSet{Signature{"b"}: true}
type boolNode bool
func (boolNode) Infer() (Signature, error) {
return Signature{"b"}, nil
}
func (b boolNode) String() string {
if b {
return "true"
}
return "false"
}
func (boolNode) Sigs() sigSet {
return boolSigSet
}
func (b boolNode) Value(sig Signature) (interface{}, error) {
if sig.str != "b" {
return nil, varTypeError{b.String(), sig}
}
return bool(b), nil
}
type arrayNode struct {
set sigSet
children []varNode
val interface{}
}
func (n arrayNode) Infer() (Signature, error) {
for _, v := range n.children {
csig, err := varInfer(v)
if err != nil {
continue
}
return Signature{"a" + csig.str}, nil
}
return Signature{}, fmt.Errorf("can't infer type for %q", n.String())
}
func (n arrayNode) String() string {
s := "["
for i, v := range n.children {
s += v.String()
if i != len(n.children)-1 {
s += ", "
}
}
return s + "]"
}
func (n arrayNode) Sigs() sigSet {
return n.set
}
func (n arrayNode) Value(sig Signature) (interface{}, error) {
if n.set.Empty() {
// no type information whatsoever, so this must be an empty slice
return reflect.MakeSlice(typeFor(sig.str), 0, 0).Interface(), nil
}
if !n.set[sig] {
return nil, varTypeError{n.String(), sig}
}
s := reflect.MakeSlice(typeFor(sig.str), len(n.children), len(n.children))
for i, v := range n.children {
rv, err := v.Value(Signature{sig.str[1:]})
if err != nil {
return nil, err
}
s.Index(i).Set(reflect.ValueOf(rv))
}
return s.Interface(), nil
}
func varMakeArrayNode(p *varParser, sig Signature) (varNode, error) {
var n arrayNode
if sig.str != "" {
n.set = sigSet{sig: true}
}
if t := p.next(); t.typ == tokArrayEnd {
return n, nil
} else {
p.backup()
}
Loop:
for {
t := p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
}
p.backup()
cn, err := varMakeNode(p)
if err != nil {
return nil, err
}
if cset := cn.Sigs(); !cset.Empty() {
if n.set.Empty() {
n.set = cset.ToArray()
} else {
nset := cset.ToArray().Intersect(n.set)
if nset.Empty() {
return nil, fmt.Errorf("can't parse %q with given type information", cn.String())
}
n.set = nset
}
}
n.children = append(n.children, cn)
switch t := p.next(); t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
case tokArrayEnd:
break Loop
case tokComma:
continue
default:
return nil, fmt.Errorf("unexpected %q", t.val)
}
}
return n, nil
}
type variantNode struct {
n varNode
}
var variantSet = sigSet{
Signature{"v"}: true,
}
func (variantNode) Infer() (Signature, error) {
return Signature{"v"}, nil
}
func (n variantNode) String() string {
return "<" + n.n.String() + ">"
}
func (variantNode) Sigs() sigSet {
return variantSet
}
func (n variantNode) Value(sig Signature) (interface{}, error) {
if sig.str != "v" {
return nil, varTypeError{n.String(), sig}
}
sig, err := varInfer(n.n)
if err != nil {
return nil, err
}
v, err := n.n.Value(sig)
if err != nil {
return nil, err
}
return MakeVariant(v), nil
}
func varMakeVariantNode(p *varParser, sig Signature) (varNode, error) {
n, err := varMakeNode(p)
if err != nil {
return nil, err
}
if t := p.next(); t.typ != tokVariantEnd {
return nil, fmt.Errorf("unexpected %q", t.val)
}
vn := variantNode{n}
if sig.str != "" && sig.str != "v" {
return nil, varTypeError{vn.String(), sig}
}
return variantNode{n}, nil
}
type dictEntry struct {
key, val varNode
}
type dictNode struct {
kset, vset sigSet
children []dictEntry
val interface{}
}
func (n dictNode) Infer() (Signature, error) {
for _, v := range n.children {
ksig, err := varInfer(v.key)
if err != nil {
continue
}
vsig, err := varInfer(v.val)
if err != nil {
continue
}
return Signature{"a{" + ksig.str + vsig.str + "}"}, nil
}
return Signature{}, fmt.Errorf("can't infer type for %q", n.String())
}
func (n dictNode) String() string {
s := "{"
for i, v := range n.children {
s += v.key.String() + ": " + v.val.String()
if i != len(n.children)-1 {
s += ", "
}
}
return s + "}"
}
func (n dictNode) Sigs() sigSet {
r := sigSet{}
for k := range n.kset {
for v := range n.vset {
sig := "a{" + k.str + v.str + "}"
r[Signature{sig}] = true
}
}
return r
}
func (n dictNode) Value(sig Signature) (interface{}, error) {
set := n.Sigs()
if set.Empty() {
// no type information -> empty dict
return reflect.MakeMap(typeFor(sig.str)).Interface(), nil
}
if !set[sig] {
return nil, varTypeError{n.String(), sig}
}
m := reflect.MakeMap(typeFor(sig.str))
ksig := Signature{sig.str[2:3]}
vsig := Signature{sig.str[3 : len(sig.str)-1]}
for _, v := range n.children {
kv, err := v.key.Value(ksig)
if err != nil {
return nil, err
}
vv, err := v.val.Value(vsig)
if err != nil {
return nil, err
}
m.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv))
}
return m.Interface(), nil
}
func varMakeDictNode(p *varParser, sig Signature) (varNode, error) {
var n dictNode
if sig.str != "" {
if len(sig.str) < 5 {
return nil, fmt.Errorf("invalid signature %q for dict type", sig)
}
ksig := Signature{string(sig.str[2])}
vsig := Signature{sig.str[3 : len(sig.str)-1]}
n.kset = sigSet{ksig: true}
n.vset = sigSet{vsig: true}
}
if t := p.next(); t.typ == tokDictEnd {
return n, nil
} else {
p.backup()
}
Loop:
for {
t := p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
}
p.backup()
kn, err := varMakeNode(p)
if err != nil {
return nil, err
}
if kset := kn.Sigs(); !kset.Empty() {
if n.kset.Empty() {
n.kset = kset
} else {
n.kset = kset.Intersect(n.kset)
if n.kset.Empty() {
return nil, fmt.Errorf("can't parse %q with given type information", kn.String())
}
}
}
t = p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
case tokColon:
default:
return nil, fmt.Errorf("unexpected %q", t.val)
}
t = p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
}
p.backup()
vn, err := varMakeNode(p)
if err != nil {
return nil, err
}
if vset := vn.Sigs(); !vset.Empty() {
if n.vset.Empty() {
n.vset = vset
} else {
n.vset = n.vset.Intersect(vset)
if n.vset.Empty() {
return nil, fmt.Errorf("can't parse %q with given type information", vn.String())
}
}
}
n.children = append(n.children, dictEntry{kn, vn})
t = p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
case tokDictEnd:
break Loop
case tokComma:
continue
default:
return nil, fmt.Errorf("unexpected %q", t.val)
}
}
return n, nil
}
type byteStringNode []byte
var byteStringSet = sigSet{
Signature{"ay"}: true,
}
func (byteStringNode) Infer() (Signature, error) {
return Signature{"ay"}, nil
}
func (b byteStringNode) String() string {
return string(b)
}
func (b byteStringNode) Sigs() sigSet {
return byteStringSet
}
func (b byteStringNode) Value(sig Signature) (interface{}, error) {
if sig.str != "ay" {
return nil, varTypeError{b.String(), sig}
}
return []byte(b), nil
}
func varParseByteString(s string) ([]byte, error) {
// quotes and b at start are guaranteed to be there
b := make([]byte, 0, 1)
s = s[2 : len(s)-1]
for len(s) != 0 {
c := s[0]
s = s[1:]
if c != '\\' {
b = append(b, c)
continue
}
c = s[0]
s = s[1:]
switch c {
case 'a':
b = append(b, 0x7)
case 'b':
b = append(b, 0x8)
case 'f':
b = append(b, 0xc)
case 'n':
b = append(b, '\n')
case 'r':
b = append(b, '\r')
case 't':
b = append(b, '\t')
case 'x':
if len(s) < 2 {
return nil, errors.New("short escape")
}
n, err := strconv.ParseUint(s[:2], 16, 8)
if err != nil {
return nil, err
}
b = append(b, byte(n))
s = s[2:]
case '0':
if len(s) < 3 {
return nil, errors.New("short escape")
}
n, err := strconv.ParseUint(s[:3], 8, 8)
if err != nil {
return nil, err
}
b = append(b, byte(n))
s = s[3:]
default:
b = append(b, c)
}
}
return append(b, 0), nil
}
func varInfer(n varNode) (Signature, error) {
if sig, ok := n.Sigs().Single(); ok {
return sig, nil
}
return n.Infer()
}

View File

@ -1,78 +0,0 @@
package dbus
import "reflect"
import "testing"
var variantFormatTests = []struct {
v interface{}
s string
}{
{int32(1), `1`},
{"foo", `"foo"`},
{ObjectPath("/org/foo"), `@o "/org/foo"`},
{Signature{"i"}, `@g "i"`},
{[]byte{}, `@ay []`},
{[]int32{1, 2}, `[1, 2]`},
{[]int64{1, 2}, `@ax [1, 2]`},
{[][]int32{{3, 4}, {5, 6}}, `[[3, 4], [5, 6]]`},
{[]Variant{MakeVariant(int32(1)), MakeVariant(1.0)}, `[<1>, <@d 1>]`},
{map[string]int32{"one": 1, "two": 2}, `{"one": 1, "two": 2}`},
{map[int32]ObjectPath{1: "/org/foo"}, `@a{io} {1: "/org/foo"}`},
{map[string]Variant{}, `@a{sv} {}`},
}
func TestFormatVariant(t *testing.T) {
for i, v := range variantFormatTests {
if s := MakeVariant(v.v).String(); s != v.s {
t.Errorf("test %d: got %q, wanted %q", i+1, s, v.s)
}
}
}
var variantParseTests = []struct {
s string
v interface{}
}{
{"1", int32(1)},
{"true", true},
{"false", false},
{"1.0", float64(1.0)},
{"0x10", int32(16)},
{"1e1", float64(10)},
{`"foo"`, "foo"},
{`"\a\b\f\n\r\t"`, "\x07\x08\x0c\n\r\t"},
{`"\u00e4\U0001f603"`, "\u00e4\U0001f603"},
{"[1]", []int32{1}},
{"[1, 2, 3]", []int32{1, 2, 3}},
{"@ai []", []int32{}},
{"[1, 5.0]", []float64{1, 5.0}},
{"[[1, 2], [3, 4.0]]", [][]float64{{1, 2}, {3, 4}}},
{`[@o "/org/foo", "/org/bar"]`, []ObjectPath{"/org/foo", "/org/bar"}},
{"<1>", MakeVariant(int32(1))},
{"[<1>, <2.0>]", []Variant{MakeVariant(int32(1)), MakeVariant(2.0)}},
{`[[], [""]]`, [][]string{{}, {""}}},
{`@a{ss} {}`, map[string]string{}},
{`{"foo": 1}`, map[string]int32{"foo": 1}},
{`[{}, {"foo": "bar"}]`, []map[string]string{{}, {"foo": "bar"}}},
{`{"a": <1>, "b": <"foo">}`,
map[string]Variant{"a": MakeVariant(int32(1)), "b": MakeVariant("foo")}},
{`b''`, []byte{0}},
{`b"abc"`, []byte{'a', 'b', 'c', 0}},
{`b"\x01\0002\a\b\f\n\r\t"`, []byte{1, 2, 0x7, 0x8, 0xc, '\n', '\r', '\t', 0}},
{`[[0], b""]`, [][]byte{{0}, {0}}},
{"int16 0", int16(0)},
{"byte 0", byte(0)},
}
func TestParseVariant(t *testing.T) {
for i, v := range variantParseTests {
nv, err := ParseVariant(v.s, Signature{})
if err != nil {
t.Errorf("test %d: parsing failed: %s", i+1, err)
continue
}
if !reflect.DeepEqual(nv.value, v.v) {
t.Errorf("test %d: got %q, wanted %q", i+1, nv, v.v)
}
}
}

View File

@ -1,27 +0,0 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,63 +0,0 @@
GoSerial
========
A simple go package to allow you to read and write from the
serial port as a stream of bytes.
Details
-------
It aims to have the same API on all platforms, including windows. As
an added bonus, the windows package does not use cgo, so you can cross
compile for windows from another platform. Unfortunately goinstall
does not currently let you cross compile so you will have to do it
manually:
GOOS=windows make clean install
Currently there is very little in the way of configurability. You can
set the baud rate. Then you can Read(), Write(), or Close() the
connection. Read() will block until at least one byte is returned.
Write is the same. There is currently no exposed way to set the
timeouts, though patches are welcome.
Currently all ports are opened with 8 data bits, 1 stop bit, no
parity, no hardware flow control, and no software flow control. This
works fine for many real devices and many faux serial devices
including usb-to-serial converters and bluetooth serial ports.
You may Read() and Write() simulantiously on the same connection (from
different goroutines).
Usage
-----
```go
package main
import (
"github.com/tarm/goserial"
"log"
)
func main() {
c := &serial.Config{Name: "COM45", Baud: 115200}
s, err := serial.OpenPort(c)
if err != nil {
log.Fatal(err)
}
n, err := s.Write([]byte("test"))
if err != nil {
log.Fatal(err)
}
buf := make([]byte, 128)
n, err = s.Read(buf)
if err != nil {
log.Fatal(err)
}
log.Print("%q", buf[:n])
}
```
Possible Future Work
--------------------
- better tests (loopback etc)

View File

@ -1,61 +0,0 @@
package serial
import (
"testing"
"time"
)
func TestConnection(t *testing.T) {
c0 := &Config{Name: "/dev/ttyUSB0", Baud: 115200}
c1 := &Config{Name: "/dev/ttyUSB1", Baud: 115200}
s1, err := OpenPort(c0)
if err != nil {
t.Fatal(err)
}
s2, err := OpenPort(c1)
if err != nil {
t.Fatal(err)
}
ch := make(chan int, 1)
go func() {
buf := make([]byte, 128)
var readCount int
for {
n, err := s2.Read(buf)
if err != nil {
t.Fatal(err)
}
readCount++
t.Logf("Read %v %v bytes: % 02x %s", readCount, n, buf[:n], buf[:n])
select {
case <-ch:
ch <- readCount
close(ch)
default:
}
}
}()
if _, err = s1.Write([]byte("hello")); err != nil {
t.Fatal(err)
}
if _, err = s1.Write([]byte(" ")); err != nil {
t.Fatal(err)
}
time.Sleep(time.Second)
if _, err = s1.Write([]byte("world")); err != nil {
t.Fatal(err)
}
time.Sleep(time.Second / 10)
ch <- 0
s1.Write([]byte(" ")) // We could be blocked in the read without this
c := <-ch
exp := 5
if c >= exp {
t.Fatalf("Expected less than %v read, got %v", exp, c)
}
}

View File

@ -1,99 +0,0 @@
/*
Goserial is a simple go package to allow you to read and write from
the serial port as a stream of bytes.
It aims to have the same API on all platforms, including windows. As
an added bonus, the windows package does not use cgo, so you can cross
compile for windows from another platform. Unfortunately goinstall
does not currently let you cross compile so you will have to do it
manually:
GOOS=windows make clean install
Currently there is very little in the way of configurability. You can
set the baud rate. Then you can Read(), Write(), or Close() the
connection. Read() will block until at least one byte is returned.
Write is the same. There is currently no exposed way to set the
timeouts, though patches are welcome.
Currently all ports are opened with 8 data bits, 1 stop bit, no
parity, no hardware flow control, and no software flow control. This
works fine for many real devices and many faux serial devices
including usb-to-serial converters and bluetooth serial ports.
You may Read() and Write() simulantiously on the same connection (from
different goroutines).
Example usage:
package main
import (
"github.com/tarm/goserial"
"log"
)
func main() {
c := &serial.Config{Name: "COM5", Baud: 115200}
s, err := serial.OpenPort(c)
if err != nil {
log.Fatal(err)
}
n, err := s.Write([]byte("test"))
if err != nil {
log.Fatal(err)
}
buf := make([]byte, 128)
n, err = s.Read(buf)
if err != nil {
log.Fatal(err)
}
log.Print("%q", buf[:n])
}
*/
package serial
import "io"
// Config contains the information needed to open a serial port.
//
// Currently few options are implemented, but more may be added in the
// future (patches welcome), so it is recommended that you create a
// new config addressing the fields by name rather than by order.
//
// For example:
//
// c0 := &serial.Config{Name: "COM45", Baud: 115200}
// or
// c1 := new(serial.Config)
// c1.Name = "/dev/tty.usbserial"
// c1.Baud = 115200
//
type Config struct {
Name string
Baud int
// Size int // 0 get translated to 8
// Parity SomeNewTypeToGetCorrectDefaultOf_None
// StopBits SomeNewTypeToGetCorrectDefaultOf_1
// RTSFlowControl bool
// DTRFlowControl bool
// XONFlowControl bool
// CRLFTranslate bool
// TimeoutStuff int
}
// OpenPort opens a serial port with the specified configuration
func OpenPort(c *Config) (io.ReadWriteCloser, error) {
return openPort(c.Name, c.Baud)
}
// func Flush()
// func SendBreak()
// func RegisterBreakHandler(func())

View File

@ -1,90 +0,0 @@
// +build linux,!cgo
package serial
import (
"io"
"os"
"syscall"
"unsafe"
)
func openPort(name string, baud int) (rwc io.ReadWriteCloser, err error) {
var bauds = map[int]uint32{
50: syscall.B50,
75: syscall.B75,
110: syscall.B110,
134: syscall.B134,
150: syscall.B150,
200: syscall.B200,
300: syscall.B300,
600: syscall.B600,
1200: syscall.B1200,
1800: syscall.B1800,
2400: syscall.B2400,
4800: syscall.B4800,
9600: syscall.B9600,
19200: syscall.B19200,
38400: syscall.B38400,
57600: syscall.B57600,
115200: syscall.B115200,
230400: syscall.B230400,
460800: syscall.B460800,
500000: syscall.B500000,
576000: syscall.B576000,
921600: syscall.B921600,
1000000: syscall.B1000000,
1152000: syscall.B1152000,
1500000: syscall.B1500000,
2000000: syscall.B2000000,
2500000: syscall.B2500000,
3000000: syscall.B3000000,
3500000: syscall.B3500000,
4000000: syscall.B4000000,
}
rate := bauds[baud]
if rate == 0 {
return
}
f, err := os.OpenFile(name, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK, 0666)
if err != nil {
return nil, err
}
defer func() {
if err != nil && f != nil {
f.Close()
}
}()
fd := f.Fd()
t := syscall.Termios{
Iflag: syscall.IGNPAR,
Cflag: syscall.CS8 | syscall.CREAD | syscall.CLOCAL | rate,
Cc: [32]uint8{syscall.VMIN: 1},
Ispeed: rate,
Ospeed: rate,
}
if _, _, errno := syscall.Syscall6(
syscall.SYS_IOCTL,
uintptr(fd),
uintptr(syscall.TCSETS),
uintptr(unsafe.Pointer(&t)),
0,
0,
0,
); errno != 0 {
return nil, errno
}
if err = syscall.SetNonblock(int(fd), false); err != nil {
return
}
return f, nil
}

View File

@ -1,107 +0,0 @@
// +build !windows,cgo
package serial
// #include <termios.h>
// #include <unistd.h>
import "C"
// TODO: Maybe change to using syscall package + ioctl instead of cgo
import (
"errors"
"fmt"
"io"
"os"
"syscall"
//"unsafe"
)
func openPort(name string, baud int) (rwc io.ReadWriteCloser, err error) {
f, err := os.OpenFile(name, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NONBLOCK, 0666)
if err != nil {
return
}
fd := C.int(f.Fd())
if C.isatty(fd) != 1 {
f.Close()
return nil, errors.New("File is not a tty")
}
var st C.struct_termios
_, err = C.tcgetattr(fd, &st)
if err != nil {
f.Close()
return nil, err
}
var speed C.speed_t
switch baud {
case 115200:
speed = C.B115200
case 57600:
speed = C.B57600
case 38400:
speed = C.B38400
case 19200:
speed = C.B19200
case 9600:
speed = C.B9600
case 4800:
speed = C.B4800
case 2400:
speed = C.B2400
default:
f.Close()
return nil, fmt.Errorf("Unknown baud rate %v", baud)
}
_, err = C.cfsetispeed(&st, speed)
if err != nil {
f.Close()
return nil, err
}
_, err = C.cfsetospeed(&st, speed)
if err != nil {
f.Close()
return nil, err
}
// Select local mode
st.c_cflag |= (C.CLOCAL | C.CREAD)
// Select raw mode
st.c_lflag &= ^C.tcflag_t(C.ICANON | C.ECHO | C.ECHOE | C.ISIG)
st.c_oflag &= ^C.tcflag_t(C.OPOST)
_, err = C.tcsetattr(fd, C.TCSANOW, &st)
if err != nil {
f.Close()
return nil, err
}
//fmt.Println("Tweaking", name)
r1, _, e := syscall.Syscall(syscall.SYS_FCNTL,
uintptr(f.Fd()),
uintptr(syscall.F_SETFL),
uintptr(0))
if e != 0 || r1 != 0 {
s := fmt.Sprint("Clearing NONBLOCK syscall error:", e, r1)
f.Close()
return nil, errors.New(s)
}
/*
r1, _, e = syscall.Syscall(syscall.SYS_IOCTL,
uintptr(f.Fd()),
uintptr(0x80045402), // IOSSIOSPEED
uintptr(unsafe.Pointer(&baud)));
if e != 0 || r1 != 0 {
s := fmt.Sprint("Baudrate syscall error:", e, r1)
f.Close()
return nil, os.NewError(s)
}
*/
return f, nil
}

View File

@ -1,263 +0,0 @@
// +build windows
package serial
import (
"fmt"
"io"
"os"
"sync"
"syscall"
"unsafe"
)
type serialPort struct {
f *os.File
fd syscall.Handle
rl sync.Mutex
wl sync.Mutex
ro *syscall.Overlapped
wo *syscall.Overlapped
}
type structDCB struct {
DCBlength, BaudRate uint32
flags [4]byte
wReserved, XonLim, XoffLim uint16
ByteSize, Parity, StopBits byte
XonChar, XoffChar, ErrorChar, EofChar, EvtChar byte
wReserved1 uint16
}
type structTimeouts struct {
ReadIntervalTimeout uint32
ReadTotalTimeoutMultiplier uint32
ReadTotalTimeoutConstant uint32
WriteTotalTimeoutMultiplier uint32
WriteTotalTimeoutConstant uint32
}
func openPort(name string, baud int) (rwc io.ReadWriteCloser, err error) {
if len(name) > 0 && name[0] != '\\' {
name = "\\\\.\\" + name
}
h, err := syscall.CreateFile(syscall.StringToUTF16Ptr(name),
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
0,
nil,
syscall.OPEN_EXISTING,
syscall.FILE_ATTRIBUTE_NORMAL|syscall.FILE_FLAG_OVERLAPPED,
0)
if err != nil {
return nil, err
}
f := os.NewFile(uintptr(h), name)
defer func() {
if err != nil {
f.Close()
}
}()
if err = setCommState(h, baud); err != nil {
return
}
if err = setupComm(h, 64, 64); err != nil {
return
}
if err = setCommTimeouts(h); err != nil {
return
}
if err = setCommMask(h); err != nil {
return
}
ro, err := newOverlapped()
if err != nil {
return
}
wo, err := newOverlapped()
if err != nil {
return
}
port := new(serialPort)
port.f = f
port.fd = h
port.ro = ro
port.wo = wo
return port, nil
}
func (p *serialPort) Close() error {
return p.f.Close()
}
func (p *serialPort) Write(buf []byte) (int, error) {
p.wl.Lock()
defer p.wl.Unlock()
if err := resetEvent(p.wo.HEvent); err != nil {
return 0, err
}
var n uint32
err := syscall.WriteFile(p.fd, buf, &n, p.wo)
if err != nil && err != syscall.ERROR_IO_PENDING {
return int(n), err
}
return getOverlappedResult(p.fd, p.wo)
}
func (p *serialPort) Read(buf []byte) (int, error) {
if p == nil || p.f == nil {
return 0, fmt.Errorf("Invalid port on read %v %v", p, p.f)
}
p.rl.Lock()
defer p.rl.Unlock()
if err := resetEvent(p.ro.HEvent); err != nil {
return 0, err
}
var done uint32
err := syscall.ReadFile(p.fd, buf, &done, p.ro)
if err != nil && err != syscall.ERROR_IO_PENDING {
return int(done), err
}
return getOverlappedResult(p.fd, p.ro)
}
var (
nSetCommState,
nSetCommTimeouts,
nSetCommMask,
nSetupComm,
nGetOverlappedResult,
nCreateEvent,
nResetEvent uintptr
)
func init() {
k32, err := syscall.LoadLibrary("kernel32.dll")
if err != nil {
panic("LoadLibrary " + err.Error())
}
defer syscall.FreeLibrary(k32)
nSetCommState = getProcAddr(k32, "SetCommState")
nSetCommTimeouts = getProcAddr(k32, "SetCommTimeouts")
nSetCommMask = getProcAddr(k32, "SetCommMask")
nSetupComm = getProcAddr(k32, "SetupComm")
nGetOverlappedResult = getProcAddr(k32, "GetOverlappedResult")
nCreateEvent = getProcAddr(k32, "CreateEventW")
nResetEvent = getProcAddr(k32, "ResetEvent")
}
func getProcAddr(lib syscall.Handle, name string) uintptr {
addr, err := syscall.GetProcAddress(lib, name)
if err != nil {
panic(name + " " + err.Error())
}
return addr
}
func setCommState(h syscall.Handle, baud int) error {
var params structDCB
params.DCBlength = uint32(unsafe.Sizeof(params))
params.flags[0] = 0x01 // fBinary
params.flags[0] |= 0x10 // Assert DSR
params.BaudRate = uint32(baud)
params.ByteSize = 8
r, _, err := syscall.Syscall(nSetCommState, 2, uintptr(h), uintptr(unsafe.Pointer(&params)), 0)
if r == 0 {
return err
}
return nil
}
func setCommTimeouts(h syscall.Handle) error {
var timeouts structTimeouts
const MAXDWORD = 1<<32 - 1
timeouts.ReadIntervalTimeout = MAXDWORD
timeouts.ReadTotalTimeoutMultiplier = MAXDWORD
timeouts.ReadTotalTimeoutConstant = MAXDWORD - 1
/* From http://msdn.microsoft.com/en-us/library/aa363190(v=VS.85).aspx
For blocking I/O see below:
Remarks:
If an application sets ReadIntervalTimeout and
ReadTotalTimeoutMultiplier to MAXDWORD and sets
ReadTotalTimeoutConstant to a value greater than zero and
less than MAXDWORD, one of the following occurs when the
ReadFile function is called:
If there are any bytes in the input buffer, ReadFile returns
immediately with the bytes in the buffer.
If there are no bytes in the input buffer, ReadFile waits
until a byte arrives and then returns immediately.
If no bytes arrive within the time specified by
ReadTotalTimeoutConstant, ReadFile times out.
*/
r, _, err := syscall.Syscall(nSetCommTimeouts, 2, uintptr(h), uintptr(unsafe.Pointer(&timeouts)), 0)
if r == 0 {
return err
}
return nil
}
func setupComm(h syscall.Handle, in, out int) error {
r, _, err := syscall.Syscall(nSetupComm, 3, uintptr(h), uintptr(in), uintptr(out))
if r == 0 {
return err
}
return nil
}
func setCommMask(h syscall.Handle) error {
const EV_RXCHAR = 0x0001
r, _, err := syscall.Syscall(nSetCommMask, 2, uintptr(h), EV_RXCHAR, 0)
if r == 0 {
return err
}
return nil
}
func resetEvent(h syscall.Handle) error {
r, _, err := syscall.Syscall(nResetEvent, 1, uintptr(h), 0, 0)
if r == 0 {
return err
}
return nil
}
func newOverlapped() (*syscall.Overlapped, error) {
var overlapped syscall.Overlapped
r, _, err := syscall.Syscall6(nCreateEvent, 4, 0, 1, 0, 0, 0, 0)
if r == 0 {
return nil, err
}
overlapped.HEvent = syscall.Handle(r)
return &overlapped, nil
}
func getOverlappedResult(h syscall.Handle, overlapped *syscall.Overlapped) (int, error) {
var n int
r, _, err := syscall.Syscall6(nGetOverlappedResult, 4,
uintptr(h),
uintptr(unsafe.Pointer(overlapped)),
uintptr(unsafe.Pointer(&n)), 1, 0, 0)
if r == 0 {
return n, err
}
return n, nil
}

View File

@ -1,147 +0,0 @@
package yaml
import (
"math"
"strconv"
"strings"
)
// TODO: merge, timestamps, base 60 floats, omap.
type resolveMapItem struct {
value interface{}
tag string
}
var resolveTable = make([]byte, 256)
var resolveMap = make(map[string]resolveMapItem)
func init() {
t := resolveTable
t[int('+')] = 'S' // Sign
t[int('-')] = 'S'
for _, c := range "0123456789" {
t[int(c)] = 'D' // Digit
}
for _, c := range "yYnNtTfFoO~" {
t[int(c)] = 'M' // In map
}
t[int('.')] = '.' // Float (potentially in map)
var resolveMapList = []struct {
v interface{}
tag string
l []string
}{
{true, "!!bool", []string{"y", "Y", "yes", "Yes", "YES"}},
{true, "!!bool", []string{"true", "True", "TRUE"}},
{true, "!!bool", []string{"on", "On", "ON"}},
{false, "!!bool", []string{"n", "N", "no", "No", "NO"}},
{false, "!!bool", []string{"false", "False", "FALSE"}},
{false, "!!bool", []string{"off", "Off", "OFF"}},
{nil, "!!null", []string{"~", "null", "Null", "NULL"}},
{math.NaN(), "!!float", []string{".nan", ".NaN", ".NAN"}},
{math.Inf(+1), "!!float", []string{".inf", ".Inf", ".INF"}},
{math.Inf(+1), "!!float", []string{"+.inf", "+.Inf", "+.INF"}},
{math.Inf(-1), "!!float", []string{"-.inf", "-.Inf", "-.INF"}},
{"<<", "!!merge", []string{"<<"}},
}
m := resolveMap
for _, item := range resolveMapList {
for _, s := range item.l {
m[s] = resolveMapItem{item.v, item.tag}
}
}
}
const longTagPrefix = "tag:yaml.org,2002:"
func shortTag(tag string) string {
if strings.HasPrefix(tag, longTagPrefix) {
return "!!" + tag[len(longTagPrefix):]
}
return tag
}
func resolvableTag(tag string) bool {
switch tag {
case "", "!!str", "!!bool", "!!int", "!!float", "!!null":
return true
}
return false
}
func resolve(tag string, in string) (rtag string, out interface{}) {
tag = shortTag(tag)
if !resolvableTag(tag) {
return tag, in
}
defer func() {
if tag != "" && tag != rtag {
panic("Can't decode " + rtag + " '" + in + "' as a " + tag)
}
}()
if in == "" {
return "!!null", nil
}
c := resolveTable[in[0]]
if c == 0 {
// It's a string for sure. Nothing to do.
return "!!str", in
}
// Handle things we can lookup in a map.
if item, ok := resolveMap[in]; ok {
return item.tag, item.value
}
switch c {
case 'M':
// We've already checked the map above.
case '.':
// Not in the map, so maybe a normal float.
floatv, err := strconv.ParseFloat(in, 64)
if err == nil {
return "!!float", floatv
}
// XXX Handle base 60 floats here (WTF!)
case 'D', 'S':
// Int, float, or timestamp.
plain := strings.Replace(in, "_", "", -1)
intv, err := strconv.ParseInt(plain, 0, 64)
if err == nil {
if intv == int64(int(intv)) {
return "!!int", int(intv)
} else {
return "!!int", intv
}
}
floatv, err := strconv.ParseFloat(plain, 64)
if err == nil {
return "!!float", floatv
}
if strings.HasPrefix(plain, "0b") {
intv, err := strconv.ParseInt(plain[2:], 2, 64)
if err == nil {
return "!!int", int(intv)
}
} else if strings.HasPrefix(plain, "-0b") {
intv, err := strconv.ParseInt(plain[3:], 2, 64)
if err == nil {
return "!!int", -int(intv)
}
}
// XXX Handle timestamps here.
default:
panic("resolveTable item not yet handled: " +
string([]byte{c}) + " (with " + in + ")")
}
return "!!str", in
}

View File

@ -77,3 +77,10 @@ coreos:
addr: 203.0.113.29:4001 addr: 203.0.113.29:4001
peer-addr: 192.0.2.13:7001 peer-addr: 192.0.2.13:7001
``` ```
## Bugs
Please use the [CoreOS issue tracker][bugs] to report all bugs, issues, and feature requests.
[bugs]: https://github.com/coreos/bugs/issues/new?labels=component/cloud-init

35
build
View File

@ -1,14 +1,37 @@
#!/bin/bash -e #!/bin/bash -x
ORG_PATH="github.com/coreos" ORG_PATH="github.com/coreos"
REPO_PATH="${ORG_PATH}/coreos-cloudinit" REPO_PATH="${ORG_PATH}/coreos-cloudinit"
VERSION=$(git describe --tags)
GLDFLAGS="-X main.version=${VERSION}"
if [ ! -h gopath/src/${REPO_PATH} ]; then rm -rf bin tmp
mkdir -p gopath/src/${ORG_PATH}
ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255
fi
export GO15VENDOREXPERIMENT=1
export GOBIN=${PWD}/bin export GOBIN=${PWD}/bin
export GOPATH=${PWD}/gopath export GOPATH=${PWD}/gopath
mkdir -p $GOBIN
mkdir -p $GOPATH
mkdir -p bin tmp
go build -o bin/coreos-cloudinit ${REPO_PATH} which go 2>/dev/null
if [ "x$?" != "x0" ]; then
export GOROOT=$(pwd)/goroot
export PATH=$GOROOT/bin:$PATH
mkdir -p $GOROOT
wget https://storage.googleapis.com/golang/go1.5.linux-amd64.tar.gz -O tmp/go.tar.gz
tar --strip-components=1 -C $GOROOT -xf tmp/go.tar.gz
fi
if [ ! -h $GOPATH/src/${REPO_PATH} ]; then
mkdir -p $GOPATH/src/${ORG_PATH}
ln -s ../../../.. $GOPATH/src/${REPO_PATH} || echo "exit 255"
fi
set -e
for os in linux freebsd netbsd openbsd windows; do
GOOS=${os} go build -x -ldflags "${GLDFLAGS}" -tags netgo -o bin/cloudinit-${os}-x86_64 ${REPO_PATH}
GOOS=${os} GOARCH=386 go build -x -ldflags "${GLDFLAGS}" -tags netgo -o bin/cloudinit-${os}-x86_32 ${REPO_PATH}
done

164
config/config.go Normal file
View File

@ -0,0 +1,164 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
import (
"fmt"
"reflect"
"regexp"
"strings"
"unicode"
yaml "gopkg.in/yaml.v2"
)
// CloudConfig encapsulates the entire cloud-config configuration file and maps
// directly to YAML. Fields that cannot be set in the cloud-config (fields
// used for internal use) have the YAML tag '-' so that they aren't marshalled.
type CloudConfig struct {
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
SSHFingerprints bool `yaml:"no_ssh_fingerprints"`
Debug bool `yaml:"debug"`
RunCMD []string `yaml:"runcmd"`
NetworkConfigPath string `yaml:"-"`
NetworkConfig string `yaml:"-"`
Bootstrap string `yaml:"-"`
SystemInfo SystemInfo `yaml:"system_info"`
DisableRoot bool `yaml:"disable_root"`
SSHPasswdAuth bool `yaml:"ssh_pwauth"`
ResizeRootfs bool `yaml:"resize_rootfs"`
CoreOS CoreOS `yaml:"coreos"`
WriteFiles []File `yaml:"write_files"`
Hostname string `yaml:"hostname"`
Users []User `yaml:"users"`
ManageEtcHosts EtcHosts `yaml:"manage_etc_hosts"`
}
type CoreOS struct {
Etcd Etcd `yaml:"etcd"`
Etcd2 Etcd2 `yaml:"etcd2"`
Flannel Flannel `yaml:"flannel"`
Fleet Fleet `yaml:"fleet"`
Locksmith Locksmith `yaml:"locksmith"`
OEM OEM `yaml:"oem"`
Update Update `yaml:"update"`
Units []Unit `yaml:"units"`
}
func IsCloudConfig(userdata string) bool {
header := strings.SplitN(userdata, "\n", 2)[0]
// Trim trailing whitespaces
header = strings.TrimRightFunc(header, unicode.IsSpace)
return (header == "#cloud-config")
}
// NewCloudConfig instantiates a new CloudConfig from the given contents (a
// string of YAML), returning any error encountered. It will ignore unknown
// fields but log encountering them.
func NewCloudConfig(contents string) (*CloudConfig, error) {
// yaml.UnmarshalMappingKeyTransform = func(nameIn string) (nameOut string) {
// return strings.Replace(nameIn, "-", "_", -1)
// }
var cfg CloudConfig
err := yaml.Unmarshal([]byte(contents), &cfg)
return &cfg, err
}
func (cc CloudConfig) String() string {
bytes, err := yaml.Marshal(cc)
if err != nil {
return ""
}
stringified := string(bytes)
stringified = fmt.Sprintf("#cloud-config\n%s", stringified)
return stringified
}
// IsZero returns whether or not the parameter is the zero value for its type.
// If the parameter is a struct, only the exported fields are considered.
func IsZero(c interface{}) bool {
return isZero(reflect.ValueOf(c))
}
type ErrorValid struct {
Value string
Valid string
Field string
}
func (e ErrorValid) Error() string {
return fmt.Sprintf("invalid value %q for option %q (valid options: %q)", e.Value, e.Field, e.Valid)
}
// AssertStructValid checks the fields in the structure and makes sure that
// they contain valid values as specified by the 'valid' flag. Empty fields are
// implicitly valid.
func AssertStructValid(c interface{}) error {
ct := reflect.TypeOf(c)
cv := reflect.ValueOf(c)
for i := 0; i < ct.NumField(); i++ {
ft := ct.Field(i)
if !isFieldExported(ft) {
continue
}
if err := AssertValid(cv.Field(i), ft.Tag.Get("valid")); err != nil {
err.Field = ft.Name
return err
}
}
return nil
}
// AssertValid checks to make sure that the given value is in the list of
// valid values. Zero values are implicitly valid.
func AssertValid(value reflect.Value, valid string) *ErrorValid {
if valid == "" || isZero(value) {
return nil
}
vs := fmt.Sprintf("%v", value.Interface())
if m, _ := regexp.MatchString(valid, vs); m {
return nil
}
return &ErrorValid{
Value: vs,
Valid: valid,
}
}
func isZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Struct:
vt := v.Type()
for i := 0; i < v.NumField(); i++ {
if isFieldExported(vt.Field(i)) && !isZero(v.Field(i)) {
return false
}
}
return true
default:
return v.Interface() == reflect.Zero(v.Type()).Interface()
}
}
func isFieldExported(f reflect.StructField) bool {
return f.PkgPath == ""
}

503
config/config_test.go Normal file
View File

@ -0,0 +1,503 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
import (
"reflect"
"regexp"
"strings"
"testing"
)
func TestNewCloudConfig(t *testing.T) {
tests := []struct {
contents string
config CloudConfig
}{
{},
{
contents: "#cloud-config\nwrite_files:\n - path: underscore",
config: CloudConfig{WriteFiles: []File{File{Path: "underscore"}}},
},
{
contents: "#cloud-config\nwrite-files:\n - path: hyphen",
config: CloudConfig{WriteFiles: []File{File{Path: "hyphen"}}},
},
{
contents: "#cloud-config\ncoreos:\n update:\n reboot-strategy: off",
config: CloudConfig{CoreOS: CoreOS{Update: Update{RebootStrategy: "off"}}},
},
{
contents: "#cloud-config\ncoreos:\n update:\n reboot-strategy: false",
config: CloudConfig{CoreOS: CoreOS{Update: Update{RebootStrategy: "false"}}},
},
{
contents: "#cloud-config\nwrite_files:\n - permissions: 0744",
config: CloudConfig{WriteFiles: []File{File{RawFilePermissions: "0744"}}},
},
{
contents: "#cloud-config\nwrite_files:\n - permissions: 744",
config: CloudConfig{WriteFiles: []File{File{RawFilePermissions: "744"}}},
},
{
contents: "#cloud-config\nwrite_files:\n - permissions: '0744'",
config: CloudConfig{WriteFiles: []File{File{RawFilePermissions: "0744"}}},
},
{
contents: "#cloud-config\nwrite_files:\n - permissions: '744'",
config: CloudConfig{WriteFiles: []File{File{RawFilePermissions: "744"}}},
},
}
for i, tt := range tests {
config, err := NewCloudConfig(tt.contents)
if err != nil {
t.Errorf("bad error (test case #%d): want %v, got %s", i, nil, err)
}
if !reflect.DeepEqual(&tt.config, config) {
t.Errorf("bad config (test case #%d): want %#v, got %#v", i, tt.config, config)
}
}
}
func TestIsZero(t *testing.T) {
tests := []struct {
c interface{}
empty bool
}{
{struct{}{}, true},
{struct{ a, b string }{}, true},
{struct{ A, b string }{}, true},
{struct{ A, B string }{}, true},
{struct{ A string }{A: "hello"}, false},
{struct{ A int }{}, true},
{struct{ A int }{A: 1}, false},
}
for _, tt := range tests {
if empty := IsZero(tt.c); tt.empty != empty {
t.Errorf("bad result (%q): want %t, got %t", tt.c, tt.empty, empty)
}
}
}
func TestAssertStructValid(t *testing.T) {
tests := []struct {
c interface{}
err error
}{
{struct{}{}, nil},
{struct {
A, b string `valid:"^1|2$"`
}{}, nil},
{struct {
A, b string `valid:"^1|2$"`
}{A: "1", b: "2"}, nil},
{struct {
A, b string `valid:"^1|2$"`
}{A: "1", b: "hello"}, nil},
{struct {
A, b string `valid:"^1|2$"`
}{A: "hello", b: "2"}, &ErrorValid{Value: "hello", Field: "A", Valid: "^1|2$"}},
{struct {
A, b int `valid:"^1|2$"`
}{}, nil},
{struct {
A, b int `valid:"^1|2$"`
}{A: 1, b: 2}, nil},
{struct {
A, b int `valid:"^1|2$"`
}{A: 1, b: 9}, nil},
{struct {
A, b int `valid:"^1|2$"`
}{A: 9, b: 2}, &ErrorValid{Value: "9", Field: "A", Valid: "^1|2$"}},
}
for _, tt := range tests {
if err := AssertStructValid(tt.c); !reflect.DeepEqual(tt.err, err) {
t.Errorf("bad result (%q): want %q, got %q", tt.c, tt.err, err)
}
}
}
func TestConfigCompile(t *testing.T) {
tests := []interface{}{
Etcd{},
File{},
Flannel{},
Fleet{},
Locksmith{},
OEM{},
Unit{},
Update{},
}
for _, tt := range tests {
ttt := reflect.TypeOf(tt)
for i := 0; i < ttt.NumField(); i++ {
ft := ttt.Field(i)
if !isFieldExported(ft) {
continue
}
if _, err := regexp.Compile(ft.Tag.Get("valid")); err != nil {
t.Errorf("bad regexp(%s.%s): want %v, got %s", ttt.Name(), ft.Name, nil, err)
}
}
}
}
func TestCloudConfigUnknownKeys(t *testing.T) {
contents := `
coreos:
etcd:
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
coreos_unknown:
foo: "bar"
section_unknown:
dunno:
something
bare_unknown:
bar
write_files:
- content: fun
path: /var/party
file_unknown: nofun
users:
- name: fry
passwd: somehash
user_unknown: philip
hostname:
foo
`
cfg, err := NewCloudConfig(contents)
if err != nil {
t.Fatalf("error instantiating CloudConfig with unknown keys: %v", err)
}
if cfg.Hostname != "foo" {
t.Fatalf("hostname not correctly set when invalid keys are present")
}
if cfg.CoreOS.Etcd.Discovery != "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877" {
t.Fatalf("etcd section not correctly set when invalid keys are present")
}
if len(cfg.WriteFiles) < 1 || cfg.WriteFiles[0].Content != "fun" || cfg.WriteFiles[0].Path != "/var/party" {
t.Fatalf("write_files section not correctly set when invalid keys are present")
}
if len(cfg.Users) < 1 || cfg.Users[0].Name != "fry" || cfg.Users[0].PasswordHash != "somehash" {
t.Fatalf("users section not correctly set when invalid keys are present")
}
}
// Assert that the parsing of a cloud config file "generally works"
func TestCloudConfigEmpty(t *testing.T) {
cfg, err := NewCloudConfig("")
if err != nil {
t.Fatalf("Encountered unexpected error :%v", err)
}
keys := cfg.SSHAuthorizedKeys
if len(keys) != 0 {
t.Error("Parsed incorrect number of SSH keys")
}
if len(cfg.WriteFiles) != 0 {
t.Error("Expected zero WriteFiles")
}
if cfg.Hostname != "" {
t.Errorf("Expected hostname to be empty, got '%s'", cfg.Hostname)
}
}
// Assert that the parsing of a cloud config file "generally works"
func TestCloudConfig(t *testing.T) {
contents := `
coreos:
etcd:
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
update:
reboot_strategy: reboot
units:
- name: 50-eth0.network
runtime: yes
content: '[Match]
Name=eth47
[Network]
Address=10.209.171.177/19
'
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
ssh_authorized_keys:
- foobar
- foobaz
write_files:
- content: |
penny
elroy
path: /etc/dogepack.conf
permissions: '0644'
owner: root:dogepack
hostname: trontastic
`
cfg, err := NewCloudConfig(contents)
if err != nil {
t.Fatalf("Encountered unexpected error :%v", err)
}
keys := cfg.SSHAuthorizedKeys
if len(keys) != 2 {
t.Error("Parsed incorrect number of SSH keys")
} else if keys[0] != "foobar" {
t.Error("Expected first SSH key to be 'foobar'")
} else if keys[1] != "foobaz" {
t.Error("Expected first SSH key to be 'foobaz'")
}
if len(cfg.WriteFiles) != 1 {
t.Error("Failed to parse correct number of write_files")
} else {
wf := cfg.WriteFiles[0]
if wf.Content != "penny\nelroy\n" {
t.Errorf("WriteFile has incorrect contents '%s'", wf.Content)
}
if wf.Encoding != "" {
t.Errorf("WriteFile has incorrect encoding %s", wf.Encoding)
}
if wf.RawFilePermissions != "0644" {
t.Errorf("WriteFile has incorrect permissions %s", wf.RawFilePermissions)
}
if wf.Path != "/etc/dogepack.conf" {
t.Errorf("WriteFile has incorrect path %s", wf.Path)
}
if wf.Owner != "root:dogepack" {
t.Errorf("WriteFile has incorrect owner %s", wf.Owner)
}
}
if len(cfg.CoreOS.Units) != 1 {
t.Error("Failed to parse correct number of units")
} else {
u := cfg.CoreOS.Units[0]
expect := `[Match]
Name=eth47
[Network]
Address=10.209.171.177/19
`
if u.Content != expect {
t.Errorf("Unit has incorrect contents '%s'.\nExpected '%s'.", u.Content, expect)
}
if u.Runtime != true {
t.Errorf("Unit has incorrect runtime value")
}
if u.Name != "50-eth0.network" {
t.Errorf("Unit has incorrect name %s", u.Name)
}
}
if cfg.CoreOS.OEM.ID != "rackspace" {
t.Errorf("Failed parsing coreos.oem. Expected ID 'rackspace', got %q.", cfg.CoreOS.OEM.ID)
}
if cfg.Hostname != "trontastic" {
t.Errorf("Failed to parse hostname")
}
if cfg.CoreOS.Update.RebootStrategy != "reboot" {
t.Errorf("Failed to parse locksmith strategy")
}
}
// Assert that our interface conversion doesn't panic
func TestCloudConfigKeysNotList(t *testing.T) {
contents := `
ssh_authorized_keys:
- foo: bar
`
cfg, err := NewCloudConfig(contents)
if err != nil {
t.Fatalf("Encountered unexpected error: %v", err)
}
keys := cfg.SSHAuthorizedKeys
if len(keys) != 0 {
t.Error("Parsed incorrect number of SSH keys")
}
}
func TestCloudConfigSerializationHeader(t *testing.T) {
cfg, _ := NewCloudConfig("")
contents := cfg.String()
header := strings.SplitN(contents, "\n", 2)[0]
if header != "#cloud-config" {
t.Fatalf("Serialized config did not have expected header")
}
}
func TestCloudConfigUsers(t *testing.T) {
contents := `
users:
- name: elroy
passwd: somehash
ssh_authorized_keys:
- somekey
gecos: arbitrary comment
homedir: /home/place
no_create_home: yes
lock_passwd: false
primary_group: things
groups:
- ping
- pong
no_user_group: true
system: y
no_log_init: True
shell: /bin/sh
`
cfg, err := NewCloudConfig(contents)
if err != nil {
t.Fatalf("Encountered unexpected error: %v", err)
}
if len(cfg.Users) != 1 {
t.Fatalf("Parsed %d users, expected 1", len(cfg.Users))
}
user := cfg.Users[0]
if user.Name != "elroy" {
t.Errorf("User name is %q, expected 'elroy'", user.Name)
}
if user.PasswordHash != "somehash" {
t.Errorf("User passwd is %q, expected 'somehash'", user.PasswordHash)
}
if keys := user.SSHAuthorizedKeys; len(keys) != 1 {
t.Errorf("Parsed %d ssh keys, expected 1", len(keys))
} else {
key := user.SSHAuthorizedKeys[0]
if key != "somekey" {
t.Errorf("User SSH key is %q, expected 'somekey'", key)
}
}
if user.GECOS != "arbitrary comment" {
t.Errorf("Failed to parse gecos field, got %q", user.GECOS)
}
if user.Homedir != "/home/place" {
t.Errorf("Failed to parse homedir field, got %q", user.Homedir)
}
if !user.NoCreateHome {
t.Errorf("Failed to parse no_create_home field")
}
if user.PrimaryGroup != "things" {
t.Errorf("Failed to parse primary_group field, got %q", user.PrimaryGroup)
}
if len(user.Groups) != 2 {
t.Errorf("Failed to parse 2 goups, got %d", len(user.Groups))
} else {
if user.Groups[0] != "ping" {
t.Errorf("First group was %q, not expected value 'ping'", user.Groups[0])
}
if user.Groups[1] != "pong" {
t.Errorf("First group was %q, not expected value 'pong'", user.Groups[1])
}
}
if !user.NoUserGroup {
t.Errorf("Failed to parse no_user_group field")
}
if !user.System {
t.Errorf("Failed to parse system field")
}
if !user.NoLogInit {
t.Errorf("Failed to parse no_log_init field")
}
if user.Shell != "/bin/sh" {
t.Errorf("Failed to parse shell field, got %q", user.Shell)
}
}
func TestCloudConfigUsersGithubUser(t *testing.T) {
contents := `
users:
- name: elroy
coreos_ssh_import_github: bcwaldon
`
cfg, err := NewCloudConfig(contents)
if err != nil {
t.Fatalf("Encountered unexpected error: %v", err)
}
if len(cfg.Users) != 1 {
t.Fatalf("Parsed %d users, expected 1", len(cfg.Users))
}
user := cfg.Users[0]
if user.Name != "elroy" {
t.Errorf("User name is %q, expected 'elroy'", user.Name)
}
if user.SSHImportGithubUser != "bcwaldon" {
t.Errorf("github user is %q, expected 'bcwaldon'", user.SSHImportGithubUser)
}
}
func TestCloudConfigUsersSSHImportURL(t *testing.T) {
contents := `
users:
- name: elroy
coreos_ssh_import_url: https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys
`
cfg, err := NewCloudConfig(contents)
if err != nil {
t.Fatalf("Encountered unexpected error: %v", err)
}
if len(cfg.Users) != 1 {
t.Fatalf("Parsed %d users, expected 1", len(cfg.Users))
}
user := cfg.Users[0]
if user.Name != "elroy" {
t.Errorf("User name is %q, expected 'elroy'", user.Name)
}
if user.SSHImportURL != "https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys" {
t.Errorf("ssh import url is %q, expected 'https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys'", user.SSHImportURL)
}
}

56
config/decode.go Normal file
View File

@ -0,0 +1,56 @@
package config
import (
"bytes"
"compress/gzip"
"encoding/base64"
"fmt"
)
func DecodeBase64Content(content string) ([]byte, error) {
output, err := base64.StdEncoding.DecodeString(content)
if err != nil {
return nil, fmt.Errorf("Unable to decode base64: %q", err)
}
return output, nil
}
func DecodeGzipContent(content string) ([]byte, error) {
gzr, err := gzip.NewReader(bytes.NewReader([]byte(content)))
if err != nil {
return nil, fmt.Errorf("Unable to decode gzip: %q", err)
}
defer gzr.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(gzr)
return buf.Bytes(), nil
}
func DecodeContent(content string, encoding string) ([]byte, error) {
switch encoding {
case "":
return []byte(content), nil
case "b64", "base64":
return DecodeBase64Content(content)
case "gz", "gzip":
return DecodeGzipContent(content)
case "gz+base64", "gzip+base64", "gz+b64", "gzip+b64":
gz, err := DecodeBase64Content(content)
if err != nil {
return nil, err
}
return DecodeGzipContent(string(gz))
}
return nil, fmt.Errorf("Unsupported encoding %q", encoding)
}

17
config/etc_hosts.go Normal file
View File

@ -0,0 +1,17 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
type EtcHosts string

67
config/etcd.go Normal file
View File

@ -0,0 +1,67 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
type Etcd struct {
Addr string `yaml:"addr" env:"ETCD_ADDR"`
AdvertiseClientURLs string `yaml:"advertise_client_urls" env:"ETCD_ADVERTISE_CLIENT_URLS" deprecated:"etcd2 options no longer work for etcd"`
BindAddr string `yaml:"bind_addr" env:"ETCD_BIND_ADDR"`
CAFile string `yaml:"ca_file" env:"ETCD_CA_FILE"`
CertFile string `yaml:"cert_file" env:"ETCD_CERT_FILE"`
ClusterActiveSize int `yaml:"cluster_active_size" env:"ETCD_CLUSTER_ACTIVE_SIZE"`
ClusterRemoveDelay float64 `yaml:"cluster_remove_delay" env:"ETCD_CLUSTER_REMOVE_DELAY"`
ClusterSyncInterval float64 `yaml:"cluster_sync_interval" env:"ETCD_CLUSTER_SYNC_INTERVAL"`
CorsOrigins string `yaml:"cors" env:"ETCD_CORS"`
DataDir string `yaml:"data_dir" env:"ETCD_DATA_DIR"`
Discovery string `yaml:"discovery" env:"ETCD_DISCOVERY"`
DiscoveryFallback string `yaml:"discovery_fallback" env:"ETCD_DISCOVERY_FALLBACK" deprecated:"etcd2 options no longer work for etcd"`
DiscoverySRV string `yaml:"discovery_srv" env:"ETCD_DISCOVERY_SRV" deprecated:"etcd2 options no longer work for etcd"`
DiscoveryProxy string `yaml:"discovery_proxy" env:"ETCD_DISCOVERY_PROXY" deprecated:"etcd2 options no longer work for etcd"`
ElectionTimeout int `yaml:"election_timeout" env:"ETCD_ELECTION_TIMEOUT" deprecated:"etcd2 options no longer work for etcd"`
ForceNewCluster bool `yaml:"force_new_cluster" env:"ETCD_FORCE_NEW_CLUSTER" deprecated:"etcd2 options no longer work for etcd"`
GraphiteHost string `yaml:"graphite_host" env:"ETCD_GRAPHITE_HOST"`
HeartbeatInterval int `yaml:"heartbeat_interval" env:"ETCD_HEARTBEAT_INTERVAL" deprecated:"etcd2 options no longer work for etcd"`
HTTPReadTimeout float64 `yaml:"http_read_timeout" env:"ETCD_HTTP_READ_TIMEOUT"`
HTTPWriteTimeout float64 `yaml:"http_write_timeout" env:"ETCD_HTTP_WRITE_TIMEOUT"`
InitialAdvertisePeerURLs string `yaml:"initial_advertise_peer_urls" env:"ETCD_INITIAL_ADVERTISE_PEER_URLS" deprecated:"etcd2 options no longer work for etcd"`
InitialCluster string `yaml:"initial_cluster" env:"ETCD_INITIAL_CLUSTER" deprecated:"etcd2 options no longer work for etcd"`
InitialClusterState string `yaml:"initial_cluster_state" env:"ETCD_INITIAL_CLUSTER_STATE" deprecated:"etcd2 options no longer work for etcd"`
InitialClusterToken string `yaml:"initial_cluster_token" env:"ETCD_INITIAL_CLUSTER_TOKEN" deprecated:"etcd2 options no longer work for etcd"`
KeyFile string `yaml:"key_file" env:"ETCD_KEY_FILE"`
ListenClientURLs string `yaml:"listen_client_urls" env:"ETCD_LISTEN_CLIENT_URLS" deprecated:"etcd2 options no longer work for etcd"`
ListenPeerURLs string `yaml:"listen_peer_urls" env:"ETCD_LISTEN_PEER_URLS" deprecated:"etcd2 options no longer work for etcd"`
MaxResultBuffer int `yaml:"max_result_buffer" env:"ETCD_MAX_RESULT_BUFFER"`
MaxRetryAttempts int `yaml:"max_retry_attempts" env:"ETCD_MAX_RETRY_ATTEMPTS"`
MaxSnapshots int `yaml:"max_snapshots" env:"ETCD_MAX_SNAPSHOTS" deprecated:"etcd2 options no longer work for etcd"`
MaxWALs int `yaml:"max_wals" env:"ETCD_MAX_WALS" deprecated:"etcd2 options no longer work for etcd"`
Name string `yaml:"name" env:"ETCD_NAME"`
PeerAddr string `yaml:"peer_addr" env:"ETCD_PEER_ADDR"`
PeerBindAddr string `yaml:"peer_bind_addr" env:"ETCD_PEER_BIND_ADDR"`
PeerCAFile string `yaml:"peer_ca_file" env:"ETCD_PEER_CA_FILE"`
PeerCertFile string `yaml:"peer_cert_file" env:"ETCD_PEER_CERT_FILE"`
PeerElectionTimeout int `yaml:"peer_election_timeout" env:"ETCD_PEER_ELECTION_TIMEOUT"`
PeerHeartbeatInterval int `yaml:"peer_heartbeat_interval" env:"ETCD_PEER_HEARTBEAT_INTERVAL"`
PeerKeyFile string `yaml:"peer_key_file" env:"ETCD_PEER_KEY_FILE"`
Peers string `yaml:"peers" env:"ETCD_PEERS"`
PeersFile string `yaml:"peers_file" env:"ETCD_PEERS_FILE"`
Proxy string `yaml:"proxy" env:"ETCD_PROXY" deprecated:"etcd2 options no longer work for etcd"`
RetryInterval float64 `yaml:"retry_interval" env:"ETCD_RETRY_INTERVAL"`
Snapshot bool `yaml:"snapshot" env:"ETCD_SNAPSHOT"`
SnapshotCount int `yaml:"snapshot_count" env:"ETCD_SNAPSHOTCOUNT"`
StrTrace string `yaml:"trace" env:"ETCD_TRACE"`
Verbose bool `yaml:"verbose" env:"ETCD_VERBOSE"`
VeryVerbose bool `yaml:"very_verbose" env:"ETCD_VERY_VERBOSE"`
VeryVeryVerbose bool `yaml:"very_very_verbose" env:"ETCD_VERY_VERY_VERBOSE"`
}

57
config/etcd2.go Normal file
View File

@ -0,0 +1,57 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
type Etcd2 struct {
AdvertiseClientURLs string `yaml:"advertise_client_urls" env:"ETCD_ADVERTISE_CLIENT_URLS"`
CAFile string `yaml:"ca_file" env:"ETCD_CA_FILE" deprecated:"ca_file obsoleted by trusted_ca_file and client_cert_auth"`
CertFile string `yaml:"cert_file" env:"ETCD_CERT_FILE"`
ClientCertAuth bool `yaml:"client_cert_auth" env:"ETCD_CLIENT_CERT_AUTH"`
CorsOrigins string `yaml:"cors" env:"ETCD_CORS"`
DataDir string `yaml:"data_dir" env:"ETCD_DATA_DIR"`
Debug bool `yaml:"debug" env:"ETCD_DEBUG"`
Discovery string `yaml:"discovery" env:"ETCD_DISCOVERY"`
DiscoveryFallback string `yaml:"discovery_fallback" env:"ETCD_DISCOVERY_FALLBACK"`
DiscoverySRV string `yaml:"discovery_srv" env:"ETCD_DISCOVERY_SRV"`
DiscoveryProxy string `yaml:"discovery_proxy" env:"ETCD_DISCOVERY_PROXY"`
ElectionTimeout int `yaml:"election_timeout" env:"ETCD_ELECTION_TIMEOUT"`
ForceNewCluster bool `yaml:"force_new_cluster" env:"ETCD_FORCE_NEW_CLUSTER"`
HeartbeatInterval int `yaml:"heartbeat_interval" env:"ETCD_HEARTBEAT_INTERVAL"`
InitialAdvertisePeerURLs string `yaml:"initial_advertise_peer_urls" env:"ETCD_INITIAL_ADVERTISE_PEER_URLS"`
InitialCluster string `yaml:"initial_cluster" env:"ETCD_INITIAL_CLUSTER"`
InitialClusterState string `yaml:"initial_cluster_state" env:"ETCD_INITIAL_CLUSTER_STATE"`
InitialClusterToken string `yaml:"initial_cluster_token" env:"ETCD_INITIAL_CLUSTER_TOKEN"`
KeyFile string `yaml:"key_file" env:"ETCD_KEY_FILE"`
ListenClientURLs string `yaml:"listen_client_urls" env:"ETCD_LISTEN_CLIENT_URLS"`
ListenPeerURLs string `yaml:"listen_peer_urls" env:"ETCD_LISTEN_PEER_URLS"`
LogPackageLevels string `yaml:"log_package_levels" env:"ETCD_LOG_PACKAGE_LEVELS"`
MaxSnapshots int `yaml:"max_snapshots" env:"ETCD_MAX_SNAPSHOTS"`
MaxWALs int `yaml:"max_wals" env:"ETCD_MAX_WALS"`
Name string `yaml:"name" env:"ETCD_NAME"`
PeerCAFile string `yaml:"peer_ca_file" env:"ETCD_PEER_CA_FILE" deprecated:"peer_ca_file obsoleted peer_trusted_ca_file and peer_client_cert_auth"`
PeerCertFile string `yaml:"peer_cert_file" env:"ETCD_PEER_CERT_FILE"`
PeerKeyFile string `yaml:"peer_key_file" env:"ETCD_PEER_KEY_FILE"`
PeerClientCertAuth bool `yaml:"peer_client_cert_auth" env:"ETCD_PEER_CLIENT_CERT_AUTH"`
PeerTrustedCAFile string `yaml:"peer_trusted_ca_file" env:"ETCD_PEER_TRUSTED_CA_FILE"`
Proxy string `yaml:"proxy" env:"ETCD_PROXY" valid:"^(on|off|readonly)$"`
ProxyDialTimeout int `yaml:"proxy_dial_timeout" env:"ETCD_PROXY_DIAL_TIMEOUT"`
ProxyFailureWait int `yaml:"proxy_failure_wait" env:"ETCD_PROXY_FAILURE_WAIT"`
ProxyReadTimeout int `yaml:"proxy_read_timeout" env:"ETCD_PROXY_READ_TIMEOUT"`
ProxyRefreshInterval int `yaml:"proxy_refresh_interval" env:"ETCD_PROXY_REFRESH_INTERVAL"`
ProxyWriteTimeout int `yaml:"proxy_write_timeout" env:"ETCD_PROXY_WRITE_TIMEOUT"`
SnapshotCount int `yaml:"snapshot_count" env:"ETCD_SNAPSHOT_COUNT"`
TrustedCAFile string `yaml:"trusted_ca_file" env:"ETCD_TRUSTED_CA_FILE"`
WalDir string `yaml:"wal_dir" env:"ETCD_WAL_DIR"`
}

23
config/file.go Normal file
View File

@ -0,0 +1,23 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
type File struct {
Encoding string `yaml:"encoding" valid:"^(base64|b64|gz|gzip|gz\\+base64|gzip\\+base64|gz\\+b64|gzip\\+b64)$"`
Content string `yaml:"content"`
Owner string `yaml:"owner"`
Path string `yaml:"path"`
RawFilePermissions string `yaml:"permissions" valid:"^0?[0-7]{3,4}$"`
}

69
config/file_test.go Normal file
View File

@ -0,0 +1,69 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
import (
"testing"
)
func TestEncodingValid(t *testing.T) {
tests := []struct {
value string
isValid bool
}{
{value: "base64", isValid: true},
{value: "b64", isValid: true},
{value: "gz", isValid: true},
{value: "gzip", isValid: true},
{value: "gz+base64", isValid: true},
{value: "gzip+base64", isValid: true},
{value: "gz+b64", isValid: true},
{value: "gzip+b64", isValid: true},
{value: "gzzzzbase64", isValid: false},
{value: "gzipppbase64", isValid: false},
{value: "unknown", isValid: false},
}
for _, tt := range tests {
isValid := (nil == AssertStructValid(File{Encoding: tt.value}))
if tt.isValid != isValid {
t.Errorf("bad assert (%s): want %t, got %t", tt.value, tt.isValid, isValid)
}
}
}
func TestRawFilePermissionsValid(t *testing.T) {
tests := []struct {
value string
isValid bool
}{
{value: "744", isValid: true},
{value: "0744", isValid: true},
{value: "1744", isValid: true},
{value: "01744", isValid: true},
{value: "11744", isValid: false},
{value: "rwxr--r--", isValid: false},
{value: "800", isValid: false},
}
for _, tt := range tests {
isValid := (nil == AssertStructValid(File{RawFilePermissions: tt.value}))
if tt.isValid != isValid {
t.Errorf("bad assert (%s): want %t, got %t", tt.value, tt.isValid, isValid)
}
}
}

27
config/flannel.go Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
type Flannel struct {
EtcdEndpoints string `yaml:"etcd_endpoints" env:"FLANNELD_ETCD_ENDPOINTS"`
EtcdCAFile string `yaml:"etcd_cafile" env:"FLANNELD_ETCD_CAFILE"`
EtcdCertFile string `yaml:"etcd_certfile" env:"FLANNELD_ETCD_CERTFILE"`
EtcdKeyFile string `yaml:"etcd_keyfile" env:"FLANNELD_ETCD_KEYFILE"`
EtcdPrefix string `yaml:"etcd_prefix" env:"FLANNELD_ETCD_PREFIX"`
IPMasq string `yaml:"ip_masq" env:"FLANNELD_IP_MASQ"`
SubnetFile string `yaml:"subnet_file" env:"FLANNELD_SUBNET_FILE"`
Iface string `yaml:"interface" env:"FLANNELD_IFACE"`
PublicIP string `yaml:"public_ip" env:"FLANNELD_PUBLIC_IP"`
}

33
config/fleet.go Normal file
View File

@ -0,0 +1,33 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
type Fleet struct {
AgentTTL string `yaml:"agent_ttl" env:"FLEET_AGENT_TTL"`
AuthorizedKeysFile string `yaml:"authorized_keys_file" env:"FLEET_AUTHORIZED_KEYS_FILE"`
DisableEngine bool `yaml:"disable_engine" env:"FLEET_DISABLE_ENGINE"`
EngineReconcileInterval float64 `yaml:"engine_reconcile_interval" env:"FLEET_ENGINE_RECONCILE_INTERVAL"`
EtcdCAFile string `yaml:"etcd_cafile" env:"FLEET_ETCD_CAFILE"`
EtcdCertFile string `yaml:"etcd_certfile" env:"FLEET_ETCD_CERTFILE"`
EtcdKeyFile string `yaml:"etcd_keyfile" env:"FLEET_ETCD_KEYFILE"`
EtcdKeyPrefix string `yaml:"etcd_key_prefix" env:"FLEET_ETCD_KEY_PREFIX"`
EtcdRequestTimeout float64 `yaml:"etcd_request_timeout" env:"FLEET_ETCD_REQUEST_TIMEOUT"`
EtcdServers string `yaml:"etcd_servers" env:"FLEET_ETCD_SERVERS"`
Metadata string `yaml:"metadata" env:"FLEET_METADATA"`
PublicIP string `yaml:"public_ip" env:"FLEET_PUBLIC_IP"`
TokenLimit int `yaml:"token_limit" env:"FLEET_TOKEN_LIMIT"`
Verbosity int `yaml:"verbosity" env:"FLEET_VERBOSITY"`
VerifyUnits bool `yaml:"verify_units" env:"FLEET_VERIFY_UNITS"`
}

26
config/ignition.go Normal file
View File

@ -0,0 +1,26 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
import (
"encoding/json"
)
func IsIgnitionConfig(userdata string) bool {
var cfg struct {
Version *int `json:"ignitionVersion" yaml:"ignition_version"`
}
return (json.Unmarshal([]byte(userdata), &cfg) == nil && cfg.Version != nil)
}

25
config/locksmith.go Normal file
View File

@ -0,0 +1,25 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
type Locksmith struct {
Endpoint string `yaml:"endpoint" env:"LOCKSMITHD_ENDPOINT"`
EtcdCAFile string `yaml:"etcd_cafile" env:"LOCKSMITHD_ETCD_CAFILE"`
EtcdCertFile string `yaml:"etcd_certfile" env:"LOCKSMITHD_ETCD_CERTFILE"`
EtcdKeyFile string `yaml:"etcd_keyfile" env:"LOCKSMITHD_ETCD_KEYFILE"`
Group string `yaml:"group" env:"LOCKSMITHD_GROUP"`
RebootWindowStart string `yaml:"window_start" env:"REBOOT_WINDOW_START" valid:"^((?i:sun|mon|tue|wed|thu|fri|sat|sun) )?0*([0-9]|1[0-9]|2[0-3]):0*([0-9]|[1-5][0-9])$"`
RebootWindowLength string `yaml:"window_length" env:"REBOOT_WINDOW_LENGTH" valid:"^[-+]?([0-9]*(\\.[0-9]*)?[a-z]+)+$"`
}

76
config/locksmith_test.go Normal file
View File

@ -0,0 +1,76 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
import (
"testing"
)
func TestRebootWindowStart(t *testing.T) {
tests := []struct {
value string
isValid bool
}{
{value: "Sun 0:0", isValid: true},
{value: "Sun 00:00", isValid: true},
{value: "sUn 23:59", isValid: true},
{value: "mon 0:0", isValid: true},
{value: "tue 0:0", isValid: true},
{value: "tues 0:0", isValid: false},
{value: "wed 0:0", isValid: true},
{value: "thu 0:0", isValid: true},
{value: "thur 0:0", isValid: false},
{value: "fri 0:0", isValid: true},
{value: "sat 0:0", isValid: true},
{value: "sat00:00", isValid: false},
{value: "00:00", isValid: true},
{value: "10:10", isValid: true},
{value: "20:20", isValid: true},
{value: "20:30", isValid: true},
{value: "20:40", isValid: true},
{value: "20:50", isValid: true},
{value: "20:60", isValid: false},
{value: "24:00", isValid: false},
}
for _, tt := range tests {
isValid := (nil == AssertStructValid(Locksmith{RebootWindowStart: tt.value}))
if tt.isValid != isValid {
t.Errorf("bad assert (%s): want %t, got %t", tt.value, tt.isValid, isValid)
}
}
}
func TestRebootWindowLength(t *testing.T) {
tests := []struct {
value string
isValid bool
}{
{value: "1h", isValid: true},
{value: "1d", isValid: true},
{value: "0d", isValid: true},
{value: "0.5h", isValid: true},
{value: "0.5.0h", isValid: false},
}
for _, tt := range tests {
isValid := (nil == AssertStructValid(Locksmith{RebootWindowLength: tt.value}))
if tt.isValid != isValid {
t.Errorf("bad assert (%s): want %t, got %t", tt.value, tt.isValid, isValid)
}
}
}

23
config/oem.go Normal file
View File

@ -0,0 +1,23 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
type OEM struct {
ID string `yaml:"id"`
Name string `yaml:"name"`
VersionID string `yaml:"version_id"`
HomeURL string `yaml:"home_url"`
BugReportURL string `yaml:"bug_report_url"`
}

31
config/script.go Normal file
View File

@ -0,0 +1,31 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
import (
"strings"
)
type Script []byte
func IsScript(userdata string) bool {
header := strings.SplitN(userdata, "\n", 2)[0]
return strings.HasPrefix(header, "#!")
}
func NewScript(userdata string) (*Script, error) {
s := Script(userdata)
return &s, nil
}

7
config/system_info.go Normal file
View File

@ -0,0 +1,7 @@
package config
type SystemInfo struct {
DefaultUser struct {
Name string `yaml:"name"`
} `yaml:"default_user"`
}

30
config/unit.go Normal file
View File

@ -0,0 +1,30 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
type Unit struct {
Name string `yaml:"name"`
Mask bool `yaml:"mask"`
Enable bool `yaml:"enable"`
Runtime bool `yaml:"runtime"`
Content string `yaml:"content"`
Command string `yaml:"command" valid:"^(start|stop|restart|reload|try-restart|reload-or-restart|reload-or-try-restart)$"`
DropIns []UnitDropIn `yaml:"drop_ins"`
}
type UnitDropIn struct {
Name string `yaml:"name"`
Content string `yaml:"content"`
}

46
config/unit_test.go Normal file
View File

@ -0,0 +1,46 @@
/*
Copyright 2014 CoreOS, Inc.
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.
*/
package config
import (
"testing"
)
func TestCommandValid(t *testing.T) {
tests := []struct {
value string
isValid bool
}{
{value: "start", isValid: true},
{value: "stop", isValid: true},
{value: "restart", isValid: true},
{value: "reload", isValid: true},
{value: "try-restart", isValid: true},
{value: "reload-or-restart", isValid: true},
{value: "reload-or-try-restart", isValid: true},
{value: "tryrestart", isValid: false},
{value: "unknown", isValid: false},
}
for _, tt := range tests {
isValid := (nil == AssertStructValid(Unit{Command: tt.value}))
if tt.isValid != isValid {
t.Errorf("bad assert (%s): want %t, got %t", tt.value, tt.isValid, isValid)
}
}
}

21
config/update.go Normal file
View File

@ -0,0 +1,21 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
type Update struct {
RebootStrategy string `yaml:"reboot_strategy" env:"REBOOT_STRATEGY" valid:"^(best-effort|etcd-lock|reboot|off)$"`
Group string `yaml:"group" env:"GROUP"`
Server string `yaml:"server" env:"SERVER"`
}

43
config/update_test.go Normal file
View File

@ -0,0 +1,43 @@
/*
Copyright 2014 CoreOS, Inc.
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.
*/
package config
import (
"testing"
)
func TestRebootStrategyValid(t *testing.T) {
tests := []struct {
value string
isValid bool
}{
{value: "best-effort", isValid: true},
{value: "etcd-lock", isValid: true},
{value: "reboot", isValid: true},
{value: "off", isValid: true},
{value: "besteffort", isValid: false},
{value: "unknown", isValid: false},
}
for _, tt := range tests {
isValid := (nil == AssertStructValid(Update{RebootStrategy: tt.value}))
if tt.isValid != isValid {
t.Errorf("bad assert (%s): want %t, got %t", tt.value, tt.isValid, isValid)
}
}
}

34
config/user.go Normal file
View File

@ -0,0 +1,34 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package config
type User struct {
Name string `yaml:"name"`
PasswordHash string `yaml:"passwd"`
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
SSHImportGithubUser string `yaml:"coreos_ssh_import_github" deprecated:"trying to fetch from a remote endpoint introduces too many intermittent errors"`
SSHImportGithubUsers []string `yaml:"coreos_ssh_import_github_users" deprecated:"trying to fetch from a remote endpoint introduces too many intermittent errors"`
SSHImportURL string `yaml:"coreos_ssh_import_url" deprecated:"trying to fetch from a remote endpoint introduces too many intermittent errors"`
GECOS string `yaml:"gecos"`
Homedir string `yaml:"homedir"`
NoCreateHome bool `yaml:"no_create_home"`
PrimaryGroup string `yaml:"primary_group"`
Groups []string `yaml:"groups"`
NoUserGroup bool `yaml:"no_user_group"`
System bool `yaml:"system"`
NoLogInit bool `yaml:"no_log_init"`
LockPasswd bool `yaml:"lock_passwd"`
Shell string `yaml:"shell"`
}

View File

@ -0,0 +1,52 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package validate
import (
"strings"
)
// context represents the current position within a newline-delimited string.
// Each line is loaded, one by one, into currentLine (newline omitted) and
// lineNumber keeps track of its position within the original string.
type context struct {
currentLine string
remainingLines string
lineNumber int
}
// Increment moves the context to the next line (if available).
func (c *context) Increment() {
if c.currentLine == "" && c.remainingLines == "" {
return
}
lines := strings.SplitN(c.remainingLines, "\n", 2)
c.currentLine = lines[0]
if len(lines) == 2 {
c.remainingLines = lines[1]
} else {
c.remainingLines = ""
}
c.lineNumber++
}
// NewContext creates a context from the provided data. It strips out all
// carriage returns and moves to the first line (if available).
func NewContext(content []byte) context {
c := context{remainingLines: strings.Replace(string(content), "\r", "", -1)}
c.Increment()
return c
}

View File

@ -0,0 +1,131 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package validate
import (
"reflect"
"testing"
)
func TestNewContext(t *testing.T) {
tests := []struct {
in string
out context
}{
{
out: context{
currentLine: "",
remainingLines: "",
lineNumber: 0,
},
},
{
in: "this\r\nis\r\na\r\ntest",
out: context{
currentLine: "this",
remainingLines: "is\na\ntest",
lineNumber: 1,
},
},
}
for _, tt := range tests {
if out := NewContext([]byte(tt.in)); !reflect.DeepEqual(tt.out, out) {
t.Errorf("bad context (%q): want %#v, got %#v", tt.in, tt.out, out)
}
}
}
func TestIncrement(t *testing.T) {
tests := []struct {
init context
op func(c *context)
res context
}{
{
init: context{
currentLine: "",
remainingLines: "",
lineNumber: 0,
},
res: context{
currentLine: "",
remainingLines: "",
lineNumber: 0,
},
op: func(c *context) {
c.Increment()
},
},
{
init: context{
currentLine: "test",
remainingLines: "",
lineNumber: 1,
},
res: context{
currentLine: "",
remainingLines: "",
lineNumber: 2,
},
op: func(c *context) {
c.Increment()
c.Increment()
c.Increment()
},
},
{
init: context{
currentLine: "this",
remainingLines: "is\na\ntest",
lineNumber: 1,
},
res: context{
currentLine: "is",
remainingLines: "a\ntest",
lineNumber: 2,
},
op: func(c *context) {
c.Increment()
},
},
{
init: context{
currentLine: "this",
remainingLines: "is\na\ntest",
lineNumber: 1,
},
res: context{
currentLine: "test",
remainingLines: "",
lineNumber: 4,
},
op: func(c *context) {
c.Increment()
c.Increment()
c.Increment()
},
},
}
for i, tt := range tests {
res := tt.init
if tt.op(&res); !reflect.DeepEqual(tt.res, res) {
t.Errorf("bad context (%d, %#v): want %#v, got %#v", i, tt.init, tt.res, res)
}
}
}

157
config/validate/node.go Normal file
View File

@ -0,0 +1,157 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package validate
import (
"fmt"
"reflect"
"regexp"
)
var (
yamlKey = regexp.MustCompile(`^ *-? ?(?P<key>.*?):`)
yamlElem = regexp.MustCompile(`^ *-`)
)
type node struct {
name string
line int
children []node
field reflect.StructField
reflect.Value
}
// Child attempts to find the child with the given name in the node's list of
// children. If no such child is found, an invalid node is returned.
func (n node) Child(name string) node {
for _, c := range n.children {
if c.name == name {
return c
}
}
return node{}
}
// HumanType returns the human-consumable string representation of the type of
// the node.
func (n node) HumanType() string {
switch k := n.Kind(); k {
case reflect.Slice:
c := n.Type().Elem()
return "[]" + node{Value: reflect.New(c).Elem()}.HumanType()
default:
return k.String()
}
}
// NewNode returns the node representation of the given value. The context
// will be used in an attempt to determine line numbers for the given value.
func NewNode(value interface{}, context context) node {
var n node
toNode(value, context, &n)
return n
}
// toNode converts the given value into a node and then recursively processes
// each of the nodes components (e.g. fields, array elements, keys).
func toNode(v interface{}, c context, n *node) {
vv := reflect.ValueOf(v)
if !vv.IsValid() {
return
}
n.Value = vv
switch vv.Kind() {
case reflect.Struct:
// Walk over each field in the structure, skipping unexported fields,
// and create a node for it.
for i := 0; i < vv.Type().NumField(); i++ {
ft := vv.Type().Field(i)
k := ft.Tag.Get("yaml")
if k == "-" || k == "" {
continue
}
cn := node{name: k, field: ft}
c, ok := findKey(cn.name, c)
if ok {
cn.line = c.lineNumber
}
toNode(vv.Field(i).Interface(), c, &cn)
n.children = append(n.children, cn)
}
case reflect.Map:
// Walk over each key in the map and create a node for it.
v := v.(map[interface{}]interface{})
for k, cv := range v {
cn := node{name: fmt.Sprintf("%s", k)}
c, ok := findKey(cn.name, c)
if ok {
cn.line = c.lineNumber
}
toNode(cv, c, &cn)
n.children = append(n.children, cn)
}
case reflect.Slice:
// Walk over each element in the slice and create a node for it.
// While iterating over the slice, preserve the context after it
// is modified. This allows the line numbers to reflect the current
// element instead of the first.
for i := 0; i < vv.Len(); i++ {
cn := node{
name: fmt.Sprintf("%s[%d]", n.name, i),
field: n.field,
}
var ok bool
c, ok = findElem(c)
if ok {
cn.line = c.lineNumber
}
toNode(vv.Index(i).Interface(), c, &cn)
n.children = append(n.children, cn)
c.Increment()
}
case reflect.String, reflect.Int, reflect.Bool, reflect.Float64:
default:
panic(fmt.Sprintf("toNode(): unhandled kind %s", vv.Kind()))
}
}
// findKey attempts to find the requested key within the provided context.
// A modified copy of the context is returned with every line up to the key
// incremented past. A boolean, true if the key was found, is also returned.
func findKey(key string, context context) (context, bool) {
return find(yamlKey, key, context)
}
// findElem attempts to find an array element within the provided context.
// A modified copy of the context is returned with every line up to the array
// element incremented past. A boolean, true if the key was found, is also
// returned.
func findElem(context context) (context, bool) {
return find(yamlElem, "", context)
}
func find(exp *regexp.Regexp, key string, context context) (context, bool) {
for len(context.currentLine) > 0 || len(context.remainingLines) > 0 {
matches := exp.FindStringSubmatch(context.currentLine)
if len(matches) > 0 && (key == "" || matches[1] == key) {
return context, true
}
context.Increment()
}
return context, false
}

View File

@ -0,0 +1,284 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package validate
import (
"reflect"
"testing"
)
func TestChild(t *testing.T) {
tests := []struct {
parent node
name string
child node
}{
{},
{
name: "c1",
},
{
parent: node{
children: []node{
node{name: "c1"},
node{name: "c2"},
node{name: "c3"},
},
},
},
{
parent: node{
children: []node{
node{name: "c1"},
node{name: "c2"},
node{name: "c3"},
},
},
name: "c2",
child: node{name: "c2"},
},
}
for _, tt := range tests {
if child := tt.parent.Child(tt.name); !reflect.DeepEqual(tt.child, child) {
t.Errorf("bad child (%q): want %#v, got %#v", tt.name, tt.child, child)
}
}
}
func TestHumanType(t *testing.T) {
tests := []struct {
node node
humanType string
}{
{
humanType: "invalid",
},
{
node: node{Value: reflect.ValueOf("hello")},
humanType: "string",
},
{
node: node{
Value: reflect.ValueOf([]int{1, 2}),
children: []node{
node{Value: reflect.ValueOf(1)},
node{Value: reflect.ValueOf(2)},
}},
humanType: "[]int",
},
}
for _, tt := range tests {
if humanType := tt.node.HumanType(); tt.humanType != humanType {
t.Errorf("bad type (%q): want %q, got %q", tt.node, tt.humanType, humanType)
}
}
}
func TestToNode(t *testing.T) {
tests := []struct {
value interface{}
context context
node node
}{
{},
{
value: struct{}{},
node: node{Value: reflect.ValueOf(struct{}{})},
},
{
value: struct {
A int `yaml:"a"`
}{},
node: node{
children: []node{
node{
name: "a",
field: reflect.TypeOf(struct {
A int `yaml:"a"`
}{}).Field(0),
},
},
},
},
{
value: struct {
A []int `yaml:"a"`
}{},
node: node{
children: []node{
node{
name: "a",
field: reflect.TypeOf(struct {
A []int `yaml:"a"`
}{}).Field(0),
},
},
},
},
{
value: map[interface{}]interface{}{
"a": map[interface{}]interface{}{
"b": 2,
},
},
context: NewContext([]byte("a:\n b: 2")),
node: node{
children: []node{
node{
line: 1,
name: "a",
children: []node{
node{name: "b", line: 2},
},
},
},
},
},
{
value: struct {
A struct {
Jon bool `yaml:"b"`
} `yaml:"a"`
}{},
node: node{
children: []node{
node{
name: "a",
children: []node{
node{
name: "b",
field: reflect.TypeOf(struct {
Jon bool `yaml:"b"`
}{}).Field(0),
Value: reflect.ValueOf(false),
},
},
field: reflect.TypeOf(struct {
A struct {
Jon bool `yaml:"b"`
} `yaml:"a"`
}{}).Field(0),
Value: reflect.ValueOf(struct {
Jon bool `yaml:"b"`
}{}),
},
},
Value: reflect.ValueOf(struct {
A struct {
Jon bool `yaml:"b"`
} `yaml:"a"`
}{}),
},
},
}
for _, tt := range tests {
var node node
toNode(tt.value, tt.context, &node)
if !nodesEqual(tt.node, node) {
t.Errorf("bad node (%#v): want %#v, got %#v", tt.value, tt.node, node)
}
}
}
func TestFindKey(t *testing.T) {
tests := []struct {
key string
context context
found bool
}{
{},
{
key: "key1",
context: NewContext([]byte("key1: hi")),
found: true,
},
{
key: "key2",
context: NewContext([]byte("key1: hi")),
found: false,
},
{
key: "key3",
context: NewContext([]byte("key1:\n key2:\n key3: hi")),
found: true,
},
{
key: "key4",
context: NewContext([]byte("key1:\n - key4: hi")),
found: true,
},
{
key: "key5",
context: NewContext([]byte("#key5")),
found: false,
},
}
for _, tt := range tests {
if _, found := findKey(tt.key, tt.context); tt.found != found {
t.Errorf("bad find (%q): want %t, got %t", tt.key, tt.found, found)
}
}
}
func TestFindElem(t *testing.T) {
tests := []struct {
context context
found bool
}{
{},
{
context: NewContext([]byte("test: hi")),
found: false,
},
{
context: NewContext([]byte("test:\n - a\n -b")),
found: true,
},
{
context: NewContext([]byte("test:\n -\n a")),
found: true,
},
}
for _, tt := range tests {
if _, found := findElem(tt.context); tt.found != found {
t.Errorf("bad find (%q): want %t, got %t", tt.context, tt.found, found)
}
}
}
func nodesEqual(a, b node) bool {
if a.name != b.name ||
a.line != b.line ||
!reflect.DeepEqual(a.field, b.field) ||
len(a.children) != len(b.children) {
return false
}
for i := 0; i < len(a.children); i++ {
if !nodesEqual(a.children[i], b.children[i]) {
return false
}
}
return true
}

88
config/validate/report.go Normal file
View File

@ -0,0 +1,88 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package validate
import (
"encoding/json"
"fmt"
)
// Report represents the list of entries resulting from validation.
type Report struct {
entries []Entry
}
// Error adds an error entry to the report.
func (r *Report) Error(line int, message string) {
r.entries = append(r.entries, Entry{entryError, message, line})
}
// Warning adds a warning entry to the report.
func (r *Report) Warning(line int, message string) {
r.entries = append(r.entries, Entry{entryWarning, message, line})
}
// Info adds an info entry to the report.
func (r *Report) Info(line int, message string) {
r.entries = append(r.entries, Entry{entryInfo, message, line})
}
// Entries returns the list of entries in the report.
func (r *Report) Entries() []Entry {
return r.entries
}
// Entry represents a single generic item in the report.
type Entry struct {
kind entryKind
message string
line int
}
// String returns a human-readable representation of the entry.
func (e Entry) String() string {
return fmt.Sprintf("line %d: %s: %s", e.line, e.kind, e.message)
}
// MarshalJSON satisfies the json.Marshaler interface, returning the entry
// encoded as a JSON object.
func (e Entry) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"kind": e.kind.String(),
"message": e.message,
"line": e.line,
})
}
type entryKind int
const (
entryError entryKind = iota
entryWarning
entryInfo
)
func (k entryKind) String() string {
switch k {
case entryError:
return "error"
case entryWarning:
return "warning"
case entryInfo:
return "info"
default:
panic(fmt.Sprintf("invalid kind %d", k))
}
}

View File

@ -0,0 +1,96 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package validate
import (
"bytes"
"reflect"
"testing"
)
func TestEntry(t *testing.T) {
tests := []struct {
entry Entry
str string
json []byte
}{
{
Entry{entryInfo, "test info", 1},
"line 1: info: test info",
[]byte(`{"kind":"info","line":1,"message":"test info"}`),
},
{
Entry{entryWarning, "test warning", 1},
"line 1: warning: test warning",
[]byte(`{"kind":"warning","line":1,"message":"test warning"}`),
},
{
Entry{entryError, "test error", 2},
"line 2: error: test error",
[]byte(`{"kind":"error","line":2,"message":"test error"}`),
},
}
for _, tt := range tests {
if str := tt.entry.String(); tt.str != str {
t.Errorf("bad string (%q): want %q, got %q", tt.entry, tt.str, str)
}
json, err := tt.entry.MarshalJSON()
if err != nil {
t.Errorf("bad error (%q): want %v, got %q", tt.entry, nil, err)
}
if !bytes.Equal(tt.json, json) {
t.Errorf("bad JSON (%q): want %q, got %q", tt.entry, tt.json, json)
}
}
}
func TestReport(t *testing.T) {
type reportFunc struct {
fn func(*Report, int, string)
line int
message string
}
tests := []struct {
fs []reportFunc
es []Entry
}{
{
[]reportFunc{
{(*Report).Warning, 1, "test warning 1"},
{(*Report).Error, 2, "test error 2"},
{(*Report).Info, 10, "test info 10"},
},
[]Entry{
Entry{entryWarning, "test warning 1", 1},
Entry{entryError, "test error 2", 2},
Entry{entryInfo, "test info 10", 10},
},
},
}
for _, tt := range tests {
r := Report{}
for _, f := range tt.fs {
f.fn(&r, f.line, f.message)
}
if es := r.Entries(); !reflect.DeepEqual(tt.es, es) {
t.Errorf("bad entries (%v): want %#v, got %#v", tt.fs, tt.es, es)
}
}
}

180
config/validate/rules.go Normal file
View File

@ -0,0 +1,180 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package validate
import (
"fmt"
"net/url"
"path"
"reflect"
"strings"
"github.com/coreos/coreos-cloudinit/config"
)
type rule func(config node, report *Report)
// Rules contains all of the validation rules.
var Rules []rule = []rule{
checkDiscoveryUrl,
checkEncoding,
checkStructure,
checkValidity,
checkWriteFiles,
checkWriteFilesUnderCoreos,
}
// checkDiscoveryUrl verifies that the string is a valid url.
func checkDiscoveryUrl(cfg node, report *Report) {
c := cfg.Child("coreos").Child("etcd").Child("discovery")
if !c.IsValid() {
return
}
if _, err := url.ParseRequestURI(c.String()); err != nil {
report.Warning(c.line, "discovery URL is not valid")
}
}
// checkEncoding validates that, for each file under 'write_files', the
// content can be decoded given the specified encoding.
func checkEncoding(cfg node, report *Report) {
for _, f := range cfg.Child("write_files").children {
e := f.Child("encoding")
if !e.IsValid() {
continue
}
c := f.Child("content")
if _, err := config.DecodeContent(c.String(), e.String()); err != nil {
report.Error(c.line, fmt.Sprintf("content cannot be decoded as %q", e.String()))
}
}
}
// checkStructure compares the provided config to the empty config.CloudConfig
// structure. Each node is checked to make sure that it exists in the known
// structure and that its type is compatible.
func checkStructure(cfg node, report *Report) {
g := NewNode(config.CloudConfig{}, NewContext([]byte{}))
checkNodeStructure(cfg, g, report)
}
func checkNodeStructure(n, g node, r *Report) {
if !isCompatible(n.Kind(), g.Kind()) {
r.Warning(n.line, fmt.Sprintf("incorrect type for %q (want %s)", n.name, g.HumanType()))
return
}
switch g.Kind() {
case reflect.Struct:
for _, cn := range n.children {
if cg := g.Child(cn.name); cg.IsValid() {
if msg := cg.field.Tag.Get("deprecated"); msg != "" {
r.Warning(cn.line, fmt.Sprintf("deprecated key %q (%s)", cn.name, msg))
}
checkNodeStructure(cn, cg, r)
} else {
r.Warning(cn.line, fmt.Sprintf("unrecognized key %q", cn.name))
}
}
case reflect.Slice:
for _, cn := range n.children {
var cg node
c := g.Type().Elem()
toNode(reflect.New(c).Elem().Interface(), context{}, &cg)
checkNodeStructure(cn, cg, r)
}
case reflect.String, reflect.Int, reflect.Float64, reflect.Bool:
default:
panic(fmt.Sprintf("checkNodeStructure(): unhandled kind %s", g.Kind()))
}
}
// isCompatible determines if the type of kind n can be converted to the type
// of kind g in the context of YAML. This is not an exhaustive list, but its
// enough for the purposes of cloud-config validation.
func isCompatible(n, g reflect.Kind) bool {
switch g {
case reflect.String:
return n == reflect.String || n == reflect.Int || n == reflect.Float64 || n == reflect.Bool
case reflect.Struct:
return n == reflect.Struct || n == reflect.Map
case reflect.Float64:
return n == reflect.Float64 || n == reflect.Int
case reflect.Bool, reflect.Slice, reflect.Int:
return n == g
default:
panic(fmt.Sprintf("isCompatible(): unhandled kind %s", g))
}
}
// checkValidity checks the value of every node in the provided config by
// running config.AssertValid() on it.
func checkValidity(cfg node, report *Report) {
g := NewNode(config.CloudConfig{}, NewContext([]byte{}))
checkNodeValidity(cfg, g, report)
}
func checkNodeValidity(n, g node, r *Report) {
if err := config.AssertValid(n.Value, g.field.Tag.Get("valid")); err != nil {
r.Error(n.line, fmt.Sprintf("invalid value %v", n.Value.Interface()))
}
switch g.Kind() {
case reflect.Struct:
for _, cn := range n.children {
if cg := g.Child(cn.name); cg.IsValid() {
checkNodeValidity(cn, cg, r)
}
}
case reflect.Slice:
for _, cn := range n.children {
var cg node
c := g.Type().Elem()
toNode(reflect.New(c).Elem().Interface(), context{}, &cg)
checkNodeValidity(cn, cg, r)
}
case reflect.String, reflect.Int, reflect.Float64, reflect.Bool:
default:
panic(fmt.Sprintf("checkNodeValidity(): unhandled kind %s", g.Kind()))
}
}
// checkWriteFiles checks to make sure that the target file can actually be
// written. Note that this check is approximate (it only checks to see if the file
// is under /usr).
func checkWriteFiles(cfg node, report *Report) {
for _, f := range cfg.Child("write_files").children {
c := f.Child("path")
if !c.IsValid() {
continue
}
d := path.Dir(c.String())
switch {
case strings.HasPrefix(d, "/usr"):
report.Error(c.line, "file cannot be written to a read-only filesystem")
}
}
}
// checkWriteFilesUnderCoreos checks to see if the 'write_files' node is a
// child of 'coreos' (it shouldn't be).
func checkWriteFilesUnderCoreos(cfg node, report *Report) {
c := cfg.Child("coreos").Child("write_files")
if c.IsValid() {
report.Info(c.line, "write_files doesn't belong under coreos")
}
}

View File

@ -0,0 +1,408 @@
// Copyright 2015 CoreOS, Inc.
//
// 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.
package validate
import (
"reflect"
"testing"
)
func TestCheckDiscoveryUrl(t *testing.T) {
tests := []struct {
config string
entries []Entry
}{
{},
{
config: "coreos:\n etcd:\n discovery: https://discovery.etcd.io/00000000000000000000000000000000",
},
{
config: "coreos:\n etcd:\n discovery: http://custom.domain/mytoken",
},
{
config: "coreos:\n etcd:\n discovery: disco",
entries: []Entry{{entryWarning, "discovery URL is not valid", 3}},
},
}
for i, tt := range tests {
r := Report{}
n, err := parseCloudConfig([]byte(tt.config), &r)
if err != nil {
panic(err)
}
checkDiscoveryUrl(n, &r)
if e := r.Entries(); !reflect.DeepEqual(tt.entries, e) {
t.Errorf("bad report (%d, %q): want %#v, got %#v", i, tt.config, tt.entries, e)
}
}
}
func TestCheckEncoding(t *testing.T) {
tests := []struct {
config string
entries []Entry
}{
{},
{
config: "write_files:\n - encoding: base64\n content: aGVsbG8K",
},
{
config: "write_files:\n - content: !!binary aGVsbG8K",
},
{
config: "write_files:\n - encoding: base64\n content: !!binary aGVsbG8K",
entries: []Entry{{entryError, `content cannot be decoded as "base64"`, 3}},
},
{
config: "write_files:\n - encoding: base64\n content: !!binary YUdWc2JHOEsK",
},
{
config: "write_files:\n - encoding: gzip\n content: !!binary H4sIAOC3tVQAA8tIzcnJ5wIAIDA6NgYAAAA=",
},
{
config: "write_files:\n - encoding: gzip+base64\n content: H4sIAOC3tVQAA8tIzcnJ5wIAIDA6NgYAAAA=",
},
{
config: "write_files:\n - encoding: custom\n content: hello",
entries: []Entry{{entryError, `content cannot be decoded as "custom"`, 3}},
},
}
for i, tt := range tests {
r := Report{}
n, err := parseCloudConfig([]byte(tt.config), &r)
if err != nil {
panic(err)
}
checkEncoding(n, &r)
if e := r.Entries(); !reflect.DeepEqual(tt.entries, e) {
t.Errorf("bad report (%d, %q): want %#v, got %#v", i, tt.config, tt.entries, e)
}
}
}
func TestCheckStructure(t *testing.T) {
tests := []struct {
config string
entries []Entry
}{
{},
// Test for unrecognized keys
{
config: "test:",
entries: []Entry{{entryWarning, "unrecognized key \"test\"", 1}},
},
{
config: "coreos:\n etcd:\n bad:",
entries: []Entry{{entryWarning, "unrecognized key \"bad\"", 3}},
},
{
config: "coreos:\n etcd:\n discovery: good",
},
// Test for deprecated keys
{
config: "coreos:\n etcd:\n addr: hi",
},
{
config: "coreos:\n etcd:\n proxy: hi",
entries: []Entry{{entryWarning, "deprecated key \"proxy\" (etcd2 options no longer work for etcd)", 3}},
},
// Test for error on list of nodes
{
config: "coreos:\n units:\n - hello\n - goodbye",
entries: []Entry{
{entryWarning, "incorrect type for \"units[0]\" (want struct)", 3},
{entryWarning, "incorrect type for \"units[1]\" (want struct)", 4},
},
},
// Test for incorrect types
// Want boolean
{
config: "coreos:\n units:\n - enable: true",
},
{
config: "coreos:\n units:\n - enable: 4",
entries: []Entry{{entryWarning, "incorrect type for \"enable\" (want bool)", 3}},
},
{
config: "coreos:\n units:\n - enable: bad",
entries: []Entry{{entryWarning, "incorrect type for \"enable\" (want bool)", 3}},
},
{
config: "coreos:\n units:\n - enable:\n bad:",
entries: []Entry{{entryWarning, "incorrect type for \"enable\" (want bool)", 3}},
},
{
config: "coreos:\n units:\n - enable:\n - bad",
entries: []Entry{{entryWarning, "incorrect type for \"enable\" (want bool)", 3}},
},
// Want string
{
config: "hostname: true",
},
{
config: "hostname: 4",
},
{
config: "hostname: host",
},
{
config: "hostname:\n name:",
entries: []Entry{{entryWarning, "incorrect type for \"hostname\" (want string)", 1}},
},
{
config: "hostname:\n - name",
entries: []Entry{{entryWarning, "incorrect type for \"hostname\" (want string)", 1}},
},
// Want struct
{
config: "coreos: true",
entries: []Entry{{entryWarning, "incorrect type for \"coreos\" (want struct)", 1}},
},
{
config: "coreos: 4",
entries: []Entry{{entryWarning, "incorrect type for \"coreos\" (want struct)", 1}},
},
{
config: "coreos: hello",
entries: []Entry{{entryWarning, "incorrect type for \"coreos\" (want struct)", 1}},
},
{
config: "coreos:\n etcd:\n discovery: fire in the disco",
},
{
config: "coreos:\n - hello",
entries: []Entry{{entryWarning, "incorrect type for \"coreos\" (want struct)", 1}},
},
// Want []string
{
config: "ssh_authorized_keys: true",
entries: []Entry{{entryWarning, "incorrect type for \"ssh_authorized_keys\" (want []string)", 1}},
},
{
config: "ssh_authorized_keys: 4",
entries: []Entry{{entryWarning, "incorrect type for \"ssh_authorized_keys\" (want []string)", 1}},
},
{
config: "ssh_authorized_keys: key",
entries: []Entry{{entryWarning, "incorrect type for \"ssh_authorized_keys\" (want []string)", 1}},
},
{
config: "ssh_authorized_keys:\n key: value",
entries: []Entry{{entryWarning, "incorrect type for \"ssh_authorized_keys\" (want []string)", 1}},
},
{
config: "ssh_authorized_keys:\n - key",
},
{
config: "ssh_authorized_keys:\n - key: value",
entries: []Entry{{entryWarning, "incorrect type for \"ssh_authorized_keys[0]\" (want string)", 2}},
},
// Want []struct
{
config: "users:\n true",
entries: []Entry{{entryWarning, "incorrect type for \"users\" (want []struct)", 1}},
},
{
config: "users:\n 4",
entries: []Entry{{entryWarning, "incorrect type for \"users\" (want []struct)", 1}},
},
{
config: "users:\n bad",
entries: []Entry{{entryWarning, "incorrect type for \"users\" (want []struct)", 1}},
},
{
config: "users:\n bad:",
entries: []Entry{{entryWarning, "incorrect type for \"users\" (want []struct)", 1}},
},
{
config: "users:\n - name: good",
},
// Want struct within array
{
config: "users:\n - true",
entries: []Entry{{entryWarning, "incorrect type for \"users[0]\" (want struct)", 2}},
},
{
config: "users:\n - name: hi\n - true",
entries: []Entry{{entryWarning, "incorrect type for \"users[1]\" (want struct)", 3}},
},
{
config: "users:\n - 4",
entries: []Entry{{entryWarning, "incorrect type for \"users[0]\" (want struct)", 2}},
},
{
config: "users:\n - bad",
entries: []Entry{{entryWarning, "incorrect type for \"users[0]\" (want struct)", 2}},
},
{
config: "users:\n - - bad",
entries: []Entry{{entryWarning, "incorrect type for \"users[0]\" (want struct)", 2}},
},
}
for i, tt := range tests {
r := Report{}
n, err := parseCloudConfig([]byte(tt.config), &r)
if err != nil {
panic(err)
}
checkStructure(n, &r)
if e := r.Entries(); !reflect.DeepEqual(tt.entries, e) {
t.Errorf("bad report (%d, %q): want %#v, got %#v", i, tt.config, tt.entries, e)
}
}
}
func TestCheckValidity(t *testing.T) {
tests := []struct {
config string
entries []Entry
}{
// string
{
config: "hostname: test",
},
// int
{
config: "coreos:\n fleet:\n verbosity: 2",
},
// bool
{
config: "coreos:\n units:\n - enable: true",
},
// slice
{
config: "coreos:\n units:\n - command: start\n - name: stop",
},
{
config: "coreos:\n units:\n - command: lol",
entries: []Entry{{entryError, "invalid value lol", 3}},
},
// struct
{
config: "coreos:\n update:\n reboot_strategy: off",
},
{
config: "coreos:\n update:\n reboot_strategy: always",
entries: []Entry{{entryError, "invalid value always", 3}},
},
// unknown
{
config: "unknown: hi",
},
}
for i, tt := range tests {
r := Report{}
n, err := parseCloudConfig([]byte(tt.config), &r)
if err != nil {
panic(err)
}
checkValidity(n, &r)
if e := r.Entries(); !reflect.DeepEqual(tt.entries, e) {
t.Errorf("bad report (%d, %q): want %#v, got %#v", i, tt.config, tt.entries, e)
}
}
}
func TestCheckWriteFiles(t *testing.T) {
tests := []struct {
config string
entries []Entry
}{
{},
{
config: "write_files:\n - path: /valid",
},
{
config: "write_files:\n - path: /tmp/usr/valid",
},
{
config: "write_files:\n - path: /usr/invalid",
entries: []Entry{{entryError, "file cannot be written to a read-only filesystem", 2}},
},
{
config: "write-files:\n - path: /tmp/../usr/invalid",
entries: []Entry{{entryError, "file cannot be written to a read-only filesystem", 2}},
},
}
for i, tt := range tests {
r := Report{}
n, err := parseCloudConfig([]byte(tt.config), &r)
if err != nil {
panic(err)
}
checkWriteFiles(n, &r)
if e := r.Entries(); !reflect.DeepEqual(tt.entries, e) {
t.Errorf("bad report (%d, %q): want %#v, got %#v", i, tt.config, tt.entries, e)
}
}
}
func TestCheckWriteFilesUnderCoreos(t *testing.T) {
tests := []struct {
config string
entries []Entry
}{
{},
{
config: "write_files:\n - path: /hi",
},
{
config: "coreos:\n write_files:\n - path: /hi",
entries: []Entry{{entryInfo, "write_files doesn't belong under coreos", 2}},
},
{
config: "coreos:\n write-files:\n - path: /hyphen",
entries: []Entry{{entryInfo, "write_files doesn't belong under coreos", 2}},
},
}
for i, tt := range tests {
r := Report{}
n, err := parseCloudConfig([]byte(tt.config), &r)
if err != nil {
panic(err)
}
checkWriteFilesUnderCoreos(n, &r)
if e := r.Entries(); !reflect.DeepEqual(tt.entries, e) {
t.Errorf("bad report (%d, %q): want %#v, got %#v", i, tt.config, tt.entries, e)
}
}
}

Some files were not shown because too many files have changed in this diff Show More