48 Commits

Author SHA1 Message Date
Jonathan Gold
9907c12eda resources: Enhancements to user and group
This patch adds autoedges between users and groups, and extends
users with additional fields for supplementary groups and a named
primary group. Also, some small fixes to log and error messages.
2017-10-23 19:18:52 -04:00
Jonathan Gold
19533a32b5 resources: Add a group resource 2017-10-21 01:28:22 -04:00
Jonathan Gold
c5a5004f9e resources: Fix user gid compare 2017-10-19 06:58:31 -04:00
Jonathan Gold
677cdea99d resources: Improve nspawn resource 2017-10-17 19:23:04 -04:00
Jonathan Gold
4d7c0ddbce resources: Add an Aws resource 2017-10-09 04:05:13 -04:00
James Shubin
81daf10157 test: Fix linter issues
These are some linter issues that were found in a new version of the
linter. Let's fix them now before that linter hits our test suite.
2017-09-26 19:38:53 -04:00
James Shubin
b3ef4e41bf test: Use stable version of gometalinter
Hopefully this prevents the various breakages seen in our lint test.
2017-09-26 19:08:43 -04:00
James Shubin
9fbf149717 etcd: Bump to newer versions 2017-09-19 18:21:15 -04:00
James Shubin
95cb94a039 vendor: Add codec package because of breakage
Recent git master 54210f4e076c57f351166f0ed60e67d3fca57a36 of
github.com/ugorji/go broke the builds. See:
https://github.com/coreos/etcd/issues/8579
2017-09-19 18:21:15 -04:00
Juan Luis de Sousa-Valadas Castaño
21f7f87716 resources: Refresh packagekit cache before install
Fixes #80
2017-09-17 22:29:15 +02:00
Jonathan Gold
831c7e2c32 resources: Add user resource 2017-09-17 01:04:36 -04:00
James Shubin
cc0d04c8b7 git: Ignore .envrc file from direnv
Some find this useful for setting a custom GOPATH per project.
2017-09-15 16:17:40 -04:00
James Shubin
46be83f8f7 legal: Re-license to GPLv3 2017-09-11 18:07:47 -04:00
James Shubin
28560e2045 resources: Fix formatting 2017-09-11 18:06:34 -04:00
James Shubin
0df4824a56 test: Increase timeouts for slow travis
Should prevent more intermittent failures.
2017-09-09 15:31:06 -04:00
James Shubin
dbcabc6517 github: Improve the PR template 2017-09-09 15:03:53 -04:00
Jonathan Gold
69f479b67e virt: Allow more than 26 disks 2017-09-08 02:15:40 +00:00
James Shubin
af75696018 github: Add a PR template to help new users
Hopefully this addresses the most common things.
2017-09-07 16:14:11 -04:00
Arthur Mello
80b8f8740f virt: Added support for ~user into expandHome
- Enabled expandHome to expand both ~/ and ~username/ paths
- Added some unit tests for expandHome
2017-09-06 14:59:08 -04:00
James Shubin
71ab325940 yaml2: Meta should keep defaults, and Res should have kind
This would previously panic since it wouldn't get a kind, and the meta
parameters would overwrite the defaults so it would block because limit
didn't have the default of +inf.

The removal of the SetKind was my fault in:

b8ff6938df

It's funny because it ends in `bad`. Guess I should have checked that!
2017-09-06 13:44:21 -04:00
James Shubin
653c76709a test: Fix another intermittent failure
Some of the tests had very precise timeouts, which weren't very
important. Here's another one that timed out early.
2017-09-04 16:39:01 -04:00
Juan Luis de Sousa-Valadas Castaño
83cc1bab38 vagrant: Fix PATH
gometalinter failed because it's not in $PATH
2017-09-04 22:08:59 +02:00
James Shubin
6c8588c019 test: Increase timeouts because travis is slow
Should hopefully prevent some intermittent failures.
2017-09-04 13:02:05 -04:00
Ismael Puerto
5b00ed2fb2 vagrant: Change box to F26
F26 provides GO 1.8
2017-09-01 22:21:39 +02:00
Juan-Luis de Sousa-Valadas Castaño
9f66962bfb docs: Change go required version to 1.8 2017-08-31 23:56:16 +02:00
James Shubin
0edba74091 etcd: Bump to version 3.2.6 and update all the grpc deps
Note: When go-grpc-prometheus was in the main $gopath (even at this
version) and everyone else was where they always were in vendor/ this
didn't build! It gave errors like:

	have SendHeader("github.com/purpleidea/mgmt/vendor/google.golang.org/grpc/metadata".MD) error
	want SendHeader("google.golang.org/grpc/metadata".MD) error

and I got frustrated. Putting it "next" to the other vendored deps seems
to have fixed this. Where are the golang docs that explain this
phenomenon?

This also requires golang 1.8+ as that is a requirement for etcd. It's
probably a reasonable thing for us too.

Note the older versions of etcd had some bugs with the concurrency
package and other things, so this is a necessary bump.
2017-08-30 14:16:02 -04:00
Dennis Kliban
1003b49dd9 resources: Add validation for Msg Priority field
This adds validation that ensures that Msg Priority field is one of the following values:
"Emerg", "Alert", "Crit", "Err", "Warning", "Notice", "Info", "Debug".
2017-08-20 12:37:39 +00:00
James Shubin
884ba54f96 resources: Include default MetaParams so Validate will pass in tests 2017-08-18 19:52:02 -04:00
Dennis Kliban
cf2325a2da vagrant: Increase amount of RAM allocated to boxes backed by libvirt 2017-08-07 13:55:21 -04:00
AdnanLFC
db6972638d pgraph: test: Added tests for DeleteEdge 2017-07-28 02:02:22 +02:00
James Shubin
74e04e81d5 travis: Update to golang 1.8 as the default
Since the release of Fedora 26 with golang 1.8.1, this is a fine
default.
2017-07-19 12:15:54 -04:00
James Shubin
7c5d7365c7 readme: Add new recording 2017-06-29 13:14:25 -04:00
James Shubin
0dadf3d78a resources: Add NewNamedResource helper
This makes the common pattern of NewResource, SetName, easier. It also
makes it less likely for you to forget to use SetName.
2017-06-17 18:09:49 -04:00
James Shubin
e341256627 resources: Add a utility to map from struct fields
For GAPI front ends that want to know what fields they can use and which
they map to, these two functions can be used.
2017-06-17 11:49:30 -04:00
James Shubin
5a3bd3ca67 hcl: Consistent formatting
Nit picks.
2017-06-16 23:01:46 -04:00
ChrisMcKenzie
8102e0a468 hcl: Added hil string interpolation to hcl frontend 2017-06-15 22:53:55 -07:00
ChrisMcKenzie
7d55179727 hcl: Removed edge object in favor of depends_on field in resource 2017-06-12 10:44:13 -07:00
ChrisMcKenzie
bc1a1d1818 hcl: Added basic hcl frontend 2017-06-09 10:31:34 -07:00
James Shubin
a8bbb22fe8 resources: Fix golint issues
Including a trick to get the golinter to allow our compact code!
2017-06-08 04:38:25 -04:00
James Shubin
6b489f71a1 remote: Add a Ready method to know when startup is finished
Previously, there was an extremely rare race where we would startup,
kick off the Run method in a goroutine, and then run Exit before Run got
very far in its execution. If Run ran some early sections of its code
_after_ we had Exited, we would trigger a panic due to the converger UID
being unregistered.

This patch blocks Exit from progressing until Run has started and
finished running. It also adds a Ready method so that you can monitor
this signal yourself if you'd like to add the necessary wait to your
code.
2017-06-08 03:55:03 -04:00
James Shubin
f1db088af4 test: Don't be noisy when running cd during testing 2017-06-08 01:05:58 -04:00
James Shubin
6fe12b3fb5 resources: Compare grouped resources properly
When comparing resources, we have to recursively compare grouped
resources as well! Now fixed.
2017-06-08 01:05:58 -04:00
James Shubin
dacbf9b68d resources: Add resource sorting and clean tests
Resource sorting is needed for comparing resource groups.
2017-06-08 01:05:58 -04:00
James Shubin
9f5057eac7 resources: Do not panic on autogrouped graph switches
Graph changes from autogrouped -> not autogrouped or vice versa cause a
panic (or I assume a leak) because we compared the auto grouped graph to
the ungrouped one, which would cause an Exit on an unstarted Vertex.
This includes a test that seems to reliably reproduces the issue.
2017-06-08 01:05:58 -04:00
James Shubin
525cd54921 pgraph: Improve testing and refactor out some test utilities 2017-06-07 07:13:12 -04:00
James Shubin
7ac94bbf5f resources: Panic if attempting to register a duplicate resource
Don't silently let this overwrite pass. It would mean a mistake.
2017-06-07 03:15:06 -04:00
James Shubin
b8ff6938df resources: Unify resource creation and kind setting
This removes the duplication of the kind string and cleans up things for
resource creation.
2017-06-07 03:07:02 -04:00
James Shubin
2f6c77fba2 misc: Update my tag script to deal with large releases 2017-06-03 03:54:49 -04:00
140 changed files with 3918 additions and 845 deletions

36
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,36 @@
## Tips:
* commit message titles must be in the form:
```topic: Capitalized message with no trailing period```
or:
```topic, topic2: Capitalized message with no trailing period```
* golang code must be formatted according to the standard, please run:
```
make gofmt # formats the entire project correctly
```
or format a single golang file correctly:
```
gofmt -w yourcode.go
```
* please rebase your patch against current git master:
```
git checkout master
git pull origin master
git checkout your-feature
git rebase master
git push your-remote your-feature
hub pull-request # or submit with the github web ui
```
* after a patch review, please ping @purpleidea so we know to re-review:
```
# make changes based on reviews...
git add -p # add new changes
git commit --amend # combine with existing commit
git push your-remote your-feature -f
# now ping @purpleidea in the github PR since it doesn't notify us automatically
```
## Thanks for contributing to mgmt and welcome to the team!

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
.omv/ .omv/
.ssh/ .ssh/
.vagrant/ .vagrant/
.envrc
old/ old/
tmp/ tmp/
*_stringer.go *_stringer.go

6
.gitmodules vendored
View File

@@ -16,3 +16,9 @@
[submodule "vendor/honnef.co/go/augeas"] [submodule "vendor/honnef.co/go/augeas"]
path = vendor/honnef.co/go/augeas path = vendor/honnef.co/go/augeas
url = https://github.com/dominikh/go-augeas/ url = https://github.com/dominikh/go-augeas/
[submodule "vendor/github.com/grpc-ecosystem/go-grpc-prometheus"]
path = vendor/github.com/grpc-ecosystem/go-grpc-prometheus
url = https://github.com/grpc-ecosystem/go-grpc-prometheus
[submodule "vendor/github.com/ugorji/go"]
path = vendor/github.com/ugorji/go
url = https://github.com/ugorji/go

View File

@@ -1,8 +1,7 @@
language: go language: go
go: go:
- 1.6.x
- 1.7.x
- 1.8.x - 1.8.x
- 1.9.x
- tip - tip
go_import_path: github.com/purpleidea/mgmt go_import_path: github.com/purpleidea/mgmt
sudo: true sudo: true
@@ -16,7 +15,7 @@ matrix:
fast_finish: true fast_finish: true
allow_failures: allow_failures:
- go: tip - go: tip
- go: 1.8.x - go: 1.9.x
notifications: notifications:
irc: irc:
channels: channels:

141
COPYING
View File

@@ -1,5 +1,5 @@
GNU AFFERO GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
Version 3, 19 November 2007 Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
@@ -7,15 +7,17 @@
Preamble Preamble
The GNU Affero General Public License is a free, copyleft license for The GNU General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure software and other kinds of works.
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast, to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free share and change all versions of a program--to make sure it remains free
software for all its users. software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you price. Our General Public Licenses are designed to make sure that you
@@ -24,34 +26,44 @@ them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things. free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights To protect your rights, we need to prevent others from denying you
with two steps: (1) assert copyright on the software, and (2) offer these rights or asking you to surrender the rights. Therefore, you have
you this License which gives you legal permission to copy, distribute certain responsibilities if you distribute copies of the software, or if
and/or modify the software. you modify it: responsibilities to respect the freedom of others.
A secondary benefit of defending all users' freedom is that For example, if you distribute copies of such a program, whether
improvements made in alternate versions of the program, if they gratis or for a fee, you must pass on to the recipients the same
receive widespread use, become available for other developers to freedoms that you received. You must make sure that they, too, receive
incorporate. Many developers of free software are heartened and or can get the source code. And you must show them these terms so they
encouraged by the resulting cooperation. However, in the case of know their rights.
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to Developers that use the GNU GPL protect your rights with two steps:
ensure that, in such cases, the modified source code becomes available (1) assert copyright on the software, and (2) offer you this License
to the community. It requires the operator of a network server to giving you legal permission to copy, distribute and/or modify it.
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and For the developers' and authors' protection, the GPL clearly explains
published by Affero, was designed to accomplish similar goals. This is that there is no warranty for this free software. For both users' and
a different license, not a version of the Affero GPL, but Affero has authors' sake, the GPL requires that modified versions be marked as
released a new version of the Affero GPL which permits relicensing under changed, so that their problems will not be attributed erroneously to
this license. authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and The precise terms and conditions for copying, distribution and
modification follow. modification follow.
@@ -60,7 +72,7 @@ modification follow.
0. Definitions. 0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License. "This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks. works, such as semiconductor masks.
@@ -537,45 +549,35 @@ to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program. License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License. 13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work, License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version but the special requirements of the GNU Affero General Public License,
3 of the GNU General Public License. section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License. 14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions the GNU General Public License from time to time. Such new versions will
will be similar in spirit to the present version, but may differ in detail to be similar in spirit to the present version, but may differ in detail to
address new problems or concerns. address new problems or concerns.
Each version is given a distinguishing version number. If the Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published GNU General Public License, you may choose any version ever published
by the Free Software Foundation. by the Free Software Foundation.
If the Program specifies that a proxy can decide which future If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you public statement of acceptance of a version permanently authorizes you
to choose that version for the Program. to choose that version for the Program.
@@ -633,29 +635,40 @@ the "copyright" line and a pointer to where the full notice is found.
Copyright (C) <year> <name of author> Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail. Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer If the program does terminal interaction, make it output a short
network, you should also make sure that it provides a way for users to notice like this when it starts in an interactive mode:
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive <program> Copyright (C) <year> <name of author>
of the code. There are many ways you could offer source, and different This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
solutions will be better for different programs; see section 13 for the This is free software, and you are welcome to redistribute it
specific requirements. under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school, You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary. if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>. <http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -3,14 +3,14 @@ Copyright (C) 2013-2017+ James Shubin and the project contributors
Written by James Shubin <james@shubin.ca> and the project contributors Written by James Shubin <james@shubin.ca> and the project contributors
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@@ -3,16 +3,16 @@
# Written by James Shubin <james@shubin.ca> and the project contributors # Written by James Shubin <james@shubin.ca> and the project contributors
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or # the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
SHELL = /usr/bin/env bash SHELL = /usr/bin/env bash

View File

