Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9907c12eda | ||
|
|
19533a32b5 | ||
|
|
c5a5004f9e | ||
|
|
677cdea99d | ||
|
|
4d7c0ddbce | ||
|
|
81daf10157 | ||
|
|
b3ef4e41bf | ||
|
|
9fbf149717 | ||
|
|
95cb94a039 | ||
|
|
21f7f87716 | ||
|
|
831c7e2c32 | ||
|
|
cc0d04c8b7 | ||
|
|
46be83f8f7 | ||
|
|
28560e2045 | ||
|
|
0df4824a56 | ||
|
|
dbcabc6517 | ||
|
|
69f479b67e | ||
|
|
af75696018 | ||
|
|
80b8f8740f | ||
|
|
71ab325940 | ||
|
|
653c76709a | ||
|
|
83cc1bab38 | ||
|
|
6c8588c019 | ||
|
|
5b00ed2fb2 | ||
|
|
9f66962bfb | ||
|
|
0edba74091 | ||
|
|
1003b49dd9 | ||
|
|
884ba54f96 | ||
|
|
cf2325a2da | ||
|
|
db6972638d | ||
|
|
74e04e81d5 | ||
|
|
7c5d7365c7 | ||
|
|
0dadf3d78a | ||
|
|
e341256627 | ||
|
|
5a3bd3ca67 | ||
|
|
8102e0a468 | ||
|
|
7d55179727 | ||
|
|
bc1a1d1818 | ||
|
|
a8bbb22fe8 | ||
|
|
6b489f71a1 | ||
|
|
f1db088af4 | ||
|
|
6fe12b3fb5 | ||
|
|
dacbf9b68d | ||
|
|
9f5057eac7 | ||
|
|
525cd54921 | ||
|
|
7ac94bbf5f | ||
|
|
b8ff6938df | ||
|
|
2f6c77fba2 |
36
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
36
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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
1
.gitignore
vendored
@@ -2,6 +2,7 @@
|
|||||||
.omv/
|
.omv/
|
||||||
.ssh/
|
.ssh/
|
||||||
.vagrant/
|
.vagrant/
|
||||||
|
.envrc
|
||||||
old/
|
old/
|
||||||
tmp/
|
tmp/
|
||||||
*_stringer.go
|
*_stringer.go
|
||||||
|
|||||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -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
|
||||||
|
|||||||
@@ -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
141
COPYING
@@ -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>.
|
||||||
|
|||||||
@@ -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/>.
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -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
|
||||||
|
|||||||
@@ -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
5
Vagrantfile
vendored
@@ -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"
|
||||||
|
|||||||
@@ -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
6
doc.go
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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{} })
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
11
etcd/etcd.go
11
etcd/etcd.go
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
19
examples/autoedges4.yaml
Normal 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
10
examples/aws_ec2_1.yaml
Normal 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
14
examples/graph0.hcl
Normal 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
4
examples/graph1.hcl
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
resource "exec" "exec1" {
|
||||||
|
cmd = "cat /tmp/mgmt-hello-world"
|
||||||
|
state = "present"
|
||||||
|
}
|
||||||
8
examples/group1.yaml
Normal file
8
examples/group1.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
graph: mygraph
|
||||||
|
resources:
|
||||||
|
group:
|
||||||
|
- name: testgroup
|
||||||
|
state: exists
|
||||||
|
gid: 10000
|
||||||
|
edges: []
|
||||||
9
examples/hil.hcl
Normal file
9
examples/hil.hcl
Normal 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"
|
||||||
|
}
|
||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
graph: mygraph
|
graph: mygraph
|
||||||
resources:
|
resources:
|
||||||
nspawn:
|
nspawn:
|
||||||
- name: mgmt-nspawn1
|
- name: nspawn1
|
||||||
state: running
|
state: running
|
||||||
edges: []
|
edges: []
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
graph: mygraph
|
|
||||||
resources:
|
|
||||||
nspawn:
|
|
||||||
- name: mgmt-nspawn2
|
|
||||||
state: stopped
|
|
||||||
edges: []
|
|
||||||
9
examples/user1.yaml
Normal file
9
examples/user1.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
graph: mygraph
|
||||||
|
resources:
|
||||||
|
user:
|
||||||
|
- name: testuser
|
||||||
|
uid: 1002
|
||||||
|
gid: 100
|
||||||
|
state: exists
|
||||||
|
edges: []
|
||||||
@@ -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
155
hcl/gapi.go
Normal 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
387
hcl/parse.go
Normal 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(¶ms, 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
89
hil/interpolate.go
Normal 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
|
||||||
|
}
|
||||||
20
lib/cli.go
20
lib/cli.go
@@ -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: "",
|
||||||
|
|||||||
28
lib/main.go
28
lib/main.go
@@ -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
|
||||||
|
|||||||
6
main.go
6
main.go
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
92
pgraph/graphsync_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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] {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
93
pgraph/util_test.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
653
resources/aws_ec2.go
Normal 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()
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
314
resources/group.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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?
|
||||||
|
|||||||
@@ -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
54
resources/msg_test.go
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
451
resources/user.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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
238
resources/util_test.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
Reference in New Issue
Block a user