Compare commits
606 Commits
add-spec-f
...
v1.3.0
Author | SHA1 | Date | |
---|---|---|---|
|
18caa5bf07 | ||
|
a27bbb912f | ||
|
3b2af743bd | ||
|
995bc63abe | ||
|
a28f870302 | ||
|
a3357c273c | ||
|
080c698ec2 | ||
|
afbf1dbb3a | ||
|
a275e18533 | ||
|
cf3baa8805 | ||
|
ed84bcef04 | ||
|
7d8b29e597 | ||
|
4eaaa5c927 | ||
|
536f8acf2a | ||
|
9605b5edf2 | ||
|
42153edbbc | ||
|
650a239fdb | ||
|
3e47c09b41 | ||
|
d4c617fc23 | ||
|
9441586229 | ||
|
be62a1df66 | ||
|
c093e44049 | ||
|
be68a8e5cc | ||
|
58b4de8093 | ||
|
ae3676096c | ||
|
a548b557ed | ||
|
a9c132a706 | ||
|
c3c4b86a3b | ||
|
44142ff8af | ||
|
e9529ede44 | ||
|
4b5b801171 | ||
|
551cbb1e5d | ||
|
3c93938f8a | ||
|
f61c08c246 | ||
|
571903cec6 | ||
|
bdbd1930ed | ||
|
cc75a943ba | ||
|
fc77ba6355 | ||
|
7cfa0df7c4 | ||
|
58f0dadaf9 | ||
|
1ab530f157 | ||
|
13e4b77130 | ||
|
54c62cbb70 | ||
|
c8e864fef5 | ||
|
60a3377e7c | ||
|
5527f09778 | ||
|
54a64454b9 | ||
|
0e70d4f01f | ||
|
af8e590575 | ||
|
40d943fb7a | ||
|
248536a5cd | ||
|
4ed1d03c97 | ||
|
057ab37364 | ||
|
182241c8d3 | ||
|
edced59fa6 | ||
|
9be836df31 | ||
|
4e54447b8e | ||
|
999c38b09b | ||
|
06d13de5c3 | ||
|
5b0903d162 | ||
|
10669be7c0 | ||
|
2edae741e1 | ||
|
ea90e553d1 | ||
|
b0cfd86902 | ||
|
565a9540c9 | ||
|
fd10e27b99 | ||
|
39763d772c | ||
|
ee69b77bfb | ||
|
353444e56d | ||
|
112ba1e31f | ||
|
9c3cd9e69c | ||
|
685d8317bc | ||
|
f42d102b26 | ||
|
c944e9ef94 | ||
|
f10d6e8bef | ||
|
f3f3af79fd | ||
|
0e63aa0f6b | ||
|
b254e17e89 | ||
|
5c059b66f0 | ||
|
c628bef666 | ||
|
2270db3f7a | ||
|
d0d467813d | ||
|
123f111efe | ||
|
521ecfdab5 | ||
|
6d0fdf1a47 | ||
|
ffc54b028c | ||
|
420f7cf202 | ||
|
624df676d0 | ||
|
75ed8dacf9 | ||
|
dcaabe4d4a | ||
|
92c57423ba | ||
|
7447e133c9 | ||
|
4e466c12da | ||
|
333468dba3 | ||
|
55c3a793ad | ||
|
eca51031c8 | ||
|
19522bcb82 | ||
|
62248ea33d | ||
|
d2a19cc86d | ||
|
08131ffab1 | ||
|
4a0019c669 | ||
|
3275ead1ec | ||
|
32b6a55724 | ||
|
6c43644369 | ||
|
e6593d49e6 | ||
|
ab752b239f | ||
|
0742e4d357 | ||
|
78f586ec9e | ||
|
6f91b76d79 | ||
|
5c80ccacc4 | ||
|
44fdf95d99 | ||
|
0a62614eec | ||
|
97758b343b | ||
|
fb6f52b360 | ||
|
786cd2a539 | ||
|
45793f1254 | ||
|
b621756d92 | ||
|
a5b5c700a6 | ||
|
ea95920f31 | ||
|
d7602f3c08 | ||
|
a20addd05e | ||
|
d9d89a6fa0 | ||
|
3c26376326 | ||
|
d3294bcb86 | ||
|
dda314b518 | ||
|
055a3c339a | ||
|
51f37100a1 | ||
|
88e8265cd6 | ||
|
6e2db882e6 | ||
|
3e2823df1b | ||
|
46cb51cf91 | ||
|
1a6cee5305 | ||
|
d02aa18839 | ||
|
e9bda98b54 | ||
|
badc874b74 | ||
|
c9e8c887b8 | ||
|
8be307de49 | ||
|
562c474275 | ||
|
b6062f0644 | ||
|
c5fada6e69 | ||
|
5c5834863b | ||
|
44f0a949c5 | ||
|
106c4e7a2c | ||
|
6c1ba590aa | ||
|
45da664c59 | ||
|
2a71551ef2 | ||
|
84e1cb3242 | ||
|
5214ead926 | ||
|
e2c24c4cef | ||
|
75e288c553 | ||
|
0785840fe3 | ||
|
c10bfc2f56 | ||
|
2f954dcdc2 | ||
|
cdfc94f4e9 | ||
|
18e2f98414 | ||
|
4b472795c4 | ||
|
85b8d804c8 | ||
|
1fbbaaec19 | ||
|
667dbd8fb7 | ||
|
6730cb7227 | ||
|
9454522033 | ||
|
c255739a93 | ||
|
2051cd3e1c | ||
|
b52cb3fea3 | ||
|
da5f85b3fb | ||
|
9999178538 | ||
|
8f766e4666 | ||
|
2d28d16c92 | ||
|
e9cd09dd7b | ||
|
8370b30aa2 | ||
|
3e015cc3a1 | ||
|
a0fe6d0884 | ||
|
585ce5fcd9 | ||
|
72445796ca | ||
|
7342d91a85 | ||
|
db1bc51c98 | ||
|
c1f373e648 | ||
|
db49a16002 | ||
|
a4a6c281d9 | ||
|
17f8733121 | ||
|
7dec922618 | ||
|
54d3ae27af | ||
|
ee2416af64 | ||
|
cda037f9a5 | ||
|
549806cf64 | ||
|
56815a6756 | ||
|
24a6f7c49c | ||
|
98484be434 | ||
|
9024659296 | ||
|
fc6940f7ba | ||
|
f2fd95699b | ||
|
65db96cc7c | ||
|
c17b93b5c0 | ||
|
d352f8ce6a | ||
|
78aa2c56ec | ||
|
c5b3788282 | ||
|
5e98970bb5 | ||
|
cbdd446c55 | ||
|
316cadcf44 | ||
|
5a939be21b | ||
|
8d76c64386 | ||
|
1b854eb51e | ||
|
9fcf338bf3 | ||
|
fda72bdb5c | ||
|
685a38c6c8 | ||
|
9d15f2cfaf | ||
|
2134fce791 | ||
|
3abd6b2225 | ||
|
2a8e6c9566 | ||
|
abe43537da | ||
|
3a550af651 | ||
|
61c3a0eb2d | ||
|
480176bc11 | ||
|
01b18eb551 | ||
|
970ef435b6 | ||
|
e8d0021140 | ||
|
e9ec78ac6f | ||
|
4a2e417781 | ||
|
604ef7ecb4 | ||
|
c39dd5cc67 | ||
|
a923161f4a | ||
|
e59e2f6cd5 | ||
|
e90fe3eba8 | ||
|
fb0187b197 | ||
|
6babe74716 | ||
|
b1e88284ca | ||
|
18a65f7dac | ||
|
0c212c72c9 | ||
|
6a800d8cc0 | ||
|
5e112147bb | ||
|
7e78b1563f | ||
|
ecbe81f103 | ||
|
45c20c1dd3 | ||
|
8ce925a060 | ||
|
eadb6ef42c | ||
|
7518f0ec93 | ||
|
f0b9eaf2fe | ||
|
7320a2cbf2 | ||
|
57950b3ed9 | ||
|
85c6a2a16a | ||
|
24b44e86a6 | ||
|
2f52ad4ef8 | ||
|
735d6c6161 | ||
|
1cf275bad6 | ||
|
f1c97cb4d5 | ||
|
d143904aa9 | ||
|
c428ce2cc5 | ||
|
dfb5b4fc3a | ||
|
97d5538533 | ||
|
6b8f82b5d3 | ||
|
facde6609f | ||
|
d68ae84b37 | ||
|
54aa39543b | ||
|
8566a2c118 | ||
|
49ac083af5 | ||
|
5d65ca230a | ||
|
38b3e1213a | ||
|
4eedca26e9 | ||
|
f2b342c8be | ||
|
c19d8f6b61 | ||
|
7913f74351 | ||
|
5593408be8 | ||
|
7fc67c2acf | ||
|
b093094292 | ||
|
9a80fd714a | ||
|
fef5473881 | ||
|
bf5a2b208f | ||
|
364507fb75 | ||
|
08d4842502 | ||
|
21e32e44f8 | ||
|
7a06dee16f | ||
|
ff9cf5743d | ||
|
1b10a3a187 | ||
|
10838e001d | ||
|
96370ac5b9 | ||
|
0b82cd074d | ||
|
a974e85103 | ||
|
f0450662b0 | ||
|
03e29d1291 | ||
|
98ae5d88aa | ||
|
bf5d3539c9 | ||
|
5e4cbcd909 | ||
|
a270c4c737 | ||
|
f356a8a690 | ||
|
b1a897d75c | ||
|
be51f4eba0 | ||
|
a55e2cd49b | ||
|
983501e43b | ||
|
e3037f18a6 | ||
|
fe388a3ab6 | ||
|
c820f2b1cf | ||
|
81824be3bf | ||
|
98c26440be | ||
|
3b5fcc393b | ||
|
9528077340 | ||
|
4355a05d55 | ||
|
52c44923dd | ||
|
47748ef4b6 | ||
|
8eca10200e | ||
|
43be8c8996 | ||
|
19b4b1160e | ||
|
ce6fccfb3c | ||
|
7d89aefb82 | ||
|
2369e2a920 | ||
|
6d808048d3 | ||
|
276f0b5d99 | ||
|
92bd5ca5d4 | ||
|
5b5ffea126 | ||
|
18068e9375 | ||
|
1b3cabb035 | ||
|
1be2bec1c2 | ||
|
f3bd5f543e | ||
|
660feb59b9 | ||
|
9673dbe12b | ||
|
2be435dd83 | ||
|
2d91369596 | ||
|
d8d3928978 | ||
|
7fcc540154 | ||
|
cb7fbd4668 | ||
|
d4e048a1f4 | ||
|
231c0fa20b | ||
|
1aabacc769 | ||
|
6a2927d701 | ||
|
126188510b | ||
|
4627ccb444 | ||
|
aff372111a | ||
|
c7081b9918 | ||
|
9ba3b18b59 | ||
|
099de62e9a | ||
|
c089216cb5 | ||
|
68dc902ed1 | ||
|
ad66b1c92f | ||
|
fbdece2762 | ||
|
f85eafb7ca | ||
|
f0dba2294e | ||
|
bda3948382 | ||
|
fae81c78f3 | ||
|
a5dec7d7bd | ||
|
e1222c9885 | ||
|
ded3bcf122 | ||
|
80d00cde94 | ||
|
2805d70ece | ||
|
439b7e8b98 | ||
|
ba1c1e97d0 | ||
|
8a50fd8595 | ||
|
465bcce72c | ||
|
361edeebc6 | ||
|
29a7b0e34f | ||
|
8496ffb53a | ||
|
2c717a6cd1 | ||
|
13a91c9181 | ||
|
338e1b64ab | ||
|
8eb0636034 | ||
|
f7c25a1b83 | ||
|
d6a0d0908c | ||
|
5c89afc18a | ||
|
376cc4bcac | ||
|
d0a6d6f92f | ||
|
2be1e52f32 | ||
|
784a71e2bf | ||
|
e6cf83a2e5 | ||
|
840c208b60 | ||
|
29ed6b38bd | ||
|
259c7e1fe2 | ||
|
033c8d352f | ||
|
16d7e8af48 | ||
|
159f4a2c7c | ||
|
160668284c | ||
|
41b9dfcb1c | ||
|
ef4c3483b6 | ||
|
4bdf633075 | ||
|
c9fc718e18 | ||
|
4461b3d33d | ||
|
c6a1412f6b | ||
|
d0cbbd2007 | ||
|
7b5e542eb4 | ||
|
376d82ba63 | ||
|
a6aa9f82b8 | ||
|
00ee047753 | ||
|
f127406d01 | ||
|
0ddc08d55a | ||
|
56f455f890 | ||
|
dd861b9f88 | ||
|
f7d01da267 | ||
|
fc8f30bf08 | ||
|
075c0557e7 | ||
|
d25e13a2c6 | ||
|
cf1ffad533 | ||
|
82706b1d5f | ||
|
38c8fda0d1 | ||
|
69240a7e39 | ||
|
c4f1996843 | ||
|
48df1be793 | ||
|
79a40a38d8 | ||
|
856061b445 | ||
|
38321fedce | ||
|
f8a823cf7e | ||
|
a4035cffea | ||
|
5c8fb7f465 | ||
|
7a02bf54ed | ||
|
388dd67388 | ||
|
ded6d94180 | ||
|
a9a910b5c4 | ||
|
8e94b4140a | ||
|
cd322863e9 | ||
|
786e4bef65 | ||
|
269a658d4b | ||
|
e317c7eb9a | ||
|
974de943e0 | ||
|
db3f008543 | ||
|
b04509ae54 | ||
|
6c07e8784f | ||
|
60ab4222de | ||
|
1a295f65c7 | ||
|
cec0926c5c | ||
|
8ca3c2ed1f | ||
|
2cedebb4eb | ||
|
3e00a37ef5 | ||
|
59d1eba423 | ||
|
af69149260 | ||
|
5fa2ad8dfd | ||
|
513a1eb602 | ||
|
5189e1594e | ||
|
8b5bc47429 | ||
|
a64fcd2893 | ||
|
5b1145c044 | ||
|
a49877b99f | ||
|
24f181f7a3 | ||
|
61e70fcce8 | ||
|
ea6262f0ae | ||
|
f83ce07416 | ||
|
140682350d | ||
|
289ada4668 | ||
|
5d58c6c1c1 | ||
|
d95df78c6d | ||
|
ac4c969454 | ||
|
04fcd3935f | ||
|
36efcc9d69 | ||
|
f7ecc2461c | ||
|
8df9ee3ca2 | ||
|
321ceaa0da | ||
|
05daad692e | ||
|
4b6fc63e8c | ||
|
fcccfb085f | ||
|
ebf134f181 | ||
|
51d77516a5 | ||
|
98f5ead730 | ||
|
81fe0dc9e0 | ||
|
e852be65f7 | ||
|
0a16532d4b | ||
|
ff70a60fbc | ||
|
31f61d7531 | ||
|
b505e6241c | ||
|
e413a97741 | ||
|
41cbec8729 | ||
|
919298e545 | ||
|
ae424b5637 | ||
|
e93911344d | ||
|
32c52d8729 | ||
|
cdee32d245 | ||
|
31cfad91e3 | ||
|
e814b37839 | ||
|
cb4d9e81a4 | ||
|
b87a4628e6 | ||
|
b22fdd5ac9 | ||
|
6939fc2ddc | ||
|
e3117269cb | ||
|
3bb3a683a4 | ||
|
e1033c979e | ||
|
9a4d24826f | ||
|
7bed1307e1 | ||
|
47b536532d | ||
|
7df5cf761e | ||
|
799c02865c | ||
|
9f38792d43 | ||
|
7e4fa423e4 | ||
|
c3f17bd07b | ||
|
85a473d972 | ||
|
aea5ca5252 | ||
|
4e84180ad5 | ||
|
0f1717bf26 | ||
|
6a9aa60a8d | ||
|
7cacb2e127 | ||
|
1f688dcdca | ||
|
f6d8190e8f | ||
|
3263816cf5 | ||
|
96e1cb5a7a | ||
|
cf556d2a81 | ||
|
62bda8e6cc | ||
|
0d1d1f77be | ||
|
a7e21747fa | ||
|
26b54534d6 | ||
|
8201d75115 | ||
|
1d024af4c1 | ||
|
09c690cbe7 | ||
|
49adf19081 | ||
|
46b046c82e | ||
|
e64b61b312 | ||
|
d72e10125a | ||
|
3de3d2c050 | ||
|
2ff0762b0c | ||
|
d6bacb24bc | ||
|
926eb4dbb7 | ||
|
e7599fea58 | ||
|
e98c58c656 | ||
|
ae350a3b34 | ||
|
c3b53f24cf | ||
|
8bee85e63d | ||
|
4c02e99bc8 | ||
|
0fb5291dd0 | ||
|
7f55876378 | ||
|
eb51a89f78 | ||
|
588ff4c26c | ||
|
5472de8821 | ||
|
e6b632f817 | ||
|
13a3d892ca | ||
|
2e237ebead | ||
|
61bb63b6e6 | ||
|
476761cf62 | ||
|
5981e12ac0 | ||
|
78d8be8427 | ||
|
10d73930d9 | ||
|
ea12c0bfd1 | ||
|
6540d12d25 | ||
|
c438a42587 | ||
|
19f8fe49af | ||
|
58b091061e | ||
|
8a7df360ac | ||
|
ba7cf90315 | ||
|
8841740a2b | ||
|
dfe1255ac3 | ||
|
0fddd1735d | ||
|
f779a3f7f5 | ||
|
7015338aef | ||
|
e01a1f70c3 | ||
|
2e4ea503b0 | ||
|
34aa147ebe | ||
|
4d02e1da8e | ||
|
5ef3e1f32b | ||
|
23d02363ee | ||
|
3c4fe9e260 | ||
|
a594e053f5 | ||
|
f3ba47ac89 | ||
|
7d814396b7 | ||
|
47ca113385 | ||
|
639c693153 | ||
|
b4027077ff | ||
|
580460ff3f | ||
|
b246ec0397 | ||
|
4977c774d8 | ||
|
661bae11fc | ||
|
58ae898948 | ||
|
f5f9a0a6a9 | ||
|
477ae29135 | ||
|
0203d4a9f3 | ||
|
e68134d884 | ||
|
2ad33487d7 | ||
|
b778fe6f41 | ||
|
3d7bda9f6b | ||
|
3d01211937 | ||
|
61808c2002 | ||
|
35655809ff | ||
|
81e4f1f896 | ||
|
e0b65066ab | ||
|
8e0f0998df | ||
|
ddd035aaa7 | ||
|
568714cadb | ||
|
9c94b3fe21 | ||
|
267617ed1f | ||
|
bc37171a2e | ||
|
490152bd16 | ||
|
9ade6673ba | ||
|
67043681cd | ||
|
b89ddae983 | ||
|
88a6e77449 | ||
|
09c473a6cb | ||
|
48f733f448 | ||
|
aeac9f987d | ||
|
9757705ae8 | ||
|
2c328f3829 | ||
|
907131496b | ||
|
b7bd997a3e | ||
|
6f5acf53cb | ||
|
c2faaa503b | ||
|
f98ec17f3d | ||
|
c8dd424f89 | ||
|
06cf75b660 | ||
|
01542ecec7 | ||
|
818bcd4b59 | ||
|
dcd82e6c50 | ||
|
9818565c7d | ||
|
f5765e4dde | ||
|
61ffbd41c9 | ||
|
cfa17ca2d2 | ||
|
c57464c845 | ||
|
d2dabee0c6 | ||
|
5185fe48da | ||
|
d397906b7f | ||
|
fdc2e68497 | ||
|
3df9c40520 | ||
|
137949f5ad | ||
|
0841173dfc | ||
|
0a83ef5e23 | ||
|
c7aef5fdf2 | ||
|
c4605160c5 | ||
|
054de85da2 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
*.swp
|
*.swp
|
||||||
bin/
|
bin/
|
||||||
pkg/
|
coverage/
|
||||||
|
gopath/
|
||||||
|
17
.travis.yml
Normal file
17
.travis.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- go: 1.4
|
||||||
|
env: TOOLS_CMD=golang.org/x/tools/cmd
|
||||||
|
- go: 1.3
|
||||||
|
env: TOOLS_CMD=code.google.com/p/go.tools/cmd
|
||||||
|
- go: 1.2
|
||||||
|
env: TOOLS_CMD=code.google.com/p/go.tools/cmd
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get ${TOOLS_CMD}/cover
|
||||||
|
- go get ${TOOLS_CMD}/vet
|
||||||
|
|
||||||
|
script:
|
||||||
|
- ./test
|
68
CONTRIBUTING.md
Normal file
68
CONTRIBUTING.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# How to Contribute
|
||||||
|
|
||||||
|
CoreOS projects are [Apache 2.0 licensed](LICENSE) and accept contributions via
|
||||||
|
GitHub pull requests. This document outlines some of the conventions on
|
||||||
|
development workflow, commit message formatting, contact points and other
|
||||||
|
resources to make it easier to get your contribution accepted.
|
||||||
|
|
||||||
|
# Certificate of Origin
|
||||||
|
|
||||||
|
By contributing to this project you agree to the Developer Certificate of
|
||||||
|
Origin (DCO). This document was created by the Linux Kernel community and is a
|
||||||
|
simple statement that you, as a contributor, have the legal right to make the
|
||||||
|
contribution. See the [DCO](DCO) file for details.
|
||||||
|
|
||||||
|
# Email and Chat
|
||||||
|
|
||||||
|
The project currently uses the general CoreOS email list and IRC channel:
|
||||||
|
- Email: [coreos-dev](https://groups.google.com/forum/#!forum/coreos-dev)
|
||||||
|
- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
- Fork the repository on GitHub
|
||||||
|
- Read the [README](README.md) for build and test instructions
|
||||||
|
- Play with the project, submit bugs, submit patches!
|
||||||
|
|
||||||
|
## Contribution Flow
|
||||||
|
|
||||||
|
This is a rough outline of what a contributor's workflow looks like:
|
||||||
|
|
||||||
|
- Create a topic branch from where you want to base your work (usually master).
|
||||||
|
- Make commits of logical units.
|
||||||
|
- Make sure your commit messages are in the proper format (see below).
|
||||||
|
- Push your changes to a topic branch in your fork of the repository.
|
||||||
|
- Make sure the tests pass, and add any new tests as appropriate.
|
||||||
|
- Submit a pull request to the original repository.
|
||||||
|
|
||||||
|
Thanks for your contributions!
|
||||||
|
|
||||||
|
### Format of the Commit Message
|
||||||
|
|
||||||
|
We follow a rough convention for commit messages that is designed to answer two
|
||||||
|
questions: what changed and why. The subject line should feature the what and
|
||||||
|
the body of the commit should describe the why.
|
||||||
|
|
||||||
|
```
|
||||||
|
environment: write new keys in consistent order
|
||||||
|
|
||||||
|
Go 1.3 randomizes the ordering of keys when iterating over a map.
|
||||||
|
Sort the keys to make this ordering consistent.
|
||||||
|
|
||||||
|
Fixes #38
|
||||||
|
```
|
||||||
|
|
||||||
|
The format can be described more formally as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
<subsystem>: <what changed>
|
||||||
|
<BLANK LINE>
|
||||||
|
<why this change was made>
|
||||||
|
<BLANK LINE>
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
The first line is the subject and should be no longer than 70 characters, the
|
||||||
|
second line is always blank, and other lines should be wrapped at 80 characters.
|
||||||
|
This allows the message to be easier to read on GitHub as well as in various
|
||||||
|
git tools.
|
36
DCO
Normal file
36
DCO
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
Developer Certificate of Origin
|
||||||
|
Version 1.1
|
||||||
|
|
||||||
|
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||||
|
660 York Street, Suite 102,
|
||||||
|
San Francisco, CA 94110 USA
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this
|
||||||
|
license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
Developer's Certificate of Origin 1.1
|
||||||
|
|
||||||
|
By making a contribution to this project, I certify that:
|
||||||
|
|
||||||
|
(a) The contribution was created in whole or in part by me and I
|
||||||
|
have the right to submit it under the open source license
|
||||||
|
indicated in the file; or
|
||||||
|
|
||||||
|
(b) The contribution is based upon previous work that, to the best
|
||||||
|
of my knowledge, is covered under an appropriate open source
|
||||||
|
license and I have the right under that license to submit that
|
||||||
|
work with modifications, whether created in whole or in part
|
||||||
|
by me, under the same open source license (unless I am
|
||||||
|
permitted to submit under a different license), as indicated
|
||||||
|
in the file; or
|
||||||
|
|
||||||
|
(c) The contribution was provided directly to me by some other
|
||||||
|
person who certified (a), (b) or (c) and I have not modified
|
||||||
|
it.
|
||||||
|
|
||||||
|
(d) I understand and agree that this project and the contribution
|
||||||
|
are public and that a record of the contribution (including all
|
||||||
|
personal information I submit with it, including my sign-off) is
|
||||||
|
maintained indefinitely and may be redistributed consistent with
|
||||||
|
this project or the open source license(s) involved.
|
37
Documentation/cloud-config-oem.md
Normal file
37
Documentation/cloud-config-oem.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
## OEM configuration
|
||||||
|
|
||||||
|
The `coreos.oem.*` parameters follow the [os-release spec][os-release], but have been repurposed as a way for coreos-cloudinit to know about the OEM partition on this machine. Customizing this section is only needed when generating a new OEM of CoreOS from the SDK. The fields include:
|
||||||
|
|
||||||
|
- **id**: Lowercase string identifying the OEM
|
||||||
|
- **name**: Human-friendly string representing the OEM
|
||||||
|
- **version-id**: Lowercase string identifying the version of the OEM
|
||||||
|
- **home-url**: Link to the homepage of the provider or OEM
|
||||||
|
- **bug-report-url**: Link to a place to file bug reports about this OEM
|
||||||
|
|
||||||
|
coreos-cloudinit renders these fields to `/etc/oem-release`.
|
||||||
|
If no **id** field is provided, coreos-cloudinit will ignore this section.
|
||||||
|
|
||||||
|
For example, the following cloud-config document...
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
#cloud-config
|
||||||
|
coreos:
|
||||||
|
oem:
|
||||||
|
id: rackspace
|
||||||
|
name: Rackspace Cloud Servers
|
||||||
|
version-id: 168.0.0
|
||||||
|
home-url: https://www.rackspace.com/cloud/servers/
|
||||||
|
bug-report-url: https://github.com/coreos/coreos-overlay
|
||||||
|
```
|
||||||
|
|
||||||
|
...would be rendered to the following `/etc/oem-release`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
ID=rackspace
|
||||||
|
NAME="Rackspace Cloud Servers"
|
||||||
|
VERSION_ID=168.0.0
|
||||||
|
HOME_URL="https://www.rackspace.com/cloud/servers/"
|
||||||
|
BUG_REPORT_URL="https://github.com/coreos/coreos-overlay"
|
||||||
|
```
|
||||||
|
|
||||||
|
[os-release]: http://www.freedesktop.org/software/systemd/man/os-release.html
|
@@ -1,38 +1,301 @@
|
|||||||
# Customize CoreOS with Cloud-Config
|
# Using Cloud-Config
|
||||||
|
|
||||||
CoreOS allows you to configure machine parameters, launch systemd units on startup and more. Only a subset of [cloud-config functionality][cloud-config] is implemented. A set of custom parameters were added to the cloud-config format that are specific to CoreOS.
|
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`.
|
||||||
|
|
||||||
|
## Configuration File
|
||||||
|
|
||||||
|
The file used by this system initialization program is called a "cloud-config" file. It is inspired by the [cloud-init][cloud-init] project's [cloud-config][cloud-config] file, which is "the defacto multi-distribution package that handles early initialization of a cloud instance" ([cloud-init docs][cloud-init-docs]). Because the cloud-init project includes tools which aren't used by CoreOS, only the relevant subset of its configuration items will be implemented in our cloud-config file. In addition to those, we added a few CoreOS-specific items, such as etcd configuration, OEM definition, and systemd units.
|
||||||
|
|
||||||
|
We've designed our implementation to allow the same cloud-config file to work across all of our supported platforms.
|
||||||
|
|
||||||
|
[cloud-init]: https://launchpad.net/cloud-init
|
||||||
|
[cloud-init-docs]: http://cloudinit.readthedocs.org/en/latest/index.html
|
||||||
[cloud-config]: http://cloudinit.readthedocs.org/en/latest/topics/format.html#cloud-config-data
|
[cloud-config]: http://cloudinit.readthedocs.org/en/latest/topics/format.html#cloud-config-data
|
||||||
|
|
||||||
## Supported cloud-config Parameters
|
### File Format
|
||||||
|
|
||||||
|
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 must contain `#cloud-config`, followed by an associative array which has zero or more of the following keys:
|
||||||
|
|
||||||
|
- `coreos`
|
||||||
|
- `ssh_authorized_keys`
|
||||||
|
- `hostname`
|
||||||
|
- `users`
|
||||||
|
- `write_files`
|
||||||
|
- `manage_etc_hosts`
|
||||||
|
|
||||||
|
The expected values for these keys are defined in the rest of this document.
|
||||||
|
|
||||||
|
[yaml]: https://en.wikipedia.org/wiki/YAML
|
||||||
|
|
||||||
|
### Providing Cloud-Config with Config-Drive
|
||||||
|
|
||||||
|
CoreOS tries to conform to each platform's native method to provide user data. Each cloud provider tends to be unique, but this complexity has been abstracted by CoreOS. You can view each platform's instructions on their documentation pages. The most universal way to provide cloud-config is [via config-drive](https://github.com/coreos/coreos-cloudinit/blob/master/Documentation/config-drive.md), which attaches a read-only device to the machine, that contains your cloud-config file.
|
||||||
|
|
||||||
|
## Configuration Parameters
|
||||||
|
|
||||||
|
### coreos
|
||||||
|
|
||||||
|
#### etcd
|
||||||
|
|
||||||
|
The `coreos.etcd.*` parameters will be translated to a partial systemd unit acting as an etcd configuration file.
|
||||||
|
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...
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
#cloud-config
|
||||||
|
|
||||||
|
coreos:
|
||||||
|
etcd:
|
||||||
|
name: node001
|
||||||
|
# generate a new token for each unique cluster from https://discovery.etcd.io/new
|
||||||
|
discovery: https://discovery.etcd.io/<token>
|
||||||
|
# multi-region and multi-cloud deployments need to use $public_ipv4
|
||||||
|
addr: $public_ipv4:4001
|
||||||
|
peer-addr: $private_ipv4:7001
|
||||||
|
```
|
||||||
|
|
||||||
|
...will generate a systemd unit drop-in like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
[Service]
|
||||||
|
Environment="ETCD_NAME=node001"
|
||||||
|
Environment="ETCD_DISCOVERY=https://discovery.etcd.io/<token>"
|
||||||
|
Environment="ETCD_ADDR=203.0.113.29:4001"
|
||||||
|
Environment="ETCD_PEER_ADDR=192.0.2.13:7001"
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information about the available configuration parameters, see the [etcd documentation][etcd-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._
|
||||||
|
|
||||||
|
[etcd-config]: https://github.com/coreos/etcd/blob/master/Documentation/configuration.md
|
||||||
|
|
||||||
|
#### 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...
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
#cloud-config
|
||||||
|
|
||||||
|
coreos:
|
||||||
|
fleet:
|
||||||
|
public-ip: $public_ipv4
|
||||||
|
metadata: region=us-west
|
||||||
|
```
|
||||||
|
|
||||||
|
...will generate a systemd unit drop-in like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
[Service]
|
||||||
|
Environment="FLEET_PUBLIC_IP=203.0.113.29"
|
||||||
|
Environment="FLEET_METADATA=region=us-west"
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
#### flannel
|
||||||
|
|
||||||
|
The `coreos.flannel.*` parameters also work very similarly to `coreos.etcd.*`
|
||||||
|
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
|
||||||
|
|
||||||
|
[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: example.com:4001
|
||||||
|
```
|
||||||
|
|
||||||
|
...will generate a systemd unit drop-in like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
[Service]
|
||||||
|
Environment="LOCKSMITHD_ENDPOINT=example.com:4001"
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
The `coreos.update.*` parameters manipulate settings related to how CoreOS instances are updated.
|
||||||
|
|
||||||
|
These fields will be written out to and replace `/etc/coreos/update.conf`. If only one of the parameters is given it will only overwrite the given field.
|
||||||
|
The `reboot-strategy` parameter also affects the behaviour of [locksmith](https://github.com/coreos/locksmith).
|
||||||
|
|
||||||
|
- **reboot-strategy**: One of "reboot", "etcd-lock", "best-effort" or "off" for controlling when reboots are issued after an update is performed.
|
||||||
|
- _reboot_: Reboot immediately after an update is applied.
|
||||||
|
- _etcd-lock_: Reboot after first taking a distributed lock in etcd, this guarantees that only one host will reboot concurrently and that the cluster will remain available during the update.
|
||||||
|
- _best-effort_ - If etcd is running, "etcd-lock", otherwise simply "reboot".
|
||||||
|
- _off_ - Disable rebooting after updates are applied (not recommended).
|
||||||
|
- **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")
|
||||||
|
|
||||||
|
[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.*
|
||||||
|
|
||||||
|
##### Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
#cloud-config
|
||||||
|
coreos:
|
||||||
|
update:
|
||||||
|
reboot-strategy: etcd-lock
|
||||||
|
```
|
||||||
|
|
||||||
|
#### units
|
||||||
|
|
||||||
|
The `coreos.units.*` parameters define a list of arbitrary systemd units to start after booting. This feature is intended to help you start essential services required to mount storage and configure networking in order to join the CoreOS cluster. It is not intended to be a Chef/Puppet replacement.
|
||||||
|
|
||||||
|
Each item is an object with the following fields:
|
||||||
|
|
||||||
|
- **name**: String representing unit's name. Required.
|
||||||
|
- **runtime**: Boolean indicating whether or not to persist the unit across reboots. This is analogous to the `--runtime` argument to `systemctl enable`. The default value is false.
|
||||||
|
- **enable**: Boolean indicating whether or not to handle the [Install] section of the unit file. This is similar to running `systemctl enable <name>`. The default value is false.
|
||||||
|
- **content**: Plaintext string representing entire unit file. If no value is provided, the unit is assumed to exist already.
|
||||||
|
- **command**: Command to execute on unit: start, stop, reload, restart, try-restart, reload-or-restart, reload-or-try-restart. 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.
|
||||||
|
- **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.
|
||||||
|
|
||||||
|
##### Examples
|
||||||
|
|
||||||
|
Write a unit to disk, automatically starting it.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
#cloud-config
|
||||||
|
|
||||||
|
coreos:
|
||||||
|
units:
|
||||||
|
- name: docker-redis.service
|
||||||
|
command: start
|
||||||
|
content: |
|
||||||
|
[Unit]
|
||||||
|
Description=Redis container
|
||||||
|
Author=Me
|
||||||
|
After=docker.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
ExecStart=/usr/bin/docker start -a redis_server
|
||||||
|
ExecStop=/usr/bin/docker stop -t 2 redis_server
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the DOCKER_OPTS environment variable to docker.service.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
#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"'
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the built-in `etcd` and `fleet` services:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
#cloud-config
|
||||||
|
|
||||||
|
coreos:
|
||||||
|
units:
|
||||||
|
- name: etcd.service
|
||||||
|
command: start
|
||||||
|
- name: fleet.service
|
||||||
|
command: start
|
||||||
|
```
|
||||||
|
|
||||||
### ssh_authorized_keys
|
### ssh_authorized_keys
|
||||||
|
|
||||||
Provided public SSH keys will be authorized for the `core` user.
|
The `ssh_authorized_keys` parameter adds public SSH keys which will be authorized for the `core` user.
|
||||||
|
|
||||||
The keys will be named "coreos-cloudinit" by default.
|
The keys will be named "coreos-cloudinit" by default.
|
||||||
Override this with the `--ssh-key-name` flag when calling `coreos-cloudinit`.
|
Override this by using the `--ssh-key-name` flag when calling `coreos-cloudinit`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
#cloud-config
|
||||||
|
|
||||||
|
ssh_authorized_keys:
|
||||||
|
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0g+ZTxC7weoIJLUafOgrm+h...
|
||||||
|
```
|
||||||
|
|
||||||
### hostname
|
### hostname
|
||||||
|
|
||||||
The provided value will be used to set the system's hostname.
|
The `hostname` parameter defines the system's hostname.
|
||||||
This is the local part of a fully-qualified domain name (i.e. `foo` in `foo.example.com`).
|
This is the local part of a fully-qualified domain name (i.e. `foo` in `foo.example.com`).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
#cloud-config
|
||||||
|
|
||||||
|
hostname: coreos1
|
||||||
|
```
|
||||||
|
|
||||||
### users
|
### users
|
||||||
|
|
||||||
Add or modify users with the `users` directive by providing a list of user objects, each consisting of the following fields.
|
The `users` parameter adds or modifies the specified list of users. Each user is an object which consists of the following fields. Each field is optional and of type string unless otherwise noted.
|
||||||
Each field is optional and of type string unless otherwise noted.
|
|
||||||
All but the `passwd` and `ssh-authorized-keys` fields will be ignored if the user already exists.
|
All but the `passwd` and `ssh-authorized-keys` fields will be ignored if the user already exists.
|
||||||
|
|
||||||
- **name**: Required. Login name of user
|
- **name**: Required. Login name of user
|
||||||
- **gecos**: GECOS comment of user
|
- **gecos**: GECOS comment of user
|
||||||
- **passwd**: Hash of the password to use for this user
|
- **passwd**: Hash of the password to use for this user
|
||||||
- **homedir**: User's home directory. Defaults to /home/<name>
|
- **homedir**: User's home directory. Defaults to /home/\<name\>
|
||||||
- **no-create-home**: Boolean. Skip home directory createion.
|
- **no-create-home**: Boolean. Skip home directory creation.
|
||||||
- **primary-group**: Default group for the user. Defaults to a new group created named after the user.
|
- **primary-group**: Default group for the user. Defaults to a new group created named after the user.
|
||||||
- **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-users**: Authorize SSH keys from a list of GitHub users
|
||||||
|
- **coreos-ssh-import-url**: 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.
|
||||||
|
|
||||||
@@ -44,129 +307,133 @@ The following fields are not yet implemented:
|
|||||||
- **selinux-user**: Corresponding SELinux user
|
- **selinux-user**: Corresponding SELinux user
|
||||||
- **ssh-import-id**: Import SSH keys by ID from Launchpad.
|
- **ssh-import-id**: Import SSH keys by ID from Launchpad.
|
||||||
|
|
||||||
##### Generating a password hash
|
```yaml
|
||||||
|
|
||||||
Generating a safe hash is important to the security of your system. Currently with updated tools like [oclhashcat](http://hashcat.net/oclhashcat/) simplified hashes like md5crypt are trivial to crack on modern GPU hardware. You can generate a "safer" hash (read: not safe, never publish your hashes publicly) via:
|
|
||||||
|
|
||||||
###### On Debian/Ubuntu (via the package "whois")
|
|
||||||
mkpasswd --method=SHA-512 --rounds=4096
|
|
||||||
|
|
||||||
###### With OpenSSL (note: this will only make md5crypt. While better than plantext it should not be considered fully secure)
|
|
||||||
openssl passwd -1
|
|
||||||
|
|
||||||
###### With Python (change password and salt values)
|
|
||||||
python -c "import crypt, getpass, pwd; print crypt.crypt('password', '\$6\$SALT\$')"
|
|
||||||
|
|
||||||
###### With Perl (change password and salt values)
|
|
||||||
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.
|
|
||||||
|
|
||||||
### write_files
|
|
||||||
|
|
||||||
Inject an arbitrary set of files to the local filesystem.
|
|
||||||
Provide a list of objects with the following attributes:
|
|
||||||
|
|
||||||
- **path**: Absolute location on disk where contents should be written
|
|
||||||
- **content**: Data to write at the provided `path`
|
|
||||||
- **permissions**: String representing file permissions 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>`.
|
|
||||||
|
|
||||||
## Custom cloud-config Parameters
|
|
||||||
|
|
||||||
### coreos.etcd.discovery_url
|
|
||||||
|
|
||||||
The value of `coreos.etcd.discovery_url` will be used to discover the instance's etcd peers using the [etcd discovery protocol][disco-proto]. Usage of the [public discovery service][disco-service] is encouraged.
|
|
||||||
|
|
||||||
[disco-proto]: https://github.com/coreos/etcd/blob/master/Documentation/discovery-protocol.md
|
|
||||||
[disco-service]: http://discovery.etcd.io
|
|
||||||
|
|
||||||
### coreos.units
|
|
||||||
|
|
||||||
Arbitrary systemd units may be provided in the `coreos.units` attribute.
|
|
||||||
`coreos.units` is a list of objects with the following fields:
|
|
||||||
|
|
||||||
- **name**: string representing unit's name
|
|
||||||
- **runtime**: boolean indicating whether or not to persist the unit across reboots. This is analagous to the `--runtime` flag to `systemd enable`.
|
|
||||||
- **content**: plaintext string representing entire unit file
|
|
||||||
|
|
||||||
See docker example below.
|
|
||||||
|
|
||||||
## user-data Script
|
|
||||||
|
|
||||||
Simply set your user-data to a script where the first line is a shebang:
|
|
||||||
|
|
||||||
```
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo 'Hello, world!'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Inject an SSH key, bootstrap etcd, and start fleet
|
|
||||||
```
|
|
||||||
#cloud-config
|
|
||||||
|
|
||||||
coreos:
|
|
||||||
etcd:
|
|
||||||
discovery_url: https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877
|
|
||||||
fleet:
|
|
||||||
autostart: yes
|
|
||||||
ssh_authorized_keys:
|
|
||||||
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0g+ZTxC7weoIJLUafOgrm+h...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Start a docker container on boot
|
|
||||||
|
|
||||||
```
|
|
||||||
#cloud-config
|
|
||||||
|
|
||||||
coreos:
|
|
||||||
units:
|
|
||||||
- name: docker-redis.service
|
|
||||||
content: |
|
|
||||||
[Unit]
|
|
||||||
Description=Redis container
|
|
||||||
Author=Me
|
|
||||||
After=docker.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Restart=always
|
|
||||||
ExecStart=/usr/bin/docker start -a redis_server
|
|
||||||
ExecStop=/usr/bin/docker stop -t 2 redis_server
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=local.target
|
|
||||||
```
|
|
||||||
|
|
||||||
### Add a user
|
|
||||||
|
|
||||||
```
|
|
||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
users:
|
users:
|
||||||
- name: elroy
|
- name: elroy
|
||||||
passwd: $6$5s2u6/jR$un0AvWnqilcgaNB3Mkxd5yYv6mTlWfOoCYHZmfi3LDKVltj.E8XNKEcwWm...
|
passwd: $6$5s2u6/jR$un0AvWnqilcgaNB3Mkxd5yYv6mTlWfOoCYHZmfi3LDKVltj.E8XNKEcwWm...
|
||||||
groups:
|
groups:
|
||||||
- staff
|
- sudo
|
||||||
- docker
|
- docker
|
||||||
ssh-authorized-keys:
|
ssh-authorized-keys:
|
||||||
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0g+ZTxC7weoIJLUafOgrm+h...
|
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0g+ZTxC7weoIJLUafOgrm+h...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Inject configuration files
|
#### Generating a password hash
|
||||||
|
|
||||||
|
If you choose to use a password instead of an SSH key, generating a safe hash is extremely important to the security of your system. Simplified hashes like md5crypt are trivial to crack on modern GPU hardware. Here are a few ways to generate secure hashes:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
# On Debian/Ubuntu (via the package "whois")
|
||||||
|
mkpasswd --method=SHA-512 --rounds=4096
|
||||||
|
|
||||||
|
# OpenSSL (note: this will only make md5crypt. While better than plantext it should not be considered fully secure)
|
||||||
|
openssl passwd -1
|
||||||
|
|
||||||
|
# Python (change password and salt values)
|
||||||
|
python -c "import crypt, getpass, pwd; print crypt.crypt('password', '\$6\$SALT\$')"
|
||||||
|
|
||||||
|
# Perl (change password and salt values)
|
||||||
|
perl -e 'print crypt("password","\$6\$SALT\$") . "\n"'
|
||||||
|
```
|
||||||
|
|
||||||
|
Using a higher number of rounds will help create more secure passwords, but given enough time, password hashes can be reversed. On most RPM based distributions there is a tool called mkpasswd available in the `expect` package, but this does not handle "rounds" nor advanced hashing algorithms.
|
||||||
|
|
||||||
|
#### Retrieving SSH Authorized Keys
|
||||||
|
|
||||||
|
##### From a GitHub User
|
||||||
|
|
||||||
|
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
|
#cloud-config
|
||||||
|
|
||||||
write_files:
|
users:
|
||||||
- path: /etc/hosts
|
- name: elroy
|
||||||
contents: |
|
coreos-ssh-import-github: elroy
|
||||||
127.0.0.1 localhost
|
```
|
||||||
192.0.2.211 buildbox
|
|
||||||
- path: /etc/resolv.conf
|
##### From an HTTP Endpoint
|
||||||
contents: |
|
|
||||||
nameserver 192.0.2.13
|
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).
|
||||||
nameserver 192.0.2.14
|
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
|
||||||
|
|
||||||
|
The `write_files` directive defines a set of files to create on the local filesystem.
|
||||||
|
Each item in the list may have the following keys:
|
||||||
|
|
||||||
|
- **path**: Absolute location on disk where contents should be written
|
||||||
|
- **content**: Data to write at the provided `path`
|
||||||
|
- **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>`.
|
||||||
|
- **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
|
||||||
|
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
#cloud-config
|
||||||
|
write_files:
|
||||||
|
- path: /etc/resolv.conf
|
||||||
|
permissions: 0644
|
||||||
|
owner: root
|
||||||
|
content: |
|
||||||
|
nameserver 8.8.8.8
|
||||||
|
- path: /etc/motd
|
||||||
|
permissions: 0644
|
||||||
|
owner: root
|
||||||
|
content: |
|
||||||
|
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
|
||||||
|
|
||||||
|
The `manage_etc_hosts` parameter configures the contents of the `/etc/hosts` file, which is used for local name resolution.
|
||||||
|
Currently, the only supported value is "localhost" which will cause your system's hostname
|
||||||
|
to resolve to "127.0.0.1". This is helpful when the host does not have DNS
|
||||||
|
infrastructure in place to resolve its own hostname, for example, when using Vagrant.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
#cloud-config
|
||||||
|
|
||||||
|
manage_etc_hosts: localhost
|
||||||
```
|
```
|
||||||
|
34
Documentation/config-drive.md
Normal file
34
Documentation/config-drive.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Distribution via Config Drive
|
||||||
|
|
||||||
|
CoreOS supports providing configuration data via [config drive][config-drive]
|
||||||
|
disk images. Currently only providing a single script or cloud config file is
|
||||||
|
supported.
|
||||||
|
|
||||||
|
[config-drive]: http://docs.openstack.org/user-guide/content/enable_config_drive.html#config_drive_contents
|
||||||
|
|
||||||
|
## Contents and Format
|
||||||
|
|
||||||
|
The image should be a single FAT or ISO9660 file system with the label
|
||||||
|
`config-2` and the configuration data should be located at
|
||||||
|
`openstack/latest/user_data`.
|
||||||
|
|
||||||
|
For example, to wrap up a config named `user_data` in a config drive image:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p /tmp/new-drive/openstack/latest
|
||||||
|
cp user_data /tmp/new-drive/openstack/latest/user_data
|
||||||
|
mkisofs -R -V config-2 -o configdrive.iso /tmp/new-drive
|
||||||
|
rm -r /tmp/new-drive
|
||||||
|
```
|
||||||
|
|
||||||
|
## QEMU virtfs
|
||||||
|
|
||||||
|
One exception to the above, when using QEMU it is possible to skip creating an
|
||||||
|
image and use a plain directory containing the same contents:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
qemu-system-x86_64 \
|
||||||
|
-fsdev local,id=conf,security_model=none,readonly,path=/tmp/new-drive \
|
||||||
|
-device virtio-9p-pci,fsdev=conf,mount_tag=config-2 \
|
||||||
|
[usual qemu options here...]
|
||||||
|
```
|
27
Documentation/debian-interfaces.md
Normal file
27
Documentation/debian-interfaces.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#Debian Interfaces#
|
||||||
|
**WARNING**: This option is EXPERIMENTAL and may change or be removed at any
|
||||||
|
point.
|
||||||
|
There is basic support for converting from a Debian network configuration to
|
||||||
|
networkd unit files. The -convert-netconf=debian option is used to activate
|
||||||
|
this feature.
|
||||||
|
|
||||||
|
#convert-netconf#
|
||||||
|
Default: ""
|
||||||
|
Read the network config provided in cloud-drive and translate it from the
|
||||||
|
specified format into networkd unit files (requires the -from-configdrive
|
||||||
|
flag). Currently only supports "debian" which provides support for a small
|
||||||
|
subset of the [Debian network configuration]
|
||||||
|
(https://wiki.debian.org/NetworkConfiguration). These options include:
|
||||||
|
|
||||||
|
- interface config methods
|
||||||
|
- static
|
||||||
|
- address/netmask
|
||||||
|
- gateway
|
||||||
|
- hwaddress
|
||||||
|
- dns-nameservers
|
||||||
|
- dhcp
|
||||||
|
- hwaddress
|
||||||
|
- manual
|
||||||
|
- loopback
|
||||||
|
- vlan_raw_device
|
||||||
|
- bond-slaves
|
34
Godeps/Godeps.json
generated
Normal file
34
Godeps/Godeps.json
generated
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ImportPath": "github.com/coreos/coreos-cloudinit",
|
||||||
|
"GoVersion": "go1.3.3",
|
||||||
|
"Packages": [
|
||||||
|
"./..."
|
||||||
|
],
|
||||||
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/cloudsigma/cepgo",
|
||||||
|
"Rev": "1bfc4895bf5c4d3b599f3f6ee142299488c8739b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/coreos/go-systemd/dbus",
|
||||||
|
"Rev": "4fbc5060a317b142e6c7bfbedb65596d5f0ab99b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/coreos/yaml",
|
||||||
|
"Rev": "6b16a5714269b2f70720a45406b1babd947a17ef"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5
Godeps/Readme
generated
Normal file
5
Godeps/Readme
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
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
Normal file
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/pkg
|
||||||
|
/bin
|
23
Godeps/_workspace/src/github.com/cloudsigma/cepgo/.gitignore
generated
vendored
Normal file
23
Godeps/_workspace/src/github.com/cloudsigma/cepgo/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
202
Godeps/_workspace/src/github.com/cloudsigma/cepgo/LICENSE
generated
vendored
Normal file
202
Godeps/_workspace/src/github.com/cloudsigma/cepgo/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
43
Godeps/_workspace/src/github.com/cloudsigma/cepgo/README.md
generated
vendored
Normal file
43
Godeps/_workspace/src/github.com/cloudsigma/cepgo/README.md
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
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
|
186
Godeps/_workspace/src/github.com/cloudsigma/cepgo/cepgo.go
generated
vendored
Normal file
186
Godeps/_workspace/src/github.com/cloudsigma/cepgo/cepgo.go
generated
vendored
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
// 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
|
||||||
|
}
|
122
Godeps/_workspace/src/github.com/cloudsigma/cepgo/cepgo_test.go
generated
vendored
Normal file
122
Godeps/_workspace/src/github.com/cloudsigma/cepgo/cepgo_test.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
@@ -18,10 +18,12 @@ limitations under the License.
|
|||||||
package dbus
|
package dbus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const signalBuffer = 100
|
const signalBuffer = 100
|
||||||
@@ -73,7 +75,12 @@ func (c *Conn) initConnection() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.sysconn.Auth(nil)
|
// 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 {
|
if err != nil {
|
||||||
c.sysconn.Close()
|
c.sysconn.Close()
|
||||||
return err
|
return err
|
@@ -18,7 +18,7 @@ package dbus
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Conn) initJobs() {
|
func (c *Conn) initJobs() {
|
||||||
@@ -35,6 +35,7 @@ func (c *Conn) jobComplete(signal *dbus.Signal) {
|
|||||||
out, ok := c.jobListener.jobs[job]
|
out, ok := c.jobListener.jobs[job]
|
||||||
if ok {
|
if ok {
|
||||||
out <- result
|
out <- result
|
||||||
|
delete(c.jobListener.jobs, job)
|
||||||
}
|
}
|
||||||
c.jobListener.Unlock()
|
c.jobListener.Unlock()
|
||||||
}
|
}
|
||||||
@@ -137,8 +138,8 @@ func (c *Conn) KillUnit(name string, signal int32) {
|
|||||||
c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store()
|
c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUnitProperties takes the unit name and returns all of its dbus object properties.
|
// getProperties takes the unit name and returns all of its dbus object properties, for the given dbus interface
|
||||||
func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
|
func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) {
|
||||||
var err error
|
var err error
|
||||||
var props map[string]dbus.Variant
|
var props map[string]dbus.Variant
|
||||||
|
|
||||||
@@ -148,7 +149,7 @@ func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
obj := c.sysconn.Object("org.freedesktop.systemd1", path)
|
obj := c.sysconn.Object("org.freedesktop.systemd1", path)
|
||||||
err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, "org.freedesktop.systemd1.Unit").Store(&props)
|
err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -161,6 +162,55 @@ func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
|
|||||||
return out, nil
|
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
|
// 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
|
// 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.
|
// be more unit names loaded than actual units behind them.
|
||||||
@@ -253,8 +303,52 @@ type EnableUnitFileChange struct {
|
|||||||
Destination string // Destination 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
|
// Reload instructs systemd to scan for and reload unit files. This is
|
||||||
// equivalent to a 'systemctl daemon-reload'.
|
// equivalent to a 'systemctl daemon-reload'.
|
||||||
func (c *Conn) Reload() (string, error) {
|
func (c *Conn) Reload() error {
|
||||||
return c.runJob("org.freedesktop.systemd1.Manager.Reload")
|
return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store()
|
||||||
}
|
}
|
@@ -18,9 +18,11 @@ package dbus
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,13 +52,16 @@ func setupUnit(target string, conn *Conn, t *testing.T) {
|
|||||||
fixture := []string{abs}
|
fixture := []string{abs}
|
||||||
|
|
||||||
install, changes, err := conn.EnableUnitFiles(fixture, true, true)
|
install, changes, err := conn.EnableUnitFiles(fixture, true, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
if install != false {
|
if install != false {
|
||||||
t.Fatal("Install was true")
|
t.Fatal("Install was true")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(changes) < 1 {
|
if len(changes) < 1 {
|
||||||
t.Fatal("Expected one change, got %v", changes)
|
t.Fatalf("Expected one change, got %v", changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
if changes[0].Filename != targetRun {
|
if changes[0].Filename != targetRun {
|
||||||
@@ -118,6 +123,37 @@ func TestStartStopUnit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// TestGetUnitProperties reads the `-.mount` which should exist on all systemd
|
||||||
// systems and ensures that one of its properties is valid.
|
// systems and ensures that one of its properties is valid.
|
||||||
func TestGetUnitProperties(t *testing.T) {
|
func TestGetUnitProperties(t *testing.T) {
|
||||||
@@ -139,6 +175,20 @@ func TestGetUnitProperties(t *testing.T) {
|
|||||||
if names[0] != "system.slice" {
|
if names[0] != "system.slice" {
|
||||||
t.Fatal("unexpected wants for /")
|
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
|
// TestGetUnitPropertiesRejectsInvalidName attempts to get the properties for a
|
||||||
@@ -150,10 +200,37 @@ func TestGetUnitPropertiesRejectsInvalidName(t *testing.T) {
|
|||||||
unit := "//invalid#$^/"
|
unit := "//invalid#$^/"
|
||||||
|
|
||||||
_, err := conn.GetUnitProperties(unit)
|
_, err := conn.GetUnitProperties(unit)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Expected an error, got 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.
|
// Ensure that basic transient unit starting and stopping works.
|
||||||
@@ -211,3 +288,27 @@ func TestStartStopTransientUnit(t *testing.T) {
|
|||||||
t.Fatalf("Test unit found in list, should be stopped")
|
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")
|
||||||
|
}
|
||||||
|
}
|
@@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
package dbus
|
package dbus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// From the systemd docs:
|
// From the systemd docs:
|
||||||
@@ -209,3 +209,12 @@ func PropPropagatesReloadTo(units ...string) Property {
|
|||||||
func PropRequiresMountsFor(units ...string) Property {
|
func PropRequiresMountsFor(units ...string) Property {
|
||||||
return propDependency("RequiresMountsFor", units)
|
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),
|
||||||
|
}
|
||||||
|
}
|
@@ -20,7 +20,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -101,7 +101,7 @@ func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitSt
|
|||||||
// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer
|
// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer
|
||||||
// size of the channels, the comparison function for detecting changes and a filter
|
// size of the channels, the comparison function for detecting changes and a filter
|
||||||
// function for cutting down on the noise that your channel receives.
|
// 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) {
|
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)
|
old := make(map[string]*UnitStatus)
|
||||||
statusChan := make(chan map[string]*UnitStatus, buffer)
|
statusChan := make(chan map[string]*UnitStatus, buffer)
|
||||||
errChan := make(chan error, buffer)
|
errChan := make(chan error, buffer)
|
@@ -1,3 +1,6 @@
|
|||||||
|
|
||||||
|
Copyright (c) 2011-2014 - Canonical Inc.
|
||||||
|
|
||||||
This software is licensed under the LGPLv3, included below.
|
This software is licensed under the LGPLv3, included below.
|
||||||
|
|
||||||
As a special exception to the GNU Lesser General Public License version 3
|
As a special exception to the GNU Lesser General Public License version 3
|
@@ -1,3 +1,15 @@
|
|||||||
|
The following files were ported to Go from C files of libyaml, and thus
|
||||||
|
are still covered by their original copyright and license:
|
||||||
|
|
||||||
|
apic.go
|
||||||
|
emitterc.go
|
||||||
|
parserc.go
|
||||||
|
readerc.go
|
||||||
|
scannerc.go
|
||||||
|
writerc.go
|
||||||
|
yamlh.go
|
||||||
|
yamlprivateh.go
|
||||||
|
|
||||||
Copyright (c) 2006 Kirill Simonov
|
Copyright (c) 2006 Kirill Simonov
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
131
Godeps/_workspace/src/github.com/coreos/yaml/README.md
generated
vendored
Normal file
131
Godeps/_workspace/src/github.com/coreos/yaml/README.md
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
Note: This is a fork of https://github.com/go-yaml/yaml. The following README
|
||||||
|
doesn't necessarily apply to this fork.
|
||||||
|
|
||||||
|
# YAML support for the Go language
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
------------
|
||||||
|
|
||||||
|
The yaml package enables Go programs to comfortably encode and decode YAML
|
||||||
|
values. It was developed within [Canonical](https://www.canonical.com) as
|
||||||
|
part of the [juju](https://juju.ubuntu.com) project, and is based on a
|
||||||
|
pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML)
|
||||||
|
C library to parse and generate YAML data quickly and reliably.
|
||||||
|
|
||||||
|
Compatibility
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The yaml package supports most of YAML 1.1 and 1.2, including support for
|
||||||
|
anchors, tags, map merging, etc. Multi-document unmarshalling is not yet
|
||||||
|
implemented, and base-60 floats from YAML 1.1 are purposefully not
|
||||||
|
supported since they're a poor design and are gone in YAML 1.2.
|
||||||
|
|
||||||
|
Installation and usage
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
The import path for the package is *gopkg.in/yaml.v1*.
|
||||||
|
|
||||||
|
To install it, run:
|
||||||
|
|
||||||
|
go get gopkg.in/yaml.v1
|
||||||
|
|
||||||
|
API documentation
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
If opened in a browser, the import path itself leads to the API documentation:
|
||||||
|
|
||||||
|
* [https://gopkg.in/yaml.v1](https://gopkg.in/yaml.v1)
|
||||||
|
|
||||||
|
API stability
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The package API for yaml v1 will remain stable as described in [gopkg.in](https://gopkg.in).
|
||||||
|
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
The yaml package is licensed under the LGPL with an exception that allows it to be linked statically. Please see the LICENSE file for details.
|
||||||
|
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
```Go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var data = `
|
||||||
|
a: Easy!
|
||||||
|
b:
|
||||||
|
c: 2
|
||||||
|
d: [3, 4]
|
||||||
|
`
|
||||||
|
|
||||||
|
type T struct {
|
||||||
|
A string
|
||||||
|
B struct{C int; D []int ",flow"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
t := T{}
|
||||||
|
|
||||||
|
err := yaml.Unmarshal([]byte(data), &t)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("--- t:\n%v\n\n", t)
|
||||||
|
|
||||||
|
d, err := yaml.Marshal(&t)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("--- t dump:\n%s\n\n", string(d))
|
||||||
|
|
||||||
|
m := make(map[interface{}]interface{})
|
||||||
|
|
||||||
|
err = yaml.Unmarshal([]byte(data), &m)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("--- m:\n%v\n\n", m)
|
||||||
|
|
||||||
|
d, err = yaml.Marshal(&m)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("--- m dump:\n%s\n\n", string(d))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This example will generate the following output:
|
||||||
|
|
||||||
|
```
|
||||||
|
--- t:
|
||||||
|
{Easy! {2 [3 4]}}
|
||||||
|
|
||||||
|
--- t dump:
|
||||||
|
a: Easy!
|
||||||
|
b:
|
||||||
|
c: 2
|
||||||
|
d: [3, 4]
|
||||||
|
|
||||||
|
|
||||||
|
--- m:
|
||||||
|
map[a:Easy! b:map[c:2 d:[3 4]]]
|
||||||
|
|
||||||
|
--- m dump:
|
||||||
|
a: Easy!
|
||||||
|
b:
|
||||||
|
c: 2
|
||||||
|
d:
|
||||||
|
- 3
|
||||||
|
- 4
|
||||||
|
```
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
package goyaml
|
package yaml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
@@ -1,8 +1,11 @@
|
|||||||
package goyaml
|
package yaml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -30,10 +33,12 @@ type parser struct {
|
|||||||
parser yaml_parser_t
|
parser yaml_parser_t
|
||||||
event yaml_event_t
|
event yaml_event_t
|
||||||
doc *node
|
doc *node
|
||||||
|
transform transformString
|
||||||
}
|
}
|
||||||
|
|
||||||
func newParser(b []byte) *parser {
|
func newParser(b []byte, t transformString) *parser {
|
||||||
p := parser{}
|
p := parser{transform: t}
|
||||||
|
|
||||||
if !yaml_parser_initialize(&p.parser) {
|
if !yaml_parser_initialize(&p.parser) {
|
||||||
panic("Failed to initialize YAML emitter")
|
panic("Failed to initialize YAML emitter")
|
||||||
}
|
}
|
||||||
@@ -62,7 +67,7 @@ func (p *parser) destroy() {
|
|||||||
func (p *parser) skip() {
|
func (p *parser) skip() {
|
||||||
if p.event.typ != yaml_NO_EVENT {
|
if p.event.typ != yaml_NO_EVENT {
|
||||||
if p.event.typ == yaml_STREAM_END_EVENT {
|
if p.event.typ == yaml_STREAM_END_EVENT {
|
||||||
panic("Attempted to go past the end of stream. Corrupted value?")
|
fail("Attempted to go past the end of stream. Corrupted value?")
|
||||||
}
|
}
|
||||||
yaml_event_delete(&p.event)
|
yaml_event_delete(&p.event)
|
||||||
}
|
}
|
||||||
@@ -88,7 +93,7 @@ func (p *parser) fail() {
|
|||||||
} else {
|
} else {
|
||||||
msg = "Unknown problem parsing YAML content"
|
msg = "Unknown problem parsing YAML content"
|
||||||
}
|
}
|
||||||
panic(where + msg)
|
fail(where + msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) anchor(n *node, anchor []byte) {
|
func (p *parser) anchor(n *node, anchor []byte) {
|
||||||
@@ -113,10 +118,9 @@ func (p *parser) parse() *node {
|
|||||||
// Happens when attempting to decode an empty buffer.
|
// Happens when attempting to decode an empty buffer.
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
panic("Attempted to parse unknown event: " +
|
panic("Attempted to parse unknown event: " + strconv.Itoa(int(p.event.typ)))
|
||||||
strconv.Itoa(int(p.event.typ)))
|
|
||||||
}
|
}
|
||||||
panic("Unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) node(kind int) *node {
|
func (p *parser) node(kind int) *node {
|
||||||
@@ -134,8 +138,7 @@ func (p *parser) document() *node {
|
|||||||
p.skip()
|
p.skip()
|
||||||
n.children = append(n.children, p.parse())
|
n.children = append(n.children, p.parse())
|
||||||
if p.event.typ != yaml_DOCUMENT_END_EVENT {
|
if p.event.typ != yaml_DOCUMENT_END_EVENT {
|
||||||
panic("Expected end of document event but got " +
|
panic("Expected end of document event but got " + strconv.Itoa(int(p.event.typ)))
|
||||||
strconv.Itoa(int(p.event.typ)))
|
|
||||||
}
|
}
|
||||||
p.skip()
|
p.skip()
|
||||||
return n
|
return n
|
||||||
@@ -174,7 +177,10 @@ func (p *parser) mapping() *node {
|
|||||||
p.anchor(n, p.event.anchor)
|
p.anchor(n, p.event.anchor)
|
||||||
p.skip()
|
p.skip()
|
||||||
for p.event.typ != yaml_MAPPING_END_EVENT {
|
for p.event.typ != yaml_MAPPING_END_EVENT {
|
||||||
n.children = append(n.children, p.parse(), p.parse())
|
key := p.parse()
|
||||||
|
key.value = p.transform(key.value)
|
||||||
|
value := p.parse()
|
||||||
|
n.children = append(n.children, key, value)
|
||||||
}
|
}
|
||||||
p.skip()
|
p.skip()
|
||||||
return n
|
return n
|
||||||
@@ -211,11 +217,21 @@ func newDecoder() *decoder {
|
|||||||
// returned to call SetYAML() with the value of *out once it's defined.
|
// returned to call SetYAML() with the value of *out once it's defined.
|
||||||
//
|
//
|
||||||
func (d *decoder) setter(tag string, out *reflect.Value, good *bool) (set func()) {
|
func (d *decoder) setter(tag string, out *reflect.Value, good *bool) (set func()) {
|
||||||
|
if (*out).Kind() != reflect.Ptr && (*out).CanAddr() {
|
||||||
|
setter, _ := (*out).Addr().Interface().(Setter)
|
||||||
|
if setter != nil {
|
||||||
|
var arg interface{}
|
||||||
|
*out = reflect.ValueOf(&arg).Elem()
|
||||||
|
return func() {
|
||||||
|
*good = setter.SetYAML(shortTag(tag), arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
again := true
|
again := true
|
||||||
for again {
|
for again {
|
||||||
again = false
|
again = false
|
||||||
setter, _ := (*out).Interface().(Setter)
|
setter, _ := (*out).Interface().(Setter)
|
||||||
if tag != "!!null" || setter != nil {
|
if tag != yaml_NULL_TAG || setter != nil {
|
||||||
if pv := (*out); pv.Kind() == reflect.Ptr {
|
if pv := (*out); pv.Kind() == reflect.Ptr {
|
||||||
if pv.IsNil() {
|
if pv.IsNil() {
|
||||||
*out = reflect.New(pv.Type().Elem()).Elem()
|
*out = reflect.New(pv.Type().Elem()).Elem()
|
||||||
@@ -231,7 +247,7 @@ func (d *decoder) setter(tag string, out *reflect.Value, good *bool) (set func()
|
|||||||
var arg interface{}
|
var arg interface{}
|
||||||
*out = reflect.ValueOf(&arg).Elem()
|
*out = reflect.ValueOf(&arg).Elem()
|
||||||
return func() {
|
return func() {
|
||||||
*good = setter.SetYAML(tag, arg)
|
*good = setter.SetYAML(shortTag(tag), arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,10 +284,10 @@ func (d *decoder) document(n *node, out reflect.Value) (good bool) {
|
|||||||
func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
|
func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
|
||||||
an, ok := d.doc.anchors[n.value]
|
an, ok := d.doc.anchors[n.value]
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("Unknown anchor '" + n.value + "' referenced")
|
fail("Unknown anchor '" + n.value + "' referenced")
|
||||||
}
|
}
|
||||||
if d.aliases[n.value] {
|
if d.aliases[n.value] {
|
||||||
panic("Anchor '" + n.value + "' value contains itself")
|
fail("Anchor '" + n.value + "' value contains itself")
|
||||||
}
|
}
|
||||||
d.aliases[n.value] = true
|
d.aliases[n.value] = true
|
||||||
good = d.unmarshal(an, out)
|
good = d.unmarshal(an, out)
|
||||||
@@ -279,20 +295,50 @@ func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
|
|||||||
return good
|
return good
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var zeroValue reflect.Value
|
||||||
|
|
||||||
|
func resetMap(out reflect.Value) {
|
||||||
|
for _, k := range out.MapKeys() {
|
||||||
|
out.SetMapIndex(k, zeroValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var durationType = reflect.TypeOf(time.Duration(0))
|
||||||
|
|
||||||
func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
|
func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
|
||||||
var tag string
|
var tag string
|
||||||
var resolved interface{}
|
var resolved interface{}
|
||||||
if n.tag == "" && !n.implicit {
|
if n.tag == "" && !n.implicit {
|
||||||
|
tag = yaml_STR_TAG
|
||||||
resolved = n.value
|
resolved = n.value
|
||||||
} else {
|
} else {
|
||||||
tag, resolved = resolve(n.tag, n.value)
|
tag, resolved = resolve(n.tag, n.value)
|
||||||
|
if tag == yaml_BINARY_TAG {
|
||||||
|
data, err := base64.StdEncoding.DecodeString(resolved.(string))
|
||||||
|
if err != nil {
|
||||||
|
fail("!!binary value contains invalid base64 data")
|
||||||
|
}
|
||||||
|
resolved = string(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
if set := d.setter(tag, &out, &good); set != nil {
|
if set := d.setter(tag, &out, &good); set != nil {
|
||||||
defer set()
|
defer set()
|
||||||
}
|
}
|
||||||
|
if resolved == nil {
|
||||||
|
if out.Kind() == reflect.Map && !out.CanAddr() {
|
||||||
|
resetMap(out)
|
||||||
|
} else {
|
||||||
|
out.Set(reflect.Zero(out.Type()))
|
||||||
|
}
|
||||||
|
good = true
|
||||||
|
return
|
||||||
}
|
}
|
||||||
switch out.Kind() {
|
switch out.Kind() {
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
if resolved != nil {
|
if tag == yaml_BINARY_TAG {
|
||||||
|
out.SetString(resolved.(string))
|
||||||
|
good = true
|
||||||
|
} else if resolved != nil {
|
||||||
out.SetString(n.value)
|
out.SetString(n.value)
|
||||||
good = true
|
good = true
|
||||||
}
|
}
|
||||||
@@ -320,6 +366,14 @@ func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
|
|||||||
out.SetInt(int64(resolved))
|
out.SetInt(int64(resolved))
|
||||||
good = true
|
good = true
|
||||||
}
|
}
|
||||||
|
case string:
|
||||||
|
if out.Type() == durationType {
|
||||||
|
d, err := time.ParseDuration(resolved)
|
||||||
|
if err == nil {
|
||||||
|
out.SetInt(int64(d))
|
||||||
|
good = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
switch resolved := resolved.(type) {
|
switch resolved := resolved.(type) {
|
||||||
@@ -358,11 +412,6 @@ func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
|
|||||||
good = true
|
good = true
|
||||||
}
|
}
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
switch resolved.(type) {
|
|
||||||
case nil:
|
|
||||||
out.Set(reflect.Zero(out.Type()))
|
|
||||||
good = true
|
|
||||||
default:
|
|
||||||
if out.Type().Elem() == reflect.TypeOf(resolved) {
|
if out.Type().Elem() == reflect.TypeOf(resolved) {
|
||||||
elem := reflect.New(out.Type().Elem())
|
elem := reflect.New(out.Type().Elem())
|
||||||
elem.Elem().Set(reflect.ValueOf(resolved))
|
elem.Elem().Set(reflect.ValueOf(resolved))
|
||||||
@@ -370,7 +419,6 @@ func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
|
|||||||
good = true
|
good = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return good
|
return good
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +430,7 @@ func settableValueOf(i interface{}) reflect.Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
|
func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
|
||||||
if set := d.setter("!!seq", &out, &good); set != nil {
|
if set := d.setter(yaml_SEQ_TAG, &out, &good); set != nil {
|
||||||
defer set()
|
defer set()
|
||||||
}
|
}
|
||||||
var iface reflect.Value
|
var iface reflect.Value
|
||||||
@@ -411,7 +459,7 @@ func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
|
func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
|
||||||
if set := d.setter("!!map", &out, &good); set != nil {
|
if set := d.setter(yaml_MAP_TAG, &out, &good); set != nil {
|
||||||
defer set()
|
defer set()
|
||||||
}
|
}
|
||||||
if out.Kind() == reflect.Struct {
|
if out.Kind() == reflect.Struct {
|
||||||
@@ -437,8 +485,19 @@ func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
|
|||||||
}
|
}
|
||||||
l := len(n.children)
|
l := len(n.children)
|
||||||
for i := 0; i < l; i += 2 {
|
for i := 0; i < l; i += 2 {
|
||||||
|
if isMerge(n.children[i]) {
|
||||||
|
d.merge(n.children[i+1], out)
|
||||||
|
continue
|
||||||
|
}
|
||||||
k := reflect.New(kt).Elem()
|
k := reflect.New(kt).Elem()
|
||||||
if d.unmarshal(n.children[i], k) {
|
if d.unmarshal(n.children[i], k) {
|
||||||
|
kkind := k.Kind()
|
||||||
|
if kkind == reflect.Interface {
|
||||||
|
kkind = k.Elem().Kind()
|
||||||
|
}
|
||||||
|
if kkind == reflect.Map || kkind == reflect.Slice {
|
||||||
|
fail(fmt.Sprintf("invalid map key: %#v", k.Interface()))
|
||||||
|
}
|
||||||
e := reflect.New(et).Elem()
|
e := reflect.New(et).Elem()
|
||||||
if d.unmarshal(n.children[i+1], e) {
|
if d.unmarshal(n.children[i+1], e) {
|
||||||
out.SetMapIndex(k, e)
|
out.SetMapIndex(k, e)
|
||||||
@@ -456,7 +515,12 @@ func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
|
|||||||
name := settableValueOf("")
|
name := settableValueOf("")
|
||||||
l := len(n.children)
|
l := len(n.children)
|
||||||
for i := 0; i < l; i += 2 {
|
for i := 0; i < l; i += 2 {
|
||||||
if !d.unmarshal(n.children[i], name) {
|
ni := n.children[i]
|
||||||
|
if isMerge(ni) {
|
||||||
|
d.merge(n.children[i+1], out)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !d.unmarshal(ni, name) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if info, ok := sinfo.FieldsMap[name.String()]; ok {
|
if info, ok := sinfo.FieldsMap[name.String()]; ok {
|
||||||
@@ -471,3 +535,37 @@ func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *decoder) merge(n *node, out reflect.Value) {
|
||||||
|
const wantMap = "map merge requires map or sequence of maps as the value"
|
||||||
|
switch n.kind {
|
||||||
|
case mappingNode:
|
||||||
|
d.unmarshal(n, out)
|
||||||
|
case aliasNode:
|
||||||
|
an, ok := d.doc.anchors[n.value]
|
||||||
|
if ok && an.kind != mappingNode {
|
||||||
|
fail(wantMap)
|
||||||
|
}
|
||||||
|
d.unmarshal(n, out)
|
||||||
|
case sequenceNode:
|
||||||
|
// Step backwards as earlier nodes take precedence.
|
||||||
|
for i := len(n.children) - 1; i >= 0; i-- {
|
||||||
|
ni := n.children[i]
|
||||||
|
if ni.kind == aliasNode {
|
||||||
|
an, ok := d.doc.anchors[ni.value]
|
||||||
|
if ok && an.kind != mappingNode {
|
||||||
|
fail(wantMap)
|
||||||
|
}
|
||||||
|
} else if ni.kind != mappingNode {
|
||||||
|
fail(wantMap)
|
||||||
|
}
|
||||||
|
d.unmarshal(ni, out)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fail(wantMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMerge(n *node) bool {
|
||||||
|
return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG)
|
||||||
|
}
|
@@ -1,10 +1,12 @@
|
|||||||
package goyaml_test
|
package yaml_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "launchpad.net/gocheck"
|
"github.com/coreos/yaml"
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/launchpad.net/goyaml"
|
. "gopkg.in/check.v1"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var unmarshalIntTest = 123
|
var unmarshalIntTest = 123
|
||||||
@@ -315,7 +317,10 @@ var unmarshalTests = []struct {
|
|||||||
map[string]*string{"foo": new(string)},
|
map[string]*string{"foo": new(string)},
|
||||||
}, {
|
}, {
|
||||||
"foo: null",
|
"foo: null",
|
||||||
map[string]string{},
|
map[string]string{"foo": ""},
|
||||||
|
}, {
|
||||||
|
"foo: null",
|
||||||
|
map[string]interface{}{"foo": nil},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Ignored field
|
// Ignored field
|
||||||
@@ -350,6 +355,50 @@ var unmarshalTests = []struct {
|
|||||||
C inlineB `yaml:",inline"`
|
C inlineB `yaml:",inline"`
|
||||||
}{1, inlineB{2, inlineC{3}}},
|
}{1, inlineB{2, inlineC{3}}},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// bug 1243827
|
||||||
|
{
|
||||||
|
"a: -b_c",
|
||||||
|
map[string]interface{}{"a": "-b_c"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a: +b_c",
|
||||||
|
map[string]interface{}{"a": "+b_c"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a: 50cent_of_dollar",
|
||||||
|
map[string]interface{}{"a": "50cent_of_dollar"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Duration
|
||||||
|
{
|
||||||
|
"a: 3s",
|
||||||
|
map[string]time.Duration{"a": 3 * time.Second},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Issue #24.
|
||||||
|
{
|
||||||
|
"a: <foo>",
|
||||||
|
map[string]string{"a": "<foo>"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Base 60 floats are obsolete and unsupported.
|
||||||
|
{
|
||||||
|
"a: 1:1\n",
|
||||||
|
map[string]string{"a": "1:1"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Binary data.
|
||||||
|
{
|
||||||
|
"a: !!binary gIGC\n",
|
||||||
|
map[string]string{"a": "\x80\x81\x82"},
|
||||||
|
}, {
|
||||||
|
"a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n",
|
||||||
|
map[string]string{"a": strings.Repeat("\x90", 54)},
|
||||||
|
}, {
|
||||||
|
"a: !!binary |\n " + strings.Repeat("A", 70) + "\n ==\n",
|
||||||
|
map[string]string{"a": strings.Repeat("\x00", 52)},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type inlineB struct {
|
type inlineB struct {
|
||||||
@@ -377,7 +426,7 @@ func (s *S) TestUnmarshal(c *C) {
|
|||||||
pv := reflect.New(pt.Elem())
|
pv := reflect.New(pt.Elem())
|
||||||
value = pv.Interface()
|
value = pv.Interface()
|
||||||
}
|
}
|
||||||
err := goyaml.Unmarshal([]byte(item.data), value)
|
err := yaml.Unmarshal([]byte(item.data), value)
|
||||||
c.Assert(err, IsNil, Commentf("Item #%d", i))
|
c.Assert(err, IsNil, Commentf("Item #%d", i))
|
||||||
if t.Kind() == reflect.String {
|
if t.Kind() == reflect.String {
|
||||||
c.Assert(*value.(*string), Equals, item.value, Commentf("Item #%d", i))
|
c.Assert(*value.(*string), Equals, item.value, Commentf("Item #%d", i))
|
||||||
@@ -389,7 +438,7 @@ func (s *S) TestUnmarshal(c *C) {
|
|||||||
|
|
||||||
func (s *S) TestUnmarshalNaN(c *C) {
|
func (s *S) TestUnmarshalNaN(c *C) {
|
||||||
value := map[string]interface{}{}
|
value := map[string]interface{}{}
|
||||||
err := goyaml.Unmarshal([]byte("notanum: .NaN"), &value)
|
err := yaml.Unmarshal([]byte("notanum: .NaN"), &value)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(math.IsNaN(value["notanum"].(float64)), Equals, true)
|
c.Assert(math.IsNaN(value["notanum"].(float64)), Equals, true)
|
||||||
}
|
}
|
||||||
@@ -397,18 +446,21 @@ func (s *S) TestUnmarshalNaN(c *C) {
|
|||||||
var unmarshalErrorTests = []struct {
|
var unmarshalErrorTests = []struct {
|
||||||
data, error string
|
data, error string
|
||||||
}{
|
}{
|
||||||
{"v: !!float 'error'", "YAML error: Can't decode !!str 'error' as a !!float"},
|
{"v: !!float 'error'", "YAML error: cannot decode !!str `error` as a !!float"},
|
||||||
{"v: [A,", "YAML error: line 1: did not find expected node content"},
|
{"v: [A,", "YAML error: line 1: did not find expected node content"},
|
||||||
{"v:\n- [A,", "YAML error: line 2: did not find expected node content"},
|
{"v:\n- [A,", "YAML error: line 2: did not find expected node content"},
|
||||||
{"a: *b\n", "YAML error: Unknown anchor 'b' referenced"},
|
{"a: *b\n", "YAML error: Unknown anchor 'b' referenced"},
|
||||||
{"a: &a\n b: *a\n", "YAML error: Anchor 'a' value contains itself"},
|
{"a: &a\n b: *a\n", "YAML error: Anchor 'a' value contains itself"},
|
||||||
{"value: -", "YAML error: block sequence entries are not allowed in this context"},
|
{"value: -", "YAML error: block sequence entries are not allowed in this context"},
|
||||||
|
{"a: !!binary ==", "YAML error: !!binary value contains invalid base64 data"},
|
||||||
|
{"{[.]}", `YAML error: invalid map key: \[\]interface \{\}\{"\."\}`},
|
||||||
|
{"{{.}}", `YAML error: invalid map key: map\[interface\ \{\}\]interface \{\}\{".":interface \{\}\(nil\)\}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *S) TestUnmarshalErrors(c *C) {
|
func (s *S) TestUnmarshalErrors(c *C) {
|
||||||
for _, item := range unmarshalErrorTests {
|
for _, item := range unmarshalErrorTests {
|
||||||
var value interface{}
|
var value interface{}
|
||||||
err := goyaml.Unmarshal([]byte(item.data), &value)
|
err := yaml.Unmarshal([]byte(item.data), &value)
|
||||||
c.Assert(err, ErrorMatches, item.error, Commentf("Partial unmarshal: %#v", value))
|
c.Assert(err, ErrorMatches, item.error, Commentf("Partial unmarshal: %#v", value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -421,6 +473,8 @@ var setterTests = []struct {
|
|||||||
{"_: [1,A]", "!!seq", []interface{}{1, "A"}},
|
{"_: [1,A]", "!!seq", []interface{}{1, "A"}},
|
||||||
{"_: 10", "!!int", 10},
|
{"_: 10", "!!int", 10},
|
||||||
{"_: null", "!!null", nil},
|
{"_: null", "!!null", nil},
|
||||||
|
{`_: BAR!`, "!!str", "BAR!"},
|
||||||
|
{`_: "BAR!"`, "!!str", "BAR!"},
|
||||||
{"_: !!foo 'BAR!'", "!!foo", "BAR!"},
|
{"_: !!foo 'BAR!'", "!!foo", "BAR!"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,17 +496,31 @@ func (o *typeWithSetter) SetYAML(tag string, value interface{}) (ok bool) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type typeWithSetterField struct {
|
type setterPointerType struct {
|
||||||
Field *typeWithSetter "_"
|
Field *typeWithSetter "_"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *S) TestUnmarshalWithSetter(c *C) {
|
type setterValueType struct {
|
||||||
|
Field typeWithSetter "_"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) TestUnmarshalWithPointerSetter(c *C) {
|
||||||
for _, item := range setterTests {
|
for _, item := range setterTests {
|
||||||
obj := &typeWithSetterField{}
|
obj := &setterPointerType{}
|
||||||
err := goyaml.Unmarshal([]byte(item.data), obj)
|
err := yaml.Unmarshal([]byte(item.data), obj)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(obj.Field, NotNil,
|
c.Assert(obj.Field, NotNil, Commentf("Pointer not initialized (%#v)", item.value))
|
||||||
Commentf("Pointer not initialized (%#v)", item.value))
|
c.Assert(obj.Field.tag, Equals, item.tag)
|
||||||
|
c.Assert(obj.Field.value, DeepEquals, item.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) TestUnmarshalWithValueSetter(c *C) {
|
||||||
|
for _, item := range setterTests {
|
||||||
|
obj := &setterValueType{}
|
||||||
|
err := yaml.Unmarshal([]byte(item.data), obj)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(obj.Field, NotNil, Commentf("Pointer not initialized (%#v)", item.value))
|
||||||
c.Assert(obj.Field.tag, Equals, item.tag)
|
c.Assert(obj.Field.tag, Equals, item.tag)
|
||||||
c.Assert(obj.Field.value, DeepEquals, item.value)
|
c.Assert(obj.Field.value, DeepEquals, item.value)
|
||||||
}
|
}
|
||||||
@@ -460,7 +528,7 @@ func (s *S) TestUnmarshalWithSetter(c *C) {
|
|||||||
|
|
||||||
func (s *S) TestUnmarshalWholeDocumentWithSetter(c *C) {
|
func (s *S) TestUnmarshalWholeDocumentWithSetter(c *C) {
|
||||||
obj := &typeWithSetter{}
|
obj := &typeWithSetter{}
|
||||||
err := goyaml.Unmarshal([]byte(setterTests[0].data), obj)
|
err := yaml.Unmarshal([]byte(setterTests[0].data), obj)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(obj.tag, Equals, setterTests[0].tag)
|
c.Assert(obj.tag, Equals, setterTests[0].tag)
|
||||||
value, ok := obj.value.(map[interface{}]interface{})
|
value, ok := obj.value.(map[interface{}]interface{})
|
||||||
@@ -477,8 +545,8 @@ func (s *S) TestUnmarshalWithFalseSetterIgnoresValue(c *C) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
m := map[string]*typeWithSetter{}
|
m := map[string]*typeWithSetter{}
|
||||||
data := "{abc: 1, def: 2, ghi: 3, jkl: 4}"
|
data := `{abc: 1, def: 2, ghi: 3, jkl: 4}`
|
||||||
err := goyaml.Unmarshal([]byte(data), m)
|
err := yaml.Unmarshal([]byte(data), m)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(m["abc"], NotNil)
|
c.Assert(m["abc"], NotNil)
|
||||||
c.Assert(m["def"], IsNil)
|
c.Assert(m["def"], IsNil)
|
||||||
@@ -489,6 +557,139 @@ func (s *S) TestUnmarshalWithFalseSetterIgnoresValue(c *C) {
|
|||||||
c.Assert(m["ghi"].value, Equals, 3)
|
c.Assert(m["ghi"].value, Equals, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *S) TestUnmarshalWithTransform(c *C) {
|
||||||
|
data := `{a_b: 1, c-d: 2, e-f_g: 3, h_i-j: 4}`
|
||||||
|
expect := map[string]int{
|
||||||
|
"a_b": 1,
|
||||||
|
"c_d": 2,
|
||||||
|
"e_f_g": 3,
|
||||||
|
"h_i_j": 4,
|
||||||
|
}
|
||||||
|
m := map[string]int{}
|
||||||
|
yaml.UnmarshalMappingKeyTransform = func(i string) string {
|
||||||
|
return strings.Replace(i, "-", "_", -1)
|
||||||
|
}
|
||||||
|
err := yaml.Unmarshal([]byte(data), m)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Assert(m, DeepEquals, expect)
|
||||||
|
}
|
||||||
|
|
||||||
|
// From http://yaml.org/type/merge.html
|
||||||
|
var mergeTests = `
|
||||||
|
anchors:
|
||||||
|
- &CENTER { "x": 1, "y": 2 }
|
||||||
|
- &LEFT { "x": 0, "y": 2 }
|
||||||
|
- &BIG { "r": 10 }
|
||||||
|
- &SMALL { "r": 1 }
|
||||||
|
|
||||||
|
# All the following maps are equal:
|
||||||
|
|
||||||
|
plain:
|
||||||
|
# Explicit keys
|
||||||
|
"x": 1
|
||||||
|
"y": 2
|
||||||
|
"r": 10
|
||||||
|
label: center/big
|
||||||
|
|
||||||
|
mergeOne:
|
||||||
|
# Merge one map
|
||||||
|
<< : *CENTER
|
||||||
|
"r": 10
|
||||||
|
label: center/big
|
||||||
|
|
||||||
|
mergeMultiple:
|
||||||
|
# Merge multiple maps
|
||||||
|
<< : [ *CENTER, *BIG ]
|
||||||
|
label: center/big
|
||||||
|
|
||||||
|
override:
|
||||||
|
# Override
|
||||||
|
<< : [ *BIG, *LEFT, *SMALL ]
|
||||||
|
"x": 1
|
||||||
|
label: center/big
|
||||||
|
|
||||||
|
shortTag:
|
||||||
|
# Explicit short merge tag
|
||||||
|
!!merge "<<" : [ *CENTER, *BIG ]
|
||||||
|
label: center/big
|
||||||
|
|
||||||
|
longTag:
|
||||||
|
# Explicit merge long tag
|
||||||
|
!<tag:yaml.org,2002:merge> "<<" : [ *CENTER, *BIG ]
|
||||||
|
label: center/big
|
||||||
|
|
||||||
|
inlineMap:
|
||||||
|
# Inlined map
|
||||||
|
<< : {"x": 1, "y": 2, "r": 10}
|
||||||
|
label: center/big
|
||||||
|
|
||||||
|
inlineSequenceMap:
|
||||||
|
# Inlined map in sequence
|
||||||
|
<< : [ *CENTER, {"r": 10} ]
|
||||||
|
label: center/big
|
||||||
|
`
|
||||||
|
|
||||||
|
func (s *S) TestMerge(c *C) {
|
||||||
|
var want = map[interface{}]interface{}{
|
||||||
|
"x": 1,
|
||||||
|
"y": 2,
|
||||||
|
"r": 10,
|
||||||
|
"label": "center/big",
|
||||||
|
}
|
||||||
|
|
||||||
|
var m map[string]interface{}
|
||||||
|
err := yaml.Unmarshal([]byte(mergeTests), &m)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
for name, test := range m {
|
||||||
|
if name == "anchors" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.Assert(test, DeepEquals, want, Commentf("test %q failed", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) TestMergeStruct(c *C) {
|
||||||
|
type Data struct {
|
||||||
|
X, Y, R int
|
||||||
|
Label string
|
||||||
|
}
|
||||||
|
want := Data{1, 2, 10, "center/big"}
|
||||||
|
|
||||||
|
var m map[string]Data
|
||||||
|
err := yaml.Unmarshal([]byte(mergeTests), &m)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
for name, test := range m {
|
||||||
|
if name == "anchors" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.Assert(test, Equals, want, Commentf("test %q failed", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var unmarshalNullTests = []func() interface{}{
|
||||||
|
func() interface{} { var v interface{}; v = "v"; return &v },
|
||||||
|
func() interface{} { var s = "s"; return &s },
|
||||||
|
func() interface{} { var s = "s"; sptr := &s; return &sptr },
|
||||||
|
func() interface{} { var i = 1; return &i },
|
||||||
|
func() interface{} { var i = 1; iptr := &i; return &iptr },
|
||||||
|
func() interface{} { m := map[string]int{"s": 1}; return &m },
|
||||||
|
func() interface{} { m := map[string]int{"s": 1}; return m },
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) TestUnmarshalNull(c *C) {
|
||||||
|
for _, test := range unmarshalNullTests {
|
||||||
|
item := test()
|
||||||
|
zero := reflect.Zero(reflect.TypeOf(item).Elem()).Interface()
|
||||||
|
err := yaml.Unmarshal([]byte("null"), item)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
if reflect.TypeOf(item).Kind() == reflect.Map {
|
||||||
|
c.Assert(reflect.ValueOf(item).Interface(), DeepEquals, reflect.MakeMap(reflect.TypeOf(item)).Interface())
|
||||||
|
} else {
|
||||||
|
c.Assert(reflect.ValueOf(item).Elem().Interface(), DeepEquals, zero)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//var data []byte
|
//var data []byte
|
||||||
//func init() {
|
//func init() {
|
||||||
// var err error
|
// var err error
|
||||||
@@ -502,7 +703,7 @@ func (s *S) TestUnmarshalWithFalseSetterIgnoresValue(c *C) {
|
|||||||
// var err error
|
// var err error
|
||||||
// for i := 0; i < c.N; i++ {
|
// for i := 0; i < c.N; i++ {
|
||||||
// var v map[string]interface{}
|
// var v map[string]interface{}
|
||||||
// err = goyaml.Unmarshal(data, &v)
|
// err = yaml.Unmarshal(data, &v)
|
||||||
// }
|
// }
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// panic(err)
|
// panic(err)
|
||||||
@@ -511,9 +712,9 @@ func (s *S) TestUnmarshalWithFalseSetterIgnoresValue(c *C) {
|
|||||||
//
|
//
|
||||||
//func (s *S) BenchmarkMarshal(c *C) {
|
//func (s *S) BenchmarkMarshal(c *C) {
|
||||||
// var v map[string]interface{}
|
// var v map[string]interface{}
|
||||||
// goyaml.Unmarshal(data, &v)
|
// yaml.Unmarshal(data, &v)
|
||||||
// c.ResetTimer()
|
// c.ResetTimer()
|
||||||
// for i := 0; i < c.N; i++ {
|
// for i := 0; i < c.N; i++ {
|
||||||
// goyaml.Marshal(&v)
|
// yaml.Marshal(&v)
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
@@ -1,4 +1,4 @@
|
|||||||
package goyaml
|
package yaml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -973,9 +973,9 @@ func yaml_emitter_analyze_tag(emitter *yaml_emitter_t, tag []byte) bool {
|
|||||||
if bytes.HasPrefix(tag, tag_directive.prefix) {
|
if bytes.HasPrefix(tag, tag_directive.prefix) {
|
||||||
emitter.tag_data.handle = tag_directive.handle
|
emitter.tag_data.handle = tag_directive.handle
|
||||||
emitter.tag_data.suffix = tag[len(tag_directive.prefix):]
|
emitter.tag_data.suffix = tag[len(tag_directive.prefix):]
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
emitter.tag_data.suffix = tag
|
emitter.tag_data.suffix = tag
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -1279,6 +1279,9 @@ func yaml_emitter_write_tag_content(emitter *yaml_emitter_t, value []byte, need_
|
|||||||
for k := 0; k < w; k++ {
|
for k := 0; k < w; k++ {
|
||||||
octet := value[i]
|
octet := value[i]
|
||||||
i++
|
i++
|
||||||
|
if !put(emitter, '%') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
c := octet >> 4
|
c := octet >> 4
|
||||||
if c < 10 {
|
if c < 10 {
|
@@ -1,9 +1,12 @@
|
|||||||
package goyaml
|
package yaml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type encoder struct {
|
type encoder struct {
|
||||||
@@ -49,14 +52,19 @@ func (e *encoder) must(ok bool) {
|
|||||||
if msg == "" {
|
if msg == "" {
|
||||||
msg = "Unknown problem generating YAML content"
|
msg = "Unknown problem generating YAML content"
|
||||||
}
|
}
|
||||||
panic(msg)
|
fail(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *encoder) marshal(tag string, in reflect.Value) {
|
func (e *encoder) marshal(tag string, in reflect.Value) {
|
||||||
|
if !in.IsValid() {
|
||||||
|
e.nilv()
|
||||||
|
return
|
||||||
|
}
|
||||||
var value interface{}
|
var value interface{}
|
||||||
if getter, ok := in.Interface().(Getter); ok {
|
if getter, ok := in.Interface().(Getter); ok {
|
||||||
tag, value = getter.GetYAML()
|
tag, value = getter.GetYAML()
|
||||||
|
tag = longTag(tag)
|
||||||
if value == nil {
|
if value == nil {
|
||||||
e.nilv()
|
e.nilv()
|
||||||
return
|
return
|
||||||
@@ -85,7 +93,11 @@ func (e *encoder) marshal(tag string, in reflect.Value) {
|
|||||||
case reflect.String:
|
case reflect.String:
|
||||||
e.stringv(tag, in)
|
e.stringv(tag, in)
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
if in.Type() == durationType {
|
||||||
|
e.stringv(tag, reflect.ValueOf(in.Interface().(time.Duration).String()))
|
||||||
|
} else {
|
||||||
e.intv(tag, in)
|
e.intv(tag, in)
|
||||||
|
}
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
e.uintv(tag, in)
|
e.uintv(tag, in)
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
@@ -93,7 +105,7 @@ func (e *encoder) marshal(tag string, in reflect.Value) {
|
|||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
e.boolv(tag, in)
|
e.boolv(tag, in)
|
||||||
default:
|
default:
|
||||||
panic("Can't marshal type yet: " + in.Type().String())
|
panic("Can't marshal type: " + in.Type().String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,11 +174,46 @@ func (e *encoder) slicev(tag string, in reflect.Value) {
|
|||||||
e.emit()
|
e.emit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
|
||||||
|
//
|
||||||
|
// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
|
||||||
|
// in YAML 1.2 and by this package, but these should be marshalled quoted for
|
||||||
|
// the time being for compatibility with other parsers.
|
||||||
|
func isBase60Float(s string) (result bool) {
|
||||||
|
// Fast path.
|
||||||
|
if s == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c := s[0]
|
||||||
|
if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Do the full match.
|
||||||
|
return base60float.MatchString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// From http://yaml.org/type/float.html, except the regular expression there
|
||||||
|
// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
|
||||||
|
var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
|
||||||
|
|
||||||
func (e *encoder) stringv(tag string, in reflect.Value) {
|
func (e *encoder) stringv(tag string, in reflect.Value) {
|
||||||
var style yaml_scalar_style_t
|
var style yaml_scalar_style_t
|
||||||
s := in.String()
|
s := in.String()
|
||||||
if rtag, _ := resolve("", s); rtag != "!!str" {
|
rtag, rs := resolve("", s)
|
||||||
|
if rtag == yaml_BINARY_TAG {
|
||||||
|
if tag == "" || tag == yaml_STR_TAG {
|
||||||
|
tag = rtag
|
||||||
|
s = rs.(string)
|
||||||
|
} else if tag == yaml_BINARY_TAG {
|
||||||
|
fail("explicitly tagged !!binary data must be base64-encoded")
|
||||||
|
} else {
|
||||||
|
fail("cannot marshal invalid UTF-8 data as " + shortTag(tag))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tag == "" && (rtag != yaml_STR_TAG || isBase60Float(s)) {
|
||||||
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
||||||
|
} else if strings.Contains(s, "\n") {
|
||||||
|
style = yaml_LITERAL_SCALAR_STYLE
|
||||||
} else {
|
} else {
|
||||||
style = yaml_PLAIN_SCALAR_STYLE
|
style = yaml_PLAIN_SCALAR_STYLE
|
||||||
}
|
}
|
||||||
@@ -213,9 +260,6 @@ func (e *encoder) nilv() {
|
|||||||
|
|
||||||
func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) {
|
func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) {
|
||||||
implicit := tag == ""
|
implicit := tag == ""
|
||||||
if !implicit {
|
|
||||||
style = yaml_PLAIN_SCALAR_STYLE
|
|
||||||
}
|
|
||||||
e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
|
e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
|
||||||
e.emit()
|
e.emit()
|
||||||
}
|
}
|
@@ -1,12 +1,14 @@
|
|||||||
package goyaml_test
|
package yaml_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
. "launchpad.net/gocheck"
|
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/launchpad.net/goyaml"
|
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/yaml"
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var marshalIntTest = 123
|
var marshalIntTest = 123
|
||||||
@@ -16,6 +18,9 @@ var marshalTests = []struct {
|
|||||||
data string
|
data string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
nil,
|
||||||
|
"null\n",
|
||||||
|
}, {
|
||||||
&struct{}{},
|
&struct{}{},
|
||||||
"{}\n",
|
"{}\n",
|
||||||
}, {
|
}, {
|
||||||
@@ -86,7 +91,7 @@ var marshalTests = []struct {
|
|||||||
"v:\n- A\n- B\n",
|
"v:\n- A\n- B\n",
|
||||||
}, {
|
}, {
|
||||||
map[string][]string{"v": []string{"A", "B\nC"}},
|
map[string][]string{"v": []string{"A", "B\nC"}},
|
||||||
"v:\n- A\n- 'B\n\n C'\n",
|
"v:\n- A\n- |-\n B\n C\n",
|
||||||
}, {
|
}, {
|
||||||
map[string][]interface{}{"v": []interface{}{"A", 1, map[string][]int{"B": []int{2, 3}}}},
|
map[string][]interface{}{"v": []interface{}{"A", 1, map[string][]int{"B": []int{2, 3}}}},
|
||||||
"v:\n- A\n- 1\n- B:\n - 2\n - 3\n",
|
"v:\n- A\n- 1\n- B:\n - 2\n - 3\n",
|
||||||
@@ -212,11 +217,51 @@ var marshalTests = []struct {
|
|||||||
}{1, inlineB{2, inlineC{3}}},
|
}{1, inlineB{2, inlineC{3}}},
|
||||||
"a: 1\nb: 2\nc: 3\n",
|
"a: 1\nb: 2\nc: 3\n",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Duration
|
||||||
|
{
|
||||||
|
map[string]time.Duration{"a": 3 * time.Second},
|
||||||
|
"a: 3s\n",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Issue #24: bug in map merging logic.
|
||||||
|
{
|
||||||
|
map[string]string{"a": "<foo>"},
|
||||||
|
"a: <foo>\n",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Issue #34: marshal unsupported base 60 floats quoted for compatibility
|
||||||
|
// with old YAML 1.1 parsers.
|
||||||
|
{
|
||||||
|
map[string]string{"a": "1:1"},
|
||||||
|
"a: \"1:1\"\n",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Binary data.
|
||||||
|
{
|
||||||
|
map[string]string{"a": "\x00"},
|
||||||
|
"a: \"\\0\"\n",
|
||||||
|
}, {
|
||||||
|
map[string]string{"a": "\x80\x81\x82"},
|
||||||
|
"a: !!binary gIGC\n",
|
||||||
|
}, {
|
||||||
|
map[string]string{"a": strings.Repeat("\x90", 54)},
|
||||||
|
"a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n",
|
||||||
|
}, {
|
||||||
|
map[string]interface{}{"a": typeWithGetter{"!!str", "\x80\x81\x82"}},
|
||||||
|
"a: !!binary gIGC\n",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Escaping of tags.
|
||||||
|
{
|
||||||
|
map[string]interface{}{"a": typeWithGetter{"foo!bar", 1}},
|
||||||
|
"a: !<foo%21bar> 1\n",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *S) TestMarshal(c *C) {
|
func (s *S) TestMarshal(c *C) {
|
||||||
for _, item := range marshalTests {
|
for _, item := range marshalTests {
|
||||||
data, err := goyaml.Marshal(item.value)
|
data, err := yaml.Marshal(item.value)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(string(data), Equals, item.data)
|
c.Assert(string(data), Equals, item.data)
|
||||||
}
|
}
|
||||||
@@ -225,21 +270,30 @@ func (s *S) TestMarshal(c *C) {
|
|||||||
var marshalErrorTests = []struct {
|
var marshalErrorTests = []struct {
|
||||||
value interface{}
|
value interface{}
|
||||||
error string
|
error string
|
||||||
}{
|
panic string
|
||||||
{
|
}{{
|
||||||
&struct {
|
value: &struct {
|
||||||
B int
|
B int
|
||||||
inlineB ",inline"
|
inlineB ",inline"
|
||||||
}{1, inlineB{2, inlineC{3}}},
|
}{1, inlineB{2, inlineC{3}}},
|
||||||
`Duplicated key 'b' in struct struct \{ B int; .*`,
|
panic: `Duplicated key 'b' in struct struct \{ B int; .*`,
|
||||||
},
|
}, {
|
||||||
}
|
value: typeWithGetter{"!!binary", "\x80"},
|
||||||
|
error: "YAML error: explicitly tagged !!binary data must be base64-encoded",
|
||||||
|
}, {
|
||||||
|
value: typeWithGetter{"!!float", "\x80"},
|
||||||
|
error: `YAML error: cannot marshal invalid UTF-8 data as !!float`,
|
||||||
|
}}
|
||||||
|
|
||||||
func (s *S) TestMarshalErrors(c *C) {
|
func (s *S) TestMarshalErrors(c *C) {
|
||||||
for _, item := range marshalErrorTests {
|
for _, item := range marshalErrorTests {
|
||||||
_, err := goyaml.Marshal(item.value)
|
if item.panic != "" {
|
||||||
|
c.Assert(func() { yaml.Marshal(item.value) }, PanicMatches, item.panic)
|
||||||
|
} else {
|
||||||
|
_, err := yaml.Marshal(item.value)
|
||||||
c.Assert(err, ErrorMatches, item.error)
|
c.Assert(err, ErrorMatches, item.error)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var marshalTaggedIfaceTest interface{} = &struct{ A string }{"B"}
|
var marshalTaggedIfaceTest interface{} = &struct{ A string }{"B"}
|
||||||
@@ -269,12 +323,12 @@ func (s *S) TestMarshalTypeCache(c *C) {
|
|||||||
var err error
|
var err error
|
||||||
func() {
|
func() {
|
||||||
type T struct{ A int }
|
type T struct{ A int }
|
||||||
data, err = goyaml.Marshal(&T{})
|
data, err = yaml.Marshal(&T{})
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
}()
|
}()
|
||||||
func() {
|
func() {
|
||||||
type T struct{ B int }
|
type T struct{ B int }
|
||||||
data, err = goyaml.Marshal(&T{})
|
data, err = yaml.Marshal(&T{})
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
}()
|
}()
|
||||||
c.Assert(string(data), Equals, "b: 0\n")
|
c.Assert(string(data), Equals, "b: 0\n")
|
||||||
@@ -298,7 +352,7 @@ func (s *S) TestMashalWithGetter(c *C) {
|
|||||||
obj := &typeWithGetterField{}
|
obj := &typeWithGetterField{}
|
||||||
obj.Field.tag = item.tag
|
obj.Field.tag = item.tag
|
||||||
obj.Field.value = item.value
|
obj.Field.value = item.value
|
||||||
data, err := goyaml.Marshal(obj)
|
data, err := yaml.Marshal(obj)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(string(data), Equals, string(item.data))
|
c.Assert(string(data), Equals, string(item.data))
|
||||||
}
|
}
|
||||||
@@ -308,7 +362,7 @@ func (s *S) TestUnmarshalWholeDocumentWithGetter(c *C) {
|
|||||||
obj := &typeWithGetter{}
|
obj := &typeWithGetter{}
|
||||||
obj.tag = ""
|
obj.tag = ""
|
||||||
obj.value = map[string]string{"hello": "world!"}
|
obj.value = map[string]string{"hello": "world!"}
|
||||||
data, err := goyaml.Marshal(obj)
|
data, err := yaml.Marshal(obj)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(string(data), Equals, "hello: world!\n")
|
c.Assert(string(data), Equals, "hello: world!\n")
|
||||||
}
|
}
|
||||||
@@ -356,7 +410,7 @@ func (s *S) TestSortedOutput(c *C) {
|
|||||||
for _, k := range order {
|
for _, k := range order {
|
||||||
m[k] = 1
|
m[k] = 1
|
||||||
}
|
}
|
||||||
data, err := goyaml.Marshal(m)
|
data, err := yaml.Marshal(m)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
out := "\n" + string(data)
|
out := "\n" + string(data)
|
||||||
last := 0
|
last := 0
|
@@ -1,4 +1,4 @@
|
|||||||
package goyaml
|
package yaml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
@@ -1,4 +1,4 @@
|
|||||||
package goyaml
|
package yaml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
190
Godeps/_workspace/src/github.com/coreos/yaml/resolve.go
generated
vendored
Normal file
190
Godeps/_workspace/src/github.com/coreos/yaml/resolve.go
generated
vendored
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}},
|
||||||
|
{true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}},
|
||||||
|
{true, yaml_BOOL_TAG, []string{"on", "On", "ON"}},
|
||||||
|
{false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}},
|
||||||
|
{false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}},
|
||||||
|
{false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}},
|
||||||
|
{nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}},
|
||||||
|
{math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}},
|
||||||
|
{math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}},
|
||||||
|
{math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}},
|
||||||
|
{math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}},
|
||||||
|
{"<<", yaml_MERGE_TAG, []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 {
|
||||||
|
// TODO This can easily be made faster and produce less garbage.
|
||||||
|
if strings.HasPrefix(tag, longTagPrefix) {
|
||||||
|
return "!!" + tag[len(longTagPrefix):]
|
||||||
|
}
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func longTag(tag string) string {
|
||||||
|
if strings.HasPrefix(tag, "!!") {
|
||||||
|
return longTagPrefix + tag[2:]
|
||||||
|
}
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolvableTag(tag string) bool {
|
||||||
|
switch tag {
|
||||||
|
case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolve(tag string, in string) (rtag string, out interface{}) {
|
||||||
|
if !resolvableTag(tag) {
|
||||||
|
return tag, in
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
switch tag {
|
||||||
|
case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fail(fmt.Sprintf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)))
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Any data is accepted as a !!str or !!binary.
|
||||||
|
// Otherwise, the prefix is enough of a hint about what it might be.
|
||||||
|
hint := byte('N')
|
||||||
|
if in != "" {
|
||||||
|
hint = resolveTable[in[0]]
|
||||||
|
}
|
||||||
|
if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG {
|
||||||
|
// Handle things we can lookup in a map.
|
||||||
|
if item, ok := resolveMap[in]; ok {
|
||||||
|
return item.tag, item.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base 60 floats are a bad idea, were dropped in YAML 1.2, and
|
||||||
|
// are purposefully unsupported here. They're still quoted on
|
||||||
|
// the way out for compatibility with other parser, though.
|
||||||
|
|
||||||
|
switch hint {
|
||||||
|
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 yaml_FLOAT_TAG, floatv
|
||||||
|
}
|
||||||
|
|
||||||
|
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 yaml_INT_TAG, int(intv)
|
||||||
|
} else {
|
||||||
|
return yaml_INT_TAG, intv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
floatv, err := strconv.ParseFloat(plain, 64)
|
||||||
|
if err == nil {
|
||||||
|
return yaml_FLOAT_TAG, floatv
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(plain, "0b") {
|
||||||
|
intv, err := strconv.ParseInt(plain[2:], 2, 64)
|
||||||
|
if err == nil {
|
||||||
|
return yaml_INT_TAG, int(intv)
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(plain, "-0b") {
|
||||||
|
intv, err := strconv.ParseInt(plain[3:], 2, 64)
|
||||||
|
if err == nil {
|
||||||
|
return yaml_INT_TAG, -int(intv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// XXX Handle timestamps here.
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tag == yaml_BINARY_TAG {
|
||||||
|
return yaml_BINARY_TAG, in
|
||||||
|
}
|
||||||
|
if utf8.ValidString(in) {
|
||||||
|
return yaml_STR_TAG, in
|
||||||
|
}
|
||||||
|
return yaml_BINARY_TAG, encodeBase64(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeBase64 encodes s as base64 that is broken up into multiple lines
|
||||||
|
// as appropriate for the resulting length.
|
||||||
|
func encodeBase64(s string) string {
|
||||||
|
const lineLen = 70
|
||||||
|
encLen := base64.StdEncoding.EncodedLen(len(s))
|
||||||
|
lines := encLen/lineLen + 1
|
||||||
|
buf := make([]byte, encLen*2+lines)
|
||||||
|
in := buf[0:encLen]
|
||||||
|
out := buf[encLen:]
|
||||||
|
base64.StdEncoding.Encode(in, []byte(s))
|
||||||
|
k := 0
|
||||||
|
for i := 0; i < len(in); i += lineLen {
|
||||||
|
j := i + lineLen
|
||||||
|
if j > len(in) {
|
||||||
|
j = len(in)
|
||||||
|
}
|
||||||
|
k += copy(out[k:], in[i:j])
|
||||||
|
if lines > 1 {
|
||||||
|
out[k] = '\n'
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(out[:k])
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package goyaml
|
package yaml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
@@ -1,4 +1,4 @@
|
|||||||
package goyaml
|
package yaml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
@@ -1,7 +1,7 @@
|
|||||||
package goyaml_test
|
package yaml_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "launchpad.net/gocheck"
|
. "gopkg.in/check.v1"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
package goyaml
|
package yaml
|
||||||
|
|
||||||
// Set the writer error and return false.
|
// Set the writer error and return false.
|
||||||
func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool {
|
func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool {
|
@@ -1,59 +1,60 @@
|
|||||||
// Package goyaml implements YAML support for the Go language.
|
// Package yaml implements YAML support for the Go language.
|
||||||
package goyaml
|
//
|
||||||
|
// Source code and other details for the project are available at GitHub:
|
||||||
|
//
|
||||||
|
// https://github.com/go-yaml/yaml
|
||||||
|
//
|
||||||
|
package yaml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type yamlError string
|
||||||
|
|
||||||
|
func fail(msg string) {
|
||||||
|
panic(yamlError(msg))
|
||||||
|
}
|
||||||
|
|
||||||
func handleErr(err *error) {
|
func handleErr(err *error) {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
if _, ok := r.(runtime.Error); ok {
|
if e, ok := r.(yamlError); ok {
|
||||||
panic(r)
|
*err = errors.New("YAML error: " + string(e))
|
||||||
} else if _, ok := r.(*reflect.ValueError); ok {
|
|
||||||
panic(r)
|
|
||||||
} else if _, ok := r.(externalPanic); ok {
|
|
||||||
panic(r)
|
|
||||||
} else if s, ok := r.(string); ok {
|
|
||||||
*err = errors.New("YAML error: " + s)
|
|
||||||
} else if e, ok := r.(error); ok {
|
|
||||||
*err = e
|
|
||||||
} else {
|
} else {
|
||||||
panic(r)
|
panic(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Objects implementing the goyaml.Setter interface will receive the YAML
|
// The Setter interface may be implemented by types to do their own custom
|
||||||
// tag and value via the SetYAML method during unmarshaling, rather than
|
// unmarshalling of YAML values, rather than being implicitly assigned by
|
||||||
// being implicitly assigned by the goyaml machinery. If setting the value
|
// the yaml package machinery. If setting the value works, the method should
|
||||||
// works, the method should return true. If it returns false, the given
|
// return true. If it returns false, the value is considered unsupported
|
||||||
// value will be omitted from maps and slices.
|
// and is omitted from maps and slices.
|
||||||
type Setter interface {
|
type Setter interface {
|
||||||
SetYAML(tag string, value interface{}) bool
|
SetYAML(tag string, value interface{}) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Objects implementing the goyaml.Getter interface will get the GetYAML()
|
// The Getter interface is implemented by types to do their own custom
|
||||||
// method called when goyaml is requested to marshal the given value, and
|
// marshalling into a YAML tag and value.
|
||||||
// the result of this method will be marshaled in place of the actual object.
|
|
||||||
type Getter interface {
|
type Getter interface {
|
||||||
GetYAML() (tag string, value interface{})
|
GetYAML() (tag string, value interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal decodes the first document found within the in byte slice
|
// Unmarshal decodes the first document found within the in byte slice
|
||||||
// and assigns decoded values into the object pointed by out.
|
// and assigns decoded values into the out value.
|
||||||
//
|
//
|
||||||
// Maps, pointers to structs and ints, etc, may all be used as out values.
|
// Maps and pointers (to a struct, string, int, etc) are accepted as out
|
||||||
// If an internal pointer within a struct is not initialized, goyaml
|
// values. If an internal pointer within a struct is not initialized,
|
||||||
// will initialize it if necessary for unmarshalling the provided data,
|
// the yaml package will initialize it if necessary for unmarshalling
|
||||||
// but the struct provided as out must not be a nil pointer.
|
// the provided data. The out parameter must not be nil.
|
||||||
//
|
//
|
||||||
// The type of the decoded values and the type of out will be considered,
|
// The type of the decoded values and the type of out will be considered,
|
||||||
// and Unmarshal() will do the best possible job to unmarshal values
|
// and Unmarshal will do the best possible job to unmarshal values
|
||||||
// appropriately. It is NOT considered an error, though, to skip values
|
// appropriately. It is NOT considered an error, though, to skip values
|
||||||
// because they are not available in the decoded YAML, or if they are not
|
// because they are not available in the decoded YAML, or if they are not
|
||||||
// compatible with the out value. To ensure something was properly
|
// compatible with the out value. To ensure something was properly
|
||||||
@@ -61,11 +62,11 @@ type Getter interface {
|
|||||||
// field (usually the zero value).
|
// field (usually the zero value).
|
||||||
//
|
//
|
||||||
// Struct fields are only unmarshalled if they are exported (have an
|
// Struct fields are only unmarshalled if they are exported (have an
|
||||||
// upper case first letter), and will be unmarshalled using the field
|
// upper case first letter), and are unmarshalled using the field name
|
||||||
// name lowercased by default. When custom field names are desired, the
|
// lowercased as the default key. Custom keys may be defined via the
|
||||||
// tag value may be used to tweak the name. Everything before the first
|
// "yaml" name in the field tag: the content preceding the first comma
|
||||||
// comma in the field tag will be used as the name. The values following
|
// is used as the key, and the following comma-separated options are
|
||||||
// the comma are used to tweak the marshalling process (see Marshal).
|
// used to tweak the marshalling process (see Marshal).
|
||||||
// Conflicting names result in a runtime error.
|
// Conflicting names result in a runtime error.
|
||||||
//
|
//
|
||||||
// For example:
|
// For example:
|
||||||
@@ -74,8 +75,8 @@ type Getter interface {
|
|||||||
// F int `yaml:"a,omitempty"`
|
// F int `yaml:"a,omitempty"`
|
||||||
// B int
|
// B int
|
||||||
// }
|
// }
|
||||||
// var T t
|
// var t T
|
||||||
// goyaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
|
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
|
||||||
//
|
//
|
||||||
// See the documentation of Marshal for the format of tags and a list of
|
// See the documentation of Marshal for the format of tags and a list of
|
||||||
// supported tag options.
|
// supported tag options.
|
||||||
@@ -83,25 +84,31 @@ type Getter interface {
|
|||||||
func Unmarshal(in []byte, out interface{}) (err error) {
|
func Unmarshal(in []byte, out interface{}) (err error) {
|
||||||
defer handleErr(&err)
|
defer handleErr(&err)
|
||||||
d := newDecoder()
|
d := newDecoder()
|
||||||
p := newParser(in)
|
p := newParser(in, UnmarshalMappingKeyTransform)
|
||||||
defer p.destroy()
|
defer p.destroy()
|
||||||
node := p.parse()
|
node := p.parse()
|
||||||
if node != nil {
|
if node != nil {
|
||||||
d.unmarshal(node, reflect.ValueOf(out))
|
v := reflect.ValueOf(out)
|
||||||
|
if v.Kind() == reflect.Ptr && !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
d.unmarshal(node, v)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal serializes the value provided into a YAML document. The structure
|
// Marshal serializes the value provided into a YAML document. The structure
|
||||||
// of the generated document will reflect the structure of the value itself.
|
// of the generated document will reflect the structure of the value itself.
|
||||||
// Maps, pointers to structs and ints, etc, may all be used as the in value.
|
// Maps and pointers (to struct, string, int, etc) are accepted as the in value.
|
||||||
//
|
//
|
||||||
// In the case of struct values, only exported fields will be serialized.
|
// Struct fields are only unmarshalled if they are exported (have an upper case
|
||||||
// The lowercased field name is used as the key for each exported field,
|
// first letter), and are unmarshalled using the field name lowercased as the
|
||||||
// but this behavior may be changed using the respective field tag.
|
// default key. Custom keys may be defined via the "yaml" name in the field
|
||||||
// The tag may also contain flags to tweak the marshalling behavior for
|
// tag: the content preceding the first comma is used as the key, and the
|
||||||
// the field. Conflicting names result in a runtime error. The tag format
|
// following comma-separated options are used to tweak the marshalling process.
|
||||||
// accepted is:
|
// Conflicting names result in a runtime error.
|
||||||
|
//
|
||||||
|
// The field tag format accepted is:
|
||||||
//
|
//
|
||||||
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
|
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
|
||||||
//
|
//
|
||||||
@@ -126,8 +133,8 @@ func Unmarshal(in []byte, out interface{}) (err error) {
|
|||||||
// F int "a,omitempty"
|
// F int "a,omitempty"
|
||||||
// B int
|
// B int
|
||||||
// }
|
// }
|
||||||
// goyaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
|
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
|
||||||
// goyaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n"
|
// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n"
|
||||||
//
|
//
|
||||||
func Marshal(in interface{}) (out []byte, err error) {
|
func Marshal(in interface{}) (out []byte, err error) {
|
||||||
defer handleErr(&err)
|
defer handleErr(&err)
|
||||||
@@ -139,10 +146,21 @@ func Marshal(in interface{}) (out []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalMappingKeyTransform is a string transformation that is applied to
|
||||||
|
// each mapping key in a YAML document before it is unmarshalled. By default,
|
||||||
|
// UnmarshalMappingKeyTransform is an identity transform (no modification).
|
||||||
|
var UnmarshalMappingKeyTransform transformString = identityTransform
|
||||||
|
|
||||||
|
type transformString func(in string) (out string)
|
||||||
|
|
||||||
|
func identityTransform(in string) (out string) {
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
// Maintain a mapping of keys to structure field indexes
|
// Maintain a mapping of keys to structure field indexes
|
||||||
|
|
||||||
// The code in this section was copied from gobson.
|
// The code in this section was copied from mgo/bson.
|
||||||
|
|
||||||
// structInfo holds details for the serialization of fields of
|
// structInfo holds details for the serialization of fields of
|
||||||
// a given struct.
|
// a given struct.
|
||||||
@@ -168,12 +186,6 @@ type fieldInfo struct {
|
|||||||
var structMap = make(map[reflect.Type]*structInfo)
|
var structMap = make(map[reflect.Type]*structInfo)
|
||||||
var fieldMapMutex sync.RWMutex
|
var fieldMapMutex sync.RWMutex
|
||||||
|
|
||||||
type externalPanic string
|
|
||||||
|
|
||||||
func (e externalPanic) String() string {
|
|
||||||
return string(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getStructInfo(st reflect.Type) (*structInfo, error) {
|
func getStructInfo(st reflect.Type) (*structInfo, error) {
|
||||||
fieldMapMutex.RLock()
|
fieldMapMutex.RLock()
|
||||||
sinfo, found := structMap[st]
|
sinfo, found := structMap[st]
|
||||||
@@ -214,8 +226,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
|
|||||||
case "inline":
|
case "inline":
|
||||||
inline = true
|
inline = true
|
||||||
default:
|
default:
|
||||||
msg := fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st)
|
return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st))
|
||||||
panic(externalPanic(msg))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tag = fields[0]
|
tag = fields[0]
|
||||||
@@ -223,6 +234,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
|
|||||||
|
|
||||||
if inline {
|
if inline {
|
||||||
switch field.Type.Kind() {
|
switch field.Type.Kind() {
|
||||||
|
// TODO: Implement support for inline maps.
|
||||||
//case reflect.Map:
|
//case reflect.Map:
|
||||||
// if inlineMap >= 0 {
|
// if inlineMap >= 0 {
|
||||||
// return nil, errors.New("Multiple ,inline maps in struct " + st.String())
|
// return nil, errors.New("Multiple ,inline maps in struct " + st.String())
|
||||||
@@ -250,8 +262,8 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
|
|||||||
fieldsList = append(fieldsList, finfo)
|
fieldsList = append(fieldsList, finfo)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
//panic("Option ,inline needs a struct value or map field")
|
//return nil, errors.New("Option ,inline needs a struct value or map field")
|
||||||
panic("Option ,inline needs a struct value field")
|
return nil, errors.New("Option ,inline needs a struct value field")
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package goyaml
|
package yaml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
@@ -294,6 +294,10 @@ const (
|
|||||||
yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences.
|
yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences.
|
||||||
yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping.
|
yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping.
|
||||||
|
|
||||||
|
// Not in original libyaml.
|
||||||
|
yaml_BINARY_TAG = "tag:yaml.org,2002:binary"
|
||||||
|
yaml_MERGE_TAG = "tag:yaml.org,2002:merge"
|
||||||
|
|
||||||
yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str.
|
yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str.
|
||||||
yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq.
|
yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq.
|
||||||
yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map.
|
yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map.
|
@@ -1,4 +1,4 @@
|
|||||||
package goyaml
|
package yaml
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// The size of the input raw buffer.
|
// The size of the input raw buffer.
|
2
Godeps/_workspace/src/github.com/dotcloud/docker/pkg/netlink/MAINTAINERS
generated
vendored
Normal file
2
Godeps/_workspace/src/github.com/dotcloud/docker/pkg/netlink/MAINTAINERS
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||||
|
Guillaume J. Charmes <guillaume@docker.com> (@creack)
|
23
Godeps/_workspace/src/github.com/dotcloud/docker/pkg/netlink/netlink.go
generated
vendored
Normal file
23
Godeps/_workspace/src/github.com/dotcloud/docker/pkg/netlink/netlink.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// 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
|
||||||
|
}
|
891
Godeps/_workspace/src/github.com/dotcloud/docker/pkg/netlink/netlink_linux.go
generated
vendored
Normal file
891
Godeps/_workspace/src/github.com/dotcloud/docker/pkg/netlink/netlink_linux.go
generated
vendored
Normal file
@@ -0,0 +1,891 @@
|
|||||||
|
// +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
|
||||||
|
}
|
69
Godeps/_workspace/src/github.com/dotcloud/docker/pkg/netlink/netlink_unsupported.go
generated
vendored
Normal file
69
Godeps/_workspace/src/github.com/dotcloud/docker/pkg/netlink/netlink_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
// +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
|
||||||
|
}
|
@@ -2,7 +2,7 @@ package introspect
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
@@ -2,7 +2,7 @@ package introspect
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
@@ -3,8 +3,8 @@
|
|||||||
package prop
|
package prop
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus/introspect"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus/introspect"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
27
Godeps/_workspace/src/github.com/tarm/goserial/LICENSE
generated
vendored
Normal file
27
Godeps/_workspace/src/github.com/tarm/goserial/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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.
|
63
Godeps/_workspace/src/github.com/tarm/goserial/README.md
generated
vendored
Normal file
63
Godeps/_workspace/src/github.com/tarm/goserial/README.md
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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)
|
61
Godeps/_workspace/src/github.com/tarm/goserial/basic_test.go
generated
vendored
Normal file
61
Godeps/_workspace/src/github.com/tarm/goserial/basic_test.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
99
Godeps/_workspace/src/github.com/tarm/goserial/serial.go
generated
vendored
Normal file
99
Godeps/_workspace/src/github.com/tarm/goserial/serial.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
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())
|
90
Godeps/_workspace/src/github.com/tarm/goserial/serial_linux.go
generated
vendored
Normal file
90
Godeps/_workspace/src/github.com/tarm/goserial/serial_linux.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// +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
|
||||||
|
}
|
107
Godeps/_workspace/src/github.com/tarm/goserial/serial_posix.go
generated
vendored
Normal file
107
Godeps/_workspace/src/github.com/tarm/goserial/serial_posix.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// +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
|
||||||
|
}
|
263
Godeps/_workspace/src/github.com/tarm/goserial/serial_windows.go
generated
vendored
Normal file
263
Godeps/_workspace/src/github.com/tarm/goserial/serial_windows.go
generated
vendored
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
// +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(¶ms)), 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
|
||||||
|
}
|
202
LICENSE
Normal file
202
LICENSE
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
3
MAINTAINERS
Normal file
3
MAINTAINERS
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Alex Crawford <alex.crawford@coreos.com> (@crawford)
|
||||||
|
Jonathan Boulle <jonathan.boulle@coreos.com> (@jonboulle)
|
||||||
|
Brian Waldon <brian.waldon@coreos.com> (@bcwaldon)
|
5
NOTICE
Normal file
5
NOTICE
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
CoreOS Project
|
||||||
|
Copyright 2014 CoreOS, Inc
|
||||||
|
|
||||||
|
This product includes software developed at CoreOS, Inc.
|
||||||
|
(http://www.coreos.com/).
|
80
README.md
80
README.md
@@ -1,9 +1,79 @@
|
|||||||
# coreos-cloudinit
|
# coreos-cloudinit [](https://travis-ci.org/coreos/coreos-cloudinit)
|
||||||
|
|
||||||
coreos-cloudinit allows a user to customize CoreOS machines by providing either an executable script or a cloud-config document as instance user-data. See below to learn how to use these features.
|
coreos-cloudinit enables a user to customize CoreOS machines by providing either a cloud-config document or an executable script through user-data.
|
||||||
|
|
||||||
## Supported Cloud-Config Features
|
## Configuration with cloud-config
|
||||||
|
|
||||||
Only a subset of [cloud-config functionality][cloud-config] is implemented. A set of custom parameters were added to the cloud-config format that are specific to CoreOS, which are [documented here](https://github.com/coreos/coreos-cloudinit/tree/master/Documentation/cloud-config.md).
|
A subset of the [official cloud-config spec][official-cloud-config] is implemented by coreos-cloudinit.
|
||||||
|
Additionally, several [CoreOS-specific options][custom-cloud-config] have been implemented to support interacting with unit files, bootstrapping etcd clusters, and more.
|
||||||
|
All supported cloud-config parameters are [documented here][all-cloud-config].
|
||||||
|
|
||||||
[cloud-config]: http://cloudinit.readthedocs.org/en/latest/topics/format.html#cloud-config-data
|
[official-cloud-config]: http://cloudinit.readthedocs.org/en/latest/topics/format.html#cloud-config-data
|
||||||
|
[custom-cloud-config]: https://github.com/coreos/coreos-cloudinit/blob/master/Documentation/cloud-config.md#coreos-parameters
|
||||||
|
[all-cloud-config]: https://github.com/coreos/coreos-cloudinit/tree/master/Documentation/cloud-config.md
|
||||||
|
|
||||||
|
The following is an example cloud-config document:
|
||||||
|
|
||||||
|
```
|
||||||
|
#cloud-config
|
||||||
|
|
||||||
|
coreos:
|
||||||
|
units:
|
||||||
|
- name: etcd.service
|
||||||
|
command: start
|
||||||
|
|
||||||
|
users:
|
||||||
|
- name: core
|
||||||
|
passwd: $1$allJZawX$00S5T756I5PGdQga5qhqv1
|
||||||
|
|
||||||
|
write_files:
|
||||||
|
- path: /etc/resolv.conf
|
||||||
|
content: |
|
||||||
|
nameserver 192.0.2.2
|
||||||
|
nameserver 192.0.2.3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Executing a Script
|
||||||
|
|
||||||
|
coreos-cloudinit supports executing user-data as a script instead of parsing it as a cloud-config document.
|
||||||
|
Make sure the first line of your user-data is a shebang and coreos-cloudinit will attempt to execute it:
|
||||||
|
|
||||||
|
```
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo 'Hello, world!'
|
||||||
|
```
|
||||||
|
|
||||||
|
## user-data Field Substitution
|
||||||
|
|
||||||
|
coreos-cloudinit will replace the following set of tokens in your user-data with system-generated values.
|
||||||
|
|
||||||
|
| Token | Description |
|
||||||
|
| ------------- | ----------- |
|
||||||
|
| $public_ipv4 | Public IPv4 address of machine |
|
||||||
|
| $private_ipv4 | Private IPv4 address of machine |
|
||||||
|
|
||||||
|
These values are determined by CoreOS based on the given provider on which your machine is running.
|
||||||
|
Read more about provider-specific functionality in the [CoreOS OEM documentation][oem-doc].
|
||||||
|
|
||||||
|
[oem-doc]: https://coreos.com/docs/sdk-distributors/distributors/notes-for-distributors/
|
||||||
|
|
||||||
|
For example, submitting the following user-data...
|
||||||
|
|
||||||
|
```
|
||||||
|
#cloud-config
|
||||||
|
coreos:
|
||||||
|
etcd:
|
||||||
|
addr: $public_ipv4:4001
|
||||||
|
peer-addr: $private_ipv4:7001
|
||||||
|
```
|
||||||
|
|
||||||
|
...will result in this cloud-config document being executed:
|
||||||
|
|
||||||
|
```
|
||||||
|
#cloud-config
|
||||||
|
coreos:
|
||||||
|
etcd:
|
||||||
|
addr: 203.0.113.29:4001
|
||||||
|
peer-addr: 192.0.2.13:7001
|
||||||
|
```
|
14
build
14
build
@@ -1,6 +1,14 @@
|
|||||||
#!/bin/bash -e
|
#!/bin/bash -e
|
||||||
|
|
||||||
export GOBIN=${PWD}/bin
|
ORG_PATH="github.com/coreos"
|
||||||
export GOPATH=${PWD}
|
REPO_PATH="${ORG_PATH}/coreos-cloudinit"
|
||||||
|
|
||||||
go build -o bin/coreos-cloudinit github.com/coreos/coreos-cloudinit
|
if [ ! -h gopath/src/${REPO_PATH} ]; then
|
||||||
|
mkdir -p gopath/src/${ORG_PATH}
|
||||||
|
ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255
|
||||||
|
fi
|
||||||
|
|
||||||
|
export GOBIN=${PWD}/bin
|
||||||
|
export GOPATH=${PWD}/gopath
|
||||||
|
|
||||||
|
go build -o bin/coreos-cloudinit ${REPO_PATH}
|
||||||
|
@@ -1,143 +0,0 @@
|
|||||||
package cloudinit
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/launchpad.net/goyaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
const DefaultSSHKeyName = "coreos-cloudinit"
|
|
||||||
|
|
||||||
type CloudConfig struct {
|
|
||||||
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
|
||||||
Coreos struct {
|
|
||||||
Etcd struct{ Discovery_URL string }
|
|
||||||
Fleet struct{ Autostart bool }
|
|
||||||
Units []Unit
|
|
||||||
}
|
|
||||||
WriteFiles []WriteFile `yaml:"write_files"`
|
|
||||||
Hostname string
|
|
||||||
Users []User
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCloudConfig(contents []byte) (*CloudConfig, error) {
|
|
||||||
var cfg CloudConfig
|
|
||||||
err := goyaml.Unmarshal(contents, &cfg)
|
|
||||||
return &cfg, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cc CloudConfig) String() string {
|
|
||||||
bytes, err := goyaml.Marshal(cc)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
stringified := string(bytes)
|
|
||||||
stringified = fmt.Sprintf("#cloud-config\n%s", stringified)
|
|
||||||
|
|
||||||
return stringified
|
|
||||||
}
|
|
||||||
|
|
||||||
func ApplyCloudConfig(cfg CloudConfig, sshKeyName string) error {
|
|
||||||
if cfg.Hostname != "" {
|
|
||||||
if err := SetHostname(cfg.Hostname); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("Set hostname to %s", cfg.Hostname)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.Users) > 0 {
|
|
||||||
for _, user := range cfg.Users {
|
|
||||||
if user.Name == "" {
|
|
||||||
log.Printf("User object has no 'name' field, skipping")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if UserExists(&user) {
|
|
||||||
log.Printf("User '%s' exists, ignoring creation-time fields", user.Name)
|
|
||||||
if user.PasswordHash != "" {
|
|
||||||
log.Printf("Setting '%s' user's password", user.Name)
|
|
||||||
if err := SetUserPassword(user.Name, user.PasswordHash); err != nil {
|
|
||||||
log.Printf("Failed setting '%s' user's password: %v", user.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("Creating user '%s'", user.Name)
|
|
||||||
if err := CreateUser(&user); err != nil {
|
|
||||||
log.Printf("Failed creating user '%s': %v", user.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(user.SSHAuthorizedKeys) > 0 {
|
|
||||||
log.Printf("Authorizing %d SSH keys for user '%s'", len(user.SSHAuthorizedKeys), user.Name)
|
|
||||||
if err := AuthorizeSSHKeys(user.Name, sshKeyName, user.SSHAuthorizedKeys); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.SSHAuthorizedKeys) > 0 {
|
|
||||||
err := AuthorizeSSHKeys("core", sshKeyName, cfg.SSHAuthorizedKeys)
|
|
||||||
if err == nil {
|
|
||||||
log.Printf("Authorized SSH keys for core user")
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.WriteFiles) > 0 {
|
|
||||||
for _, file := range cfg.WriteFiles {
|
|
||||||
if err := ProcessWriteFile("/", &file); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("Wrote file %s to filesystem", file.Path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Coreos.Etcd.Discovery_URL != "" {
|
|
||||||
err := PersistEtcdDiscoveryURL(cfg.Coreos.Etcd.Discovery_URL)
|
|
||||||
if err == nil {
|
|
||||||
log.Printf("Consumed etcd discovery url")
|
|
||||||
} else {
|
|
||||||
log.Fatalf("Failed to persist etcd discovery url to filesystem: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.Coreos.Units) > 0 {
|
|
||||||
for _, unit := range cfg.Coreos.Units {
|
|
||||||
log.Printf("Placing unit %s on filesystem", unit.Name)
|
|
||||||
dst, err := PlaceUnit("/", &unit)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("Placed unit %s at %s", unit.Name, dst)
|
|
||||||
|
|
||||||
if unit.Group() != "network" {
|
|
||||||
log.Printf("Enabling unit file %s", dst)
|
|
||||||
if err := EnableUnitFile(dst, unit.Runtime); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("Enabled unit %s", unit.Name)
|
|
||||||
} else {
|
|
||||||
log.Printf("Skipping enable for network-like unit %s", unit.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DaemonReload()
|
|
||||||
StartUnits(cfg.Coreos.Units)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Coreos.Fleet.Autostart {
|
|
||||||
err := StartUnitByName("fleet.service")
|
|
||||||
if err == nil {
|
|
||||||
log.Printf("Started fleet service.")
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@@ -1,252 +0,0 @@
|
|||||||
package cloudinit
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Assert that the parsing of a cloud config file "generally works"
|
|
||||||
func TestCloudConfigEmpty(t *testing.T) {
|
|
||||||
cfg, err := NewCloudConfig([]byte{})
|
|
||||||
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 cfg.Coreos.Etcd.Discovery_URL != "" {
|
|
||||||
t.Error("Parsed incorrect value of discovery url")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Coreos.Fleet.Autostart {
|
|
||||||
t.Error("Expected AutostartFleet not to be defined")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 := []byte(`
|
|
||||||
coreos:
|
|
||||||
etcd:
|
|
||||||
discovery_url: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
|
|
||||||
fleet:
|
|
||||||
autostart: Yes
|
|
||||||
units:
|
|
||||||
- name: 50-eth0.network
|
|
||||||
runtime: yes
|
|
||||||
content: '[Match]
|
|
||||||
|
|
||||||
Name=eth47
|
|
||||||
|
|
||||||
|
|
||||||
[Network]
|
|
||||||
|
|
||||||
Address=10.209.171.177/19
|
|
||||||
|
|
||||||
'
|
|
||||||
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 cfg.Coreos.Etcd.Discovery_URL != "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877" {
|
|
||||||
t.Error("Failed to parse etcd discovery url")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.Coreos.Fleet.Autostart {
|
|
||||||
t.Error("Expected AutostartFleet to be true")
|
|
||||||
}
|
|
||||||
|
|
||||||
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.Permissions != "0644" {
|
|
||||||
t.Errorf("WriteFile has incorrect permissions %s", wf.Permissions)
|
|
||||||
}
|
|
||||||
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 u.Type() != "network" {
|
|
||||||
t.Errorf("Unit has incorrect type '%s'", u.Type())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Hostname != "trontastic" {
|
|
||||||
t.Errorf("Failed to parse hostname")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that our interface conversion doesn't panic
|
|
||||||
func TestCloudConfigKeysNotList(t *testing.T) {
|
|
||||||
contents := []byte(`
|
|
||||||
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([]byte{})
|
|
||||||
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 := []byte(`
|
|
||||||
users:
|
|
||||||
- name: elroy
|
|
||||||
passwd: somehash
|
|
||||||
ssh-authorized-keys:
|
|
||||||
- somekey
|
|
||||||
gecos: arbitrary comment
|
|
||||||
homedir: /home/place
|
|
||||||
no-create-home: yes
|
|
||||||
primary-group: things
|
|
||||||
groups:
|
|
||||||
- ping
|
|
||||||
- pong
|
|
||||||
no-user-group: true
|
|
||||||
system: y
|
|
||||||
no-log-init: True
|
|
||||||
`)
|
|
||||||
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", 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")
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,25 +0,0 @@
|
|||||||
package cloudinit
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
etcdDiscoveryPath = "/var/run/etcd/bootstrap.disco"
|
|
||||||
)
|
|
||||||
|
|
||||||
func PersistEtcdDiscoveryURL(url string) error {
|
|
||||||
dir := path.Dir(etcdDiscoveryPath)
|
|
||||||
if _, err := os.Stat(dir); err != nil {
|
|
||||||
log.Printf("Creating directory /var/run/etcd")
|
|
||||||
err := os.MkdirAll(dir, os.FileMode(0644))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ioutil.WriteFile(etcdDiscoveryPath, []byte(url), os.FileMode(0644))
|
|
||||||
}
|
|
@@ -1,36 +0,0 @@
|
|||||||
package cloudinit
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type metadataService struct {
|
|
||||||
url string
|
|
||||||
client http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMetadataService(url string) *metadataService {
|
|
||||||
return &metadataService{url, http.Client{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms *metadataService) UserData() ([]byte, error) {
|
|
||||||
resp, err := ms.client.Get(ms.url)
|
|
||||||
if err != nil {
|
|
||||||
return []byte{}, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode / 100 != 2 {
|
|
||||||
return []byte{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return respBytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@@ -1,157 +0,0 @@
|
|||||||
package cloudinit
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/coreos/go-systemd/dbus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Unit struct {
|
|
||||||
Name string
|
|
||||||
Runtime bool
|
|
||||||
Content string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *Unit) Type() string {
|
|
||||||
ext := filepath.Ext(u.Name)
|
|
||||||
return strings.TrimLeft(ext, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *Unit) Group() (group string) {
|
|
||||||
t := u.Type()
|
|
||||||
if t == "network" || t == "netdev" || t == "link" {
|
|
||||||
group = "network"
|
|
||||||
} else {
|
|
||||||
group = "system"
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type Script []byte
|
|
||||||
|
|
||||||
func PlaceUnit(root string, u *Unit) (string, error) {
|
|
||||||
dir := "etc"
|
|
||||||
if u.Runtime {
|
|
||||||
dir = "run"
|
|
||||||
}
|
|
||||||
|
|
||||||
dst := path.Join(root, dir, "systemd", u.Group())
|
|
||||||
if _, err := os.Stat(dst); os.IsNotExist(err) {
|
|
||||||
if err := os.MkdirAll(dst, os.FileMode(0755)); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dst = path.Join(dst, u.Name)
|
|
||||||
err := ioutil.WriteFile(dst, []byte(u.Content), os.FileMode(0644))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return dst, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func EnableUnitFile(file string, runtime bool) error {
|
|
||||||
conn, err := dbus.New()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
files := []string{file}
|
|
||||||
_, _, err = conn.EnableUnitFiles(files, runtime, true)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func separateNetworkUnits(units []Unit) ([]Unit, []Unit) {
|
|
||||||
networkUnits := make([]Unit, 0)
|
|
||||||
nonNetworkUnits := make([]Unit, 0)
|
|
||||||
for _, unit := range units {
|
|
||||||
if unit.Group() == "network" {
|
|
||||||
networkUnits = append(networkUnits, unit)
|
|
||||||
} else {
|
|
||||||
nonNetworkUnits = append(nonNetworkUnits, unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return networkUnits, nonNetworkUnits
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartUnits(units []Unit) error {
|
|
||||||
networkUnits, nonNetworkUnits := separateNetworkUnits(units)
|
|
||||||
if len(networkUnits) > 0 {
|
|
||||||
if err := RestartUnitByName("systemd-networkd.service"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, unit := range nonNetworkUnits {
|
|
||||||
if err := RestartUnitByName(unit.Name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DaemonReload() error {
|
|
||||||
conn, err := dbus.New()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = conn.Reload()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func RestartUnitByName(name string) error {
|
|
||||||
log.Printf("Restarting unit %s", name)
|
|
||||||
conn, err := dbus.New()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := conn.RestartUnit(name, "replace")
|
|
||||||
log.Printf("Restart completed with '%s'", output)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartUnitByName(name string) error {
|
|
||||||
conn, err := dbus.New()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = conn.StartUnit(name, "replace")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExecuteScript(scriptPath string) (string, error) {
|
|
||||||
props := []dbus.Property{
|
|
||||||
dbus.PropDescription("Unit generated and executed by coreos-cloudinit on behalf of user"),
|
|
||||||
dbus.PropExecStart([]string{"/bin/bash", scriptPath}, false),
|
|
||||||
}
|
|
||||||
|
|
||||||
base := path.Base(scriptPath)
|
|
||||||
name := fmt.Sprintf("coreos-cloudinit-%s.service", base)
|
|
||||||
|
|
||||||
log.Printf("Creating transient systemd unit '%s'", name)
|
|
||||||
|
|
||||||
conn, err := dbus.New()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = conn.StartTransientUnit(name, "replace", props...)
|
|
||||||
return name, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetHostname(hostname string) error {
|
|
||||||
return exec.Command("hostnamectl", "set-hostname", hostname).Run()
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user