@@ -80,6 +80,7 @@ We'd love to have your patches! Please send them by email, or as a pull request.
| James Shubin | blog | [Metaparameters in mgmt](https://ttboj.wordpress.com/2017/03/01/metaparameters-in-mgmt/) | | James Shubin | blog | [Metaparameters in mgmt](https://ttboj.wordpress.com/2017/03/01/metaparameters-in-mgmt/) |
| James Shubin | video | [Recording from Incontro DevOps 2017](https://vimeo.com/212241877) | | James Shubin | video | [Recording from Incontro DevOps 2017](https://vimeo.com/212241877) |
| Yves Brissaud | blog | [mgmt aux HumanTalks Grenoble (french)](http://log.winsos.net/2017/04/12/mgmt-aux-human-talks-grenoble.html) | | Yves Brissaud | blog | [mgmt aux HumanTalks Grenoble (french)](http://log.winsos.net/2017/04/12/mgmt-aux-human-talks-grenoble.html) |
| James Shubin | video | [Recording from OSDC Berlin 2017](https://www.youtube.com/watch?v=LkEtBVLfygE&html5=1) |
## ##

5
Vagrantfile vendored
View File

@@ -6,13 +6,16 @@ Vagrant.configure(2) do |config|
config.vm.synced_folder ".", "/vagrant", disabled: true config.vm.synced_folder ".", "/vagrant", disabled: true
config.vm.define "mgmt-dev" do |instance| config.vm.define "mgmt-dev" do |instance|
instance.vm.box = "fedora/24-cloud-base" instance.vm.box = "fedora/26-cloud-base"
end end
config.vm.provider "virtualbox" do |v| config.vm.provider "virtualbox" do |v|
v.memory = 1536 v.memory = 1536
v.cpus = 2 v.cpus = 2
end end
config.vm.provider "libvirt" do |v|
v.memory = 2048
end
config.vm.provision "file", source: "vagrant/motd", destination: ".motd" config.vm.provision "file", source: "vagrant/motd", destination: ".motd"
config.vm.provision "shell", inline: "cp ~vagrant/.motd /etc/motd" config.vm.provision "shell", inline: "cp ~vagrant/.motd /etc/motd"

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package converger is a facility for reporting the converged state. // Package converger is a facility for reporting the converged state.
@@ -85,7 +85,7 @@ type cuid struct {
} }
// NewConverger builds a new converger struct. // NewConverger builds a new converger struct.
func NewConverger(timeout int, stateFn func(bool) error) *converger { func NewConverger(timeout int, stateFn func(bool) error) Converger {
return &converger{ return &converger{
timeout: timeout, timeout: timeout,
stateFn: stateFn, stateFn: stateFn,

6
doc.go
View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package main provides the main entrypoint for using the `mgmt` software. // Package main provides the main entrypoint for using the `mgmt` software.

View File

@@ -21,7 +21,8 @@ dependencies, _runtime_ dependencies, and additionally, a few extra dependencies
required for running the _test_ suite. required for running the _test_ suite.
### Build ### Build
* `golang` 1.6 or higher (required, available in most distros) * `golang` 1.8 or higher (required, available in some distros and distributed
as a binary officially by [golang.org](https://golang.org/dl/))
* golang libraries (required, available with `go get ./...`) a partial list includes: * golang libraries (required, available with `go get ./...`) a partial list includes:
``` ```
github.com/coreos/etcd/client github.com/coreos/etcd/client
@@ -60,7 +61,7 @@ For building a visual representation of the graph, `graphviz` is required.
* golint `github.com/golang/lint/golint` * golint `github.com/golang/lint/golint`
## Quick start ## Quick start
* Make sure you have golang version 1.6 or greater installed. * Make sure you have golang version 1.8 or greater installed.
* If you do not have a GOPATH yet, create one and export it: * If you do not have a GOPATH yet, create one and export it:
``` ```
mkdir $HOME/gopath mkdir $HOME/gopath

View File

@@ -73,14 +73,13 @@ Init() error
``` ```
This is called to initialize the resource. If something goes wrong, it should This is called to initialize the resource. If something goes wrong, it should
return an error. It should set the resource `kind`, do any resource specific return an error. It should do any resource specific work, and finish by calling
work, and finish by calling the `Init` method of the base resource. the `Init` method of the base resource.
#### Example #### Example
```golang ```golang
// Init initializes the Foo resource. // Init initializes the Foo resource.
func (obj *FooRes) Init() error { func (obj *FooRes) Init() error {
obj.BaseRes.Kind = "foo" // must lower case resource kind
// run the resource specific initialization, and error if anything fails // run the resource specific initialization, and error if anything fails
if some_error { if some_error {
return err // something went wrong! return err // something went wrong!
@@ -399,9 +398,9 @@ UnmarshalYAML(unmarshal func(interface{}) error) error // optional
``` ```
This is optional, but recommended for any resource that will have a YAML This is optional, but recommended for any resource that will have a YAML
accessible struct, and an entry in the `GraphConfig` struct. It is not required accessible struct. It is not required because to do so would mean that
because to do so would mean that third-party or custom resources (such as those third-party or custom resources (such as those someone writes to use with
someone writes to use with `libmgmt`) would have to implement this needlessly. `libmgmt`) would have to implement this needlessly.
The signature intentionally matches what is required to satisfy the `go-yaml` The signature intentionally matches what is required to satisfy the `go-yaml`
[Unmarshaler](https://godoc.org/gopkg.in/yaml.v2#Unmarshaler) interface. [Unmarshaler](https://godoc.org/gopkg.in/yaml.v2#Unmarshaler) interface.
@@ -453,35 +452,15 @@ type FooRes struct {
} }
``` ```
### YAML ### Resource registration
In addition to labelling your resource struct with YAML fields, you must also All resources must be registered with the engine so that they can be found. This
add an entry to the internal `GraphConfig` struct. It is a fairly straight also ensures they can be encoded and decoded. Make sure to include the following
forward one line patch. code snippet for this to work.
```golang ```golang
type GraphConfig struct {
// [snip...]
Resources struct {
Noop []*resources.NoopRes `yaml:"noop"`
File []*resources.FileRes `yaml:"file"`
// [snip...]
Foo []*resources.FooRes `yaml:"foo"` // tada :)
}
}
```
It's also recommended that you add the [UnmarshalYAML](#unmarshalyaml) method to
your resources so that unspecified values are given sane defaults.
### Gob registration
All resources must be registered with the `golang` _gob_ module so that they can
be encoded and decoded. Make sure to include the following code snippet for this
to work.
```golang
import "encoding/gob"
func init() { // special golang method that runs once func init() { // special golang method that runs once
gob.Register(&FooRes{}) // substitude your resource here // set your resource kind and struct here (the kind must be lower case)
RegisterResource("foo", func() Res { return &FooRes{} })
} }
``` ```

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// TODO: Add TTL's (eg: volunteering) // TODO: Add TTL's (eg: volunteering)
@@ -529,8 +529,9 @@ func (obj *EmbdEtcd) CtxError(ctx context.Context, err error) (context.Context,
if obj.ctxErr != nil { // stop on permanent error if obj.ctxErr != nil { // stop on permanent error
return ctx, obj.ctxErr return ctx, obj.ctxErr
} }
const ctxErr = "ctxErr" type ctxKey string // use a non-basic type as ctx key (str can conflict)
const ctxIter = "ctxIter" const ctxErr ctxKey = "ctxErr"
const ctxIter ctxKey = "ctxIter"
expBackoff := func(tmin, texp, iter, tmax int) time.Duration { expBackoff := func(tmin, texp, iter, tmax int) time.Duration {
// https://en.wikipedia.org/wiki/Exponential_backoff // https://en.wikipedia.org/wiki/Exponential_backoff
// tmin <= texp^iter - 1 <= tmax // TODO: check my math // tmin <= texp^iter - 1 <= tmax // TODO: check my math

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package etcd package etcd

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package etcd package etcd
@@ -171,7 +171,6 @@ func GetResources(obj *EmbdEtcd, hostnameFilter, kindFilter []string) ([]resourc
} }
if obj, err := resources.B64ToRes(val); err == nil { if obj, err := resources.B64ToRes(val); err == nil {
obj.SetKind(kind) // cheap init
log.Printf("Etcd: Get: (Hostname, Kind, Name): (%s, %s, %s)", hostname, kind, name) log.Printf("Etcd: Get: (Hostname, Kind, Name): (%s, %s, %s)", hostname, kind, name)
resourceList = append(resourceList, obj) resourceList = append(resourceList, obj)
} else { } else {

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package etcd package etcd

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package etcd package etcd

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package etcd package etcd

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package event provides some primitives that are used for message passing. // Package event provides some primitives that are used for message passing.

19
examples/autoedges4.yaml Normal file
View File

@@ -0,0 +1,19 @@
---
graph: mygraph
resources:
user:
- name: edgeuser
state: absent
gid: 10000
- name: edgeuser2
state: exists
group: edgegroup
groups: [edgegroup2, edgegroup3]
group:
- name: edgegroup
state: exists
gid: 10000
- name: edgegroup2
state: exists
- name: edgegroup3
state: exists

10
examples/aws_ec2_1.yaml Normal file
View File

@@ -0,0 +1,10 @@
---
graph: mygraph
resources:
aws:ec2:
- name: ec2example
region: ca-central-1
type: t2.micro
imageid: ami-5ac17f3e
state: running
edges: []

14
examples/graph0.hcl Normal file
View File

@@ -0,0 +1,14 @@
resource "file" "file1" {
path = "/tmp/mgmt-hello-world"
content = "hello, world"
state = "exists"
depends_on = ["noop.noop1", "exec.sleep"]
}
resource "noop" "noop1" {
test = "nil"
}
resource "exec" "sleep" {
cmd = "sleep 10s"
}

4
examples/graph1.hcl Normal file
View File

@@ -0,0 +1,4 @@
resource "exec" "exec1" {
cmd = "cat /tmp/mgmt-hello-world"
state = "present"
}

8
examples/group1.yaml Normal file
View File

@@ -0,0 +1,8 @@
---
graph: mygraph
resources:
group:
- name: testgroup
state: exists
gid: 10000
edges: []

9
examples/hil.hcl Normal file
View File

@@ -0,0 +1,9 @@
resource "file" "file1" {
path = "/tmp/mgmt-hello-world"
content = "${exec.sleep.Output}"
state = "exists"
}
resource "exec" "sleep" {
cmd = "echo hello"
}

View File

@@ -61,12 +61,12 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
return nil, err return nil, err
} }
// FIXME: these are being specified temporarily until it's the default!
metaparams := resources.DefaultMetaParams metaparams := resources.DefaultMetaParams
exec1 := &resources.ExecRes{ exec1 := &resources.ExecRes{
BaseRes: resources.BaseRes{ BaseRes: resources.BaseRes{
Name: "exec1", Name: "exec1",
Kind: "exec",
MetaParams: metaparams, MetaParams: metaparams,
}, },
Cmd: "echo hello world && echo goodbye world 1>&2", // to stdout && stderr Cmd: "echo hello world && echo goodbye world 1>&2", // to stdout && stderr
@@ -77,6 +77,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
output := &resources.FileRes{ output := &resources.FileRes{
BaseRes: resources.BaseRes{ BaseRes: resources.BaseRes{
Name: "output", Name: "output",
Kind: "file",
MetaParams: metaparams, MetaParams: metaparams,
// send->recv! // send->recv!
Recv: map[string]*resources.Send{ Recv: map[string]*resources.Send{
@@ -92,6 +93,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
stdout := &resources.FileRes{ stdout := &resources.FileRes{
BaseRes: resources.BaseRes{ BaseRes: resources.BaseRes{
Name: "stdout", Name: "stdout",
Kind: "file",
MetaParams: metaparams, MetaParams: metaparams,
// send->recv! // send->recv!
Recv: map[string]*resources.Send{ Recv: map[string]*resources.Send{
@@ -107,6 +109,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
stderr := &resources.FileRes{ stderr := &resources.FileRes{
BaseRes: resources.BaseRes{ BaseRes: resources.BaseRes{
Name: "stderr", Name: "stderr",
Kind: "file",
MetaParams: metaparams, MetaParams: metaparams,
// send->recv! // send->recv!
Recv: map[string]*resources.Send{ Recv: map[string]*resources.Send{
@@ -229,10 +232,7 @@ func Run() error {
} }
}() }()
if err := obj.Run(); err != nil { return obj.Run()
return err
}
return nil
} }
func main() { func main() {

View File

@@ -63,6 +63,7 @@ func (obj *MyGAPI) subGraph() (*pgraph.Graph, error) {
f1 := &resources.FileRes{ f1 := &resources.FileRes{
BaseRes: resources.BaseRes{ BaseRes: resources.BaseRes{
Name: "file1", Name: "file1",
Kind: "file",
MetaParams: metaparams, MetaParams: metaparams,
}, },
Path: "/tmp/mgmt/sub1", Path: "/tmp/mgmt/sub1",
@@ -74,6 +75,7 @@ func (obj *MyGAPI) subGraph() (*pgraph.Graph, error) {
n1 := &resources.NoopRes{ n1 := &resources.NoopRes{
BaseRes: resources.BaseRes{ BaseRes: resources.BaseRes{
Name: "noop1", Name: "noop1",
Kind: "noop",
MetaParams: metaparams, MetaParams: metaparams,
}, },
} }
@@ -93,7 +95,6 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
return nil, err return nil, err
} }
// FIXME: these are being specified temporarily until it's the default!
metaparams := resources.DefaultMetaParams metaparams := resources.DefaultMetaParams
content := "I created a subgraph!\n" content := "I created a subgraph!\n"
@@ -238,10 +239,7 @@ func Run() error {
} }
}() }()
if err := obj.Run(); err != nil { return obj.Run()
return err
}
return nil
} }
func main() { func main() {

View File

@@ -61,13 +61,13 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
return nil, err return nil, err
} }
// FIXME: these are being specified temporarily until it's the default!
metaparams := resources.DefaultMetaParams metaparams := resources.DefaultMetaParams
content := "I created a subgraph!\n" content := "I created a subgraph!\n"
f0 := &resources.FileRes{ f0 := &resources.FileRes{
BaseRes: resources.BaseRes{ BaseRes: resources.BaseRes{
Name: "README", Name: "README",
Kind: "file",
MetaParams: metaparams, MetaParams: metaparams,
}, },
Path: "/tmp/mgmt/README", Path: "/tmp/mgmt/README",
@@ -86,6 +86,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
f1 := &resources.FileRes{ f1 := &resources.FileRes{
BaseRes: resources.BaseRes{ BaseRes: resources.BaseRes{
Name: "file1", Name: "file1",
Kind: "file",
MetaParams: metaparams, MetaParams: metaparams,
}, },
Path: "/tmp/mgmt/sub1", Path: "/tmp/mgmt/sub1",
@@ -97,6 +98,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
n1 := &resources.NoopRes{ n1 := &resources.NoopRes{
BaseRes: resources.BaseRes{ BaseRes: resources.BaseRes{
Name: "noop1", Name: "noop1",
Kind: "noop",
MetaParams: metaparams, MetaParams: metaparams,
}, },
} }
@@ -110,6 +112,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
subGraphRes0 := &resources.GraphRes{ // TODO: should we name this SubGraphRes ? subGraphRes0 := &resources.GraphRes{ // TODO: should we name this SubGraphRes ?
BaseRes: resources.BaseRes{ BaseRes: resources.BaseRes{
Name: "subgraph1", Name: "subgraph1",
Kind: "graph",
MetaParams: metaparams, MetaParams: metaparams,
}, },
Graph: subGraph, Graph: subGraph,
@@ -226,10 +229,7 @@ func Run() error {
} }
}() }()
if err := obj.Run(); err != nil { return obj.Run()
return err
}
return nil
} }
func main() { func main() {

View File

@@ -57,13 +57,13 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
return nil, fmt.Errorf("libmgmt: MyGAPI is not initialized") return nil, fmt.Errorf("libmgmt: MyGAPI is not initialized")
} }
n1 := &resources.NoopRes{ n1, err := resources.NewNamedResource("noop", "noop1")
BaseRes: resources.BaseRes{ if err != nil {
Name: "noop1", return nil, err
MetaParams: resources.DefaultMetaParams,
},
} }
// NOTE: This is considered the legacy method to build graphs. Avoid
// importing the legacy `yamlgraph` lib if possible for custom graphs.
// we can still build a graph via the yaml method // we can still build a graph via the yaml method
gc := &yamlgraph.GraphConfig{ gc := &yamlgraph.GraphConfig{
Graph: obj.Name, Graph: obj.Name,
@@ -72,7 +72,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
Exec: []*resources.ExecRes{}, Exec: []*resources.ExecRes{},
File: []*resources.FileRes{}, File: []*resources.FileRes{},
Msg: []*resources.MsgRes{}, Msg: []*resources.MsgRes{},
Noop: []*resources.NoopRes{n1}, Noop: []*resources.NoopRes{n1.(*resources.NoopRes)},
Pkg: []*resources.PkgRes{}, Pkg: []*resources.PkgRes{},
Svc: []*resources.SvcRes{}, Svc: []*resources.SvcRes{},
Timer: []*resources.TimerRes{}, Timer: []*resources.TimerRes{},
@@ -190,10 +190,7 @@ func Run() error {
} }
}() }()
if err := obj.Run(); err != nil { return obj.Run()
return err
}
return nil
} }
func main() { func main() {

View File

@@ -65,11 +65,9 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
} }
var vertex pgraph.Vertex var vertex pgraph.Vertex
for i := uint(0); i < obj.Count; i++ { for i := uint(0); i < obj.Count; i++ {
n := &resources.NoopRes{ n, err := resources.NewNamedResource("noop", fmt.Sprintf("noop%d", i))
BaseRes: resources.BaseRes{ if err != nil {
Name: fmt.Sprintf("noop%d", i), return nil, err
MetaParams: resources.DefaultMetaParams,
},
} }
g.AddVertex(n) g.AddVertex(n)
if i > 0 { if i > 0 {
@@ -186,10 +184,7 @@ func Run(count uint) error {
} }
}() }()
if err := obj.Run(); err != nil { return obj.Run()
return err
}
return nil
} }
func main() { func main() {

View File

@@ -61,13 +61,13 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
return nil, err return nil, err
} }
// FIXME: these are being specified temporarily until it's the default!
metaparams := resources.DefaultMetaParams metaparams := resources.DefaultMetaParams
content := "Delete me to trigger a notification!\n" content := "Delete me to trigger a notification!\n"
f0 := &resources.FileRes{ f0 := &resources.FileRes{
BaseRes: resources.BaseRes{ BaseRes: resources.BaseRes{
Name: "README", Name: "README",
Kind: "file",
MetaParams: metaparams, MetaParams: metaparams,
}, },
Path: "/tmp/mgmt/README", Path: "/tmp/mgmt/README",
@@ -80,6 +80,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
p1 := &resources.PasswordRes{ p1 := &resources.PasswordRes{
BaseRes: resources.BaseRes{ BaseRes: resources.BaseRes{
Name: "password1", Name: "password1",
Kind: "password",
MetaParams: metaparams, MetaParams: metaparams,
}, },
Length: 8, // generated string will have this many characters Length: 8, // generated string will have this many characters
@@ -90,6 +91,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
f1 := &resources.FileRes{ f1 := &resources.FileRes{
BaseRes: resources.BaseRes{ BaseRes: resources.BaseRes{
Name: "file1", Name: "file1",
Kind: "file",
MetaParams: metaparams, MetaParams: metaparams,
// send->recv! // send->recv!
Recv: map[string]*resources.Send{ Recv: map[string]*resources.Send{
@@ -106,6 +108,7 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
n1 := &resources.NoopRes{ n1 := &resources.NoopRes{
BaseRes: resources.BaseRes{ BaseRes: resources.BaseRes{
Name: "noop1", Name: "noop1",
Kind: "noop",
MetaParams: metaparams, MetaParams: metaparams,
}, },
} }
@@ -231,10 +234,7 @@ func Run() error {
} }
}() }()
if err := obj.Run(); err != nil { return obj.Run()
return err
}
return nil
} }
func main() { func main() {

View File

@@ -2,6 +2,6 @@
graph: mygraph graph: mygraph
resources: resources:
nspawn: nspawn:
- name: mgmt-nspawn1 - name: nspawn1
state: running state: running
edges: [] edges: []

View File

@@ -1,7 +0,0 @@
---
graph: mygraph
resources:
nspawn:
- name: mgmt-nspawn2
state: stopped
edges: []

9
examples/user1.yaml Normal file
View File

@@ -0,0 +1,9 @@
---
graph: mygraph
resources:
user:
- name: testuser
uid: 1002
gid: 100
state: exists
edges: []

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package gapi defines the interface that graph API generators must meet. // Package gapi defines the interface that graph API generators must meet.

155
hcl/gapi.go Normal file
View File

@@ -0,0 +1,155 @@
// Mgmt
// Copyright (C) 2013-2017+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package hcl
import (
"fmt"
"log"
"sync"
"github.com/purpleidea/mgmt/gapi"
"github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/recwatch"
)
// GAPI ...
type GAPI struct {
File *string
initialized bool
data gapi.Data
wg sync.WaitGroup
closeChan chan struct{}
configWatcher *recwatch.ConfigWatcher
}
// NewGAPI ...
func NewGAPI(data gapi.Data, file *string) (*GAPI, error) {
if file == nil {
return nil, fmt.Errorf("empty file given")
}
obj := &GAPI{
File: file,
}
return obj, obj.Init(data)
}
// Init ...
func (obj *GAPI) Init(d gapi.Data) error {
if obj.initialized {
return fmt.Errorf("already initialized")
}
if obj.File == nil {
return fmt.Errorf("file cannot be nil")
}
obj.data = d
obj.closeChan = make(chan struct{})
obj.initialized = true
obj.configWatcher = recwatch.NewConfigWatcher()
return nil
}
// Graph ...
func (obj *GAPI) Graph() (*pgraph.Graph, error) {
config, err := loadHcl(obj.File)
if err != nil {
return nil, fmt.Errorf("unable to parse graph: %s", err)
}
return graphFromConfig(config, obj.data)
}
// Next ...
func (obj *GAPI) Next() chan gapi.Next {
ch := make(chan gapi.Next)
obj.wg.Add(1)
go func() {
defer obj.wg.Done()
defer close(ch)
if !obj.initialized {
next := gapi.Next{
Err: fmt.Errorf("hcl: GAPI is not initialized"),
Exit: true,
}
ch <- next
return
}
startChan := make(chan struct{}) // start signal
close(startChan) // kick it off!
watchChan, configChan := make(chan error), make(chan error)
if obj.data.NoConfigWatch {
configChan = nil
} else {
configChan = obj.configWatcher.ConfigWatch(*obj.File) // simple
}
if obj.data.NoStreamWatch {
watchChan = nil
} else {
watchChan = obj.data.World.ResWatch()
}
for {
var err error
var ok bool
select {
case <-startChan:
startChan = nil
case err, ok = <-watchChan:
case err, ok = <-configChan:
if !ok {
return
}
case <-obj.closeChan:
return
}
log.Printf("hcl: generating new graph")
next := gapi.Next{
Err: err,
}
select {
case ch <- next:
case <-obj.closeChan:
return
}
}
}()
return ch
}
// Close ...
func (obj *GAPI) Close() error {
if !obj.initialized {
return fmt.Errorf("hcl: GAPI is not initialized")
}
obj.configWatcher.Close()
close(obj.closeChan)
obj.wg.Wait()
obj.initialized = false
return nil
}

387
hcl/parse.go Normal file
View File

@@ -0,0 +1,387 @@
// Mgmt
// Copyright (C) 2013-2017+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package hcl
import (
"fmt"
"io/ioutil"
"log"
"strings"
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/hil"
"github.com/purpleidea/mgmt/gapi"
hv "github.com/purpleidea/mgmt/hil"
"github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/resources"
)
type collectorResConfig struct {
Kind string
Pattern string
}
// Config defines the structure of the hcl config.
type Config struct {
Resources []*Resource
Edges []*Edge
Collector []collectorResConfig
}
// vertex is the data structure of a vertex.
type vertex struct {
Kind string `hcl:"kind"`
Name string `hcl:"name"`
}
// Edge defines an edge in hcl.
type Edge struct {
Name string
From vertex
To vertex
Notify bool
}
// Resources define the state for resources.
type Resources struct {
Resources []resources.Res
}
// Resource ...
type Resource struct {
Name string
Kind string
resource resources.Res
Meta resources.MetaParams
deps []*Edge
rcv map[string]*hv.ResourceVariable
}
type key struct {
kind, name string
}
func graphFromConfig(c *Config, data gapi.Data) (*pgraph.Graph, error) {
var graph *pgraph.Graph
var err error
graph, err = pgraph.NewGraph("Graph")
if err != nil {
return nil, fmt.Errorf("unable to create graph from config: %s", err)
}
lookup := make(map[key]pgraph.Vertex)
var keep []pgraph.Vertex
var resourceList []resources.Res
log.Printf("hcl: parsing %d resources", len(c.Resources))
for _, r := range c.Resources {
res := r.resource
kind := r.resource.GetKind()
log.Printf("hcl: resource \"%s\" \"%s\"", kind, r.Name)
if !strings.HasPrefix(res.GetName(), "@@") {
fn := func(v pgraph.Vertex) (bool, error) {
return resources.VtoR(v).Compare(res), nil
}
v, err := graph.VertexMatchFn(fn)
if err != nil {
return nil, fmt.Errorf("could not match vertex: %s", err)
}
if v == nil {
v = res
graph.AddVertex(v)
}
lookup[key{kind, res.GetName()}] = v
keep = append(keep, v)
} else if !data.Noop {
res.SetName(res.GetName()[2:])
res.SetKind(kind)
resourceList = append(resourceList, res)
}
}
// store in backend (usually etcd)
if err := data.World.ResExport(resourceList); err != nil {
return nil, fmt.Errorf("Config: Could not export resources: %v", err)
}
// lookup from backend (usually etcd)
var hostnameFilter []string // empty to get from everyone
kindFilter := []string{}
for _, t := range c.Collector {
kind := strings.ToLower(t.Kind)
kindFilter = append(kindFilter, kind)
}
// do all the graph look ups in one single step, so that if the backend
// database changes, we don't have a partial state of affairs...
if len(kindFilter) > 0 { // if kindFilter is empty, don't need to do lookups!
var err error
resourceList, err = data.World.ResCollect(hostnameFilter, kindFilter)
if err != nil {
return nil, fmt.Errorf("Config: Could not collect resources: %v", err)
}
}
for _, res := range resourceList {
matched := false
// see if we find a collect pattern that matches
for _, t := range c.Collector {
kind := strings.ToLower(t.Kind)
// use t.Kind and optionally t.Pattern to collect from storage
log.Printf("Collect: %v; Pattern: %v", kind, t.Pattern)
// XXX: expand to more complex pattern matching here...
if res.GetKind() != kind {
continue
}
if matched {
// we've already matched this resource, should we match again?
log.Printf("Config: Warning: Matching %s again!", res)
}
matched = true
// collect resources but add the noop metaparam
//if noop { // now done in mgmtmain
// res.Meta().Noop = noop
//}
if t.Pattern != "" { // XXX: simplistic for now
res.CollectPattern(t.Pattern) // res.Dirname = t.Pattern
}
log.Printf("Collect: %s: collected!", res)
// XXX: similar to other resource add code:
// if _, exists := lookup[kind]; !exists {
// lookup[kind] = make(map[string]pgraph.Vertex)
// }
fn := func(v pgraph.Vertex) (bool, error) {
return resources.VtoR(v).Compare(res), nil
}
v, err := graph.VertexMatchFn(fn)
if err != nil {
return nil, fmt.Errorf("could not VertexMatchFn() resource: %s", err)
}
if v == nil { // no match found
v = res // a standalone res can be a vertex
graph.AddVertex(v) // call standalone in case not part of an edge
}
lookup[key{kind, res.GetName()}] = v // used for constructing edges
keep = append(keep, v) // append
//break // let's see if another resource even matches
}
}
for _, r := range c.Resources {
for _, e := range r.deps {
if _, ok := lookup[key{strings.ToLower(e.From.Kind), e.From.Name}]; !ok {
return nil, fmt.Errorf("can't find 'from' name")
}
if _, ok := lookup[key{strings.ToLower(e.To.Kind), e.To.Name}]; !ok {
return nil, fmt.Errorf("can't find 'to' name")
}
from := lookup[key{strings.ToLower(e.From.Kind), e.From.Name}]
to := lookup[key{strings.ToLower(e.To.Kind), e.To.Name}]
edge := &resources.Edge{
Name: e.Name,
Notify: e.Notify,
}
graph.AddEdge(from, to, edge)
}
recv := make(map[string]*resources.Send)
// build Rcv's from resource variables
for k, v := range r.rcv {
send, ok := lookup[key{strings.ToLower(v.Kind), v.Name}]
if !ok {
return nil, fmt.Errorf("resource not found")
}
recv[strings.ToUpper(string(k[0]))+k[1:]] = &resources.Send{
Res: resources.VtoR(send),
Key: v.Field,
}
to := lookup[key{strings.ToLower(r.Kind), r.Name}]
edge := &resources.Edge{
Name: v.Name,
Notify: true,
}
graph.AddEdge(send, to, edge)
}
r.resource.SetRecv(recv)
}
return graph, nil
}
func loadHcl(f *string) (*Config, error) {
if f == nil {
return nil, fmt.Errorf("empty file given")
}
data, err := ioutil.ReadFile(*f)
if err != nil {
return nil, fmt.Errorf("unable to read file: %v", err)
}
file, err := hcl.ParseBytes(data)
if err != nil {
return nil, fmt.Errorf("unable to parse file: %s", err)
}
config := new(Config)
list, ok := file.Node.(*ast.ObjectList)
if !ok {
return nil, fmt.Errorf("unable to parse file: file does not contain root node object")
}
if resources := list.Filter("resource"); len(resources.Items) > 0 {
var err error
config.Resources, err = loadResourcesHcl(resources)
if err != nil {
return nil, fmt.Errorf("unable to parse: %s", err)
}
}
return config, nil
}
func loadResourcesHcl(list *ast.ObjectList) ([]*Resource, error) {
list = list.Children()
if len(list.Items) == 0 {
return nil, nil
}
var result []*Resource
for _, item := range list.Items {
kind := item.Keys[0].Token.Value().(string)
name := item.Keys[1].Token.Value().(string)
var listVal *ast.ObjectList
if ot, ok := item.Val.(*ast.ObjectType); ok {
listVal = ot.List
} else {
return nil, fmt.Errorf("module '%s': should be an object", name)
}
var params = resources.DefaultMetaParams
if o := listVal.Filter("meta"); len(o.Items) > 0 {
err := hcl.DecodeObject(&params, o)
if err != nil {
return nil, fmt.Errorf(
"Error parsing meta for %s: %s",
name,
err)
}
}
var deps []string
if edges := listVal.Filter("depends_on"); len(edges.Items) > 0 {
err := hcl.DecodeObject(&deps, edges.Items[0].Val)
if err != nil {
return nil, fmt.Errorf("unable to parse: %s", err)
}
}
var edges []*Edge
for _, dep := range deps {
vertices := strings.Split(dep, ".")
edges = append(edges, &Edge{
To: vertex{
Kind: kind,
Name: name,
},
From: vertex{
Kind: vertices[0],
Name: vertices[1],
},
})
}
var config map[string]interface{}
if err := hcl.DecodeObject(&config, item.Val); err != nil {
log.Printf("hcl: unable to decode body: %v", err)
return nil, fmt.Errorf(
"Error reading config for %s: %s",
name,
err)
}
delete(config, "meta")
delete(config, "depends_on")
rcv := make(map[string]*hv.ResourceVariable)
// parse strings for hil
for k, v := range config {
n, err := hil.Parse(v.(string))
if err != nil {
return nil, fmt.Errorf("unable to parse fields: %v", err)
}
variables, err := hv.ParseVariables(n)
if err != nil {
return nil, fmt.Errorf("unable to parse variables: %v", err)
}
for _, v := range variables {
val, ok := v.(*hv.ResourceVariable)
if !ok {
continue
}
rcv[k] = val
}
}
res, err := resources.NewNamedResource(kind, name)
if err != nil {
log.Printf("hcl: unable to parse resource: %v", err)
return nil, err
}
if err := hcl.DecodeObject(res, item.Val); err != nil {
log.Printf("hcl: unable to decode body: %v", err)
return nil, fmt.Errorf(
"Error reading config for %s: %s",
name,
err)
}
meta := res.Meta()
*meta = params
result = append(result, &Resource{
Name: name,
Kind: kind,
resource: res,
deps: edges,
rcv: rcv,
})
}
return result, nil
}

89
hil/interpolate.go Normal file
View File

@@ -0,0 +1,89 @@
// Mgmt
// Copyright (C) 2013-2017+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package hil
import (
"fmt"
"strings"
"github.com/hashicorp/hil/ast"
)
// Variable defines an interpolated variable.
type Variable interface {
Key() string
}
// ResourceVariable defines a variable type used to reference fields of a resource
// e.g. ${file.file1.Content}
type ResourceVariable struct {
Kind, Name, Field string
}
// Key returns a string representation of the variable key.
func (r *ResourceVariable) Key() string {
return fmt.Sprintf("%s.%s.%s", r.Kind, r.Name, r.Field)
}
// NewInterpolatedVariable takes a variable key and return the interpolated variable
// of the required type.
func NewInterpolatedVariable(k string) (Variable, error) {
// for now resource variables are the only thing.
parts := strings.SplitN(k, ".", 3)
return &ResourceVariable{
Kind: parts[0],
Name: parts[1],
Field: parts[2],
}, nil
}
// ParseVariables will traverse a HIL tree looking for variables and returns a
// list of them.
func ParseVariables(tree ast.Node) ([]Variable, error) {
var result []Variable
var finalErr error
visitor := func(n ast.Node) ast.Node {
if finalErr != nil {
return n
}
switch nt := n.(type) {
case *ast.VariableAccess:
v, err := NewInterpolatedVariable(nt.Name)
if err != nil {
finalErr = err
return n
}
result = append(result, v)
default:
return n
}
return n
}
tree.Accept(visitor)
if finalErr != nil {
return nil, finalErr
}
return result, nil
}

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package lib package lib
@@ -24,6 +24,7 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/purpleidea/mgmt/hcl"
"github.com/purpleidea/mgmt/puppet" "github.com/purpleidea/mgmt/puppet"
"github.com/purpleidea/mgmt/yamlgraph" "github.com/purpleidea/mgmt/yamlgraph"
"github.com/purpleidea/mgmt/yamlgraph2" "github.com/purpleidea/mgmt/yamlgraph2"
@@ -89,6 +90,14 @@ func run(c *cli.Context) error {
PuppetConf: c.String("puppet-conf"), PuppetConf: c.String("puppet-conf"),
} }
} }
if h := c.String("hcl"); c.IsSet("hcl") {
if obj.GAPI != nil {
return fmt.Errorf("can't combine hcl GAPI with existing GAPI")
}
obj.GAPI = &hcl.GAPI{
File: &h,
}
}
obj.Remotes = c.StringSlice("remote") // FIXME: GAPI-ify somehow? obj.Remotes = c.StringSlice("remote") // FIXME: GAPI-ify somehow?
obj.NoWatch = c.Bool("no-watch") obj.NoWatch = c.Bool("no-watch")
@@ -222,6 +231,11 @@ func CLI(program, version string, flags Flags) error {
Value: "", Value: "",
Usage: "yaml graph definition to run (parser v2)", Usage: "yaml graph definition to run (parser v2)",
}, },
cli.StringFlag{
Name: "hcl",
Value: "",
Usage: "hcl graph definition to run",
},
cli.StringFlag{ cli.StringFlag{
Name: "puppet, p", Name: "puppet, p",
Value: "", Value: "",

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package lib package lib
@@ -524,6 +524,8 @@ func (obj *Main) Run() error {
continue continue
} }
//savedGraph := oldGraph.Copy() // save a copy for errors
// TODO: should we call each Res.Setup() here instead? // TODO: should we call each Res.Setup() here instead?
// add autoedges; modifies the graph only if no error // add autoedges; modifies the graph only if no error
@@ -537,12 +539,20 @@ func (obj *Main) Run() error {
continue continue
} }
graph.Update(oldGraph) // copy in structure of new graph // at this point, any time we error after a destructive
// modification of the graph we need to restore the old
// graph that was previously running, eg:
//
// oldGraph = savedGraph.Copy()
//
// which we are (luckily) able to avoid testing for now
resources.AutoGroup(graph.Graph, &resources.NonReachabilityGrouper{}) // run autogroup; modifies the graph resources.AutoGroup(oldGraph, &resources.NonReachabilityGrouper{}) // run autogroup; modifies the graph
// TODO: do we want to do a transitive reduction? // TODO: do we want to do a transitive reduction?
// FIXME: run a type checker that verifies all the send->recv relationships // FIXME: run a type checker that verifies all the send->recv relationships
graph.Update(oldGraph) // copy in structure of new graph
// Call this here because at this point the graph does // Call this here because at this point the graph does
// not know anything about the prometheus instance. // not know anything about the prometheus instance.
if err := prom.UpdatePgraphStartTime(); err != nil { if err := prom.UpdatePgraphStartTime(); err != nil {
@@ -619,6 +629,14 @@ func (obj *Main) Run() error {
// TODO: is there any benefit to running the remotes above in the loop? // TODO: is there any benefit to running the remotes above in the loop?
// wait for etcd to be running before we remote in, which we do above! // wait for etcd to be running before we remote in, which we do above!
go remotes.Run() go remotes.Run()
// wait for remotes to be ready before continuing...
select {
case <-remotes.Ready():
log.Printf("Main: Remotes: Run: Ready!")
// pass
//case <-time.After( ? * time.Second):
// obj.Exit(fmt.Errorf("Main: Remotes: Run timeout"))
}
if obj.GAPI == nil { if obj.GAPI == nil {
converger.Start() // better start this for empty graphs converger.Start() // better start this for empty graphs

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package main package main

View File

@@ -80,5 +80,5 @@ if [[ $ret != 0 ]]; then
fi fi
go get golang.org/x/tools/cmd/stringer # for automatic stringer-ing go get golang.org/x/tools/cmd/stringer # for automatic stringer-ing
go get github.com/golang/lint/golint # for `golint`-ing go get github.com/golang/lint/golint # for `golint`-ing
go get github.com/alecthomas/gometalinter && gometalinter --install # bonus go get -u gopkg.in/alecthomas/gometalinter.v1 && mv "$(dirname $(which gometalinter.v1))/gometalinter.v1" "$(dirname $(which gometalinter.v1))/gometalinter" && gometalinter --install # bonus
cd "$XPWD" >/dev/null cd "$XPWD" >/dev/null

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package pgp package pgp

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package pgraph package pgraph
@@ -23,12 +23,25 @@ import (
errwrap "github.com/pkg/errors" errwrap "github.com/pkg/errors"
) )
func strVertexCmpFn(v1, v2 Vertex) (bool, error) {
if v1.String() == "" || v2.String() == "" {
return false, fmt.Errorf("empty vertex")
}
return v1.String() == v2.String(), nil
}
func strEdgeCmpFn(e1, e2 Edge) (bool, error) {
if e1.String() == "" || e2.String() == "" {
return false, fmt.Errorf("empty edge")
}
return e1.String() == e2.String(), nil
}
// GraphSync updates the Graph so that it matches the newGraph. It leaves // GraphSync updates the Graph so that it matches the newGraph. It leaves
// identical elements alone so that they don't need to be refreshed. // identical elements alone so that they don't need to be refreshed.
// It tries to mutate existing elements into new ones, if they support this. // It tries to mutate existing elements into new ones, if they support this.
// This updates the Graph on success only. // This updates the Graph on success only.
// FIXME: should we do this with copies of the vertex resources? // FIXME: should we do this with copies of the vertex resources?
// FIXME: add test cases
func (obj *Graph) GraphSync(newGraph *Graph, vertexCmpFn func(Vertex, Vertex) (bool, error), vertexAddFn func(Vertex) error, vertexRemoveFn func(Vertex) error, edgeCmpFn func(Edge, Edge) (bool, error)) error { func (obj *Graph) GraphSync(newGraph *Graph, vertexCmpFn func(Vertex, Vertex) (bool, error), vertexAddFn func(Vertex) error, vertexRemoveFn func(Vertex) error, edgeCmpFn func(Edge, Edge) (bool, error)) error {
oldGraph := obj.Copy() // work on a copy of the old graph oldGraph := obj.Copy() // work on a copy of the old graph
@@ -41,6 +54,19 @@ func (obj *Graph) GraphSync(newGraph *Graph, vertexCmpFn func(Vertex, Vertex) (b
} }
oldGraph.SetName(newGraph.GetName()) // overwrite the name oldGraph.SetName(newGraph.GetName()) // overwrite the name
if vertexCmpFn == nil {
vertexCmpFn = strVertexCmpFn // use simple string cmp version
}
if vertexAddFn == nil {
vertexAddFn = func(Vertex) error { return nil } // noop
}
if vertexRemoveFn == nil {
vertexRemoveFn = func(Vertex) error { return nil } // noop
}
if edgeCmpFn == nil {
edgeCmpFn = strEdgeCmpFn // use simple string cmp version
}
var lookup = make(map[Vertex]Vertex) var lookup = make(map[Vertex]Vertex)
var vertexKeep []Vertex // list of vertices which are the same in new graph var vertexKeep []Vertex // list of vertices which are the same in new graph
var edgeKeep []Edge // list of vertices which are the same in new graph var edgeKeep []Edge // list of vertices which are the same in new graph

92
pgraph/graphsync_test.go Normal file
View File

@@ -0,0 +1,92 @@
// Mgmt
// Copyright (C) 2013-2017+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package pgraph
import (
"testing"
)
func TestGraphSync1(t *testing.T) {
g := &Graph{}
v1 := NV("v1")
v2 := NV("v2")
v3 := NV("v3")
e1 := NE("e1")
e2 := NE("e2")
g.AddEdge(v1, v3, e1)
g.AddEdge(v2, v3, e2)
// new graph
newGraph := &Graph{}
v4 := NV("v4")
v5 := NV("v5")
e3 := NE("e3")
newGraph.AddEdge(v4, v5, e3)
err := g.GraphSync(newGraph, nil, nil, nil, nil)
if err != nil {
t.Errorf("GraphSync failed: %v", err)
return
}
// g should change and become the same
if s := runGraphCmp(t, g, newGraph); s != "" {
t.Errorf("%s", s)
}
}
func TestGraphSync2(t *testing.T) {
v1 := NV("v1")
v2 := NV("v2")
v3 := NV("v3")
v4 := NV("v4")
v5 := NV("v5")
e1 := NE("e1")
e2 := NE("e2")
e3 := NE("e3")
g := &Graph{}
g.AddEdge(v1, v3, e1)
g.AddEdge(v2, v3, e2)
// new graph
newGraph := &Graph{}
newGraph.AddEdge(v1, v3, e1)
newGraph.AddEdge(v2, v3, e2)
newGraph.AddEdge(v4, v5, e3)
//newGraph.AddEdge(v3, v4, NE("v3,v4"))
//newGraph.AddEdge(v3, v5, NE("v3,v5"))
// graphs should differ!
if runGraphCmp(t, g, newGraph) == "" {
t.Errorf("graphs should differ initially")
return
}
err := g.GraphSync(newGraph, strVertexCmpFn, vertexAddFn, vertexRemoveFn, strEdgeCmpFn)
if err != nil {
t.Errorf("GraphSync failed: %v", err)
return
}
// g should change and become the same
if s := runGraphCmp(t, g, newGraph); s != "" {
t.Errorf("%s", s)
}
}

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package pgraph // TODO: this should be a subpackage package pgraph // TODO: this should be a subpackage

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package pgraph represents the internal "pointer graph" that we use. // Package pgraph represents the internal "pointer graph" that we use.
@@ -142,7 +142,6 @@ func (g *Graph) AddEdge(v1, v2 Vertex, e Edge) {
} }
// DeleteEdge deletes a particular edge from the graph. // DeleteEdge deletes a particular edge from the graph.
// FIXME: add test cases
func (g *Graph) DeleteEdge(e Edge) { func (g *Graph) DeleteEdge(e Edge) {
for v1 := range g.adjacency { for v1 := range g.adjacency {
for v2, edge := range g.adjacency[v1] { for v2, edge := range g.adjacency[v1] {

View File

@@ -3,58 +3,26 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package pgraph package pgraph
import ( import (
"fmt"
"reflect" "reflect"
"testing" "testing"
) )
// vertex is a test struct to test the library. func TestCount1(t *testing.T) {
type vertex struct {
name string
}
// String is a required method of the Vertex interface that we must fulfill.
func (v *vertex) String() string {
return v.name
}
// NV is a helper function to make testing easier. It creates a new noop vertex.
func NV(s string) Vertex {
return &vertex{s}
}
// edge is a test struct to test the library.
type edge struct {
name string
}
// String is a required method of the Edge interface that we must fulfill.
func (e *edge) String() string {
return e.name
}
// NE is a helper function to make testing easier. It creates a new noop edge.
func NE(s string) Edge {
return &edge{s}
}
func TestPgraphT1(t *testing.T) {
G := &Graph{} G := &Graph{}
if i := G.NumVertices(); i != 0 { if i := G.NumVertices(); i != 0 {
@@ -79,8 +47,7 @@ func TestPgraphT1(t *testing.T) {
} }
} }
func TestPgraphT2(t *testing.T) { func TestAddVertex1(t *testing.T) {
G := &Graph{Name: "g2"} G := &Graph{Name: "g2"}
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
@@ -106,8 +73,7 @@ func TestPgraphT2(t *testing.T) {
} }
} }
func TestPgraphT3(t *testing.T) { func TestDFS1(t *testing.T) {
G, _ := NewGraph("g3") G, _ := NewGraph("g3")
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
@@ -147,8 +113,7 @@ func TestPgraphT3(t *testing.T) {
} }
} }
func TestPgraphT4(t *testing.T) { func TestDFS2(t *testing.T) {
G, _ := NewGraph("g4") G, _ := NewGraph("g4")
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
@@ -170,7 +135,7 @@ func TestPgraphT4(t *testing.T) {
} }
} }
func TestPgraphT5(t *testing.T) { func TestFilterGraph1(t *testing.T) {
G, _ := NewGraph("g5") G, _ := NewGraph("g5")
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
@@ -203,7 +168,7 @@ func TestPgraphT5(t *testing.T) {
} }
} }
func TestPgraphT6(t *testing.T) { func TestDisconnectedGraphs1(t *testing.T) {
G, _ := NewGraph("g6") G, _ := NewGraph("g6")
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
@@ -235,8 +200,7 @@ func TestPgraphT6(t *testing.T) {
} }
} }
func TestPgraphT7(t *testing.T) { func TestDeleteVertex1(t *testing.T) {
G, _ := NewGraph("g7") G, _ := NewGraph("g7")
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
@@ -277,8 +241,17 @@ func TestPgraphT7(t *testing.T) {
} }
} }
func TestPgraphT8(t *testing.T) { func TestDeleteVertex2(t *testing.T) {
G := &Graph{}
v1 := NV("v1")
G.DeleteVertex(v1) // check this doesn't panic
if i := G.NumVertices(); i != 0 {
t.Errorf("should have 0 vertices instead of: %d", i)
}
}
func TestVertexContains1(t *testing.T) {
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
v3 := NV("v3") v3 := NV("v3")
@@ -306,8 +279,7 @@ func TestPgraphT8(t *testing.T) {
} }
} }
func TestPgraphT9(t *testing.T) { func TestTopoSort1(t *testing.T) {
G, _ := NewGraph("g9") G, _ := NewGraph("g9")
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
@@ -382,8 +354,7 @@ func TestPgraphT9(t *testing.T) {
} }
} }
func TestPgraphT10(t *testing.T) { func TestTopoSort2(t *testing.T) {
G, _ := NewGraph("g10") G, _ := NewGraph("g10")
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
@@ -410,7 +381,7 @@ func TestPgraphT10(t *testing.T) {
} }
// empty // empty
func TestPgraphReachability0(t *testing.T) { func TestReachability0(t *testing.T) {
{ {
G, _ := NewGraph("g") G, _ := NewGraph("g")
result := G.Reachability(nil, nil) result := G.Reachability(nil, nil)
@@ -474,7 +445,7 @@ func TestPgraphReachability0(t *testing.T) {
} }
// simple linear path // simple linear path
func TestPgraphReachability1(t *testing.T) { func TestReachability1(t *testing.T) {
G, _ := NewGraph("g") G, _ := NewGraph("g")
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
@@ -508,7 +479,7 @@ func TestPgraphReachability1(t *testing.T) {
} }
// pick one of two correct paths // pick one of two correct paths
func TestPgraphReachability2(t *testing.T) { func TestReachability2(t *testing.T) {
G, _ := NewGraph("g") G, _ := NewGraph("g")
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
@@ -545,7 +516,7 @@ func TestPgraphReachability2(t *testing.T) {
} }
// pick shortest path // pick shortest path
func TestPgraphReachability3(t *testing.T) { func TestReachability3(t *testing.T) {
G, _ := NewGraph("g") G, _ := NewGraph("g")
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
@@ -580,7 +551,7 @@ func TestPgraphReachability3(t *testing.T) {
} }
// direct path // direct path
func TestPgraphReachability4(t *testing.T) { func TestReachability4(t *testing.T) {
G, _ := NewGraph("g") G, _ := NewGraph("g")
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
@@ -614,7 +585,7 @@ func TestPgraphReachability4(t *testing.T) {
} }
} }
func TestPgraphT11(t *testing.T) { func TestReverse1(t *testing.T) {
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
v3 := NV("v3") v3 := NV("v3")
@@ -639,7 +610,7 @@ func TestPgraphT11(t *testing.T) {
} }
} }
func TestPgraphCopy1(t *testing.T) { func TestCopy1(t *testing.T) {
g1 := &Graph{} g1 := &Graph{}
g2 := g1.Copy() // check this doesn't panic g2 := g1.Copy() // check this doesn't panic
if !reflect.DeepEqual(g1.String(), g2.String()) { if !reflect.DeepEqual(g1.String(), g2.String()) {
@@ -647,31 +618,7 @@ func TestPgraphCopy1(t *testing.T) {
} }
} }
func TestPgraphDelete1(t *testing.T) { func TestGraphCmp1(t *testing.T) {
G := &Graph{}
v1 := NV("v1")
G.DeleteVertex(v1) // check this doesn't panic
if i := G.NumVertices(); i != 0 {
t.Errorf("should have 0 vertices instead of: %d", i)
}
}
func vertexCmpFn(v1, v2 Vertex) (bool, error) {
if v1.String() == "" || v2.String() == "" {
return false, fmt.Errorf("oops, empty vertex")
}
return v1.String() == v2.String(), nil
}
func edgeCmpFn(e1, e2 Edge) (bool, error) {
if e1.String() == "" || e2.String() == "" {
return false, fmt.Errorf("oops, empty edge")
}
return e1.String() == e2.String(), nil
}
func TestPgraphGraphCmp1(t *testing.T) {
g1 := &Graph{} g1 := &Graph{}
g2 := &Graph{} g2 := &Graph{}
g3 := &Graph{} g3 := &Graph{}
@@ -679,20 +626,20 @@ func TestPgraphGraphCmp1(t *testing.T) {
g4 := &Graph{} g4 := &Graph{}
g4.AddVertex(NV("v2")) g4.AddVertex(NV("v2"))
if err := g1.GraphCmp(g2, vertexCmpFn, edgeCmpFn); err != nil { if err := g1.GraphCmp(g2, strVertexCmpFn, strEdgeCmpFn); err != nil {
t.Errorf("should have no error during GraphCmp, but got: %v", err) t.Errorf("should have no error during GraphCmp, but got: %v", err)
} }
if err := g1.GraphCmp(g3, vertexCmpFn, edgeCmpFn); err == nil { if err := g1.GraphCmp(g3, strVertexCmpFn, strEdgeCmpFn); err == nil {
t.Errorf("should have error during GraphCmp, but got nil") t.Errorf("should have error during GraphCmp, but got nil")
} }
if err := g3.GraphCmp(g4, vertexCmpFn, edgeCmpFn); err == nil { if err := g3.GraphCmp(g4, strVertexCmpFn, strEdgeCmpFn); err == nil {
t.Errorf("should have error during GraphCmp, but got nil") t.Errorf("should have error during GraphCmp, but got nil")
} }
} }
func TestPgraphSort0(t *testing.T) { func TestSort0(t *testing.T) {
vs := []Vertex{} vs := []Vertex{}
s := Sort(vs) s := Sort(vs)
@@ -710,7 +657,7 @@ func TestPgraphSort0(t *testing.T) {
} }
} }
func TestPgraphSort1(t *testing.T) { func TestSort1(t *testing.T) {
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
v3 := NV("v3") v3 := NV("v3")
@@ -739,3 +686,84 @@ func TestPgraphSort1(t *testing.T) {
t.Errorf(str) t.Errorf(str)
} }
} }
func TestDeleteEdge1(t *testing.T) {
g, _ := NewGraph("g")
v1 := NV("v1")
v2 := NV("v2")
v3 := NV("v3")
e1 := NE("e1")
e2 := NE("e2")
e3 := NE("e3")
e4 := NE("e4")
e5 := NE("e5")
e6 := NE("e6")
g.AddEdge(v1, v2, e1)
g.AddEdge(v2, v3, e2)
g.AddEdge(v1, v3, e3)
g.AddEdge(v2, v1, e4)
g.AddEdge(v3, v2, e5)
g.AddEdge(v3, v1, e6)
g.DeleteEdge(e1)
g.DeleteEdge(e2)
g.DeleteEdge(e3)
g.DeleteEdge(e3)
if g.NumEdges() != 3 {
t.Errorf("expected number of edges: 3, instead of: %d", g.NumEdges())
}
}
func TestDeleteEdge2(t *testing.T) {
g, _ := NewGraph("g")
v1 := NV("v1")
v2 := NV("v2")
v3 := NV("v3")
v4 := NV("v4")
e1 := NE("e1")
e2 := NE("e2")
e3 := NE("e3")
e4 := NE("e4")
e5 := NE("e5")
e6 := NE("e6")
e7 := NE("e7")
g.AddEdge(v1, v2, e1)
g.AddEdge(v1, v2, e2)
g.AddEdge(v1, v3, e3)
g.AddEdge(v1, v4, e4)
g.AddEdge(v2, v1, e5)
g.AddEdge(v3, v1, e6)
g.AddEdge(v4, v1, e7)
g.DeleteEdge(e1)
g.DeleteEdge(e2)
g.DeleteEdge(e3)
g.DeleteEdge(e5)
g.DeleteEdge(e6)
ie := g.IncomingGraphEdges(v1)
oe := g.OutgoingGraphEdges(v1)
if !reflect.DeepEqual(ie, []Edge{e7}) {
res := ""
for _, e := range ie {
res += e.String() + " "
}
t.Errorf("expected incoming graph edges for vertex v1: e7, instead of: %s", res)
}
if !reflect.DeepEqual(oe, []Edge{e4}) {
res := ""
for _, e := range oe {
res += e.String() + " "
}
t.Errorf("expected outgoing graph edges for vertex v1: e4, instead of: %s", res)
}
}

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package pgraph package pgraph

View File

@@ -3,58 +3,25 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package pgraph package pgraph
import ( import (
"fmt"
"testing" "testing"
) )
// TODO: unify with the other function like this... func TestAddEdgeGraph1(t *testing.T) {
// TODO: where should we put our test helpers?
func runGraphCmp(t *testing.T, g1, g2 *Graph) {
err := g1.GraphCmp(g2, vertexCmpFn, edgeCmpFn)
if err != nil {
t.Logf(" actual (g1): %v%v", g1, fullPrint(g1))
t.Logf("expected (g2): %v%v", g2, fullPrint(g2))
t.Logf("Cmp error:")
t.Errorf("%v", err)
}
}
// TODO: unify with the other function like this...
func fullPrint(g *Graph) (str string) {
str += "\n"
for v := range g.Adjacency() {
str += fmt.Sprintf("* v: %s\n", v)
}
for v1 := range g.Adjacency() {
for v2, e := range g.Adjacency()[v1] {
str += fmt.Sprintf("* e: %s -> %s # %s\n", v1, v2, e)
}
}
return
}
// edgeGenFn generates unique edges for each vertex pair, assuming unique
// vertices.
func edgeGenFn(v1, v2 Vertex) Edge {
return NE(fmt.Sprintf("%s,%s", v1, v2))
}
func TestPgraphAddEdgeGraph1(t *testing.T) {
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
v3 := NV("v3") v3 := NV("v3")
@@ -82,10 +49,12 @@ func TestPgraphAddEdgeGraph1(t *testing.T) {
//expected.AddEdge(v3, v4, NE("v3,v4")) //expected.AddEdge(v3, v4, NE("v3,v4"))
//expected.AddEdge(v3, v5, NE("v3,v5")) //expected.AddEdge(v3, v5, NE("v3,v5"))
runGraphCmp(t, g, expected) if s := runGraphCmp(t, g, expected); s != "" {
t.Errorf("%s", s)
}
} }
func TestPgraphAddEdgeVertexGraph1(t *testing.T) { func TestAddEdgeVertexGraph1(t *testing.T) {
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
v3 := NV("v3") v3 := NV("v3")
@@ -113,10 +82,12 @@ func TestPgraphAddEdgeVertexGraph1(t *testing.T) {
expected.AddEdge(v3, v4, NE("v3,v4")) expected.AddEdge(v3, v4, NE("v3,v4"))
expected.AddEdge(v3, v5, NE("v3,v5")) expected.AddEdge(v3, v5, NE("v3,v5"))
runGraphCmp(t, g, expected) if s := runGraphCmp(t, g, expected); s != "" {
t.Errorf("%s", s)
}
} }
func TestPgraphAddEdgeGraphVertex1(t *testing.T) { func TestAddEdgeGraphVertex1(t *testing.T) {
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
v3 := NV("v3") v3 := NV("v3")
@@ -144,10 +115,12 @@ func TestPgraphAddEdgeGraphVertex1(t *testing.T) {
expected.AddEdge(v4, v3, NE("v4,v3")) expected.AddEdge(v4, v3, NE("v4,v3"))
expected.AddEdge(v5, v3, NE("v5,v3")) expected.AddEdge(v5, v3, NE("v5,v3"))
runGraphCmp(t, g, expected) if s := runGraphCmp(t, g, expected); s != "" {
t.Errorf("%s", s)
}
} }
func TestPgraphAddEdgeVertexGraphLight1(t *testing.T) { func TestAddEdgeVertexGraphLight1(t *testing.T) {
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
v3 := NV("v3") v3 := NV("v3")
@@ -175,10 +148,12 @@ func TestPgraphAddEdgeVertexGraphLight1(t *testing.T) {
expected.AddEdge(v3, v4, NE("v3,v4")) expected.AddEdge(v3, v4, NE("v3,v4"))
//expected.AddEdge(v3, v5, NE("v3,v5")) // not needed with light //expected.AddEdge(v3, v5, NE("v3,v5")) // not needed with light
runGraphCmp(t, g, expected) if s := runGraphCmp(t, g, expected); s != "" {
t.Errorf("%s", s)
}
} }
func TestPgraphAddEdgeGraphVertexLight1(t *testing.T) { func TestAddEdgeGraphVertexLight1(t *testing.T) {
v1 := NV("v1") v1 := NV("v1")
v2 := NV("v2") v2 := NV("v2")
v3 := NV("v3") v3 := NV("v3")
@@ -206,5 +181,7 @@ func TestPgraphAddEdgeGraphVertexLight1(t *testing.T) {
//expected.AddEdge(v4, v3, NE("v4,v3")) // not needed with light //expected.AddEdge(v4, v3, NE("v4,v3")) // not needed with light
expected.AddEdge(v5, v3, NE("v5,v3")) expected.AddEdge(v5, v3, NE("v5,v3"))
runGraphCmp(t, g, expected) if s := runGraphCmp(t, g, expected); s != "" {
t.Errorf("%s", s)
}
} }

93
pgraph/util_test.go Normal file
View File

@@ -0,0 +1,93 @@
// Mgmt
// Copyright (C) 2013-2017+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package pgraph
import (
"fmt"
"testing"
)
// vertex is a test struct to test the library.
type vertex struct {
name string
}
// String is a required method of the Vertex interface that we must fulfill.
func (v *vertex) String() string {
return v.name
}
// NV is a helper function to make testing easier. It creates a new noop vertex.
func NV(s string) Vertex {
return &vertex{s}
}
// edge is a test struct to test the library.
type edge struct {
name string
}
// String is a required method of the Edge interface that we must fulfill.
func (e *edge) String() string {
return e.name
}
// NE is a helper function to make testing easier. It creates a new noop edge.
func NE(s string) Edge {
return &edge{s}
}
// edgeGenFn generates unique edges for each vertex pair, assuming unique
// vertices.
func edgeGenFn(v1, v2 Vertex) Edge {
return NE(fmt.Sprintf("%s,%s", v1, v2))
}
func vertexAddFn(v Vertex) error {
return nil
}
func vertexRemoveFn(v Vertex) error {
return nil
}
func runGraphCmp(t *testing.T, g1, g2 *Graph) string {
err := g1.GraphCmp(g2, strVertexCmpFn, strEdgeCmpFn)
if err != nil {
str := ""
str += fmt.Sprintf(" actual (g1): %v%s", g1, fullPrint(g1))
str += fmt.Sprintf("expected (g2): %v%s", g2, fullPrint(g2))
str += fmt.Sprintf("cmp error:")
str += fmt.Sprintf("%v", err)
return str
}
return ""
}
func fullPrint(g *Graph) (str string) {
str += "\n"
for v := range g.Adjacency() {
str += fmt.Sprintf("* v: %s\n", v)
}
for v1 := range g.Adjacency() {
for v2, e := range g.Adjacency()[v1] {
str += fmt.Sprintf("* e: %s -> %s # %s\n", v1, v2, e)
}
}
return
}

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package prometheus provides functions that are useful to control and manage // Package prometheus provides functions that are useful to control and manage

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package puppet package puppet

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package puppet provides the integration entrypoint for the puppet language. // Package puppet provides the integration entrypoint for the puppet language.

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package recwatch package recwatch

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package recwatch package recwatch

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package recwatch provides recursive file watching events via fsnotify. // Package recwatch provides recursive file watching events via fsnotify.

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package remote provides the remoting facilities for agentless execution. // Package remote provides the remoting facilities for agentless execution.
@@ -692,7 +692,7 @@ type Remotes struct {
fileWatch chan string fileWatch chan string
cConns uint16 // number of concurrent ssh connections, zero means unlimited cConns uint16 // number of concurrent ssh connections, zero means unlimited
interactive bool // allow interactive prompting interactive bool // allow interactive prompting
sshPrivIdRsa string // path to ~/.ssh/id_rsa sshPrivIDRsa string // path to ~/.ssh/id_rsa
caching bool // whether to try and cache the copy of the binary caching bool // whether to try and cache the copy of the binary
depth uint16 // depth of this node in the remote execution hierarchy depth uint16 // depth of this node in the remote execution hierarchy
prefix string // folder prefix to use for misc storage prefix string // folder prefix to use for misc storage
@@ -702,6 +702,7 @@ type Remotes struct {
wg sync.WaitGroup // keep track of each running SSH connection wg sync.WaitGroup // keep track of each running SSH connection
lock sync.Mutex // mutex for access to sshmap lock sync.Mutex // mutex for access to sshmap
sshmap map[string]*SSH // map to each SSH struct with the remote as the key sshmap map[string]*SSH // map to each SSH struct with the remote as the key
running chan struct{} // closes when main loop is running
exiting bool // flag to let us know if we're exiting exiting bool // flag to let us know if we're exiting
exitChan chan struct{} // closes when we should exit exitChan chan struct{} // closes when we should exit
semaphore *semaphore.Semaphore // counting semaphore to limit concurrent connections semaphore *semaphore.Semaphore // counting semaphore to limit concurrent connections
@@ -714,7 +715,7 @@ type Remotes struct {
} }
// NewRemotes builds a Remotes struct. // NewRemotes builds a Remotes struct.
func NewRemotes(clientURLs, remoteURLs []string, noop bool, remotes []string, fileWatch chan string, cConns uint16, interactive bool, sshPrivIdRsa string, caching bool, depth uint16, prefix string, converger cv.Converger, convergerCb func(func(map[string]bool) error) (func(), error), flags Flags) *Remotes { func NewRemotes(clientURLs, remoteURLs []string, noop bool, remotes []string, fileWatch chan string, cConns uint16, interactive bool, sshPrivIDRsa string, caching bool, depth uint16, prefix string, converger cv.Converger, convergerCb func(func(map[string]bool) error) (func(), error), flags Flags) *Remotes {
return &Remotes{ return &Remotes{
clientURLs: clientURLs, clientURLs: clientURLs,
remoteURLs: remoteURLs, remoteURLs: remoteURLs,
@@ -723,13 +724,14 @@ func NewRemotes(clientURLs, remoteURLs []string, noop bool, remotes []string, fi
fileWatch: fileWatch, fileWatch: fileWatch,
cConns: cConns, cConns: cConns,
interactive: interactive, interactive: interactive,
sshPrivIdRsa: sshPrivIdRsa, sshPrivIDRsa: sshPrivIDRsa,
caching: caching, caching: caching,
depth: depth, depth: depth,
prefix: prefix, prefix: prefix,
converger: converger, converger: converger,
convergerCb: convergerCb, convergerCb: convergerCb,
sshmap: make(map[string]*SSH), sshmap: make(map[string]*SSH),
running: make(chan struct{}),
exitChan: make(chan struct{}), exitChan: make(chan struct{}),
semaphore: semaphore.NewSemaphore(int(cConns)), semaphore: semaphore.NewSemaphore(int(cConns)),
hostnames: make([]string, len(remotes)), hostnames: make([]string, len(remotes)),
@@ -830,18 +832,18 @@ func (obj *Remotes) NewSSH(file string) (*SSH, error) {
// sshKeyAuth is a helper function to get the ssh key auth struct needed // sshKeyAuth is a helper function to get the ssh key auth struct needed
func (obj *Remotes) sshKeyAuth() (ssh.AuthMethod, error) { func (obj *Remotes) sshKeyAuth() (ssh.AuthMethod, error) {
if obj.sshPrivIdRsa == "" { if obj.sshPrivIDRsa == "" {
return nil, fmt.Errorf("empty path specified") return nil, fmt.Errorf("empty path specified")
} }
p := "" p := ""
// TODO: this doesn't match strings of the form: ~james/.ssh/id_rsa // TODO: this doesn't match strings of the form: ~james/.ssh/id_rsa
if strings.HasPrefix(obj.sshPrivIdRsa, "~/") { if strings.HasPrefix(obj.sshPrivIDRsa, "~/") {
usr, err := user.Current() usr, err := user.Current()
if err != nil { if err != nil {
log.Printf("Remote: Can't find home directory automatically.") log.Printf("Remote: Can't find home directory automatically.")
return nil, err return nil, err
} }
p = path.Join(usr.HomeDir, obj.sshPrivIdRsa[len("~/"):]) p = path.Join(usr.HomeDir, obj.sshPrivIDRsa[len("~/"):])
} }
if p == "" { if p == "" {
return nil, fmt.Errorf("empty path specified") return nil, fmt.Errorf("empty path specified")
@@ -1022,11 +1024,17 @@ func (obj *Remotes) Run() {
}(sshobj, f) }(sshobj, f)
obj.lock.Unlock() obj.lock.Unlock()
} }
close(obj.running) // notify
} }
// Ready closes its returned channel when the Run method is up and ready. It is
// useful to know when ready, since we often execute Run in a go routine.
func (obj *Remotes) Ready() <-chan struct{} { return obj.running }
// Exit causes as much of the Remotes struct to shutdown as quickly and as // Exit causes as much of the Remotes struct to shutdown as quickly and as
// cleanly as possible. It only returns once everything is shutdown. // cleanly as possible. It only returns once everything is shutdown.
func (obj *Remotes) Exit() error { func (obj *Remotes) Exit() error {
<-obj.running // wait for Run to be finished before we exit!
obj.lock.Lock() obj.lock.Lock()
obj.exiting = true // don't spawn new ones once this flag is set! obj.exiting = true // don't spawn new ones once this flag is set!
obj.lock.Unlock() obj.lock.Unlock()

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources

View File

@@ -3,23 +3,23 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// +build !noaugeas // +build !noaugeas
package resources package resources
import ( import (
"encoding/gob"
"fmt" "fmt"
"log" "log"
"os" "os"
@@ -39,7 +39,6 @@ const (
) )
func init() { func init() {
gob.Register(&AugeasRes{})
RegisterResource("augeas", func() Res { return &AugeasRes{} }) RegisterResource("augeas", func() Res { return &AugeasRes{} })
} }
@@ -94,7 +93,6 @@ func (obj *AugeasRes) Validate() error {
// Init initiates the resource. // Init initiates the resource.
func (obj *AugeasRes) Init() error { func (obj *AugeasRes) Init() error {
obj.BaseRes.Kind = "augeas"
return obj.BaseRes.Init() // call base init, b/c we're overriding return obj.BaseRes.Init() // call base init, b/c we're overriding
} }

View File

@@ -3,17 +3,18 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// +build noaugeas // +build noaugeas
package resources package resources

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
@@ -108,6 +108,7 @@ func NewNoopResTest(name string) *NoopResTest {
NoopRes: NoopRes{ NoopRes: NoopRes{
BaseRes: BaseRes{ BaseRes: BaseRes{
Name: name, Name: name,
Kind: "noop",
MetaParams: MetaParams{ MetaParams: MetaParams{
AutoGroup: true, // always autogroup AutoGroup: true, // always autogroup
}, },

653
resources/aws_ec2.go Normal file
View File

@@ -0,0 +1,653 @@
// Mgmt
// Copyright (C) 2013-2017+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources
import (
"context"
"fmt"
"log"
"sync"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
errwrap "github.com/pkg/errors"
)
func init() {
RegisterResource("aws:ec2", func() Res { return &AwsEc2Res{} })
}
// AwsPrefix is a const which gets prepended onto the instance name.
const AwsPrefix = "mgmt:"
// AwsRegions is a list of all AWS regions generated using ec2.DescribeRegions.
// cn-north-1 and us-gov-west-1 are not returned, probably due to security.
// List available at http://docs.aws.amazon.com/general/latest/gr/rande.html
var AwsRegions = []string{
"ap-northeast-1",
"ap-northeast-2",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
"cn-north-1",
"eu-central-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-gov-west-1",
"us-west-1",
"us-west-2",
}
// AwsEc2Res is an AWS EC2 resource. In order to create a client session, your
// AWS credentials must be present in ~/.aws - For detailed instructions see
// http://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html
type AwsEc2Res struct {
BaseRes `yaml:",inline"`
State string `yaml:"state"` // state: running, stopped, terminated
Region string `yaml:"region"`
Type string `yaml:"type"` // the ec2 instance type, ie. t2.micro
ImageID string `yaml:"imageid"`
client *ec2.EC2 // client session for AWS API calls
}
// Default returns some sensible defaults for this resource.
func (obj *AwsEc2Res) Default() Res {
return &AwsEc2Res{
BaseRes: BaseRes{
MetaParams: DefaultMetaParams, // force a default
},
}
}
// Validate if the params passed in are valid data.
func (obj *AwsEc2Res) Validate() error {
if obj.State != "running" && obj.State != "stopped" && obj.State != "terminated" {
return fmt.Errorf("state must be 'running', 'stopped' or 'terminated'")
}
// compare obj.Region to the list of available AWS endpoints.
validRegion := false
for _, region := range AwsRegions {
if obj.Region == region {
validRegion = true
break
}
}
if !validRegion {
return fmt.Errorf("region must be a valid AWS endpoint")
}
// check the instance type
// there is currently no api call to enumerate available instance types
if obj.Type == "" {
return fmt.Errorf("no instance type specified")
}
// check imageId against a list of available images
sess, err := session.NewSession(&aws.Config{
Region: aws.String(obj.Region),
})
if err != nil {
return errwrap.Wrapf(err, "error creating session")
}
client := ec2.New(sess)
imagesInput := &ec2.DescribeImagesInput{}
images, err := client.DescribeImages(imagesInput)
if err != nil {
return errwrap.Wrapf(err, "error describing images")
}
validImage := false
for _, image := range images.Images {
if obj.ImageID == *image.ImageId {
validImage = true
break
}
}
if !validImage {
return fmt.Errorf("imageid must be a valid ami available in the specified region")
}
return obj.BaseRes.Validate()
}
// Init initializes the resource.
func (obj *AwsEc2Res) Init() error {
// create a client session for the AWS API
sess, err := session.NewSession(&aws.Config{
Region: aws.String(obj.Region),
})
if err != nil {
return errwrap.Wrapf(err, "error creating session")
}
obj.client = ec2.New(sess)
return obj.BaseRes.Init() // call base init, b/c we're overriding
}
// Watch is the primary listener for this resource and it outputs events.
func (obj *AwsEc2Res) Watch() error {
send := false
var exit *error
if err := obj.Running(); err != nil {
return err
}
type chanStruct struct {
str string
err error
}
awsChan := make(chan *chanStruct)
closeChan := make(chan struct{})
ctx, cancel := context.WithCancel(context.TODO())
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
select {
case <-closeChan:
cancel()
}
}()
wg.Add(1)
defer wg.Wait()
go func() {
defer wg.Done()
defer close(awsChan)
for {
diInput := &ec2.DescribeInstancesInput{
Filters: []*ec2.Filter{
{
Name: aws.String("tag:Name"),
Values: []*string{aws.String(obj.prependName())},
},
{
Name: aws.String("instance-state-name"),
Values: []*string{
aws.String("pending"),
aws.String("running"),
aws.String("stopping"),
aws.String("stopped"),
},
},
},
}
diOutput, err := obj.client.DescribeInstances(diInput)
if err != nil {
select {
case awsChan <- &chanStruct{
err: errwrap.Wrapf(err, "error describing instances"),
}:
case <-closeChan:
}
return
}
if obj.State == "running" {
stoppedInput := &ec2.DescribeInstancesInput{
Filters: []*ec2.Filter{
{
Name: aws.String("tag:Name"),
Values: []*string{aws.String(obj.prependName())},
},
{
Name: aws.String("instance-state-name"),
Values: []*string{
aws.String("stopped"),
},
},
},
}
stoppedOutput, err := obj.client.DescribeInstances(stoppedInput)
if err != nil {
select {
case awsChan <- &chanStruct{
err: errwrap.Wrapf(err, "error describing instances"),
}:
case <-closeChan:
}
return
}
if len(diOutput.Reservations) == 1 && len(stoppedOutput.Reservations) == 0 {
waitInput := &ec2.DescribeInstancesInput{
InstanceIds: []*string{diOutput.Reservations[0].Instances[0].InstanceId},
Filters: []*ec2.Filter{
{
Name: aws.String("instance-state-name"),
Values: []*string{
aws.String("stopped"),
aws.String("terminated"),
},
},
},
}
log.Printf("%s: Watching: %s", obj, *diOutput.Reservations[0].Instances[0].InstanceId)
if err := obj.client.WaitUntilInstanceStoppedWithContext(ctx, waitInput); err != nil {
if aerr, ok := err.(awserr.Error); ok {
if aerr.Code() == request.CanceledErrorCode {
log.Printf("%s: Request cancelled", obj)
}
}
select {
case awsChan <- &chanStruct{
err: errwrap.Wrapf(err, "unknown error waiting for instance to stop"),
}:
case <-closeChan:
}
return
}
stateOutput, err := obj.client.DescribeInstances(diInput)
if err != nil {
select {
case awsChan <- &chanStruct{
err: errwrap.Wrapf(err, "error describing instances"),
}:
case <-closeChan:
}
return
}
var stateName string
if len(stateOutput.Reservations) == 1 {
stateName = *stateOutput.Reservations[0].Instances[0].State.Name
}
if len(stateOutput.Reservations) == 0 || (len(stateOutput.Reservations) == 1 && stateName != "running") {
select {
case awsChan <- &chanStruct{
str: "stopped",
}:
case <-closeChan:
return
}
}
}
}
if obj.State == "stopped" {
runningInput := &ec2.DescribeInstancesInput{
Filters: []*ec2.Filter{
{
Name: aws.String("tag:Name"),
Values: []*string{aws.String(obj.prependName())},
},
{
Name: aws.String("instance-state-name"),
Values: []*string{
aws.String("running"),
},
},
},
}
runningOutput, err := obj.client.DescribeInstances(runningInput)
if err != nil {
select {
case awsChan <- &chanStruct{
err: errwrap.Wrapf(err, "error describing instances"),
}:
case <-closeChan:
}
return
}
if len(diOutput.Reservations) == 1 && len(runningOutput.Reservations) == 0 {
waitInput := &ec2.DescribeInstancesInput{
InstanceIds: []*string{diOutput.Reservations[0].Instances[0].InstanceId},
Filters: []*ec2.Filter{
{
Name: aws.String("instance-state-name"),
Values: []*string{aws.String("running")},
},
},
}
log.Printf("%s: watching: %s", obj, *diOutput.Reservations[0].Instances[0].InstanceId)
if err := obj.client.WaitUntilInstanceRunningWithContext(ctx, waitInput); err != nil {
if aerr, ok := err.(awserr.Error); ok {
if aerr.Code() == request.CanceledErrorCode {
log.Printf("%s: Request cancelled", obj)
}
}
select {
case awsChan <- &chanStruct{
err: errwrap.Wrapf(err, "unknown error waiting for instance to start"),
}:
case <-closeChan:
}
return
}
stateOutput, err := obj.client.DescribeInstances(diInput)
if err != nil {
select {
case awsChan <- &chanStruct{
err: errwrap.Wrapf(err, "error describing instances"),
}:
case <-closeChan:
}
return
}
var stateName string
if len(stateOutput.Reservations) == 1 {
stateName = *stateOutput.Reservations[0].Instances[0].State.Name
}
if len(stateOutput.Reservations) == 0 || (len(stateOutput.Reservations) == 1 && stateName != "stopped") {
select {
case awsChan <- &chanStruct{
str: "running",
}:
case <-closeChan:
return
}
}
}
}
if obj.State == "terminated" {
obj.client.WaitUntilInstanceExistsWithContext(ctx, diInput)
if err := obj.client.WaitUntilInstanceExistsWithContext(ctx, diInput); err != nil {
if aerr, ok := err.(awserr.Error); ok {
if aerr.Code() == request.CanceledErrorCode {
log.Printf("%s: Request cancelled", obj)
}
}
select {
case awsChan <- &chanStruct{
err: errwrap.Wrapf(err, "unknown error waiting for instance to exist"),
}:
case <-closeChan:
}
return
}
stateOutput, err := obj.client.DescribeInstances(diInput)
if err != nil {
select {
case awsChan <- &chanStruct{
err: errwrap.Wrapf(err, "error describing instances"),
}:
case <-closeChan:
}
return
}
if len(stateOutput.Reservations) == 1 {
{
select {
case awsChan <- &chanStruct{
str: "exists",
}:
case <-closeChan:
return
}
}
}
}
select {
case <-closeChan:
return
default:
}
}
}()
for {
select {
case event := <-obj.Events():
if exit, send = obj.ReadEvent(event); exit != nil {
close(closeChan)
return *exit
}
case msg, ok := <-awsChan:
if !ok {
return *exit
}
if err := msg.err; err != nil {
return err
}
log.Printf("%s: State: %s", obj, msg.str)
obj.StateOK(false)
send = true
}
if send {
send = false
obj.Event()
}
}
}
// CheckApply method for AwsEc2 resource.
func (obj *AwsEc2Res) CheckApply(apply bool) (checkOK bool, err error) {
log.Printf("%s: CheckApply(%t)", obj, apply)
const waitTimeout = 400
diInput := ec2.DescribeInstancesInput{
Filters: []*ec2.Filter{
{
Name: aws.String("tag:Name"),
Values: []*string{aws.String(obj.prependName())},
},
{
Name: aws.String("instance-state-name"),
Values: []*string{
aws.String("running"),
aws.String("pending"),
aws.String("stopped"),
aws.String("stopping"),
},
},
},
}
diOutput, err := obj.client.DescribeInstances(&diInput)
if err != nil {
return false, errwrap.Wrapf(err, "error describing instances")
}
if len(diOutput.Reservations) < 1 && obj.State == "terminated" {
return true, nil
}
if len(diOutput.Reservations) == 1 && *diOutput.Reservations[0].Instances[0].State.Name == obj.State {
return true, nil
}
if !apply {
return false, nil
}
if len(diOutput.Reservations) > 1 {
return false, fmt.Errorf("too many reservations")
}
ctx, cancel := context.WithTimeout(context.TODO(), waitTimeout*time.Second)
defer cancel()
if len(diOutput.Reservations) == 1 {
instanceID := diOutput.Reservations[0].Instances[0].InstanceId
describeInput := &ec2.DescribeInstancesInput{
InstanceIds: []*string{instanceID},
}
if len(diOutput.Reservations[0].Instances) > 1 {
return false, fmt.Errorf("more than one instance was returned")
}
if obj.State == "running" {
startInput := &ec2.StartInstancesInput{
InstanceIds: []*string{instanceID},
}
_, err := obj.client.StartInstances(startInput)
if err != nil {
return false, errwrap.Wrapf(err, "error starting instance")
}
if err := obj.client.WaitUntilInstanceRunningWithContext(ctx, describeInput); err != nil {
if aerr, ok := err.(awserr.Error); ok {
if aerr.Code() == request.CanceledErrorCode {
return false, errwrap.Wrapf(err, "timeout while waiting for instance to start")
}
}
return false, errwrap.Wrapf(err, "unknown error waiting for instance to start")
}
log.Printf("%s: instance running", obj)
}
if obj.State == "stopped" {
stopInput := &ec2.StopInstancesInput{
InstanceIds: []*string{instanceID},
}
_, err := obj.client.StopInstances(stopInput)
if err != nil {
return false, errwrap.Wrapf(err, "error stopping instance")
}
if err := obj.client.WaitUntilInstanceStoppedWithContext(ctx, describeInput); err != nil {
if aerr, ok := err.(awserr.Error); ok {
if aerr.Code() == request.CanceledErrorCode {
return false, errwrap.Wrapf(err, "timeout while waiting for instance to stop")
}
}
return false, errwrap.Wrapf(err, "unknown error waiting for instance to stop")
}
log.Printf("%s: instance stopped", obj)
}
if obj.State == "terminated" {
terminateInput := &ec2.TerminateInstancesInput{
InstanceIds: []*string{instanceID},
}
_, err := obj.client.TerminateInstances(terminateInput)
if err != nil {
return false, errwrap.Wrapf(err, "error terminating instance")
}
if err := obj.client.WaitUntilInstanceTerminatedWithContext(ctx, describeInput); err != nil {
if aerr, ok := err.(awserr.Error); ok {
if aerr.Code() == request.CanceledErrorCode {
return false, errwrap.Wrapf(err, "timeout while waiting for instance to terminate")
}
}
return false, errwrap.Wrapf(err, "unknown error waiting for instance to terminate")
}
log.Printf("%s: instance terminated", obj)
}
}
if len(diOutput.Reservations) < 1 && obj.State == "running" {
runParams := &ec2.RunInstancesInput{
ImageId: aws.String(obj.ImageID),
InstanceType: aws.String(obj.Type),
}
runParams.SetMinCount(1)
runParams.SetMaxCount(1)
runResult, err := obj.client.RunInstances(runParams)
if err != nil {
return false, errwrap.Wrapf(err, "could not create instance")
}
_, err = obj.client.CreateTags(&ec2.CreateTagsInput{
Resources: []*string{runResult.Instances[0].InstanceId},
Tags: []*ec2.Tag{
&ec2.Tag{
Key: aws.String("Name"),
Value: aws.String(obj.prependName()),
},
},
})
if err != nil {
return false, errwrap.Wrapf(err, "could not create tags for instance")
}
describeInput := &ec2.DescribeInstancesInput{
InstanceIds: []*string{runResult.Instances[0].InstanceId},
}
err = obj.client.WaitUntilInstanceRunningWithContext(ctx, describeInput)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
if aerr.Code() == request.CanceledErrorCode {
return false, errwrap.Wrapf(err, "timeout while waiting for instance to start")
}
}
return false, errwrap.Wrapf(err, "unknown error waiting for instance to start")
}
log.Printf("%s: instance running", obj)
}
return false, nil
}
// AwsEc2UID is the UID struct for AwsEc2Res.
type AwsEc2UID struct {
BaseUID
name string
}
// UIDs includes all params to make a unique identification of this object.
// Most resources only return one, although some resources can return multiple.
func (obj *AwsEc2Res) UIDs() []ResUID {
x := &AwsEc2UID{
BaseUID: BaseUID{Name: obj.GetName(), Kind: obj.GetKind()},
name: obj.Name,
}
return []ResUID{x}
}
// GroupCmp returns whether two resources can be grouped together or not.
func (obj *AwsEc2Res) GroupCmp(r Res) bool {
_, ok := r.(*AwsEc2Res)
if !ok {
return false
}
return false
}
// Compare two resources and return if they are equivalent.
func (obj *AwsEc2Res) Compare(r Res) bool {
// we can only compare AwsEc2Res to others of the same resource kind
res, ok := r.(*AwsEc2Res)
if !ok {
return false
}
if !obj.BaseRes.Compare(res) { // call base Compare
return false
}
if obj.Name != res.Name {
return false
}
if obj.State != res.State {
return false
}
if obj.Region != res.Region {
return false
}
if obj.Type != res.Type {
return false
}
if obj.ImageID != res.ImageID {
return false
}
return true
}
// UnmarshalYAML is the custom unmarshal handler for this struct.
// It is primarily useful for setting the defaults.
func (obj *AwsEc2Res) UnmarshalYAML(unmarshal func(interface{}) error) error {
type rawRes AwsEc2Res // indirection to avoid infinite recursion
def := obj.Default() // get the default
res, ok := def.(*AwsEc2Res) // put in the right format
if !ok {
return fmt.Errorf("could not convert to AwsEc2Res")
}
raw := rawRes(*res) // convert; the defaults go here
if err := unmarshal(&raw); err != nil {
return err
}
*obj = AwsEc2Res(raw) // restore from indirection with type conversion!
return nil
}
func (obj *AwsEc2Res) prependName() string {
return AwsPrefix + obj.GetName()
}

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
@@ -20,7 +20,6 @@ package resources
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/gob"
"fmt" "fmt"
"log" "log"
"os/exec" "os/exec"
@@ -34,7 +33,6 @@ import (
) )
func init() { func init() {
gob.Register(&ExecRes{})
RegisterResource("exec", func() Res { return &ExecRes{} }) RegisterResource("exec", func() Res { return &ExecRes{} })
} }
@@ -73,7 +71,6 @@ func (obj *ExecRes) Validate() error {
// Init runs some startup code for this resource. // Init runs some startup code for this resource.
func (obj *ExecRes) Init() error { func (obj *ExecRes) Init() error {
obj.BaseRes.Kind = "exec"
return obj.BaseRes.Init() // call base init, b/c we're overriding return obj.BaseRes.Init() // call base init, b/c we're overriding
} }

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
@@ -25,13 +25,17 @@ func TestExecSendRecv1(t *testing.T) {
r1 := &ExecRes{ r1 := &ExecRes{
BaseRes: BaseRes{ BaseRes: BaseRes{
Name: "exec1", Name: "exec1",
//MetaParams: MetaParams, Kind: "exec",
MetaParams: DefaultMetaParams,
}, },
Cmd: "echo hello world", Cmd: "echo hello world",
Shell: "/bin/bash", Shell: "/bin/bash",
} }
r1.Setup(nil, r1, r1) r1.Setup(nil, r1, r1)
if err := r1.Validate(); err != nil {
t.Errorf("validate failed with: %v", err)
}
defer func() { defer func() {
if err := r1.Close(); err != nil { if err := r1.Close(); err != nil {
t.Errorf("close failed with: %v", err) t.Errorf("close failed with: %v", err)
@@ -71,13 +75,17 @@ func TestExecSendRecv2(t *testing.T) {
r1 := &ExecRes{ r1 := &ExecRes{
BaseRes: BaseRes{ BaseRes: BaseRes{
Name: "exec1", Name: "exec1",
//MetaParams: MetaParams, Kind: "exec",
MetaParams: DefaultMetaParams,
}, },
Cmd: "echo hello world 1>&2", // to stderr Cmd: "echo hello world 1>&2", // to stderr
Shell: "/bin/bash", Shell: "/bin/bash",
} }
r1.Setup(nil, r1, r1) r1.Setup(nil, r1, r1)
if err := r1.Validate(); err != nil {
t.Errorf("validate failed with: %v", err)
}
defer func() { defer func() {
if err := r1.Close(); err != nil { if err := r1.Close(); err != nil {
t.Errorf("close failed with: %v", err) t.Errorf("close failed with: %v", err)
@@ -117,13 +125,17 @@ func TestExecSendRecv3(t *testing.T) {
r1 := &ExecRes{ r1 := &ExecRes{
BaseRes: BaseRes{ BaseRes: BaseRes{
Name: "exec1", Name: "exec1",
//MetaParams: MetaParams, Kind: "exec",
MetaParams: DefaultMetaParams,
}, },
Cmd: "echo hello world && echo goodbye world 1>&2", // to stdout && stderr Cmd: "echo hello world && echo goodbye world 1>&2", // to stdout && stderr
Shell: "/bin/bash", Shell: "/bin/bash",
} }
r1.Setup(nil, r1, r1) r1.Setup(nil, r1, r1)
if err := r1.Validate(); err != nil {
t.Errorf("validate failed with: %v", err)
}
defer func() { defer func() {
if err := r1.Close(); err != nil { if err := r1.Close(); err != nil {
t.Errorf("close failed with: %v", err) t.Errorf("close failed with: %v", err)

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
@@ -20,7 +20,6 @@ package resources
import ( import (
"bytes" "bytes"
"crypto/sha256" "crypto/sha256"
"encoding/gob"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io" "io"
@@ -41,7 +40,6 @@ import (
) )
func init() { func init() {
gob.Register(&FileRes{})
RegisterResource("file", func() Res { return &FileRes{} }) RegisterResource("file", func() Res { return &FileRes{} })
} }
@@ -148,7 +146,6 @@ func (obj *FileRes) Init() error {
obj.path = obj.GetPath() // compute once obj.path = obj.GetPath() // compute once
obj.isDir = strings.HasSuffix(obj.path, "/") // dirs have trailing slashes obj.isDir = strings.HasSuffix(obj.path, "/") // dirs have trailing slashes
obj.BaseRes.Kind = "file"
return obj.BaseRes.Init() // call base init, b/c we're overriding return obj.BaseRes.Init() // call base init, b/c we're overriding
} }

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// +build go1.7 // +build go1.7

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// +build !go1.7 // +build !go1.7

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources

View File

@@ -3,22 +3,21 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
import ( import (
"encoding/gob"
"fmt" "fmt"
"github.com/purpleidea/mgmt/pgraph" "github.com/purpleidea/mgmt/pgraph"
@@ -29,7 +28,6 @@ import (
func init() { func init() {
RegisterResource("graph", func() Res { return &GraphRes{} }) RegisterResource("graph", func() Res { return &GraphRes{} })
gob.Register(&GraphRes{})
} }
// GraphRes is a resource that recursively runs a sub graph of resources. // GraphRes is a resource that recursively runs a sub graph of resources.
@@ -89,7 +87,6 @@ func (obj *GraphRes) Init() error {
} }
} }
obj.BaseRes.Kind = "graph"
return obj.BaseRes.Init() // call base init, b/c we're overrriding return obj.BaseRes.Init() // call base init, b/c we're overrriding
} }

314
resources/group.go Normal file
View File

@@ -0,0 +1,314 @@
// Mgmt
// Copyright (C) 2013-2017+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources
import (
"fmt"
"io/ioutil"
"log"
"os/exec"
"os/user"
"strconv"
"syscall"
"github.com/purpleidea/mgmt/recwatch"
errwrap "github.com/pkg/errors"
)
func init() {
RegisterResource("group", func() Res { return &GroupRes{} })
}
const groupFile = "/etc/group"
// GroupRes is a user group resource.
type GroupRes struct {
BaseRes `yaml:",inline"`
State string `yaml:"state"` // state: exists, absent
GID *uint32 `yaml:"gid"` // the group's gid
recWatcher *recwatch.RecWatcher
}
// Default returns some sensible defaults for this resource.
func (obj *GroupRes) Default() Res {
return &GroupRes{
BaseRes: BaseRes{
MetaParams: DefaultMetaParams, // force a default
},
}
}
// Validate if the params passed in are valid data.
func (obj *GroupRes) Validate() error {
if obj.State != "exists" && obj.State != "absent" {
return fmt.Errorf("State must be 'exists' or 'absent'")
}
return obj.BaseRes.Validate()
}
// Init initializes the resource.
func (obj *GroupRes) Init() error {
return obj.BaseRes.Init() // call base init, b/c we're overriding
}
// Watch is the primary listener for this resource and it outputs events.
func (obj *GroupRes) Watch() error {
var err error
obj.recWatcher, err = recwatch.NewRecWatcher(groupFile, false)
if err != nil {
return err
}
defer obj.recWatcher.Close()
// notify engine that we're running
if err := obj.Running(); err != nil {
return err // bubble up a NACK...
}
var send = false // send event?
var exit *error
for {
if obj.debug {
log.Printf("%s: Watching: %s", obj, groupFile) // attempting to watch...
}
select {
case event, ok := <-obj.recWatcher.Events():
if !ok { // channel shutdown
return nil
}
if err := event.Error; err != nil {
return errwrap.Wrapf(err, "Unknown %s watcher error", obj)
}
if obj.debug { // don't access event.Body if event.Error isn't nil
log.Printf("%s: Event(%s): %v", obj, event.Body.Name, event.Body.Op)
}
send = true
obj.StateOK(false) // dirty
case event := <-obj.Events():
if exit, send = obj.ReadEvent(event); exit != nil {
return *exit // exit
}
//obj.StateOK(false) // dirty // these events don't invalidate state
}
// do all our event sending all together to avoid duplicate msgs
if send {
send = false
obj.Event()
}
}
}
// CheckApply method for Group resource.
func (obj *GroupRes) CheckApply(apply bool) (checkOK bool, err error) {
log.Printf("%s: CheckApply(%t)", obj, apply)
// check if the group exists
exists := true
group, err := user.LookupGroup(obj.GetName())
if err != nil {
if _, ok := err.(user.UnknownGroupError); !ok {
return false, errwrap.Wrapf(err, "error looking up group")
}
exists = false
}
// if the group doesn't exist and should be absent, we are done
if obj.State == "absent" && !exists {
return true, nil
}
// if the group exists and no GID is specified, we are done
if obj.State == "exists" && exists && obj.GID == nil {
return true, nil
}
if exists && obj.GID != nil {
// check if GID is taken
lookupGID, err := user.LookupGroupId(strconv.Itoa(int(*obj.GID)))
if err != nil {
if _, ok := err.(user.UnknownGroupIdError); !ok {
return false, errwrap.Wrapf(err, "error looking up GID")
}
}
if lookupGID != nil && lookupGID.Name != obj.GetName() {
return false, fmt.Errorf("the requested GID belongs to another group")
}
// get the existing group's GID
existingGID, err := strconv.ParseUint(group.Gid, 10, 32)
if err != nil {
return false, errwrap.Wrapf(err, "error casting existing GID")
}
// check if existing group has the wrong GID
// if it is wrong groupmod will change it to the desired value
if *obj.GID != uint32(existingGID) {
log.Printf("%s: Inconsistent GID: %s", obj, obj.GetName())
}
// if the group exists and has the correct GID, we are done
if obj.State == "exists" && *obj.GID == uint32(existingGID) {
return true, nil
}
}
if !apply {
return false, nil
}
var cmdName string
args := []string{obj.GetName()}
if obj.State == "exists" {
if exists {
log.Printf("%s: Modifying group: %s", obj, obj.GetName())
cmdName = "groupmod"
} else {
log.Printf("%s: Adding group: %s", obj, obj.GetName())
cmdName = "groupadd"
}
if obj.GID != nil {
args = append(args, "-g", fmt.Sprintf("%d", *obj.GID))
}
}
if obj.State == "absent" && exists {
log.Printf("%s: Deleting group: %s", obj, obj.GetName())
cmdName = "groupdel"
}
cmd := exec.Command(cmdName, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
Pgid: 0,
}
// open a pipe to get error messages from os/exec
stderr, err := cmd.StderrPipe()
if err != nil {
return false, errwrap.Wrapf(err, "failed to initialize stderr pipe")
}
// start the command
if err := cmd.Start(); err != nil {
return false, errwrap.Wrapf(err, "cmd failed to start")
}
// capture any error messages
slurp, err := ioutil.ReadAll(stderr)
if err != nil {
return false, errwrap.Wrapf(err, "error slurping error message")
}
// wait until cmd exits and return error message if any
if err := cmd.Wait(); err != nil {
return false, errwrap.Wrapf(err, "%s", slurp)
}
return false, nil
}
// GroupUID is the UID struct for GroupRes.
type GroupUID struct {
BaseUID
name string
gid *uint32
}
// IFF aka if and only if they are equivalent, return true. If not, false.
func (obj *GroupUID) IFF(uid ResUID) bool {
res, ok := uid.(*GroupUID)
if !ok {
return false
}
if obj.gid != nil && res.gid != nil {
if *obj.gid != *res.gid {
return false
}
}
if obj.name != "" && res.name != "" {
if obj.name != res.name {
return false
}
}
return true
}
// UIDs includes all params to make a unique identification of this object.
// Most resources only return one, although some resources can return multiple.
func (obj *GroupRes) UIDs() []ResUID {
x := &GroupUID{
BaseUID: BaseUID{Name: obj.GetName(), Kind: obj.GetKind()},
name: obj.Name,
gid: obj.GID,
}
return []ResUID{x}
}
// GroupCmp returns whether two resources can be grouped together or not.
func (obj *GroupRes) GroupCmp(r Res) bool {
_, ok := r.(*GroupRes)
if !ok {
return false
}
return false
}
// Compare two resources and return if they are equivalent.
func (obj *GroupRes) Compare(r Res) bool {
// we can only compare GroupRes to others of the same resource kind
res, ok := r.(*GroupRes)
if !ok {
return false
}
if !obj.BaseRes.Compare(res) { // call base Compare
return false
}
if obj.Name != res.Name {
return false
}
if obj.State != res.State {
return false
}
if (obj.GID == nil) != (res.GID == nil) {
return false
}
if obj.GID != nil && res.GID != nil {
if *obj.GID != *res.GID {
return false
}
}
return true
}
// UnmarshalYAML is the custom unmarshal handler for this struct.
// It is primarily useful for setting the defaults.
func (obj *GroupRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
type rawRes GroupRes // indirection to avoid infinite recursion
def := obj.Default() // get the default
res, ok := def.(*GroupRes) // put in the right format
if !ok {
return fmt.Errorf("could not convert to GroupRes")
}
raw := rawRes(*res) // convert; the defaults go here
if err := unmarshal(&raw); err != nil {
return err
}
*obj = GroupRes(raw) // restore from indirection with type conversion!
return nil
}

View File

@@ -3,22 +3,21 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
import ( import (
"encoding/gob"
"errors" "errors"
"fmt" "fmt"
"log" "log"
@@ -36,7 +35,6 @@ var ErrResourceInsufficientParameters = errors.New(
func init() { func init() {
RegisterResource("hostname", func() Res { return &HostnameRes{} }) RegisterResource("hostname", func() Res { return &HostnameRes{} })
gob.Register(&HostnameRes{})
} }
const ( const (
@@ -88,7 +86,6 @@ func (obj *HostnameRes) Validate() error {
// Init runs some startup code for this resource. // Init runs some startup code for this resource.
func (obj *HostnameRes) Init() error { func (obj *HostnameRes) Init() error {
obj.BaseRes.Kind = "hostname"
if obj.PrettyHostname == "" { if obj.PrettyHostname == "" {
obj.PrettyHostname = obj.Hostname obj.PrettyHostname = obj.Hostname
} }

View File

@@ -3,22 +3,21 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
import ( import (
"encoding/gob"
"fmt" "fmt"
"log" "log"
"strconv" "strconv"
@@ -28,7 +27,6 @@ import (
func init() { func init() {
RegisterResource("kv", func() Res { return &KVRes{} }) RegisterResource("kv", func() Res { return &KVRes{} })
gob.Register(&KVRes{})
} }
// KVResSkipCmpStyle represents the different styles of comparison when using SkipLessThan. // KVResSkipCmpStyle represents the different styles of comparison when using SkipLessThan.
@@ -89,7 +87,6 @@ func (obj *KVRes) Validate() error {
// Init initializes the resource. // Init initializes the resource.
func (obj *KVRes) Init() error { func (obj *KVRes) Init() error {
obj.BaseRes.Kind = "kv"
return obj.BaseRes.Init() // call base init, b/c we're overriding return obj.BaseRes.Init() // call base init, b/c we're overriding
} }

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources // TODO: can this be a separate package or will it break the dag? package resources // TODO: can this be a separate package or will it break the dag?

View File

@@ -3,22 +3,21 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
import ( import (
"encoding/gob"
"fmt" "fmt"
"log" "log"
"regexp" "regexp"
@@ -29,7 +28,6 @@ import (
func init() { func init() {
RegisterResource("msg", func() Res { return &MsgRes{} }) RegisterResource("msg", func() Res { return &MsgRes{} })
gob.Register(&MsgRes{})
} }
// MsgRes is a resource that writes messages to logs. // MsgRes is a resource that writes messages to logs.
@@ -71,12 +69,23 @@ func (obj *MsgRes) Validate() error {
return fmt.Errorf("fields cannot begin with _") return fmt.Errorf("fields cannot begin with _")
} }
} }
switch obj.Priority {
case "Emerg":
case "Alert":
case "Crit":
case "Err":
case "Warning":
case "Notice":
case "Info":
case "Debug":
default:
return fmt.Errorf("invalid Priority '%s'", obj.Priority)
}
return obj.BaseRes.Validate() return obj.BaseRes.Validate()
} }
// Init runs some startup code for this resource. // Init runs some startup code for this resource.
func (obj *MsgRes) Init() error { func (obj *MsgRes) Init() error {
obj.BaseRes.Kind = "msg"
return obj.BaseRes.Init() // call base init, b/c we're overrriding return obj.BaseRes.Init() // call base init, b/c we're overrriding
} }
@@ -97,7 +106,6 @@ func (obj *MsgRes) updateStateOK() {
} }
// JournalPriority converts a string description to a numeric priority. // JournalPriority converts a string description to a numeric priority.
// XXX: Have Validate() make sure it actually is one of these.
func (obj *MsgRes) journalPriority() journal.Priority { func (obj *MsgRes) journalPriority() journal.Priority {
switch obj.Priority { switch obj.Priority {
case "Emerg": case "Emerg":

54
resources/msg_test.go Normal file
View File

@@ -0,0 +1,54 @@
// Mgmt
// Copyright (C) 2013-2017+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources
import (
"testing"
)
func TestMsgValidate1(t *testing.T) {
r1 := &MsgRes{
BaseRes: BaseRes{
Name: "msg1",
Kind: "msg",
MetaParams: DefaultMetaParams,
},
Priority: "Debug",
}
r1.Setup(nil, r1, r1)
if err := r1.Validate(); err != nil {
t.Errorf("validate failed with: %v", err)
}
}
func TestMsgValidate2(t *testing.T) {
r1 := &MsgRes{
BaseRes: BaseRes{
Name: "msg1",
Kind: "msg",
MetaParams: DefaultMetaParams,
},
Priority: "UnrealPriority",
}
r1.Setup(nil, r1, r1)
if err := r1.Validate(); err == nil {
t.Errorf("validation error is nil")
}
}

View File

@@ -3,29 +3,27 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
import ( import (
"encoding/gob"
"fmt" "fmt"
"log" "log"
) )
func init() { func init() {
RegisterResource("noop", func() Res { return &NoopRes{} }) RegisterResource("noop", func() Res { return &NoopRes{} })
gob.Register(&NoopRes{})
} }
// NoopRes is a no-op resource that does nothing. // NoopRes is a no-op resource that does nothing.
@@ -50,7 +48,6 @@ func (obj *NoopRes) Validate() error {
// Init runs some startup code for this resource. // Init runs some startup code for this resource.
func (obj *NoopRes) Init() error { func (obj *NoopRes) Init() error {
obj.BaseRes.Kind = "noop"
return obj.BaseRes.Init() // call base init, b/c we're overriding return obj.BaseRes.Init() // call base init, b/c we're overriding
} }

View File

@@ -3,32 +3,34 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
import ( import (
"encoding/gob"
"errors" "errors"
"fmt" "fmt"
"log" "log"
"strconv"
"unicode"
"github.com/purpleidea/mgmt/util" "github.com/purpleidea/mgmt/util"
systemdDbus "github.com/coreos/go-systemd/dbus"
machined "github.com/coreos/go-systemd/machine1"
systemdUtil "github.com/coreos/go-systemd/util" systemdUtil "github.com/coreos/go-systemd/util"
"github.com/godbus/dbus" "github.com/godbus/dbus"
errwrap "github.com/pkg/errors" errwrap "github.com/pkg/errors"
machined "github.com/purpleidea/go-systemd/machine1"
) )
const ( const (
@@ -42,15 +44,14 @@ const (
func init() { func init() {
RegisterResource("nspawn", func() Res { return &NspawnRes{} }) RegisterResource("nspawn", func() Res { return &NspawnRes{} })
gob.Register(&NspawnRes{})
} }
// NspawnRes is an nspawn container resource. // NspawnRes is an nspawn container resource.
type NspawnRes struct { type NspawnRes struct {
BaseRes `yaml:",inline"` BaseRes `yaml:",inline"`
State string `yaml:"state"` State string `yaml:"state"`
// We're using the svc resource to start the machine because that's // We're using the svc resource to start and stop the machine because
// what machinectl does. We're not using svc.Watch because then we // that's what machinectl does. We're not using svc.Watch because then we
// would have two watches potentially racing each other and producing // would have two watches potentially racing each other and producing
// potentially unexpected results. We get everything we need to monitor // potentially unexpected results. We get everything we need to monitor
// the machine state changes from the org.freedesktop.machine1 object. // the machine state changes from the org.freedesktop.machine1 object.
@@ -67,33 +68,90 @@ func (obj *NspawnRes) Default() Res {
} }
} }
// makeComposite creates a pointer to a SvcRes. The pointer is used to
// validate and initialize the nested svc.
func (obj *NspawnRes) makeComposite() (*SvcRes, error) {
res, err := NewNamedResource("svc", fmt.Sprintf(nspawnServiceTmpl, obj.GetName()))
if err != nil {
return nil, err
}
svc := res.(*SvcRes)
svc.State = obj.State
return svc, nil
}
// systemdVersion uses dbus to check which version of systemd is installed.
func systemdVersion() (uint16, error) {
// check if systemd is running
if !systemdUtil.IsRunningSystemd() {
return 0, fmt.Errorf("systemd is not running")
}
bus, err := systemdDbus.NewSystemdConnection()
if err != nil {
return 0, errwrap.Wrapf(err, "failed to connect to bus")
}
defer bus.Close()
// get the systemd version
verString, err := bus.GetManagerProperty("Version")
if err != nil {
return 0, errwrap.Wrapf(err, "could not get version property")
}
// lose the surrounding quotes
verNum, err := strconv.Unquote(verString)
if err != nil {
return 0, errwrap.Wrapf(err, "error unquoting version number")
}
// cast to uint16
ver, err := strconv.ParseUint(verNum, 10, 16)
if err != nil {
return 0, errwrap.Wrapf(err, "error casting systemd version number")
}
return uint16(ver), nil
}
// Validate if the params passed in are valid data. // Validate if the params passed in are valid data.
func (obj *NspawnRes) Validate() error { func (obj *NspawnRes) Validate() error {
// TODO: validStates should be an enum! if len(obj.GetName()) > 64 {
validStates := map[string]struct{}{ return fmt.Errorf("name must be 64 characters or less")
stopped: {},
running: {},
} }
if _, exists := validStates[obj.State]; !exists { // check if systemd version is higher than 231 to allow non-alphanumeric
// machine names, as previous versions would error in such cases
ver, err := systemdVersion()
if err != nil {
return err
}
if ver < 231 {
for _, char := range obj.GetName() {
if !unicode.IsLetter(char) && !unicode.IsNumber(char) {
return fmt.Errorf("name must only contain alphanumeric characters for systemd versions < 231")
}
}
}
if obj.State != running && obj.State != stopped {
return fmt.Errorf("invalid state: %s", obj.State) return fmt.Errorf("invalid state: %s", obj.State)
} }
if err := obj.svc.Validate(); err != nil { // composite resource svc, err := obj.makeComposite()
return errwrap.Wrapf(err, "validate failed for embedded svc") if err != nil {
return errwrap.Wrapf(err, "makeComposite failed in validate")
}
if err := svc.Validate(); err != nil { // composite resource
return errwrap.Wrapf(err, "validate failed for embedded svc: %s", obj.svc)
} }
return obj.BaseRes.Validate() return obj.BaseRes.Validate()
} }
// Init runs some startup code for this resource. // Init runs some startup code for this resource.
func (obj *NspawnRes) Init() error { func (obj *NspawnRes) Init() error {
var serviceName = fmt.Sprintf(nspawnServiceTmpl, obj.GetName()) svc, err := obj.makeComposite()
obj.svc = &SvcRes{} if err != nil {
obj.svc.Name = serviceName return errwrap.Wrapf(err, "makeComposite failed in init")
obj.svc.State = obj.State }
obj.svc = svc
if err := obj.svc.Init(); err != nil { if err := obj.svc.Init(); err != nil {
return err return err
} }
obj.BaseRes.Kind = "nspawn"
return obj.BaseRes.Init() return obj.BaseRes.Init()
} }
@@ -118,8 +176,10 @@ func (obj *NspawnRes) Watch() error {
if err := call.Err; err != nil { if err := call.Err; err != nil {
return err return err
} }
buschan := make(chan *dbus.Signal, 10) // TODO: verify that implementation doesn't deadlock if there are unread
bus.Signal(buschan) // messages left in the channel
busChan := make(chan *dbus.Signal, 10)
bus.Signal(busChan)
// notify engine that we're running // notify engine that we're running
if err := obj.Running(); err != nil { if err := obj.Running(); err != nil {
@@ -129,9 +189,12 @@ func (obj *NspawnRes) Watch() error {
var send = false var send = false
var exit *error var exit *error
defer close(busChan)
defer bus.Close()
defer bus.RemoveSignal(busChan)
for { for {
select { select {
case event := <-buschan: case event := <-busChan:
// process org.freedesktop.machine1 events for this resource's name // process org.freedesktop.machine1 events for this resource's name
if event.Body[0] == obj.GetName() { if event.Body[0] == obj.GetName() {
log.Printf("%s: Event received: %v", obj, event.Name) log.Printf("%s: Event received: %v", obj, event.Name)
@@ -178,7 +241,7 @@ func (obj *NspawnRes) CheckApply(apply bool) (checkOK bool, err error) {
// compare the current state with the desired state and perform the // compare the current state with the desired state and perform the
// appropriate action // appropriate action
var exists = true var exists = true
properties, err := conn.GetProperties(obj.GetName()) properties, err := conn.DescribeMachine(obj.GetName())
if err != nil { if err != nil {
if err, ok := err.(dbus.Error); ok && err.Name != if err, ok := err.(dbus.Error); ok && err.Name !=
"org.freedesktop.machine1.NoSuchMachine" { "org.freedesktop.machine1.NoSuchMachine" {
@@ -211,26 +274,11 @@ func (obj *NspawnRes) CheckApply(apply bool) (checkOK bool, err error) {
return false, nil return false, nil
} }
if obj.debug {
log.Printf("%s: CheckApply() applying '%s' state", obj, obj.State) log.Printf("%s: CheckApply() applying '%s' state", obj, obj.State)
} // use the embedded svc to apply the correct state
if obj.State == running {
// start the machine using svc resource
log.Printf("%s: Starting machine", obj)
// assume state had to be changed at this point, ignore checkOK
if _, err := obj.svc.CheckApply(apply); err != nil { if _, err := obj.svc.CheckApply(apply); err != nil {
return false, errwrap.Wrapf(err, "nested svc failed") return false, errwrap.Wrapf(err, "nested svc failed")
} }
}
if obj.State == stopped {
// terminate the machine with
// org.freedesktop.machine1.Manager.KillMachine
log.Printf("%s: Stopping machine", obj)
if err := conn.TerminateMachine(obj.GetName()); err != nil {
return false, errwrap.Wrapf(err, "failed to stop machine")
}
}
return false, nil return false, nil
} }
@@ -287,6 +335,9 @@ func (obj *NspawnRes) Compare(r Res) bool {
if obj.Name != res.Name { if obj.Name != res.Name {
return false return false
} }
if obj.State != res.State {
return false
}
if !obj.svc.Compare(res.svc) { if !obj.svc.Compare(res.svc) {
return false return false

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package packagekit provides an interface to interact with packagekit. // Package packagekit provides an interface to interact with packagekit.
@@ -33,8 +33,8 @@ import (
// global tweaks of verbosity and code path // global tweaks of verbosity and code path
const ( const (
PK_DEBUG = false Debug = false
PARANOID = false // enable if you see any ghosts Paranoid = false // enable if you see any ghosts
) )
// constants which might need to be tweaked or which contain special dbus strings. // constants which might need to be tweaked or which contain special dbus strings.
@@ -74,76 +74,76 @@ var (
//type enum_filter uint64 //type enum_filter uint64
// https://github.com/hughsie/PackageKit/blob/master/lib/packagekit-glib2/pk-enum.c // https://github.com/hughsie/PackageKit/blob/master/lib/packagekit-glib2/pk-enum.c
const ( //static const PkEnumMatch enum_filter[] const ( //static const PkEnumMatch enum_filter[]
PK_FILTER_ENUM_UNKNOWN uint64 = 1 << iota // "unknown" PkFilterEnumUnknown uint64 = 1 << iota // "unknown"
PK_FILTER_ENUM_NONE // "none" PkFilterEnumNone // "none"
PK_FILTER_ENUM_INSTALLED // "installed" PkFilterEnumInstalled // "installed"
PK_FILTER_ENUM_NOT_INSTALLED // "~installed" PkFilterEnumNotInstalled // "~installed"
PK_FILTER_ENUM_DEVELOPMENT // "devel" PkFilterEnumDevelopment // "devel"
PK_FILTER_ENUM_NOT_DEVELOPMENT // "~devel" PkFilterEnumNotDevelopment // "~devel"
PK_FILTER_ENUM_GUI // "gui" PkFilterEnumGui // "gui"
PK_FILTER_ENUM_NOT_GUI // "~gui" PkFilterEnumNotGui // "~gui"
PK_FILTER_ENUM_FREE // "free" PkFilterEnumFree // "free"
PK_FILTER_ENUM_NOT_FREE // "~free" PkFilterEnumNotFree // "~free"
PK_FILTER_ENUM_VISIBLE // "visible" PkFilterEnumVisible // "visible"
PK_FILTER_ENUM_NOT_VISIBLE // "~visible" PkFilterEnumNotVisible // "~visible"
PK_FILTER_ENUM_SUPPORTED // "supported" PkFilterEnumSupported // "supported"
PK_FILTER_ENUM_NOT_SUPPORTED // "~supported" PkFilterEnumNotSupported // "~supported"
PK_FILTER_ENUM_BASENAME // "basename" PkFilterEnumBasename // "basename"
PK_FILTER_ENUM_NOT_BASENAME // "~basename" PkFilterEnumNotBasename // "~basename"
PK_FILTER_ENUM_NEWEST // "newest" PkFilterEnumNewest // "newest"
PK_FILTER_ENUM_NOT_NEWEST // "~newest" PkFilterEnumNotNewest // "~newest"
PK_FILTER_ENUM_ARCH // "arch" PkFilterEnumArch // "arch"
PK_FILTER_ENUM_NOT_ARCH // "~arch" PkFilterEnumNotArch // "~arch"
PK_FILTER_ENUM_SOURCE // "source" PkFilterEnumSource // "source"
PK_FILTER_ENUM_NOT_SOURCE // "~source" PkFilterEnumNotSource // "~source"
PK_FILTER_ENUM_COLLECTIONS // "collections" PkFilterEnumCollections // "collections"
PK_FILTER_ENUM_NOT_COLLECTIONS // "~collections" PkFilterEnumNotCollections // "~collections"
PK_FILTER_ENUM_APPLICATION // "application" PkFilterEnumApplication // "application"
PK_FILTER_ENUM_NOT_APPLICATION // "~application" PkFilterEnumNotApplication // "~application"
PK_FILTER_ENUM_DOWNLOADED // "downloaded" PkFilterEnumDownloaded // "downloaded"
PK_FILTER_ENUM_NOT_DOWNLOADED // "~downloaded" PkFilterEnumNotDownloaded // "~downloaded"
) )
// constants from packagekit c library. // constants from packagekit c library.
const ( //static const PkEnumMatch enum_transaction_flag[] const ( //static const PkEnumMatch enum_transaction_flag[]
PK_TRANSACTION_FLAG_ENUM_NONE uint64 = 1 << iota // "none" PkTransactionFlagEnumNone uint64 = 1 << iota // "none"
PK_TRANSACTION_FLAG_ENUM_ONLY_TRUSTED // "only-trusted" PkTransactionFlagEnumOnlyTrusted // "only-trusted"
PK_TRANSACTION_FLAG_ENUM_SIMULATE // "simulate" PkTransactionFlagEnumSimulate // "simulate"
PK_TRANSACTION_FLAG_ENUM_ONLY_DOWNLOAD // "only-download" PkTransactionFlagEnumOnlyDownload // "only-download"
PK_TRANSACTION_FLAG_ENUM_ALLOW_REINSTALL // "allow-reinstall" PkTransactionFlagEnumAllowReinstall // "allow-reinstall"
PK_TRANSACTION_FLAG_ENUM_JUST_REINSTALL // "just-reinstall" PkTransactionFlagEnumJustReinstall // "just-reinstall"
PK_TRANSACTION_FLAG_ENUM_ALLOW_DOWNGRADE // "allow-downgrade" PkTransactionFlagEnumAllowDowngrade // "allow-downgrade"
) )
// constants from packagekit c library. // constants from packagekit c library.
const ( //typedef enum const ( //typedef enum
PK_INFO_ENUM_UNKNOWN uint64 = 1 << iota PkInfoEnumUnknown uint64 = 1 << iota
PK_INFO_ENUM_INSTALLED PkInfoEnumInstalled
PK_INFO_ENUM_AVAILABLE PkInfoEnumAvailable
PK_INFO_ENUM_LOW PkInfoEnumLow
PK_INFO_ENUM_ENHANCEMENT PkInfoEnumEnhancement
PK_INFO_ENUM_NORMAL PkInfoEnumNormal
PK_INFO_ENUM_BUGFIX PkInfoEnumBugfix
PK_INFO_ENUM_IMPORTANT PkInfoEnumImportant
PK_INFO_ENUM_SECURITY PkInfoEnumSecurity
PK_INFO_ENUM_BLOCKED PkInfoEnumBlocked
PK_INFO_ENUM_DOWNLOADING PkInfoEnumDownloading
PK_INFO_ENUM_UPDATING PkInfoEnumUpdating
PK_INFO_ENUM_INSTALLING PkInfoEnumInstalling
PK_INFO_ENUM_REMOVING PkInfoEnumRemoving
PK_INFO_ENUM_CLEANUP PkInfoEnumCleanup
PK_INFO_ENUM_OBSOLETING PkInfoEnumObsoleting
PK_INFO_ENUM_COLLECTION_INSTALLED PkInfoEnumCollectionInstalled
PK_INFO_ENUM_COLLECTION_AVAILABLE PkInfoEnumCollectionAvailable
PK_INFO_ENUM_FINISHED PkInfoEnumFinished
PK_INFO_ENUM_REINSTALLING PkInfoEnumReinstalling
PK_INFO_ENUM_DOWNGRADING PkInfoEnumDowngrading
PK_INFO_ENUM_PREPARING PkInfoEnumPreparing
PK_INFO_ENUM_DECOMPRESSING PkInfoEnumDecompressing
PK_INFO_ENUM_UNTRUSTED PkInfoEnumUntrusted
PK_INFO_ENUM_TRUSTED PkInfoEnumTrusted
PK_INFO_ENUM_UNAVAILABLE PkInfoEnumUnavailable
PK_INFO_ENUM_LAST PkInfoEnumLast
) )
// Conn is a wrapper struct so we can pass bus connection around in the struct. // Conn is a wrapper struct so we can pass bus connection around in the struct.
@@ -184,7 +184,7 @@ func (bus *Conn) Close() error {
// internal helper to add signal matches to the bus, should only be called once // internal helper to add signal matches to the bus, should only be called once
func (bus *Conn) matchSignal(ch chan *dbus.Signal, path dbus.ObjectPath, iface string, signals []string) error { func (bus *Conn) matchSignal(ch chan *dbus.Signal, path dbus.ObjectPath, iface string, signals []string) error {
if PK_DEBUG { if Debug {
log.Printf("PackageKit: matchSignal(%v, %v, %v, %v)", ch, path, iface, signals) log.Printf("PackageKit: matchSignal(%v, %v, %v, %v)", ch, path, iface, signals)
} }
// eg: gdbus monitor --system --dest org.freedesktop.PackageKit --object-path /org/freedesktop/PackageKit | grep <signal> // eg: gdbus monitor --system --dest org.freedesktop.PackageKit --object-path /org/freedesktop/PackageKit | grep <signal>
@@ -224,7 +224,7 @@ func (bus *Conn) WatchChanges() (chan *dbus.Signal, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if PARANOID { // TODO: this filtering might not be necessary anymore... if Paranoid { // TODO: this filtering might not be necessary anymore...
// try to handle the filtering inside this function! // try to handle the filtering inside this function!
rch := make(chan *dbus.Signal) rch := make(chan *dbus.Signal)
go func() { go func() {
@@ -257,7 +257,7 @@ func (bus *Conn) WatchChanges() (chan *dbus.Signal, error) {
// CreateTransaction creates and returns a transaction path. // CreateTransaction creates and returns a transaction path.
func (bus *Conn) CreateTransaction() (dbus.ObjectPath, error) { func (bus *Conn) CreateTransaction() (dbus.ObjectPath, error) {
if PK_DEBUG { if Debug {
log.Println("PackageKit: CreateTransaction()") log.Println("PackageKit: CreateTransaction()")
} }
var interfacePath dbus.ObjectPath var interfacePath dbus.ObjectPath
@@ -266,7 +266,7 @@ func (bus *Conn) CreateTransaction() (dbus.ObjectPath, error) {
if call != nil { if call != nil {
return "", call return "", call
} }
if PK_DEBUG { if Debug {
log.Printf("PackageKit: CreateTransaction(): %v", interfacePath) log.Printf("PackageKit: CreateTransaction(): %v", interfacePath)
} }
return interfacePath, nil return interfacePath, nil
@@ -284,12 +284,12 @@ func (bus *Conn) ResolvePackages(packages []string, filter uint64) ([]string, er
// add signal matches for Package and Finished which will always be last // add signal matches for Package and Finished which will always be last
var signals = []string{"Package", "Finished", "Error", "Destroy"} var signals = []string{"Package", "Finished", "Error", "Destroy"}
bus.matchSignal(ch, interfacePath, PkIfaceTransaction, signals) bus.matchSignal(ch, interfacePath, PkIfaceTransaction, signals)
if PK_DEBUG { if Debug {
log.Printf("PackageKit: ResolvePackages(): Object(%v, %v)", PkIface, interfacePath) log.Printf("PackageKit: ResolvePackages(): Object(%v, %v)", PkIface, interfacePath)
} }
obj := bus.GetBus().Object(PkIface, interfacePath) // pass in found transaction path obj := bus.GetBus().Object(PkIface, interfacePath) // pass in found transaction path
call := obj.Call(FmtTransactionMethod("Resolve"), 0, filter, packages) call := obj.Call(FmtTransactionMethod("Resolve"), 0, filter, packages)
if PK_DEBUG { if Debug {
log.Println("PackageKit: ResolvePackages(): Call: Success!") log.Println("PackageKit: ResolvePackages(): Call: Success!")
} }
if call.Err != nil { if call.Err != nil {
@@ -300,7 +300,7 @@ loop:
// FIXME: add a timeout option to error in case signals are dropped! // FIXME: add a timeout option to error in case signals are dropped!
select { select {
case signal := <-ch: case signal := <-ch:
if PK_DEBUG { if Debug {
log.Printf("PackageKit: ResolvePackages(): Signal: %+v", signal) log.Printf("PackageKit: ResolvePackages(): Signal: %+v", signal)
} }
if signal.Path != interfacePath { if signal.Path != interfacePath {
@@ -339,7 +339,7 @@ loop:
// IsInstalledList queries a list of packages to see if they are installed. // IsInstalledList queries a list of packages to see if they are installed.
func (bus *Conn) IsInstalledList(packages []string) ([]bool, error) { func (bus *Conn) IsInstalledList(packages []string) ([]bool, error) {
var filter uint64 // initializes at the "zero" value of 0 var filter uint64 // initializes at the "zero" value of 0
filter += PK_FILTER_ENUM_ARCH // always search in our arch filter += PkFilterEnumArch // always search in our arch
packageIDs, e := bus.ResolvePackages(packages, filter) packageIDs, e := bus.ResolvePackages(packages, filter)
if e != nil { if e != nil {
return nil, fmt.Errorf("ResolvePackages error: %v", e) return nil, fmt.Errorf("ResolvePackages error: %v", e)
@@ -396,7 +396,11 @@ func (bus *Conn) InstallPackages(packageIDs []string, transactionFlags uint64) e
bus.matchSignal(ch, interfacePath, PkIfaceTransaction, signals) bus.matchSignal(ch, interfacePath, PkIfaceTransaction, signals)
obj := bus.GetBus().Object(PkIface, interfacePath) // pass in found transaction path obj := bus.GetBus().Object(PkIface, interfacePath) // pass in found transaction path
call := obj.Call(FmtTransactionMethod("InstallPackages"), 0, transactionFlags, packageIDs) call := obj.Call(FmtTransactionMethod("RefreshCache"), 0, false)
if call.Err != nil {
return call.Err
}
call = obj.Call(FmtTransactionMethod("InstallPackages"), 0, transactionFlags, packageIDs)
if call.Err != nil { if call.Err != nil {
return call.Err return call.Err
} }
@@ -593,7 +597,7 @@ loop:
// GetUpdates gets a list of packages that are installed and which can be updated, mod filter. // GetUpdates gets a list of packages that are installed and which can be updated, mod filter.
func (bus *Conn) GetUpdates(filter uint64) ([]string, error) { func (bus *Conn) GetUpdates(filter uint64) ([]string, error) {
if PK_DEBUG { if Debug {
log.Println("PackageKit: GetUpdates()") log.Println("PackageKit: GetUpdates()")
} }
packageIDs := []string{} packageIDs := []string{}
@@ -664,11 +668,11 @@ func (bus *Conn) PackagesToPackageIDs(packageMap map[string]string, filter uint6
count++ count++
} }
if !(filter&PK_FILTER_ENUM_ARCH == PK_FILTER_ENUM_ARCH) { if !(filter&PkFilterEnumArch == PkFilterEnumArch) {
filter += PK_FILTER_ENUM_ARCH // always search in our arch filter += PkFilterEnumArch // always search in our arch
} }
if PK_DEBUG { if Debug {
log.Printf("PackageKit: PackagesToPackageIDs(): %v", strings.Join(packages, ", ")) log.Printf("PackageKit: PackagesToPackageIDs(): %v", strings.Join(packages, ", "))
} }
resolved, e := bus.ResolvePackages(packages, filter) resolved, e := bus.ResolvePackages(packages, filter)
@@ -771,7 +775,7 @@ func (bus *Conn) PackagesToPackageIDs(packageMap map[string]string, filter uint6
// this check is for packages that need to verify their "newest" status // this check is for packages that need to verify their "newest" status
// we need to know this so we can install the correct newest packageID! // we need to know this so we can install the correct newest packageID!
recursion := make(map[string]*PkPackageIDActionData) recursion := make(map[string]*PkPackageIDActionData)
if !(filter&PK_FILTER_ENUM_NEWEST == PK_FILTER_ENUM_NEWEST) { if !(filter&PkFilterEnumNewest == PkFilterEnumNewest) {
checkPackages := []string{} checkPackages := []string{}
filteredPackageMap := make(map[string]string) filteredPackageMap := make(map[string]string)
for index, pkg := range packages { for index, pkg := range packages {
@@ -788,13 +792,13 @@ func (bus *Conn) PackagesToPackageIDs(packageMap map[string]string, filter uint6
} }
// we _could_ do a second resolve and then parse like this... // we _could_ do a second resolve and then parse like this...
//resolved, e := bus.ResolvePackages(..., filter+PK_FILTER_ENUM_NEWEST) //resolved, e := bus.ResolvePackages(..., filter+PkFilterEnumNewest)
// but that's basically what recursion here could do too! // but that's basically what recursion here could do too!
if len(checkPackages) > 0 { if len(checkPackages) > 0 {
if PK_DEBUG { if Debug {
log.Printf("PackageKit: PackagesToPackageIDs(): Recurse: %v", strings.Join(checkPackages, ", ")) log.Printf("PackageKit: PackagesToPackageIDs(): Recurse: %v", strings.Join(checkPackages, ", "))
} }
recursion, e = bus.PackagesToPackageIDs(filteredPackageMap, filter+PK_FILTER_ENUM_NEWEST) recursion, e = bus.PackagesToPackageIDs(filteredPackageMap, filter+PkFilterEnumNewest)
if e != nil { if e != nil {
return nil, fmt.Errorf("Recursion error: %v", e) return nil, fmt.Errorf("Recursion error: %v", e)
} }

View File

@@ -3,23 +3,22 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
import ( import (
"crypto/rand" "crypto/rand"
"encoding/gob"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
@@ -35,7 +34,6 @@ import (
func init() { func init() {
RegisterResource("password", func() Res { return &PasswordRes{} }) RegisterResource("password", func() Res { return &PasswordRes{} })
gob.Register(&PasswordRes{})
} }
const ( const (
@@ -74,7 +72,6 @@ func (obj *PasswordRes) Validate() error {
// Init generates a new password for this resource if one was not provided. It // Init generates a new password for this resource if one was not provided. It
// will save this into a local file. It will load it back in from previous runs. // will save this into a local file. It will load it back in from previous runs.
func (obj *PasswordRes) Init() error { func (obj *PasswordRes) Init() error {
obj.BaseRes.Kind = "password" // must be set before using VarDir
dir, err := obj.VarDir("") dir, err := obj.VarDir("")
if err != nil { if err != nil {

View File

@@ -3,22 +3,21 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
import ( import (
"encoding/gob"
"fmt" "fmt"
"log" "log"
"path" "path"
@@ -32,7 +31,6 @@ import (
func init() { func init() {
RegisterResource("pkg", func() Res { return &PkgRes{} }) RegisterResource("pkg", func() Res { return &PkgRes{} })
gob.Register(&PkgRes{})
} }
// PkgRes is a package resource for packagekit. // PkgRes is a package resource for packagekit.
@@ -67,7 +65,6 @@ func (obj *PkgRes) Validate() error {
// Init runs some startup code for this resource. // Init runs some startup code for this resource.
func (obj *PkgRes) Init() error { func (obj *PkgRes) Init() error {
obj.BaseRes.Kind = "pkg"
if err := obj.BaseRes.Init(); err != nil { // call base init, b/c we're overriding if err := obj.BaseRes.Init(); err != nil { // call base init, b/c we're overriding
return err return err
} }
@@ -183,19 +180,19 @@ func (obj *PkgRes) pkgMappingHelper(bus *packagekit.Conn) (map[string]*packageki
packageMap := obj.groupMappingHelper() // get the grouped values packageMap := obj.groupMappingHelper() // get the grouped values
packageMap[obj.Name] = obj.State // key is pkg name, value is pkg state packageMap[obj.Name] = obj.State // key is pkg name, value is pkg state
var filter uint64 // initializes at the "zero" value of 0 var filter uint64 // initializes at the "zero" value of 0
filter += packagekit.PK_FILTER_ENUM_ARCH // always search in our arch (optional!) filter += packagekit.PkFilterEnumArch // always search in our arch (optional!)
// we're requesting latest version, or to narrow down install choices! // we're requesting latest version, or to narrow down install choices!
if obj.State == "newest" || obj.State == "installed" { if obj.State == "newest" || obj.State == "installed" {
// if we add this, we'll still see older packages if installed // if we add this, we'll still see older packages if installed
// this is an optimization, and is *optional*, this logic is // this is an optimization, and is *optional*, this logic is
// handled inside of PackagesToPackageIDs now automatically! // handled inside of PackagesToPackageIDs now automatically!
filter += packagekit.PK_FILTER_ENUM_NEWEST // only search for newest packages filter += packagekit.PkFilterEnumNewest // only search for newest packages
} }
if !obj.AllowNonFree { if !obj.AllowNonFree {
filter += packagekit.PK_FILTER_ENUM_FREE filter += packagekit.PkFilterEnumFree
} }
if !obj.AllowUnsupported { if !obj.AllowUnsupported {
filter += packagekit.PK_FILTER_ENUM_SUPPORTED filter += packagekit.PkFilterEnumSupported
} }
result, err := bus.PackagesToPackageIDs(packageMap, filter) result, err := bus.PackagesToPackageIDs(packageMap, filter)
if err != nil { if err != nil {
@@ -301,7 +298,7 @@ func (obj *PkgRes) CheckApply(apply bool) (checkOK bool, err error) {
var transactionFlags uint64 // initializes at the "zero" value of 0 var transactionFlags uint64 // initializes at the "zero" value of 0
if !obj.AllowUntrusted { // allow if !obj.AllowUntrusted { // allow
transactionFlags += packagekit.PK_TRANSACTION_FLAG_ENUM_ONLY_TRUSTED transactionFlags += packagekit.PkTransactionFlagEnumOnlyTrusted
} }
// apply correct state! // apply correct state!
log.Printf("%s: Set: %v...", obj.fmtNames(util.StrListIntersection(applyPackages, obj.getNames())), obj.State) log.Printf("%s: Set: %v...", obj.fmtNames(util.StrListIntersection(applyPackages, obj.getNames())), obj.State)

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources

View File

@@ -3,22 +3,23 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package resources provides the resource framework and idempotent primitives. // Package resources provides the resource framework and idempotent primitives.
package resources package resources
import ( import (
"encoding/gob"
"fmt" "fmt"
"log" "log"
"math" "math"
@@ -43,18 +44,40 @@ var registeredResources = map[string]func() Res{}
// RegisterResource registers a new resource by providing a constructor // RegisterResource registers a new resource by providing a constructor
// function that returns a resource object ready to be unmarshalled from YAML. // function that returns a resource object ready to be unmarshalled from YAML.
func RegisterResource(name string, creator func() Res) { func RegisterResource(kind string, fn func() Res) {
registeredResources[name] = creator if _, ok := registeredResources[kind]; ok {
panic(fmt.Sprintf("a resource kind of %s is already registered", kind))
}
gob.Register(fn())
registeredResources[kind] = fn
} }
// NewEmptyNamedResource returns an empty resource object from a registered // NewResource returns an empty resource object from a registered kind. It
// type, ready to be unmarshalled. // errors if the resource kind doesn't exist.
func NewEmptyNamedResource(name string) (Res, error) { func NewResource(kind string) (Res, error) {
fn, ok := registeredResources[name] fn, ok := registeredResources[kind]
if !ok { if !ok {
return nil, fmt.Errorf("no resource named %s available", name) return nil, fmt.Errorf("no resource kind `%s` available", kind)
} }
return fn(), nil res := fn().Default()
res.SetKind(kind)
//*res.Meta() = DefaultMetaParams // TODO: centralize this here?
return res, nil
}
// NewNamedResource returns an empty resource object from a registered kind. It
// also sets the name. It is a wrapper around NewResource. It also errors if the
// name is empty.
func NewNamedResource(kind, name string) (Res, error) {
if name == "" {
return nil, fmt.Errorf("resource name is empty")
}
res, err := NewResource(kind)
if err != nil {
return nil, err
}
res.SetName(name)
return res, nil
} }
//go:generate stringer -type=ResState -output=resstate_stringer.go //go:generate stringer -type=ResState -output=resstate_stringer.go
@@ -108,11 +131,13 @@ type ResData struct {
// The Base interface is everything that is common to all resources. // The Base interface is everything that is common to all resources.
// Everything here only needs to be implemented once, in the BaseRes. // Everything here only needs to be implemented once, in the BaseRes.
type Base interface { type Base interface {
fmt.Stringer // String() string
GetName() string // can't be named "Name()" because of struct field GetName() string // can't be named "Name()" because of struct field
SetName(string) SetName(string)
SetKind(string) SetKind(string)
GetKind() string GetKind() string
String() string
Meta() *MetaParams Meta() *MetaParams
Events() chan *event.Event Events() chan *event.Event
Data() *ResData Data() *ResData
@@ -130,6 +155,7 @@ type Base interface {
Refresh() bool // is there a pending refresh to run? Refresh() bool // is there a pending refresh to run?
SetRefresh(bool) // set the refresh state of this resource SetRefresh(bool) // set the refresh state of this resource
SendRecv(Res) (map[string]bool, error) // send->recv data passing function SendRecv(Res) (map[string]bool, error) // send->recv data passing function
SetRecv(map[string]*Send)
IsStateOK() bool IsStateOK() bool
StateOK(b bool) StateOK(b bool)
GroupCmp(Res) bool // TODO: is there a better name for this? GroupCmp(Res) bool // TODO: is there a better name for this?
@@ -520,6 +546,19 @@ func (obj *BaseRes) Compare(res Res) bool {
if !cmpSlices(obj.Meta().Sema, res.Meta().Sema) { if !cmpSlices(obj.Meta().Sema, res.Meta().Sema) {
return false return false
} }
// if resources are grouped, are the groups the same?
if i, j := obj.GetGroup(), res.GetGroup(); len(i) != len(j) {
return false
} else if len(i) > 0 { // trick the golinter
ix, jx := Sort(i), Sort(j)
for k := range ix {
if !ix[k].Compare(jx[k]) { // compare sub resources
return false
}
}
}
return true return true
} }

View File

@@ -3,24 +3,21 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
import ( import (
"bytes"
"encoding/base64"
"encoding/gob"
"testing" "testing"
) )
@@ -42,6 +39,7 @@ func TestCompare2(t *testing.T) {
r1 := &NoopRes{ r1 := &NoopRes{
BaseRes: BaseRes{ BaseRes: BaseRes{
Name: "noop1", Name: "noop1",
Kind: "noop",
MetaParams: MetaParams{ MetaParams: MetaParams{
Noop: true, Noop: true,
}, },
@@ -49,7 +47,8 @@ func TestCompare2(t *testing.T) {
} }
r2 := &NoopRes{ r2 := &NoopRes{
BaseRes: BaseRes{ BaseRes: BaseRes{
Name: "noop1", // same nampe Name: "noop1", // same name
Kind: "noop",
MetaParams: MetaParams{ MetaParams: MetaParams{
Noop: false, // different noop Noop: false, // different noop
}, },
@@ -65,86 +64,6 @@ func TestCompare2(t *testing.T) {
} }
} }
func TestMiscEncodeDecode1(t *testing.T) {
var err error
//gob.Register( &NoopRes{} ) // happens in noop.go : init()
//gob.Register( &FileRes{} ) // happens in file.go : init()
// ...
// encode
var input interface{} = &FileRes{}
b1 := bytes.Buffer{}
e := gob.NewEncoder(&b1)
err = e.Encode(&input) // pass with &
if err != nil {
t.Errorf("Gob failed to Encode: %v", err)
}
str := base64.StdEncoding.EncodeToString(b1.Bytes())
// decode
var output interface{}
bb, err := base64.StdEncoding.DecodeString(str)
if err != nil {
t.Errorf("Base64 failed to Decode: %v", err)
}
b2 := bytes.NewBuffer(bb)
d := gob.NewDecoder(b2)
err = d.Decode(&output) // pass with &
if err != nil {
t.Errorf("Gob failed to Decode: %v", err)
}
res1, ok := input.(Res)
if !ok {
t.Errorf("Input %v is not a Res", res1)
return
}
res2, ok := output.(Res)
if !ok {
t.Errorf("Output %v is not a Res", res2)
return
}
if !res1.Compare(res2) {
t.Error("The input and output Res values do not match!")
}
}
func TestMiscEncodeDecode2(t *testing.T) {
var err error
//gob.Register( &NoopRes{} ) // happens in noop.go : init()
//gob.Register( &FileRes{} ) // happens in file.go : init()
// ...
// encode
var input Res = &FileRes{}
b64, err := ResToB64(input)
if err != nil {
t.Errorf("Can't encode: %v", err)
return
}
output, err := B64ToRes(b64)
if err != nil {
t.Errorf("Can't decode: %v", err)
return
}
res1, ok := input.(Res)
if !ok {
t.Errorf("Input %v is not a Res", res1)
return
}
res2, ok := output.(Res)
if !ok {
t.Errorf("Output %v is not a Res", res2)
return
}
if !res1.Compare(res2) {
t.Error("The input and output Res values do not match!")
}
}
func TestIFF(t *testing.T) { func TestIFF(t *testing.T) {
uid := &BaseUID{Name: "/tmp/unit-test"} uid := &BaseUID{Name: "/tmp/unit-test"}
same := &BaseUID{Name: "/tmp/unit-test"} same := &BaseUID{Name: "/tmp/unit-test"}

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
@@ -41,6 +41,7 @@ func NewNoopResTestSema(name string, semas []string) *NoopResTest {
NoopRes: NoopRes{ NoopRes: NoopRes{
BaseRes: BaseRes{ BaseRes: BaseRes{
Name: name, Name: name,
Kind: "noop",
MetaParams: MetaParams{ MetaParams: MetaParams{
AutoGroup: true, // always autogroup AutoGroup: true, // always autogroup
Sema: semas, Sema: semas,

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
@@ -173,6 +173,11 @@ type Send struct {
Changed bool // set to true if this key was updated, read only! Changed bool // set to true if this key was updated, read only!
} }
// SetRecv sets the Res Recv field to given map of Send structs.
func (obj *BaseRes) SetRecv(recv map[string]*Send) {
obj.Recv = recv
}
// SendRecv pulls in the sent values into the receive slots. It is called by the // SendRecv pulls in the sent values into the receive slots. It is called by the
// receiver and must be given as input the full resource struct to receive on. // receiver and must be given as input the full resource struct to receive on.
func (obj *BaseRes) SendRecv(res Res) (map[string]bool, error) { func (obj *BaseRes) SendRecv(res Res) (map[string]bool, error) {

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// DOCS: https://godoc.org/github.com/coreos/go-systemd/dbus // DOCS: https://godoc.org/github.com/coreos/go-systemd/dbus
@@ -20,7 +20,6 @@
package resources package resources
import ( import (
"encoding/gob"
"fmt" "fmt"
"log" "log"
@@ -34,7 +33,6 @@ import (
func init() { func init() {
RegisterResource("svc", func() Res { return &SvcRes{} }) RegisterResource("svc", func() Res { return &SvcRes{} })
gob.Register(&SvcRes{})
} }
// SvcRes is a service resource for systemd units. // SvcRes is a service resource for systemd units.
@@ -67,7 +65,6 @@ func (obj *SvcRes) Validate() error {
// Init runs some startup code for this resource. // Init runs some startup code for this resource.
func (obj *SvcRes) Init() error { func (obj *SvcRes) Init() error {
obj.BaseRes.Kind = "svc"
return obj.BaseRes.Init() // call base init, b/c we're overriding return obj.BaseRes.Init() // call base init, b/c we're overriding
} }

View File

@@ -3,22 +3,21 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
import ( import (
"encoding/gob"
"fmt" "fmt"
"log" "log"
"time" "time"
@@ -26,7 +25,6 @@ import (
func init() { func init() {
RegisterResource("timer", func() Res { return &TimerRes{} }) RegisterResource("timer", func() Res { return &TimerRes{} })
gob.Register(&TimerRes{})
} }
// TimerRes is a timer resource for time based events. // TimerRes is a timer resource for time based events.
@@ -59,7 +57,6 @@ func (obj *TimerRes) Validate() error {
// Init runs some startup code for this resource. // Init runs some startup code for this resource.
func (obj *TimerRes) Init() error { func (obj *TimerRes) Init() error {
obj.BaseRes.Kind = "timer"
return obj.BaseRes.Init() // call base init, b/c we're overrriding return obj.BaseRes.Init() // call base init, b/c we're overrriding
} }

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources

451
resources/user.go Normal file
View File

@@ -0,0 +1,451 @@
// Mgmt
// Copyright (C) 2013-2017+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources
import (
"fmt"
"io/ioutil"
"log"
"os/exec"
"os/user"
"sort"
"strconv"
"strings"
"syscall"
"github.com/purpleidea/mgmt/recwatch"
errwrap "github.com/pkg/errors"
)
func init() {
RegisterResource("user", func() Res { return &UserRes{} })
}
const passwdFile = "/etc/passwd"
// UserRes is a user account resource.
type UserRes struct {
BaseRes `yaml:",inline"`
State string `yaml:"state"` // state: exists, absent
UID *uint32 `yaml:"uid"` // uid must be unique unless AllowDuplicateUID is true
GID *uint32 `yaml:"gid"` // gid of the user's primary group
Group *string `yaml:"group"` // name of the user's primary group
Groups []string `yaml:"groups"` // list of supplemental groups
HomeDir *string `yaml:"homedir"` // path to the user's home directory
AllowDuplicateUID bool `yaml:"allowduplicateuid"` // allow duplicate uid
recWatcher *recwatch.RecWatcher
}
// Default returns some sensible defaults for this resource.
func (obj *UserRes) Default() Res {
return &UserRes{
BaseRes: BaseRes{
MetaParams: DefaultMetaParams, // force a default
},
}
}
// Validate if the params passed in are valid data.
func (obj *UserRes) Validate() error {
const whitelist string = "_abcdefghijklmnopqrstuvwxyz0123456789"
if obj.State != "exists" && obj.State != "absent" {
return fmt.Errorf("state must be 'exists' or 'absent'")
}
if obj.GID != nil && obj.Group != nil {
return fmt.Errorf("cannot use both GID and Group")
}
if obj.Group != nil {
if *obj.Group == "" {
return fmt.Errorf("group cannot be empty string")
}
for _, char := range *obj.Group {
if !strings.Contains(whitelist, string(char)) {
return fmt.Errorf("group contains invalid character(s)")
}
}
}
if obj.Groups != nil {
for _, group := range obj.Groups {
if group == "" {
return fmt.Errorf("group cannot be empty string")
}
for _, char := range group {
if !strings.Contains(whitelist, string(char)) {
return fmt.Errorf("groups list contains invalid character(s)")
}
}
}
}
return obj.BaseRes.Validate()
}
// Init initializes the resource.
func (obj *UserRes) Init() error {
return obj.BaseRes.Init() // call base init, b/c we're overriding
}
// Watch is the primary listener for this resource and it outputs events.
func (obj *UserRes) Watch() error {
var err error
obj.recWatcher, err = recwatch.NewRecWatcher(passwdFile, false)
if err != nil {
return err
}
defer obj.recWatcher.Close()
// notify engine that we're running
if err := obj.Running(); err != nil {
return err // bubble up a NACK...
}
var send = false // send event?
var exit *error
for {
if obj.debug {
log.Printf("%s: Watching: %s", obj, passwdFile) // attempting to watch...
}
select {
case event, ok := <-obj.recWatcher.Events():
if !ok { // channel shutdown
return nil
}
if err := event.Error; err != nil {
return errwrap.Wrapf(err, "Unknown %s watcher error", obj)
}
if obj.debug { // don't access event.Body if event.Error isn't nil
log.Printf("%s: Event(%s): %v", obj, event.Body.Name, event.Body.Op)
}
send = true
obj.StateOK(false) // dirty
case event := <-obj.Events():
if exit, send = obj.ReadEvent(event); exit != nil {
return *exit // exit
}
//obj.StateOK(false) // dirty // these events don't invalidate state
}
// do all our event sending all together to avoid duplicate msgs
if send {
send = false
obj.Event()
}
}
}
// CheckApply method for User resource.
func (obj *UserRes) CheckApply(apply bool) (checkOK bool, err error) {
log.Printf("%s: CheckApply(%t)", obj, apply)
var exists = true
usr, err := user.Lookup(obj.GetName())
if err != nil {
if _, ok := err.(user.UnknownUserError); !ok {
return false, errwrap.Wrapf(err, "error looking up user")
}
exists = false
}
if obj.AllowDuplicateUID == false && obj.UID != nil {
existingUID, err := user.LookupId(strconv.Itoa(int(*obj.UID)))
if err != nil {
if _, ok := err.(user.UnknownUserIdError); !ok {
return false, errwrap.Wrapf(err, "error looking up UID")
}
} else if existingUID.Username != obj.GetName() {
return false, fmt.Errorf("the requested UID is already taken")
}
}
if obj.State == "absent" && !exists {
return true, nil
}
if usercheck := true; exists && obj.State == "exists" {
intUID, err := strconv.Atoi(usr.Uid)
if err != nil {
return false, errwrap.Wrapf(err, "error casting UID to int")
}
intGID, err := strconv.Atoi(usr.Gid)
if err != nil {
return false, errwrap.Wrapf(err, "error casting GID to int")
}
if obj.UID != nil && int(*obj.UID) != intUID {
usercheck = false
}
if obj.GID != nil && int(*obj.GID) != intGID {
usercheck = false
}
if obj.HomeDir != nil && *obj.HomeDir != usr.HomeDir {
usercheck = false
}
if usercheck {
return true, nil
}
}
if !apply {
return false, nil
}
var cmdName string
var args []string
if obj.State == "exists" {
if exists {
cmdName = "usermod"
log.Printf("%s: Modifying user: %s", obj, obj.GetName())
} else {
cmdName = "useradd"
log.Printf("%s: Adding user: %s", obj, obj.GetName())
}
if obj.AllowDuplicateUID {
args = append(args, "--non-unique")
}
if obj.UID != nil {
args = append(args, "-u", fmt.Sprintf("%d", *obj.UID))
}
if obj.GID != nil {
args = append(args, "-g", fmt.Sprintf("%d", *obj.GID))
}
if obj.Group != nil {
args = append(args, "-g", *obj.Group)
}
if obj.Groups != nil {
args = append(args, "-G", strings.Join(obj.Groups, ","))
}
if obj.HomeDir != nil {
args = append(args, "-d", *obj.HomeDir)
}
}
if obj.State == "absent" {
cmdName = "userdel"
log.Printf("%s: Deleting user: %s", obj, obj.GetName())
}
args = append(args, obj.GetName())
cmd := exec.Command(cmdName, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
Pgid: 0,
}
// open a pipe to get error messages from os/exec
stderr, err := cmd.StderrPipe()
if err != nil {
return false, errwrap.Wrapf(err, "failed to initialize stderr pipe")
}
// start the command
if err := cmd.Start(); err != nil {
return false, errwrap.Wrapf(err, "cmd failed to start")
}
// capture any error messages
slurp, err := ioutil.ReadAll(stderr)
if err != nil {
return false, errwrap.Wrapf(err, "error slurping error message")
}
// wait until cmd exits and return error message if any
if err := cmd.Wait(); err != nil {
return false, errwrap.Wrapf(err, "%s", slurp)
}
return false, nil
}
// UserUID is the UID struct for UserRes.
type UserUID struct {
BaseUID
name string
}
// UserResAutoEdges holds the state of the auto edge generator.
type UserResAutoEdges struct {
UIDs []ResUID
pointer int
}
// AutoEdges returns edges from the user resource to each group found in
// its definition. The groups can be in any of the three applicable fields
// (GID, Group and Groups.) If the user exists, reversed ensures the edge
// goes from group to user, and if the user is absent the edge goes from
// user to group. This ensures that we don't add users to groups that
// don't exist or delete groups before we delete their members.
func (obj *UserRes) AutoEdges() (AutoEdge, error) {
var result []ResUID
var reversed bool
if obj.State == "exists" {
reversed = true
}
if obj.GID != nil {
result = append(result, &GroupUID{
BaseUID: BaseUID{
Reversed: &reversed,
},
gid: obj.GID,
})
}
if obj.Group != nil {
result = append(result, &GroupUID{
BaseUID: BaseUID{
Reversed: &reversed,
},
name: *obj.Group,
})
}
for _, group := range obj.Groups {
result = append(result, &GroupUID{
BaseUID: BaseUID{
Reversed: &reversed,
},
name: group,
})
}
return &UserResAutoEdges{
UIDs: result,
pointer: 0,
}, nil
}
// Next returns the next automatic edge.
func (obj *UserResAutoEdges) Next() []ResUID {
if len(obj.UIDs) == 0 {
return nil
}
value := obj.UIDs[obj.pointer]
obj.pointer++
return []ResUID{value}
}
// Test gets results of the earlier Next() call, & returns if we should continue.
func (obj *UserResAutoEdges) Test(input []bool) bool {
if len(obj.UIDs) <= obj.pointer {
return false
}
if len(input) != 1 { // in case we get given bad data
log.Fatal("Expecting a single value!")
}
return true // keep going
}
// UIDs includes all params to make a unique identification of this object.
// Most resources only return one, although some resources can return multiple.
func (obj *UserRes) UIDs() []ResUID {
x := &UserUID{
BaseUID: BaseUID{Name: obj.GetName(), Kind: obj.GetKind()},
name: obj.Name,
}
return []ResUID{x}
}
// GroupCmp returns whether two resources can be grouped together or not.
func (obj *UserRes) GroupCmp(r Res) bool {
_, ok := r.(*UserRes)
if !ok {
return false
}
return false
}
// Compare two resources and return if they are equivalent.
func (obj *UserRes) Compare(r Res) bool {
// we can only compare UserRes to others of the same resource kind
res, ok := r.(*UserRes)
if !ok {
return false
}
if !obj.BaseRes.Compare(res) { // call base Compare
return false
}
if obj.Name != res.Name {
return false
}
if obj.State != res.State {
return false
}
if (obj.UID == nil) != (res.UID == nil) {
return false
}
if obj.UID != nil && res.UID != nil {
if *obj.UID != *res.UID {
return false
}
}
if (obj.GID == nil) != (res.GID == nil) {
return false
}
if obj.GID != nil && res.GID != nil {
if *obj.GID != *res.GID {
return false
}
}
if (obj.Groups == nil) != (res.Groups == nil) {
return false
}
if obj.Groups != nil && res.Groups != nil {
if len(obj.Groups) != len(res.Groups) {
return false
}
objGroups := obj.Groups
resGroups := res.Groups
sort.Strings(objGroups)
sort.Strings(resGroups)
for i := range objGroups {
if objGroups[i] != resGroups[i] {
return false
}
}
}
if (obj.HomeDir == nil) != (res.HomeDir == nil) {
return false
}
if obj.HomeDir != nil && res.HomeDir != nil {
if *obj.HomeDir != *obj.HomeDir {
return false
}
}
if obj.AllowDuplicateUID != res.AllowDuplicateUID {
return false
}
return true
}
// UnmarshalYAML is the custom unmarshal handler for this struct.
// It is primarily useful for setting the defaults.
func (obj *UserRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
type rawRes UserRes // indirection to avoid infinite recursion
def := obj.Default() // get the default
res, ok := def.(*UserRes) // put in the right format
if !ok {
return fmt.Errorf("could not convert to UserRes")
}
raw := rawRes(*res) // convert; the defaults go here
if err := unmarshal(&raw); err != nil {
return err
}
*obj = UserRes(raw) // restore from indirection with type conversion!
return nil
}

View File

@@ -3,16 +3,16 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources package resources
@@ -22,10 +22,37 @@ import (
"encoding/base64" "encoding/base64"
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"reflect"
"sort"
"strings"
errwrap "github.com/pkg/errors" errwrap "github.com/pkg/errors"
) )
const (
// StructTag is the key we use in struct field names for key mapping.
StructTag = "lang"
)
// ResourceSlice is a linear list of resources. It can be sorted.
type ResourceSlice []Res
func (rs ResourceSlice) Len() int { return len(rs) }
func (rs ResourceSlice) Swap(i, j int) { rs[i], rs[j] = rs[j], rs[i] }
func (rs ResourceSlice) Less(i, j int) bool { return rs[i].String() < rs[j].String() }
// Sort the list of resources and return a copy without modifying the input.
func Sort(rs []Res) []Res {
resources := []Res{}
for _, r := range rs { // copy
resources = append(resources, r)
}
sort.Sort(ResourceSlice(resources))
return resources
// sort.Sort(ResourceSlice(rs)) // this is wrong, it would modify input!
//return rs
}
// ResToB64 encodes a resource to a base64 encoded string (after serialization). // ResToB64 encodes a resource to a base64 encoded string (after serialization).
func ResToB64(res Res) (string, error) { func ResToB64(res Res) (string, error) {
b := bytes.Buffer{} b := bytes.Buffer{}
@@ -52,8 +79,57 @@ func B64ToRes(str string) (Res, error) {
} }
res, ok := output.(Res) res, ok := output.(Res)
if !ok { if !ok {
return nil, fmt.Errorf("Output %v is not a Res", res) return nil, fmt.Errorf("output `%v` is not a Res", output)
} }
return res, nil return res, nil
} }
// StructTagToFieldName returns a mapping from recommended alias to actual field
// name. It returns an error if it finds a collision. It uses the `lang` tags.
func StructTagToFieldName(res Res) (map[string]string, error) {
// TODO: fallback to looking up yaml tags, although harder to parse
result := make(map[string]string) // `lang` field tag -> field name
st := reflect.TypeOf(res).Elem() // elem for ptr to res
for i := 0; i < st.NumField(); i++ {
field := st.Field(i)
name := field.Name
// TODO: golang 1.7+
// if !ok, then nothing is found
//if alias, ok := field.Tag.Lookup(StructTag); ok { // golang 1.7+
if alias := field.Tag.Get(StructTag); alias != "" { // golang 1.6
if val, exists := result[alias]; exists {
return nil, fmt.Errorf("field `%s` uses the same key `%s` as field `%s`", name, alias, val)
}
// empty string ("") is a valid value
if alias != "" {
result[alias] = name
}
}
}
return result, nil
}
// LowerStructFieldNameToFieldName returns a mapping from the lower case version
// of each field name to the actual field name. It only returns public fields.
// It returns an error if it finds a collision.
func LowerStructFieldNameToFieldName(res Res) (map[string]string, error) {
result := make(map[string]string) // lower field name -> field name
st := reflect.TypeOf(res).Elem() // elem for ptr to res
for i := 0; i < st.NumField(); i++ {
field := st.Field(i)
name := field.Name
if strings.Title(name) != name { // must have been a priv field
continue
}
if alias := strings.ToLower(name); alias != "" {
if val, exists := result[alias]; exists {
return nil, fmt.Errorf("field `%s` uses the same key `%s` as field `%s`", name, alias, val)
}
result[alias] = name
}
}
return result, nil
}

238
resources/util_test.go Normal file
View File

@@ -0,0 +1,238 @@
// Mgmt
// Copyright (C) 2013-2017+ James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources
import (
"bytes"
"encoding/base64"
"encoding/gob"
"reflect"
"testing"
)
func TestSort0(t *testing.T) {
rs := []Res{}
s := Sort(rs)
if !reflect.DeepEqual(s, []Res{}) {
t.Errorf("sort failed!")
if s == nil {
t.Logf("output is nil!")
} else {
str := "Got:"
for _, r := range s {
str += " " + r.String()
}
t.Errorf(str)
}
}
}
func TestSort1(t *testing.T) {
r1, _ := NewNamedResource("noop", "noop1")
r2, _ := NewNamedResource("noop", "noop2")
r3, _ := NewNamedResource("noop", "noop3")
r4, _ := NewNamedResource("noop", "noop4")
r5, _ := NewNamedResource("noop", "noop5")
r6, _ := NewNamedResource("noop", "noop6")
rs := []Res{r3, r2, r6, r1, r5, r4}
s := Sort(rs)
if !reflect.DeepEqual(s, []Res{r1, r2, r3, r4, r5, r6}) {
t.Errorf("sort failed!")
str := "Got:"
for _, r := range s {
str += " " + r.String()
}
t.Errorf(str)
}
if !reflect.DeepEqual(rs, []Res{r3, r2, r6, r1, r5, r4}) {
t.Errorf("sort modified input!")
str := "Got:"
for _, r := range rs {
str += " " + r.String()
}
t.Errorf(str)
}
}
func TestMiscEncodeDecode1(t *testing.T) {
var err error
// encode
var input interface{} = &FileRes{}
b1 := bytes.Buffer{}
e := gob.NewEncoder(&b1)
err = e.Encode(&input) // pass with &
if err != nil {
t.Errorf("Gob failed to Encode: %v", err)
}
str := base64.StdEncoding.EncodeToString(b1.Bytes())
// decode
var output interface{}
bb, err := base64.StdEncoding.DecodeString(str)
if err != nil {
t.Errorf("Base64 failed to Decode: %v", err)
}
b2 := bytes.NewBuffer(bb)
d := gob.NewDecoder(b2)
err = d.Decode(&output) // pass with &
if err != nil {
t.Errorf("Gob failed to Decode: %v", err)
}
res1, ok := input.(Res)
if !ok {
t.Errorf("Input %v is not a Res", res1)
return
}
res2, ok := output.(Res)
if !ok {
t.Errorf("Output %v is not a Res", res2)
return
}
if !res1.Compare(res2) {
t.Error("The input and output Res values do not match!")
}
}
func TestMiscEncodeDecode2(t *testing.T) {
var err error
// encode
input, err := NewNamedResource("file", "file1")
if err != nil {
t.Errorf("Can't create: %v", err)
return
}
b64, err := ResToB64(input)
if err != nil {
t.Errorf("Can't encode: %v", err)
return
}
output, err := B64ToRes(b64)
if err != nil {
t.Errorf("Can't decode: %v", err)
return
}
res1, ok := input.(Res)
if !ok {
t.Errorf("Input %v is not a Res", res1)
return
}
res2, ok := output.(Res)
if !ok {
t.Errorf("Output %v is not a Res", res2)
return
}
if !res1.Compare(res2) {
t.Error("The input and output Res values do not match!")
}
}
func TestStructTagToFieldName0(t *testing.T) {
type TestStruct struct {
// TODO: switch this to TestRes when it is in git master
NoopRes // so that this struct implements `Res`
Alpha bool `lang:"alpha" yaml:"nope"`
Beta string `yaml:"beta"`
Gamma string
Delta int `lang:"surprise"`
}
mapping, err := StructTagToFieldName(&TestStruct{})
if err != nil {
t.Errorf("failed: %+v", err)
return
}
expected := map[string]string{
"alpha": "Alpha",
"surprise": "Delta",
}
if !reflect.DeepEqual(mapping, expected) {
t.Errorf("expected: %+v", expected)
t.Errorf("received: %+v", mapping)
}
}
func TestLowerStructFieldNameToFieldName0(t *testing.T) {
type TestStruct struct {
// TODO: switch this to TestRes when it is in git master
NoopRes // so that this struct implements `Res`
Alpha bool
skipMe bool
Beta string
IAmACamel uint
pass *string
Gamma string
Delta int
}
mapping, err := LowerStructFieldNameToFieldName(&TestStruct{})
if err != nil {
t.Errorf("failed: %+v", err)
return
}
expected := map[string]string{
"noopres": "NoopRes", // TODO: switch this to TestRes when it is in git master
"alpha": "Alpha",
//"skipme": "skipMe",
"beta": "Beta",
"iamacamel": "IAmACamel",
//"pass": "pass",
"gamma": "Gamma",
"delta": "Delta",
}
if !reflect.DeepEqual(mapping, expected) {
t.Errorf("expected: %+v", expected)
t.Errorf("received: %+v", mapping)
}
}
func TestLowerStructFieldNameToFieldName1(t *testing.T) {
type TestStruct struct {
// TODO: switch this to TestRes when it is in git master
NoopRes // so that this struct implements `Res`
Alpha bool
skipMe bool
Beta string
// these two should collide
DoubleWord bool
Doubleword string
IAmACamel uint
pass *string
Gamma string
Delta int
}
mapping, err := LowerStructFieldNameToFieldName(&TestStruct{})
if err == nil {
t.Errorf("expected failure, but passed with: %+v", mapping)
return
}
}

View File

@@ -3,29 +3,30 @@
// Written by James Shubin <james@shubin.ca> and the project contributors // Written by James Shubin <james@shubin.ca> and the project contributors
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// +build !novirt // +build !novirt
package resources package resources
import ( import (
"encoding/gob"
"fmt" "fmt"
"log" "log"
"math/rand" "math/rand"
"net/url" "net/url"
"os/user" "os/user"
"path" "path"
"regexp"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -38,7 +39,6 @@ import (
func init() { func init() {
RegisterResource("virt", func() Res { return &VirtRes{} }) RegisterResource("virt", func() Res { return &VirtRes{} })
gob.Register(&VirtRes{})
} }
const ( const (
@@ -192,7 +192,6 @@ func (obj *VirtRes) Init() error {
} }
} }
obj.wg = &sync.WaitGroup{} obj.wg = &sync.WaitGroup{}
obj.BaseRes.Kind = "virt"
return obj.BaseRes.Init() // call base init, b/c we're overriding return obj.BaseRes.Init() // call base init, b/c we're overriding
} }
@@ -1000,7 +999,7 @@ func (d *diskDevice) GetXML(idx int) string {
b += "<disk type='file' device='disk'>" b += "<disk type='file' device='disk'>"
b += fmt.Sprintf("<driver name='qemu' type='%s'/>", d.Type) b += fmt.Sprintf("<driver name='qemu' type='%s'/>", d.Type)
b += fmt.Sprintf("<source file='%s'/>", source) b += fmt.Sprintf("<source file='%s'/>", source)
b += fmt.Sprintf("<target dev='vd%s' bus='virtio'/>", (string)(idx+97)) // TODO: 26, 27... should be 'aa', 'ab'... b += fmt.Sprintf("<target dev='vd%s' bus='virtio'/>", numToAlpha(idx))
b += "</disk>" b += "</disk>"
return b return b
} }
@@ -1011,7 +1010,7 @@ func (d *cdRomDevice) GetXML(idx int) string {
b += "<disk type='file' device='cdrom'>" b += "<disk type='file' device='cdrom'>"
b += fmt.Sprintf("<driver name='qemu' type='%s'/>", d.Type) b += fmt.Sprintf("<driver name='qemu' type='%s'/>", d.Type)
b += fmt.Sprintf("<source file='%s'/>", source) b += fmt.Sprintf("<source file='%s'/>", source)
b += fmt.Sprintf("<target dev='hd%s' bus='ide'/>", (string)(idx+97)) // TODO: 26, 27... should be 'aa', 'ab'... b += fmt.Sprintf("<target dev='hd%s' bus='ide'/>", numToAlpha(idx))
b += "<readonly/>" b += "<readonly/>"
b += "</disk>" b += "</disk>"
return b return b
@@ -1171,15 +1170,38 @@ func isNotFound(err error) bool {
return false // some other error return false // some other error
} }
// expandHome does a simple expansion of the tilde into your $HOME value. // expandHome does an expansion of ~/ or ~james/ into user's home dir value.
func expandHome(p string) (string, error) { func expandHome(p string) (string, error) {
// TODO: this doesn't match strings of the form: ~james/... if strings.HasPrefix(p, "~/") {
if !strings.HasPrefix(p, "~/") {
return p, nil
}
usr, err := user.Current() usr, err := user.Current()
if err != nil { if err != nil {
return p, fmt.Errorf("can't expand ~ into home directory") return p, fmt.Errorf("can't expand ~ into home directory")
} }
return path.Join(usr.HomeDir, p[len("~/"):]), nil return path.Join(usr.HomeDir, p[len("~/"):]), nil
} }
// check if provided path is in format ~username and keep track of provided username
r, err := regexp.Compile("~([^/]+)/")
if err != nil {
return p, errwrap.Wrapf(err, "can't compile regexp")
}
if match := r.FindStringSubmatch(p); match != nil {
username := match[len(match)-1]
usr, err := user.Lookup(username)
if err != nil {
return p, fmt.Errorf("can't expand %s into home directory", match[0])
}
return path.Join(usr.HomeDir, p[len(match[0]):]), nil
}
return p, nil
}
func numToAlpha(idx int) string {
var mod = idx % 26
var div = idx / 26
if div > 0 {
return numToAlpha(div-1) + string(rune(mod+int('a')))
}
return string(rune(mod + int('a')))
}

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