426 Commits

Author SHA1 Message Date
James Shubin
4d9c78003a lang: core: embedded: Add standalone provisioning tool
This commit adds the ability to build a standalone provisioning tool.
This is the first useful public mcl code base as well. It is not
perfect, but does serve as a rough starting point to show what is
possible. In the future as the language and the engine evolve, this will
likely get more elegant, and also grow new features.

To build this, run `make clean && GOTAGS='embedded_provisioner' make`.
To run this, run `mgmt provisioner`.
2024-03-26 20:33:43 -04:00
James Shubin
375fe19f52 make: Build some extravagant release hacks
This should make a maintainers life easier. (I hope!) The .release files
contain the "magic name" for the respective release type. If they start
with a # then they are skipped. You shouldn't need to keep bumping the
distro versions in the Makefile anymore.
2024-03-26 20:23:06 -04:00
James Shubin
1895c63e89 make: Clarify how mkdir runs 2024-03-26 18:40:44 -04:00
James Shubin
33a00436b1 make: Selectively include files if they exist
Make our Makefile fancier...
2024-03-26 17:56:35 -04:00
James Shubin
8ad232d96a make: Update fedora release versions
These were untested.
2024-03-26 17:07:39 -04:00
James Shubin
01b7721b13 make: Add release targets for standalone binary builds
Many people might just want to download a single binary.
2024-03-26 16:57:06 -04:00
James Shubin
46b2fe0eba docs: Add old release notes
These release notes used to live on the mailing list at:
https://listman.redhat.com/archives/mgmtconfig-list/ until Red Hat
killed off this excellent service recently.

I'm adding them all here for reference.

Before 0.0.9 there were no release notes.
2024-03-26 14:38:26 -04:00
James Shubin
89784e86bd lang: gapi, unification: Shutdown unification quickly when asked
This is a bit of a hack until we improve the GAPI a bit, but will let us
shut down type unification a bit faster if we want to interrupt a long
running operation. Hopefully our future algorthmic performance
improvements will obliviate the need for this to be a common issue.
2024-03-22 03:23:46 -04:00
James Shubin
c4b14ac40d lang: download, cli: Move to new git version
Apparently there were some security issues with the old one.
2024-03-22 02:56:59 -04:00
James Shubin
12b519a543 lang: gapi: Improve logging to be cleaner 2024-03-22 02:56:59 -04:00
James Shubin
f75f6623b3 util: recwatch: Improve godoc 2024-03-22 02:56:59 -04:00
James Shubin
43b5a0ea6a util: recwatch: Remove last use of core logger
We've cleaned up quite a bit, yay!
2024-03-22 02:56:59 -04:00
James Shubin
3541954df8 lib, pgp: Remove direct logger usage
This cleans things up a little bit more.
2024-03-22 02:56:59 -04:00
James Shubin
e0d6d35b56 entry: Execute embedded commands if you use their correct name
I think this is the more intuitive way to do things. Force the user the
explicitly choose the embedded command which prevents accidental running
of a different command, and allows us to potentially have multiple
commands in the future.
2024-03-22 01:36:04 -04:00
James Shubin
8cf7719476 cli: Refactor the logger setup 2024-03-22 01:36:04 -04:00
James Shubin
d2188609e4 entry: Add a new API for log related things
This needs improvement to eventually let the embedded application
disable logs from the main execution, or at least filter them through
itself to decide what to keep. That's a future complex project though.
2024-03-22 01:13:21 -04:00
James Shubin
3e180eafb4 cli: Refactor out log to the top level
Remove some cruft at the same time.
2024-03-22 00:55:03 -04:00
James Shubin
b8a3c39984 util: password: Add a new helper util function for passwords
Also fix some doc issues from an earlier copy paste.
2024-03-22 00:55:03 -04:00
James Shubin
946468dc99 lang: ast: Ensure a list doesn't sneak through type interpolation
If we had a single list wrapped in an interpolated string, it could
sneak through type unification, which is not correct. Wrapping a
variable by interpolation in a string, must force it to be a string.
2024-03-20 18:36:47 -04:00
James Shubin
340a832884 lang: ast: Optimize unification invariants for common case
This optimizes the list of type unification invariants that we generate
for the common case where a resource or edge name is known statically.

For one code base this halved the type unification time in half. More
work can be done though!
2024-03-20 18:07:59 -04:00
James Shubin
388d08e245 engine: resources: Add a dhcp range resource
This adds the ability to offer a dhcp lease to someone when we don't
know their mac address in advance.

This also uses the extended autogrouping API to keep the internal API
simpler.
2024-03-20 17:45:06 -04:00
James Shubin
8a78907977 engine: autogroup, traits, graph: Extend autogroup API
This extends the autogrouping API so that a child can easily get a
reference to the parent that it is autogrouped in. This can simplify the
API for some resources when it makes sense to allow them access to the
parent handle. Use sparingly and intelligently!
2024-03-20 17:45:06 -04:00
James Shubin
6347e275d3 util: Add a fedora mirror lookup function 2024-03-18 15:38:57 -04:00
James Shubin
c8f19e0d96 mod: Ran a combination of go get, go mod tidy, and go-mod-upgrade
These tools are all junk. Maybe one day I can hire someone to fix things
the right way.
2024-03-18 15:37:22 -04:00
James Shubin
ba6d816186 lang: unification: Clean up the solver plumbing
This refactors the solver into a separate struct that can be extended as
needed.
2024-03-16 01:38:33 -04:00
James Shubin
849de648f0 engine: resources: Add a creates field for exec
This adds a standard gate that prevents execution if a file exists. Of
note, this also adds a watch on it, so we can have a proper watched exec
resource without a watch cmd.
2024-03-16 01:21:32 -04:00
James Shubin
4d18044851 util: Move recwatch package to util directory
This makes more sense and this is also a reminded that it needs a bit of
a cleanup.
2024-03-16 01:21:32 -04:00
James Shubin
d6a6734b65 lang: Plumb the ctx deeper into the lang API 2024-03-16 01:21:32 -04:00
James Shubin
10319dd641 lang: Plumb through a context into unification
If we have a long type unification, we might want to cancel it early.
This also helps us visualize where we want context to be seen.
2024-03-16 01:21:32 -04:00
James Shubin
a8b945e36e lang: types, core: Skip over invalid functions
It's not clear how best to support complex functions with struct fields
in templates at the moment. Skip these for now.
2024-03-16 01:21:32 -04:00
James Shubin
719c56e754 lang: core: convert: Add a function to convert an int to a str
Faster than using printf.
2024-03-16 01:21:32 -04:00
James Shubin
cbf10bdb44 lang: core: os: Add a simple distro uid parsing function
I had previous good success using this kind of pattern for discussing
distro UID's. Let's put this in as a reminded to see if it's worth
pursuing longer term.
2024-03-16 01:21:32 -04:00
James Shubin
ee60c428b2 engine: resources: Clarify log message
This way you don't have to remember which way it works.
2024-03-16 01:21:32 -04:00
James Shubin
a410b4981f lang: Add timing information to lang execution
This makes performance optimizing a little easier.
2024-03-16 01:21:32 -04:00
James Shubin
f973009b83 lang: Rephrase the scope building log messages 2024-03-16 00:36:06 -04:00
James Shubin
e0ace35525 engine: resources: The http proxy should support streaming files
This adds basic support for streaming files directly from the download
server. This avoids clients timing out if they are blocked while first
waiting for a giant file to download.
2024-03-16 00:36:06 -04:00
James Shubin
cf49d9f784 engine: resources: Improve proxy conventions
The earlier path mangling code was incorrect. I've taken more time to
understand the correct use case and I've improved it. I've also split
out the parser logic and added tests, so this should either stay stable
or grow new tests and fixes if we find new issues.
2024-03-16 00:36:06 -04:00
James Shubin
79d5873445 engine: resources: Also look at stderr
We might have an executing program which prints on stderr which we were
effectively ignoring. Read from that too.
2024-03-16 00:36:06 -04:00
James Shubin
c5e3e0ee70 engine: resources: Add firewalld resource
This is a simple firewalld resource to make the seamless opening of
firewall ports in standalone laptop (and other) environments easy.
2024-03-16 00:36:06 -04:00
James Shubin
6976f5f3f0 util: Add simple distro release version function
This helps us determine the latest version automatically as a user
convenience.
2024-03-08 17:57:13 -05:00
James Shubin
1f37ac83e4 lang: types: Fix capitalization 2024-03-08 17:57:13 -05:00
James Shubin
36ebddf986 util: Add a terrible heuristic for getting ethernet devices
I don't know of a better way. This will have to do for now.
2024-03-08 17:57:13 -05:00
James Shubin
b496f8d70a util: password: Add a helper to generate salted, hashed passwords
I have no idea if I'm doing this correctly, and I have no idea if the
library I am using is doing this correctly. Please check!
2024-03-08 17:57:13 -05:00
James Shubin
9351eee3f1 lang: funcs: simple: Add a function from struct generator
This is a helper function that can generate a bunch of functions from a
struct type. This is most useful when using a CLI args struct for
command line parsing and then storing the values as functions.

An alternative version of this might choose to return all of the values
as a single giant struct.
2024-03-08 17:57:13 -05:00
James Shubin
d412d6502d lang: gapi: Fix a very rare nil pointer issue
This only showed up while debugging a bug I caused, but technically if
we fail in Init() then we could hit this nil pointer condition.
2024-03-08 17:57:13 -05:00
James Shubin
b85f81d529 lang: types: Add simple parsing of net.HardwareAddr
If we want to use special struct types from our CLI parser, we also need
to be able to both identify, and convert them to our language type and
value representations.

For as long as we don't have fancier types in our language, these should
both be strings. Tests and extensions to these additions are welcome!
2024-03-08 17:57:13 -05:00
James Shubin
4140492d56 lang: ast: Embedded imports have function scopes too
If we're importing an embedded module, we need to also include any
possible system scope imports (like functions) that might be using the
same namespace. These might be used by a custom CLI frontend to extend
the code with values from the CLI parsing, for example.
2024-03-08 17:41:57 -05:00
James Shubin
59e133d3bc make: Find on all mcl files
We forgot to port this line when we moved the core package up one
directory.
2024-03-08 17:41:57 -05:00
James Shubin
90f6d4e563 legal: Update http to https 2024-03-05 01:05:50 -05:00
James Shubin
3e31ee9455 legal: Additional permission under GNU GPL version 3 section 7
With the recent merging of embedded package imports and the entry CLI
package, it is now possible for users to build in mcl code into a single
binary. This additional permission makes it explicitly clear that this
is permitted to make it easier for those users. The condition is phrased
so that the terms can be "patched" by the original author if it's
necessary for the project. For example, if the name of the language
(mcl) changes, has a differently named new version, someone finds a
phrasing improvement or a legal loophole, or for some other
reasonable circumstance. Now go write some beautiful embedded tools!
2024-03-05 01:04:09 -05:00
James Shubin
d52c90ede4 cli, entry, lang: Add an entry package for embeddable CLI's
This adds a new entry package that allows embedded programs to exist
inside of mgmt. This took a lot of refactoring to get the API right, but
I think it's incredibly elegant now. There is a chance we tweak things a
bit, but it's a good first start. All-in-one programs are coming soon!
2024-03-05 01:03:54 -05:00
James Shubin
9527d0dcbd lib, cli: Move cli struct tags and embed this datastructure
This moves over the cli `arg` struct tags which are used to generate and
parse things on the command line. Furthermore, we then embed this data
directly in our more general parser struct so that we avoid duplication.
Finally, since the data shares a common struct type, we don't need to do
the manual field-by-field copying to pull things in!
2024-03-03 20:07:04 -05:00
James Shubin
51d21b8dab lib: Split out all of our config data as a separate struct
Hopefully this will make things more reusable.
2024-03-03 19:18:32 -05:00
James Shubin
601fcf40c4 cli, gapi: empty, lang, yaml: Refactor the args structs
Put these datastructures into an external package so they can be re-used
for parsing elsewhere.

Since we remove these dependencies, we need to manually import the
GAPI's so that they register. Despite efforts to embed them deeper into
the import tree without cycles, this failed. Logically what this told me
is that it actually makes sense to allow a different binary with only
one of the multiple GAPI's contained within.
2024-03-03 17:17:21 -05:00
James Shubin
d537c3d523 cli: Lookup subcommand names dynamically
This lets us get the chosen subcommands dynamically and removes the
eventual need for three package imports.
2024-03-03 15:50:54 -05:00
James Shubin
a65c87b584 gapi: Add a new Names API
This gets the list more directly. We could also just lookup a key in the
big map to get the same effect, but this is more logical for now. This
is useful for removing the importing of three packages.
2024-03-03 15:50:54 -05:00
James Shubin
589a5f9aeb cli, lib, lang: Port to new cli library
The new version of the urfave/cli library is moving to generics, and
it's completely unclear to me why this is an improvement. Their new API
is very complicated to understand, which for me, defeats the purpose of
golang.

In parallel, I needed to do some upcoming cli API refactoring, so this
was a good time to look into new libraries. After a review of the
landscape, I found the alexflint/go-arg library which has a delightfully
elegant API. It does have a few rough edges, but it's otherwise very
usable, and I think it would be straightforward to add features and fix
issues.

Thanks Alex!
2024-03-01 21:02:55 -05:00
James Shubin
e767655ede cli, puppet, langpuppet: Remove puppet GAPI's
I'm currently refactoring the CLI code. Unfortunately this means a
pretty big churn in the various GAPI frontends. Since nobody is actively
using the puppet frontend code, I'm removing it for now. If someone is
actively using it, and wants to either port it to the new API, or
sponsor the porting of it to the new API, I'm happy to allow it back in.

Sorry Felix, it was a fun idea, and I loved seeing it work, but I can't
personally afford the maintenance cost of having this in right now.
2024-03-01 21:02:55 -05:00
James Shubin
62295e370c test: shell: Disable flaky test
Not sure what's up, but we should investigate eventually.
2024-03-01 21:02:55 -05:00
James Shubin
f818f5ccf5 util: errwrap: Add more tests 2024-02-28 16:37:28 -05:00
James Shubin
f28d22d20f cli: Typo fix 2024-02-28 16:36:49 -05:00
James Shubin
80af171a35 cli: Further refactor our input cli data 2024-02-28 16:36:49 -05:00
James Shubin
71c54ab212 cli: Refactor even more code out of cli package
Quite honestly, I'm trying to clean things up, and removing anything
non-consequential will help!
2024-02-28 16:36:49 -05:00
James Shubin
c37ff3efce lang, test: Fix capitalization for consistency 2024-02-28 16:36:49 -05:00
James Shubin
dd0e67540f all: Remove deprecated io/ioutil package
Porting everything to the newer imports was trivial except for one
instance which required a very small refactor.
2024-02-28 16:36:49 -05:00
James Shubin
8db41e7701 cli: Rename confusing obj variable name 2024-02-28 16:36:49 -05:00
James Shubin
37569aae17 lang: core: Add package documentation string 2024-02-28 16:36:49 -05:00
James Shubin
ffd6385dd5 lang: inputs: Add visualization of parsed input struct
Occasionally helps with debugging.
2024-02-28 16:36:49 -05:00
James Shubin
abe3e0e7a5 lib: Split off the CLI portions into a separate package
This cleans things up a bit more and forces the lib package to not
contain any accidental CLI parsing code.
2024-02-28 16:36:49 -05:00
James Shubin
70b5ed7067 lang: Add an embedded package for embedded imports
This adds a new "embedded" package which can be used to import
system-like packages that are embedded into the binary.
2024-02-28 16:36:49 -05:00
James Shubin
9d8beb85d7 lang: ast: Refactor import scope for sanity
This makes it easier to integrate future additions to the import code.
2024-02-28 16:36:49 -05:00
James Shubin
296fc484ba test: shell: Disable flaky test
Legacy yaml anyways...
2024-02-22 17:41:13 -05:00
James Shubin
ad900fc8f1 etcd: Store the scheme as a constant 2024-02-22 17:41:13 -05:00
James Shubin
f60c25aacf util: Add afero relpath implementation
This new filesystem implements a relative filesystem modifier which can
be useful for converting between absolute filesystems rooted at / and
relative ones. This is particularly useful when interfacing with the
golang embed package.

The upstream requires a CLA, so we'll just store this here instead.
https://github.com/spf13/afero/pull/417
2024-02-22 17:41:13 -05:00
James Shubin
d6cf595899 lang: Unnested the core package from the functions dir
The core package could contain non-functions, so we might as well move
it upwards.
2024-02-22 17:19:02 -05:00
James Shubin
9329ed1e37 lang: ast: Add a new flavour and signature for scope importing
We'll need this more flexible version for the future.
2024-02-22 13:46:27 -05:00
James Shubin
90628dc5c1 engine: Improve docs
Things were messy here, give this a small cleanup.
2024-02-22 13:46:27 -05:00
James Shubin
73ae197d20 util: Add scheme and path support to our fs util struct
This lets us have a custom URI when wrapping an Afero.Fs interface.
2024-02-22 13:46:27 -05:00
James Shubin
15fa6b82a5 lang: funcs: core: Formatting cleanup 2024-02-22 13:36:46 -05:00
James Shubin
d887e7fea5 util: More error handling 2024-02-22 13:36:46 -05:00
James Shubin
871f0e73c0 lang, lib, util: Rename fs to be more unique
Trying to do a big refactor and this will help.
2024-02-22 13:36:46 -05:00
James Shubin
d117cb8ed5 util: Improve tree printing function
This makes it behave more like the core GNU tree util.
2024-02-22 13:36:46 -05:00
James Shubin
733d7fb55f lang: ast: Clean up code slightly 2024-02-22 13:15:39 -05:00
James Shubin
6ae3481ae9 engine, lang, gapi: Split out some functions to a writeable API
Start breaking down the filesystem interface to make things more
flexible.
2024-02-22 13:15:39 -05:00
James Shubin
b7efd94147 lang: Pass through the fs and be consistent in usage
This simplifies the API by passing through the filesystem so that
function signatures don't need to be as complicated, and furthermore use
that consistently throughout.
2024-02-22 13:15:39 -05:00
James Shubin
a05b6a927e lang: funcs: core: os: Add an args function to read argv 2024-02-22 13:15:39 -05:00
James Shubin
30648a7858 lib: Print out timing from core graph generation
This should help us determine what steps need improving. As it turns
out, autoedges are approximately the slowest. (Highly dependent on the
specific mcl code being used.)

The trend is clear though; note the units:

main: new graph took: 913.653µs
main: auto edges took: 9.273807153s
main: auto grouping took: 28.690819ms
main: send/recv building took: 566ns

main: new graph took: 779.255µs
main: auto edges took: 4.03670168s
main: auto grouping took: 37.682101ms
main: send/recv building took: 121.017µs

main: new graph took: 1.157479ms
main: auto edges took: 3.794132165s
main: auto grouping took: 49.732836ms
main: send/recv building took: 95.921µs

main: new graph took: 900.937µs
main: auto edges took: 7.206085s
main: auto grouping took: 25.508671ms
main: send/recv building took: 489ns

main: new graph took: 794.224µs
main: auto edges took: 4.313729756s
main: auto grouping took: 47.970533ms
main: send/recv building took: 207.62µs

main: new graph took: 884.49µs
main: auto edges took: 7.585529786s
main: auto grouping took: 24.327938ms
main: send/recv building took: 72.741µs

main: new graph took: 774.157µs
main: auto edges took: 2.827380129s
main: auto grouping took: 28.303023ms
main: send/recv building took: 85.246µs

main: new graph took: 746.841µs
main: auto edges took: 2.775868117s
main: auto grouping took: 33.11291ms
main: send/recv building took: 104.875µs

main: new graph took: 796.445µs
main: auto edges took: 2.71556122s
main: auto grouping took: 24.03827ms
main: send/recv building took: 106.414µs

main: new graph took: 1.217452ms
main: auto edges took: 2.908416104s
main: auto grouping took: 61.175916ms
main: send/recv building took: 92.328µs

main: new graph took: 807.894µs
main: auto edges took: 3.222089261s
main: auto grouping took: 40.032629ms
main: send/recv building took: 106.49µs

main: new graph took: 986.963µs
main: auto edges took: 3.538425263s
main: auto grouping took: 30.660849ms
main: send/recv building took: 99.74µs
2024-02-22 13:15:39 -05:00
James Shubin
26bce5ddc0 engine: resources: Add a new http:proxy resource
This adds a new caching http proxy resource that can be autogrouped into
the core http:server resource, and which caches and serves files that it
received from a different http server. It first pulls them down when
they are initially requested, which makes it possible for us to use this
for provisioning a Linux installation without having to pre-rsync the
entire package repository.
2024-02-22 13:15:39 -05:00
James Shubin
bd708159a1 engine: resources: Close files after use
Don't need to wait for gc.
2024-02-22 13:15:39 -05:00
James Shubin
87ce637bd2 engine: resources: Add a helper function for creating errors 2024-02-22 13:15:39 -05:00
James Shubin
7698b1b5fd engine: resources: Add an http error wrapper
This makes it easier to return errors more succinctly.
2024-02-16 08:06:20 -05:00
James Shubin
e256d886e0 engine: resources: Add a fancy http error wrapper
This lets use use a regular error signature but also specify which http
error code we want to return.
2024-02-16 08:06:20 -05:00
James Shubin
6f268e3a40 engine: resources: Fix up docstring
This was a copy+pasta mistake.
2024-02-16 08:06:20 -05:00
James Shubin
57910470a9 docs: Add new talks from Belgium 2024 2024-02-09 10:49:35 -05:00
James Shubin
890b6e9a28 docs: Add empty list declarations to style guide
This seems to have come up lately, so add my reasoning. Debate is
welcome-- to a point.
2024-02-09 04:48:16 -05:00
Eng Zer Jun
b09b21e939 lang: ast: Remove redundant nil check
From the Go specification [1]:

  "1. ... For a nil slice, the number of iterations is 0."

Therefore, an additional nil check for around the loop is unnecessary.

[1]: https://go.dev/ref/spec#For_range

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2024-02-09 04:31:00 +08:00
James Shubin
edf47a1737 lang: gapi, docs: Add a --skip-unify option to speed up run
This is useful for run and dangerous for deploy. If we make type
unification blazing fast, we should probably get rid of this option.
2024-02-08 10:49:22 -05:00
James Shubin
5b8a1ce821 readme: Update links 2024-02-02 11:12:49 -05:00
Oliver Lowe
d47869ac2f lang: types: Fail build if stringer run from GOROOT
If stringer is run from GOROOT, it can't find any packages; same error
as in https://go.dev/issue/31843. And since GOCACHE is always ignored
(see issue above) we can have a bare go generate command.
2024-02-01 19:49:01 -05:00
James Shubin
8ea7d4cf84 engine: graph, util: Restore send/recv behaviour
A regression in 4b0cdf9123 caused the
basic send/recv functionality to break for simple scenarios. This was
due to inadequate testing, and a partial misunderstanding of the
situation.

New testing should hopefully catch more cases, but send/recv and
compile-time checks are still not as complete as is probably possible.
2024-01-31 22:59:58 -05:00
James Shubin
fc49888ba2 examples: lang: Add a simpler map-iterator example 2024-01-31 16:02:47 -05:00
James Shubin
d0d6a4d1a1 engine: resources: Log some tftp errors
This was helpful in debugging.
2024-01-31 13:09:58 -05:00
James Shubin
961370c43c engine: resources: Log dhcp mac addresses
Useful for identifying who is trying to connect when they're not in the
system yet.
2024-01-31 13:09:58 -05:00
James Shubin
902f4c957a lang: Print stats for debugging if function engine takes too long
If we don't startup fast enough, print some debugging information. We
should eventually change this to print the list of functions that aren't
started yet, and also to give each function entry a better String method
so that we have a better idea of what everything is.
2024-01-28 23:16:25 -05:00
James Shubin
7e1a4dea6c engine: resources: The http:file resource should allow directories
This expands the use of the http:file resource to allow it to be used as
a directory root.
2024-01-28 23:16:25 -05:00
James Shubin
c78ef29bda util: safepath: Add AbsPath and RelPath
These add some new types for when you don't know if something is a file
or a directory, but you do know if it's absolute or relative.
2024-01-28 23:16:20 -05:00
James Shubin
305a4ab6dd examples: lang: Move to v3 of tftp client 2024-01-28 15:55:24 -05:00
James Shubin
7777107d83 lang: funcs, interfaces: Add the Graph method
The Txn wants to be able to call graph directly.
2024-01-24 20:49:17 -05:00
James Shubin
67c72a0e86 engine: resources: Add an http:flag resource
This adds a new http:flag resource which can autogroup into an
http:server resource to receive actions from client HTTP requests, and
forward these values on to other resources.
2024-01-22 20:37:53 -05:00
James Shubin
e7a89a4a42 engine: Resource Cmp should be relaxed
When graph swapping (which is quite common) we only use the newly-made
resource if the Cmp function between the two shows a difference. If the
old resource has previously received a value via send/recv, then when it
is compared to the new value, it will almost always be different. As a
result, we need to run send/recv on the newly made graph to make sure it
has up-to-date values before we compare. This has to happen after
autogrouping since the resources can often be autogrouped and any child
grouped resource will cause a remake of all of the other children and
parents.

It turns out that the actual send/recv properties were being compared as
well, and for unknown reasons (tunnel vision perhaps) they are often not
identical. Skip comparing these for now until we find a fix or
understanding of how to make them identical.
2024-01-22 20:37:53 -05:00
James Shubin
6761984f2c lib: Refresh new graph with received values from previous graph
This pulls in the Send/Recv values from the previous graph so that our
Cmp functions are more likely to not remake resources that should
otherwise not have changed. Unnecessary remakes can destroy the private
state of a resource which can make certain operations impossible.
2024-01-22 20:37:53 -05:00
James Shubin
1630eafbe3 engine: graph: Allow Recv overriding for Send/Recv
This allows us to pass in an alternate implementation for Recv, if we
want to temporarily use a different data source.
2024-01-22 20:37:53 -05:00
James Shubin
091bf3a64c engine: graph: Add a function for printing Send/Recv logs 2024-01-22 20:37:53 -05:00
James Shubin
e5a189b8c6 engine: graph: Split SendRecv off from the engine
It can be used in more places if it's not tied to the engine struct.
This also changes the signature so that more information is returned.
This can be used for logging or other useful things. Of note, this
happens to be the same struct as already exists. It's used for
convenience since it happens to match up! Of course they're related.
2024-01-22 20:37:53 -05:00
James Shubin
f68b34a485 engine: Add a mapper function for comparing two res graphs
This gives us a simple mapping between a new resource and an old one. We
compare by kind and name because those two values are our uniqueness
constraint in the resource graphs.
2024-01-22 20:37:53 -05:00
James Shubin
e946b39960 engine: graph: Set closed flag when resume signal closes
We had this backwards. Woops...
2024-01-22 20:37:53 -05:00
James Shubin
0711d05232 engine: resources: http: Improve groupability of resources
This improves groupability of http resources so that we can have fancier
kinds!
2024-01-22 20:37:53 -05:00
James Shubin
9dd5dfdde2 engine: graph: autogroup: Improve the autogrouping algorithm
This improves the autogrouping algorithm to support hierarchical
autogrouping. It's not guaranteed to work if we replace the reachability
grouper with something more efficient, but it's good enough for now.
2024-01-22 20:37:53 -05:00
James Shubin
024aa60209 lang: parser: Allow edges of resources with colons
Due to a small copy-pasta bug in the parser, resource edges with kinds
containing colons didn't work. This fixes the mistake and adds a test.
2024-01-22 19:40:15 -05:00
James Shubin
32916f9a6f engine: resources: Receive keys should match mcl, not golang
The capitalization of these keys was wrong and they weren't getting
seen. Add a test as well.
2024-01-22 19:40:15 -05:00
James Shubin
b670bb8d2c engine: resources: http: Recurse on Init and Watch
Make this resource handle sub-resources more powerfully.
2024-01-22 19:16:28 -05:00
James Shubin
1abf6547ff engine: resources: http: Improved CheckApply for grouped resources 2024-01-22 19:16:28 -05:00
James Shubin
3739fa401e engine: resources: Refactor HTTP constants 2024-01-22 17:43:24 -05:00
James Shubin
377d62999f engine: resources: The kv resource can set un-mapped values
Previously the resource could only set values in a per-hostname
namespace, but for single, user-managed values, we'd like to be able to
control things entirely. Now this resource can do that.
2024-01-22 17:43:24 -05:00
James Shubin
6f1f69683d lang: funcs: core: world: Add a function to get world values
Useful for following single values that we set.
2024-01-22 17:43:24 -05:00
James Shubin
b4bb4ca397 engine: resources: Update kv error messages 2024-01-22 17:43:24 -05:00
James Shubin
6b4fb434da engine: resources: Rename var
More accurate.
2024-01-22 17:43:24 -05:00
James Shubin
9e70f53afa engine: resources: Add missing apply guard 2024-01-22 17:43:24 -05:00
James Shubin
81885dec63 engine: resources: Add some spacing 2024-01-22 17:43:24 -05:00
James Shubin
f59f3c3c83 lang: funcs: core: convert: Add format and parse for bool 2024-01-22 17:43:24 -05:00
James Shubin
18c66ae7ac lang: funcs: core: Remove unused struct fields and bool
Old copy-pasta misc!
2024-01-22 17:43:24 -05:00
James Shubin
d01c168450 lang: Add per-test config with count maximums
Some of our special tests can only be run once per `go test` invocation.
That is, using the test -count flag will cause a guaranteed failure
since we depend on a global being initialized only once as part of that
test.

This adds a per-test config option so that a user can specify to never
run a particular test more than once. This lets us continue to use the
-count flag with the test suite, without it causing some tests to fail.
2024-01-22 16:55:45 -05:00
James Shubin
f0a4a9e3c4 lang: Don't print test comments if empty 2024-01-22 16:53:47 -05:00
James Shubin
78e59a9400 github: New versions of github actions 2024-01-22 15:52:49 -05:00
James Shubin
a8f194259b legal: Happy 2024 everyone...
Done with:

ack '2023+' -l | xargs sed -i -e 's/2023+/2024+/g'

Checked manually with:

git add -p

Hello to future James from 2025, and Happy Hacking!
2024-01-22 15:52:49 -05:00
James Shubin
c39fdcb8ac lang: funcs: dage: Have the compiler verify expectations
This engine should fulfill the GraphAPI interface. Specify that here in
case it's not obvious.
2024-01-22 15:52:49 -05:00
James Shubin
26e46a6e18 lang: funcs: txn: Add a simple graph implementation
This fulfills the GraphAPI that we use.
2024-01-22 15:52:49 -05:00
James Shubin
4ad7edc35e lang: funcs: txn: Move transaction code to a new package
This also makes it public, although it is designed for internal use
only.
2024-01-22 15:38:18 -05:00
James Shubin
28eacdb2bb lang: funcs: ref: Move reference counting code to a new package
This also makes it public, although it is designed for internal use
only.
2024-01-22 15:38:18 -05:00
James Shubin
66b826a8e1 pgraph, lang: funcs: dage: Add an awkward graphviz interface
This refactors the code to make it not depend on the specific engine.
2024-01-22 15:35:21 -05:00
James Shubin
20c8a856a2 lang: funcs: dage: Add a Graph method to improve the API
This also adds it to the GraphAPI so that users of it can pull out a
graph when needed. It's not likely to be used by the dage engine
consumers.
2024-01-22 15:09:32 -05:00
James Shubin
dd20bd5486 lang: ast: Improve ordering to eliminate false positives
The Ordering and DAG detection code is challenging because we need
Ordering to do SetScope, but Ordering itself needs to know about scopes.
This improved variant should hopefully catch all the scenarios of
identically named variables causing invalid loops.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2024-01-22 13:19:10 -05:00
James Shubin
f8077d9fc4 lang: ast: Add a util function for node map copying 2024-01-22 13:07:17 -05:00
Samuel Gélineau
16dfb7b5f5 lang: Add more test cases related to future graph optimization
We are planning to implement an optimization in which some function
calls are compiled to a single static graph rather than to a CallFunc
which dynamically creates a sub-graph at runtime. These test cases
exercise corner cases for which it would be theoretically possible to
use a static graph, but which we might not be able to optimize.
2024-01-20 15:01:56 -05:00
James Shubin
aae0e16350 lang: ast, parser, interfaces: Implementation of nested class sugar
This implements a new type of syntactic sugar for the common pattern of
a base class which returns a child class, and so on. Instead of needing
to repeatedly indent the child classes, we can instead prefix them at
the definition site (where created with the class keyword) with the name
of the parent class, followed by a colon, to get the desired embedded
sugar.

For example, instead of writing:

class base() {
	class inner() {
		class deepest() {
		}
	}
}

You can instead write:

class base() {
}
class base:inner() {
}
class base:inner:deepest() {
}

Of course, you can only access any of the inner classes by first
including (with the include keyword) a parent class, and then
subsequently including the inner one.
2024-01-14 17:10:31 -05:00
James Shubin
741a71b490 lang: parser: Clean up the parser to avoid parsing res separately
This cleans up some cruft in the parser, and makes it easier for us to
extend this to future colon separated identifiers.
2024-01-14 17:10:31 -05:00
James Shubin
44ee578a3a lang: parser, ast, interfaces: Implement include as
This adds support for `include as <identifier>` type statements which in
addition to pulling in any defined resources, it also makes the contents
of the scope of the class available to the scope of the include
statement, but prefixed by the identifier specified.

This makes passing data between scopes much more powerful, and it also
allows classes to return useful classes for subsequent use.

This also improves the SetScope procedure and adds to the Ordering
stage. It's unclear if the current Ordering stage can handle all code,
or if there exist corner-cases which are valid code, but which would
produce a wrong or imprecise topological sort.

Some extraneous scoping bugs still exist, which expose certain variables
that we should not depend on in future code.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2024-01-14 17:08:51 -05:00
James Shubin
f92f34dc54 lang: The in keyword can't be a variable name
At least for now until we figure out some fancier parser magic. (If
at all possible!)
2024-01-12 18:22:00 -05:00
James Shubin
dff9c9ad22 lang: ast: Add more validation on struct fields
Catch more errors early, just in case.
2024-01-12 18:11:39 -05:00
James Shubin
acae7eccc4 lang: interfaces: Expand docs on compiler constants
Improve the docs to make things clearer.
2024-01-07 18:44:58 -05:00
James Shubin
837739d7dd pgraph: Implement FilterGraph in terms of FilterGraphWithFn
Simplify the code base by having one core implementation.
2024-01-07 18:44:58 -05:00
James Shubin
2567303fd7 pgraph: Add FilterGraphWithFn implementation
This lets us filter more programmatically.
2024-01-07 18:44:58 -05:00
James Shubin
4939ae1a2f pgraph: FilterGraph doesn't need a name arg
It's not being consumed anywhere, so remove it. If you really want to
rename the graph, this can be done as a second step.
2024-01-07 18:25:50 -05:00
James Shubin
58607e2ca0 pgraph: Filter graph should not include unfiltered vertices
This adds a test for another sneaky aspect of filter graph: It should
not pull in a vertex because another across the edge was included. If we
want this behaviour, then we need a different function or a config
option for this filter function.
2024-01-07 18:24:56 -05:00
James Shubin
cb021e3a25 pgraph: Filter graph should preserve vertices without an edge
There was a bug in filter graph that caused it to not preserve any
vertices that weren't bound to another with an edge. This fixes the bug
and adds a test too.
2024-01-07 17:56:02 -05:00
James Shubin
d61936c06d test: Add some simple commit body tests
I never remember the capitalization rules, so hopefully this helps me!
2024-01-05 16:57:42 -05:00
Samuel Gélineau
3553eb1f2a lang: ast: Fix data race in ExprSingleton
Init the mutex everywhere, but consider calling Init instead and
plumbing though the data input field in the future.
2024-01-05 16:27:41 -05:00
James Shubin
c4a9560d53 engine: local: Fix benign race
Use the mutex in a safer manner to eliminate the benign data race.
2024-01-05 15:50:43 -05:00
James Shubin
bc63b7608e engine: graph: Use an atomic bool instead of a mutex
The isStateOK variable can be accessed concurrently as these are
supposed to be "benign" races. As such, they need to be labelled as such
so that we don't hit some undefined compiler behaviour.

Here are five good references relating to "benign" data races in golang.

1) https://web.archive.org/web/20181022150257/https://software.intel.com/en-us/blogs/2013/01/06/benign-data-races-what-could-possibly-go-wrong

2) https://go.dev/ref/mem - "Informal Overview" section.

3) https://docs.oracle.com/cd/E19205-01/820-0619/gecqt/index.html

4) https://www.usenix.org/legacy/event/hotpar11/tech/final_files/Boehm.pdf

5) https://go.dev/doc/articles/race_detector

TL;DR: wrap your benign races with sync/atomic or eliminate them.
2024-01-05 15:50:42 -05:00
James Shubin
c2f508e261 engine: graph: Use an rwmutex around read/write of vertex state
This fixes two small races we had while simultaneously reading from and
writing to the vertex timestamp, and simultaneously writing to the
vertex isStateOK (dirty) flag.

They were actually "safe" races, in that it doesn't matter if the
read/write race got the old or new value, or that the double write
happened. The time sequencing was correct (I believe) in both cases, but
this triggers the race detector now that we have tests for it.
2024-01-02 18:18:34 -05:00
James Shubin
a07dc0a511 lang: interpolate, funcs: Add a concat function for faster interpolation
This adds a concat function which can be used directly by string
interpolation to avoid having to constantly unify the plus operator
which is much slower at this time.

The new monomorphisms changes caused type unification of a notable
example to go from ~25s to ~5m30s which was obviously not bearable. With
this fix, things are now down to ~6s.

This is an important optimization, but it's also a good reminder that
type unification of polymorphic functions needs to be improved in
general too.
2024-01-02 15:23:40 -05:00
James Shubin
d8db320722 lang: gapi: Prettier printing of type unification timing
This makes things a little nicer to visualize.
2024-01-02 13:26:51 -05:00
James Shubin
24054f905f lib, lang, docs: Add --only-unify option
This adds a new run flag for the lang frontend to exit immediately
following type unification. This makes it easier to use this as a step
in CI, and also to type the execution for performance comparison
reasons.
2024-01-02 13:06:19 -05:00
James Shubin
32ca815896 lib, docs: Small cleanups
Formatting and removing of old docs.
2024-01-02 13:04:49 -05:00
James Shubin
fa5949e191 lang: funcs: core: test: Make a new instance for each test
Since this special one_instance function uses global state, if it's
re-used in more than one test, this won't work since they still all use
the whole global state. Make new ones for each test.

This also breaks the count=2 feature (any number other than 1) when
running these, which is not ideal. Create a cleanup API that we can run
between tests to reset the global state.
2023-12-27 18:25:53 -05:00
Samuel Gélineau
1c0a98a0cc lang: ast: ExprBind is now monomorphic
This adds ExprTopLevel and ExprSingleton and ensures that ExprBind is
now monomorphic.

This corrects a previous design bug where it was not monomorphic and
would thus cause spawning of many more copies than necessary. In most
cases this was only harmful to memory and performance, and not
behaviour, since these functions were pure, and we didn't have a test
for this.

This also adds a bunch more tests. Most notably, the graph shape tests
generally produce smaller graphs now.

Lastly, a lambda cannot have two different types when used at two
different call sites. It is rare that this would be used, and when it
would make sense, there are easy workarounds to accomplish equivalent
goals.

This was mostly authored by Sam, James helped with some cleanup and
debugging.

Co-authored-by: James Shubin <james@shubin.ca>
2023-12-27 16:33:38 -05:00
James Shubin
9d208e8795 lang: Test more iter funcs without polymorphic id function
This tests the lambdas in more ways so that we are sure non-polymorphic
$id functions work the way we want.
2023-12-27 16:33:38 -05:00
James Shubin
72fe0cd6db lang: Polymorphic lambda functions aren't allowed for now
We don't allow a lambda variable to be polymorphic anymore. Of course
this isn't bad, but it makes things too difficult, at least for now.
It's also likely that we don't need this specific feature very often I
think.
2023-12-27 16:33:38 -05:00
James Shubin
734590b6bd lang: Add a test for duplicate functions called
We should not call either of these functions more than once for their
values. If we do, it means we have made a mistake with a compiler
optimization.

This is important, because otherwise if you had code like:

$x = random_password()

Then this would obviously be a problem. Thankfully, the situations where
functions generate unique data is rare, but it's probably something we
should take care of.
2023-12-27 16:33:38 -05:00
James Shubin
7cc231e8b9 lang: unification, interfaces: Add a skip invariant
This is a cleaner way of telling the type unifier that we don't want
this particular expression in the solution set at the end.
2023-12-27 16:33:38 -05:00
James Shubin
c2bf4ef7d4 lang: Test a top-level var explicitly
This makes sure the top-level scope is really seen.
2023-12-27 16:33:38 -05:00
James Shubin
439179e37f lang: Remove duplicate test
It's contents were identical to the stmtfunc-recursive-double.txtar
test.
2023-12-27 16:33:38 -05:00
James Shubin
c333cb542c lang: ast: Expr Param and Poly should not have values
Sometimes a recursive call through ExprVar's Value method would hit one
of these and return (nil, nil) which would throw off things.
2023-12-17 21:39:06 -05:00
James Shubin
6a6546db8d lang: ast: Save the non-polyfunc args
We probably want to save these args too in case.
2023-12-16 14:26:29 -05:00
James Shubin
a6d22a5a4b misc: Add editorconfig info for txtar tests 2023-12-12 00:24:41 -05:00
James Shubin
da5f94e62c test: Add a simple mcl formatting test
This should definitely get expanded overtime when we can have something
decent for formatting mcl code. For now, catch the easy things.
2023-12-12 00:13:27 -05:00
James Shubin
1d886f2995 test: Improve bash fmt test
This catches slightly more issues.
2023-12-12 00:13:27 -05:00
James Shubin
a6b6aa570e test: Fix small test warnings
These were issues we should be catching.
2023-12-12 00:01:29 -05:00
James Shubin
32b26a09c2 test: Don't error on unbound variable
We would get this error instead of a test error:

	test/util.sh: line 32: GITHUB_ACTION: unbound variable

This would happen when run locally.
2023-12-12 00:00:19 -05:00
James Shubin
8fcaa4abf2 lang: Output of tests should also show autogrouped resources
This makes our test cases slightly more complete.
2023-12-08 18:25:54 -05:00
James Shubin
18e1f08156 engine: graph: Allow send/recv to work with autogrouped resources
We've previously not received a value from within an autogrouped
resource. It turns out this would be quite useful, and so this patch
implements the additional plumbing and testing so that this works!

Testing that an autogrouped resource can still send values has not been
done at this time.
2023-12-08 18:18:17 -05:00
James Shubin
bf5cc63bc5 misc: Move to golang 1.20
Make sure to quote your 1.20 otherwise it shows up as 1.2 which is very
old!
2023-12-03 21:40:48 -05:00
James Shubin
271a94e0c7 engine: resources: Improve logging on resource errors
When a subcommand errors, it's helpful to see what it was.
2023-12-03 21:21:55 -05:00
James Shubin
c3f34db81e test: Remove new grep warning
With newer versions of grep, you now see:

	grep: warning: stray \ before "

This is because we had an erroneous backslash escaping a quote. Remove
it.
2023-12-03 18:24:20 -05:00
James Shubin
b7d8a769db lang: funcs: core: Add value functions
This adds a new series of "get*" functions which can read values from
the associated "value" resources. The key name of the function must
match the name value of the resource for things to work.

Type unification isn't yet perfect in these scenarios, so you should use
casually and with caution.
2023-12-03 18:24:20 -05:00
James Shubin
c05af3b9b3 lib, lang, engine: local: Add Value mechanisms to local API
This expands the Local API with the first (and in theory, only ever) API
for reading and writing simple values. This is a coordination point for
resources and functions to share things directly.
2023-12-03 18:24:20 -05:00
James Shubin
3e37a60635 lang: funcs: core: test: Fix small formatting issue 2023-12-03 18:24:20 -05:00
James Shubin
9d47b6843f engine, gapi, lang, lib: Plumb through new local API
This is a new API that is similar in spirit and plumbing to the World
API, but it intended for all local machine operations and will likely
only ever have one implementation.
2023-12-03 18:24:20 -05:00
James Shubin
12ffac1f06 lang: Add placeholders for future prefix passing 2023-12-03 18:02:00 -05:00
James Shubin
7991b4ab25 engine, lang, lib: Re-order for consistency
Some small cleanups to make room for future patches.
2023-12-03 18:02:00 -05:00
James Shubin
984fb702e5 engine: resources: Make test resource easier for debugging
Let some fields not get printed if we request it.
2023-12-03 18:02:00 -05:00
James Shubin
019b88cedf util: Add value to B64 serialization functions
These small utility functions are useful.
2023-12-03 16:52:01 -05:00
Samuel Gélineau
55932cf3c7 lang: ast: Clear env on ExprCall.Graph()
Fix the same bug in ExprCall.Graph().
2023-12-02 01:19:31 -05:00
Samuel Gélineau
1ff2c9bbd9 lang: ast: Clear env in ExprVar.Graph()
Fix the bug in ExprVar.Graph() described in the previous commit.
2023-12-02 01:19:31 -05:00
Samuel Gélineau
72235b0fd4 lang: ast: New test "clear-env-on-var"
This test detects a mistake which is easy to make: when making a
recursive call to the target of an ExprVar, it would be easy to
accidentally pass the environment, like we usually do with every other
recursive call. For variables, this is a mistake, because the lambda
parameters which are in scope where the variable is used must not be in
scope where the variable is defined.

In fact, ExprVar.Graph() currently makes this mistake. The test passes
anyway, because an earlier phase (SetScope) correctly clears the
environment and detects the problem before the Graph phase. Thus, this
test does not guarantee that all the phases correctly clear their
environment, it merely detects the unlikely case in which all the phases
make the same mistake.
2023-12-02 01:19:21 -05:00
James Shubin
b8d391024a engine: resources: Add a DoneCmd feature for exec resource
This makes a common pattern of `$cmd && echo done > /tmp/donefile` much
clearer and easier.
2023-11-28 15:24:22 -05:00
James Shubin
598bec0eab engine: resources: Clean up var naming and misc in exec 2023-11-28 15:24:11 -05:00
James Shubin
709bf7b246 engine: resources: Add missing WatchCmd UID
Not sure if this is even used anywhere, but might as well add it for
now.
2023-11-28 15:24:11 -05:00
James Shubin
8251c8f259 lang: funcs: core: Add a second panic signature
This modifies the panic feature to accept a boolean or a string. If true
or not empty, then it will cause the panic. This makes some of the error
code a little less ugly.
2023-11-28 14:43:39 -05:00
James Shubin
9c0bde0b29 lang: funcs, parser: Add improved panic magic
This is a newer implementation of the panic magic. I kept the old commit
in for posterity and to show the difference. The two versions are
identical to the end-user with one exception: the newer version doesn't
include a useless panic resource in the graph when there is no panic. In
this version, the panic function returns false and the if statement it's
the condition of, doesn't produce the resource within. On error, we
still consume the function in the if expression, and doing so causes
everything to shutdown.

The other benefit is that the implementation is much cleaner and doesn't
need the interpolate hack.
2023-11-28 14:40:21 -05:00
James Shubin
2cbce963b7 engine: resources, lang: funcs, parser: Add panic magic
It's valuable to check your runtime values and to shut down the entire
engine in case something doesn't match. This patch adds some magic
plumbing to support a "panic" mechanism.

A new "panic" statement gets transparently converted into a panic
function and panic resource. The former errors if the input is not
empty. The latter must be present to consume the value, but doesn't
actually do anything.
2023-11-28 13:49:31 -05:00
James Shubin
64e6e686e0 lang: funcs: core: math: Add minus1 function
This is a useful template hack until we get something better.
2023-11-28 11:28:49 -05:00
James Shubin
dad3458ece mod: Try and update things
No idea why things break. go.mod sucks.
2023-11-27 21:00:03 -05:00
James Shubin
e727e7adb6 lang: funcs: core: net: Add more mac fmt functions 2023-11-27 21:00:03 -05:00
James Shubin
af1c952700 lang: funcs: funcgen: Allow []string as inputs
This allows us to have the strings.Join function generated.
2023-11-27 21:00:03 -05:00
James Shubin
ce2f7112a3 lang: funcs: funcgen: Don't nest unnecessarily
Clean up the code.
2023-11-27 19:57:54 -05:00
James Shubin
4650ed01eb lang: funcs: funcgen: Refactor slightly for list input
This code had some design issues. We'll rewrite this eventually, but for
now, let's allow list inputs. Refactor this to make it possible.
2023-11-27 19:57:54 -05:00
James Shubin
0e2c73a36d engine: resources: Reduce logging for file resource 2023-11-27 19:57:54 -05:00
James Shubin
139fed40dd docs: Add a reminder about a common gotcha
When I hit it the second time, I decided it needed to get added here.
2023-11-27 19:57:54 -05:00
James Shubin
ee88161808 engine: graph, resources: Reduce and clean up logging
Make the output more usable.
2023-11-22 22:37:35 -05:00
James Shubin
04d54e8d82 engine: resources: Mask normal closing error
This could possibly be fixed upstream, but we'll workaround it for now.
2023-11-22 22:11:50 -05:00
James Shubin
c6c0d3d420 lang: funcs: dage: Move much logging behind debug flag
Most of this logging isn't useful for ordinary usage. Hide it for now.
Eventually when we have a fancy logging system (curses-like) we can
bring back more information on the state of everything.
2023-11-22 22:10:38 -05:00
James Shubin
f4d70068b1 engine: resources: Add a default NBP option to dhcp:server
This let's us do some overrides and is a good alternative since
send/recv+autogrouping doesn't work yet.
2023-11-22 20:22:50 -05:00
James Shubin
3fe5a8d0d6 mod: Update tftp libraries 2023-11-22 20:21:40 -05:00
James Shubin
29a124900c mod: Update dhcp libraries 2023-11-22 20:21:40 -05:00
James Shubin
6670407c9c engine: resources: Make net resource reversible
This allows the net resource to be reversible if we want it to be. It
only flips the network state from up to down or vice-versa.
2023-11-22 20:21:40 -05:00
James Shubin
641b6067cd engine: resources: Remove net config file on down
This cleans up the file we would have created when building the resource.
2023-11-22 20:21:40 -05:00
James Shubin
b1d61fa90b engine: resources: Ignore cleanup errors that don't matter
If the file is already gone, ignore it.
2023-11-22 20:21:40 -05:00
James Shubin
0c93cf2600 engine: resources: Add constants for state up and down
Safer and cleaner code.
2023-11-22 20:21:40 -05:00
James Shubin
53753c0932 engine: resources: Improve net error messages
This is a common case of parse failures for not using CIDR. Make it
cleaner for the user.
2023-11-22 20:21:40 -05:00
James Shubin
029cfaf1f8 engine, lib: Misc log and comment changes
Just misc polishing.
2023-11-22 20:21:40 -05:00
James Shubin
66edc39ccb engine: resources: Improve exec docs
So that I don't hit this too often.
2023-11-22 20:21:40 -05:00
James Shubin
3f42f785ac engine: resources: Handle recurse field more appropriately
This smoothes out some edge cases. We should add comprehensive tests in
the future.
2023-11-22 20:21:40 -05:00
James Shubin
fd4d32351b engine: resources: Refactor file mode code
Small cleanup in old code.
2023-11-22 20:21:40 -05:00
James Shubin
75d3895e84 engine: Improve logging for resources and exec
This makes things a bit clearer and fixes some silly formatting bugs.
2023-11-22 20:21:40 -05:00
James Shubin
d022d7f09e engine: resources: Rename the value field in value resource
Having a different name makes it more obvious, and also leaves us open
to add a string or bool field for more explicit typing.
2023-11-18 15:15:33 -05:00
James Shubin
88b414b9a3 engine: resources: Add new value resource
This is a strange resource which is probably most useful for passing
values between scopes. It supports a variant resource field, and should
only be used as a last resort and if you know exactly what you're doing.
2023-11-18 14:23:31 -05:00
James Shubin
47c441ba40 lang: Test the resource engine briefly
We run the resource engine once and look at its values. This is useful
for testing send/recv in particular.

The converger code is probably not working properly. We'll look into
that subsequently if this gets used a lot.
2023-11-18 13:45:46 -05:00
James Shubin
7105e38544 engine: util: Remove the ValueOf hack
We've got something in the core utility function now.
2023-11-18 13:45:46 -05:00
James Shubin
4b0cdf9123 lang: types, engine: graph: Support pointer interfaces
If we have rare, but special *interface{} values in resource structs, we
should be able to handle them normally. It's really not recommended that
you use these unless you know exactly why they are useful.
2023-11-18 13:35:52 -05:00
James Shubin
1c9fdc79c0 engine: util: Add a workaround for printing special resources
This let's our tests compare strings of interface fields!
2023-11-18 13:35:52 -05:00
James Shubin
90d04990ca engine, lang: Add an AST test that looks at fields too
This gives us more options for testing when we need those kinds of more
extensive resource examination features.
2023-11-18 13:35:52 -05:00
James Shubin
aa001ed2dc engine: graph: Improve error message for rare case
Not sure if we ever hit this, but we should fix it up since I noticed
it.
2023-11-18 13:35:52 -05:00
James Shubin
ce1c37dbca lang: Rename a var for consistency
This is going to get used by a future package.
2023-11-15 17:55:47 -05:00
James Shubin
3a3bc568b3 engine: Allow more send/recv cases
This allows the other part of this. This still needs to be improved, but
it unblocks some use-cases.
2023-11-12 17:50:07 -05:00
James Shubin
b048b2684b engine, lang: Allow resources with a field of type interface
This lets us add a resource that has an implementation with a field
whose type is determined at compile time. This let's us write more
flexible resources.

What's missing is additional type checking so that we guarantee that a
specific resource doesn't change types during run-time.
2023-11-12 17:25:03 -05:00
James Shubin
9a1a81925e engine: Improve error messages for ambiguity
This appears twice, make each one unique.
2023-11-12 17:09:50 -05:00
James Shubin
e38f3cc12c lang: types: Let the fancy TypeOf method match interfaces
This adds the functionality to match on interface kinds.
2023-11-12 16:14:38 -05:00
James Shubin
4fb356b19b engine: graph: Refactor the send/recv code to inline it
This is useful subsequently.
2023-11-12 16:13:04 -05:00
James Shubin
cb6b8a9670 engine: util: Refactor code for future changes
This makes things a bit cleaner and easier to follow and accepts future
patches easier.
2023-11-12 15:26:44 -05:00
James Shubin
efc5237265 lang: types: Improve error handling of value code
We can give a bit more feedback and try and also catch a rare scenario.
2023-11-12 15:24:16 -05:00
James Shubin
cb999af653 engine: Improve debugging for invalid send/recv res
If we don't implement send/recv properly, at least give the user more
information as to where.
2023-11-12 15:21:08 -05:00
James Shubin
8d7d2fb1f1 engine: resources: Add missing struct tags for password res
Just noticed these.
2023-11-04 16:59:09 -04:00
James Shubin
eaff060bf9 lang: ast: Add missing backtick
Purely a formatting fix.
2023-11-04 16:57:41 -04:00
James Shubin
6cc5adcd25 lang, test, examples: lang: Use new syntax for structlookup
Sugar makes the world go round.
2023-11-04 14:52:19 -04:00
James Shubin
233625db20 lang: parser: Lexer should allow in keyword as a variable name
We move it downwards to allow this case. Whether we want to allow this
long-term or not is to be decided.
2023-11-04 14:37:19 -04:00
James Shubin
96093984e4 test, examples: lang: Add new syntax for map lookup
Sugar at last!
2023-10-17 15:32:13 -04:00
James Shubin
ea0af4dc43 lang: parser: Add lookup functions to lexer/parser
This seems to make things work. I'm worried I might have an ordering bug
where we might choose the wrong precedence if we write ambiguous code
somehow, but at least for now, let's commit this and move on. Hopefully
the nonassoc stuff is actually correct.
2023-10-17 15:21:18 -04:00
James Shubin
8fa5241a13 lang: funcs: Add struct lookup with an optional field
This adds an interesting version of the struct lookup function. In the
situation where we can't type-check the field name, it will use the
optional value passed in. This makes it easy to write a function that
will pull in the desired value, even as the input struct changes type
between compilations, without having to re-write your code.

It's structurally different from the other default lookup functions,
which is why it is named differently.
2023-10-17 15:21:18 -04:00
James Shubin
e38eb43955 lang: funcs: Add core lookup functions
These versions don't take defaults and instead return the zero value if
there is an issue.
2023-10-17 15:21:18 -04:00
James Shubin
3b46e88734 lang: types: Improve docs for New and initialize the list
If we use this to generate a zero value, we want to make sure it's
completely initialized in case we use it subsequently. We also improve
the docs at the same time.
2023-10-17 15:21:18 -04:00
James Shubin
d6a58f33f3 lang: funcs: Add lookup default func for list or map
This version requires you specify the default value.
2023-10-17 15:21:18 -04:00
James Shubin
d1c15bd0b7 lang: funcs: Rename the lookup functions
This will make things more consistent with future use.
2023-10-17 15:21:18 -04:00
James Shubin
1dc6ebbffc lang: funcs: Fix one small typo and one small logic bug
I don't think the logic bug had any negative effect, but let's be safe.
2023-10-17 15:21:18 -04:00
James Shubin
ee1e07f3d7 lang: funcs: Rename T2 to T3 for future consistency
This makes things more symmetrical with the maplookup function.
2023-10-17 15:21:18 -04:00
James Shubin
9e7b7fbb3a lang: parser: Define variable identifier in the parser
Instead of in the lexer. I think this simplifies things and gives the
parser more information which should hopefully make it easier to parse
without shift/reduce conflicts.
2023-10-17 15:21:18 -04:00
James Shubin
04fd330733 test: Improve parser indentation test
We need to be fancier with our regexp now that pipes are in the grammar.
Don't match them in comments of course!
2023-10-17 15:21:18 -04:00
James Shubin
361b671799 lang: Clean up test 2023-10-11 17:56:25 -04:00
James Shubin
b8f8ec8aa3 lang: parser, funcs: Take out the built-in history function
It's a normally named function for now, until we think of a common
package to move it into.

Hopefully this makes improving the lexer and parser easier for now. We
can consider bringing it back if needed.
2023-10-09 17:19:08 -04:00
James Shubin
05d570d250 lang: parser, funcs: Change the logical operators to OR, AND, NOT
This makes it easier to read for some, and easier to parse for us. This
also frees up more characters to use elsewhere.
2023-10-09 17:19:08 -04:00
James Shubin
e8f11286dc lang: parser: Sort things in the standardized order
Nitpick fix.
2023-10-09 17:16:06 -04:00
James Shubin
48bc5637a0 misc: Add the mcl type to ackrc for ack users
Now you can `ack --type=mcl` to find .mcl files!
2023-10-09 16:43:50 -04:00
Samuel Gélineau
7bfbe264c7 authors: Adding myself to the authors file 2023-10-07 12:50:19 -04:00
James Shubin
e37876afec engine: resources: Small formatting cleanups 2023-10-07 11:44:47 -04:00
James Shubin
02153356de lang: funcs: core: net: Tidy up test 2023-10-03 18:45:17 -04:00
James Shubin
a686c5f9aa lang: parser: Add new tests for deeper modules
You can have a module that's nested two deep or more.
2023-10-03 16:36:37 -04:00
James Shubin
ab7eb0a293 util: password: Reverse the nonblocking stdin
It's not clear if this is absolutely necessary or not, but it probably
doesn't hurt. It's not clear if there is a way to read the previous
state before running this. Of course this isn't really thread-safe, so
use at your own risk.
2023-09-29 22:13:14 -04:00
James Shubin
738485a655 etcd: Add world API changes for watching member status
This adds some useful functionality so that anyone with access to the
world API, can learn information about the changing etcd cluster it's
using.
2023-09-29 20:46:36 -04:00
James Shubin
0c751ea14f etcd: util: Move etcd utils into separate package 2023-09-29 20:46:36 -04:00
James Shubin
01f249d484 util: password: Add a cancellable ReadPassword style functions
It was non-trivial to do this, so I put it into a library. Strangely I
couldn't directly wrap the ReadPassword function from the
golang.org/x/term package, as it wouldn't unblock for some reason.
2023-09-29 20:46:36 -04:00
James Shubin
53f9f35233 docs: Misc fixes 2023-09-25 18:53:54 -04:00
James Shubin
9a890c7625 misc: More script notes added 2023-09-25 18:53:51 -04:00
James Shubin
2bc23c468e lang: funcs: core: iter: Finish map function
This was the goal all along. Proper iteration without for loops.

Yay!

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2023-09-25 18:53:31 -04:00
James Shubin
6e1cde815c lang: ast: Implement lambdas
This is a big giant patch that implements the AST part of lambdas!

I don't know how Sam is able to understand the AST so well, but he does,
and we're all grateful for it. Most of this code was written by him.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2023-09-25 18:52:26 -04:00
James Shubin
d1f1ed8957 lang: funcs: simple, simplepoly, operator: Misc fix ups for lambdas
These core functions need some small fixups to bring us up to our
lambdas branch.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2023-09-25 18:52:05 -04:00
James Shubin
a1a23b66c8 lang: funcs: structs: Core function struct modifications
These changes are needed for the lambdas implementation.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2023-09-25 18:51:51 -04:00
James Shubin
b4a8d0d783 lang: interfaces, ast: Add debug methods for graphing scope
This can occasionally help in our debugging of the scope graph.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2023-09-25 18:51:33 -04:00
James Shubin
fcc76809e3 lang: interfaces: API changes for lambdas
Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2023-09-25 18:51:25 -04:00
James Shubin
1eae5cf272 lang: interfaces: Remove indexes
These indexes might be useful later, but they are currently not used.
2023-09-25 18:51:22 -04:00
James Shubin
ad1ee0f3dc lang: ast: Remove unused scope test
Maybe this will come back for function values, but it's not needed for
now.
2023-09-25 18:51:13 -04:00
James Shubin
0f99bfe7c4 lang: types: Add full func value
This needs to be in a separate package for now due to import cycles.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2023-09-25 18:51:07 -04:00
James Shubin
dbba49540e lang: types: Export the base value for others
This lets others implement this more easily.
2023-09-25 18:51:03 -04:00
James Shubin
5440ef7eb2 lang: types: Misc function changes
This will probably change up again in the future, but this is what we
need for now to make lambdas work.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2023-09-25 18:50:53 -04:00
James Shubin
8d63b98212 lang: Add a bunch of new language tests
These test both graph shape consistency and single value outputs.
Eventually we want to make the graph shape tests more precise, and also
verify specific outputs how it used to be. For now, this is okay.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2023-09-25 18:50:43 -04:00
James Shubin
d4b1e8f1be lang: Core language and GAPI changes
These changes help plumb things in more easily for the lambdas work.
2023-09-25 18:50:07 -04:00
James Shubin
47d2a661bc lang: interfaces, funcs: Add a new graph engine called dage
This adds a new implementation of the function engine that runs the DAG
function graph. This version is notable in that it can run a graph that
changes shape over time. To make changes to the same of the graph, you
must use the new transaction (Txn) system. This system implements a
simple garbage collector (GC) for scheduled removal of nodes that the
transaction system "reverses" out of the graph.

Special thanks to Samuel Gélineau <gelisam@gmail.com> for his help
hacking on and debugging so much of this concurrency work with me.
2023-09-25 18:48:40 -04:00
James Shubin
7f46bcef61 lang: types: Loop in deterministic order
This makes some traces and reproducing of bugs slightly more
deterministic.
2023-09-25 18:48:38 -04:00
James Shubin
4fd90b5d52 lang: types: Avoid a panic if it's not settable
We should check this for safety. An error is better than a panic. If we
try to set an unexported field, this would panic. We should prevent
being able to even type unify that though!
2023-09-25 18:48:33 -04:00
James Shubin
28c206da18 lang: ast: structs: Remove value lookup from if expr type
This is really not needed and makes our future API changes to Value more
difficult.
2023-09-25 18:48:28 -04:00
James Shubin
5e58735bb3 lang: funcs: Add listlookup function
This looks up a value in a list from an integer index.
2023-09-25 18:48:21 -04:00
James Shubin
c06c391461 lang: Update the Build signature to return a type
This returns the type with the arg names we'll actually use. This is
helpful so we can pass values to the right places. We have named edges
so you can actually see what's going on.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2023-09-25 18:48:15 -04:00
James Shubin
31c7144fff lang: ast, interfaces, interpret: Change the Output sig
This plumbs through the new Output method signature that accepts a table
of function pointers to values and relies on the previous storing of the
function pointers to be used for the lookup right now. This has the
elegant side-effect that Output generation could run in parallel with
the graph engine, as the engine only needs to pause to take a snapshot
of the current values tables.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2023-09-25 18:48:07 -04:00
James Shubin
9175d26b3b lang: ast, interfaces: Plumb in the Graph sig changes and value pointers
The Graph signature changes are needed for future function work, and it
also fits in nicely with the need for storing the value pointer for each
function node. These are used to later extract values during the Output
stage.

Sam deserves all of the credit for realizing both of these points and
convincing me to make the change! It worked out great, cheers!

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2023-09-25 18:47:58 -04:00
James Shubin
f1d6f70cd2 lang: interfaces: Partially change Graph signature
This makes some of the Graph sig changes to prepare the code for proper
functions. The remaining bits will happen later.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2023-09-25 18:46:30 -04:00
James Shubin
b27b809ea9 lang: funcs: Remove old engine code
This gets rid of the old engine implementation which can only run a
static graph. We'll need a changing graph for lambdas.
2023-09-19 14:45:58 -04:00
James Shubin
1ec837089e lang: interpolate: Rename interpolate functions to please linters 2023-09-19 14:45:44 -04:00
James Shubin
089267837d engine: graph: Add a lock around metas access
We forgot about the concurrent writes. This should fix that.
2023-09-19 13:46:20 -04:00
James Shubin
c851322826 engine: resources: Fix up test resource
Woops...
2023-09-19 09:12:59 -04:00
James Shubin
1b12db92ab util: socketset: Add missing return
If our machine has that pipe busy, don't panic the test. (We do want it
to fail though!)

We're also more careful to check nil object just as a convenience to
help programmers.
2023-09-19 06:50:26 -04:00
James Shubin
6c0775ba59 engine: resources: Consider passing in funcs if possible
Let this sit in the test resource for now.
2023-09-04 16:33:05 -04:00
James Shubin
0d381e4c91 engine: graph: Handle the back poke differently
A back poke is the deferral or delay of a Process/CheckApply. This is
because we notice that we're not truly ready to CheckApply due to some
timestamp issue. When Process errors, we should accept that, but not
treat it as a success.
2023-09-04 15:22:14 -04:00
James Shubin
7ccda7e99b engine: Add a ctx to the CheckApply API
This is just a rough port, there are lots of optimizations to be done
and lots of timeout values that should be replaced by a new timeout meta
param!
2023-09-02 01:34:42 -04:00
James Shubin
567de2e115 engine: Use a struct instead of a compound string for resUID
This struct combination of resource kind+name is suitable for use as a
unique map key.
2023-09-02 01:34:42 -04:00
James Shubin
cf49a9a6e7 engine: graph: Give retry channel its own signal
This just makes the copy+pasting less confusing.
2023-09-02 00:59:48 -04:00
James Shubin
41493993a2 engine: resources: Respect the apply variable
Woops!
2023-09-01 22:51:44 -04:00
James Shubin
1d10f85c28 engine: graph: Move misplaced comment 2023-09-01 22:13:34 -04:00
James Shubin
d3d84524f5 engine: graph: Improve limit output
Just show the milliseconds, and round it slightly.
2023-09-01 21:57:05 -04:00
James Shubin
cc04221516 engine: graph: Allow pause/resume while in retry or limit
The retry and limit "satellite" event loops didn't allow pausing or
resuming, and instead you needed to wait until either was done before
you could pause.

The downside of this patch is that for very fast graph transitions, we
wouldn't be really obeying the limits anymore, however now that we have
per resource kind+name uid, we can persist the limits across graph swaps
if we want to.

Most importantly, this allows us to exit entirely when we're stuck in
one of these satellite loops.
2023-09-01 21:57:05 -04:00
James Shubin
f9bc50e262 engine: Retry should be stateful and add RetryReset
Make the retry meta param a bit more sane now that we can persist it
between graph switches. This also unblocks us from pausing during retry
loops.
2023-09-01 21:57:05 -04:00
James Shubin
9545e409d4 engine: Create a resource kind+name specific stateful store
This adds a meta state store that is preserved between graph switches if
the kind and name match. This is useful so that rapid graph changes
don't necessarily reset their retry count if they've only changed one
resource field.
2023-09-01 21:57:05 -04:00
James Shubin
07bd8afc4a engine: graph, metaparams: Default rewatch to false
The frequency of graph changes makes it unlikely that you want this
enabled on most resources by default.
2023-09-01 19:02:11 -04:00
James Shubin
9ac8d7ec49 test: shell: Disable flaky tests
I think this is related to timing and slow CI, but I really don't want
to waste time with old shell yaml tests.
2023-09-01 17:17:59 -04:00
James Shubin
b4bdc8adee github: Don't fail other jobs just because one failed
We like fast CI, but we prefer to have more information about the
failures.
2023-09-01 17:08:59 -04:00
James Shubin
8299c04fc6 engine: graph, util: Clean up error printing
We should improve on this more, but at least as a quick fix, stop
splitting the error across two lines. This makes the logs really ugly.
2023-09-01 16:53:40 -04:00
James Shubin
0b1b0a3f80 engine: graph: Don't deadlock on error
This simplifies the pause mechanism and also avoids a deadlock on error.
If the Worker shuts down completely, but before we've been removed from
the graph, then an attempted pause would deadlock if we didn't have an
escape hatch here.

This removes the unnecessary ack mechanism now that we have a
synchronous channel send to represent the pausing, rather than an
asynchronous channel closing.
2023-09-01 16:53:40 -04:00
James Shubin
2773a621a2 engine: graph: Cleanup pause/resume code
There's always the fear that there is either a panic or a deadlock in
the highly concurrent engine resource code. I have not seen one recently
and I've been running some pretty concurrent tests. In the meantime, and
with my hopefully improved knowledge of concurrency, I decided to
rewrite some of the "uglier" parts of the engine. I think it is a lot
clearer now, and much less likely that there is a concurrency issue.

This has been tested by running the examples/lang/fastcount.mcl example.
2023-08-30 21:58:38 -04:00
James Shubin
2edae22a65 lang: funcs: core: test: Add a new test functions package
This adds a new test functions package and also a new "fastcount"
function which counts up from zero as fast as is possible. You probably
don't want to use this in production, but it is useful for performance
and deadlock testing the resource and function engines.
2023-08-30 21:58:38 -04:00
James Shubin
c06cf44fd7 lib, engine: graph: Rename the Close method 2023-08-30 21:38:01 -04:00
James Shubin
7288e5d4a4 engine: graph: Improve documentation on concurrent use
Certain other methods should not be called concurrently, but this only
documents the most important cases.
2023-08-30 21:38:01 -04:00
James Shubin
b62f501745 engine: graph: Fix typo
We actually now cleanup instead of closing. It's semantically slightly
different, so be consistent with the error message.
2023-08-30 21:38:01 -04:00
James Shubin
c199a51eeb readme: Travis is not really being used anymore 2023-08-30 13:01:59 -04:00
James Shubin
b60d222c81 integration: Port tests to use standalone etcd
This ports the integration tests to use a standalone etcd server instead
of depending on the flaky elastic etcd clustering. Hopefully we will
polish and/or reimplement that at some point in the future, but at least
for now let's make things reliable.
2023-08-30 03:44:19 -04:00
James Shubin
fb57fb714a main, lib: Build in standalone etcd
Standalone etcd is useful for when we don't want to use the embedded
version to make it easier to deploy somewhere or for testing.

This pulls in about the same amount of code since we already embedded
etcd previously. Since the embedded etcd feature of mgmt is not very
stable, we'll add this for now.
2023-08-30 03:44:12 -04:00
James Shubin
bc390088b3 etcd: Don't unset our only endpoint
When mgmt is in etcd-client-only mode and using an external etcd server,
we don't want to unset our only known endpoint since this would deadlock
our etcd client since it can't connect to anyone. This could have
happened because a plain etcd server didn't set any endpoints to follow,
and as a result we noticed it was empty and decided to use that instead.

To workaround this issue on an earlier version of mgmt, you would have
had to run:

etcdctl put /_mgmt/endpoints/etcd http://localhost:2379

to set this magic key on the initial etcd server.
2023-08-30 03:05:22 -04:00
James Shubin
532e2ec8e1 etcd: Chooser shouldn't be used with --no-server
We forgot to check this in Init().
2023-08-30 03:05:22 -04:00
James Shubin
6f4b6cec7e make: Add a flag to disable trimpath except on release
This is kind of magic now. Use .envrc in dev environments to disable it,
except during releases of course!
2023-08-30 03:05:22 -04:00
James Shubin
0b972c5b4b lang: funcs: core: fmt: Add more invariants to printf
These don't seem to change the number of passing tests, but add them in
case we hit some situation that benefits from them.
2023-08-29 19:33:46 -04:00
James Shubin
45e126bf07 lang: funcs: core: fmt: Allow dynamic format strings
There are many reasonable cases where we might want to allow a dynamic
format string. Support that situation by adding the new invariants that
are needed for those cases.
2023-08-29 19:33:45 -04:00
James Shubin
318f28affd lang: unification, interfaces: Don't pass over generators
We were skipping over being fully consistent with all of the generator
invariants when running the solver. This allowed us to miss some of the
conditions that a generator might impose. Usually this caused us to be
"solved" when in fact we had an invalid program.
2023-08-29 19:33:45 -04:00
James Shubin
2214954c51 lang: unification: Error instead of panic
This is still a programming error, but not as dangerous for the edge
cases we might still have present, particularly with printf.
2023-08-29 19:33:45 -04:00
James Shubin
ec515f4fb5 make: Make builds more likely to be reproducible
As per https://go.dev/blog/rebuild if we include -trimpath then we'll
not store full build paths in the build artifacts.

We still have some CGO dependencies, but we'll look into those
separately.
2023-08-28 17:44:40 -04:00
James Shubin
3fb75707e7 lang: types: We should use the platform-dependent int instead
This could be a 32 or 64 bit sized int depending on arch.
2023-08-28 15:54:36 -04:00
James Shubin
c1850e0e20 engine: resources, lang: Set resource fields more accurately
There were some bugs about setting resource fields that were structs
with various fields. This makes things more strict and correct. Now we
check for duplicate field names earlier (duplicates due to identical
aliases) and we also don't try and set private fields, or incorrectly
set partial structs.

Most interestingly, this also cleans up all of the resources and ensures
that each one has nicer docs and a clear struct tag for fields that we
want to use in mcl. These are mandatory now, and if you're missing the
tag, then we will ignore the field.
2023-08-26 01:11:50 -04:00
James Shubin
b8d87e2d5a lang: funcs: core: fmt: Fix printf unification bug
We also add a backup fix to avoid a panic in case we ever hit a new
unification bug that lets something through, we can at least turn it
into a runtime issue. This adds a test as well.
2023-08-22 16:35:08 -04:00
James Shubin
1f53fd85b4 lang: unification: Add new eq to main list
We want to add the equality to the main list of equalities in case it is
needed somewhere else. This doesn't have any effect on any of our test
cases, but it doesn't seem to be harmful, and it could conceivably be
useful in the future. This is a separate commit in case we want to check
if this behaviour still holds true well into the future.
2023-08-22 15:56:04 -04:00
James Shubin
564c93ee21 lang: unification: Rename short vars
These two vars are used pretty deeply through this code, so rename them
to prevent any confusion.

We also switch from a range loop to a counter so that the loop list can
be changed while it's looping.
2023-08-22 15:55:55 -04:00
James Shubin
6769786241 lang: unification: Add equiv matching and resultant equalities
The set of initial invariants that we see might include:

	?8 = func(inputs []int, function  ?4) ?3
	?8 = func(inputs []int, function ?10) ?9
	?8 = func arg0   []int, arg1      ?6) ?7

From this we can infer that since they are all equal, that we also know
that ?4, ?10 and ?6 must also be equal. The same is true of ?3, ?9 and
?10. Those new equalities are sometimes necessary in order to complete
the full unification.

The second interesting aspect is when we have dissimilar equalities:

	?2  = func(x  ?1) str
	?4  = func(a int) ?5
	?10 = func(a int) ?11

In this example we also have an additional equality:

	?6 = ?2

From this and the above we can determine that ?2, ?4, ?6 and ?10 are all
equal. We only know about ?4, ?6, and ?10 from the direct relationship,
and we add in ?2 from the indirect (graph) relationship. These
relationships let us determine new information that ?5 and ?11 are both
str and that ?1 is an int.

Two important reminders:

1) Arg names don't have to match. It would impossible to build such a
   system where this was both possible, but also let us name our
   functions sanely.

2) None of this guarantees we won't find an inconsistency in our
   solution. If this is found, it simply means that someone wrote code
   which does not type check.
2023-08-22 15:55:48 -04:00
James Shubin
0946e860f1 lang: unification: Catch possible rare bug
When adding some likely incorrect invariants (while testing code) we hit
this panic. While it should not occur, it's probably better to catch it.
2023-08-22 15:55:44 -04:00
James Shubin
33366469a3 lang: unification: Add partial func knowledge to solved
This performs the same task as the previous two commits, but it also
adds partial information when solving a func.
2023-08-20 21:00:02 -04:00
James Shubin
7d29efeb33 lang: unification: Add more partial knowledge to solved
This performs the same task as the previous commit, but it also adds
partial information when solving a list, map, or struct.
2023-08-20 21:00:02 -04:00
James Shubin
f0e9af1cf5 lang: unification: Add partial knowledge to solved
When we were running our partial unification to compare similar looking
function invariants, we would sometimes learn new information, even if
it didn't entirely solve that invariant. When this new information was
learned, it's important that we globally mark it as solved so that
others can use it to complete their invariants. When it was a critical
piece of information, this would result in new information, which would
eventually lead to that original partial invariant that we learned that
information from to becoming solved.
2023-08-20 21:00:02 -04:00
Samuel Gélineau
aec8e1db2d lang: unification: Add type inference state debugging
This presents things in a more formal way for those who are more
familiar with standard type unification syntax.

Patch created by Sam, and cleaned up by James.
2023-08-20 21:00:02 -04:00
James Shubin
170fb64bfc pgraph: graphviz: Update our graphviz library
This makes things a bit easier to use. Especially when building fancy
graphs.
2023-08-08 22:36:52 -04:00
James Shubin
b134c4b778 lang: interfaces, funcs: Port Func API to new Stream signature
This removes the `Close() error` and replaces it with a more modern
Stream API that takes a context. This removes boilerplate and makes
integration with concurrent code easier. The only downside is that there
isn't an explicit cleanup step, but only one function was even using
that and it was possible to switch it to a defer in Stream.

This also renames the functions from polyfunc to just func which we
determine by API not naming.
2023-08-08 21:33:06 -04:00
James Shubin
6a06f7b2ea pgraph: Make vertex sorting more deterministic
If two vertices have the same String output, then sort by pointer string
instead. This makes display of certain graphs more deterministic.
2023-08-08 01:12:06 -04:00
James Shubin
5e0922395c util: Add a new sync primitive named brs
This adds the BoundedReadSemaphore mutex that I invented. It's not that
is necessarily particularly revolutionary, but it is useful. I wish I
had a better name for it, but I couldn't think of anything. It's fairly
obvious what it does, so if you have a suggestion of how to name it,
please do so!
2023-08-08 01:12:06 -04:00
James Shubin
514927c0b3 engine: resources, graph: Change the Close method to Cleanup
This also changes a few similarly named methods. Clearer what it's doing
in terms of cleanup vs. causing some action.
2023-08-08 01:11:29 -04:00
James Shubin
63f05e12ca engine: resources: net: Clean up weird error handling
Not sure how this happened.
2023-08-08 01:11:29 -04:00
James Shubin
b87d70c71c engine: resources: Move wait out of Close method
Watch should wait until everything is done before exiting.
2023-08-08 01:11:29 -04:00
James Shubin
3f403d34a4 etcd: world: Make sure to check if wg is nil or not
We never hit this as far as I know, but might as well be safe.
2023-08-08 01:11:29 -04:00
James Shubin
5d84e33be7 etcd: world: Add missing defer
Not sure how this snuck in!
I've now ran a quick grep across the code base, and I can't find any
similar mistakes.

ack '.Done()' | grep -v defer | grep -iv ctx # then check these
2023-08-08 01:11:29 -04:00
James Shubin
00a37b6289 engine: resources: config_etcd: Fix missing return
Woops!
2023-08-08 01:11:29 -04:00
James Shubin
963393e3d9 engine: graph, resources: Change Watch to use ctx
This is a general port. There are many optimizations and cleanups we can
do now that we have a proper context passed in. That's for a future
patch.
2023-08-08 01:11:29 -04:00
James Shubin
53a878bf61 engine: resources, graph: Change the done channel into a ctx
This is part one of porting Watch to context.
2023-08-08 01:11:29 -04:00
James Shubin
5eac48094b engine: resources: Disable broken docker test
It's not my fault it broke, afaict!
2023-08-08 01:11:29 -04:00
James Shubin
90e8e02b0c misc: Move to new golang version 2023-08-07 23:52:37 -04:00
Laurent Indermuehle
0dcfe027b0 make: Add a Dockerfile to build mgmt for Fedora 2023-07-11 14:26:02 +02:00
James Shubin
7d73c7fca9 lang: funcs: core: Clean up functions with constants 2023-06-29 19:30:23 -04:00
James Shubin
d37862b123 lang: funcs: Use more constants to build types 2023-06-29 15:31:12 -04:00
James Shubin
df9b338279 util: Handle negative values for NumToAlpha
This shouldn't be used often, but it makes writing some kinds of code
easier.
2023-06-29 15:15:37 -04:00
James Shubin
97c7d176f0 lang: funcs: Use constants for arg names 2023-06-29 14:59:05 -04:00
James Shubin
f966b1ae6a lang: funcs: simplepoly: Fix copy pasta error
Print the actual result.
2023-06-27 18:49:00 -04:00
James Shubin
8bc08d7716 lang: unification: Add a test for different func arg names
It's important that during unification that function types can unify
even if their arg names are different. (Their actual unnamed types must
be identical of course.) This adds a test to make sure we preserve this
behaviour.
2023-06-27 16:37:14 -04:00
James Shubin
ef4c0f961d lang: unification: Improve simple solver
This adds a new interpretation of the EqualityWrapFuncInvariant that can
combine two into one if their Expr1 fields are the same. We might know
different partial aspects of multiple functions. This also includes a
test. The test does not pass before this commit which adds it.
2023-06-27 16:37:14 -04:00
James Shubin
42840827f8 lang: unification: Add bare solver tests
This adds some simple test infra to test the solver directly without an
AST.
2023-06-27 16:37:14 -04:00
James Shubin
13b6b9de60 lang: types: Print the full function args
This makes it easier to see logs, but more importantly copy (well the
copy hack) doesn't erase arg names anymore.
2023-06-25 12:25:44 -04:00
James Shubin
74a21bab1a test: Update rule configuration
We got a warning about this.
2023-06-25 02:11:01 -04:00
James Shubin
d1c7770949 lang: parser, funcs, core, iter: Rename xmap to map
The map function previously existed as "xmap" because this was a
reserved word in the parser. This now adds a special case for this
identifier name.
2023-06-01 18:08:37 -04:00
James Shubin
66edf22ea3 lang: Port TestAstFunc1 to txtar format 2023-06-01 16:56:44 -04:00
James Shubin
8fffd10280 lang: Port TestAstFunc2 to txtar format
This ports TestAstFunc2 from our home-grown content storage system to
the txtar package. Since a single file can be used to represent the
entire folder hierarchy, this makes it much easier to see and edit
tests.
2023-06-01 16:56:44 -04:00
James Shubin
446dbde836 util: Add new blocked timer utility
This adds a new *BlockedTimer struct which can be used to run a function
or a printf after a short duration. It can be cancelled early. A short
blog post about the topic is available here:

https://purpleidea.com/blog/2023/05/26/blocked-select-logging-in-golang/
2023-05-26 01:19:13 -04:00
James Shubin
ef84a5a76a test: Rename var
Copy pasted from old script.
2023-05-12 12:10:46 -04:00
Kaushal
25a63956f3 test: Add golangci-lint test 2023-05-12 12:07:52 -04:00
Ofek Atar
eddd16283d engine: util: Add test for StructKindToFieldNameTypeMap function 2023-05-12 14:45:42 +03:00
James Shubin
c5efe7a17b lang, engine: Remove unneeded error wrapping
These situations basically never fail, and if they do, we certainly
don't need more context. This simplifies things a bit.
2023-04-20 18:02:40 -04:00
James Shubin
7075b8b973 pgraph: Add LookupEdge function
The new LookupEdge function lets you find which vertices are associated
with a particular edge, if any.
2023-04-20 15:45:46 -04:00
James Shubin
3f5957d30e docs: readme: Add new talk and clarify README
This should make it more obvious how to reach this content as it might
not have been particularly clear to new users where to find this
information.
2023-04-07 11:51:43 -04:00
James Shubin
bc29957d1e lang: Add a useful debug message to tests
When running this test a lot in series, it helps to have more obvious
debug output.
2023-03-31 19:29:52 -04:00
James Shubin
289835039a lang: Remove SetValue from the engine
This removes the calling of SetValue from the engine, and instead
replaces it with the Table() API. The downside is that this is likely
slower, and the current API with locking being exposed publicly is kind
of ugly. The upside is that this might make building the new engine
easier.

Future versions might remove locking from the API if we can avoid making
any accesses to expressions. Currently this happens within Logf/SafeLogf
which is our main (only?) usage at the moment. Logging could become
smarter perhaps. Alternatively, we might pass in a "setter" function
that gets called safely from within the engine. This could wrap SetValue
and the locking functions wouldn't be part of the public API.
2023-03-31 19:29:52 -04:00
James Shubin
b1e08ef231 util: deadline: Add a simple test for deadline and testing
This is a simple test/example placeholder if we want to remember how to
run deadlines with contexts for testing.
2023-03-24 16:53:42 -04:00
James Shubin
8a463767bf lang: funcs: Make printable function names more unique
This helps a lot with debugging.
2023-03-24 16:50:01 -04:00
James Shubin
c598e4d289 engine, etcd: Update code for latest gofmt fixes
Latest version of golang broken gofmt again...
2023-03-14 16:43:08 -04:00
James Shubin
a7624a2bf9 legal: Happy 2023 everyone...
Done with:

ack '2022+' -l | xargs sed -i -e 's/2022+/2023+/g'

Checked manually with:

git add -p

Hello to future James from 2024, and Happy Hacking!
2023-03-05 18:31:52 -05:00
James Shubin
d20fcbd845 lang: Fixup tests that broke
I should not have changed the error string when I unified the pgraph
library.
2023-03-03 14:47:30 -05:00
James Shubin
5d664855de lang: interfaces, funcs: Implement fmt.Stringer for functions
This adds the requirement that all function implementations provider a
String() string method so that these can be used as vertices in the
pgraph library. If we eventually move to generics for the pgraph DAG,
then this might not matter, but it's not bad that these have names
either.
2023-03-03 14:12:09 -05:00
James Shubin
8366cf0873 pgraph: Add a named error for detected cycles
This makes it useful to find when a topological sort failed.
2023-02-25 23:20:05 -05:00
James Shubin
a41789a746 examples, lang: funcs: core: example: Update vumeter
This patch moves to use the sox package instead of arecord for getting
microphone data, and it also validates that both sox and rec and
installed. We also add a standalone example.
2023-02-08 12:14:11 -05:00
James Shubin
cde3251dd8 test: shell: Disable flaky network test 2022-11-11 22:42:29 -05:00
James Shubin
7c394bf735 lang: Move the Edge struct into the interfaces package
This makes it consumable from more than one package and avoids future
cycles.
2022-11-11 20:28:22 -05:00
James Shubin
76e0345609 pgraph: Make some functions variadic for consistency
This will make it more useful when consuming these functions in a Txn
API which might be more convenient as a big one-liner.
2022-11-11 20:20:14 -05:00
James Shubin
d8820fa185 make: Improve crossbuild docs 2022-10-24 02:38:38 -04:00
James Shubin
b6502693e4 make, golang: Work around new go.mod issues
This updates the versions of virtually all our deps. Ever since golang
switched to go.mod we were unable to do so due to blockages with the
retract mechanism that one dep had used. Here's what seems like a
workaround for now. Suggestions from expert welcome.
2022-10-17 19:36:12 -04:00
James Shubin
f7e5402966 make: Remove missing clean target
This is no longer present.
2022-09-12 14:27:39 -04:00
James Shubin
1e6a825412 lang: funcs: Add cancel methods in a different way
Previously go vet found:
"this return statement may be reached without using the cancel var
defined on line..."
2022-09-12 14:11:25 -04:00
Samuel Gélineau
c23065aacd lang: funcs: Add SystemFunc
Runs a string as a shell command, then produces each line from stdout.
2022-09-11 21:58:39 -04:00
James Shubin
04f5ba67a2 lang: Small build fixes 2022-09-11 21:43:57 -04:00
James Shubin
b87fa6715b misc: Exclude more directories from ack matching
This is almost always what we want.
2022-09-11 20:55:37 -04:00
James Shubin
f6f3298e03 misc: Use go install instead of go get for build binaries 2022-09-11 20:55:37 -04:00
James Shubin
6bfd781947 lang: Replace the go-bindata usage with embed
This doesn't let us have nested mcl at the moment, but we could improve
on this with an embed API for each package. For now this makes building
the project easier.
2022-09-11 20:55:37 -04:00
James Shubin
aff6331211 misc, docs: Move to golang 1.18
Hopefully this fixes some yucky things.
2022-09-11 20:55:37 -04:00
James Shubin
d547c39a16 misc, docs: Move to golang 1.17
There are a few small source fixes and other tidbits to move past 1.16,
but we can do those later.
2022-08-16 19:30:20 -04:00
James Shubin
3cea422365 legal: Happy 2022 everyone...
Done with:

ack '2021+' -l | xargs sed -i -e 's/2021+/2022+/g'

Checked manually with:

git add -p

Hello to future James from 2023, and Happy Hacking!
2022-08-05 23:06:27 -04:00
James Shubin
ac39606386 lang: Misc changes from an old feature branch 2022-08-04 14:49:24 -04:00
James Shubin
12ae44d563 lang: funcs: core: fmt: Add important comment to printf 2022-08-04 14:36:56 -04:00
James Shubin
57b37d9005 pgraph: Misc cleanups and additions that I had kicking around
These changes had been sitting in a feature branch for a while. Push
them up.
2022-08-04 14:36:56 -04:00
James Shubin
9d5cc07567 lang: funcs: core: iter: Add map iterator function part3
This flattens the type unification of the map function so that the
solver has more to work with. It's possible that some scenarios might
solve faster, or without recursion, after this improvement.
2022-08-04 14:24:38 -04:00
James Shubin
75d4d767c6 lang: funcs: core: iter: Add map iterator function part2
This adds the Unify method to our map function and also switches the arg
order because I decided it would look nicer. Completely untested.
2022-08-04 14:24:05 -04:00
James Shubin
0be4b86230 lang: funcs: core: iter: Add map iterator function
Sadly this doesn't all work yet, but the tests and xmap function are
approximately correct. Eventually we add filter and reduce too!
2022-08-04 14:23:16 -04:00
James Shubin
784d15b012 all: Misc housekeeping for new golang versions 2022-08-04 14:16:33 -04:00
James Shubin
00f6045b12 util: safepath: Add a new Dir method and tests
This adds a new helper method. It should be considered for other types
as well.
2022-05-10 23:44:48 -04:00
Jef Masereel
b26f842de1 engine: resources: Add hetzner:vm resource
Hetzner cloud resource using hcloud-go. Requires polling via Meta:poll param. This first commit provides a stable vm resource with support for the basic functions of creating, deleting and updating a live server instance. SSH key handling does still require some attention to make sure checkapply can detect and update live changes to the specified keylist. A dedicated hetzner:sshkeys resource might be in order to make sure the keyset is handled correctly if there are multiple hetzner:vm resources running under the same Hetzner project. All remarks for future improvements are indicated with a TODO prefix
2022-04-20 14:24:25 -04:00
James Shubin
0ab2406db9 engine: Pass through the program version
We forgot this in a few places.
2022-02-17 13:34:26 -05:00
Joe Groocock
bf7e45439b engine: util: Skip unexported fields in struct field mapping
Unexported fields should be ignored when mapping structs from Golang to
mcl, as this avoids issues where certain structs cannot be used in mcl
representation due to their structure that may be incompatible with
conversion. Disallowing private fields allows those to still be included
in the struct but ignored when mapping the types, allowing the mcl
representation of the type to stil be valid

Resolves: https://github.com/purpleidea/mgmt/issues/684
Signed-off-by: Joe Groocock <me@frebib.net>
2021-12-01 22:06:06 +00:00
dantefromhell
0652273fe1 misc: Add systemd service file to release packages
According to [1] "/usr/lib/systemd/system/" is where systemd files from installed packages are stored in Arch Linux

Big thanks to @jordansissel for the in-depth explanation of this PRs effects:
> This PR replaces --prefix with the fpm dir package type's feature "path mapping" which lets you map a local file to a destination file when creating the package using this syntax: localpath=destinationpath

[1] https://wiki.archlinux.org/title/Systemd#Writing_unit_files
2021-11-09 21:42:50 +00:00
James Shubin
5927a54208 docs: Improve autogenerate godoc
There were a bunch of packages that weren't well documented. With the
recent split up of the lang package, I figured it would be more helpful
for new contributors who want to learn the structure of the project.
2021-10-26 00:12:18 -04:00
James Shubin
b46db59948 readme: Update to new godoc link
Thanks to Drew DeVault for hosting this.
2021-10-22 17:44:08 -04:00
James Shubin
23b5a4729f lang: Split lang package out into many subpackages
This is a giant refactor to split the giant lang package into many
subpackages. The most difficult piece was figuring out how to extract
the extra ast structs into their own package, because they needed to
call two functions which also needed to import the ast.

The solution was to separate out those functions into their own
packages, and to pass them into the ast at the root when they're needed,
and to let the relevant ast portions call a handle.

This isn't terribly ugly because we already had a giant data struct
woven through the ast.

The bad part is rebasing any WIP work on top of this.
2021-10-21 04:13:04 -04:00
James Shubin
8ae47bd490 test: Disable flaky shell tests
We need to improve and add these things back eventually, but for now I
don't want intermittent failures.
2021-10-21 01:17:52 -04:00
James Shubin
1796d20399 lang: Split the download code out into its own package
More cleanups.
2021-10-21 00:05:37 -04:00
James Shubin
5ac2447b85 lang: Split out the GAPI code for the lang frontend
This can go in a separate package.
2021-10-21 00:03:07 -04:00
James Shubin
db445c3a8e lang: Move the inputs logic into a separate package 2021-10-20 18:13:46 -04:00
James Shubin
de2914978d lang: Move interpret function to a separate package 2021-10-20 17:50:21 -04:00
James Shubin
09812a7bfc lang: Rename prog struct field to body
This is more consistent and avoids the future prog.prog stuttering.
2021-10-20 16:24:14 -04:00
James Shubin
2eb3b541f4 lang: Fix typo 2021-10-20 15:32:43 -04:00
James Shubin
e9791ff92c lang: funcs: core: fmt: Add variant verb for printf
There's no reason we can't support a %v variant verb. Of course it makes
type unification more difficult, and certain uses of this will produce
unsolvable situations, but it's useful for debugging, and fun to have.
2021-10-11 00:36:29 -04:00
James Shubin
88516546fa lib: Move to go modules and shed a few tears
The old system with vendor/ and git submodules worked great,
unfortunately FUD around git submodules seemed to scare people away and
golang moved to a go.mod system that adds a new lock file format instead
of using the built-in git version. It's now almost impossible to use
modern golang without this, so we've switched.

So much for the golang compatibility promise-- turns out it doesn't
apply to the useful parts that I actually care about like this.

Thanks to frebib for his incredibly valuable contributions to this
patch. This snide commit message is mine alone.

This patch also mixes in some changes due to legacy golang as we've also
bumped the minimum version to 1.16 in the docs and tests.

Lastly, we had to disable some tests and fix up a few other misc things
to get this passing. We've definitely hot bugs in the go.mod system, and
our Makefile tries to workaround those.
2021-10-05 08:34:51 -04:00
1113 changed files with 46851 additions and 13885 deletions

3
.ackrc
View File

@@ -1,3 +1,6 @@
--ignore-dir=old/ --ignore-dir=old/
--ignore-dir=tmp/ --ignore-dir=tmp/
--ignore-dir=vendor/ --ignore-dir=vendor/
--ignore-dir=releases/
--ignore-dir=rpmbuild/
--type-set=mcl:ext:mcl

View File

@@ -23,3 +23,6 @@ indent_style = tab
[*.mcl] [*.mcl]
indent_style = tab indent_style = tab
[*.txtar]
indent_style = tab

View File

@@ -27,29 +27,27 @@ jobs:
# macos tests are currently failing in CI # macos tests are currently failing in CI
#- macos-latest #- macos-latest
golang_version: golang_version:
# TODO: add 1.15.x and tip # TODO: add 1.21.x and tip
# minimum required and latest published go_version # minimum required and latest published go_version
#- 1.13 - "1.20"
- 1.15
test_block: test_block:
- basic - basic
- shell - shell
- race - race
#fail-fast: false fail-fast: false
steps: steps:
# Do not shallow fetch, will fail when building bindata/ # Do not shallow fetch. The path can't be absolute, so we need to move it
# The path can't be absolute, so we need to move it to the # to the expected location later.
# expected location later.
- name: Clone mgmt - name: Clone mgmt
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
fetch-depth: 0 fetch-depth: 0
path: ./go/src/github.com/purpleidea/mgmt path: ./go/src/github.com/purpleidea/mgmt
- name: Install Go ${{ matrix.golang_version }} - name: Install Go ${{ matrix.golang_version }}
uses: actions/setup-go@v2 uses: actions/setup-go@v4
with: with:
go-version: ${{ matrix.golang_version }} go-version: ${{ matrix.golang_version }}

1
.gitignore vendored
View File

@@ -7,7 +7,6 @@ old/
tmp/ tmp/
*WIP *WIP
*_stringer.go *_stringer.go
bindata/*.go
mgmt mgmt
mgmt.static mgmt.static
# crossbuild artifacts # crossbuild artifacts

39
.gitmodules vendored
View File

@@ -1,39 +0,0 @@
[submodule "vendor/github.com/coreos/etcd"]
path = vendor/go.etcd.io/etcd
url = https://github.com/coreos/etcd/
[submodule "vendor/google.golang.org/grpc"]
path = vendor/google.golang.org/grpc
url = https://github.com/grpc/grpc-go
[submodule "vendor/github.com/grpc-ecosystem/grpc-gateway"]
path = vendor/github.com/grpc-ecosystem/grpc-gateway
url = https://github.com/grpc-ecosystem/grpc-gateway
[submodule "vendor/gopkg.in/fsnotify.v1"]
path = vendor/gopkg.in/fsnotify.v1
url = https://gopkg.in/fsnotify.v1
[submodule "vendor/github.com/purpleidea/go-systemd"]
path = vendor/github.com/purpleidea/go-systemd
url = https://github.com/purpleidea/go-systemd
[submodule "vendor/honnef.co/go/augeas"]
path = vendor/honnef.co/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
[submodule "vendor/github.com/purpleidea/docker"]
path = vendor/github.com/docker/docker
url = https://github.com/purpleidea/docker
[submodule "vendor/github.com/purpleidea/distribution"]
path = vendor/github.com/docker/distribution
url = https://github.com/purpleidea/distribution
[submodule "vendor/github.com/hashicorp/go-multierror"]
path = vendor/github.com/hashicorp/go-multierror
url = https://github.com/hashicorp/go-multierror
[submodule "vendor/github.com/containerd/containerd"]
path = vendor/github.com/containerd/containerd
url = https://github.com/purpleidea/containerd
[submodule "vendor/github.com/hashicorp/consul"]
path = vendor/github.com/hashicorp/consul
url = https://github.com/hashicorp/consul/

View File

@@ -24,21 +24,21 @@ install: 'make deps'
matrix: matrix:
fast_finish: false fast_finish: false
allow_failures: allow_failures:
- go: 1.14.x - go: 1.21.x
- go: tip - go: tip
- os: osx - os: osx
# include only one build for osx for a quicker build as the nr. of these runners are sparse # include only one build for osx for a quicker build as the nr. of these runners are sparse
include: include:
- name: "basic tests" - name: "basic tests"
go: 1.13.x go: 1.20.x
env: TEST_BLOCK=basic env: TEST_BLOCK=basic
- name: "shell tests" - name: "shell tests"
go: 1.13.x go: 1.20.x
env: TEST_BLOCK=shell env: TEST_BLOCK=shell
- name: "race tests" - name: "race tests"
go: 1.13.x go: 1.20.x
env: TEST_BLOCK=race env: TEST_BLOCK=race
- go: 1.14.x - go: 1.21.x
- go: tip - go: tip
- os: osx - os: osx
script: 'TEST_BLOCK="$TEST_BLOCK" make test' script: 'TEST_BLOCK="$TEST_BLOCK" make test'

View File

@@ -11,3 +11,4 @@ Johan Bloemberg
Jonathan Gold Jonathan Gold
Julien Pivotto Julien Pivotto
Paul Morgan Paul Morgan
Samuel Gélineau

View File

@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007 Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. of this license document, but changing it is not allowed.
@@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU 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 <https://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.
@@ -664,11 +664,11 @@ 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 GPL, see For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>. <https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with 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 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 Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>. <https://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -1,5 +1,5 @@
Mgmt Mgmt
Copyright (C) 2013-2021+ James Shubin and the project contributors Copyright (C) 2013-2024+ 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
@@ -13,4 +13,16 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
Additional permission under GNU GPL version 3 section 7
If you modify this program, or any covered work, by linking or combining it
with embedded mcl code and modules (and that the embedded mcl code and
modules which link with this program, contain a copy of their source code in
the authoritative form) containing parts covered by the terms of any other
license, the licensors of this program grant you additional permission to
convey the resulting work. Furthermore, the licensors of this program grant
the original author, James Shubin, additional permission to update this
additional permission if he deems it necessary to achieve the goals of this
additional permission.

293
Makefile
View File

@@ -1,5 +1,5 @@
# Mgmt # Mgmt
# Copyright (C) 2013-2021+ James Shubin and the project contributors # Copyright (C) 2013-2024+ 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
@@ -13,19 +13,31 @@
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
#
# Additional permission under GNU GPL version 3 section 7
#
# If you modify this program, or any covered work, by linking or combining it
# with embedded mcl code and modules (and that the embedded mcl code and
# modules which link with this program, contain a copy of their source code in
# the authoritative form) containing parts covered by the terms of any other
# license, the licensors of this program grant you additional permission to
# convey the resulting work. Furthermore, the licensors of this program grant
# the original author, James Shubin, additional permission to update this
# additional permission if he deems it necessary to achieve the goals of this
# additional permission.
SHELL = /usr/bin/env bash SHELL = /usr/bin/env bash
.PHONY: all art cleanart version program lang path deps run race bindata generate build build-debug crossbuild clean test gofmt yamlfmt format docs .PHONY: all art cleanart version program lang path deps run race generate build build-debug crossbuild clean test gofmt yamlfmt format docs
.PHONY: rpmbuild mkdirs rpm srpm spec tar upload upload-sources upload-srpms upload-rpms upload-releases copr tag .PHONY: rpmbuild mkdirs rpm srpm spec tar upload upload-sources upload-srpms upload-rpms upload-releases copr tag
.PHONY: mkosi mkosi_fedora-30 mkosi_fedora-29 mkosi_centos-7 mkosi_debian-10 mkosi_ubuntu-bionic mkosi_archlinux .PHONY: mkosi mkosi_fedora-latest mkosi_fedora-older mkosi_stream-latest mkosi_debian-stable mkosi_ubuntu-latest mkosi_archlinux
.PHONY: release releases_path release_fedora-30 release_fedora-29 release_centos-7 release_debian-10 release_ubuntu-bionic release_archlinux .PHONY: release release_test releases_path release_binary_amd64 release_binary_arm64 release_fedora-latest release_fedora-older release_stream-latest release_debian-stable release_ubuntu-latest release_archlinux
.PHONY: funcgen .PHONY: funcgen
.SILENT: clean bindata .SILENT: clean
# a large amount of output from this `find`, can cause `make` to be much slower! # a large amount of output from this `find`, can cause `make` to be much slower!
GO_FILES := $(shell find * -name '*.go' -not -path 'old/*' -not -path 'tmp/*') GO_FILES := $(shell find * -name '*.go' -not -path 'old/*' -not -path 'tmp/*')
MCL_FILES := $(shell find lang/funcs/ -name '*.mcl' -not -path 'old/*' -not -path 'tmp/*') MCL_FILES := $(shell find lang/ -name '*.mcl' -not -path 'old/*' -not -path 'tmp/*')
SVERSION := $(or $(SVERSION),$(shell git describe --match '[0-9]*\.[0-9]*\.[0-9]*' --tags --dirty --always)) SVERSION := $(or $(SVERSION),$(shell git describe --match '[0-9]*\.[0-9]*\.[0-9]*' --tags --dirty --always))
VERSION := $(or $(VERSION),$(shell git describe --match '[0-9]*\.[0-9]*\.[0-9]*' --tags --abbrev=0)) VERSION := $(or $(VERSION),$(shell git describe --match '[0-9]*\.[0-9]*\.[0-9]*' --tags --abbrev=0))
@@ -36,6 +48,13 @@ ifeq ($(VERSION),$(SVERSION))
else else
RELEASE = untagged RELEASE = untagged
endif endif
# debugging is harder if -trimpath is set, so disable it if env var is set
# this is force enabled automatically when using the release target
ifeq ($(MGMT_NOTRIMPATH),true)
TRIMPATH =
else
TRIMPATH = -trimpath
endif
ARCH = $(uname -m) ARCH = $(uname -m)
SPEC = rpmbuild/SPECS/$(PROGRAM).spec SPEC = rpmbuild/SPECS/$(PROGRAM).spec
SOURCE = rpmbuild/SOURCES/$(PROGRAM)-$(VERSION).tar.bz2 SOURCE = rpmbuild/SOURCES/$(PROGRAM)-$(VERSION).tar.bz2
@@ -53,27 +72,69 @@ GOOSARCHES ?= linux/amd64 linux/ppc64 linux/ppc64le linux/arm64 darwin/amd64
GOHOSTOS = $(shell go env GOHOSTOS) GOHOSTOS = $(shell go env GOHOSTOS)
GOHOSTARCH = $(shell go env GOHOSTARCH) GOHOSTARCH = $(shell go env GOHOSTARCH)
TOKEN_FEDORA-30 = fedora-30 # The underscores separate the prefix name ("TOKEN") the distro ("BINARY",
TOKEN_FEDORA-29 = fedora-29 # "FEDORA-LATEST", etc...) and the arch ("AMD64"). The distro can have a dash.
TOKEN_CENTOS-7 = centos-7 TOKEN_BINARY_AMD64 = $(shell grep -v '#' releases/binary-amd64.release)
TOKEN_DEBIAN-10 = debian-10 TOKEN_BINARY_ARM64 = $(shell grep -v '#' releases/binary-arm64.release)
TOKEN_UBUNTU-BIONIC = ubuntu-bionic TOKEN_FEDORA-LATEST = $(shell grep -v '#' releases/fedora-latest.release)
TOKEN_ARCHLINUX = archlinux TOKEN_FEDORA-OLDER = $(shell grep -v '#' releases/fedora-older.release)
TOKEN_STREAM-LATEST = $(shell grep -v '#' releases/stream-latest.release)
TOKEN_DEBIAN-STABLE = $(shell grep -v '#' releases/debian-stable.release)
TOKEN_UBUNTU-LATEST = $(shell grep -v '#' releases/ubuntu-latest.release)
TOKEN_ARCHLINUX = $(shell grep -v '#' releases/archlinux.release)
FILE_FEDORA-30 = mgmt-$(TOKEN_FEDORA-30)-$(VERSION)-1.x86_64.rpm FILE_BINARY_AMD64 = mgmt-linux-amd64-$(VERSION)
FILE_FEDORA-29 = mgmt-$(TOKEN_FEDORA-29)-$(VERSION)-1.x86_64.rpm FILE_BINARY_ARM64 = mgmt-linux-arm64-$(VERSION)
FILE_CENTOS-7 = mgmt-$(TOKEN_CENTOS-7)-$(VERSION)-1.x86_64.rpm # TODO: add ARCH onto these at the end, eg _AMD64
FILE_DEBIAN-10 = mgmt_$(TOKEN_DEBIAN-10)_$(VERSION)_amd64.deb FILE_FEDORA-LATEST = mgmt-$(TOKEN_FEDORA-LATEST)-$(VERSION)-1.x86_64.rpm
FILE_UBUNTU-BIONIC = mgmt_$(TOKEN_UBUNTU-BIONIC)_$(VERSION)_amd64.deb FILE_FEDORA-OLDER = mgmt-$(TOKEN_FEDORA-OLDER)-$(VERSION)-1.x86_64.rpm
FILE_STREAM-LATEST = mgmt-$(TOKEN_STREAM-LATEST)-$(VERSION)-1.x86_64.rpm
FILE_DEBIAN-STABLE = mgmt_$(TOKEN_DEBIAN-STABLE)_$(VERSION)_amd64.deb
FILE_UBUNTU-LATEST = mgmt_$(TOKEN_UBUNTU-LATEST)_$(VERSION)_amd64.deb
FILE_ARCHLINUX = mgmt-$(TOKEN_ARCHLINUX)-$(VERSION)-1-x86_64.pkg.tar.xz FILE_ARCHLINUX = mgmt-$(TOKEN_ARCHLINUX)-$(VERSION)-1-x86_64.pkg.tar.xz
PKG_FEDORA-30 = releases/$(VERSION)/$(TOKEN_FEDORA-30)/$(FILE_FEDORA-30) PKG_BINARY_AMD64 = releases/$(VERSION)/$(TOKEN_BINARY_AMD64)/$(FILE_BINARY_AMD64)
PKG_FEDORA-29 = releases/$(VERSION)/$(TOKEN_FEDORA-29)/$(FILE_FEDORA-29) PKG_BINARY_ARM64 = releases/$(VERSION)/$(TOKEN_BINARY_ARM64)/$(FILE_BINARY_ARM64)
PKG_CENTOS-7 = releases/$(VERSION)/$(TOKEN_CENTOS-7)/$(FILE_CENTOS-7) PKG_FEDORA-LATEST = releases/$(VERSION)/$(TOKEN_FEDORA-LATEST)/$(FILE_FEDORA-LATEST)
PKG_DEBIAN-10 = releases/$(VERSION)/$(TOKEN_DEBIAN-10)/$(FILE_DEBIAN-10) PKG_FEDORA-OLDER = releases/$(VERSION)/$(TOKEN_FEDORA-OLDER)/$(FILE_FEDORA-OLDER)
PKG_UBUNTU-BIONIC = releases/$(VERSION)/$(TOKEN_UBUNTU-BIONIC)/$(FILE_UBUNTU-BIONIC) PKG_STREAM-LATEST = releases/$(VERSION)/$(TOKEN_STREAM-LATEST)/$(FILE_STREAM-LATEST)
PKG_DEBIAN-STABLE = releases/$(VERSION)/$(TOKEN_DEBIAN-STABLE)/$(FILE_DEBIAN-STABLE)
PKG_UBUNTU-LATEST = releases/$(VERSION)/$(TOKEN_UBUNTU-LATEST)/$(FILE_UBUNTU-LATEST)
PKG_ARCHLINUX = releases/$(VERSION)/$(TOKEN_ARCHLINUX)/$(FILE_ARCHLINUX) PKG_ARCHLINUX = releases/$(VERSION)/$(TOKEN_ARCHLINUX)/$(FILE_ARCHLINUX)
DEP_BINARY_AMD64 =
ifneq ($(TOKEN_BINARY_AMD64),)
DEP_BINARY_AMD64 = $(PKG_BINARY_AMD64)
endif
DEP_BINARY_ARM64 =
ifneq ($(TOKEN_BINARY_ARM64),)
DEP_BINARY_ARM64 = $(PKG_BINARY_ARM64)
endif
DEP_FEDORA-LATEST =
ifneq ($(TOKEN_FEDORA-LATEST),)
DEP_FEDORA-LATEST = $(PKG_FEDORA-LATEST)
endif
DEP_FEDORA-OLDER =
ifneq ($(TOKEN_FEDORA-OLDER),)
DEP_FEDORA-OLDER = $(PKG_FEDORA-OLDER)
endif
DEP_STREAM-LATEST =
ifneq ($(TOKEN_STREAM-LATEST),)
DEP_STREAM-LATEST = $(PKG_STREAM-LATEST)
endif
DEP_DEBIAN-STABLE =
ifneq ($(TOKEN_DEBIAN-STABLE),)
DEP_DEBIAN-STABLE = $(PKG_DEBIAN-STABLE)
endif
DEP_UBUNTU-LATEST =
ifneq ($(TOKEN_UBUNTU-LATEST),)
DEP_UBUNTU-LATEST = $(PKG_UBUNTU-LATEST)
endif
DEP_ARCHLINUX =
ifneq ($(TOKEN_ARCHLINUX),)
DEP_ARCHLINUX = $(PKG_ARCHLINUX)
endif
SHA256SUMS = releases/$(VERSION)/SHA256SUMS SHA256SUMS = releases/$(VERSION)/SHA256SUMS
SHA256SUMS_ASC = $(SHA256SUMS).asc SHA256SUMS_ASC = $(SHA256SUMS).asc
@@ -137,11 +198,6 @@ run: ## run mgmt
race: race:
find . -maxdepth 1 -type f -name '*.go' -not -name '*_test.go' | xargs go run -race -ldflags "-X main.program=$(PROGRAM) -X main.version=$(SVERSION)" find . -maxdepth 1 -type f -name '*.go' -not -name '*_test.go' | xargs go run -race -ldflags "-X main.program=$(PROGRAM) -X main.version=$(SVERSION)"
# generate go files from non-go source
bindata: ## generate go files from non-go sources
$(MAKE) --quiet -C bindata
$(MAKE) --quiet -C lang/funcs
generate: generate:
go generate go generate
@@ -153,10 +209,10 @@ lang: ## generates the lexer/parser for the language frontend
$(PROGRAM): build/mgmt-${GOHOSTOS}-${GOHOSTARCH} ## build an mgmt binary for current host os/arch $(PROGRAM): build/mgmt-${GOHOSTOS}-${GOHOSTARCH} ## build an mgmt binary for current host os/arch
cp -a $< $@ cp -a $< $@
$(PROGRAM).static: $(GO_FILES) $(MCL_FILES) $(PROGRAM).static: $(GO_FILES) $(MCL_FILES) go.mod go.sum
@echo "Building: $(PROGRAM).static, version: $(SVERSION)..." @echo "Building: $(PROGRAM).static, version: $(SVERSION)..."
go generate go generate
go build -a -installsuffix cgo -tags netgo -ldflags '-extldflags "-static" -X main.program=$(PROGRAM) -X main.version=$(SVERSION) -s -w' -o $(PROGRAM).static $(BUILD_FLAGS); go build $(TRIMPATH) -a -installsuffix cgo -tags netgo -ldflags '-extldflags "-static" -X main.program=$(PROGRAM) -X main.version=$(SVERSION) -s -w' -o $(PROGRAM).static $(BUILD_FLAGS);
build: LDFLAGS=-s -w ## build a fresh mgmt binary build: LDFLAGS=-s -w ## build a fresh mgmt binary
build: $(PROGRAM) build: $(PROGRAM)
@@ -168,22 +224,23 @@ build-debug: $(PROGRAM)
# extract os and arch from target pattern # extract os and arch from target pattern
GOOS=$(firstword $(subst -, ,$*)) GOOS=$(firstword $(subst -, ,$*))
GOARCH=$(lastword $(subst -, ,$*)) GOARCH=$(lastword $(subst -, ,$*))
build/mgmt-%: $(GO_FILES) $(MCL_FILES) | bindata lang funcgen build/mgmt-%: $(GO_FILES) $(MCL_FILES) go.mod go.sum | lang funcgen
@echo "Building: $(PROGRAM), os/arch: $*, version: $(SVERSION)..." @echo "Building: $(PROGRAM), os/arch: $*, version: $(SVERSION)..."
@time env GOOS=${GOOS} GOARCH=${GOARCH} go build -i -ldflags=$(PKGNAME)="-X main.program=$(PROGRAM) -X main.version=$(SVERSION) ${LDFLAGS}" -o $@ $(BUILD_FLAGS) time env GOOS=${GOOS} GOARCH=${GOARCH} go build $(TRIMPATH) -ldflags=$(PKGNAME)="-X main.program=$(PROGRAM) -X main.version=$(SVERSION) ${LDFLAGS}" -o $@ $(BUILD_FLAGS)
# create a list of binary file names to use as make targets # create a list of binary file names to use as make targets
# to use this you might want to run something like:
# GOOSARCHES='linux/arm64' GOTAGS='noaugeas novirt' make crossbuild
# and the output will end up in build/
crossbuild_targets = $(addprefix build/mgmt-,$(subst /,-,${GOOSARCHES})) crossbuild_targets = $(addprefix build/mgmt-,$(subst /,-,${GOOSARCHES}))
crossbuild: ${crossbuild_targets} crossbuild: ${crossbuild_targets}
clean: ## clean things up clean: ## clean things up
$(MAKE) --quiet -C test clean $(MAKE) --quiet -C test clean
$(MAKE) --quiet -C bindata clean
$(MAKE) --quiet -C lang/funcs clean
$(MAKE) --quiet -C lang clean $(MAKE) --quiet -C lang clean
$(MAKE) --quiet -C misc/mkosi clean $(MAKE) --quiet -C misc/mkosi clean
rm -f lang/funcs/core/generated_funcs.go || true rm -f lang/core/generated_funcs.go || true
rm -f lang/funcs/core/generated_funcs_test.go || true rm -f lang/core/generated_funcs_test.go || true
[ ! -e $(PROGRAM) ] || rm $(PROGRAM) [ ! -e $(PROGRAM) ] || rm $(PROGRAM)
rm -f *_stringer.go # generated by `go generate` rm -f *_stringer.go # generated by `go generate`
rm -f *_mock.go # generated by `go generate` rm -f *_mock.go # generated by `go generate`
@@ -365,25 +422,25 @@ tag: ## tags a new release
# #
# mkosi # mkosi
# #
mkosi: mkosi_fedora-30 mkosi_fedora-29 mkosi_centos-7 mkosi_debian-10 mkosi_ubuntu-bionic mkosi_archlinux ## builds distro packages via mkosi mkosi: mkosi_fedora-latest mkosi_fedora-older mkosi_stream-latest mkosi_debian-stable mkosi_ubuntu-latest mkosi_archlinux ## builds distro packages via mkosi
mkosi_fedora-30: releases/$(VERSION)/.mkdir mkosi_fedora-latest: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..." @title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"` @title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
mkosi_fedora-29: releases/$(VERSION)/.mkdir mkosi_fedora-older: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..." @title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"` @title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
mkosi_centos-7: releases/$(VERSION)/.mkdir mkosi_stream-latest: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..." @title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"` @title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
mkosi_debian-10: releases/$(VERSION)/.mkdir mkosi_debian-stable: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..." @title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"` @title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
mkosi_ubuntu-bionic: releases/$(VERSION)/.mkdir mkosi_ubuntu-latest: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..." @title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"` @title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
@@ -394,31 +451,73 @@ mkosi_archlinux: releases/$(VERSION)/.mkdir
# #
# release # release
# #
release: TRIMPATH = -trimpath
release: releases/$(VERSION)/mgmt-release.url ## generates and uploads a release release: releases/$(VERSION)/mgmt-release.url ## generates and uploads a release
releases_path: releases_path:
@#Don't put any other output or dependencies in here or they'll show! @#Don't put any other output or dependencies in here or they'll show!
@echo "releases/$(VERSION)/" @echo "releases/$(VERSION)/"
release_fedora-30: $(PKG_FEDORA-30) release_test: $(DEP_BINARY_AMD64) $(DEP_BINARY_ARM64) $(DEP_FEDORA-LATEST) $(DEP_FEDORA-OLDER) $(DEP_STREAM-LATEST) $(DEP_DEBIAN-STABLE) $(DEP_UBUNTU-LATEST) $(DEP_ARCHLINUX) $(SHA256SUMS_ASC)
release_fedora-29: $(PKG_FEDORA-29) @echo '$$< denotes the first dependency of the current rule.'
release_centos-7: $(PKG_CENTOS-7) @echo '> '"$<"
release_debian-10: $(PKG_DEBIAN-10) @echo
release_ubuntu-bionic: $(PKG_UBUNTU-BIONIC) @echo '$$@ denotes the target of the current rule.'
@echo '> '"$@"
@echo
@echo '$$^ denotes the dependencies of the current rule.'
@echo '> '"$^"
@echo
@echo '$$* denotes the stem with which the pattern of the current rule matched.'
@echo '> '"$*"
@echo
@echo "TOKEN_BINARY_AMD64: $(TOKEN_BINARY_AMD64)"
@echo "DEP_BINARY_AMD64: $(DEP_BINARY_AMD64)"
@echo
@echo "TOKEN_BINARY_ARM64: $(TOKEN_BINARY_ARM64)"
@echo "DEP_BINARY_ARM64: $(DEP_BINARY_ARM64)"
@echo
@echo "TOKEN_FEDORA-LATEST: $(TOKEN_FEDORA-LATEST)"
@echo "DEP_FEDORA-LATEST: $(DEP_FEDORA-LATEST)"
@echo
@echo "TOKEN_FEDORA-OLDER: $(TOKEN_FEDORA-OLDER)"
@echo "DEP_FEDORA-OLDER: $(DEP_FEDORA-OLDER)"
@echo
@echo "TOKEN_STREAM-LATEST: $(TOKEN_STREAM-LATEST)"
@echo "DEP_STREAM-LATEST: $(DEP_STREAM-LATEST)"
@echo
@echo "TOKEN_DEBIAN-STABLE: $(TOKEN_DEBIAN-STABLE)"
@echo "DEP_DEBIAN-STABLE: $(DEP_DEBIAN-STABLE)"
@echo
@echo "TOKEN_UBUNTU-LATEST: $(TOKEN_UBUNTU-LATEST)"
@echo "DEP_UBUNTU-LATEST: $(DEP_UBUNTU-LATEST)"
@echo
@echo "TOKEN_ARCHLINUX: $(TOKEN_ARCHLINUX)"
@echo "DEP_ARCHLINUX: $(DEP_ARCHLINUX)"
release_binary_amd64: $(PKG_BINARY_AMD64)
release_binary_arm64: $(PKG_BINARY_ARM64)
release_fedora-latest: $(PKG_FEDORA-LATEST)
release_fedora-older: $(PKG_FEDORA-OLDER)
release_stream-latest: $(PKG_STREAM-LATEST)
release_debian-stable: $(PKG_DEBIAN-STABLE)
release_ubuntu-latest: $(PKG_UBUNTU-LATEST)
release_archlinux: $(PKG_ARCHLINUX) release_archlinux: $(PKG_ARCHLINUX)
releases/$(VERSION)/mgmt-release.url: $(PKG_FEDORA-30) $(PKG_FEDORA-29) $(PKG_CENTOS-7) $(PKG_DEBIAN-10) $(PKG_UBUNTU-BIONIC) $(PKG_ARCHLINUX) $(SHA256SUMS_ASC) releases/$(VERSION)/mgmt-release.url: $(DEP_BINARY_AMD64) $(DEP_BINARY_ARM64) $(DEP_FEDORA-LATEST) $(DEP_FEDORA-OLDER) $(DEP_STREAM-LATEST) $(DEP_DEBIAN-STABLE) $(DEP_UBUNTU-LATEST) $(DEP_ARCHLINUX) $(SHA256SUMS_ASC)
@echo "Pushing git tag $(VERSION) to origin..." @echo "Pushing git tag $(VERSION) to origin..."
git push origin $(VERSION) git push origin $(VERSION)
@echo "Creating github release..." @echo "Creating github release..."
hub release create \ hub release create \
-F <( echo -e "$(VERSION)\n";echo "Verify the signatures of all packages before you use them. The signing key can be downloaded from https://purpleidea.com/contact/#pgp-key to verify the release." ) \ -F <( echo -e "$(VERSION)\n";echo "Verify the signatures of all packages before you use them. The signing key can be downloaded from https://purpleidea.com/contact/#pgp-key to verify the release." ) \
-a $(PKG_FEDORA-30) \ ` [ -e $(PKG_BINARY_AMD64) ] && printf -- "-a $(PKG_BINARY_AMD64)" ` \
-a $(PKG_FEDORA-29) \ ` [ -e $(PKG_BINARY_ARM64) ] && printf -- "-a $(PKG_BINARY_ARM64)" ` \
-a $(PKG_CENTOS-7) \ ` [ -e $(PKG_FEDORA-LATEST) ] && printf -- "-a $(PKG_FEDORA-LATEST)" ` \
-a $(PKG_DEBIAN-10) \ ` [ -e $(PKG_FEDORA-OLDER) ] && printf -- "-a $(PKG_FEDORA-OLDER)" ` \
-a $(PKG_UBUNTU-BIONIC) \ ` [ -e $(PKG_STREAM-LATEST) ] && printf -- "-a $(PKG_STREAM-LATEST)" ` \
-a $(PKG_ARCHLINUX) \ ` [ -e $(PKG_DEBIAN-STABLE) ] && printf -- "-a $(PKG_DEBIAN-STABLE)" ` \
` [ -e $(PKG_UBUNTU-LATEST) ] && printf -- "-a $(PKG_UBUNTU-LATEST)" ` \
` [ -e $(PKG_ARCHLINUX) ] && printf -- "-a $(PKG_ARCHLINUX)" ` \
-a $(SHA256SUMS_ASC) \ -a $(SHA256SUMS_ASC) \
$(VERSION) \ $(VERSION) \
> releases/$(VERSION)/mgmt-release.url \ > releases/$(VERSION)/mgmt-release.url \
@@ -426,56 +525,92 @@ releases/$(VERSION)/mgmt-release.url: $(PKG_FEDORA-30) $(PKG_FEDORA-29) $(PKG_CE
|| rm -f releases/$(VERSION)/mgmt-release.url || rm -f releases/$(VERSION)/mgmt-release.url
releases/$(VERSION)/.mkdir: releases/$(VERSION)/.mkdir:
mkdir -p releases/$(VERSION)/{$(TOKEN_FEDORA-30),$(TOKEN_FEDORA-29),$(TOKEN_CENTOS-7),$(TOKEN_DEBIAN-10),$(TOKEN_UBUNTU-BIONIC),$(TOKEN_ARCHLINUX)}/ && touch releases/$(VERSION)/.mkdir mkdir -p \
` [ "$(TOKEN_BINARY_AMD64)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_BINARY_AMD64)/" ` \
` [ "$(TOKEN_BINARY_ARM64)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_BINARY_ARM64)/" ` \
` [ "$(TOKEN_FEDORA-LATEST)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_FEDORA-LATEST)/" ` \
` [ "$(TOKEN_FEDORA-OLDER)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_FEDORA-OLDER)/" ` \
` [ "$(TOKEN_STREAM-LATEST)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_STREAM-LATEST)/" ` \
` [ "$(TOKEN_DEBIAN-STABLE)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_DEBIAN-STABLE)/" ` \
` [ "$(TOKEN_UBUNTU-LATEST)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_UBUNTU-LATEST)/" ` \
` [ "$(TOKEN_ARCHLINUX)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_ARCHLINUX)/" ` \
&& touch releases/$(VERSION)/.mkdir
releases/$(VERSION)/$(TOKEN_FEDORA-30)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir # These are defined conditionally, since if the token is empty, they warn!
ifneq ($(TOKEN_BINARY_AMD64),)
$(PKG_BINARY_AMD64): build/mgmt-linux-amd64 releases/$(VERSION)/.mkdir
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; cp -a build/mgmt-linux-amd64 $(PKG_BINARY_AMD64)
endif
ifneq ($(TOKEN_BINARY_ARM64),)
$(PKG_BINARY_ARM64): build/mgmt-linux-arm64 releases/$(VERSION)/.mkdir
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; cp -a build/mgmt-linux-arm64 $(PKG_BINARY_ARM64)
endif
ifneq ($(TOKEN_FEDORA-LATEST),)
releases/$(VERSION)/$(TOKEN_FEDORA-LATEST)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..." @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION) @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
$(PKG_FEDORA-30): releases/$(VERSION)/$(TOKEN_FEDORA-30)/changelog $(PKG_FEDORA-LATEST): releases/$(VERSION)/$(TOKEN_FEDORA-LATEST)/changelog
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..." @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_FEDORA-30)" libvirt-devel augeas-devel @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_FEDORA-LATEST)" libvirt-devel augeas-devel
endif
releases/$(VERSION)/$(TOKEN_FEDORA-29)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir ifneq ($(TOKEN_FEDORA-OLDER),)
releases/$(VERSION)/$(TOKEN_FEDORA-OLDER)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..." @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION) @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
$(PKG_FEDORA-29): releases/$(VERSION)/$(TOKEN_FEDORA-29)/changelog $(PKG_FEDORA-OLDER): releases/$(VERSION)/$(TOKEN_FEDORA-OLDER)/changelog
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..." @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_FEDORA-29)" libvirt-devel augeas-devel @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_FEDORA-OLDER)" libvirt-devel augeas-devel
endif
releases/$(VERSION)/$(TOKEN_CENTOS-7)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir ifneq ($(TOKEN_STREAM-LATEST),)
releases/$(VERSION)/$(TOKEN_STREAM-LATEST)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..." @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION) @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
$(PKG_CENTOS-7): releases/$(VERSION)/$(TOKEN_CENTOS-7)/changelog $(PKG_STREAM-LATEST): releases/$(VERSION)/$(TOKEN_STREAM-LATEST)/changelog
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..." @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_CENTOS-7)" libvirt-devel augeas-devel @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_STREAM-LATEST)" libvirt-devel augeas-devel
endif
releases/$(VERSION)/$(TOKEN_DEBIAN-10)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir ifneq ($(TOKEN_DEBIAN-STABLE),)
releases/$(VERSION)/$(TOKEN_DEBIAN-STABLE)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..." @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-deb-changelog.sh "$${distro}" $(VERSION) @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-deb-changelog.sh "$${distro}" $(VERSION)
$(PKG_DEBIAN-10): releases/$(VERSION)/$(TOKEN_DEBIAN-10)/changelog $(PKG_DEBIAN-STABLE): releases/$(VERSION)/$(TOKEN_DEBIAN-STABLE)/changelog
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..." @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_DEBIAN-10)" libvirt-dev libaugeas-dev @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_DEBIAN-STABLE)" libvirt-dev libaugeas-dev
endif
releases/$(VERSION)/$(TOKEN_UBUNTU-BIONIC)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir ifneq ($(TOKEN_UBUNTU-LATEST),)
releases/$(VERSION)/$(TOKEN_UBUNTU-LATEST)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..." @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-deb-changelog.sh "$${distro}" $(VERSION) @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-deb-changelog.sh "$${distro}" $(VERSION)
$(PKG_UBUNTU-BIONIC): releases/$(VERSION)/$(TOKEN_UBUNTU-BIONIC)/changelog $(PKG_UBUNTU-LATEST): releases/$(VERSION)/$(TOKEN_UBUNTU-LATEST)/changelog
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..." @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_UBUNTU-BIONIC)" libvirt-dev libaugeas-dev @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_UBUNTU-LATEST)" libvirt-dev libaugeas-dev
endif
ifneq ($(TOKEN_ARCHLINUX),)
$(PKG_ARCHLINUX): $(PROGRAM) releases/$(VERSION)/.mkdir $(PKG_ARCHLINUX): $(PROGRAM) releases/$(VERSION)/.mkdir
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..." @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_ARCHLINUX)" libvirt augeas @title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_ARCHLINUX)" libvirt augeas
endif
$(SHA256SUMS): $(PKG_FEDORA-30) $(PKG_FEDORA-29) $(PKG_CENTOS-7) $(PKG_DEBIAN-10) $(PKG_UBUNTU-BIONIC) $(PKG_ARCHLINUX) $(SHA256SUMS): $(DEP_BINARY_AMD64) $(DEP_BINARY_ARM64) $(DEP_FEDORA-LATEST) $(DEP_FEDORA-OLDER) $(DEP_STREAM-LATEST) $(DEP_DEBIAN-STABLE) $(DEP_UBUNTU-LATEST) $(DEP_ARCHLINUX)
@# remove the directory separator in the SHA256SUMS file @# remove the directory separator in the SHA256SUMS file
@echo "Generating: sha256 sum..." @echo "Generating: sha256 sum..."
sha256sum $(PKG_FEDORA-30) $(PKG_FEDORA-29) $(PKG_CENTOS-7) $(PKG_DEBIAN-10) $(PKG_UBUNTU-BIONIC) $(PKG_ARCHLINUX) | awk -F '/| ' '{print $$1" "$$6}' > $(SHA256SUMS) sha256sum \
` [ -e $(PKG_BINARY_AMD64) ] && printf -- "$(PKG_BINARY_AMD64)" ` \
` [ -e $(PKG_BINARY_ARM64) ] && printf -- "$(PKG_BINARY_ARM64)" ` \
` [ -e $(PKG_FEDORA-LATEST) ] && printf -- "$(PKG_FEDORA-LATEST)" ` \
` [ -e $(PKG_FEDORA-OLDER) ] && printf -- "$(PKG_FEDORA-OLDER)" ` \
` [ -e $(PKG_STREAM-LATEST) ] && printf -- "$(PKG_STREAM-LATEST)" ` \
` [ -e $(PKG_DEBIAN-STABLE) ] && printf -- "$(PKG_DEBIAN-STABLE)" ` \
` [ -e $(PKG_UBUNTU-LATEST) ] && printf -- "$(PKG_UBUNTU-LATEST)" ` \
` [ -e $(PKG_ARCHLINUX) ] && printf -- "$(PKG_ARCHLINUX)" ` \
| awk -F '/| ' '{print $$1" "$$6}' > $(SHA256SUMS)
$(SHA256SUMS_ASC): $(SHA256SUMS) $(SHA256SUMS_ASC): $(SHA256SUMS)
@echo "Signing sha256 sum..." @echo "Signing sha256 sum..."
@@ -501,9 +636,9 @@ help: ## show this help screen
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
@echo '' @echo ''
funcgen: lang/funcs/core/generated_funcs.go funcgen: lang/core/generated_funcs.go
lang/funcs/core/generated_funcs.go: lang/funcs/funcgen/*.go lang/funcs/core/funcgen.yaml lang/funcs/funcgen/templates/generated_funcs.go.tpl lang/core/generated_funcs.go: lang/funcs/funcgen/*.go lang/core/funcgen.yaml lang/funcs/funcgen/templates/generated_funcs.go.tpl
@echo "Generating: funcs..." @echo "Generating: funcs..."
@go run `find lang/funcs/funcgen/ -maxdepth 1 -type f -name '*.go' -not -name '*_test.go'` -templates=lang/funcs/funcgen/templates/generated_funcs.go.tpl >/dev/null @go run `find lang/funcs/funcgen/ -maxdepth 1 -type f -name '*.go' -not -name '*_test.go'` -templates=lang/funcs/funcgen/templates/generated_funcs.go.tpl >/dev/null

View File

@@ -3,9 +3,9 @@
[![mgmt!](art/mgmt.png)](art/) [![mgmt!](art/mgmt.png)](art/)
[![Go Report Card](https://goreportcard.com/badge/github.com/purpleidea/mgmt?style=flat-square)](https://goreportcard.com/report/github.com/purpleidea/mgmt) [![Go Report Card](https://goreportcard.com/badge/github.com/purpleidea/mgmt?style=flat-square)](https://goreportcard.com/report/github.com/purpleidea/mgmt)
[![Build Status](https://img.shields.io/travis/purpleidea/mgmt/master.svg?style=flat-square)](http://travis-ci.org/purpleidea/mgmt)
[![Build Status](https://github.com/purpleidea/mgmt/workflows/.github/workflows/test.yaml/badge.svg)](https://github.com/purpleidea/mgmt/actions/) [![Build Status](https://github.com/purpleidea/mgmt/workflows/.github/workflows/test.yaml/badge.svg)](https://github.com/purpleidea/mgmt/actions/)
[![GoDoc](https://img.shields.io/badge/godoc-reference-5272B4.svg?style=flat-square)](https://godoc.org/github.com/purpleidea/mgmt) [![GoDoc](https://img.shields.io/badge/godoc-reference-5272B4.svg?style=flat-square)](https://godocs.io/github.com/purpleidea/mgmt)
[![Matrix](https://img.shields.io/badge/matrix-%23mgmtconfig-orange.svg?style=flat-square)](https://matrix.to/#/#mgmtconfig:matrix.org)
[![IRC](https://img.shields.io/badge/irc-%23mgmtconfig-orange.svg?style=flat-square)](https://web.libera.chat/?channels=#mgmtconfig) [![IRC](https://img.shields.io/badge/irc-%23mgmtconfig-orange.svg?style=flat-square)](https://web.libera.chat/?channels=#mgmtconfig)
[![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg?style=flat-square)](https://www.patreon.com/purpleidea) [![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg?style=flat-square)](https://www.patreon.com/purpleidea)
[![Liberapay](https://img.shields.io/badge/liberapay-donate-yellow.svg?style=flat-square)](https://liberapay.com/purpleidea/donate) [![Liberapay](https://img.shields.io/badge/liberapay-donate-yellow.svg?style=flat-square)](https://liberapay.com/purpleidea/donate)
@@ -66,11 +66,11 @@ Come join us in the `mgmt` community!
| Medium | Link | | Medium | Link |
|---|---| |---|---|
| Matrix | [#mgmtconfig](https://matrix.to/#/#mgmtconfig:matrix.org) on Matrix.org |
| IRC | [#mgmtconfig](https://web.libera.chat/?channels=#mgmtconfig) on Libera.Chat | | IRC | [#mgmtconfig](https://web.libera.chat/?channels=#mgmtconfig) on Libera.Chat |
| Twitter | [@mgmtconfig](https://twitter.com/mgmtconfig) & [#mgmtconfig](https://twitter.com/hashtag/mgmtconfig) | | Twitter | [@mgmtconfig](https://twitter.com/mgmtconfig) & [#mgmtconfig](https://twitter.com/hashtag/mgmtconfig) |
| Mailing list | [mgmtconfig-list@redhat.com](https://www.redhat.com/mailman/listinfo/mgmtconfig-list) | | Mailing list | [looking for a new home, suggestions welcome](https://gitlab.freedesktop.org/freedesktop/freedesktop/-/issues/1082) |
| Patreon | [purpleidea](https://www.patreon.com/purpleidea) on Patreon | | Patreon | [purpleidea](https://www.patreon.com/purpleidea) on Patreon |
| Liberapay | [purpleidea](https://liberapay.com/purpleidea/donate) on Liberapay |
## Status: ## Status:
@@ -98,8 +98,9 @@ Please read, enjoy and help improve our documentation!
| [style guide](docs/style-guide.md) | for mgmt developers | | [style guide](docs/style-guide.md) | for mgmt developers |
| [godoc API reference](https://godoc.org/github.com/purpleidea/mgmt) | for mgmt developers | | [godoc API reference](https://godoc.org/github.com/purpleidea/mgmt) | for mgmt developers |
| [prometheus guide](docs/prometheus.md) | for everyone | | [prometheus guide](docs/prometheus.md) | for everyone |
| [puppet guide](docs/puppet-guide.md) | for puppet sysadmins |
| [development](docs/development.md) | for mgmt developers | | [development](docs/development.md) | for mgmt developers |
| [videos](docs/on-the-web.md) | for everyone |
| [blogs](docs/on-the-web.md) | for everyone |
## Questions: ## Questions:
@@ -128,6 +129,6 @@ We'd love to have your patches! Please send them by email, or as a pull request.
## On the web: ## On the web:
[Read what people are saying and publishing about mgmt!](docs/on-the-web.md) [Blog posts and recorded talks about mgmt are listed here!](docs/on-the-web.md)
Happy hacking! Happy hacking!

View File

@@ -1,42 +0,0 @@
# Mgmt
# Copyright (C) 2013-2021+ 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/>.
# The bindata target generates go files from any source defined below. To use
# the files, import the generated "bindata" package and use:
# `bytes, err := bindata.Asset("FILEPATH")`
# where FILEPATH is the path of the original input file relative to `bindata/`.
# To get a list of files stored in this "bindata" package, you can use:
# `paths := bindata.AssetNames()` and `paths, err := bindata.AssetDir(name)`
# to get a list of files with a directory prefix.
.PHONY: build clean
default: build
build: bindata.go
# add more input files as dependencies at the end here...
bindata.go: ../COPYING
@echo "Generating: bindata..."
# go-bindata --pkg bindata -o <OUTPUT> <INPUT>
go-bindata --pkg bindata -o ./$@ $^
# gofmt the output file
gofmt -s -w $@
@ROOT=$$(dirname "$${BASH_SOURCE}")/.. && $$ROOT/misc/header.sh '$@'
clean:
# remove generated bindata.go
@ROOT=$$(dirname "$${BASH_SOURCE}")/.. && rm -f bindata.go

165
cli/cli.go Normal file
View File

@@ -0,0 +1,165 @@
// Mgmt
// Copyright (C) 2013-2024+ 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// Package cli handles all of the core command line parsing. It's the first
// entry point after the real main function, and it imports and runs our core
// "lib".
package cli
import (
"context"
"fmt"
"os"
cliUtil "github.com/purpleidea/mgmt/cli/util"
"github.com/purpleidea/mgmt/util/errwrap"
"github.com/alexflint/go-arg"
)
func init() {
if _, err := arg.NewParser(arg.Config{}, &Args{}); err != nil { // sanity check
panic(errwrap.Wrapf(err, "invalid args cli struct"))
}
}
// CLI is the entry point for using mgmt normally from the CLI.
func CLI(ctx context.Context, data *cliUtil.Data) error {
// test for sanity
if data == nil {
return fmt.Errorf("this CLI was not run correctly")
}
if data.Program == "" || data.Version == "" {
return fmt.Errorf("program was not compiled correctly")
}
if data.Copying == "" {
return fmt.Errorf("program copyrights were removed, can't run")
}
args := Args{}
args.version = data.Version // copy this in
args.description = data.Tagline
config := arg.Config{
Program: data.Program,
}
parser, err := arg.NewParser(config, &args)
if err != nil {
// programming error
return errwrap.Wrapf(err, "cli config error")
}
err = parser.Parse(data.Args[1:]) // XXX: args[0] needs to be dropped
if err == arg.ErrHelp {
parser.WriteHelp(os.Stdout)
return nil
}
if err == arg.ErrVersion {
fmt.Printf("%s\n", data.Version) // byon: bring your own newline
return nil
}
if err != nil {
//parser.WriteHelp(os.Stdout) // TODO: is doing this helpful?
return cliUtil.CliParseError(err) // consistent errors
}
// display the license
if args.License {
fmt.Printf("%s", data.Copying) // file comes with a trailing nl
return nil
}
if ok, err := args.Run(ctx, data); err != nil {
return err
} else if ok { // did we activate one of the commands?
return nil
}
// print help if no subcommands are set
parser.WriteHelp(os.Stdout)
return nil
}
// Args is the CLI parsing structure and type of the parsed result. This
// particular struct is the top-most one.
type Args struct {
// XXX: We cannot have both subcommands and a positional argument.
// XXX: I think it's a bug of this library that it can't handle argv[0].
//Argv0 string `arg:"positional"`
License bool `arg:"--license" help:"display the license and exit"`
RunCmd *RunArgs `arg:"subcommand:run" help:"run code on this machine"`
DeployCmd *DeployArgs `arg:"subcommand:deploy" help:"deploy code into a cluster"`
// This never runs, it gets preempted in the real main() function.
// XXX: Can we do it nicely with the new arg parser? can it ignore all args?
EtcdCmd *EtcdArgs `arg:"subcommand:etcd" help:"run standalone etcd"`
// version is a private handle for our version string.
version string `arg:"-"` // ignored from parsing
// description is a private handle for our description string.
description string `arg:"-"` // ignored from parsing
}
// Version returns the version string. Implementing this signature is part of
// the API for the cli library.
func (obj *Args) Version() string {
return obj.version
}
// Description returns a description string. Implementing this signature is part
// of the API for the cli library.
func (obj *Args) Description() string {
return obj.description
}
// Run executes the correct subcommand. It errors if there's ever an error. It
// returns true if we did activate one of the subcommands. It returns false if
// we did not. This information is used so that the top-level parser can return
// usage or help information if no subcommand activates.
func (obj *Args) Run(ctx context.Context, data *cliUtil.Data) (bool, error) {
if cmd := obj.RunCmd; cmd != nil {
return cmd.Run(ctx, data)
}
if cmd := obj.DeployCmd; cmd != nil {
return cmd.Run(ctx, data)
}
// NOTE: we could return true, fmt.Errorf("...") if more than one did
return false, nil // nobody activated
}
// EtcdArgs is the CLI parsing structure and type of the parsed result. This
// particular one is empty because the `etcd` subcommand is preempted in the
// real main() function.
type EtcdArgs struct{}

255
cli/deploy.go Normal file
View File

@@ -0,0 +1,255 @@
// Mgmt
// Copyright (C) 2013-2024+ 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package cli
import (
"context"
"fmt"
"os"
"os/signal"
cliUtil "github.com/purpleidea/mgmt/cli/util"
"github.com/purpleidea/mgmt/etcd/client"
"github.com/purpleidea/mgmt/etcd/deployer"
etcdfs "github.com/purpleidea/mgmt/etcd/fs"
"github.com/purpleidea/mgmt/gapi"
"github.com/purpleidea/mgmt/lib"
"github.com/purpleidea/mgmt/util"
"github.com/purpleidea/mgmt/util/errwrap"
git "github.com/go-git/go-git/v5"
"github.com/google/uuid"
)
// DeployArgs is the CLI parsing structure and type of the parsed result. This
// particular one contains all the common flags for the `deploy` subcommand
// which all frontends can use.
type DeployArgs struct {
Seeds []string `arg:"--seeds,env:MGMT_SEEDS" help:"default etc client endpoint"`
Noop bool `arg:"--noop" help:"globally force all resources into no-op mode"`
Sema int `arg:"--sema" default:"-1" help:"globally add a semaphore to all resources with this lock count"`
NoGit bool `arg:"--no-git" help:"don't look at git commit id for safe deploys"`
Force bool `arg:"--force" help:"force a new deploy, even if the safety chain would break"`
DeployEmpty *cliUtil.EmptyArgs `arg:"subcommand:empty" help:"deploy empty payload"`
DeployLang *cliUtil.LangArgs `arg:"subcommand:lang" help:"deploy lang (mcl) payload"`
DeployYaml *cliUtil.YamlArgs `arg:"subcommand:yaml" help:"deploy yaml graph payload"`
}
// Run executes the correct subcommand. It errors if there's ever an error. It
// returns true if we did activate one of the subcommands. It returns false if
// we did not. This information is used so that the top-level parser can return
// usage or help information if no subcommand activates. This particular Run is
// the run for the main `deploy` subcommand. This always requires a frontend to
// deploy to the cluster, but if you don't want a graph, you can use the `empty`
// frontend. The engine backend is agnostic to which frontend is deployed, in
// fact, you can deploy with multiple different frontends, one after another, on
// the same engine.
func (obj *DeployArgs) Run(ctx context.Context, data *cliUtil.Data) (bool, error) {
var name string
var args interface{}
if cmd := obj.DeployEmpty; cmd != nil {
name = cliUtil.LookupSubcommand(obj, cmd) // "empty"
args = cmd
}
if cmd := obj.DeployLang; cmd != nil {
name = cliUtil.LookupSubcommand(obj, cmd) // "lang"
args = cmd
}
if cmd := obj.DeployYaml; cmd != nil {
name = cliUtil.LookupSubcommand(obj, cmd) // "yaml"
args = cmd
}
// XXX: workaround https://github.com/alexflint/go-arg/issues/239
gapiNames := gapi.Names() // list of registered names
if l := len(obj.Seeds); name == "" && l > 1 {
elem := obj.Seeds[l-2] // second to last element
if util.StrInList(elem, gapiNames) {
return false, cliUtil.CliParseError(cliUtil.MissingEquals) // consistent errors
}
}
fn, exists := gapi.RegisteredGAPIs[name]
if !exists {
return false, nil // did not activate
}
gapiObj := fn()
program, version := data.Program, data.Version
Logf := func(format string, v ...interface{}) {
data.Flags.Logf("deploy: "+format, v...)
}
// TODO: consider adding a timeout based on an args.Timeout flag ?
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
defer cancel()
cliUtil.Hello(program, version, data.Flags) // say hello!
defer Logf("goodbye!")
var hash, pHash string
if !obj.NoGit {
wd, err := os.Getwd()
if err != nil {
return false, errwrap.Wrapf(err, "could not get current working directory")
}
repo, err := git.PlainOpen(wd)
if err != nil {
return false, errwrap.Wrapf(err, "could not open git repo")
}
head, err := repo.Head()
if err != nil {
return false, errwrap.Wrapf(err, "could not read git HEAD")
}
hash = head.Hash().String() // current commit id
Logf("hash: %s", hash)
lo := &git.LogOptions{
From: head.Hash(),
}
commits, err := repo.Log(lo)
if err != nil {
return false, errwrap.Wrapf(err, "could not read git log")
}
if _, err := commits.Next(); err != nil { // skip over HEAD
return false, errwrap.Wrapf(err, "could not read HEAD in git log") // weird!
}
commit, err := commits.Next()
if err == nil { // errors are okay, we might be empty
pHash = commit.Hash.String() // previous commit id
}
Logf("previous deploy hash: %s", pHash)
if obj.Force {
pHash = "" // don't check this :(
}
if hash == "" {
return false, errwrap.Wrapf(err, "could not get git deploy hash")
}
}
uniqueid := uuid.New() // panic's if it can't generate one :P
etcdClient := client.NewClientFromSeedsNamespace(
obj.Seeds, // endpoints
lib.NS,
)
if err := etcdClient.Init(); err != nil {
return false, errwrap.Wrapf(err, "client Init failed")
}
defer func() {
err := errwrap.Wrapf(etcdClient.Close(), "client Close failed")
if err != nil {
// TODO: cause the final exit code to be non-zero
Logf("client cleanup error: %+v", err)
}
}()
simpleDeploy := &deployer.SimpleDeploy{
Client: etcdClient,
Debug: data.Flags.Debug,
Logf: func(format string, v ...interface{}) {
Logf("deploy: "+format, v...)
},
}
if err := simpleDeploy.Init(); err != nil {
return false, errwrap.Wrapf(err, "deploy Init failed")
}
defer func() {
err := errwrap.Wrapf(simpleDeploy.Close(), "deploy Close failed")
if err != nil {
// TODO: cause the final exit code to be non-zero
Logf("deploy cleanup error: %+v", err)
}
}()
// get max id (from all the previous deploys)
max, err := simpleDeploy.GetMaxDeployID(ctx)
if err != nil {
return false, errwrap.Wrapf(err, "error getting max deploy id")
}
// find the latest id
var id = max + 1 // next id
Logf("previous max deploy id: %d", max)
etcdFs := &etcdfs.Fs{
Client: etcdClient,
// TODO: using a uuid is meant as a temporary measure, i hate them
Metadata: lib.MetadataPrefix + fmt.Sprintf("/deploy/%d-%s", id, uniqueid),
DataPrefix: lib.StoragePrefix,
Debug: data.Flags.Debug,
Logf: func(format string, v ...interface{}) {
Logf("fs: "+format, v...)
},
}
info := &gapi.Info{
Args: args,
Flags: &gapi.Flags{
Noop: obj.Noop,
Sema: obj.Sema,
//Update: obj.Update,
},
Fs: etcdFs,
Debug: data.Flags.Debug,
Logf: func(format string, v ...interface{}) {
// TODO: is this a sane prefix to use here?
data.Flags.Logf("cli: "+format, v...)
},
}
deploy, err := gapiObj.Cli(info)
if err != nil {
return false, cliUtil.CliParseError(err) // consistent errors
}
if deploy == nil { // not used
return false, fmt.Errorf("not enough information specified")
}
// redundant
deploy.Noop = obj.Noop
deploy.Sema = obj.Sema
str, err := deploy.ToB64()
if err != nil {
return false, errwrap.Wrapf(err, "encoding error")
}
// this nominally checks the previous git hash matches our expectation
if err := simpleDeploy.AddDeploy(ctx, id, hash, pHash, &str); err != nil {
return false, errwrap.Wrapf(err, "could not create deploy id `%d`", id)
}
Logf("success, id: %d", id)
return true, nil
}

233
cli/run.go Normal file
View File

@@ -0,0 +1,233 @@
// Mgmt
// Copyright (C) 2013-2024+ 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package cli
import (
"context"
"fmt"
"os"
"os/signal"
"sync"
"syscall"
cliUtil "github.com/purpleidea/mgmt/cli/util"
"github.com/purpleidea/mgmt/gapi"
"github.com/purpleidea/mgmt/lib"
"github.com/purpleidea/mgmt/util"
"github.com/purpleidea/mgmt/util/errwrap"
"github.com/spf13/afero"
)
// RunArgs is the CLI parsing structure and type of the parsed result. This
// particular one contains all the common flags for the `run` subcommand which
// all frontends can use.
type RunArgs struct {
lib.Config // embedded config (can't be a pointer) https://github.com/alexflint/go-arg/issues/240
RunEmpty *cliUtil.EmptyArgs `arg:"subcommand:empty" help:"run empty payload"`
RunLang *cliUtil.LangArgs `arg:"subcommand:lang" help:"run lang (mcl) payload"`
RunYaml *cliUtil.YamlArgs `arg:"subcommand:yaml" help:"run yaml graph payload"`
}
// Run executes the correct subcommand. It errors if there's ever an error. It
// returns true if we did activate one of the subcommands. It returns false if
// we did not. This information is used so that the top-level parser can return
// usage or help information if no subcommand activates. This particular Run is
// the run for the main `run` subcommand. This always requires a frontend to
// start the engine, but if you don't want a graph, you can use the `empty`
// frontend. The engine backend is agnostic to which frontend is running, in
// fact, you can deploy with multiple different frontends, one after another, on
// the same engine.
func (obj *RunArgs) Run(ctx context.Context, data *cliUtil.Data) (bool, error) {
var name string
var args interface{}
if cmd := obj.RunEmpty; cmd != nil {
name = cliUtil.LookupSubcommand(obj, cmd) // "empty"
args = cmd
}
if cmd := obj.RunLang; cmd != nil {
name = cliUtil.LookupSubcommand(obj, cmd) // "lang"
args = cmd
}
if cmd := obj.RunYaml; cmd != nil {
name = cliUtil.LookupSubcommand(obj, cmd) // "yaml"
args = cmd
}
// XXX: workaround https://github.com/alexflint/go-arg/issues/239
lists := [][]string{
obj.Seeds,
obj.ClientURLs,
obj.ServerURLs,
obj.AdvertiseClientURLs,
obj.AdvertiseServerURLs,
}
gapiNames := gapi.Names() // list of registered names
for _, list := range lists {
if l := len(list); name == "" && l > 1 {
elem := list[l-2] // second to last element
if util.StrInList(elem, gapiNames) {
return false, cliUtil.CliParseError(cliUtil.MissingEquals) // consistent errors
}
}
}
fn, exists := gapi.RegisteredGAPIs[name]
if !exists {
return false, nil // did not activate
}
gapiObj := fn()
main := &lib.Main{}
main.Config = &obj.Config // pass in all the parsed data
main.Program, main.Version = data.Program, data.Version
main.Debug, main.Logf = data.Flags.Debug, data.Flags.Logf // no prefix
Logf := func(format string, v ...interface{}) {
data.Flags.Logf("main: "+format, v...)
}
cliUtil.Hello(main.Program, main.Version, data.Flags) // say hello!
defer Logf("goodbye!")
// create a memory backed temporary filesystem for storing runtime data
mmFs := afero.NewMemMapFs()
afs := &afero.Afero{Fs: mmFs} // wrap so that we're implementing ioutil
standaloneFs := &util.AferoFs{Afero: afs}
main.DeployFs = standaloneFs
info := &gapi.Info{
Args: args,
Flags: &gapi.Flags{
Hostname: obj.Hostname,
Noop: obj.Noop,
Sema: obj.Sema,
//Update: obj.Update,
},
Fs: standaloneFs,
Debug: data.Flags.Debug,
Logf: func(format string, v ...interface{}) {
data.Flags.Logf("cli: "+format, v...)
},
}
deploy, err := gapiObj.Cli(info)
if err != nil {
return false, cliUtil.CliParseError(err) // consistent errors
}
if cmd := obj.RunLang; cmd != nil && cmd.OnlyUnify && deploy == nil {
return true, nil // we end early
}
if cmd := obj.RunLang; cmd != nil && cmd.OnlyDownload && deploy == nil {
return true, nil // we end early
}
main.Deploy = deploy
if main.Deploy == nil {
// nobody activated, but we'll still watch the etcd deploy chan,
// and if there is deployed code that's ready to run, we'll run!
data.Flags.Logf("main: no frontend selected (no GAPI activated)")
}
if err := main.Validate(); err != nil {
return false, err
}
if err := main.Init(); err != nil {
return false, err
}
// install the exit signal handler
wg := &sync.WaitGroup{}
defer wg.Wait()
exit := make(chan struct{})
defer close(exit)
wg.Add(1)
go func() {
defer wg.Done()
// must have buffer for max number of signals
signals := make(chan os.Signal, 3+1) // 3 * ^C + 1 * SIGTERM
signal.Notify(signals, os.Interrupt) // catch ^C
//signal.Notify(signals, os.Kill) // catch signals
signal.Notify(signals, syscall.SIGTERM)
var count uint8
for {
select {
case sig := <-signals: // any signal will do
if sig != os.Interrupt {
data.Flags.Logf("interrupted by signal")
main.Interrupt(fmt.Errorf("killed by %v", sig))
return
}
switch count {
case 0:
data.Flags.Logf("interrupted by ^C")
main.Exit(nil)
case 1:
data.Flags.Logf("interrupted by ^C (fast pause)")
main.FastExit(nil)
case 2:
data.Flags.Logf("interrupted by ^C (hard interrupt)")
main.Interrupt(nil)
}
count++
case <-exit:
return
}
}
}()
reterr := main.Run()
if reterr != nil {
// log the error message returned
if data.Flags.Debug {
data.Flags.Logf("main: %+v", reterr)
}
}
if err := main.Close(); err != nil {
if data.Flags.Debug {
data.Flags.Logf("main: Close: %+v", err)
}
if reterr == nil {
return false, err
}
reterr = errwrap.Append(reterr, err)
}
if reterr != nil {
return false, reterr
}
return true, nil
}

102
cli/util/args.go Normal file
View File

@@ -0,0 +1,102 @@
// Mgmt
// Copyright (C) 2013-2024+ 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package util
import (
"reflect"
"strings"
)
// LookupSubcommand returns the name of the subcommand in the obj, of a struct.
// This is useful for determining the name of the subcommand that was activated.
// It returns an empty string if a specific name was not found.
func LookupSubcommand(obj interface{}, st interface{}) string {
val := reflect.ValueOf(obj)
if val.Kind() == reflect.Ptr { // max one de-referencing
val = val.Elem()
}
v := reflect.ValueOf(st) // value of the struct
typ := val.Type()
for i := 0; i < typ.NumField(); i++ {
f := val.Field(i) // value of the field
if f.Interface() != v.Interface() {
continue
}
field := typ.Field(i)
alias, ok := field.Tag.Lookup("arg")
if !ok {
continue
}
// XXX: `arg` needs a split by comma first or fancier parsing
prefix := "subcommand"
split := strings.Split(alias, ":")
if len(split) != 2 || split[0] != prefix {
continue
}
return split[1] // found
}
return "" // not found
}
// EmptyArgs is the empty CLI parsing structure and type of the parsed result.
type EmptyArgs struct{}
// LangArgs is the lang CLI parsing structure and type of the parsed result.
type LangArgs struct {
// Input is the input mcl code or file path or any input specification.
Input string `arg:"positional,required"`
// TODO: removed (temporarily?)
//Stdin bool `arg:"--stdin" help:"use passthrough stdin"`
Download bool `arg:"--download" help:"download any missing imports"`
OnlyDownload bool `arg:"--only-download" help:"stop after downloading any missing imports"`
Update bool `arg:"--update" help:"update all dependencies to the latest versions"`
OnlyUnify bool `arg:"--only-unify" help:"stop after type unification"`
SkipUnify bool `arg:"--skip-unify" help:"skip type unification"`
Depth int `arg:"--depth" default:"-1" help:"max recursion depth limit (-1 is unlimited)"`
// The default of 0 means any error is a failure by default.
Retry int `arg:"--depth" help:"max number of retries (-1 is unlimited)"`
ModulePath string `arg:"--module-path,env:MGMT_MODULE_PATH" help:"choose the modules path (absolute)"`
}
// YamlArgs is the yaml CLI parsing structure and type of the parsed result.
type YamlArgs struct {
// Input is the input yaml code or file path or any input specification.
Input string `arg:"positional,required"`
}

47
cli/util/hello.go Normal file
View File

@@ -0,0 +1,47 @@
// Mgmt
// Copyright (C) 2013-2024+ 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package util
import (
"fmt"
"time"
)
// Hello is a simple helper function to print a hello message and time.
func Hello(program, version string, flags Flags) {
var start = time.Now().UnixNano()
if program == "" {
program = "<unknown>"
}
fmt.Println(fmt.Sprintf("This is: %s, version: %s", program, version))
fmt.Println("Copyright (C) 2013-2024+ James Shubin and the project contributors")
fmt.Println("Written by James Shubin <james@shubin.ca> and the project contributors")
flags.Logf("main: start: %v", start)
}

98
cli/util/util.go Normal file
View File

@@ -0,0 +1,98 @@
// Mgmt
// Copyright (C) 2013-2024+ 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// Package util has some CLI related utility code.
package util
import (
"log"
"os"
"strings"
"github.com/purpleidea/mgmt/util/errwrap"
)
// Error is a constant error type that implements error.
type Error string
// Error fulfills the error interface of this type.
func (e Error) Error() string { return string(e) }
const (
// MissingEquals means we probably hit the parsing bug.
// XXX: see: https://github.com/alexflint/go-arg/issues/239
MissingEquals = Error("missing equals sign for list element")
)
// CliParseError returns a consistent error if we have a CLI parsing issue.
func CliParseError(err error) error {
return errwrap.Wrapf(err, "cli parse error")
}
// Flags are some constant flags which are used throughout the program.
type Flags struct {
Debug bool // add additional log messages
Logf func(format string, v ...interface{})
}
// Data is a struct of values that we usually pass to the main CLI function.
type Data struct {
Program string
Version string
Copying string
Tagline string
Flags Flags
Args []string // os.Args usually
}
// SafeProgram returns the correct program string when given a buggy variant.
func SafeProgram(program string) string {
// FIXME: in sub commands, the cli package appends a space and the sub
// command name at the end. hack around this by only using the first bit
// see: https://github.com/urfave/cli/issues/783 for more details...
split := strings.Split(program, " ")
program = split[0]
//if program == "" {
// program = "<unknown>"
//}
return program
}
// LogSetup changes some of the core logger package settings.
func LogSetup(debug bool) {
// TODO: Move these log package initialization steps to the top main.go?
logFlags := log.LstdFlags
if debug {
logFlags = logFlags + log.Lshortfile
}
logFlags = logFlags - log.Ldate // remove the date for now
log.SetFlags(logFlags)
log.SetOutput(os.Stderr)
}

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// Package converger is a facility for reporting the converged state. // Package converger is a facility for reporting the converged state.
package converger package converger
@@ -29,7 +41,7 @@ import (
) )
// New builds a new converger coordinator. // New builds a new converger coordinator.
func New(timeout int64) *Coordinator { func New(timeout int) *Coordinator {
return &Coordinator{ return &Coordinator{
timeout: timeout, timeout: timeout,
@@ -61,7 +73,7 @@ func New(timeout int64) *Coordinator {
type Coordinator struct { type Coordinator struct {
// timeout must be zero (instant) or greater seconds to run. If it's -1 // timeout must be zero (instant) or greater seconds to run. If it's -1
// then this is disabled, and we never run stateFns. // then this is disabled, and we never run stateFns.
timeout int64 timeout int
// mutex is used for controlling access to status and lastid. // mutex is used for controlling access to status and lastid.
mutex *sync.RWMutex mutex *sync.RWMutex
@@ -365,7 +377,7 @@ func (obj *Coordinator) Status() map[*UID]bool {
// Timeout returns the timeout in seconds that converger was created with. This // Timeout returns the timeout in seconds that converger was created with. This
// is useful to avoid passing in the timeout value separately when you're // is useful to avoid passing in the timeout value separately when you're
// already passing in the Coordinator struct. // already passing in the Coordinator struct.
func (obj *Coordinator) Timeout() int64 { func (obj *Coordinator) Timeout() int {
return obj.timeout return obj.timeout
} }
@@ -375,7 +387,7 @@ func (obj *Coordinator) Timeout() int64 {
type UID struct { type UID struct {
// timeout is a copy of the main timeout. It could eventually be used // timeout is a copy of the main timeout. It could eventually be used
// for per-UID timeouts too. // for per-UID timeouts too.
timeout int64 timeout int
// isConverged stores the convergence state of this particular UID. // isConverged stores the convergence state of this particular UID.
isConverged bool isConverged bool

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,9 +13,21 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// +build !root //go:build !root
package converger package converger

14
debian/copyright vendored
View File

@@ -3,7 +3,7 @@ Upstream-Name: mgmt
Source: <https://github.com/purpleidea/mgmt> Source: <https://github.com/purpleidea/mgmt>
Files: * Files: *
Copyright: Copyright (C) 2013-2021+ James Shubin and the project contributors Copyright: Copyright (C) 2013-2024+ James Shubin and the project contributors
License: GPL-3.0 License: GPL-3.0
License: GPL-3.0 License: GPL-3.0
@@ -19,3 +19,15 @@ License: GPL-3.0
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
Additional permission under GNU GPL version 3 section 7
If you modify this program, or any covered work, by linking or combining it
with embedded mcl code and modules (and that the embedded mcl code and
modules which link with this program, contain a copy of their source code in
the authoritative form) containing parts covered by the terms of any other
license, the licensors of this program grant you additional permission to
convey the resulting work. Furthermore, the licensors of this program grant
the original author, James Shubin, additional permission to update this
additional permission if he deems it necessary to achieve the goals of this
additional permission.

16
doc.go
View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// Package main provides the main entrypoint for using the `mgmt` software. // Package main provides the main entrypoint for using the `mgmt` software.
package main package main

View File

@@ -1,4 +1,4 @@
FROM golang:1.13 FROM golang:1.20
MAINTAINER Michał Czeraszkiewicz <contact@czerasz.com> MAINTAINER Michał Czeraszkiewicz <contact@czerasz.com>

View File

@@ -0,0 +1,20 @@
FROM fedora:38
LABEL org.opencontainers.image.authors="laurent.indermuehle@pm.me"
ENV GOPATH=/root/gopath
ENV PATH=/root/gopath/bin:/usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/go/bin:/usr/local/bin
ENV LD_LIBRARY_PATH=/usr/lib64
ENV PKG_CONFIG_PATH=/usr/lib64/pkgconfig
# This forces make-deps.sh to install Ragel 6.1 instead of 7.0
ENV DOCKER=true
RUN dnf -y install wget unzip git make which gcc gcc-c++ ruby golang
RUN mkdir -p $GOPATH/src/github.com/purpleidea
WORKDIR $GOPATH/src/github.com/purpleidea
RUN git clone --recursive https://github.com/purpleidea/mgmt mgmt
WORKDIR $GOPATH/src/github.com/purpleidea/mgmt
RUN make deps
RUN make build
CMD ["/bin/bash"]

View File

@@ -6,7 +6,7 @@ ENV PATH=/opt/rh/rh-ruby22/root/usr/bin:/root/gopath/bin:/usr/local/sbin:/sbin:/
ENV LD_LIBRARY_PATH=/opt/rh/rh-ruby22/root/usr/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} ENV LD_LIBRARY_PATH=/opt/rh/rh-ruby22/root/usr/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
ENV PKG_CONFIG_PATH=/opt/rh/rh-ruby22/root/usr/lib64/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}} ENV PKG_CONFIG_PATH=/opt/rh/rh-ruby22/root/usr/lib64/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}
RUN yum -y install epel-release wget unzip git make which centos-release-scl gcc && sed -i "s/enabled=0/enabled=1/" /etc/yum.repos.d/epel-testing.repo && yum -y install rh-ruby22 && wget -O /opt/go1.9.1.linux-amd64.tar.gz https://storage.googleapis.com/golang/go1.9.1.linux-amd64.tar.gz && tar -C /usr/local -xzf /opt/go1.9.1.linux-amd64.tar.gz RUN yum -y install epel-release wget unzip git make which centos-release-scl gcc && sed -i "s/enabled=0/enabled=1/" /etc/yum.repos.d/epel-testing.repo && yum -y install rh-ruby22 && wget -O /opt/go1.20.11.linux-amd64.tar.gz https://storage.googleapis.com/golang/go1.20.11.linux-amd64.tar.gz && tar -C /usr/local -xzf /opt/go1.20.11.linux-amd64.tar.gz
RUN mkdir -p $GOPATH/src/github.com/purpleidea && cd $GOPATH/src/github.com/purpleidea && git clone --recursive https://github.com/purpleidea/mgmt RUN mkdir -p $GOPATH/src/github.com/purpleidea && cd $GOPATH/src/github.com/purpleidea && git clone --recursive https://github.com/purpleidea/mgmt
RUN go get -u gopkg.in/alecthomas/gometalinter.v1 && cd $GOPATH/src/github.com/purpleidea/mgmt && make deps && make build RUN go get -u gopkg.in/alecthomas/gometalinter.v1 && cd $GOPATH/src/github.com/purpleidea/mgmt && make deps && make build
CMD ["/bin/bash"] CMD ["/bin/bash"]

View File

@@ -1,4 +1,4 @@
FROM golang:1.13 FROM golang:1.20
MAINTAINER Michał Czeraszkiewicz <contact@czerasz.com> MAINTAINER Michał Czeraszkiewicz <contact@czerasz.com>
@@ -27,8 +27,5 @@ WORKDIR /home/$USER_NAME/mgmt
# Install dependencies # Install dependencies
RUN make deps RUN make deps
# Chown $GOPATH
RUN chown -R ${USER_ID}:${GROUP_ID} /go
# Change user # Change user
USER ${USER_NAME} USER ${USER_NAME}

View File

@@ -51,7 +51,7 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = u'mgmt' project = u'mgmt'
copyright = u'2013-2021+ James Shubin and the project contributors' copyright = u'2013-2024+ James Shubin and the project contributors'
author = u'James Shubin' author = u'James Shubin'
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for

View File

@@ -28,7 +28,7 @@ required for running the _test_ suite.
### Build ### Build
* `golang` 1.13 or higher (required, available in some distros and distributed * `golang` 1.20 or higher (required, available in some distros and distributed
as a binary officially by [golang.org](https://golang.org/dl/)) as a binary officially by [golang.org](https://golang.org/dl/))
### Runtime ### Runtime

View File

@@ -131,33 +131,6 @@ execute via a `remote` resource.
You can read the introductory blog post about this topic here: You can read the introductory blog post about this topic here:
[https://purpleidea.com/blog/2016/10/07/remote-execution-in-mgmt/](https://purpleidea.com/blog/2016/10/07/remote-execution-in-mgmt/) [https://purpleidea.com/blog/2016/10/07/remote-execution-in-mgmt/](https://purpleidea.com/blog/2016/10/07/remote-execution-in-mgmt/)
### Puppet support
You can supply a Puppet manifest instead of creating the (YAML) graph manually.
Puppet must be installed and in `mgmt`'s search path. You also need the
[ffrank-mgmtgraph Puppet module](https://forge.puppet.com/ffrank/mgmtgraph).
Invoke `mgmt` with the `--puppet` switch, which supports 3 variants:
1. Request the configuration from the Puppet Master (like `puppet agent` does)
`mgmt run puppet --puppet agent`
2. Compile a local manifest file (like `puppet apply`)
`mgmt run puppet --puppet /path/to/my/manifest.pp`
3. Compile an ad hoc manifest from the commandline (like `puppet apply -e`)
`mgmt run puppet --puppet 'file { "/etc/ntp.conf": ensure => file }'`
For more details and caveats see [puppet-guide.md](puppet-guide.md).
#### Blog post
An introductory post on the Puppet support is on
[Felix's blog](http://ffrank.github.io/features/2016/06/19/puppet-powered-mgmt/).
## Reference ## Reference
Please note that there are a number of undocumented options. For more Please note that there are a number of undocumented options. For more
@@ -364,27 +337,11 @@ collision with this globally defined semaphore. The size value must be greater
than zero at this time. The traditional non-parallel execution found in config than zero at this time. The traditional non-parallel execution found in config
management tools such as `Puppet` can be obtained with `--sema 1`. management tools such as `Puppet` can be obtained with `--sema 1`.
#### `--allow-interactive`
Allow interactive prompting for SSH passwords if there is no authentication
method that works.
#### `--ssh-priv-id-rsa` #### `--ssh-priv-id-rsa`
Specify the path for finding SSH keys. This defaults to `~/.ssh/id_rsa`. To Specify the path for finding SSH keys. This defaults to `~/.ssh/id_rsa`. To
never use this method of authentication, set this to the empty string. never use this method of authentication, set this to the empty string.
#### `--cconns`
The maximum number of concurrent remote ssh connections to run. This defaults
to `0`, which means unlimited.
#### `--no-caching`
Don't allow remote caching of the remote execution binary. This will require
the binary to be copied over for every remote execution, but it limits the
likelihood that there is leftover information from the configuration process.
#### `--prefix <path>` #### `--prefix <path>`
Specify a path to a custom working directory prefix. This directory will get Specify a path to a custom working directory prefix. This directory will get
@@ -486,7 +443,7 @@ To report any bugs, please file a ticket at: [https://github.com/purpleidea/mgmt
## Authors ## Authors
Copyright (C) 2013-2021+ James Shubin and the project contributors Copyright (C) 2013-2024+ James Shubin and the project contributors
Please see the Please see the
[AUTHORS](https://github.com/purpleidea/mgmt/tree/master/AUTHORS) file [AUTHORS](https://github.com/purpleidea/mgmt/tree/master/AUTHORS) file

View File

@@ -216,16 +216,41 @@ requires a number of seconds as an argument.
./mgmt run lang examples/lang/hello0.mcl --converged-timeout=5 ./mgmt run lang examples/lang/hello0.mcl --converged-timeout=5
``` ```
### When I try to build `mgmt` I see: `no Go files in $GOPATH/src/github.com/purpleidea/mgmt/bindata`. ### Can I run `mgmt` for type-checking only?
Due to the arcane way that `golang` designed its `$GOPATH`, the main project Yes, you can, add the `--only-unify` option to the lang frontend while using the
directory must be inside your `$GOPATH`, and at the appropriate FQDN. This is: `run` command, and it will exit after type unification.
`$GOPATH/src/github.com/purpleidea/mgmt/`. If you have your project root outside
of that directory, then you may get this error when you try to build it. In this #### Example:
case there is likely a `go get` version of the project at this location. Remove
it and replace it with your git cloned directory. In my case, I like to work on ```
things in `~/code/mgmt/`, so that path is a symlink that points to the long ./mgmt run --tmp-prefix lang --only-unify examples/lang/hello0.mcl
project directory. ```
It will also print how long it took on either success or failure. Keep in mind
that even if you pass type unification, an `mgmt` run can still fail later on
for other reasons, although these are mostly runtime considerations.
### Why is type unification happening twice with `mgmt run`?
When you use the `run` action, it runs all of the compile time checks (including
type unification) that are possible, and then packages everything up into a
deployable object and runs the same mechanism that `mgmt deploy` uses, sending
the deploy to itself. At this point, mgmt starts up as a server, and receives
the deploy. It will then need to run type unification again before running the
code.
You can skip the first type unification check by adding the `--skip-unify`
option to the lang frontend when using the `run` command.
You can also skip this check when running the `deploy` action, but if your code
doesn't pass, you might be deploying broken code. This is not recommended.
#### Example:
```
./mgmt run --tmp-prefix lang --skip-unify examples/lang/hello0.mcl
```
### Why does my file resource error with `no such file or directory`? ### Why does my file resource error with `no such file or directory`?
@@ -253,7 +278,7 @@ Similar logic applies for situations when you only specify the `mode` parameter.
This all turns out to be more safe and "correct", in that it would error and This all turns out to be more safe and "correct", in that it would error and
prevent masking an error for a situation when you expected a file to already be prevent masking an error for a situation when you expected a file to already be
at that location. It also turns out to simplify the internals significantly, and at that location. It also turns out to simplify the internals significantly, and
remove an ambiguous scenario with the reversable file resource. remove an ambiguous scenario with the reversible file resource.
### Why do function names inside of templates include underscores? ### Why do function names inside of templates include underscores?
@@ -295,6 +320,58 @@ an instance of mgmt running, or if a related file locking issue occurred. To
solve this, shutdown and running mgmt process, run `rm mgmt` to remove the file, solve this, shutdown and running mgmt process, run `rm mgmt` to remove the file,
and then get a new one by running `make` again. and then get a new one by running `make` again.
### Type unification error: "could not unify types: 2 unconsumed generators".
Look carefully at the following code:
```
$num = 42
print "hello" {
msg => "My favourite number is ${num}",
}
```
What's actually happening is that we can't unify `str + int`, because our
addition operator only supports `str + str`, `int + int`, and `float + float`.
The string interpolation feature can only combine strings. One solution is to
instead write:
```
$num = "42" # now this is a string
print "hello" {
msg => "My favourite number is ${num}",
}
```
Yes we know the compiler gives horrible error messages, and yes we would
absolutely love your help improving this.
### The run and deploy commands don't parse correctly when used with `--seeds`.
If you're running a command with `--seeds`, `--server-urls`, or `--client-urls`,
then make sure you are using an equals sign between the flag name and the value.
For example, if you were to run:
```
# wrong invocation!
mgmt deploy --no-git --seeds http://127.0.0.1:2379 lang code/test.mcl
```
Then the `--seeds` flag would interpret `lang` and `code/test.mcl` as additional
seeds. This flag as well as the other aforementioned ones all accept multiple
values. Use an equals sign to guarantee you enter the correct data, eg:
```
# better invocation! (note the equals sign)
mgmt deploy --no-git --seeds=http://127.0.0.1:2379 lang code/test.mcl
```
This is caused by a parsing peculiarity of the CLI library that we are using.
This is tracked upstream at: [https://github.com/alexflint/go-arg/issues/239](https://github.com/alexflint/go-arg/issues/239).
We have a workaround in place to mitigate it and attempt to show you a helpful
error message, but it's also documented here in the meantime. The error you will
see is: `cli parse error: missing equals sign for list element`.
### The docs speaks of `--remote` but the CLI errors out? ### The docs speaks of `--remote` but the CLI errors out?
The `--remote` flag existed in an earlier version of mgmt. It was removed and The `--remote` flag existed in an earlier version of mgmt. It was removed and
@@ -364,6 +441,14 @@ Don't blindly use the tools that others tell you to. Learn what they do, think
for yourself, and become a power user today! That process led us to using for yourself, and become a power user today! That process led us to using
`git submodules`. Hopefully you'll come to the same conclusions that we did. `git submodules`. Hopefully you'll come to the same conclusions that we did.
**UPDATE:**
After golang made it virtually impossible to build without `go.mod` stuff, we've
switched to it since golang 1.16. I still think the above approach was better,
and that the `go mod` tooling should have been a layer on top of git submodules
so that we don't grow yet another lock file format, and existing folks who are
comfortable with `git` can use those tools directly.
### Did you know that there is a band named `MGMT`? ### Did you know that there is a band named `MGMT`?
I didn't realize this when naming the project, and it is accidental. After much I didn't realize this when naming the project, and it is accidental. After much

View File

@@ -40,9 +40,9 @@ from looking at example code.
To implement a function, you'll need to create a file that imports the To implement a function, you'll need to create a file that imports the
[`lang/funcs/simple/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/simple/) [`lang/funcs/simple/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/simple/)
module. It should probably get created in the correct directory inside of: module. It should probably get created in the correct directory inside of:
[`lang/funcs/core/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/core/). [`lang/core/`](https://github.com/purpleidea/mgmt/tree/master/lang/core/). The
The function should be implemented as a `FuncValue` in our type system. It is function should be implemented as a `FuncValue` in our type system. It is then
then registered with the engine during `init()`. An example explains it best: registered with the engine during `init()`. An example explains it best:
### Example ### Example
@@ -115,11 +115,11 @@ re-evaluated as needed when their input values change.
To implement a function, you'll need to create a file that imports the To implement a function, you'll need to create a file that imports the
[`lang/funcs/simplepoly/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/simplepoly/) [`lang/funcs/simplepoly/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/simplepoly/)
module. It should probably get created in the correct directory inside of: module. It should probably get created in the correct directory inside of:
[`lang/funcs/core/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/core/). [`lang/core/`](https://github.com/purpleidea/mgmt/tree/master/lang/core/). The
The function should be implemented as a list of `FuncValue`'s in our type function should be implemented as a list of `FuncValue`'s in our type system. It
system. It is then registered with the engine during `init()`. You may also use is then registered with the engine during `init()`. You may also use the
the `variant` type in your type definitions. This special type will never be `variant` type in your type definitions. This special type will never be seen
seen inside a running program, and will get converted to a concrete type if a inside a running program, and will get converted to a concrete type if a
suitable match to this signature can be found. Be warned that signatures which suitable match to this signature can be found. Be warned that signatures which
contain too many variants, or which are very general, might be hard for the contain too many variants, or which are very general, might be hard for the
compiler to match, and ambiguous type graphs make for user compiler errors. The compiler to match, and ambiguous type graphs make for user compiler errors. The
@@ -239,27 +239,6 @@ use in the other methods.
// Init runs some startup code for this function. // Init runs some startup code for this function.
func (obj *FooFunc) Init(init *interfaces.Init) error { func (obj *FooFunc) Init(init *interfaces.Init) error {
obj.init = init obj.init = init
obj.closeChan = make(chan struct{}) // shutdown signal
return nil
}
```
### Close
```golang
Close() error
```
This is called to cleanup the function. It usually causes the stream to
shutdown. Even if `Stream()` decided to shutdown early, it might still get
called. It is usually called by the engine to tell the function to shutdown.
#### Example
```golang
// Close runs some shutdown code for this function and turns off the stream.
func (obj *FooFunc) Close() error {
close(obj.closeChan) // send a signal to tell the stream to close
return nil return nil
} }
``` ```
@@ -267,23 +246,24 @@ func (obj *FooFunc) Close() error {
### Stream ### Stream
```golang ```golang
Stream() error Stream(context.Context) error
``` ```
`Stream` is where the real _work_ is done. This method is started by the `Stream` is where the real _work_ is done. This method is started by the
language function engine. It will run this function while simultaneously sending language function engine. It will run this function while simultaneously sending
it values on the `input` channel. It will only send a complete set of input it values on the `Input` channel. It will only send a complete set of input
values. You should send a value to the output channel when you have decided that values. You should send a value to the output channel when you have decided that
one should be produced. Make sure to only use input values of the expected type one should be produced. Make sure to only use input values of the expected type
as declared in the `Info` struct, and send values of the similarly declared as declared in the `Info` struct, and send values of the similarly declared
appropriate return type. Failure to do so will may result in a panic and appropriate return type. Failure to do so will may result in a panic and
sadness. sadness. You must shutdown if the input context cancels. You must close the
`Output` channel if you are done generating new values and/or when you shutdown.
#### Example #### Example
```golang ```golang
// Stream returns the single value that was generated and then closes. // Stream returns the single value that was generated and then closes.
func (obj *FooFunc) Stream() error { func (obj *FooFunc) Stream(ctx context.Context) error {
defer close(obj.init.Output) // the sender closes defer close(obj.init.Output) // the sender closes
var result string var result string
for { for {
@@ -300,7 +280,7 @@ func (obj *FooFunc) Stream() error {
result = fmt.Sprintf("the input is: %d", ix) result = fmt.Sprintf("the input is: %d", ix)
case <-obj.closeChan: case <-ctx.Done():
return nil return nil
} }
@@ -309,7 +289,7 @@ func (obj *FooFunc) Stream() error {
V: result, V: result,
}: }:
case <-obj.closeChan: case <-ctx.Done():
return nil return nil
} }
} }
@@ -340,8 +320,6 @@ type FooFunc struct {
init *interfaces.Init init *interfaces.Init
// this space can be used if needed // this space can be used if needed
closeChan chan struct{} // shutdown signal
} }
``` ```
@@ -364,7 +342,9 @@ method instead.
```golang ```golang
// moduleName is already set to "math" by the math package. Do this in `init`. // moduleName is already set to "math" by the math package. Do this in `init`.
funcs.ModuleRegister(moduleName, "cos", func() interfaces.Func { return &CosFunc{} }) funcs.ModuleRegister(moduleName, "cos", func() interfaces.Func {
return &CosFunc{}
})
``` ```
### Composite functions ### Composite functions

View File

@@ -14,4 +14,3 @@ Welcome to mgmt's documentation!
quick-start-guide quick-start-guide
resource-guide resource-guide
prometheus prometheus
puppet-guide

View File

@@ -402,6 +402,38 @@ time.
Recursive classes are not currently supported and it is not clear if they will Recursive classes are not currently supported and it is not clear if they will
be in the future. Discussion about this topic is welcome on the mailing list. be in the future. Discussion about this topic is welcome on the mailing list.
A class names can contain colons to indicate it is nested inside of the class in
the same scope which is named with the prefix indicated by colon separation.
Instead of needing to repeatedly indent the child classes, we can instead prefix
them at the definition site (where created with the class keyword) with the name
of the parent class, followed by a colon, to get the desired embedded sugar.
For example, instead of writing:
```mcl
class base() {
class inner() {
class deepest() {
}
}
}
```
You can instead write:
```mcl
class base() {
}
class base:inner() {
}
class base:inner:deepest() {
}
```
Of course, you can only access any of the inner classes by first including
(with the include keyword) a parent class, and then subsequently including the
inner one.
#### Include #### Include
The `include` statement causes the previously defined class to produce the The `include` statement causes the previously defined class to produce the
@@ -414,6 +446,57 @@ parameters, then the same class can even be called with different signatures.
Whether the output is useful and whether there is a unique type unification Whether the output is useful and whether there is a unique type unification
solution is dependent on your code. solution is dependent on your code.
Classes can be included under a new scoped prefix by using the `as` field and an
identifier. When used in this manner, the captured scope of the class at its
definition site are made available in the scope of the include. Variables,
functions, and child classes are all exported.
Variables are available in the include scope:
```mcl
import "fmt"
class c1 {
test "t1" {} # gets pulled out
$x = "hello" # gets exported
}
include c1 as i1
test "print0" {
anotherstr => fmt.printf("%s", $i1.x), # hello
onlyshow => ["AnotherStr",], # displays nicer
}
```
Classes are also available in the new scope:
```mcl
import "fmt"
class c1 {
test "t1" {} # gets pulled out
$x = "hello" # gets exported
class c0 {
test "t2" {}
$x = "goodbye"
}
}
include c1 as i1
include i1.c0 as i0
test "print0" {
anotherstr => fmt.printf("%s", $i1.x), # hello
onlyshow => ["AnotherStr",], # displays nicer
}
test "print1" {
anotherstr => fmt.printf("%s", $i0.x), # goodbye
onlyshow => ["AnotherStr",], # displays nicer
}
```
Of course classes can be parameterized too, and those variables specified during
the `include`.
#### Import #### Import
The `import` statement imports a scope into the specified namespace. A scope can The `import` statement imports a scope into the specified namespace. A scope can
@@ -502,6 +585,10 @@ and can be used for other scenarios in which one statement or expression would
be better represented by a larger AST. Most nodes in the AST simply return their be better represented by a larger AST. Most nodes in the AST simply return their
own node address, and do not modify the AST. own node address, and do not modify the AST.
This stage also implements the class nesting when it finds class names with
colons that should be nested inside of a base class. Currently this does modify
the AST for efficiency and simplicity.
#### Scope propagation #### Scope propagation
Scope propagation passes the parent scope (starting with the top-level, built-in Scope propagation passes the parent scope (starting with the top-level, built-in
@@ -563,11 +650,11 @@ would like to propose a more logical or performant variant.
#### Function graph generation #### Function graph generation
At this point we have a fully type AST. The AST must now be transformed into a At this point we have a fully typed AST. The AST must now be transformed into a
directed, acyclic graph (DAG) data structure that represents the flow of data as directed, acyclic graph (DAG) data structure that represents the flow of data as
necessary for everything to be reactive. Note that this graph is *different* necessary for everything to be reactive. Note that this graph is *different*
from the resource graph which is produced and sent to the engine. It is just a from the resource graph which is produced and sent to the engine. It is just a
coincidence that both happen to be DAG's. (You don't freak out when you see a coincidence that both happen to be DAG's. (You aren't surprised when you see a
list data structure show up in more than one place, do you?) list data structure show up in more than one place, do you?)
To produce this graph, each node has a `Graph` method which it can call. This To produce this graph, each node has a `Graph` method which it can call. This
@@ -575,9 +662,8 @@ starts at the top most node, and is called down through the AST. The edges in
the graphs must represent the individual expression values which are passed the graphs must represent the individual expression values which are passed
from node to node. The names of the edges must match the function type argument from node to node. The names of the edges must match the function type argument
names which are used in the definition of the corresponding function. These names which are used in the definition of the corresponding function. These
corresponding functions must exist for each expression node and are produced by corresponding functions must exist for each expression node and are produced as
calling that expression's `Func` method. These are usually called by the the vertices of this returned graph. This is built for the function engine.
function engine during function creation and validation.
#### Function engine creation and validation #### Function engine creation and validation
@@ -677,13 +763,13 @@ one value must be produced.
```golang ```golang
Please see the example functions in Please see the example functions in
[lang/funcs/core/](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/core/). [lang/core/](https://github.com/purpleidea/mgmt/tree/master/lang/core/).
``` ```
### Stream ### Stream
```golang ```golang
Stream() error Stream(context.Context) error
``` ```
Stream is called by the function engine when it is ready for your function to Stream is called by the function engine when it is ready for your function to
@@ -692,29 +778,14 @@ value. Failure to produce at least one value will probably cause the function
engine to hang waiting for your output. This function must close the `Output` engine to hang waiting for your output. This function must close the `Output`
channel when it has no more values to send. The engine will close the `Input` channel when it has no more values to send. The engine will close the `Input`
channel when it has no more values to send. This may or may not influence channel when it has no more values to send. This may or may not influence
whether or not you close the `Output` channel. whether or not you close the `Output` channel. You must shutdown if the input
context cancels.
#### Example #### Example
```golang ```golang
Please see the example functions in Please see the example functions in
[lang/funcs/core/](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/core/). [lang/core/](https://github.com/purpleidea/mgmt/tree/master/lang/core/).
```
### Close
```golang
Close() error
```
Close asks the particular function to shutdown its `Stream()` function and
return.
#### Example
```golang
Please see the example functions in
[lang/funcs/core/](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/core/).
``` ```
### Polymorphic Function API ### Polymorphic Function API

View File

@@ -56,3 +56,6 @@ if we missed something that you think is relevant!
| James Shubin | video | [Recording from FOSDEM 2020, Infra Management Devroom](https://video.fosdem.org/2020/UA2.120/mgmt.webm) | | James Shubin | video | [Recording from FOSDEM 2020, Infra Management Devroom](https://video.fosdem.org/2020/UA2.120/mgmt.webm) |
| James Shubin | video | [Recording from FOSDEM 2020, Minimalistic Languages Devroom](https://video.fosdem.org/2020/AW1.125/mgmtconfigmore.webm) | | James Shubin | video | [Recording from FOSDEM 2020, Minimalistic Languages Devroom](https://video.fosdem.org/2020/AW1.125/mgmtconfigmore.webm) |
| James Shubin | video | [Recording from CfgMgmtCamp.eu 2020](https://www.youtube.com/watch?v=Kd7FAORFtsc) | | James Shubin | video | [Recording from CfgMgmtCamp.eu 2020](https://www.youtube.com/watch?v=Kd7FAORFtsc) |
| James Shubin | video | [Recording from CfgMgmtCamp.eu 2023](https://www.youtube.com/watch?v=FeRGRj8w0BU) |
| James Shubin | video | [Recording from FOSDEM 2024, Golang Devroom](https://video.fosdem.org/2024/ud2218a/fosdem-2024-2575-single-binary-full-stack-provisioning.mp4) |
| James Shubin | video | [Recording from CfgMgmtCamp.eu 2024](https://www.youtube.com/watch?v=vBt9lpGD4bc) |

View File

@@ -1,315 +0,0 @@
# Puppet guide
`mgmt` can use Puppet as its source for the configuration graph.
This document goes into detail on how this works, and lists
some pitfalls and limitations.
For basic instructions on how to use the Puppet support, see
the [main documentation](documentation.md#puppet-support).
## Prerequisites
You need Puppet installed in your system. It is not important how you
get it. On the most common Linux distributions, you can use packages
from the OS maintainer, or upstream Puppet repositories. An alternative
that will also work on OSX is the `puppet` Ruby gem. It also has the
advantage that you can install any desired version in your home directory
or any other location.
Any release of Puppet's 3.x and 4.x series should be suitable for use with
`mgmt`. Most importantly, make sure to install the `ffrank-mgmtgraph` Puppet
module (referred to below as "the translator module").
```
puppet module install ffrank-mgmtgraph
```
Please note that the module is not required on your Puppet master (if you
use a master/agent setup). It's needed on the machine that runs `mgmt`.
You can install the module on the master anyway, so that it gets distributed
to your agents through Puppet's `pluginsync` mechanism.
### Testing the Puppet side
The following command should run successfully and print a YAML hash on your
terminal:
```puppet
puppet mgmtgraph print --code 'file { "/tmp/mgmt-test": ensure => present }'
```
You can use this CLI to test any manifests before handing them straight
to `mgmt`.
## Writing a suitable manifest
### Unsupported attributes
`mgmt` inherited its resource module from Puppet, so by and large, it's quite
possible to express `mgmt` graphs in terms of Puppet manifests. However,
there isn't (and likely never will be) full feature parity between the
respective resource types. In consequence, a manifest can have semantics that
cannot be transferred to `mgmt`.
For example, at the time of writing this, the `file` type in `mgmt` had no
notion of permissions (the file `mode`) yet. This lead to the following
warning (among others that will be discussed below):
```
$ puppet mgmtgraph print --code 'file { "/tmp/foo": mode => "0600" }'
Warning: cannot translate: File[/tmp/foo] { mode => "600" } (attribute is ignored)
```
This is a heads-up for the user, because the resulting `mgmt` graph will
in fact not pass this information to the `/tmp/foo` file resource, and
`mgmt` will ignore this file's permissions. Including such attributes in
manifests that are written expressly for `mgmt` is not sensible and should
be avoided.
### Unsupported resources
Puppet has a fairly large number of
[built-in types](https://docs.puppet.com/puppet/latest/reference/type.html),
and countless more are available through
[modules](https://forge.puppet.com/). It's unlikely that all of them will
eventually receive native counterparts in `mgmt`.
When encountering an unknown resource, the translator module will replace
it with an `exec` resource in its output. This resource will run the equivalent
of a `puppet resource` command to make Puppet apply the original resource
itself. This has quite abysmal performance, because processing such a
resource requires the forking of at least one Puppet process (two if it
is found to be out of sync). This comes with considerable overhead.
On most systems, starting up any Puppet command takes several seconds.
Compared to the split second that the actual work usually takes,
this overhead can amount to several orders of magnitude.
Avoid Puppet types that `mgmt` does not implement (yet).
### Avoiding common warnings
Many resource parameters in Puppet take default values. For the most part,
the translator module just ignores them. However, there are cases in which
Puppet will default to convenient behavior that `mgmt` cannot quite replicate.
For example, translating a plain `file` resource will lead to a warning message:
```
$ puppet mgmtgraph print --code 'file { "/tmp/mgmt-test": }'
Warning: File[/tmp/mgmt-test] uses the 'puppet' file bucket, which mgmt cannot do. There will be no backup copies!
```
The reason is that per default, Puppet assumes the following parameter value
(among others)
```puppet
file { "/tmp/mgmt-test":
backup => 'puppet',
}
```
To avoid this, specify the parameter explicitly:
```bash
puppet mgmtgraph print --code 'file { "/tmp/mgmt-test": backup => false }'
```
This is tedious in a more complex manifest. A good simplification is the
following [resource default](https://docs.puppet.com/puppet/latest/reference/lang_defaults.html)
anywhere on the top scope of your manifest:
```puppet
File { backup => false }
```
If you encounter similar warnings from other types and/or parameters,
use the same approach to silence them if possible.
## Configuring Puppet
Since `mgmt` uses an actual Puppet CLI behind the scenes, you might
need to tweak some of Puppet's runtime options in order to make it
do what you want. Reasons for this could be among the following:
* You use the `--puppet agent` variant and need to configure
`servername`, `certname` and other master/agent-related options.
* You don't want runtime information to end up in the `vardir`
that is used by your regular `puppet agent`.
* You install specific Puppet modules for `mgmt` in a non-standard
location.
`mgmt` exposes only one Puppet option in order to allow you to
control all of them, through its `--puppet-conf` option. It allows
you to specify which `puppet.conf` file should be used during
translation.
```
mgmt run puppet --puppet /opt/my-manifest.pp --puppet-conf /etc/mgmt/puppet.conf
```
Within this file, you can just specify any needed options in the
`[main]` section:
```
[main]
server=mgmt-master.example.net
vardir=/var/lib/mgmt/puppet
```
## Caveats
Please see the [README](https://github.com/ffrank/puppet-mgmtgraph/blob/master/README.md)
of the translator module for the current state of supported and unsupported
language features.
You should probably make sure to always use the latest release of
both `ffrank-mgmtgraph` and `ffrank-yamlresource` (the latter is
getting pulled in as a dependency of the former).
## Using Puppet in conjunction with the mcl lang
The graph that Puppet generates for `mgmt` can be united with a graph
that is created from native `mgmt` code in its mcl language. This is
useful when you are in the process of replacing Puppet with mgmt. You
can translate your custom modules into mgmt's language one by one,
and let mgmt run the current mix.
Instead of the usual `--puppet-conf` flag and argv for `puppet` and `mcl` input,
you need to use alternative flags to make this work:
* `--lp-lang` to specify the mcl input
* `--lp-puppet` to specify the puppet input
* `--lp-puppet-conf` to point to the optional puppet.conf file
`mgmt` will derive a graph that contains all edges and vertices from
both inputs. You essentially get two unrelated subgraphs that run in
parallel. To form edges between these subgraphs, you have to define
special vertices that will be merged. This works through a hard-coded
naming scheme.
### Mixed graph example 1 - No merges
```mcl
# lang
file "/tmp/mgmt_dir/" { state => "present" }
file "/tmp/mgmt_dir/a" { state => "present" }
```
```puppet
# puppet
file { "/tmp/puppet_dir": ensure => "directory" }
file { "/tmp/puppet_dir/a": ensure => "file" }
```
These very simple inputs (including implicit edges from directory to
respective file) result in two subgraphs that do not relate.
```
File[/tmp/mgmt_dir/] -> File[/tmp/mgmt_dir/a]
File[/tmp/puppet_dir] -> File[/tmp/puppet_dir/a]
```
### Mixed graph example 2 - Merged vertex
In order to have merged vertices in the resulting graph, you will
need to include special resources and classes in the respective
input code.
* On the lang side, add `noop` resources with names starting in `puppet_`.
* On the Puppet side, add **empty** classes with names starting in `mgmt_`.
```mcl
# lang
noop "puppet_handover_to_mgmt" {}
file "/tmp/mgmt_dir/" { state => "present" }
file "/tmp/mgmt_dir/a" { state => "present" }
Noop["puppet_handover_to_mgmt"] -> File["/tmp/mgmt_dir/"]
```
```puppet
# puppet
class mgmt_handover_to_mgmt {}
include mgmt_handover_to_mgmt
file { "/tmp/puppet_dir": ensure => "directory" }
file { "/tmp/puppet_dir/a": ensure => "file" }
File["/tmp/puppet_dir/a"] -> Class["mgmt_handover_to_mgmt"]
```
The new `noop` resource is merged with the new class, resulting in
the following graph:
```
File[/tmp/puppet_dir] -> File[/tmp/puppet_dir/a]
|
V
Noop[handover_to_mgmt]
|
V
File[/tmp/mgmt_dir/] -> File[/tmp/mgmt_dir/a]
```
You put all your ducks in a row, and the resources from the Puppet input
run before those from the mcl input.
**Note:** The names of the `noop` and the class must be identical after the
respective prefix. The common part (here, `handover_to_mgmt`) becomes the name
of the merged resource.
## Mixed graph example 3 - Multiple merges
In most scenarios, it will not be possible to define a single handover
point like in the previous example. For example, if some Puppet resources
need to run in between two stages of native resources, you need at least
two merged vertices:
```mcl
# lang
noop "puppet_handover" {}
noop "puppet_handback" {}
file "/tmp/mgmt_dir/" { state => "present" }
file "/tmp/mgmt_dir/a" { state => "present" }
file "/tmp/mgmt_dir/puppet_subtree/state-file" { state => "present" }
File["/tmp/mgmt_dir/"] -> Noop["puppet_handover"]
Noop["puppet_handback"] -> File["/tmp/mgmt_dir/puppet_subtree/state-file"]
```
```puppet
# puppet
class mgmt_handover {}
class mgmt_handback {}
include mgmt_handover, mgmt_handback
class important_stuff {
file { "/tmp/mgmt_dir/puppet_subtree":
ensure => "directory"
}
# ...
}
Class["mgmt_handover"] -> Class["important_stuff"] -> Class["mgmt_handback"]
```
The resulting graph looks roughly like this:
```
File[/tmp/mgmt_dir/] -> File[/tmp/mgmt_dir/a]
|
V
Noop[handover] -> ( class important_stuff resources )
|
V
Noop[handback]
|
V
File[/tmp/mgmt_dir/puppet_subtree/state-file]
```
You can add arbitrary numbers of merge pairs to your code bases,
with relationships as needed. From our limited experience, code
readability suffers quite a lot from these, however. We advise
to keep these structures simple.

View File

@@ -21,6 +21,8 @@ to build your own.
### Downloading a pre-built release: ### Downloading a pre-built release:
This method is not recommended because those packages are now very old.
The latest releases can be found [here](https://github.com/purpleidea/mgmt/releases/). The latest releases can be found [here](https://github.com/purpleidea/mgmt/releases/).
An alternate mirror is available [here](https://dl.fedoraproject.org/pub/alt/purpleidea/mgmt/releases/). An alternate mirror is available [here](https://dl.fedoraproject.org/pub/alt/purpleidea/mgmt/releases/).
@@ -37,7 +39,7 @@ You'll need some dependencies, including `golang`, and some associated tools.
#### Installing golang #### Installing golang
* You need golang version 1.13 or greater installed. * You need golang version 1.20 or greater installed.
* To install on rpm style systems: `sudo dnf install golang` * To install on rpm style systems: `sudo dnf install golang`
* To install on apt style systems: `sudo apt install golang` * To install on apt style systems: `sudo apt install golang`
* To install on macOS systems install [Homebrew](https://brew.sh) * To install on macOS systems install [Homebrew](https://brew.sh)
@@ -63,13 +65,11 @@ export GOPATH=$HOME/gopath
#### Getting the mgmt code and associated dependencies #### Getting the mgmt code and associated dependencies
* Download the `mgmt` code into the `GOPATH`, and switch to that directory: * Download the `mgmt` code and switch to that directory:
```shell ```shell
[ -z "$GOPATH" ] && mkdir ~/go/ || mkdir -p $GOPATH/src/github.com/purpleidea/ git clone --recursive https://github.com/purpleidea/mgmt/ ~/mgmt/
cd $GOPATH/src/github.com/purpleidea/ || cd ~/go/ cd ~/mgmt/
git clone --recursive https://github.com/purpleidea/mgmt/
cd $GOPATH/src/github.com/purpleidea/mgmt/ || cd ~/go/src/github.com/purpleidea/mgmt/
``` ```
* Add `$GOPATH/bin` to `$PATH` * Add `$GOPATH/bin` to `$PATH`
@@ -97,6 +97,21 @@ At the moment we have:
Please contribute more and help improve these! We'd especially like to see a Please contribute more and help improve these! We'd especially like to see a
Debian package! Debian package!
### Building from a container:
This method avoids polluting your workstation with the dependencies for the
build. Here is an example using Fedora, Podman and Buildah:
```shell
git clone --recursive https://github.com/purpleidea/mgmt/ ~/mgmt/
cd ~/mgmt/docker
buildah build -f Dockerfile-fedora.build -t mgmt_build
podman run -d -it --name mgmt_build localhost/mgmt_build
podman cp mgmt_build:/src/github.com/purpleidea/mgmt/mgmt /tmp/mgmt
sudo mv /tmp/mgmt /usr/local/bin # be sure this is in your $PATH
sudo chown root:root /usr/local/bin/mgmt
```
## Running mgmt ## Running mgmt
* Run `mgmt run --tmp-prefix lang examples/lang/hello0.mcl` to try out a very * Run `mgmt run --tmp-prefix lang examples/lang/hello0.mcl` to try out a very

51
docs/release-notes/0.0.10 Normal file
View File

@@ -0,0 +1,51 @@
I've just released version 0.0.10 of mgmt!
NEWS
57 files changed, 1991 insertions(+), 752 deletions(-)
* There's a new resource called `KV`. Short examples exist, but I
haven't yet published a whole integration showing the usefulness.
* A major race was fixed. The issue of what to do with BackPokes during
start/pause was never previously solved. I had this as an open issue on
my whiteboard for a while, and I finally got some time to work through
it. The answer wasn't that difficult, but I think it was shrouded in
some tunnel vision. See the commit messages and source comments for the
details.
* The GAPI grew four new associated World API methods: StrWatch,
StrGet, StrSet, StrDel, and the associated etcd backed implementations.
These are quite useful when combined with the KV resource.
* There are now P/V style counting semaphores available as metaparams.
This is particularly cool because the implementation is (AFAIK,
assuming no bugs) dead-lock free! This is mentioned in my recent blog
post.
* See the git log for more NEWS, and sorry for anything notable I left
out!
BUGS
* There's a `concurrent map write` bug in the semaphore implementation
which is fixed in git master. Since it was a race, it was only caught
after this release was made. I should also figure out if the sema check
should go after the BackPoke or not.
MISC
We're still looking for new contributors, and there are easy, medium
and hard issues available! You're also welcome to suggest your own!
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
you'd like help getting started!
THANKS
Thanks (alphabetically) to everyone who contributed to the latest
release:
James Shubin, Julien Pivotto, Michael Borden.
We had 3 unique committers since 0.0.9, and have had 30 overall.
Happy hacking,
James

59
docs/release-notes/0.0.11 Normal file
View File

@@ -0,0 +1,59 @@
I've just released version 0.0.11 of mgmt!
NEWS
20 files changed, 579 insertions(+), 126 deletions(-)
* Added a missing mutex around the semaphore map which prevents
occasional panics
* Removed exec pollint param which is not needed because of the poll
metaparam
* Fixed a state rechecking bug in exec resource (things are faster now)
* Fixed the major annoyance of exec resources receiving main's signals.
If we would ^C the main mgmt, the child processes would receive this
too which was incorrect.
* Fixed the deadlock on resource errors. This meant that previously if
a resource failed, we would deadlock the graph from shutting down. This
was bad and I'm glad it's now fixed. Sorry about that!
* Improved the backpoke logic to not require semaphores since we used
to take the lock even when we were going to backpoke which was
unnecessary.
* Added fast pausing to the graph. This means that a ^C or a pause
transition used to wait for the whole graph to roll through, but it now
finishes after the currently running resources finish executing. Read
the commit messages for more background here including the discussion
about a possible Interrupt() addition to the resource API.
* Prometheus support has been updated!
* See the git log for more NEWS, and sorry for anything notable I left
out!
BUGS
* We're in pretty good shape now. There are some small issues, but
nothing major that I don't know about.
MISC
We're still looking for new contributors, and there are easy, medium
and hard issues available! You're also welcome to suggest your own!
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
you'd like help getting started!
THANKS
Thanks (alphabetically) to everyone who contributed to the latest
release:
James Shubin, Julien Pivotto
We had 2 unique committers since 0.0.10, and have had 30 overall.
Happy hacking,
James
@purpleidea

87
docs/release-notes/0.0.12 Normal file
View File

@@ -0,0 +1,87 @@
I've just released version 0.0.12 of mgmt!
Sorry if I've been more quiet than usual, I've had to focus a lot of my
time on GlusterFS related features ($dayjob official directives) and
work there.
One goal is to try and use libmgmt to either wholly or partially re-
implement glusterd. As a result, a lot of my upstream focus has been
re-prioritized to features needed for that effort.
I wrote a PoC called gd3: https://github.com/purpleidea/gd3
(It has now bit-rotted compared to upstream mgmt, but is easy to fix.)
The initial scope looks like it will be much smaller, but hopefully
this is interesting to you too. Ping me if you'd like to help.
We desperately need your contributions if we're going to get mgmt
standalone into a MVP. To motivate you, there's some great new stuff
that landed since 0.0.11, including:
* a great new YAML parser from contributor Mildred
* a huge pgraph refactor (to remove internal deps and cycles)
* a great amount of new tests and testing
and so much more...
NEWS
76 files changed, 7549 insertions(+), 4269 deletions(-)
* The svc resource now also supports user services
* There's a fabulous new yaml parser that fixes a longstanding issue in
my original code. Try it with --yaml2. I'll remove the old one
eventually. Thanks to contributor Mildred for this great work!
* Refactored the lib/ etcd usage into the GAPI's for a cleaner main
* World API grew some new methods for libmgmt users
* pgraph refactor and cleanup, now it's a fairly clean standalone pkg
* pgraph functions to flatten/merge in subgraphs along with examples
* Giant resource refactor to hopefully make things more logical, and to
simplify the resource API. This also introduces the mgraph struct to
add the higher level graph knowledge outside of pgraph.
* A partial implementation of a "Graph" (recursive subgraph?) resource!
See the code for details, as help is wanted to finish this. This should
help elucidate what the most elegant design for the mgmt core should
be.
* Send/Recv support for the exec resource as output, stdout, and stderr
* GAPI improvements to support exit requests and fast pausing
* AutoEdge API improvements including a fix+test for a regression
* A possible fix for the possible etcd server startup race
* A fun amount of new tests all over including for gometalinter
* See the git log for more NEWS, and for anything notable I left out!
BUGS
* We fixed a bunch of stuff, and added more tests!
MISC
We're still looking for new contributors, and there are easy, medium
and hard issues available! You're also welcome to suggest your own!
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
you'd like help getting started!
THANKS
Thanks (alphabetically) to everyone who contributed to the latest
release:
James Shubin, Julien Pivotto, Mildred Ki'Lya
We had 3 unique committers since 0.0.11, and have had 30 overall.
Happy hacking,
James
@purpleidea

89
docs/release-notes/0.0.13 Normal file
View File

@@ -0,0 +1,89 @@
I've just released version 0.0.13 of mgmt!
I guess this is an appropriate number for a scary October release.
We recently re-licensed to the more permissive GPL. I put a lot of
thought into it, and also wrote up a short blog post about some of the
reasoning. It's here:
https://ttboj.wordpress.com/2017/10/17/copyleft-is-dead-long-live-copyl
eft/
If you read to the end you'll also see that Red Hat is not funding my
mgmt work anymore. This is too bad, and means I'll only have small
amounts of personal time available to work on this. If you'd like to
help fund my work, or know someone who'd like to, please let me know!
Having said that, there's some great new stuff that landed since
0.0.12, including:
* new resources from new contributor Jonathan Gold (aws, group, user)
* an HCL frontend from new contributor Chris McKenzie
* polish in a number of places including in the nspawn resource
and so much more... If you'd prefer to have releases more often, then
please let me know! Lastly, the language and a number of associated
parts are on its way. I hope to push this monster patch to git master
before February.
It's also worth mentioning that we have 17 resources now! wow.
NEWS
140 files changed, 3921 insertions(+), 848 deletions(-)
* Many improvements to tests, testing and small fixes to avoid false-
failures on travis.
* golint now reports 0 issues.
* An HCL frontend if you'd prefer that to the YAML. Also a great
example of how to plug in a new frontend.
* An update to golang 1.8 as the minimum version required.
* Bump the etcd version to 3.2.6+ -- Looking forward to a 3.3 release
which should probably include some patches we upstreamed.
* Addition of new user and group resources. These also include a bunch
of automatic edges.
* Addition of an AWS resource! I've wanted this for a while, as it
demonstrates nicely how event based cloud resources can fit nicely into
our design. There's still a lot to do, and we have some suggestions for
Amazon too. If you have a contact there, please put me in touch!
* Our nspawn resource is more polished now.
* See the git log for more NEWS, and for anything notable I left out!
MISC
We're still looking for new contributors, and there are easy, medium
and hard issues available! You're also welcome to suggest your own!
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
you'd like help getting started!
MENTORING
We offer mentoring for new golang/mgmt hackers who want to get
involved. This is free and friendly. You get to improve your skills,
and we get some patches in return. Ping me off-list for details.
THANKS
Thanks (alphabetically) to everyone who contributed to the latest
release:
AdnanLFC, Arthur Mello, ChrisMcKenzie, Dennis Kliban, Ismael Puerto,
James Shubin, Jonathan Gold, Juan Luis de Sousa-Valadas Castaño, Juan-
Luis de Sousa-Valadas Castaño (although I suspect the last two are the
same ;))
We had 9 unique committers since 0.0.12, and have had 38 overall.
Run 'git log 0.0.12..0.0.13' to see what has changed since 0.0.12
Happy hacking,
James
@purpleidea

81
docs/release-notes/0.0.14 Normal file
View File

@@ -0,0 +1,81 @@
I've just released version 0.0.14 of mgmt!
> 118 files changed, 2688 insertions(+), 974 deletions(-)
There's some great new stuff that landed since
0.0.13, including:
* amazon AWS EC2 resource is now in git master.
* more automatic edges from new contributor Guillaume Herail (xiu)
* a move to golang 1.8 or higher
and so much more... This will probably be the last release before the
language lands in git master. It's a pretty giant patch coming :/
NEWS
* We're > 1k stars on GitHub now. It's a silly metric, but ¯\_(ツ)_/¯
* Jonathan Gold has done a lot of hard work on the AWS EC2 resource,
and it's now in git master. There are still many things we'd like to
do, but it's a great start on what is a *monster* resource, and
hopefully it will inspire others to get involved.
In particular, it was a great learning experience (for me in
particular!) about how bad the EC2 golang API is. There are some
notable design bugs we found, and if anyone from Amazon engineering
would like to reach out to us, we'd love to provide some constructive
ideas.
* Guillaume Herail (xiu) wrote some really nice patches, and picked up
on the autoedges API very quickly. Hopefully he'll have time to work on
even more!
* Paul Morgan sent us some nice shell fixups-- many more exist in an
open PR that didn't make it into this release. Hopefully we'll get
those merged by 0.0.15
* Felix Frank did a few Puppet compiler fix ups. I think he's been
refreshing his work with new resources recently...
* We're looking for help writing Google, DigitalOcean, Rackspace, etc
resources if anyone is interested, reach out to us. Particularly if
there is support from those organizations as well.
* See the git log for more NEWS, and for anything notable I left out!
EVENTS
There are a bunch of upcoming mgmt talks and events! Stay tuned for
details in the coming email, but TL;DR: Linux Conf Australia, FOSDEM, &
CfgMgmtCamp.eu -- from three different speakers, and including a
hackathon too!
MISC
We're still looking for new contributors, and there are easy, medium
and hard issues available! You're also welcome to suggest your own!
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
you'd like help getting started! Expect many new tagged #mgmtlove
issues within the month.
MENTORING
We offer mentoring for new golang/mgmt hackers who want to get
involved. This is free and friendly. You get to improve your skills,
and we get some patches in return. Ping me off-list for details.
THANKS
Thanks (alphabetically) to everyone who contributed to the latest
release:
Felix Frank, Guillaume Herail, James Shubin, Jonathan Gold,
jonathangold, Julien Pivotto, Paul Morgan, Toshaan Bharvani
We had 8 unique committers since 0.0.13, and have had 41 overall.
run 'git log 0.0.13..0.0.14' to see what has changed since 0.0.13
Happy hacking,
James
@purpleidea

116
docs/release-notes/0.0.15 Normal file
View File

@@ -0,0 +1,116 @@
I've just released version 0.0.15 of mgmt!
> 328 files changed, 29869 insertions(+), 943 deletions(-)
(Yeah, that's almost 30k+ LOC)
There's some great new stuff that landed since 0.0.14, including:
* THE LANGUAGE (mcl)
* "Deploys": a distributed way to push code into your cluster
* Scheduling (as a reactive function)
* Better testing
* a move to etcd 3.3+ and golang 1.9+
and so much more... This is a monster release. Please try out the
language and all the other new features today :)
NEWS
* We released the language. Please play around with it :) It's time to
get used to this cool new paradigm. Learn more from the...
Blog post:
https://purpleidea.com/blog/2018/02/05/mgmt-configuration-language/
Video:
https://www.youtube.com/watch?v=NxObmwZDyrI
Docs:
https://github.com/purpleidea/mgmt/blob/master/docs/language-guide.md
Function guide:
https://github.com/purpleidea/mgmt/blob/master/docs/function-guide.md
And tons of code all over git master. Check the lang/ folder.
* There is a reactive scheduler in the language. Use your imagination,
or play around with:
https://github.com/purpleidea/mgmt/blob/3ad7097c8aa7eab7f895aab9af22338
c0cf82986/lang/funcs/core/schedule_polyfunc.go#L18
* There is a "deploys" feature. It's not documented yet. You should
poke around if you're curious. Consider this an early soft release.
* There is a FS implementation to store files in a POSIX-like layer on
top of etcd. It's used by deploys. It needs more tests though :)
* The language grew two "simple" API's for implementing functions, so
that new functionality can be exposed in the mgmt language.
* The language grew two ways to specify edges between resources: either
internal to the resource, or externally as standalone edges.
* The language now supports optional resource parameters via the
"elvis" operator. This keeps things type safe and avoids needing an
"undef" or "nil" in the language. This operator also works for edge
declarations.
* New contributor Johan Bloemberg has been on fire sending patches!
He has already made some great improvements to our Makefile for
testing, and the addition of the env* functions in the language, with
much more code pending in open PR's.
* Initial debian packaging has been added. It now needs a maintainer to
build, upload, and love it :)
* We have an early emacs major mode for "mcl", our language.
* Lots of new documentation has been added. Particularly for developers
wanting to contribute to the project.
* We're looking for help writing Google, DigitalOcean, Rackspace, etc
resources if anyone is interested, reach out to us. Particularly if
there is support from those organizations as well.
* See the git log for more NEWS, and for anything notable I left out!
MISC
We're still looking for new contributors, and there are easy, medium
and hard issues available! You're also welcome to suggest your own!
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
you'd like help getting started! For details please see:
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
tribute-to-the-project-if-i-dont-know-golang
Many new tagged #mgmtlove issues were tagged:
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
3Amgmtlove
MENTORING
We offer mentoring for new golang/mgmt hackers who want to get
involved.
This is free and friendly. You get to improve your skills,
and we get
some patches in return. Ping me off-list for details.
THANKS
Thanks (alphabetically) to everyone who contributed to the latest
release:
Carsten Thiel, dsx, James Shubin, Joe Julian, Johan Bloemberg, Jonathan
Gold, jonathangold, karimb, Oliver Frommel, Peter Oliver, Toshaan
Bharvani, Wim
We had 12 unique committers since 0.0.14, and have had 48 overall.
run 'git log 0.0.14..0.0.15' to see what has changed since 0.0.14
Happy hacking,
James
@purpleidea

104
docs/release-notes/0.0.16 Normal file
View File

@@ -0,0 +1,104 @@
I've just released version 0.0.16 of mgmt!
> 220 files changed, 14243 insertions(+), 9491 deletions(-)
Woo...
There's some great new stuff that landed since 0.0.15, including:
* A giant engine re-write! (Makes resource writing more elegant too.)
* New resources!!
* New language features!!!
and so much more... This is a monster release. Please try out the new
features today :)
NEWS
* New resources include: net, mount and docker:container. Jonathan was
responsible for all of these. Please take them for a spin! He's looking
for a job too, and would probably be happy to get paid to work on mgmt.
* We're > 1.5k stars on GitHub now. It's a silly metric, but ¯\_(ツ)_/¯
* A giant engine refactoring/re-write was done. This cleaned up the
code significantly, and made it more elegant to write resources.
Unfortunately there is one small bug that I missed and that I haven't
fixed yet. It rarely happens except during some of our tests during
shutdown, which causes intermittent failures. It shouldn't block you
playing with mgmt.
* The language "class" and "include" statements have been added. These
are important pieces for writing reusable modules which are coming
soon. Try them out! (This comes with a bunch of tests too.)
* We have an integration testing framework. It's pretty cool, it spins
up a full mgmt cluster and runs stuff. Try it out or add some tests.
* I had fun fixing a big bug: 06ee05026b0c743d19c7d62675f8ddeabdc8dd4f
* I removed the remote execution functionality from core. I realized it
could be re-written as a resource, and it was also in the way from some
other cleanups that were more important. Half the new code is done,
ping me if this is a priority for you or you want to help.
* I also removed the HCL front-end, because mcl is usable enough to be
more fun to play with, and I wanted to refactor some code. If someone
really wants it back, let me know.
* We have some release building scripts in git master, so you can now
download pre-built (with fpm) RPM, DEB, or PACMAN packages! They're
signed too. https://github.com/purpleidea/mgmt/releases/tag/0.0.16
* We're looking for help writing Google, DigitalOcean, Rackspace, etc
resources if anyone is interested, reach out to us. Particularly if
there is support from those organizations as well.
* Many other bug fixes, changes, etc...
* See the git log for more NEWS, and for anything notable I left out!
MISC
I took a bit of a break recently to catch up on some life stuff, but I
think I'm back on track. While git master hasn't been especially busy,
there's an active feature branch at feat/import which contains some fun
stuff, with a very WIP giant patch still sitting on my machine. I hope
to finish it up soon and then do another release. That branch contains
one of the last big features before I'll really be ready to run mgmt on
my personal servers!
We're still looking for new contributors, and there are easy, medium
and hard issues available! You're also welcome to suggest your own!
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
you'd like help getting started! For details please see:
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
tribute-to-the-project-if-i-dont-know-golang
Many new tagged #mgmtlove issues were tagged:
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
3Amgmtlove
MENTORING
We offer mentoring for new golang/mgmt hackers who want to get
involved.
This is free and friendly. You get to improve your skills,
and we get
some patches in return. Ping me off-list for details.
THANKS
Thanks (alphabetically) to everyone who contributed to the latest
release:
Alan Jenkins, James Shubin, jesus m. rodriguez, Jonathan Gold,
jonathangold, Lauri Ojansivu, phaer
We had 7 unique committers since 0.0.15, and have had 52 overall.
run 'git log 0.0.15..0.0.16' to see what has changed since 0.0.15
Happy hacking,
James
@purpleidea

111
docs/release-notes/0.0.17 Normal file
View File

@@ -0,0 +1,111 @@
I've just released version 0.0.17 of mgmt!
> 269 files changed, 13281 insertions(+), 1633 deletions(-)
There's some very useful stuff that landed since 0.0.16, including:
* Modules and import system now exists!
* A lot of tests, fixes and a huge new test infra was added
* Merging puppet with mcl code is now possible (madness!)
* We made a small change to the cli UI
* Bump to golang 1.10 (please update your environments)
And much more...
DOWNLOAD
Prebuilt binaries are available here:
https://github.com/purpleidea/mgmt/releases/tag/0.0.17
NEWS
* One of the biggest missing features was the lack of a module/import
system. After some initial exploration on what turned out to be a dead-
end, I found what I think is a very elegant approach, which is now in
this release. Please try it out, there are docs available. I hope to
write a blog post about it soon.
(There's one additional "kind" of import that I'm considering, similar
to a macro "#include", that I might add. To be determined. Let me know
if you find anything missing as of today.)
* Felix added the first version of his mcl+puppet frontend. This should
allow you to more cleverly merge legacy puppet environments with mcl
code. It's fantastic, take a look.
* Jonathan added a systemd-timer resource. This is a great replacement
for cron.
* We changed the CLI ui to improve the determinism of the frontend
selection. Basically we changed from: `mgmt run --lang code.mcl` to:
`mgmt run lang --lang code.mcl`. Remember to put --tmp-prefix after
`run` directly where it is used.
* We made a whole bunch of cleanups to the test infra, added new test
infra for testing complex mcl modules and the import/module system, and
of course added new tests.
* You can pass a list of strings as the resource name to build that
many resources. (Looping/iteration!)
* You can specify all the metaparams and auto-* properties in mcl now.
* Native mcl code can be used to write imported core code.
* There was a bug that snuck into the pkg res. This has now been fixed.
* A small, long-time copy+pasta error bug was fixed in Exec.
* Virtually all the imports of the "log" package are at the top-level
now. This will make moving to a new logger easier in the future. I have
an innovative logger idea that I have a design for that I'll eventually
get to.
* A few workarounds for occasional test failures were added. Some
legacy code needs a cleanup, and it's not done yet. Fortunately, none
of these issues seem to occur in real-life as far as I can tell, and
are caused by closing down mgmt at weird times.
* Found a bug (now fixed) in the upstream lexer Yikes! See:
57ce3fa587897d74634c1216af67dd42252c64e5
* We're looking for help writing Amazon, Google, DigitalOcean, etc
resources if anyone is interested, reach out to us. Particularly if
there is support from those organizations as well.
* Many other bug fixes, changes, etc...
* See the git log for more NEWS, and for anything notable I left out!
MISC
We're still looking for new contributors, and there are easy, medium
and hard issues available! You're also welcome to suggest your own!
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
you'd like help getting started! For details please see:
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
tribute-to-the-project-if-i-dont-know-golang
Many new tagged #mgmtlove issues were tagged:
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
3Amgmtlove
MENTORING
We offer mentoring for new golang/mgmt hackers who want to get
involved. This is fun and friendly! You get to improve your skills,
and we get some patches in return. Ping me off-list for details.
THANKS
Thanks (alphabetically) to everyone who contributed to the latest
release:
Felix Frank, James Shubin, Jonathan Gold, Kevin Kuehler, Michael Lesko-
Krleza, Tom Payne, Vincent Membré
We had 7 unique committers since 0.0.16, and have had 56 overall.
run 'git log 0.0.16..0.0.17' to see what has changed since 0.0.16
Happy hacking,
James
@purpleidea

106
docs/release-notes/0.0.18 Normal file
View File

@@ -0,0 +1,106 @@
I've just released version 0.0.18 of mgmt!
> 202 files changed, 5606 insertions(+), 1880 deletions(-)
There's some great stuff that landed since 0.0.17, including:
* A re-write of the core engine algorithm
* Tests are very stable
* At least three old bugs were killed
* An infra to write tests for individual resources was added
And much more...
This adds a significant amount of polish and bug fixes to mgmt. We're
on the home stretch to MVP!
DOWNLOAD
Prebuilt binaries are available here:
https://github.com/purpleidea/mgmt/releases/tag/0.0.18
NEWS
* There was a rare race that would panic the engine on shutdown. This
only seemed to happen in CPU/system call starved environments like
travis-ci. This was due to some ignorance when writing this early part
of the code base. The algorithm has been re-written, and this also
removed the use of at least one mutex. Things should be stable now, and
also much faster. (Although perf was never an issue.)
* A hidden race/deadlock in the exec resource was found and killed.
Woo! Some new tools to help find these and other problems are in misc/
* The early converger code was re-written. I was not as knowledgeable
about golang in the very beginning, and this code needed refreshing. It
contained a rare deadlock which needed to be killed.
* Toshaan added an uptime() function.
* Julien added a method for generating some simple functions for the
language.
* Lander added two new functions.
* James added a new readfile() function, and other examples.
* The template function now allows you to use imported functions. They
use underscores instead of periods for namespace separation due to a
limitation in the template library.
* Kevin and I killed a tricky race in the SocketSet code! Woo :) Kevin
also added a great cpucount() fact!
* James gave a number of presentations at FOSDEM. Some recordings are
available: https://purpleidea.com/talks/
* We're looking for help writing Amazon, Google, DigitalOcean, etc
resources if anyone is interested, reach out to us. Particularly if
there is support from those organizations as well.
* Many other bug fixes, changes, etc...
* See the git log for more NEWS, and for anything notable I left out!
BUGS
* An unfortunate bug in the type unification code was found. This can
cause small code bases to take a lot of ram/cpu to run. This will be
prioritized in an upcoming release. Until then you'll have to avoid
fancy type unification. (Specify types you know when it has to guess.)
If efficient type unification algorithms are your specialty, please let
us know, we'd like your help!
MISC
We're still looking for new contributors, and there are easy, medium
and hard issues available! You're also welcome to suggest your own!
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
you'd like help getting started! For details please see:
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
tribute-to-the-project-if-i-dont-know-golang
Many tagged #mgmtlove issues exist:
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
3Amgmtlove
MENTORING
We offer mentoring for new golang/mgmt hackers who want to get
involved. This is fun and friendly! You get to improve your skills,
and we get some patches in return. Ping me off-list for details.
THANKS
Thanks (alphabetically) to everyone who contributed to the latest
release:
Felix Frank, James Shubin, Jeff Waugh, Johan Bloemberg, Julien Pivotto,
Kevin Kuehler, Lander Van den Bulcke, Toshaan Bharvani
We had 8 unique committers since 0.0.17, and have had 58 overall.
run 'git log 0.0.17..0.0.18' to see what has changed since 0.0.17
Happy hacking,
James
@purpleidea

131
docs/release-notes/0.0.19 Normal file
View File

@@ -0,0 +1,131 @@
I've just released version 0.0.19 of mgmt!
> 361 files changed, 10451 insertions(+), 3919 deletions(-)
This is a very important (and huge) release and has some important
fixes that landed since 0.0.18, including:
* A huge re-write of the elastic etcd clustering code base
* A significant improvement in the type unification algorithm
* An important import/class scoping bug was discovered and fixed
* New mcl functions and resource improvements
And much more...
What comes next is just polish, new features and small bug fixes!
DOWNLOAD
Prebuilt binaries are available here:
https://github.com/purpleidea/mgmt/releases/tag/0.0.19
NEWS
* A giant etcd re-write was completed and merged. The elastic
clustering algorithm is not perfect, however it should suffice for most
use cases, and it's always possible to point mgmt at an external etcd
cluster if you don't understand the limitations of the automatic
clustering algorithm. The important part is that the core code is much
cleaner now, so hopefully races and bugs of ignorance are gone now. :)
* I found an unfortunate bug in the type unification algorithm that
severely impacted performance for some types of code bases. This is now
fixed, and I hope we should not experience problems again! Special
thanks to Sam for talking me through the problem and understanding the
space better! Woo \o/
* An important import/class scoping bug was fixed. Thanks to Nicolas
for the bug report. We also added tests for this too!
* Nicolas also added our first os detection function. os.is_debian and
os.is_redhat are now in core. Get your favourite os added today!
* The polymorphic len function can also check str length.
* The exec resource got a big cleanup. It also learned the interrupt
trait so that long running commands can be forcefully killed if need
be.
* A fancy new test infra for testing functions over time was added.
Anytime we want to check our individual FRP functions are working as
expected, this is an easy way to add a test. This way, if we ever find
a bug, we can drop in a test with the fix. This actually helped find a
very subtle bug in readfile that nobody had experienced yet!
* File res with state => exists but no content now performs as
expected.
* Improved send/recv, since it was neglected a bit. Hopefully it ends
up being a useful primitive.
* Added a new synchronization primitive that I'm called
SubscribedSignal. I found it very useful for building some of my code,
and I hope you'll find it useful too. I'd offer it upstream to the sync
package if Google didn't force their crappy CLA nonsense on everyone
who wanted to send a patch. :/ Death by 1000 paper cuts, I guess.
* Added a match function in the new regexp core package. Try it out and
add some more functions!
* Wouter has been testing mgmt and filling all sorts of useful bug
reports. We fixed at least one related to a report, and more are
planned for the next release. Wouter also sent in one cleanup patch to
remove some dead code. Welcome to the project!
* We're looking for help writing Amazon, Google, DigitalOcean, etc
resources if anyone is interested, reach out to us. Particularly if
there is support from those organizations as well.
* Many other bug fixes, changes, etc...
* See the git log for more NEWS, and for anything notable I left out!
BUGS
* There are a few known issues, in particular with some over eager
checking done in the Validate portion of two resources, that should
actually be runtime checks in CheckApply. As a result, if you intend to
change some state during the graph execution, the resource won't see
it. These should be very easy to fix if someone is interested in
writing the patch!
TALKS
* James will be presenting at this year's OSDC in Berlin. There will be
a workshop: https://osdc.de/events/mgmt-config-workshop/
and a talk:
https://osdc.de/events/mgmt-config-the-future-of-your-autonomous-datacentre/
Sign up soon if you want to guarantee a spot, as they're limited!
MISC
We're still looking for new contributors, and there are easy, medium
and hard issues available! You're also welcome to suggest your own!
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
you'd like help getting started! For details please see:
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
tribute-to-the-project-if-i-dont-know-golang
Many tagged #mgmtlove issues exist:
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
3Amgmtlove
MENTORING
We offer mentoring for new golang/mgmt hackers who want to get
involved. This is fun and friendly! You get to improve your skills,
and we get some patches in return. Ping me off-list for details.
THANKS
Thanks (alphabetically) to everyone who contributed to the latest
release:
Adam Sigal, Felix Frank, James Shubin, Jonathan Gold, Michael Schubert,
Mitch Fossen, Nicolas Charles, Wouter Dullaert
We had 8 unique committers since 0.0.18, and have had 63 overall.
run 'git log 0.0.18..0.0.19' to see what has changed since 0.0.18
Happy hacking,
James
@purpleidea

145
docs/release-notes/0.0.20 Normal file
View File

@@ -0,0 +1,145 @@
I've just released version 0.0.20 of mgmt!
> 295 files changed, 8585 insertions(+), 1413 deletions(-)
This was a very challenging release but it includes many useful changes
since 0.0.19, including:
* Function values / lambdas exist and are first-class
* Over 70 new tests have been added!
* Core functions, classes, and globals can now be written in mcl code
* A new compiler "Ordering" step was added
And much more...
DOWNLOAD
Prebuilt binaries are available here:
https://github.com/purpleidea/mgmt/releases/tag/0.0.20
NEWS
* After a long and challenging road, I finally got function values and
lambdas merged. If you find any bugs, please let me know! You can now
treat functions as first-class values, and even use them as closures by
capturing variable state.
* Over 70 new tests were added, most of which test the behaviour of the
new functions.
* Core packages always allowed you to write new functions in pure
golang, but now you can implement new functions, classes, and even
variables in native mcl code! It's still desirable and perhaps
efficient in some cases to want native golang implementations, but all
good programs self-host some of their stdlib in their own language
eventually.
* A new compiler step called "Ordering" was added. It's hidden inside
the SetScope step, but now lets us generate code ordering graphs and
determine exactly what to run first.
* Light copying of Node's allows more correct and efficient function
graphs that can share common vertices and edges. For example, if two
different closures capture a variable $x, they'll both use the same
copy when running the function, since the compiler can prove if they're
identical.
* Improved the type system slightly to allow advanced type comparisons.
* The type unification algorithm was improved. Hopefully it should
solve all scenarios without needing the recursive solver which was very
slow. If you find a case that isn't speedy, please let us know!
* Added subtest listing by using -short and -v in a test. This lets you
know what's available and how to run individual sub tests easily.
* Support for the systemd STATE_DIRECTORY and xdg cache dir was added
by new contributor John! Thanks!
* New contributor Adam added a pgraph test.
* A bug in the systemd mount resource was fixed. Hopefully it should
work correctly now.
* A bug that prevented us from allowing nested system imports was
fixed. Nest away! This will pave the way for us to automatically import
most of the golang standard library by doing: import "golang/regexp" or
golang/whatever".
* Added an example showing that unicode is allowed in strings.
* Fixed a rare race in the engine.
* Added some new core functions including math.mod and datetime
improvements.
* Changed the API to remove the use of --lang. This avoids the
stuttering.
* Moved to golang 1.11 and etcd 3.3.13. The later includes a fix for an
un-catchable error scenario which we fixed in etcd.
* Improved the pgraph library significantly so that we can generate
better graphs with accurate vertices based on the vertex pointers.
* Added ArchLinux OS family detection.
* We're looking for help writing Amazon, Google, DigitalOcean, etc
resources if anyone is interested, reach out to us. Particularly if
there is support from those organizations as well.
* Many other bug fixes, changes, etc...
* See the git log for more NEWS, and for anything notable I left out!
BUGS
* There are a few known issues with some disabled code paths in the new
function value code. These issues don't *need* to be fixed, but if they
are, then we should see a slight performance increase. Happy to have
someone dig into these, and they shouldn't bother anyone.
* Some of the pre-built binaries might not work on your system. We need
to start building them with the right dependencies so that `ldd`
related things are happy. For now, please try building yourself if the
build doesn't work for you, or help improve our build system.
TALKS
I'll be giving a talk at an upcoming mini-conference in Montreal. If
you're interested in attending, please let me know.
If you'd like to give an mgmt talk somewhere, please let me know!
MISC
We're still looking for new contributors, and there are easy, medium
and hard issues available! You're also welcome to suggest your own!
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
you'd like help getting started! For details please see:
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
tribute-to-the-project-if-i-dont-know-golang
Many tagged #mgmtlove issues exist:
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
3Amgmtlove
MENTORING
We offer mentoring for new golang/mgmt hackers who want to get
involved. This is fun and friendly! You get to improve your skills,
and we get some patches in return. Ping me off-list for details.
THANKS
Thanks (alphabetically) to everyone who contributed to the latest
release:
Adam Sigal, Christian Rebischke, Felix Frank, James Shubin, Jan
Martens, Johan Bloemberg, John Hooks, Ward Vandewege
We had 8 unique committers since 0.0.19, and have had 67 overall.
run 'git log 0.0.19..0.0.20' to see what has changed since 0.0.19
Happy hacking,
James
@purpleidea

133
docs/release-notes/0.0.21 Normal file
View File

@@ -0,0 +1,133 @@
I've just released version 0.0.21 of mgmt!
> 178 files changed, 4351 insertions(+), 829 deletions(-)
This was a very lonely release but it includes some very useful
additions since 0.0.20, including:
* The first mgmt meme!
* Working distro packages for Fedora, Debian, Ubuntu and Arch!
* Reversible resources!
* A deploy package to let you read files from the active deploy
* Improved file resource behaviours
And much more...
DOWNLOAD
Prebuilt binaries are available here:
https://github.com/purpleidea/mgmt/releases/tag/0.0.21
NEWS
* Someone made a cool mgmt meme. Look in art/mgmt_*_meme.jpg
* Distro packages are now built properly, so they should all work now.
We have builds for Fedora-30, Fedora-29, Debian-10, Ubuntu-Bionic, and
Archlinux. If you'd like a build for a different distro/version please
let me know.
* We finally got rid of the old `Compare(...) bool` API, and moved to
`Cmp(...) error`. We'll now get more useful information from Res
compares when they differ. Thanks to new contributor Donald Bakong for
working on this. He's ramping up his golang contributions, so we expect
to see more from him in the future!
* We now have reversible resources. Basically if you create a resource
and specify the reverse metaparam, eg: `Meta:reverse => true`, then if
a resource is removed (either because a new version of code doesn't
have it anymore OR more importantly if it was inside an `if` block that
became false) then the engine will perform some "reverse" action for
it. For a file, if it was added, we'll remove it. If it was edited,
we'll undo the edit. If we added ugo+w, we'll remove that. And so on.
The engine bits are done, and as well so have the file resource bits.
It should be easy to add this for any other resource where it makes
sense. This will likely be a very powerful feature that we use a lot.
* The file resource was changed slightly so that by default the "state"
is undefined. As a result, if you want a file to be created and none is
present, you need to specify the state. Otherwise specifying "content"
will only edit a file if it already exists, and otherwise be an error.
It turns out this is actually a better behaviour, even if it's not
necessarily intuitive for puppet users. It turns out it simplifies the
code drastically and makes the reversible file resource much more
logical. It seems that Puppet and Ansible got this wrong, but Cfengine
got it right. IIRC. Do you agree? (Look at the code!)
* We now catch CR \r characters in code so that you don't wonder why
the compiler is telling you about unexpected whitespace. This should
make your life easier.
* You can now read files from within the deploy. This can be used for
templates or anything else. This was one of the last missing things
that was blocking me from writing useful mcl modules.
* Fixed a copy-pasta bug where the != operator (for strings only) was
actually doing an ==. Woops! The good news is that we've been shaking
out silly bugs because I've been using mgmt more and more. Hopefully
there aren't any woops ones like this left!
* A bunch of function, class, and include tests were added. We're
getting really well tested!
* We're looking for help writing Amazon, Google, DigitalOcean, etc
resources if anyone is interested, reach out to us. Particularly if
there is support from those organizations as well.
* Many other bug fixes, changes, etc...
* See the git log for more NEWS, and for anything notable I left out!
BUGS
* Function values as args don't work yet. This is blocking us from
implementing functions like map/reduce/filter, because they'd want to
receive a function as input. TBH, I'm a bit tunnel-visioned on this,
because I'm not the compilers genius that you are. If you can help,
please let me know. I'll be posting a bunch of test cases that show
what's needed shortly.
* Three patches have been submitted to mkosi to support the image
building I've been doing. They're not merged yet, so you'll have to
apply them yourself if you want to make your own distro images. This
isn't a major requirement anyone should have, but if they're not
merged, we'll store them here and apply them as needed.
TALKS
I'll be in Belgium for FOSDEM and CfgMgmtCamp in 2020. If I'm lucky
I'll have at least one mgmt presentation. I might also consider going
to DevConf.cz if I get a talk accepted. Feel to ping me if you'd like
to hack, get consulting, training, etc while I'm in Europe!
MISC
We're still looking for new contributors, and there are easy, medium
and hard issues available! You're also welcome to suggest your own!
Please join us in #mgmtconfig on Freenode IRC, or ping this list if
you'd like help getting started! For details please see:
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
tribute-to-the-project-if-i-dont-know-golang
Many tagged #mgmtlove issues exist:
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
3Amgmtlove
MENTORING
We offer mentoring for new golang/mgmt hackers who want to get
involved. This is fun and friendly! You get to improve your skills,
and we get some patches in return. Ping me off-list for details.
THANKS
Thanks (alphabetically) to everyone who contributed to the latest
release:
bjanssens, Donald Bakong, James Shubin
We had 3 unique committers since 0.0.20, and have had 69 overall.
run 'git log 0.0.20..0.0.21' to see what has changed since 0.0.20
Happy hacking,
James
@purpleidea

160
docs/release-notes/0.0.22 Normal file
View File

@@ -0,0 +1,160 @@
I've just released version 0.0.22 of mgmt!
> 579 files changed, 17984 insertions(+), 3136 deletions(-)
Compared to the last release, this is a monster. Previously:
> 178 files changed, 4351 insertions(+), 829 deletions(-)
I apologize for not doing a release earlier, but to be quite honest,
I've been busy, the people who are playing with mgmt are doing their
own builds anyways, and there are still some unimplemented, pre-
production features missing.
Also, I started a new job. News on my blog and how (if at all) it
affects mgmt is there.
This is still an incredibly important release, so let's cover some of
the points!
* This is the last release before we switch to go.mod
* IRC channel moved to #mgmtconfig on libera.chat after Freenode died
* New resources including tftp, dhcp, and http (all as servers, wow!)
* New string interpolation implementation with many tests
* Resource fields can accept complex structs and other types now
* Improved type unification solver and new invariants like generator
* A new polymorphic function API interface
And much more...
DOWNLOAD
Prebuilt binaries are NOT available here for this release:
https://github.com/purpleidea/mgmt/releases/tag/0.0.22
NEWS
* The file resource supports building files from other "fragments".
This is magic and automatic and real-time. Docs and examples are in the
repo.
* The file resource (and others) have a new trait and queryable API to
make decisions based on what other resources are in the graph.
* The file resource has a "purge" option to remove unmanaged files from
a managed directory.
* A lot of built-in functions are auto-generated from the stdlib. Most
things you would want are now present, particularly if they're pure
functions.
* There's a new consul KV resource.
* File resources support symbolic modes now!
* New tftp related resources are now present. They're great and I use
them to provision things!
* A docker image resource was added.
* First-class constants now exist. So you can do
$const.res.file.state.exists instead of typing "exists" which is prone
to typos. This is more verbose, but it's safer if that's your priority.
* We found a bug with fuzzing! Cool, thanks Patrick!
* We have dhcp server related resources. This is pretty cool when
combined with the tftp resource and you can provision a lot of stuff
from your laptop and one binary now.
* We also have http server resources. Combined with the tftp and dhcp
resources mgmt starts to look like a powerful tool to greenfield a new
datacentre and then take over and manage it continuous. All from a
single, type-safe, code base. Of course you can do other things with
this, and I'm looking forward to seeing the ideas that I haven't
thought of yet! PS: An http:ui has been partially implemented too. Ping
if you want to know more.
* Resource fields couldn't previously accept anonymous structs as types
because of how golang built its reflect library. Joe found an elegant
workaround, thanks!
* The type unification solver was improved to support some new
invariants. This makes a lot of new things possible, and was done to
support new complex functions including the eventual addition of map,
reduce, and filter. One of the new invariants is a "generator"
invariant, so that unification can take into account the entire
relevant parts of the AST. It's not a textbook CS implementation, but
it's based on sound theory I think, and it seems to work great. If you
find an edge case, please let us know.
* The polymorphic function interface was changed to use the new
unification logic. This makes a lot more sense. All the functions have
been ported to the new interface.
* 42
* We're looking for help writing Amazon, Google, DigitalOcean, etc
resources if anyone is interested, reach out to us. Particularly if
there is support from those organizations as well.
* Many other bug fixes, changes, etc...
* See the git log for more NEWS, and for anything notable I left out!
BUGS
* Function values as args don't work yet. This is blocking us from
implementing functions like map/reduce/filter, because they'd want to
receive a function as input. TBH, I'm a bit tunnel-visioned on this,
because I'm not the compilers genius that you are. If you can help,
please let me know. I'll be posting a bunch of test cases that show
what's needed shortly.
(This is the exact message I wrote last time. I've made a lot of
progress since then, but motivation here has been low. Reach out if you
can help.)
TALKS
Hopefully CfgMgmtCamp in 2022 will be back on. If we're lucky and safe,
maybe I can travel there. TBD... Feel to ping me if you'd like to hack,
talk, whatever if I'm in Europe.
MISC
We're still looking for new contributors, and there are easy, medium
and hard issues available! You're also welcome to suggest your own!
Please join us in #mgmtconfig on Libera IRC, or ping this list if
you'd like help getting started! For details please see:
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
tribute-to-the-project-if-i-dont-know-golang
Many tagged #mgmtlove issues exist:
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
3Amgmtlove
Although asking in IRC is the best way to find something to work on.
MENTORING
We offer mentoring for new golang/mgmt hackers who want to get
involved. This is fun and friendly! You get to improve your skills,
and we get some patches in return. Ping me off-list for details.
THANKS
Thanks (alphabetically) to everyone who contributed to the latest
release:
Adam Sigal, Ahmed Al-Hulaibi, David Randall, Derek Buckley, Donald
Bakong, Felix Frank, Francois Rompre-Lanctot, Ivan Pejić, James Shubin,
Jean-Philippe Evrard, Jimmy Tang, Joe Groocock, Jonathan Gold, Julien
Pivotto, Kenneth Hoste, Matthew Lesko-Krleza, Patrick Meyer, viq, Yohan
Belval
We had 19 unique committers since 0.0.21, and have had 82 overall.
run 'git log 0.0.21..0.0.22' to see what has changed since 0.0.21
Happy hacking,
James
@purpleidea

102
docs/release-notes/0.0.23 Normal file
View File

@@ -0,0 +1,102 @@
I've just released version 0.0.23 of mgmt!
> 424 files changed, 7051 insertions(+), 2256 deletions(-)
This is a fairly quiet release, and I'm mostly doing this to have a
permanent tag before I start really breaking git master.
I'd like to apologize for things being kind of quiet. I've had to focus
on life and making money to pay my bills, and I've been struggling a
bit to complete some of the tougher algorithmic parts that I think are
necessary for a solid MVP. Hopefully I will succeed, but to do so, it's
going to be easier if I break git master and then sort things out
later.
I'm feeling optimistic about the future, although help with some
complex concurrent programming would certainly be appreciated.
With that, here are a few highlights from the release:
* We're using go.mod, but I'm not keeping it up-to-date regularly yet.
* We have an os.system(`cmd`) function!
* We replaced go-bindata with the embed package.
* We added a hetzner vm resource.
* We did a giant lang/ package refactor.
* Printf supports %v now (any type).
And much more...
DOWNLOAD
Prebuilt binaries are NOT available here for this release:
https://github.com/purpleidea/mgmt/releases/tag/0.0.23
NEWS
* We have our Libera IRC channel bridged to Matrix, so you can join
that way too.
* We're looking for help writing Amazon, Google, DigitalOcean, Hetzner,
etc, resources if anyone is interested, reach out to us. Particularly
if there is support from those organizations as well.
* Many other bug fixes, changes, etc...
* See the git log for more NEWS, and for anything notable I left out!
BUGS
* Function values as args don't work yet. This is blocking us from
implementing functions like map/reduce/filter, because they'd want to
receive a function as input.
(This is the exact message I wrote last time. I've made a lot of
progress since then, but motivation here has been low. Reach out if you
can help.)
TALKS
I don't have anything planned until CfgMgmtCamp 2024. If you'd like to
book me for a private event, or sponsor my travel for your conference,
please let me know.
MISC
We're still looking for new contributors, and there are easy, medium
and hard issues available! You're also welcome to suggest your own!
Please join us in #mgmtconfig on Libera IRC or Matrix, or ping this
list if you'd like help getting started! For details please see:
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
tribute-to-the-project-if-i-dont-know-golang
Many tagged #mgmtlove issues exist:
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
3Amgmtlove
Although asking in IRC is the best way to find something to work on.
MENTORING
We offer mentoring for new golang/mgmt hackers who want to get
involved. This is fun and friendly! You get to improve your skills,
and we get some patches in return. Ping me off-list for details.
THANKS
Thanks (alphabetically) to everyone who contributed to the latest
release:
dantefromhell, James Shubin, Jef Masereel, Joe Groocock, Samuel
Gélineau
We had 5 unique committers since 0.0.22, and have had 85 overall.
run 'git log 0.0.22..0.0.23' to see what has changed since 0.0.22
Happy hacking,
James
@purpleidea

161
docs/release-notes/0.0.24 Normal file
View File

@@ -0,0 +1,161 @@
I've just released version 0.0.24 of mgmt!
> 600 files changed, 13622 insertions(+), 6907 deletions(-)
This is a huge and hugely important release! It has been a long time
coming. We have lambdas! I could not have done this without the
unrelentingly supportive and brilliant Samuel Gélineau.
Getting past the blockers and tricky code changes was all thanks to
him. Cleanups, polish and making it more golang idiomatic was my doing.
(The easy stuff.) If Sam wanted to be a golang expert, he could have
done it all, but it was more sensible than I do all the mundane and
filler stuff.
With that, here are a few highlights from the release:
* We have working lambdas, including iter.map =D
* We have a new function engine!
* We have improved type unification!
* We have an improved resource engine!
* We have improved many of the internal API's.
* We have so many tests.
And much more...
DOWNLOAD
Prebuilt binaries are NOT available here for this release:
https://github.com/purpleidea/mgmt/releases/tag/0.0.24
NEWS
* Our main tests are now in the excellent .txtar format. This makes
things much easier to manage.
* The iter.map function can be named as such due to parser tricks! No
need to name it xmap anymore!
* The unification solver has been greatly improved. We can infer a lot
more about function types.
* The resources API uses the context package for closing Watch and
returning early from CheckApply. The next step would be to remove all
the resource-specific timeout code and make that a metaparam.
* A new sync primitive has been added in case you'd like to use it
somewhere. We'd love help adding an even more complex one. Look in
util/sync.go for more information.
* Sam added some beautiful type inference debugging that makes things
easier for those familiar with the standard literature.
* An important bug in standalone etcd has been fixed. While embedded
etcd and automatic clustering isn't "supported" (it's buggy) the
status-quo of using your own etcd cluster is stable, and you can even
use the embedded etcd server in standalone mode...
* This means you can run `mgmt etcd` and get the standard etcd binary
behviour that you'd get from running `etcd` normally. This makes it
easy to use both together since you only need to transport one binary
around. (And maybe mgmt will do that for you!)
* I fixed and cleaned up some sketchy code in the resource engine. I
had been unmotivated to fix this for a while because I really wanted
lambdas first, but now that they are in, I took a good look at the
code, made some fixes, and I'm really happy with it now.
* Metaparams are appropriately stateful between graph switches now.
Retry is the easy example. Limit/Burst need to be ported if you care
about these fine details.
* A RetryReset metaparam has been added. This is another good example
of how powerful metaparams are and how much potential there is for
building systems with these and future ones.
* A bunch of internal API's have been updated. This makes it better for
function and resource writers! Some GAPI changes also got pushed
through that make things clearer for those reading code.
* We have a listlookup function. It's still missing syntactic sugar
though!
* Our new function graph engine is called "dage". I think it's pretty
clever. There's a chance there is still a bug inside, but it's unclear.
Please report any issues. If you have some large machines I can test
very large and fast graphs on, please let me know.
* Lambdas really work now! The txn and ref/gc code is pretty fantastic.
* Many bugs have been killed!
* We're looking for help writing Amazon, Google, DigitalOcean, Hetzner,
etc, resources if anyone is interested, reach out to us. Particularly
if there is support from those organizations as well.
* Many other bug fixes, changes, etc...
* See the git log for more NEWS, and for anything notable I left out!
BUGS/TODO
* Function values getting _passed_ to resources doesn't work yet, but
it's not a blocker, but it would definitely be useful. We're looking
into it.
* Function graphs are unnecessarily dynamic. We might make them more
static so that we don't need as many transactions. This is really a
compiler optimization and not a bug, but it's something important we'd
like to have.
* Running two Txn's during the same pause would be really helpful. I'm
not sure how much of a performance improvement we'd get from this, but
it would sure be interesting to build. If you want to build a fancy
synchronization primitive, then let us know! Again this is not a bug.
TALKS
I don't have anything planned until CfgMgmtCamp 2024. If you'd like to
book me for a private event, or sponsor my travel for your conference,
please let me know.
MISC
We're still looking for new contributors, and there are easy, medium
and hard issues available! You're also welcome to suggest your own!
Please join us in #mgmtconfig on Libera IRC or Matrix, or ping this
list if you'd like help getting started! For details please see:
https://github.com/purpleidea/mgmt/blob/master/docs/faq.md#how-do-i-con
tribute-to-the-project-if-i-dont-know-golang
Many tagged #mgmtlove issues exist:
https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%
3Amgmtlove
Although asking in IRC/matrix is the best way to find something to work
on.
MENTORING
We offer mentoring for new golang/mgmt hackers who want to get
involved. This is fun and friendly! You get to improve your skills,
and we get some patches in return. Ping me off-list for details.
THANKS
Thanks (alphabetically) to everyone who contributed to the latest
release:
James Shubin, Kaushal, Laurent Indermuehle, Ofek Atar, Samuel Gélineau
We had 5 unique committers since 0.0.23, and have had 88 overall.
run 'git log 0.0.23..0.0.24' to see what has changed since 0.0.23
Happy hacking,
James
@purpleidea

71
docs/release-notes/0.0.9 Normal file
View File

@@ -0,0 +1,71 @@
I've just released 0.0.9!
Release tags are now signed with my GPG key.
From now on, I'm going to try and write a release email to the list
with information about each release. Here we go!
NEWS
* There were far more commits that went into this release than I was
expecting. I delayed a while because of some nasty races and deadlocks
I encountered when cleaning some cruft out of the engine.
134 files changed, 5394 insertions(+), 2168 deletions(-)
* We grew initial integration with Prometheus thanks to new contributor
`roidelapluie`.
* The file resource can now chown/chmod files! Thanks to new
contributor `mildred`.
* The virt resource can now hotplug/hotunplug cpus. This made for some
dope demos at CfgMgmtCamp. Special thanks to pkrempa for helping me
with the libvirt API.
* Native testing of all the golang code is now enabled again. I broke
this when I split mgmt up into multiple packages. (Sorry!) Golang tests
in foo_test.go should now run and be tested automatically. I'd
especially like it if someone wrote some for pgraph/pgraph.go:
GraphSync().
* Documentation has been significantly improved. We have a resource
guide (for writing new native resources) and much more. We also now
build the docs as a RTD project:
https://mgmt.readthedocs.io/en/master/
Patches welcome to make it style as pretty as GitHub markdown does.
* See the git log for more NEWS, and sorry for anything notable I left
out!
BUGS
* I hope to resolve this soon, but it shouldn't block further
development on git master for now. The issue: if you make extremely
high speed graph changes to graphs involving the virt resource, it can
eventually cause a panic. This is being tracked here:
https://github.com/libvirt/libvirt-go/issues/7 (help welcome!)
GLUSTERFS
I've started working on some stuff for Glusterd so that it can embed
mgmt as a lib, and use the resource model to simplify cluster
management. This will involve writing Gluster resources in mgmt
(volume, brick, etc...) and all sorts of other fun stuff. If you'd like
to participate, please LMK!
MISC
We're still looking for new contributors, and there are easy, medium and hard issues available! You're also welcome to suggest your own! Please join us in #mgmtconfig on Freenode IRC, or ping this list if you'd like help getting started!
THANKS
We had 12 unique committers since 0.0.8, and have had 29 overall.
Thanks (alphabetically) to everyone who contributed to the latest
release: Daniele Sluijters, Daniel P. Berrange, Felix Frank, goberghen,
James Shubin, Julien Pivotto, Kaushal M, Lars Kulseng, Mildred Ki'Lya,
Sean Jones, Steve Milner, Tom Ritserveldt.
Happy hacking,
James
@purpleidea

View File

@@ -60,10 +60,7 @@ it is not specified, but others cannot, and some might poorly infer if the
struct name is ambiguous. struct name is ambiguous.
If you'd like your resource to be accessible by the `YAML` graph API (GAPI), If you'd like your resource to be accessible by the `YAML` graph API (GAPI),
then you'll need to include the appropriate YAML fields as shown below. This is then you'll need to include the appropriate YAML fields as shown below.
used by the `Puppet` compiler as well, so make sure you include these struct
tags if you want existing `Puppet` code to be able to run using the `mgmt`
engine.
#### Example #### Example
@@ -177,10 +174,10 @@ this. In other words, you should expect `Validate` to have run first, but you
shouldn't allow `Init` to dangerously `rm -rf /$the_world` if your code only shouldn't allow `Init` to dangerously `rm -rf /$the_world` if your code only
checks `$the_world` in `Validate`. Remember to always program safely! checks `$the_world` in `Validate`. Remember to always program safely!
### Close ### Cleanup
```golang ```golang
Close() error Cleanup() error
``` ```
This is called to cleanup after the resource. It is usually not necessary, but This is called to cleanup after the resource. It is usually not necessary, but
@@ -192,8 +189,8 @@ loop.
#### Example #### Example
```golang ```golang
// Close runs some cleanup code for this resource. // Cleanup is run by the engine to clean up after the resource is done.
func (obj *FooRes) Close() error { func (obj *FooRes) Cleanup() error {
err := obj.conn.Close() // close some internal connection err := obj.conn.Close() // close some internal connection
obj.someMap = nil // free up some large data structure from memory obj.someMap = nil // free up some large data structure from memory
return err return err
@@ -206,7 +203,7 @@ on an error if something went wrong.
### CheckApply ### CheckApply
```golang ```golang
CheckApply(apply bool) (checkOK bool, err error) CheckApply(ctx context.Context, apply bool) (checkOK bool, err error)
``` ```
`CheckApply` is where the real _work_ is done. Under normal circumstances, this `CheckApply` is where the real _work_ is done. Under normal circumstances, this
@@ -215,7 +212,8 @@ should return: `(true, nil)`. If the `apply` variable is set to `true`, then
this means that we should then proceed to run the changes required to bring the this means that we should then proceed to run the changes required to bring the
resource into the correct state. If the `apply` variable is set to `false`, then resource into the correct state. If the `apply` variable is set to `false`, then
the resource is operating in _noop_ mode and _no operational changes_ should be the resource is operating in _noop_ mode and _no operational changes_ should be
made! made! The ctx should be monitored in case a shutdown has been requested. This
may be used if a timeout occurred, or if the user shutdown the engine.
After having executed the necessary operations to bring the resource back into After having executed the necessary operations to bring the resource back into
the desired state, or after having detected that the state was incorrect, but the desired state, or after having detected that the state was incorrect, but
@@ -234,7 +232,7 @@ to `CheckApply`.
```golang ```golang
// CheckApply does the idempotent work of checking and applying resource state. // CheckApply does the idempotent work of checking and applying resource state.
func (obj *FooRes) CheckApply(apply bool) (bool, error) { func (obj *FooRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
// check the state // check the state
if state_is_okay { return true, nil } // done early! :) if state_is_okay { return true, nil } // done early! :)
@@ -277,7 +275,7 @@ will likely find the state to now be correct.
### Watch ### Watch
```golang ```golang
Watch() error Watch(ctx context.Context) error
``` ```
`Watch` is a main loop that runs and sends messages when it detects that the `Watch` is a main loop that runs and sends messages when it detects that the
@@ -304,23 +302,25 @@ If the resource is activated in `polling` mode, the `Watch` method will not get
executed. As a result, the resource must still work even if the main loop is not executed. As a result, the resource must still work even if the main loop is not
running. running.
You must make sure to cleanup any running code or goroutines before Watch exits.
#### Select #### Select
The lifetime of most resources `Watch` method should be spent in an infinite The lifetime of most resources `Watch` method should be spent in an infinite
loop that is bounded by a `select` call. The `select` call is the point where loop that is bounded by a `select` call. The `select` call is the point where
our method hands back control to the engine (and the kernel) so that we can our method hands back control to the engine (and the kernel) so that we can
sleep until something of interest wakes us up. In this loop we must wait until sleep until something of interest wakes us up. In this loop we must wait until
we get a shutdown event from the engine via the `<-obj.init.Done` channel, which we get a shutdown event from the engine via the `<-ctx.Done()` channel, which
closes when we'd like to shut everything down. At this point you should cleanup, closes when we'd like to shut everything down. At this point you should cleanup,
and let `Watch` close. and let `Watch` close.
#### Events #### Events
If the `<-obj.init.Done` channel closes, we should shutdown our resource. When If the `<-ctx.Done()` channel closes, we should shutdown our resource. When we
When we want to send an event, we use the `Event` helper function. This want to send an event, we use the `Event` helper function. This automatically
automatically marks the resource state as `dirty`. If you're unsure, it's not marks the resource state as `dirty`. If you're unsure, it's not harmful to send
harmful to send the event. This will ultimately cause `CheckApply` to run. This the event. This will ultimately cause `CheckApply` to run. This method can block
method can block if the resource is being paused. if the resource is being paused.
#### Startup #### Startup
@@ -347,7 +347,7 @@ sending out erroneous `Event` messages to keep things alive until it finishes.
```golang ```golang
// Watch is the listener and main loop for this resource. // Watch is the listener and main loop for this resource.
func (obj *FooRes) Watch() error { func (obj *FooRes) Watch(ctx context.Context) error {
// setup the Foo resource // setup the Foo resource
var err error var err error
if err, obj.foo = OpenFoo(); err != nil { if err, obj.foo = OpenFoo(); err != nil {
@@ -371,7 +371,7 @@ func (obj *FooRes) Watch() error {
case err := <-obj.foo.Errors: case err := <-obj.foo.Errors:
return err // will cause a retry or permanent failure return err // will cause a retry or permanent failure
case <-obj.init.Done: // signal for shutdown request case <-ctx.Done(): // signal for shutdown request
return nil return nil
} }
@@ -553,11 +553,6 @@ ready to detect changes.
Event sends an event notifying the engine of a possible state change. It is Event sends an event notifying the engine of a possible state change. It is
only called from within `Watch`. only called from within `Watch`.
### Done
Done is a channel that closes when the engine wants us to shutdown. It is only
called from within `Watch`.
### Refresh ### Refresh
Refresh returns whether the resource received a notification. This flag can be Refresh returns whether the resource received a notification. This flag can be
@@ -582,15 +577,15 @@ It is only called from within `CheckApply`.
World provides a connection to the outside world. This is most often used for World provides a connection to the outside world. This is most often used for
communicating with the distributed database. It can be used in `Init`, communicating with the distributed database. It can be used in `Init`,
`CheckApply` and `Watch`. Use with discretion and understanding of the internals `CheckApply` and `Watch`. Use with discretion and understanding of the internals
if needed in `Close`. if needed in `Cleanup`.
### VarDir ### VarDir
VarDir is a facility for local storage. It is used to return a path to a VarDir is a facility for local storage. It is used to return a path to a
directory which may be used for temporary storage. It should be cleaned up on directory which may be used for temporary storage. It should be cleaned up on
resource `Close` if the resource would like to delete the contents. The resource resource `Cleanup` if the resource would like to delete the contents. The
should not assume that the initial directory is empty, and it should be cleaned resource should not assume that the initial directory is empty, and it should be
on `Init` if that is a requirement. cleaned on `Init` if that is a requirement.
### Debug ### Debug
@@ -625,7 +620,7 @@ func init() { // special golang method that runs once
To support YAML unmarshalling for your resource, you must implement an To support YAML unmarshalling for your resource, you must implement an
additional method. It is recommended if you want to use your resource with the additional method. It is recommended if you want to use your resource with the
`Puppet` compiler. `yaml` compiler.
```golang ```golang
UnmarshalYAML(unmarshal func(interface{}) error) error // optional UnmarshalYAML(unmarshal func(interface{}) error) error // optional
@@ -680,10 +675,10 @@ receiving one. This can _only_ be done inside of the `CheckApply` function!
```golang ```golang
// inside CheckApply, probably near the top // inside CheckApply, probably near the top
if val, exists := obj.init.Recv()["SomeKey"]; exists { if val, exists := obj.init.Recv()["some_key"]; exists {
obj.init.Logf("the SomeKey param was sent to us from: %s.%s", val.Res, val.Key) obj.init.Logf("the some_key param was sent to us from: %s.%s", val.Res, val.Key)
if val.Changed { if val.Changed {
obj.init.Logf("the SomeKey param was just updated!") obj.init.Logf("the some_key param was just updated!")
// you may want to invalidate some local cache // you may want to invalidate some local cache
} }
} }
@@ -727,16 +722,16 @@ two calls, this is much more difficult. A common example is that a resource
might want to open a connection to `dbus` or `http` to do resource state testing might want to open a connection to `dbus` or `http` to do resource state testing
and applying. If the methods are combined, there's no need to open and close and applying. If the methods are combined, there's no need to open and close
them twice. A counter argument might be that you could open the connection in them twice. A counter argument might be that you could open the connection in
`Init`, and close it in `Close`, however you might not want that open for the `Init`, and close it in `Cleanup`, however you might not want that open for the
full lifetime of the resource if you only change state occasionally. full lifetime of the resource if you only change state occasionally.
2. Suppose you came up with a really good reason why you wanted the two methods 2. Suppose you came up with a really good reason why you wanted the two methods
to be separate. It turns out that the current `CheckApply` can wrap this easily. to be separate. It turns out that the current `CheckApply` can wrap this easily.
It would look approximately like this: It would look approximately like this:
```golang ```golang
func (obj *FooRes) CheckApply(apply bool) (bool, error) { func (obj *FooRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
// my private split implementation of check and apply // my private split implementation of check and apply
if c, err := obj.check(); err != nil { if c, err := obj.check(ctx); err != nil {
return false, err // we errored return false, err // we errored
} else if c { } else if c {
return true, nil // state was good! return true, nil // state was good!
@@ -746,7 +741,7 @@ func (obj *FooRes) CheckApply(apply bool) (bool, error) {
return false, nil // state needs fixing, but apply is false return false, nil // state needs fixing, but apply is false
} }
err := obj.apply() // errors if failure or unable to apply err := obj.apply(ctx) // errors if failure or unable to apply
return false, err // always return false, with an optional error return false, err // always return false, with an optional error
} }

View File

@@ -141,6 +141,24 @@ that variable (often with a `const`) rather than leaving a naked `bool` in the
code. For example, `x := MyFoo("blah", false)` is less clear than code. For example, `x := MyFoo("blah", false)` is less clear than
`const useMagic = false; x := MyFoo("blah", useMagic)`. `const useMagic = false; x := MyFoo("blah", useMagic)`.
### Empty slice declarations
When declaring a new empty slice, there are three different mechanisms:
1. `a := []string{}`
2. `var a []string`
3. `a := make([]string, 0)`
In general, we prefer the first method because we find that it is succinct, and
very readable. The third method is the least recommended because you're adding
extra data that a smart compiler could probably figure out on its own. There are
performance implications between these three methods, so unless your code is in
a fast path or memory constrained environment where this matters (and that you
ideally have proof of this) please use the methods as ordered as much as
possible.
### Consistent ordering ### Consistent ordering
In general we try to preserve a logical ordering in source files which usually In general we try to preserve a logical ordering in source files which usually

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package engine package engine

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,9 +13,21 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// +build !root //go:build !root
package engine package engine

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package engine package engine
@@ -42,7 +54,8 @@ type GroupableRes interface {
// grouping. This usually needs to be unique to your resource. // grouping. This usually needs to be unique to your resource.
GroupCmp(res GroupableRes) error GroupCmp(res GroupableRes) error
// GroupRes groups resource argument (res) into self. // GroupRes groups resource argument (res) into self. Callers of this
// method should probably also run SetParent.
GroupRes(res GroupableRes) error GroupRes(res GroupableRes) error
// IsGrouped determines if we are grouped. // IsGrouped determines if we are grouped.
@@ -54,8 +67,15 @@ type GroupableRes interface {
// GetGroup returns everyone grouped inside me. // GetGroup returns everyone grouped inside me.
GetGroup() []GroupableRes // return everyone grouped inside me GetGroup() []GroupableRes // return everyone grouped inside me
// SetGroup sets the grouped resources into me. // SetGroup sets the grouped resources into me. Callers of this method
// should probably also run SetParent.
SetGroup([]GroupableRes) SetGroup([]GroupableRes)
// Parent returns the parent groupable resource that I am inside of.
Parent() GroupableRes
// SetParent tells a particular grouped resource who their parent is.
SetParent(res GroupableRes)
} }
// AutoGroupMeta provides some parameters specific to automatic grouping. // AutoGroupMeta provides some parameters specific to automatic grouping.

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package engine package engine
@@ -21,6 +33,7 @@ import (
"fmt" "fmt"
"github.com/purpleidea/mgmt/pgraph" "github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util/errwrap"
) )
// ResCmp compares two resources by checking multiple aspects. This is the main // ResCmp compares two resources by checking multiple aspects. This is the main
@@ -106,6 +119,7 @@ func ResCmp(r1, r2 Res) error {
for k := range ix { for k := range ix {
// compare sub resources // compare sub resources
if err := ResCmp(ix[k], jx[k]); err != nil { if err := ResCmp(ix[k], jx[k]); err != nil {
//fmt.Printf("bad Cmp: %+v <> %+v for: %+v <> %+v err: %+v\n", r1, r2, ix[k], jx[k], err)
return err return err
} }
} }
@@ -121,14 +135,29 @@ func ResCmp(r1, r2 Res) error {
v1 := r1r.Recv() v1 := r1r.Recv()
v2 := r2r.Recv() v2 := r2r.Recv()
// XXX: Our Send/Recv in the lib/main.go doesn't seem to be
// pulling this in, so this always compares differently. We can
// comment it out for now, since it's not too consequential.
// XXX: Find out what the issue is and fix it for here and send.
// XXX: The below errors are commented out until this is fixed.
if (v1 == nil) != (v2 == nil) { // xor if (v1 == nil) != (v2 == nil) { // xor
return fmt.Errorf("recv params differ") //return fmt.Errorf("recv params differ")
} }
if v1 != nil && v2 != nil { if v1 != nil && v2 != nil {
// TODO: until we hit this code path, don't allow if len(v1) != len(v2) {
// comparing anything that has this set to non-zero //return fmt.Errorf("recv param lengths differ")
if len(v1) != 0 || len(v2) != 0 { }
return fmt.Errorf("recv params exist") for key, send1 := range v1 { // map[string]*engine.Send
send2, exists := v2[key]
if !exists {
//return fmt.Errorf("recv param key %s doesn't exist", key)
}
if (send1 == nil) != (send2 == nil) { // xor
//return fmt.Errorf("recv param key %s send differs", key)
}
if send1 != nil && send2 != nil && send1.Key != send2.Key {
//return fmt.Errorf("recv param key %s send key differs (%v != %v)", key, send1.Key, send2.Key)
}
} }
} }
} }
@@ -142,13 +171,17 @@ func ResCmp(r1, r2 Res) error {
s1 := r1s.Sent() s1 := r1s.Sent()
s2 := r2s.Sent() s2 := r2s.Sent()
// XXX: Our Send/Recv in the lib/main.go doesn't seem to be
// pulling this in, so this always compares differently. We can
// comment it out for now, since it's not too consequential.
// XXX: Find out what the issue is and fix it for here and recv.
// XXX: The below errors are commented out until this is fixed.
if (s1 == nil) != (s2 == nil) { // xor if (s1 == nil) != (s2 == nil) { // xor
return fmt.Errorf("send params differ") //return fmt.Errorf("send params differ")
} }
if s1 != nil && s2 != nil { if s1 != nil && s2 != nil {
// TODO: until we hit this code path, don't allow // TODO: reflect.DeepEqual?
// adapting anything that has this set to non-nil //return fmt.Errorf("send params exist")
return fmt.Errorf("send params exist")
} }
} }
@@ -261,14 +294,29 @@ func AdaptCmp(r1, r2 CompatibleRes) error {
v1 := r1r.Recv() v1 := r1r.Recv()
v2 := r2r.Recv() v2 := r2r.Recv()
// XXX: Our Send/Recv in the lib/main.go doesn't seem to be
// pulling this in, so this always compares differently. We can
// comment it out for now, since it's not too consequential.
// XXX: Find out what the issue is and fix it for here and send.
// XXX: The below errors are commented out until this is fixed.
if (v1 == nil) != (v2 == nil) { // xor if (v1 == nil) != (v2 == nil) { // xor
return fmt.Errorf("recv params differ") //return fmt.Errorf("recv params differ")
} }
if v1 != nil && v2 != nil { if v1 != nil && v2 != nil {
// TODO: until we hit this code path, don't allow if len(v1) != len(v2) {
// adapting anything that has this set to non-zero //return fmt.Errorf("recv param lengths differ")
if len(v1) != 0 || len(v2) != 0 { }
return fmt.Errorf("recv params exist") for key, send1 := range v1 { // map[string]*engine.Send
send2, exists := v2[key]
if !exists {
//return fmt.Errorf("recv param key %s doesn't exist", key)
}
if (send1 == nil) != (send2 == nil) { // xor
//return fmt.Errorf("recv param key %s send differs", key)
}
if send1 != nil && send2 != nil && send1.Key != send2.Key {
//return fmt.Errorf("recv param key %s send key differs (%v != %v)", key, send1.Key, send2.Key)
}
} }
} }
} }
@@ -282,13 +330,17 @@ func AdaptCmp(r1, r2 CompatibleRes) error {
s1 := r1s.Sent() s1 := r1s.Sent()
s2 := r2s.Sent() s2 := r2s.Sent()
// XXX: Our Send/Recv in the lib/main.go doesn't seem to be
// pulling this in, so this always compares differently. We can
// comment it out for now, since it's not too consequential.
// XXX: Find out what the issue is and fix it for here and recv.
// XXX: The below errors are commented out until this is fixed.
if (s1 == nil) != (s2 == nil) { // xor if (s1 == nil) != (s2 == nil) { // xor
return fmt.Errorf("send params differ") //return fmt.Errorf("send params differ")
} }
if s1 != nil && s2 != nil { if s1 != nil && s2 != nil {
// TODO: until we hit this code path, don't allow // TODO: reflect.DeepEqual?
// adapting anything that has this set to non-nil //return fmt.Errorf("send params exist")
return fmt.Errorf("send params exist")
} }
} }
@@ -320,9 +372,11 @@ func VertexCmpFn(v1, v2 pgraph.Vertex) (bool, error) {
return false, fmt.Errorf("v2 is not a Res") return false, fmt.Errorf("v2 is not a Res")
} }
if ResCmp(r1, r2) != nil { if err := ResCmp(r1, r2); err != nil {
//fmt.Printf("bad Cmp: %p %+v <> %p %+v err: %+v\n", r1, r1, r2, r2, err)
return false, nil return false, nil
} }
//fmt.Printf("ok Cmp: %p %+v <> %p %+v\n", r1, r1, r2, r2)
return true, nil return true, nil
} }
@@ -341,3 +395,55 @@ func EdgeCmpFn(e1, e2 pgraph.Edge) (bool, error) {
} }
return edge1.Cmp(edge2) == nil, nil return edge1.Cmp(edge2) == nil, nil
} }
// ResGraphMapper compares two graphs, and gives us a mapping from new to old
// based on the resource kind and name only. This allows us to know which
// previous resource might have data to pass on to the new version in the next
// generation.
// FIXME: Optimize this for performance since it runs a lot...
func ResGraphMapper(oldGraph, newGraph *pgraph.Graph) (map[RecvableRes]RecvableRes, error) {
mapper := make(map[RecvableRes]RecvableRes) // new -> old based on name and kind only?
cmp := func(r1, r2 Res) error {
if r1.Kind() != r2.Kind() {
return fmt.Errorf("kind differs")
}
if r1.Name() != r2.Name() {
return fmt.Errorf("name differs")
}
return nil
}
// XXX: run this as a topological sort or reverse topological sort?
for v := range newGraph.Adjacency() { // loop through the vertices (resources)
r, ok := v.(RecvableRes)
if !ok {
continue // skip
}
fn := func(vv pgraph.Vertex) (bool, error) {
rr, ok := vv.(Res)
if !ok {
return false, fmt.Errorf("not a Res")
}
if err := cmp(rr, r); err != nil {
return false, nil
}
return true, nil
}
vertex, err := oldGraph.VertexMatchFn(fn)
if err != nil {
return nil, errwrap.Wrapf(err, "VertexMatchFn failed")
}
if vertex == nil {
continue // skip (error?)
}
res, ok := vertex.(RecvableRes)
if !ok {
continue // skip (error?)
}
mapper[r] = res
}
return mapper, nil
}

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package engine package engine
@@ -81,6 +93,7 @@ func ResCopy(r CopyableRes) (CopyableRes, error) {
if !ok { if !ok {
return nil, fmt.Errorf("resource wasn't groupable") return nil, fmt.Errorf("resource wasn't groupable")
} }
g2.SetParent(dst) // store who my parent is
grouped = append(grouped, g2) grouped = append(grouped, g2)
} }
dst.SetGroup(grouped) dst.SetGroup(grouped)

33
engine/doc.go Normal file
View File

@@ -0,0 +1,33 @@
// Mgmt
// Copyright (C) 2013-2024+ 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// Package engine represents the implementation of the resource engine that runs
// the graph of resources in real-time. This package has the common imports that
// most consumers use directly.
package engine

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package engine package engine

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package engine package engine
@@ -26,4 +38,7 @@ func (e Error) Error() string { return string(e) }
const ( const (
// ErrClosed means we couldn't complete a task because we had closed. // ErrClosed means we couldn't complete a task because we had closed.
ErrClosed = Error("closed") ErrClosed = Error("closed")
// ErrBackPoke means we're postponing due to a needed backpoke.
ErrBackPoke = Error("backpoke")
) )

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package engine package engine
@@ -23,39 +35,64 @@ import (
"github.com/spf13/afero" "github.com/spf13/afero"
) )
// from the ioutil package: // Fs is an interface that represents the file system API that we support.
// NopCloser(r io.Reader) io.ReadCloser // not implemented here // TODO: rename this to FS for consistency with the io/fs.FS naming scheme
// ReadAll(r io.Reader) ([]byte, error)
// ReadDir(dirname string) ([]os.FileInfo, error)
// ReadFile(filename string) ([]byte, error)
// TempDir(dir, prefix string) (name string, err error)
// TempFile(dir, prefix string) (f *os.File, err error) // slightly different here
// WriteFile(filename string, data []byte, perm os.FileMode) error
// Fs is an interface that represents this file system API that we support.
// TODO: this should be in the gapi package or elsewhere.
type Fs interface { type Fs interface {
//fmt.Stringer // TODO: add this method? //fmt.Stringer // TODO: add this method?
afero.Fs // TODO: why doesn't this interface exist in the os pkg?
// URI returns a unique string handle to access this filesystem.
URI() string // returns the URI for this file system URI() string // returns the URI for this file system
//DirExists(path string) (bool, error) afero.Fs // TODO: why doesn't this interface exist in the os pkg?
//Exists(path string) (bool, error)
//FileContainsAnyBytes(filename string, subslices [][]byte) (bool, error) // FS is the read-only filesystem interface from the io/fs.FS package.
//FileContainsBytes(filename string, subslice []byte) (bool, error) //fs.FS // io/fs.FS
//FullBaseFsPath(basePathFs *BasePathFs, relativePath string) string
//GetTempDir(subPath string) string // ReadDir reads the named directory and returns a list of directory
//IsDir(path string) (bool, error) // entries sorted by filename.
//IsEmpty(path string) (bool, error) //
//NeuterAccents(s string) string // This mimics the signature from io/fs.ReadDirFS and has the same docs.
//ReadAll(r io.Reader) ([]byte, error) // not needed, same as ioutil //
// XXX: Not currently implemented because of legacy Afero.Fs above
//ReadDir(name string) ([]fs.DirEntry, error) // io/fs.ReadDirFS
// ReadFile reads the named file and returns its contents. A successful
// call returns a nil error, not io.EOF. (Because ReadFile reads the
// whole file, the expected EOF from the final Read is not treated as an
// error to be reported.)
//
// The caller is permitted to modify the returned byte slice. This
// method should return a copy of the underlying data.
//
// This mimics the signature from io/fs.ReadFileFS and has the same
// docs.
ReadFile(name string) ([]byte, error) // io/fs.ReadFileFS
// Stat returns a FileInfo describing the file. If there is an error, it
// should be of type *fs.PathError.
//
// This mimics the signature from io/fs.StatFS and has the same docs.
//
// XXX: Not currently implemented because of legacy Afero.Fs above
//Stat(name string) (FileInfo, error) // io/fs.StatFS
// afero.Fs versions:
ReadDir(dirname string) ([]os.FileInfo, error) ReadDir(dirname string) ([]os.FileInfo, error)
ReadFile(filename string) ([]byte, error) }
//SafeWriteReader(path string, r io.Reader) (err error)
TempDir(dir, prefix string) (name string, err error) // WriteableFS is our internal filesystem interface for filesystems we write to.
TempFile(dir, prefix string) (f afero.File, err error) // slightly different from upstream // It can wrap whatever implementations we want.
//UnicodeSanitize(s string) string type WriteableFS interface {
//Walk(root string, walkFn filepath.WalkFunc) error Fs
WriteFile(filename string, data []byte, perm os.FileMode) error
//WriteReader(path string, r io.Reader) (err error) // WriteFile writes data to the named file, creating it if necessary. If
// the file does not exist, WriteFile creates it with permissions perm
// (before umask); otherwise WriteFile truncates it before writing,
// without changing permissions. Since Writefile requires multiple
// system calls to complete, a failure mid-operation can leave the file
// in a partially written state.
//
// This mimics the internal os.WriteFile function and has the same docs.
WriteFile(name string, data []byte, perm os.FileMode) error
} }

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,17 +13,31 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package graph package graph
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/purpleidea/mgmt/engine" "github.com/purpleidea/mgmt/engine"
engineUtil "github.com/purpleidea/mgmt/engine/util"
"github.com/purpleidea/mgmt/pgraph" "github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
@@ -39,14 +53,18 @@ func (obj *Engine) OKTimestamp(vertex pgraph.Vertex) bool {
// be bad. // be bad.
func (obj *Engine) BadTimestamps(vertex pgraph.Vertex) []pgraph.Vertex { func (obj *Engine) BadTimestamps(vertex pgraph.Vertex) []pgraph.Vertex {
vs := []pgraph.Vertex{} vs := []pgraph.Vertex{}
ts := obj.state[vertex].timestamp obj.state[vertex].mutex.RLock() // concurrent read start
ts := obj.state[vertex].timestamp // race
obj.state[vertex].mutex.RUnlock() // concurrent read end
// these are all the vertices pointing TO vertex, eg: ??? -> vertex // these are all the vertices pointing TO vertex, eg: ??? -> vertex
for _, v := range obj.graph.IncomingGraphVertices(vertex) { for _, v := range obj.graph.IncomingGraphVertices(vertex) {
// If the vertex has a greater timestamp than any prerequisite, // If the vertex has a greater timestamp than any prerequisite,
// then we can't run right now. If they're equal (eg: initially // then we can't run right now. If they're equal (eg: initially
// with a value of 0) then we also can't run because we should // with a value of 0) then we also can't run because we should
// let our pre-requisites go first. // let our pre-requisites go first.
t := obj.state[v].timestamp obj.state[v].mutex.RLock() // concurrent read start
t := obj.state[v].timestamp // race
obj.state[v].mutex.RUnlock() // concurrent read end
if obj.Debug { if obj.Debug {
obj.Logf("OKTimestamp: %d >= %d (%s): !%t", ts, t, v.String(), ts >= t) obj.Logf("OKTimestamp: %d >= %d (%s): !%t", ts, t, v.String(), ts >= t)
} }
@@ -59,7 +77,7 @@ func (obj *Engine) BadTimestamps(vertex pgraph.Vertex) []pgraph.Vertex {
} }
// Process is the primary function to execute a particular vertex in the graph. // Process is the primary function to execute a particular vertex in the graph.
func (obj *Engine) Process(vertex pgraph.Vertex) error { func (obj *Engine) Process(ctx context.Context, vertex pgraph.Vertex) error {
res, isRes := vertex.(engine.Res) res, isRes := vertex.(engine.Res)
if !isRes { if !isRes {
return fmt.Errorf("vertex is not a Res") return fmt.Errorf("vertex is not a Res")
@@ -83,7 +101,9 @@ func (obj *Engine) Process(vertex pgraph.Vertex) error {
} }
wg.Wait() wg.Wait()
return nil // can't continue until timestamp is in sequence
// can't continue until timestamp is in sequence, defer for now
return engine.ErrBackPoke
} }
// semaphores! // semaphores!
@@ -108,21 +128,41 @@ func (obj *Engine) Process(vertex pgraph.Vertex) error {
// sendrecv! // sendrecv!
// connect any senders to receivers and detect if values changed // connect any senders to receivers and detect if values changed
// this actually checks and sends into resource trees recursively...
if res, ok := vertex.(engine.RecvableRes); ok { if res, ok := vertex.(engine.RecvableRes); ok {
if updated, err := obj.SendRecv(res); err != nil { if obj.Debug {
obj.Logf("SendRecv: %s", res) // receiving here
}
if updated, err := SendRecv(res, nil); err != nil {
return errwrap.Wrapf(err, "could not SendRecv") return errwrap.Wrapf(err, "could not SendRecv")
} else if len(updated) > 0 { } else if len(updated) > 0 {
for _, changed := range updated { //for _, s := range graph.UpdatedStrings(updated) {
if changed { // at least one was updated // obj.Logf("SendRecv: %s", s)
// invalidate cache, mark as dirty //}
obj.state[vertex].tuid.StopTimer() for r, m := range updated { // map[engine.RecvableRes]map[string]*engine.Send
obj.state[vertex].isStateOK = false v, ok := r.(pgraph.Vertex)
break if !ok {
continue
}
_, stateExists := obj.state[v] // autogrouped children probably don't have a state
if !stateExists {
continue
}
for s, send := range m {
if !send.Changed {
continue
}
obj.Logf("Send/Recv: %v.%s -> %v.%s", send.Res, send.Key, r, s)
// if send.Changed == true, at least one was updated
// invalidate cache, mark as dirty
obj.state[v].setDirty()
//break // we might have more vertices now
}
// re-validate after we change any values
if err := engine.Validate(r); err != nil {
return errwrap.Wrapf(err, "failed Validate after SendRecv")
} }
}
// re-validate after we change any values
if err := engine.Validate(res); err != nil {
return errwrap.Wrapf(err, "failed Validate after SendRecv")
} }
} }
} }
@@ -144,18 +184,18 @@ func (obj *Engine) Process(vertex pgraph.Vertex) error {
// Check cached state, to skip CheckApply, but can't skip if refreshing! // Check cached state, to skip CheckApply, but can't skip if refreshing!
// If the resource doesn't implement refresh, skip the refresh test. // If the resource doesn't implement refresh, skip the refresh test.
// FIXME: if desired, check that we pass through refresh notifications! // FIXME: if desired, check that we pass through refresh notifications!
if (!refresh || !isRefreshableRes) && obj.state[vertex].isStateOK { if (!refresh || !isRefreshableRes) && obj.state[vertex].isStateOK.Load() { // mutex RLock/RUnlock
checkOK, err = true, nil checkOK, err = true, nil
} else if noop && (refresh && isRefreshableRes) { // had a refresh to do w/ noop! } else if noop && (refresh && isRefreshableRes) { // had a refresh to do w/ noop!
checkOK, err = false, nil // therefore the state is wrong checkOK, err = false, nil // therefore the state is wrong
// run the CheckApply!
} else { } else {
// run the CheckApply!
obj.Logf("%s: CheckApply(%t)", res, !noop) obj.Logf("%s: CheckApply(%t)", res, !noop)
// if this fails, don't UpdateTimestamp() // if this fails, don't UpdateTimestamp()
checkOK, err = res.CheckApply(!noop) checkOK, err = res.CheckApply(ctx, !noop)
obj.Logf("%s: CheckApply(%t): Return(%t, %+v)", res, !noop, checkOK, err) obj.Logf("%s: CheckApply(%t): Return(%t, %s)", res, !noop, checkOK, engineUtil.CleanError(err))
} }
if checkOK && err != nil { // should never return this way if checkOK && err != nil { // should never return this way
@@ -172,7 +212,9 @@ func (obj *Engine) Process(vertex pgraph.Vertex) error {
// if CheckApply ran without noop and without error, state should be good // if CheckApply ran without noop and without error, state should be good
if !noop && err == nil { // aka !noop || checkOK if !noop && err == nil { // aka !noop || checkOK
obj.state[vertex].tuid.StartTimer() obj.state[vertex].tuid.StartTimer()
obj.state[vertex].isStateOK = true // reset //obj.state[vertex].mutex.Lock()
obj.state[vertex].isStateOK.Store(true) // reset
//obj.state[vertex].mutex.Unlock()
if refresh { if refresh {
obj.SetUpstreamRefresh(vertex, false) // refresh happened, clear the request obj.SetUpstreamRefresh(vertex, false) // refresh happened, clear the request
if isRefreshableRes { if isRefreshableRes {
@@ -209,7 +251,9 @@ func (obj *Engine) Process(vertex pgraph.Vertex) error {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
// update this timestamp *before* we poke or the poked // update this timestamp *before* we poke or the poked
// nodes might fail due to having a too old timestamp! // nodes might fail due to having a too old timestamp!
obj.state[vertex].timestamp = time.Now().UnixNano() // update timestamp obj.state[vertex].mutex.Lock() // concurrent write start
obj.state[vertex].timestamp = time.Now().UnixNano() // update timestamp (race)
obj.state[vertex].mutex.Unlock() // concurrent write end
for _, v := range obj.graph.OutgoingGraphVertices(vertex) { for _, v := range obj.graph.OutgoingGraphVertices(vertex) {
if !obj.OKTimestamp(v) { if !obj.OKTimestamp(v) {
// there is at least another one that will poke this... // there is at least another one that will poke this...
@@ -255,6 +299,16 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
return fmt.Errorf("permanently limited (rate != Inf, burst = 0)") return fmt.Errorf("permanently limited (rate != Inf, burst = 0)")
} }
// initialize or reinitialize the meta state for this resource uid
obj.mlock.Lock()
if _, exists := obj.metas[engine.PtrUID(res)]; !exists || res.MetaParams().Reset {
obj.metas[engine.PtrUID(res)] = &engine.MetaState{
CheckApplyRetry: res.MetaParams().Retry, // lookup the retry value
}
}
metas := obj.metas[engine.PtrUID(res)] // handle
obj.mlock.Unlock()
//defer close(obj.state[vertex].stopped) // done signal //defer close(obj.state[vertex].stopped) // done signal
obj.state[vertex].cuid = obj.Converger.Register() obj.state[vertex].cuid = obj.Converger.Register()
@@ -272,7 +326,7 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
defer close(obj.state[vertex].eventsChan) // we close this on behalf of res defer close(obj.state[vertex].eventsChan) // we close this on behalf of res
// This is a close reverse-multiplexer. If any of the channels // This is a close reverse-multiplexer. If any of the channels
// close, then it will cause the doneChan to close. That way, // close, then it will cause the doneCtx to cancel. That way,
// multiple different folks can send a close signal, without // multiple different folks can send a close signal, without
// every worrying about duplicate channel close panics. // every worrying about duplicate channel close panics.
obj.state[vertex].wg.Add(1) obj.state[vertex].wg.Add(1)
@@ -284,12 +338,13 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
case <-obj.state[vertex].processDone: case <-obj.state[vertex].processDone:
case <-obj.state[vertex].watchDone: case <-obj.state[vertex].watchDone:
case <-obj.state[vertex].limitDone: case <-obj.state[vertex].limitDone:
case <-obj.state[vertex].retryDone:
case <-obj.state[vertex].removeDone: case <-obj.state[vertex].removeDone:
case <-obj.state[vertex].eventsDone: case <-obj.state[vertex].eventsDone:
} }
// the main "done" signal gets activated here! // the main "done" signal gets activated here!
close(obj.state[vertex].doneChan) obj.state[vertex].doneCtxCancel() // cancels doneCtx
}() }()
var err error var err error
@@ -308,7 +363,7 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
case <-timer.C: // the wait is over case <-timer.C: // the wait is over
return errDelayExpired // special return errDelayExpired // special
case <-obj.state[vertex].init.Done: case <-obj.state[vertex].doneCtx.Done():
return nil return nil
} }
} }
@@ -319,13 +374,21 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
} }
} else if interval := res.MetaParams().Poll; interval > 0 { // poll instead of watching :( } else if interval := res.MetaParams().Poll; interval > 0 { // poll instead of watching :(
obj.state[vertex].cuid.StartTimer() obj.state[vertex].cuid.StartTimer()
err = obj.state[vertex].poll(interval) err = obj.state[vertex].poll(obj.state[vertex].doneCtx, interval)
obj.state[vertex].cuid.StopTimer() // clean up nicely obj.state[vertex].cuid.StopTimer() // clean up nicely
} else { } else {
obj.state[vertex].cuid.StartTimer() obj.state[vertex].cuid.StartTimer()
obj.Logf("Watch(%s)", vertex) if obj.Debug {
err = res.Watch() // run the watch normally obj.Logf("%s: Watch...", vertex)
obj.Logf("Watch(%s): Exited(%+v)", vertex, err) }
err = res.Watch(obj.state[vertex].doneCtx) // run the watch normally
if obj.Debug {
if s := engineUtil.CleanError(err); err != nil {
obj.Logf("%s: Watch Error: %s", vertex, s)
} else {
obj.Logf("%s: Watch Exited...", vertex)
}
}
obj.state[vertex].cuid.StopTimer() // clean up nicely obj.state[vertex].cuid.StopTimer() // clean up nicely
} }
if err == nil { // || err == engine.ErrClosed if err == nil { // || err == engine.ErrClosed
@@ -359,13 +422,19 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
// If this exits cleanly, we must unblock the reverse-multiplexer. // If this exits cleanly, we must unblock the reverse-multiplexer.
// I think this additional close is unnecessary, but it's not harmful. // I think this additional close is unnecessary, but it's not harmful.
defer close(obj.state[vertex].eventsDone) // causes doneChan to close defer close(obj.state[vertex].eventsDone) // causes doneCtx to cancel
limiter := rate.NewLimiter(res.MetaParams().Limit, res.MetaParams().Burst) limiter := rate.NewLimiter(res.MetaParams().Limit, res.MetaParams().Burst)
var reserv *rate.Reservation var reserv *rate.Reservation
var reterr error var reterr error
var failed bool // has Process permanently failed? var failed bool // has Process permanently failed?
var closed bool // has the resumeSignal channel closed?
Loop: Loop:
for { // process loop for { // process loop
// This is the main select where things happen and where we exit
// from. It's similar to the two "satellite" select's which we
// might spend some time in if we're retrying or rate limiting.
// This select is also the main event receiver and is also the
// only place where we read from the poke channel.
select { select {
case err, ok := <-obj.state[vertex].eventsChan: // read from watch channel case err, ok := <-obj.state[vertex].eventsChan: // read from watch channel
if !ok { if !ok {
@@ -376,7 +445,7 @@ Loop:
// we then save so we can return it to the caller of us. // we then save so we can return it to the caller of us.
if err != nil { if err != nil {
failed = true failed = true
close(obj.state[vertex].watchDone) // causes doneChan to close close(obj.state[vertex].watchDone) // causes doneCtx to cancel
reterr = errwrap.Append(reterr, err) // permanent failure reterr = errwrap.Append(reterr, err) // permanent failure
continue continue
} }
@@ -394,9 +463,33 @@ Loop:
obj.Logf("poke received") obj.Logf("poke received")
} }
reserv = nil // we didn't receive a real event here... reserv = nil // we didn't receive a real event here...
}
if failed { // don't Process anymore if we've already failed... case _, ok := <-obj.state[vertex].pauseSignal: // one message
continue Loop if !ok {
obj.state[vertex].pauseSignal = nil
continue // this is not a new pause message
}
// NOTE: If we allowed a doneCtx below to let us out
// of the resumeSignal wait, then we could loop around
// and run this again, causing a panic. Instead of this
// being made safe with a sync.Once, we instead run a
// close() call inside of the vertexRemoveFn function,
// which should unblock resumeSignal so we can shutdown.
// we are paused now, and waiting for resume or exit...
select {
case _, ok := <-obj.state[vertex].resumeSignal: // channel closes
if !ok {
closed = true
}
// resumed!
// pass through to allow a Process to try to run
// TODO: consider adding this fast pause here...
//if obj.fastPause {
// obj.Logf("fast pausing on resume")
// continue
//}
}
} }
// drop redundant pokes // drop redundant pokes
@@ -408,31 +501,8 @@ Loop:
} }
} }
// pause if one was requested... // don't Process anymore if we've already failed or shutdown...
select { if failed || closed {
case <-obj.state[vertex].pauseSignal: // channel closes
// NOTE: If we allowed a doneChan below to let us out
// of the resumeSignal wait, then we could loop around
// and run this again, causing a panic. Instead of this
// being made safe with a sync.Once, we instead run a
// Resume() call inside of the vertexRemoveFn function,
// which should unblock it when we're going to need to.
obj.state[vertex].pausedAck.Ack() // send ack
// we are paused now, and waiting for resume or exit...
select {
case <-obj.state[vertex].resumeSignal: // channel closes
// resumed!
// pass through to allow a Process to try to run
// TODO: consider adding this fast pause here...
//if obj.fastPause {
// obj.Logf("fast pausing on resume")
// continue
//}
}
default:
// no pause requested, keep going...
}
if failed { // don't Process anymore if we've already failed...
continue Loop continue Loop
} }
@@ -442,10 +512,13 @@ Loop:
d = reserv.DelayFrom(time.Now()) d = reserv.DelayFrom(time.Now())
} }
if reserv != nil && d > 0 { // delay if reserv != nil && d > 0 { // delay
obj.state[vertex].init.Logf("limited (rate: %v/sec, burst: %d, next: %v)", res.MetaParams().Limit, res.MetaParams().Burst, d) obj.state[vertex].init.Logf("limited (rate: %v/sec, burst: %d, next: %dms)", res.MetaParams().Limit, res.MetaParams().Burst, d/time.Millisecond)
timer := time.NewTimer(time.Duration(d) * time.Millisecond) timer := time.NewTimer(time.Duration(d) * time.Millisecond)
LimitWait: LimitWait:
for { for {
// This "satellite" select doesn't need a poke
// channel because we're already in "event
// received" mode, and poke doesn't block.
select { select {
case <-timer.C: // the wait is over case <-timer.C: // the wait is over
break LimitWait break LimitWait
@@ -457,7 +530,7 @@ Loop:
} }
if e != nil { if e != nil {
failed = true failed = true
close(obj.state[vertex].limitDone) // causes doneChan to close close(obj.state[vertex].limitDone) // causes doneCtx to cancel
reterr = errwrap.Append(reterr, e) // permanent failure reterr = errwrap.Append(reterr, e) // permanent failure
break LimitWait break LimitWait
} }
@@ -466,19 +539,34 @@ Loop:
} }
// TODO: does this get added in properly? // TODO: does this get added in properly?
limiter.ReserveN(time.Now(), 1) // one event limiter.ReserveN(time.Now(), 1) // one event
// this pause/resume block is the same as the upper main one
case _, ok := <-obj.state[vertex].pauseSignal:
if !ok {
obj.state[vertex].pauseSignal = nil
break LimitWait
}
select {
case _, ok := <-obj.state[vertex].resumeSignal: // channel closes
if !ok {
closed = true
}
// resumed!
}
} }
} }
timer.Stop() // it's nice to cleanup timer.Stop() // it's nice to cleanup
obj.state[vertex].init.Logf("rate limiting expired!") obj.state[vertex].init.Logf("rate limiting expired!")
} }
if failed { // don't Process anymore if we've already failed... // don't Process anymore if we've already failed or shutdown...
if failed || closed {
continue Loop continue Loop
} }
// end of limit delay // end of limit delay
// retry... // retry...
var err error var err error
var retry = res.MetaParams().Retry // lookup the retry value //var retry = res.MetaParams().Retry // lookup the retry value
var delay uint64 var delay uint64
RetryLoop: RetryLoop:
for { // retry loop for { // retry loop
@@ -486,6 +574,10 @@ Loop:
timer := time.NewTimer(time.Duration(delay) * time.Millisecond) timer := time.NewTimer(time.Duration(delay) * time.Millisecond)
RetryWait: RetryWait:
for { for {
// This "satellite" select doesn't need
// a poke channel because we're already
// in "event received" mode, and poke
// doesn't block.
select { select {
case <-timer.C: // the wait is over case <-timer.C: // the wait is over
break RetryWait break RetryWait
@@ -497,7 +589,7 @@ Loop:
} }
if e != nil { if e != nil {
failed = true failed = true
close(obj.state[vertex].limitDone) // causes doneChan to close close(obj.state[vertex].retryDone) // causes doneCtx to cancel
reterr = errwrap.Append(reterr, e) // permanent failure reterr = errwrap.Append(reterr, e) // permanent failure
break RetryWait break RetryWait
} }
@@ -506,38 +598,68 @@ Loop:
} }
// TODO: does this get added in properly? // TODO: does this get added in properly?
limiter.ReserveN(time.Now(), 1) // one event limiter.ReserveN(time.Now(), 1) // one event
// this pause/resume block is the same as the upper main one
case _, ok := <-obj.state[vertex].pauseSignal:
if !ok {
obj.state[vertex].pauseSignal = nil
break RetryWait
}
select {
case _, ok := <-obj.state[vertex].resumeSignal: // channel closes
if !ok {
closed = true
}
// resumed!
}
} }
} }
timer.Stop() // it's nice to cleanup timer.Stop() // it's nice to cleanup
delay = 0 // reset delay = 0 // reset
obj.state[vertex].init.Logf("the CheckApply delay expired!") obj.state[vertex].init.Logf("the CheckApply delay expired!")
} }
if failed { // don't Process anymore if we've already failed... // don't Process anymore if we've already failed or shutdown...
if failed || closed {
continue Loop continue Loop
} }
if obj.Debug { if obj.Debug {
obj.Logf("Process(%s)", vertex) obj.Logf("Process(%s)", vertex)
} }
err = obj.Process(vertex) backPoke := false
if obj.Debug { err = obj.Process(obj.state[vertex].doneCtx, vertex)
obj.Logf("Process(%s): Return(%+v)", vertex, err) if err == engine.ErrBackPoke {
backPoke = true
err = nil // for future code safety
} }
if err == nil { if obj.Debug && backPoke {
obj.Logf("Process(%s): BackPoke!", vertex)
}
if obj.Debug && !backPoke {
obj.Logf("Process(%s): Return(%s)", vertex, engineUtil.CleanError(err))
}
if err == nil && !backPoke && res.MetaParams().RetryReset { // reset it on success!
metas.CheckApplyRetry = res.MetaParams().Retry // lookup the retry value
}
if err == nil || backPoke {
break RetryLoop break RetryLoop
} }
// we've got an error... // we've got an error...
delay = res.MetaParams().Delay delay = res.MetaParams().Delay
if retry < 0 { // infinite retries if metas.CheckApplyRetry < 0 { // infinite retries
continue continue
} }
if retry > 0 { // don't decrement past 0 if metas.CheckApplyRetry > 0 { // don't decrement past 0
retry-- metas.CheckApplyRetry--
obj.state[vertex].init.Logf("retrying CheckApply after %.4f seconds (%d left)", float64(delay)/1000, retry) obj.state[vertex].init.Logf(
"retrying CheckApply after %.4f seconds (%d left)",
float64(delay)/1000,
metas.CheckApplyRetry,
)
continue continue
} }
//if retry == 0 { // optional //if metas.CheckApplyRetry == 0 { // optional
// err = errwrap.Wrapf(err, "permanent process error") // err = errwrap.Wrapf(err, "permanent process error")
//} //}
@@ -545,7 +667,7 @@ Loop:
// this dies. If Process fails permanently, we ask it // this dies. If Process fails permanently, we ask it
// to exit right here... (It happens when we loop...) // to exit right here... (It happens when we loop...)
failed = true failed = true
close(obj.state[vertex].processDone) // causes doneChan to close close(obj.state[vertex].processDone) // causes doneCtx to cancel
reterr = errwrap.Append(reterr, err) // permanent failure reterr = errwrap.Append(reterr, err) // permanent failure
continue continue

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package graph package graph

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package autoedge package autoedge

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package graph package graph
@@ -109,6 +121,7 @@ func (obj *wrappedGrouper) VertexMerge(v1, v2 pgraph.Vertex) (v pgraph.Vertex, e
if err = r1.GroupRes(r2); err != nil { // GroupRes skips stupid groupings if err = r1.GroupRes(r2); err != nil { // GroupRes skips stupid groupings
return // return early on error return // return early on error
} }
r2.SetParent(r1) // store who my parent is
// merging two resources into one should yield the sum of their semas // merging two resources into one should yield the sum of their semas
if semas := r2.MetaParams().Sema; len(semas) > 0 { if semas := r2.MetaParams().Sema; len(semas) > 0 {

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package autogroup package autogroup

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,13 +13,26 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// +build !root //go:build !root
package autogroup package autogroup
import ( import (
"context"
"fmt" "fmt"
"reflect" "reflect"
"sort" "sort"
@@ -62,15 +75,15 @@ func (obj *NoopResTest) Init(init *engine.Init) error {
return nil return nil
} }
func (obj *NoopResTest) Close() error { func (obj *NoopResTest) Cleanup() error {
return nil return nil
} }
func (obj *NoopResTest) Watch() error { func (obj *NoopResTest) Watch(context.Context) error {
return nil // not needed return nil // not needed
} }
func (obj *NoopResTest) CheckApply(apply bool) (checkOK bool, err error) { func (obj *NoopResTest) CheckApply(ctx context.Context, apply bool) (checkOK bool, err error) {
return true, nil // state is always okay return true, nil // state is always okay
} }
@@ -189,6 +202,8 @@ func (obj *testGrouper) VertexMerge(v1, v2 pgraph.Vertex) (v pgraph.Vertex, err
if err := r1.GroupRes(r2); err != nil { // group them first if err := r1.GroupRes(r2); err != nil { // group them first
return nil, err return nil, err
} }
r2.SetParent(r1) // store who my parent is
// HACK: update the name so it matches full list of self+grouped // HACK: update the name so it matches full list of self+grouped
res := v1.(engine.GroupableRes) res := v1.(engine.GroupableRes)
names := strings.Split(res.Name(), ",") // load in stored names names := strings.Split(res.Name(), ",") // load in stored names
@@ -595,7 +610,8 @@ func TestPgraphGrouping11(t *testing.T) {
runGraphCmp(t, g1, g2) runGraphCmp(t, g1, g2)
} }
/* simple merge 1 /*
// simple merge 1
// a1 a2 a1,a2 // a1 a2 a1,a2
// \ / >>> | (arrows point downwards) // \ / >>> | (arrows point downwards)
// b b // b b
@@ -621,7 +637,8 @@ func TestPgraphGrouping12(t *testing.T) {
runGraphCmp(t, g1, g2) runGraphCmp(t, g1, g2)
} }
/* simple merge 2 /*
// simple merge 2
// b b // b b
// / \ >>> | (arrows point downwards) // / \ >>> | (arrows point downwards)
// a1 a2 a1,a2 // a1 a2 a1,a2
@@ -647,7 +664,8 @@ func TestPgraphGrouping13(t *testing.T) {
runGraphCmp(t, g1, g2) runGraphCmp(t, g1, g2)
} }
/* triple merge /*
// triple merge
// a1 a2 a3 a1,a2,a3 // a1 a2 a3 a1,a2,a3
// \ | / >>> | (arrows point downwards) // \ | / >>> | (arrows point downwards)
// b b // b b
@@ -676,7 +694,8 @@ func TestPgraphGrouping14(t *testing.T) {
runGraphCmp(t, g1, g2) runGraphCmp(t, g1, g2)
} }
/* chain merge /*
// chain merge
// a1 a1 // a1 a1
// / \ | // / \ |
// b1 b2 >>> b1,b2 (arrows point downwards) // b1 b2 >>> b1,b2 (arrows point downwards)
@@ -712,7 +731,8 @@ func TestPgraphGrouping15(t *testing.T) {
runGraphCmp(t, g1, g2) runGraphCmp(t, g1, g2)
} }
/* re-attach 1 (outer) /*
// re-attach 1 (outer)
// technically the second possibility is valid too, depending on which order we // technically the second possibility is valid too, depending on which order we
// merge edges in, and if we don't filter out any unnecessary edges afterwards! // merge edges in, and if we don't filter out any unnecessary edges afterwards!
// a1 a2 a1,a2 a1,a2 // a1 a2 a1,a2 a1,a2
@@ -735,20 +755,34 @@ func TestPgraphGrouping16(t *testing.T) {
g1.AddEdge(b1, c1, e2) g1.AddEdge(b1, c1, e2)
g1.AddEdge(a2, c1, e3) g1.AddEdge(a2, c1, e3)
} }
g2, _ := pgraph.NewGraph("g2") // expected result //g2, _ := pgraph.NewGraph("g2") // expected result
//{
// a := NewNoopResTest("a1,a2")
// b1 := NewNoopResTest("b1")
// c1 := NewNoopResTest("c1")
// e1 := NE("e1,e3")
// e2 := NE("e2,e3") // e3 gets "merged through" to BOTH edges!
// g2.AddEdge(a, b1, e1)
// g2.AddEdge(b1, c1, e2)
//}
//runGraphCmp(t, g1, g2)
g3, _ := pgraph.NewGraph("g3") // alternative expected result
{ {
a := NewNoopResTest("a1,a2") a := NewNoopResTest("a1,a2")
b1 := NewNoopResTest("b1") b1 := NewNoopResTest("b1")
c1 := NewNoopResTest("c1") c1 := NewNoopResTest("c1")
e1 := NE("e1,e3") e1 := NE("e1")
e2 := NE("e2,e3") // e3 gets "merged through" to BOTH edges! e2 := NE("e2")
g2.AddEdge(a, b1, e1) e3 := NE("e3")
g2.AddEdge(b1, c1, e2) g3.AddEdge(a, b1, e1)
g3.AddEdge(b1, c1, e2)
g3.AddEdge(a, c1, e3)
} }
runGraphCmp(t, g1, g2) runGraphCmp(t, g1, g3)
} }
/* re-attach 2 (inner) /*
// re-attach 2 (inner)
// a1 b2 a1 // a1 b2 a1
// | / | // | / |
// b1 / >>> b1,b2 (arrows point downwards) // b1 / >>> b1,b2 (arrows point downwards)
@@ -782,13 +816,15 @@ func TestPgraphGrouping17(t *testing.T) {
runGraphCmp(t, g1, g2) runGraphCmp(t, g1, g2)
} }
/* re-attach 3 (double) /*
// re-attach 3 (double)
// similar to "re-attach 1", technically there is a second possibility for this // similar to "re-attach 1", technically there is a second possibility for this
// a2 a1 b2 a1,a2 // TODO: verify this second possibility manually
// \ | / | // a2 a1 b2 a1,a2 a1,a2
// \ b1 / >>> b1,b2 (arrows point downwards) // \ | / | | \
// \ | / | // \ b1 / >>> b1,b2 OR b1,b2 / (arrows point downwards)
// c1 c1 // \ | / | | /
// c1 c1 c1
*/ */
func TestPgraphGrouping18(t *testing.T) { func TestPgraphGrouping18(t *testing.T) {
g1, _ := pgraph.NewGraph("g1") // original graph g1, _ := pgraph.NewGraph("g1") // original graph
@@ -807,20 +843,34 @@ func TestPgraphGrouping18(t *testing.T) {
g1.AddEdge(a2, c1, e3) g1.AddEdge(a2, c1, e3)
g1.AddEdge(b2, c1, e4) g1.AddEdge(b2, c1, e4)
} }
g2, _ := pgraph.NewGraph("g2") // expected result //g2, _ := pgraph.NewGraph("g2") // expected result
//{
// a := NewNoopResTest("a1,a2")
// b := NewNoopResTest("b1,b2")
// c1 := NewNoopResTest("c1")
// e1 := NE("e1,e3")
// e2 := NE("e2,e3,e4") // e3 gets "merged through" to BOTH edges!
// g2.AddEdge(a, b, e1)
// g2.AddEdge(b, c1, e2)
//}
//runGraphCmp(t, g1, g2)
g3, _ := pgraph.NewGraph("g3") // alternative expected result
{ {
a := NewNoopResTest("a1,a2") a := NewNoopResTest("a1,a2")
b := NewNoopResTest("b1,b2") b := NewNoopResTest("b1,b2")
c1 := NewNoopResTest("c1") c1 := NewNoopResTest("c1")
e1 := NE("e1,e3") e1 := NE("e1")
e2 := NE("e2,e3,e4") // e3 gets "merged through" to BOTH edges! e2 := NE("e2,e4")
g2.AddEdge(a, b, e1) e3 := NE("e3")
g2.AddEdge(b, c1, e2) g3.AddEdge(a, b, e1)
g3.AddEdge(b, c1, e2)
g3.AddEdge(a, c1, e3)
} }
runGraphCmp(t, g1, g2) runGraphCmp(t, g1, g3)
} }
/* connected merge 0, (no change!) /*
// connected merge 0, (no change!)
// a1 a1 // a1 a1
// \ >>> \ (arrows point downwards) // \ >>> \ (arrows point downwards)
// a2 a2 // a2 a2
@@ -843,7 +893,8 @@ func TestPgraphGroupingConnected0(t *testing.T) {
runGraphCmp(t, g1, g2) runGraphCmp(t, g1, g2)
} }
/* connected merge 1, (no change!) /*
// connected merge 1, (no change!)
// a1 a1 // a1 a1
// \ \ // \ \
// b >>> b (arrows point downwards) // b >>> b (arrows point downwards)

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package autogroup package autogroup
@@ -43,8 +55,24 @@ func (ag *baseGrouper) Init(g *pgraph.Graph) error {
if ag.graph != nil { if ag.graph != nil {
return fmt.Errorf("the init method has already been called") return fmt.Errorf("the init method has already been called")
} }
ag.graph = g // pointer ag.graph = g // pointer
ag.vertices = ag.graph.VerticesSorted() // cache in deterministic order!
// We sort deterministically, first by kind, and then by name. In
// particular, longer kind chunks sort first. So http:ui:text should
// appear before http:server and http:ui. This is a hack so that if we
// are doing hierarchical automatic grouping, it gives the http:ui:text
// a chance to get grouped into http:ui, before http:ui gets grouped
// into http:server, because once that happens, http:ui:text will never
// get grouped, and this won't work properly. This works, because when
// we start comparing iteratively the list of resources, it does this
// with a O(n^2) loop that compares the X and Y zero indexes first, and
// and then continues along. If the "longer" resources appear first,
// then they'll group together first. We should probably put this into
// a new Grouper struct, but for now we might as well leave it here.
//vertices := ag.graph.VerticesSorted() // formerly
vertices := RHVSort(ag.graph.Vertices())
ag.vertices = vertices // cache in deterministic order!
ag.i = 0 ag.i = 0
ag.j = 0 ag.j = 0
if len(ag.vertices) == 0 { // empty graph if len(ag.vertices) == 0 { // empty graph

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package autogroup package autogroup

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,12 +13,26 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package autogroup package autogroup
import ( import (
"fmt" "fmt"
"sort"
"strings"
"github.com/purpleidea/mgmt/engine" "github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/pgraph" "github.com/purpleidea/mgmt/pgraph"
@@ -136,3 +150,67 @@ func VertexMerge(g *pgraph.Graph, v1, v2 pgraph.Vertex, vertexMergeFn func(pgrap
} }
return nil // success return nil // success
} }
// RHVSlice is a linear list of vertices. It can be sorted by the Kind, taking
// into account the hierarchy of names separated by colons. Afterwards, it uses
// String() to avoid the non-determinism in the map type. RHV stands for Reverse
// Hierarchical Vertex, meaning the hierarchical topology of the vertex
// (resource) names are used.
type RHVSlice []pgraph.Vertex
// Len returns the length of the slice of vertices.
func (obj RHVSlice) Len() int { return len(obj) }
// Swap swaps two elements in the slice.
func (obj RHVSlice) Swap(i, j int) { obj[i], obj[j] = obj[j], obj[i] }
// Less returns the smaller element in the sort order according to the
// aforementioned rules.
// XXX: Add some tests to make sure I didn't get any "reverse" part backwards.
func (obj RHVSlice) Less(i, j int) bool {
resi, oki := obj[i].(engine.Res)
resj, okj := obj[j].(engine.Res)
if !oki || !okj || resi.Kind() == "" || resj.Kind() == "" {
// One of these isn't a normal Res, so just compare normally.
return obj[i].String() > obj[j].String() // reverse
}
si := strings.Split(resi.Kind(), ":")
sj := strings.Split(resj.Kind(), ":")
// both lengths should each be at least one now
li := len(si)
lj := len(sj)
if li != lj { // eg: http:ui vs. http:ui:text
return li > lj // reverse
}
// same number of chunks
for k := 0; k < li; k++ {
if si[k] != sj[k] { // lhs chunk differs
return si[k] > sj[k] // reverse
}
// if the chunks are the same, we continue...
}
// They must all have the same chunks, so finally we compare the names.
return resi.Name() > resj.Name() // reverse
}
// Sort is a convenience method.
func (obj RHVSlice) Sort() { sort.Sort(obj) }
// RHVSort returns a deterministically sorted slice of all vertices in the list.
// The order is sorted by the Kind, taking into account the hierarchy of names
// separated by colons. Afterwards, it uses String() to avoid the
// non-determinism in the map type. RHV stands for Reverse Hierarchical Vertex,
// meaning the hierarchical topology of the vertex (resource) names are used.
func RHVSort(vertices []pgraph.Vertex) []pgraph.Vertex {
var vs []pgraph.Vertex
for _, v := range vertices { // copy first
vs = append(vs, v)
}
sort.Sort(RHVSlice(vs)) // add determinism
return vs
}

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,8 +13,23 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// Package graph contains the actual implementation of the resource graph engine
// that runs the graph of resources in real-time. This package has the algorithm
// that runs all the graph transitions.
package graph package graph
import ( import (
@@ -25,6 +40,7 @@ import (
"github.com/purpleidea/mgmt/converger" "github.com/purpleidea/mgmt/converger"
"github.com/purpleidea/mgmt/engine" "github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/engine/local"
engineUtil "github.com/purpleidea/mgmt/engine/util" engineUtil "github.com/purpleidea/mgmt/engine/util"
"github.com/purpleidea/mgmt/pgraph" "github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
@@ -40,16 +56,18 @@ const (
// Engine encapsulates a generic graph and manages its operations. // Engine encapsulates a generic graph and manages its operations.
type Engine struct { type Engine struct {
Program string Program string
Version string
Hostname string Hostname string
World engine.World
Converger *converger.Coordinator
Local *local.API
World engine.World
// Prefix is a unique directory prefix which can be used. It should be // Prefix is a unique directory prefix which can be used. It should be
// created if needed. // created if needed.
Prefix string Prefix string
Converger *converger.Coordinator Debug bool
Logf func(format string, v ...interface{})
Debug bool
Logf func(format string, v ...interface{})
graph *pgraph.Graph graph *pgraph.Graph
nextGraph *pgraph.Graph nextGraph *pgraph.Graph
@@ -57,6 +75,9 @@ type Engine struct {
waits map[pgraph.Vertex]*sync.WaitGroup // wg for the Worker func waits map[pgraph.Vertex]*sync.WaitGroup // wg for the Worker func
wlock *sync.Mutex // lock around waits map wlock *sync.Mutex // lock around waits map
mlock *sync.Mutex // metas lock
metas map[engine.ResPtrUID]*engine.MetaState // meta state
slock *sync.Mutex // semaphore lock slock *sync.Mutex // semaphore lock
semas map[string]*semaphore.Semaphore semas map[string]*semaphore.Semaphore
@@ -93,6 +114,9 @@ func (obj *Engine) Init() error {
obj.waits = make(map[pgraph.Vertex]*sync.WaitGroup) obj.waits = make(map[pgraph.Vertex]*sync.WaitGroup)
obj.wlock = &sync.Mutex{} obj.wlock = &sync.Mutex{}
obj.mlock = &sync.Mutex{}
obj.metas = make(map[engine.ResPtrUID]*engine.MetaState)
obj.slock = &sync.Mutex{} obj.slock = &sync.Mutex{}
obj.semas = make(map[string]*semaphore.Semaphore) obj.semas = make(map[string]*semaphore.Semaphore)
@@ -134,7 +158,7 @@ func (obj *Engine) Validate() error {
} }
if err := engine.Validate(res); err != nil { if err := engine.Validate(res); err != nil {
return errwrap.Wrapf(err, "the Res did not Validate") return fmt.Errorf("%s did not Validate: %v", res, err)
} }
} }
return nil return nil
@@ -151,8 +175,21 @@ func (obj *Engine) Apply(fn func(*pgraph.Graph) error) error {
// it errors, then the running graph wasn't changed. It is recommended that you // it errors, then the running graph wasn't changed. It is recommended that you
// pause the engine before running this, and resume it after you're done. // pause the engine before running this, and resume it after you're done.
func (obj *Engine) Commit() error { func (obj *Engine) Commit() error {
// It would be safer to lock this, but it would be slower and mask bugs.
//obj.mutex.Lock()
//defer obj.mutex.Unlock()
// TODO: Does this hurt performance or graph changes ? // TODO: Does this hurt performance or graph changes ?
activeMetas := make(map[engine.ResPtrUID]struct{})
for vertex := range obj.state {
res, ok := vertex.(engine.Res)
if !ok { // should not happen, previously validated
return fmt.Errorf("not a Res")
}
activeMetas[engine.PtrUID(res)] = struct{}{} // add
}
start := []func() error{} // functions to run after graphsync to start... start := []func() error{} // functions to run after graphsync to start...
vertexAddFn := func(vertex pgraph.Vertex) error { vertexAddFn := func(vertex pgraph.Vertex) error {
// some of these validation steps happen before this Commit step // some of these validation steps happen before this Commit step
@@ -170,12 +207,14 @@ func (obj *Engine) Commit() error {
return fmt.Errorf("the Res state already exists") return fmt.Errorf("the Res state already exists")
} }
activeMetas[engine.PtrUID(res)] = struct{}{} // add
if obj.Debug { if obj.Debug {
obj.Logf("Validate(%s)", res) obj.Logf("Validate(%s)", res)
} }
err := engine.Validate(res) err := engine.Validate(res)
if obj.Debug { if obj.Debug {
obj.Logf("Validate(%s): Return(%+v)", res, err) obj.Logf("Validate(%s): Return(%s)", res, engineUtil.CleanError(err))
} }
if err != nil { if err != nil {
return errwrap.Wrapf(err, "the Res did not Validate") return errwrap.Wrapf(err, "the Res did not Validate")
@@ -195,11 +234,13 @@ func (obj *Engine) Commit() error {
Vertex: vertex, Vertex: vertex,
Program: obj.Program, Program: obj.Program,
Version: obj.Version,
Hostname: obj.Hostname, Hostname: obj.Hostname,
//Converger: obj.Converger,
Local: obj.Local,
World: obj.World, World: obj.World,
Prefix: statePrefix, Prefix: statePrefix,
//Converger: obj.Converger,
Debug: obj.Debug, Debug: obj.Debug,
Logf: func(format string, v ...interface{}) { Logf: func(format string, v ...interface{}) {
@@ -227,10 +268,18 @@ func (obj *Engine) Commit() error {
obj.wlock.Unlock() obj.wlock.Unlock()
}() }()
obj.Logf("Worker(%s)", v) if obj.Debug || true {
obj.Logf("%s: Working...", v)
}
// contains the Watch and CheckApply loops // contains the Watch and CheckApply loops
err := obj.Worker(v) err := obj.Worker(v)
obj.Logf("Worker(%s): Exited(%+v)", v, err) if obj.Debug || true {
if s := engineUtil.CleanError(err); err != nil {
obj.Logf("%s: Error: %s", v, s)
} else {
obj.Logf("%s: Exited...", v)
}
}
obj.state[v].workerErr = err // store the error obj.state[v].workerErr = err // store the error
// If the Rewatch metaparam is true, then this will get // If the Rewatch metaparam is true, then this will get
// restarted if we do a graph cmp swap. This is why the // restarted if we do a graph cmp swap. This is why the
@@ -245,15 +294,21 @@ func (obj *Engine) Commit() error {
free := []func() error{} // functions to run after graphsync to reset... free := []func() error{} // functions to run after graphsync to reset...
vertexRemoveFn := func(vertex pgraph.Vertex) error { vertexRemoveFn := func(vertex pgraph.Vertex) error {
res, ok := vertex.(engine.Res)
if !ok { // should not happen, previously validated
return fmt.Errorf("not a Res")
}
delete(activeMetas, engine.PtrUID(res))
// wait for exit before starting new graph! // wait for exit before starting new graph!
close(obj.state[vertex].removeDone) // causes doneChan to close close(obj.state[vertex].removeDone) // causes doneCtx to cancel
obj.state[vertex].Resume() // unblock from resume close(obj.state[vertex].resumeSignal) // unblock (it only closes here)
obj.waits[vertex].Wait() // sync obj.waits[vertex].Wait() // sync
// close the state and resource // close the state and resource
// FIXME: will this mess up the sync and block the engine? // FIXME: will this mess up the sync and block the engine?
if err := obj.state[vertex].Close(); err != nil { if err := obj.state[vertex].Cleanup(); err != nil {
return errwrap.Wrapf(err, "the Res did not Close") return errwrap.Wrapf(err, "the Res did not Cleanup")
} }
// delete to free up memory from old graphs // delete to free up memory from old graphs
@@ -309,6 +364,23 @@ func (obj *Engine) Commit() error {
if err := obj.graph.GraphSync(obj.nextGraph, vertexCmpFn, vertexAddFn, vertexRemoveFn, engine.EdgeCmpFn); err != nil { if err := obj.graph.GraphSync(obj.nextGraph, vertexCmpFn, vertexAddFn, vertexRemoveFn, engine.EdgeCmpFn); err != nil {
return errwrap.Wrapf(err, "error running graph sync") return errwrap.Wrapf(err, "error running graph sync")
} }
// This happens after GraphSync when vertexRemoveFn and vertexAddFn are
// done running. Those two modified the activeMetas map. It's important
// that vertexRemoveFn runs before vertexAddFn, but GraphSync guarantees
// that, and it would be kind of illogical to not run things that way.
metaGC := make(map[engine.ResPtrUID]struct{}) // which metas should we garbage collect?
obj.mlock.Lock()
for ptrUID := range obj.metas {
if _, exists := activeMetas[ptrUID]; !exists {
metaGC[ptrUID] = struct{}{}
}
}
for ptrUID := range metaGC {
delete(obj.metas, ptrUID) // otherwise, this could grow forever
}
obj.mlock.Unlock()
// We run these afterwards, so that we don't unnecessarily start anyone // We run these afterwards, so that we don't unnecessarily start anyone
// if GraphSync failed in some way. Otherwise we'd have to do clean up! // if GraphSync failed in some way. Otherwise we'd have to do clean up!
for _, fn := range start { for _, fn := range start {
@@ -344,8 +416,13 @@ func (obj *Engine) Commit() error {
// Resume runs the currently active graph. It also un-pauses the graph if it was // Resume runs the currently active graph. It also un-pauses the graph if it was
// paused. Very little that is interesting should happen here. It all happens in // paused. Very little that is interesting should happen here. It all happens in
// the Commit method. After Commit, new things are already started, but we still // the Commit method. After Commit, new things are already started, but we still
// need to Resume any pre-existing resources. // need to Resume any pre-existing resources. Do not call this concurrently with
// the Pause method.
func (obj *Engine) Resume() error { func (obj *Engine) Resume() error {
// It would be safer to lock this, but it would be slower and mask bugs.
//obj.mutex.Lock()
//defer obj.mutex.Unlock()
if !obj.paused { if !obj.paused {
return fmt.Errorf("already resumed") return fmt.Errorf("already resumed")
} }
@@ -358,8 +435,22 @@ func (obj *Engine) Resume() error {
reversed := pgraph.Reverse(topoSort) reversed := pgraph.Reverse(topoSort)
for _, vertex := range reversed { for _, vertex := range reversed {
// The very first resume is skipped as those resources are
// already running! We could do that by checking here, but it is
// more convenient to just have a state struct field (paused) to
// track things for this instead. As a bonus, it helps us know
// if a resource is paused or not if we print for debugging.
//if !obj.state[vertex].initialStartupDone {
// obj.state[vertex].initialStartupDone = true
// continue
//}
//obj.state[vertex].starter = (indegree[vertex] == 0) //obj.state[vertex].starter = (indegree[vertex] == 0)
obj.state[vertex].Resume() // doesn't error obj.state[vertex].Resume() // doesn't error
// This always works because if a resource errored while it was
// paused, then we're in the paused state and we can still exit
// from there. If a resource errors when we're trying to Pause
// then it will only succeed without error if the resource ACKs.
} }
// we wait for everyone to start before exiting! // we wait for everyone to start before exiting!
obj.paused = false obj.paused = false
@@ -380,6 +471,10 @@ func (obj *Engine) SetFastPause() {
// Pause the active, running graph. // Pause the active, running graph.
func (obj *Engine) Pause(fastPause bool) error { func (obj *Engine) Pause(fastPause bool) error {
// It would be safer to lock this, but it would be slower and mask bugs.
//obj.mutex.Lock()
//defer obj.mutex.Unlock()
if obj.paused { if obj.paused {
return fmt.Errorf("already paused") return fmt.Errorf("already paused")
} }
@@ -401,8 +496,10 @@ func (obj *Engine) Pause(fastPause bool) error {
return nil return nil
} }
// Close triggers a shutdown. Engine must be already paused before this is run. // Shutdown the engine. Engine must be already paused before this is run. It is
func (obj *Engine) Close() error { // actually just a Load of an empty graph and a Commit. It waits for all the
// resources to exit before returning.
func (obj *Engine) Shutdown() error {
emptyGraph, reterr := pgraph.NewGraph("empty") emptyGraph, reterr := pgraph.NewGraph("empty")
// this is a graph switch (graph sync) that switches to an empty graph! // this is a graph switch (graph sync) that switches to an empty graph!

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,9 +13,21 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// +build !root //go:build !root
package graph package graph

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package graph package graph

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,13 +13,24 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package graph package graph
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path" "path"
"sort" "sort"
@@ -161,7 +172,7 @@ func (obj *Engine) ReversalList() (map[string]string, error) {
result := make(map[string]string) // some key to contents result := make(map[string]string) // some key to contents
dir := obj.statePrefix() // loop through this dir... dir := obj.statePrefix() // loop through this dir...
files, err := ioutil.ReadDir(dir) files, err := os.ReadDir(dir)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return nil, errwrap.Wrapf(err, "error reading list of state dirs") return nil, errwrap.Wrapf(err, "error reading list of state dirs")
} else if err != nil { } else if err != nil {
@@ -171,7 +182,7 @@ func (obj *Engine) ReversalList() (map[string]string, error) {
for _, x := range files { for _, x := range files {
key := x.Name() // some uid for the resource key := x.Name() // some uid for the resource
file := path.Join(dir, key, ReverseFile) file := path.Join(dir, key, ReverseFile)
content, err := ioutil.ReadFile(file) content, err := os.ReadFile(file)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return nil, errwrap.Wrapf(err, "could not read reverse file: %s", file) return nil, errwrap.Wrapf(err, "could not read reverse file: %s", file)
} else if err != nil { } else if err != nil {
@@ -231,9 +242,9 @@ func (obj *State) ReversalInit() error {
return obj.ReversalWrite(str, res.ReversibleMeta().Overwrite) // Store! return obj.ReversalWrite(str, res.ReversibleMeta().Overwrite) // Store!
} }
// ReversalClose performs the reversal shutdown steps if necessary for this // ReversalCleanup performs the reversal shutdown steps if necessary for this
// resource. // resource.
func (obj *State) ReversalClose() error { func (obj *State) ReversalCleanup() error {
res, ok := obj.Vertex.(engine.ReversibleRes) res, ok := obj.Vertex.(engine.ReversibleRes)
if !ok { if !ok {
return nil // nothing to do return nil // nothing to do
@@ -246,7 +257,7 @@ func (obj *State) ReversalClose() error {
return nil // nothing to erase, we're not a reversal resource return nil // nothing to erase, we're not a reversal resource
} }
if !obj.isStateOK { // did we successfully reverse? if !obj.isStateOK.Load() { // did we successfully reverse? (mutex RLock/RUnlock)
obj.Logf("did not complete reversal") // warn obj.Logf("did not complete reversal") // warn
return nil return nil
} }
@@ -263,7 +274,7 @@ func (obj *State) ReversalWrite(str string, overwrite bool) error {
} }
file := path.Join(dir, ReverseFile) // return a unique file file := path.Join(dir, ReverseFile) // return a unique file
content, err := ioutil.ReadFile(file) content, err := os.ReadFile(file)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return errwrap.Wrapf(err, "could not read reverse file: %s", file) return errwrap.Wrapf(err, "could not read reverse file: %s", file)
} }
@@ -275,12 +286,12 @@ func (obj *State) ReversalWrite(str string, overwrite bool) error {
if str != oldStr { if str != oldStr {
obj.Logf("existing, pending, reversible resource exists") obj.Logf("existing, pending, reversible resource exists")
//obj.Logf("diff:") //obj.Logf("diff:")
//obj.Logf("") // TODO: print the diff w/o and secret values //obj.Logf("") // TODO: print the diff w/o secret values
return fmt.Errorf("existing, pending, reversible resource exists") return fmt.Errorf("existing, pending, reversible resource exists")
} }
} }
return ioutil.WriteFile(file, []byte(str), ReversePerm) return os.WriteFile(file, []byte(str), ReversePerm)
} }
// ReversalDelete removes the reversal state information for this resource. // ReversalDelete removes the reversal state information for this resource.

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package graph package graph

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,9 +13,21 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// +build !root //go:build !root
package graph package graph

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,33 +13,114 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package graph package graph
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"sort"
"github.com/purpleidea/mgmt/engine" "github.com/purpleidea/mgmt/engine"
engineUtil "github.com/purpleidea/mgmt/engine/util" engineUtil "github.com/purpleidea/mgmt/engine/util"
"github.com/purpleidea/mgmt/lang/types"
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
) )
// RecvFn represents a custom Recv function which can be used in place of the
// stock, built-in one. This is needed if we want to receive from a different
// resource data source than our own. (Only for special occasions of course!)
type RecvFn func(engine.RecvableRes) (map[string]*engine.Send, error)
// 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.
// It applies the loaded values to the resource. // It applies the loaded values to the resource. It is called recursively, as it
func (obj *Engine) SendRecv(res engine.RecvableRes) (map[string]bool, error) { // recurses into any grouped resources found within the first receiver. It
recv := res.Recv() // returns a map of resource pointer, to resource field key, to changed boolean.
if obj.Debug { func SendRecv(res engine.RecvableRes, fn RecvFn) (map[engine.RecvableRes]map[string]*engine.Send, error) {
// NOTE: this could expose private resource data like passwords updated := make(map[engine.RecvableRes]map[string]*engine.Send) // list of updated keys
obj.Logf("%s: SendRecv: %+v", res, recv) if groupableRes, ok := res.(engine.GroupableRes); ok {
for _, x := range groupableRes.GetGroup() { // grouped elements
recvableRes, ok := x.(engine.RecvableRes)
if !ok {
continue
}
//if obj.Debug {
// obj.Logf("SendRecv: %s: grouped: %s", res, x) // receiving here
//}
// We need to recurse here so that autogrouped resources
// inside autogrouped resources would work... In case we
// work correctly. We just need to make sure that things
// are grouped in the correct order, but that is not our
// problem! Recurse and merge in the changed results...
innerUpdated, err := SendRecv(recvableRes, fn)
if err != nil {
return nil, errwrap.Wrapf(err, "recursive SendRecv error")
}
for r, m := range innerUpdated { // res ptr, map
if _, exists := updated[r]; !exists {
updated[r] = make(map[string]*engine.Send)
}
for s, send := range m { // map[string]*engine.Send
b := send.Changed
// don't overwrite in case one exists...
if old, exists := updated[r][s]; exists {
b = b || old.Changed // unlikely i think
}
if _, exists := updated[r][s]; !exists {
newSend := &engine.Send{
Res: send.Res,
Key: send.Key,
Changed: b,
}
updated[r][s] = newSend
}
updated[r][s].Changed = b
}
}
}
} }
var updated = make(map[string]bool) // list of updated keys
var err error var err error
for k, v := range recv { recv := res.Recv()
updated[k] = false // default if fn != nil {
v.Changed = false // reset to the default recv, err = fn(res) // use a custom Recv function
if err != nil {
return nil, err
}
}
keys := []string{}
for k := range recv { // map[string]*Send
keys = append(keys, k)
}
sort.Strings(keys)
//if obj.Debug && len(keys) > 0 {
// // NOTE: this could expose private resource data like passwords
// obj.Logf("SendRecv: %s recv: %+v", res, strings.Join(keys, ", "))
//}
for k, v := range recv { // map[string]*Send
// v.Res // SendableRes // a handle to the resource which is sending a value
// v.Key // string // the key in the resource that we're sending
if _, exists := updated[res]; !exists {
updated[res] = make(map[string]*engine.Send)
}
//updated[res][k] = false // default
v.Changed = false // reset to the default
updated[res][k] = v // default
var st interface{} = v.Res // old style direct send/recv var st interface{} = v.Res // old style direct send/recv
if true { // new style send/recv API if true { // new style send/recv API
@@ -71,7 +152,7 @@ func (obj *Engine) SendRecv(res engine.RecvableRes) (map[string]bool, error) {
} }
obj1 := reflect.Indirect(reflect.ValueOf(st)) obj1 := reflect.Indirect(reflect.ValueOf(st))
type1 := obj1.Type() //type1 := obj1.Type()
value1 := obj1.FieldByName(key1) value1 := obj1.FieldByName(key1)
kind1 := value1.Kind() kind1 := value1.Kind()
@@ -89,52 +170,110 @@ func (obj *Engine) SendRecv(res engine.RecvableRes) (map[string]bool, error) {
} }
obj2 := reflect.Indirect(reflect.ValueOf(res)) // pass in full struct obj2 := reflect.Indirect(reflect.ValueOf(res)) // pass in full struct
type2 := obj2.Type() //type2 := obj2.Type()
value2 := obj2.FieldByName(key2) value2 := obj2.FieldByName(key2)
kind2 := value2.Kind() kind2 := value2.Kind()
if obj.Debug { //orig := value1
obj.Logf("Send(%s) has %v: %v", type1, kind1, value1) dest := value2 // save the o.g. because we need the real dest!
obj.Logf("Recv(%s) has %v: %v", type2, kind2, value2)
// NOTE: Reminder: obj1 comes from st and it is the *<Res>Sends
// struct which contains whichever fields that resource sends.
// For example, this might be *TestSends for the Test resource.
// The receiver is obj2 and that is actually the resource struct
// which is a *<Res> and which gets it's fields directly set on.
// For example, this might be *TestRes for the Test resource.
//fmt.Printf("obj1(%T): %+v\n", obj1, obj1)
//fmt.Printf("obj2(%T): %+v\n", obj2, obj2)
// Lastly, remember that many of the type incompatibilities are
// caught during type unification, and so we might have overly
// relaxed the checks here and something could slip by. If we
// find something, this code will need new checks added back.
// Here we unpack one-level, and then leave the complex stuff
// for the Into() method below.
// for kind1 == reflect.Interface || kind1 == reflect.Ptr // wrong
// if kind1 == reflect.Interface || kind1 == reflect.Ptr // wrong
// for kind1 == reflect.Interface // wrong
if kind1 == reflect.Interface {
value1 = value1.Elem() // un-nest one interface
kind1 = value1.Kind()
} }
// i think we probably want the same kind, at least for now... // This second block is identical, but it's just accidentally
if kind1 != kind2 { // symmetrical. The types of input structs are different shapes.
e := fmt.Errorf("kind mismatch between %s: %s and %s: %s", v.Res, kind1, res, kind2) // for kind2 == reflect.Interface || kind2 == reflect.Ptr // wrong
err = errwrap.Append(err, e) // list of errors // if kind2 == reflect.Interface || kind2 == reflect.Ptr // wrong
continue // for kind2 == reflect.Interface // wrong
if kind2 == reflect.Interface {
value2 = value2.Elem() // un-nest one interface
kind2 = value2.Kind()
} }
// if the types don't match, we can't use send->recv //if obj.Debug {
// FIXME: do we want to relax this for string -> *string ? // obj.Logf("Send(%s) has %v: %v", type1, kind1, value1)
if e := TypeCmp(value1, value2); e != nil { // obj.Logf("Recv(%s) has %v: %v", type2, kind2, value2)
e := errwrap.Wrapf(e, "type mismatch between %s and %s", v.Res, res) //}
err = errwrap.Append(err, e) // list of errors
continue // Skip this check in favour of the more complex Into() below...
} //if kind1 != kind2 {
// e := fmt.Errorf("send/recv kind mismatch between %s: %s and %s: %s", v.Res, kind1, res, kind2)
// err = errwrap.Append(err, e) // list of errors
// continue
//}
// Skip this check in favour of the more complex Into() below...
//if e := TypeCmp(value1, value2); e != nil {
// e := errwrap.Wrapf(e, "type mismatch between %s and %s", v.Res, res)
// err = errwrap.Append(err, e) // list of errors
// continue
//}
// if we can't set, then well this is pointless! // if we can't set, then well this is pointless!
if !value2.CanSet() { if !dest.CanSet() {
e := fmt.Errorf("can't set %s.%s", res, k) e := fmt.Errorf("can't set %s.%s", res, k)
err = errwrap.Append(err, e) // list of errors err = errwrap.Append(err, e) // list of errors
continue continue
} }
// if we can't interface, we can't compare... // if we can't interface, we can't compare...
if !value1.CanInterface() || !value2.CanInterface() { if !value1.CanInterface() {
e := fmt.Errorf("can't interface %s.%s", v.Res, v.Key)
err = errwrap.Append(err, e) // list of errors
continue
}
if !value2.CanInterface() {
e := fmt.Errorf("can't interface %s.%s", res, k) e := fmt.Errorf("can't interface %s.%s", res, k)
err = errwrap.Append(err, e) // list of errors err = errwrap.Append(err, e) // list of errors
continue continue
} }
// if the values aren't equal, we're changing the receiver // if the values aren't equal, we're changing the receiver
if !reflect.DeepEqual(value1.Interface(), value2.Interface()) { if reflect.DeepEqual(value1.Interface(), value2.Interface()) {
// TODO: can we catch the panics here in case they happen? continue // skip as they're the same, no error needed
value2.Set(value1) // do it for all types that match
updated[k] = true // we updated this key!
v.Changed = true // tag this key as updated!
obj.Logf("SendRecv: %s.%s -> %s.%s", v.Res, v.Key, res, k)
} }
// TODO: can we catch the panics here in case they happen?
fv, e := types.ValueOf(value1)
if e != nil {
e := errwrap.Wrapf(e, "bad value %s.%s", v.Res, v.Key)
err = errwrap.Append(err, e) // list of errors
continue
}
// mutate the struct field dest with the mcl data in fv
if e := types.Into(fv, dest); e != nil {
// runtime error, probably from using value res
e := errwrap.Wrapf(e, "mismatch: %s.%s (%s) -> %s.%s (%s)", v.Res, v.Key, kind1, res, k, kind2)
err = errwrap.Append(err, e) // list of errors
continue
}
//dest.Set(orig) // do it for all types that match
//updated[res][k] = true // we updated this key!
v.Changed = true // tag this key as updated!
updated[res][k] = v // we updated this key!
//obj.Logf("SendRecv: %s.%s -> %s.%s (%+v)", v.Res, v.Key, res, k, fv) // fv may be private data
} }
return updated, err return updated, err
} }
@@ -150,3 +289,19 @@ func TypeCmp(a, b reflect.Value) error {
return nil // identical Type()'s return nil // identical Type()'s
} }
// UpdatedStrings returns a list of strings showing what was updated after a
// Send/Recv run returned the updated datastructure. This is useful for logs.
func UpdatedStrings(updated map[engine.RecvableRes]map[string]*engine.Send) []string {
out := []string{}
for r, m := range updated { // map[engine.RecvableRes]map[string]*engine.Send
for s, send := range m {
if !send.Changed {
continue
}
x := fmt.Sprintf("%v.%s -> %v.%s", send.Res, send.Key, r, s)
out = append(out, x)
}
}
return out
}

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,19 +13,34 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package graph package graph
import ( import (
"context"
"fmt" "fmt"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/purpleidea/mgmt/converger" "github.com/purpleidea/mgmt/converger"
"github.com/purpleidea/mgmt/engine" "github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/engine/local"
engineUtil "github.com/purpleidea/mgmt/engine/util"
"github.com/purpleidea/mgmt/pgraph" "github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util"
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
) )
@@ -40,39 +55,51 @@ type State struct {
Vertex pgraph.Vertex Vertex pgraph.Vertex
Program string Program string
Version string
Hostname string Hostname string
World engine.World
//Converger *converger.Coordinator
Local *local.API
World engine.World
// Prefix is a unique directory prefix which can be used. It should be // Prefix is a unique directory prefix which can be used. It should be
// created if needed. // created if needed.
Prefix string Prefix string
//Converger *converger.Coordinator
// Debug turns on additional output and behaviours. // Debug turns on additional output and behaviours.
Debug bool Debug bool
// Logf is the logging function that should be used to display messages. // Logf is the logging function that should be used to display messages.
Logf func(format string, v ...interface{}) Logf func(format string, v ...interface{})
timestamp int64 // last updated timestamp timestamp int64 // last updated timestamp
isStateOK bool // is state OK or do we need to run CheckApply ? isStateOK *atomic.Bool // is state OK or do we need to run CheckApply ?
workerErr error // did the Worker error? workerErr error // did the Worker error?
// doneChan closes when Watch should shut down. When any of the mutex *sync.RWMutex // used for editing state properties
// doneCtx is cancelled when Watch should shut down. When any of the
// following channels close, it causes this to close. // following channels close, it causes this to close.
doneChan chan struct{} doneCtx context.Context
// doneCtxCancel is the cancel function for doneCtx.
doneCtxCancel func()
// processDone is closed when the Process/CheckApply function fails // processDone is closed when the Process/CheckApply function fails
// permanently, and wants to cause Watch to exit. // permanently, and wants to cause Watch to exit.
processDone chan struct{} processDone chan struct{}
// watchDone is closed when the Watch function fails permanently, and we // watchDone is closed when the Watch function fails permanently, and we
// close this to signal we should definitely exit. (Often redundant.) // close this to signal we should definitely exit. (Often redundant.)
watchDone chan struct{} // could be shared with limitDone watchDone chan struct{} // could be shared with limitDone or retryDone
// limitDone is closed when the Watch function fails permanently, and we // limitDone is closed when the Watch function fails permanently, and we
// close this to signal we should definitely exit. This happens inside // close this to signal we should definitely exit. This happens inside
// of the limit loop of the Process section of Worker. // of the limit loop of the Process section of Worker.
limitDone chan struct{} // could be shared with watchDone limitDone chan struct{} // could be shared with watchDone or retryDone
// retryDone is closed when the Watch function fails permanently, and we
// close this to signal we should definitely exit. This happens inside
// of the retry loop of the Process section of Worker.
retryDone chan struct{} // could be shared with watchDone or limitDone
// removeDone is closed when the vertexRemoveFn method asks for an exit. // removeDone is closed when the vertexRemoveFn method asks for an exit.
// This happens when we're switching graphs. The switch to an "empty" is // This happens when we're switching graphs. The switch to an "empty" is
// the equivalent of asking for a final shutdown. // the equivalent of asking for a final shutdown.
@@ -94,14 +121,15 @@ type State struct {
// to send on since it is buffered. // to send on since it is buffered.
pokeChan chan struct{} // outgoing from resource pokeChan chan struct{} // outgoing from resource
// paused represents if this particular res is paused or not. // paused represents if this particular res is paused or not. This is
// primarily used to avoid running an unnecessary Resume on the first
// run of this resource.
paused bool paused bool
// pauseSignal closes to request a pause of this resource. // pauseSignal receives a message to request a pause of this resource.
pauseSignal chan struct{} pauseSignal chan struct{}
// resumeSignal closes to request a resume of this resource. // resumeSignal receives a message to resume this resource. The channel
// closes when the resource is removed from the graph.
resumeSignal chan struct{} resumeSignal chan struct{}
// pausedAck is used to send an ack message saying that we've paused.
pausedAck *util.EasyAck
wg *sync.WaitGroup // used for all vertex specific processes wg *sync.WaitGroup // used for all vertex specific processes
@@ -130,11 +158,15 @@ func (obj *State) Init() error {
return fmt.Errorf("the Logf function is missing") return fmt.Errorf("the Logf function is missing")
} }
obj.doneChan = make(chan struct{}) obj.isStateOK = &atomic.Bool{}
obj.mutex = &sync.RWMutex{}
obj.doneCtx, obj.doneCtxCancel = context.WithCancel(context.Background())
obj.processDone = make(chan struct{}) obj.processDone = make(chan struct{})
obj.watchDone = make(chan struct{}) obj.watchDone = make(chan struct{})
obj.limitDone = make(chan struct{}) obj.limitDone = make(chan struct{})
obj.retryDone = make(chan struct{})
obj.removeDone = make(chan struct{}) obj.removeDone = make(chan struct{})
obj.eventsDone = make(chan struct{}) obj.eventsDone = make(chan struct{})
@@ -144,8 +176,7 @@ func (obj *State) Init() error {
//obj.paused = false // starts off as started //obj.paused = false // starts off as started
obj.pauseSignal = make(chan struct{}) obj.pauseSignal = make(chan struct{})
//obj.resumeSignal = make(chan struct{}) // happens on pause obj.resumeSignal = make(chan struct{})
//obj.pausedAck = util.NewEasyAck() // happens on pause
obj.wg = &sync.WaitGroup{} obj.wg = &sync.WaitGroup{}
@@ -154,12 +185,12 @@ func (obj *State) Init() error {
obj.init = &engine.Init{ obj.init = &engine.Init{
Program: obj.Program, Program: obj.Program,
Version: obj.Version,
Hostname: obj.Hostname, Hostname: obj.Hostname,
// Watch: // Watch:
Running: obj.event, Running: obj.event,
Event: obj.event, Event: obj.event,
Done: obj.doneChan,
// CheckApply: // CheckApply:
Refresh: func() bool { Refresh: func() bool {
@@ -186,7 +217,7 @@ func (obj *State) Init() error {
FilteredGraph: func() (*pgraph.Graph, error) { FilteredGraph: func() (*pgraph.Graph, error) {
graph, err := pgraph.NewGraph("filtered") graph, err := pgraph.NewGraph("filtered")
if err != nil { if err != nil {
return nil, errwrap.Wrapf(err, "could not create graph") return nil, err
} }
// filter graph and build a new one... // filter graph and build a new one...
@@ -228,12 +259,13 @@ func (obj *State) Init() error {
return graph, nil // we return in a func so it's fresh! return graph, nil // we return in a func so it's fresh!
}, },
Local: obj.Local,
World: obj.World, World: obj.World,
VarDir: obj.varDir, VarDir: obj.varDir,
Debug: obj.Debug, Debug: obj.Debug,
Logf: func(format string, v ...interface{}) { Logf: func(format string, v ...interface{}) {
obj.Logf("resource: "+format, v...) obj.Logf(format, v...)
}, },
} }
@@ -249,7 +281,7 @@ func (obj *State) Init() error {
err := res.Init(obj.init) err := res.Init(obj.init)
if obj.Debug { if obj.Debug {
obj.Logf("Init(%s): Return(%+v)", res, err) obj.Logf("Init(%s): Return(%s)", res, engineUtil.CleanError(err))
} }
if err != nil { if err != nil {
return errwrap.Wrapf(err, "could not Init() resource") return errwrap.Wrapf(err, "could not Init() resource")
@@ -258,9 +290,9 @@ func (obj *State) Init() error {
return nil return nil
} }
// Close shuts down and performs any cleanup. This is most akin to a "post" or // Cleanup shuts down and performs any cleanup. This is most akin to a "post" or
// cleanup command as the initiator for closing a vertex happens in graph sync. // cleanup command as the initiator for closing a vertex happens in graph sync.
func (obj *State) Close() error { func (obj *State) Cleanup() error {
res, isRes := obj.Vertex.(engine.Res) res, isRes := obj.Vertex.(engine.Res)
if !isRes { if !isRes {
return fmt.Errorf("vertex is not a Res") return fmt.Errorf("vertex is not a Res")
@@ -283,15 +315,15 @@ func (obj *State) Close() error {
var reverr error var reverr error
// clear the reverse request from the disk... // clear the reverse request from the disk...
if err := obj.ReversalClose(); err != nil { if err := obj.ReversalCleanup(); err != nil {
// TODO: test this code path... // TODO: test this code path...
// TODO: should this be an error or a warning? // TODO: should this be an error or a warning?
reverr = err reverr = err
} }
reterr := res.Close() reterr := res.Cleanup()
if obj.Debug { if obj.Debug {
obj.Logf("Close(%s): Return(%+v)", res, reterr) obj.Logf("Close(%s): Return(%s)", res, engineUtil.CleanError(reterr))
} }
reterr = errwrap.Append(reterr, reverr) reterr = errwrap.Append(reterr, reverr)
@@ -306,37 +338,28 @@ func (obj *State) Close() error {
// callers are expected to make sure that they don't leave any of these running // callers are expected to make sure that they don't leave any of these running
// by the time the Worker() shuts down. // by the time the Worker() shuts down.
func (obj *State) Poke() { func (obj *State) Poke() {
// redundant
//if len(obj.pokeChan) > 0 {
// return
//}
select { select {
case obj.pokeChan <- struct{}{}: case obj.pokeChan <- struct{}{}:
default: // if chan is now full because more than one poke happened... default: // if chan is now full because more than one poke happened...
} }
} }
// Pause pauses this resource. It should not be called on any already paused // Pause pauses this resource. It must not be called on any already paused
// resource. It will block until the resource pauses with an acknowledgment, or // resource. It will block until the resource pauses with an acknowledgment, or
// until an exit for that resource is seen. If the latter happens it will error. // until an exit for that resource is seen. If the latter happens it will error.
// It is NOT thread-safe with the Resume() method so only call either one at a // It must not be called concurrently with either the Resume() method or itself,
// time. // so only call these one at a time and alternate between the two.
func (obj *State) Pause() error { func (obj *State) Pause() error {
if obj.paused { if obj.paused {
return fmt.Errorf("already paused") panic("already paused")
} }
obj.pausedAck = util.NewEasyAck()
obj.resumeSignal = make(chan struct{}) // build the resume signal
close(obj.pauseSignal)
obj.Poke() // unblock and notice the pause if necessary
// wait for ack (or exit signal) // wait for ack (or exit signal)
select { select {
case <-obj.pausedAck.Wait(): // we got it! case obj.pauseSignal <- struct{}{}:
// we're paused // we're paused
case <-obj.doneChan:
case <-obj.doneCtx.Done():
return engine.ErrClosed return engine.ErrClosed
} }
obj.paused = true obj.paused = true
@@ -344,23 +367,22 @@ func (obj *State) Pause() error {
return nil return nil
} }
// Resume unpauses this resource. It can be safely called on a brand-new // Resume unpauses this resource. It can be safely called once on a brand-new
// resource that has just started running without incident. It is NOT // resource that has just started running, without incident. It must not be
// thread-safe with the Pause() method, so only call either one at a time. // called concurrently with either the Pause() method or itself, so only call
// these one at a time and alternate between the two.
func (obj *State) Resume() { func (obj *State) Resume() {
// TODO: do we need a mutex around Resume? // This paused check prevents unnecessary "resume" calls to the resource
// on its first run, since resources start in the running state!
if !obj.paused { // no need to unpause brand-new resources if !obj.paused { // no need to unpause brand-new resources
return return
} }
obj.pauseSignal = make(chan struct{}) // rebuild for next pause select {
close(obj.resumeSignal) case obj.resumeSignal <- struct{}{}:
//obj.Poke() // not needed, we're already waiting for resume }
obj.paused = false obj.paused = false
// no need to wait for it to resume
//return // implied
} }
// event is a helper function to send an event to the CheckApply process loop. // event is a helper function to send an event to the CheckApply process loop.
@@ -372,7 +394,7 @@ func (obj *State) event() {
obj.setDirty() // assume we're initially dirty obj.setDirty() // assume we're initially dirty
select { select {
case obj.eventsChan <- nil: case obj.eventsChan <- nil: // blocks! (this is unbuffered)
// send! // send!
} }
@@ -383,11 +405,13 @@ func (obj *State) event() {
// CheckApply will have some work to do in order to converge it. // CheckApply will have some work to do in order to converge it.
func (obj *State) setDirty() { func (obj *State) setDirty() {
obj.tuid.StopTimer() obj.tuid.StopTimer()
obj.isStateOK = false //obj.mutex.Lock()
obj.isStateOK.Store(false) // concurrent write
//obj.mutex.Unlock()
} }
// poll is a replacement for Watch when the Poll metaparameter is used. // poll is a replacement for Watch when the Poll metaparameter is used.
func (obj *State) poll(interval uint32) error { func (obj *State) poll(ctx context.Context, interval uint32) error {
// create a time.Ticker for the given interval // create a time.Ticker for the given interval
ticker := time.NewTicker(time.Duration(interval) * time.Second) ticker := time.NewTicker(time.Duration(interval) * time.Second)
defer ticker.Stop() defer ticker.Stop()
@@ -399,7 +423,7 @@ func (obj *State) poll(interval uint32) error {
case <-ticker.C: // received the timer event case <-ticker.C: // received the timer event
obj.init.Logf("polling...") obj.init.Logf("polling...")
case <-obj.init.Done: // signal for shutdown request case <-ctx.Done(): // signal for shutdown request
return nil return nil
} }

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package graph package graph

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package engine package engine

334
engine/local/local.go Normal file
View File

@@ -0,0 +1,334 @@
// Mgmt
// Copyright (C) 2013-2024+ 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// Package local contains functions and interfaces that are shared between
// functions and resources. It's similar to the "world" functionality, except
// that it only involves local operations that stay within a single machine or
// local mgmt instance.
package local
import (
"context"
"fmt"
"os"
"path"
"strings"
"sync"
"github.com/purpleidea/mgmt/util"
)
// API implements the base handle for all the methods in this package. If we
// were going to have more than one implementation for all of these, then this
// would be an interface instead, and different packages would implement it.
// Since this is not the expectation for the local API, it's all self-contained.
type API struct {
Prefix string
Debug bool
Logf func(format string, v ...interface{})
// Each piece of the API can take a handle here.
*Value
}
// Init initializes the API before first use. It returns itself so it can be
// chained for API aesthetical purposes.
func (obj *API) Init() *API {
obj.Value = &Value{}
obj.Value.Init(&ValueInit{
Prefix: obj.Prefix,
Debug: obj.Debug,
Logf: obj.Logf,
})
return obj
}
// ValueInit are the init values that the Value API needs to work correctly.
type ValueInit struct {
Prefix string
Debug bool
Logf func(format string, v ...interface{})
}
// Value is the API for getting, setting, and watching local values.
type Value struct {
init *ValueInit
mutex *sync.Mutex
prefix string
prefixExists bool // is it okay to use the prefix?
values map[string]interface{}
notify map[chan struct{}]string // one chan (unique ptr) for each watch
skipread map[string]struct{}
}
// Init runs some initialization code for the Value API.
func (obj *Value) Init(init *ValueInit) {
obj.init = init
obj.mutex = &sync.Mutex{}
obj.prefix = fmt.Sprintf("%s/", path.Join(obj.init.Prefix, "value"))
obj.values = make(map[string]interface{})
obj.notify = make(map[chan struct{}]string)
obj.skipread = make(map[string]struct{})
// We don't need to, or want to, load any of the keys from disk
// initially, because (1) this would consume memory for keys we never
// use, and (2) we can load them on first read instead.
// TODO: build in some sort of expiry system that deletes keys older
// than X weeks to prevent infinite growth of the on-disk database.
}
// ValueGet pulls a value out of a local in-memory, key-value store that is
// backed by on-disk storage. While each value is intended to have an underlying
// type, we use the `any` or empty `interface{}` value to represent each value
// instead of a `types.Value` because it's more generic, and not limited to
// being used with the language type system. If the value doesn't exist, we
// return a nil value and no error.
func (obj *Value) ValueGet(ctx context.Context, key string) (interface{}, error) {
prefix, err := obj.getPrefix()
if err != nil {
return nil, err
}
obj.mutex.Lock()
defer obj.mutex.Unlock()
var val interface{}
//var err error
if _, skip := obj.skipread[key]; skip {
val, err = valueRead(ctx, prefix, key) // must return val == nil if missing
if err != nil {
// We had an actual read issue. Report this and stop
// because it means we might not be allowing our
// cold-cache warming if we ignored it.
return nil, err
}
// File not found errors are masked in the valueRead function
}
// Anything in memory, will override whatever we might have read.
value, exists := obj.values[key]
if !exists {
// disable future disk reads since the cache is now warm!
obj.skipread[key] = struct{}{}
return val, nil // if val is nil, we didn't find it
}
return value, nil
}
// ValueSet sets a value to our in-memory, key-value store that is backed by
// on-disk storage. If you provide a nil value, this is the equivalent of
// removing or deleting the value.
func (obj *Value) ValueSet(ctx context.Context, key string, value interface{}) error {
prefix, err := obj.getPrefix()
if err != nil {
return err
}
obj.mutex.Lock()
defer obj.mutex.Unlock()
// Write to state dir on disk first. If ctx cancels, we assume it's not
// written or it doesn't matter because we're cancelling, meaning we're
// shutting down, so our local cache can be invalidated anyways.
if value == nil { // remove/delete
if err := valueRemove(ctx, prefix, key); err != nil {
return err
}
} else {
if err := valueWrite(ctx, prefix, key, value); err != nil {
return err
}
}
if value == nil { // remove/delete
delete(obj.values, key)
} else {
obj.values[key] = value // store to in-memory map
}
// We still notify on remove/delete!
for ch, k := range obj.notify { // send notifications to any watchers...
if k != key { // there might be more than one watcher per key
continue
}
select {
case ch <- struct{}{}: // must be async and not block forever
// send
// We don't ever exit here, because that would be the equivalent
// of dropping a notification on the floor. This loop is
// non-blocking, and so it's okay to just finish it up quickly.
//case <-ctx.Done():
}
}
return nil
}
// ValueWatch watches a value from our in-memory, key-value store that is backed
// by on-disk storage. Conveniently, it never has to watch the on-disk storage,
// because after the initial startup which always sends a single startup event,
// it suffices to watch the in-memory store for events!
func (obj *Value) ValueWatch(ctx context.Context, key string) (chan struct{}, error) {
// No need to look at the prefix on disk, because we can do all our
// watches from memory!
//prefix, err := obj.getPrefix()
//if err != nil {
// return nil, err
//}
obj.mutex.Lock()
defer obj.mutex.Unlock()
notifyCh := make(chan struct{}, 1) // so we can async send
obj.notify[notifyCh] = key // add (while within the mutex)
notifyCh <- struct{}{} // startup signal, send one!
ch := make(chan struct{})
go func() {
defer func() { // cleanup
obj.mutex.Lock()
defer obj.mutex.Unlock()
delete(obj.notify, notifyCh) // free memory (in mutex)
}()
for {
select {
case _, ok := <-notifyCh:
if !ok {
// programming error
panic("unexpected channel closure")
}
// recv
case <-ctx.Done():
break // we exit
}
select {
case ch <- struct{}{}:
// send
case <-ctx.Done():
break // we exit
}
}
}()
return ch, nil
}
// getPrefix gets the prefix dir to use, or errors if it can't make one. It
// makes it on first use, and returns quickly from any future calls to it.
func (obj *Value) getPrefix() (string, error) {
// NOTE: Moving this mutex to just below the first early return, would
// be a benign race, but as it turns out, it's possible that a compiler
// would see this behaviour as "undefined" and things might not work as
// intended. It could perhaps be replaced with a sync/atomic primitive
// if we wanted better performance here.
obj.mutex.Lock()
defer obj.mutex.Unlock()
if obj.prefixExists { // former race read
return obj.prefix, nil
}
// MkdirAll instead of Mkdir because we have no idea if the parent
// local/ directory was already made yet or not. (If at all.) If path is
// already a directory, MkdirAll does nothing and returns nil. (Good!)
// TODO: I hope MkdirAll is thread-safe on path creation in case another
// future local API tries to make the base (parent) directory too!
if err := os.MkdirAll(obj.prefix, 0755); err != nil {
return "", err
}
obj.prefixExists = true // former race write
return obj.prefix, nil
}
func valueRead(ctx context.Context, prefix, key string) (interface{}, error) {
// TODO: implement ctx cancellation
// TODO: replace with my path library
if !strings.HasSuffix(prefix, "/") {
return nil, fmt.Errorf("prefix is not a dir")
}
if !strings.HasPrefix(prefix, "/") {
return nil, fmt.Errorf("prefix is not absolute")
}
p := fmt.Sprintf("%s%s", prefix, key)
b, err := os.ReadFile(p)
if os.IsNotExist(err) {
return nil, nil // not found
}
if err != nil {
return nil, err
}
// file exists!
s := string(b)
s = strings.TrimSpace(s) // get rid of any newline
return util.B64ToValue(s)
}
func valueWrite(ctx context.Context, prefix, key string, value interface{}) error {
// TODO: implement ctx cancellation
// TODO: replace with my path library
if !strings.HasSuffix(prefix, "/") {
return fmt.Errorf("prefix is not a dir")
}
if !strings.HasPrefix(prefix, "/") {
return fmt.Errorf("prefix is not absolute")
}
p := fmt.Sprintf("%s%s", prefix, key)
s, err := util.ValueToB64(value)
if err != nil {
return err
}
s += "\n" // files end with a newline
return os.WriteFile(p, []byte(s), 0600)
}
func valueRemove(ctx context.Context, prefix, key string) error {
// TODO: implement ctx cancellation
// TODO: replace with my path library
if !strings.HasSuffix(prefix, "/") {
return fmt.Errorf("prefix is not a dir")
}
if !strings.HasPrefix(prefix, "/") {
return fmt.Errorf("prefix is not absolute")
}
p := fmt.Sprintf("%s%s", prefix, key)
if err := os.Remove(p); err != nil && !os.IsNotExist(err) {
return err
}
return nil // ignore not found errors
}

View File

@@ -0,0 +1,62 @@
// Mgmt
// Copyright (C) 2013-2024+ 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
//go:build !root
package local
import (
"context"
"fmt"
"reflect"
"testing"
)
func TestWrite(t *testing.T) {
tmpdir := fmt.Sprintf("%s/", t.TempDir()) // gets cleaned up at end, new dir for each call
key := "test1"
value := 42
if err := valueWrite(context.Background(), tmpdir, key, value); err != nil {
t.Errorf("error: %+v", err)
return
}
if val, err := valueRead(context.Background(), tmpdir, key); err != nil {
t.Errorf("error: %+v", err)
return
} else if !reflect.DeepEqual(value, val) {
t.Errorf("error: not equal: %+v != %+v", val, value)
//return
}
if err := valueRemove(context.Background(), tmpdir, key); err != nil {
t.Errorf("error: %+v", err)
//return
}
}

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package engine package engine
@@ -36,8 +48,9 @@ var DefaultMetaParams = &MetaParams{
Poll: 0, // defaults to watching for events Poll: 0, // defaults to watching for events
Limit: rate.Inf, // defaults to no limit Limit: rate.Inf, // defaults to no limit
Burst: 0, // no burst needed on an infinite rate Burst: 0, // no burst needed on an infinite rate
Reset: false,
//Sema: []string{}, //Sema: []string{},
Rewatch: true, Rewatch: false,
Realize: false, // true would be more awesome, but unexpected for users Realize: false, // true would be more awesome, but unexpected for users
} }
@@ -64,9 +77,16 @@ type MetaParams struct {
// reason to want to do something differently for the Watch errors. // reason to want to do something differently for the Watch errors.
// Retry is the number of times to retry on error. Use -1 for infinite. // Retry is the number of times to retry on error. Use -1 for infinite.
// This value is used for both Watch and CheckApply.
Retry int16 `yaml:"retry"` Retry int16 `yaml:"retry"`
// Delay is the number of milliseconds to wait between retries. // RetryReset resets the retry count for CheckApply if it succeeds. This
// value is currently different from the count used for Watch.
// TODO: Consider resetting retry count for watch if it sends an event?
RetryReset bool `yaml:"retryreset"`
// Delay is the number of milliseconds to wait between retries. This
// value is used for both Watch and CheckApply.
Delay uint64 `yaml:"delay"` Delay uint64 `yaml:"delay"`
// Poll is the number of seconds between poll intervals. Use 0 to Watch. // Poll is the number of seconds between poll intervals. Use 0 to Watch.
@@ -78,6 +98,15 @@ type MetaParams struct {
// Burst is the number of events to allow in a burst. // Burst is the number of events to allow in a burst.
Burst int `yaml:"burst"` Burst int `yaml:"burst"`
// Reset causes the meta param state to reset when the resource changes.
// What this means is if you have a resource of a specific kind and name
// and in the subsequent graph it changes because one of its params
// changed, normally the Retry, and other params will remember their
// state, and you'll not reset the retry counter, however if this is
// true, then it will get reset. Note that any normal reset mechanisms
// built into retry are not affected by this.
Reset bool `yaml:"reset"`
// Sema is a list of semaphore ids in the form `id` or `id:count`. If // Sema is a list of semaphore ids in the form `id` or `id:count`. If
// you don't specify a count, then 1 is assumed. The sema of `foo` which // you don't specify a count, then 1 is assumed. The sema of `foo` which
// has a count equal to 1, is different from a sema named `foo:1` which // has a count equal to 1, is different from a sema named `foo:1` which
@@ -90,7 +119,9 @@ type MetaParams struct {
// and add it back as a new vertex, thus causing it to run again. This // and add it back as a new vertex, thus causing it to run again. This
// is different from the Retry metaparam which applies during the normal // is different from the Retry metaparam which applies during the normal
// execution. It is only when this is exhausted that we're in permanent // execution. It is only when this is exhausted that we're in permanent
// worker failure, and only then can we rely on this metaparam. // worker failure, and only then can we rely on this metaparam. This is
// false by default, as the frequency of graph changes makes it unlikely
// that you wanted this enabled on most resources.
Rewatch bool `yaml:"rewatch"` Rewatch bool `yaml:"rewatch"`
// Realize ensures that the resource is guaranteed to converge at least // Realize ensures that the resource is guaranteed to converge at least
@@ -133,6 +164,9 @@ func (obj *MetaParams) Cmp(meta *MetaParams) error {
if obj.Burst != meta.Burst { if obj.Burst != meta.Burst {
return fmt.Errorf("values for Burst are different") return fmt.Errorf("values for Burst are different")
} }
if obj.Reset != meta.Reset {
return fmt.Errorf("values for Reset are different")
}
if err := util.SortedStrSliceCompare(obj.Sema, meta.Sema); err != nil { if err := util.SortedStrSliceCompare(obj.Sema, meta.Sema); err != nil {
return errwrap.Wrapf(err, "values for Sema are different") return errwrap.Wrapf(err, "values for Sema are different")
@@ -180,6 +214,7 @@ func (obj *MetaParams) Copy() *MetaParams {
Poll: obj.Poll, Poll: obj.Poll,
Limit: obj.Limit, // FIXME: can we copy this type like this? test me! Limit: obj.Limit, // FIXME: can we copy this type like this? test me!
Burst: obj.Burst, Burst: obj.Burst,
Reset: obj.Reset,
Sema: sema, Sema: sema,
Rewatch: obj.Rewatch, Rewatch: obj.Rewatch,
Realize: obj.Realize, Realize: obj.Realize,
@@ -200,3 +235,18 @@ func (obj *MetaParams) UnmarshalYAML(unmarshal func(interface{}) error) error {
*obj = MetaParams(raw) // restore from indirection with type conversion! *obj = MetaParams(raw) // restore from indirection with type conversion!
return nil return nil
} }
// MetaState is some local meta param state that is saved between resources of
// the same kind+name unique ID. Even though the vertex/resource pointer might
// change during a graph switch (because the params changed) we might be
// logically referring to the same resource, and we might want to preserve some
// data across that switch. The common example is the resource retry count. If a
// resource failed three times, and we had a limit of five before it would be a
// permanent failure, then we don't want to reset this counter just because we
// changed a parameter (field) of the resource. This doesn't mean we don't want
// to ever reset these counts. For that, flip on the reset meta param.
type MetaState struct {
// CheckApplyRetry is the current retry count for CheckApply.
CheckApplyRetry int16
}

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,9 +13,21 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// +build !root //go:build !root
package engine package engine

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package engine package engine

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,14 +13,28 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package engine package engine
import ( import (
"context"
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"github.com/purpleidea/mgmt/engine/local"
"github.com/purpleidea/mgmt/pgraph" "github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
@@ -87,6 +101,9 @@ type Init struct {
// Program is the name of the program. // Program is the name of the program.
Program string Program string
// Version is the version of the program.
Version string
// Hostname is the uuid for the host. // Hostname is the uuid for the host.
Hostname string Hostname string
@@ -98,10 +115,6 @@ type Init struct {
// Event sends an event notifying the engine of a possible state change. // Event sends an event notifying the engine of a possible state change.
Event func() Event func()
// Done returns a channel that will close to signal to us that it's time
// for us to shutdown.
Done chan struct{}
// Called from within CheckApply: // Called from within CheckApply:
// Refresh returns whether the resource received a notification. This // Refresh returns whether the resource received a notification. This
@@ -135,6 +148,11 @@ type Init struct {
// TODO: GraphQuery offers an interface to query the resource graph. // TODO: GraphQuery offers an interface to query the resource graph.
// Local has a bunch of methods and properties which are useful for
// operations on the local machine and for communication between
// functions and resources.
Local *local.API
// World provides a connection to the outside world. This is most often // World provides a connection to the outside world. This is most often
// used for communicating with the distributed database. // used for communicating with the distributed database.
World World World World
@@ -190,17 +208,21 @@ type Res interface {
// and data from the engine. // and data from the engine.
Init(*Init) error Init(*Init) error
// Close is run by the engine to clean up after the resource is done. // Cleanup is run by the engine to clean up after the resource is done.
Close() error Cleanup() error
// Watch is run by the engine to monitor for state changes. If it // Watch is run by the engine to monitor for state changes. If it
// detects any, it notifies the engine which will usually run CheckApply // detects any, it notifies the engine which will usually run CheckApply
// in response. // in response. If the input context cancels, we must shutdown.
Watch() error Watch(context.Context) error
// CheckApply determines if the state of the resource is correct and if // CheckApply determines if the state of the resource is correct and if
// asked to with the `apply` variable, applies the requested state. // asked to with the `apply` variable, applies the requested state. If
CheckApply(apply bool) (checkOK bool, err error) // the input context cancels, we must return as quickly as possible. We
// should never exit immediately if this would cause permanent
// corruption of some sort. However it doesn't mean that a resource was
// taken to the desired state.
CheckApply(ctx context.Context, apply bool) (checkOK bool, err error)
// Cmp compares itself to another resource and returns an error if they // Cmp compares itself to another resource and returns an error if they
// are not equivalent. This is more strict than the Adapts method of the // are not equivalent. This is more strict than the Adapts method of the
@@ -220,6 +242,22 @@ func Stringer(res Res) string {
return Repr(res.Kind(), res.Name()) return Repr(res.Kind(), res.Name())
} }
// ResPtrUID is a unique identifier that is consistent for the kind and name of
// the resource only. This was formerly a string, but a struct is more precise.
// The result is suitable as a unique map key.
type ResPtrUID struct {
kind string
name string
}
// PtrUID generates a ResPtrUID from a resource. The result is suitable as a
// unique map key.
func PtrUID(res Res) ResPtrUID {
// the use of "repr" is kind of arbitrary as long as it's unique
//return ResPtrUID(Repr(res.Kind(), res.Name()))
return ResPtrUID{kind: res.Kind(), name: res.Name()}
}
// Validate validates a resource by checking multiple aspects. This is the main // Validate validates a resource by checking multiple aspects. This is the main
// entry point for running all the validation steps on a resource. // entry point for running all the validation steps on a resource.
func Validate(res Res) error { func Validate(res Res) error {

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,24 +13,35 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// +build !noaugeas //go:build !noaugeas
package resources package resources
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"strings" "strings"
"github.com/purpleidea/mgmt/engine" "github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/engine/traits" "github.com/purpleidea/mgmt/engine/traits"
"github.com/purpleidea/mgmt/recwatch"
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
"github.com/purpleidea/mgmt/util/recwatch"
// FIXME: we vendor go/augeas because master requires augeas 1.6.0
// and libaugeas-dev-1.6.0 is not yet available in a PPA.
"honnef.co/go/augeas" "honnef.co/go/augeas"
) )
@@ -51,24 +62,24 @@ type AugeasRes struct {
init *engine.Init init *engine.Init
// File is the path to the file targeted by this resource. // File is the path to the file targeted by this resource.
File string `yaml:"file"` File string `lang:"file" yaml:"file"`
// Lens is the lens used by this resource. If specified, mgmt // Lens is the lens used by this resource. If specified, mgmt
// will lower the augeas overhead by only loading that lens. // will lower the augeas overhead by only loading that lens.
Lens string `yaml:"lens"` Lens string `lang:"lens" yaml:"lens"`
// Sets is a list of changes that will be applied to the file, in the form of // Sets is a list of changes that will be applied to the file, in the
// ["path", "value"]. mgmt will run augeas.Get() before augeas.Set(), to // form of ["path", "value"]. mgmt will run augeas.Get() before
// prevent changing the file when it is not needed. // augeas.Set(), to prevent changing the file when it is not needed.
Sets []*AugeasSet `yaml:"sets"` Sets []*AugeasSet `lang:"sets" yaml:"sets"`
recWatcher *recwatch.RecWatcher // used to watch the changed files recWatcher *recwatch.RecWatcher // used to watch the changed files
} }
// AugeasSet represents a key/value pair of settings to be applied. // AugeasSet represents a key/value pair of settings to be applied.
type AugeasSet struct { type AugeasSet struct {
Path string `yaml:"path"` // The relative path to the value to be changed. Path string `lang:"path" yaml:"path"` // The relative path to the value to be changed.
Value string `yaml:"value"` // The value to be set on the given Path. Value string `lang:"value" yaml:"value"` // The value to be set on the given Path.
} }
// Cmp compares this set with another one. // Cmp compares this set with another one.
@@ -119,15 +130,15 @@ func (obj *AugeasRes) Init(init *engine.Init) error {
return nil return nil
} }
// Close is run by the engine to clean up after the resource is done. // Cleanup is run by the engine to clean up after the resource is done.
func (obj *AugeasRes) Close() error { func (obj *AugeasRes) Cleanup() error {
return nil return nil
} }
// Watch is the primary listener for this resource and it outputs events. This // Watch is the primary listener for this resource and it outputs events. This
// was taken from the File resource. // was taken from the File resource.
// FIXME: DRY - This is taken from the file resource // FIXME: DRY - This is taken from the file resource
func (obj *AugeasRes) Watch() error { func (obj *AugeasRes) Watch(ctx context.Context) error {
var err error var err error
obj.recWatcher, err = recwatch.NewRecWatcher(obj.File, false) obj.recWatcher, err = recwatch.NewRecWatcher(obj.File, false)
if err != nil { if err != nil {
@@ -156,7 +167,7 @@ func (obj *AugeasRes) Watch() error {
} }
send = true send = true
case <-obj.init.Done: // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }
@@ -169,7 +180,7 @@ func (obj *AugeasRes) Watch() error {
} }
// checkApplySet runs CheckApply for one element of the AugeasRes.Set // checkApplySet runs CheckApply for one element of the AugeasRes.Set
func (obj *AugeasRes) checkApplySet(apply bool, ag *augeas.Augeas, set *AugeasSet) (bool, error) { func (obj *AugeasRes) checkApplySet(ctx context.Context, apply bool, ag *augeas.Augeas, set *AugeasSet) (bool, error) {
fullpath := fmt.Sprintf("/files/%v/%v", obj.File, set.Path) fullpath := fmt.Sprintf("/files/%v/%v", obj.File, set.Path)
// We do not check for errors because errors are also thrown when // We do not check for errors because errors are also thrown when
@@ -194,7 +205,7 @@ func (obj *AugeasRes) checkApplySet(apply bool, ag *augeas.Augeas, set *AugeasSe
} }
// CheckApply method for Augeas resource. // CheckApply method for Augeas resource.
func (obj *AugeasRes) CheckApply(apply bool) (bool, error) { func (obj *AugeasRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
obj.init.Logf("CheckApply: %s", obj.File) obj.init.Logf("CheckApply: %s", obj.File)
// By default we do not set any option to augeas, we use the defaults. // By default we do not set any option to augeas, we use the defaults.
opts := augeas.None opts := augeas.None
@@ -231,7 +242,7 @@ func (obj *AugeasRes) CheckApply(apply bool) (bool, error) {
checkOK := true checkOK := true
for _, set := range obj.Sets { for _, set := range obj.Sets {
if setCheckOK, err := obj.checkApplySet(apply, &ag, set); err != nil { if setCheckOK, err := obj.checkApplySet(ctx, apply, &ag, set); err != nil {
return false, errwrap.Wrapf(err, "augeas: error during CheckApply of one Set") return false, errwrap.Wrapf(err, "augeas: error during CheckApply of one Set")
} else if !setCheckOK { } else if !setCheckOK {
checkOK = false checkOK = false

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package resources package resources
@@ -24,7 +36,7 @@ import (
"encoding/json" "encoding/json"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"io/ioutil" "io"
"net" "net"
"net/http" "net/http"
"regexp" "regexp"
@@ -147,26 +159,42 @@ var AwsRegions = []string{
// http://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html // http://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html
type AwsEc2Res struct { type AwsEc2Res struct {
traits.Base // add the base methods without re-implementation traits.Base // add the base methods without re-implementation
traits.Sendable
init *engine.Init init *engine.Init
State string `yaml:"state"` // state: running, stopped, terminated // State must be running, stopped, or terminated.
Region string `yaml:"region"` // region must match an element of AwsRegions State string `lang:"state" yaml:"state"`
Type string `yaml:"type"` // type of ec2 instance, eg: t2.micro
ImageID string `yaml:"imageid"` // imageid must be available on the chosen region // Region must match one of the AwsRegions. This list is static at the
// moment.
Region string `lang:"region" yaml:"region"`
// Type of ec2 instance, eg: t2.micro for example.
Type string `lang:"type" yaml:"type"`
// ImageID to use, and note that it must be available on the chosen
// region.
ImageID string `lang:"imageid" yaml:"imageid"`
// WatchEndpoint is the public url of the sns endpoint, eg:
// http://server:12345/ for example.
WatchEndpoint string `lang:"watchendpoint" yaml:"watchendpoint"`
// WatchListenAddr is the local address or port that the sns listens on,
// eg: 10.0.0.0:23456 or 23456.
WatchListenAddr string `lang:"watchlistenaddr" yaml:"watchlistenaddr"`
WatchEndpoint string `yaml:"watchendpoint"` // the public url of the sns endpoint, eg: http://server:12345/
WatchListenAddr string `yaml:"watchlistenaddr"` // the local address or port that the sns listens on, eg: 10.0.0.0:23456 or 23456
// ErrorOnMalformedPost controls whether or not malformed HTTP post // ErrorOnMalformedPost controls whether or not malformed HTTP post
// requests, that cause JSON decoder errors, will also make the engine // requests, that cause JSON decoder errors, will also make the engine
// shut down. If ErrorOnMalformedPost set to true and an error occurs, // shut down. If ErrorOnMalformedPost set to true and an error occurs,
// Watch() will return the error and the engine will shut down. // Watch() will return the error and the engine will shut down.
ErrorOnMalformedPost bool `yaml:"erroronmalformedpost"` ErrorOnMalformedPost bool `lang:"erroronmalformedpost" yaml:"erroronmalformedpost"`
// UserData is used to run bash and cloud-init commands on first launch. // UserData is used to run bash and cloud-init commands on first launch.
// See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html // See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html
// for documantation and examples. // for documantation and examples.
UserData string `yaml:"userdata"` UserData string `lang:"userdata" yaml:"userdata"`
client *ec2.EC2 // client session for AWS API calls client *ec2.EC2 // client session for AWS API calls
@@ -388,10 +416,11 @@ func (obj *AwsEc2Res) Init(init *engine.Init) error {
return nil return nil
} }
// Close cleans up when we're done. This is needed to delete some of the AWS // Cleanup cleans up when we're done. This is needed to delete some of the AWS
// objects created for the SNS endpoint. // objects created for the SNS endpoint.
func (obj *AwsEc2Res) Close() error { func (obj *AwsEc2Res) Cleanup() error {
var errList error var errList error
// XXX: do these in a defer of Watch instead.
// clean up sns objects created by Init/snsWatch // clean up sns objects created by Init/snsWatch
if obj.snsClient != nil { if obj.snsClient != nil {
// delete the topic and associated subscriptions // delete the topic and associated subscriptions
@@ -409,16 +438,16 @@ func (obj *AwsEc2Res) Close() error {
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *AwsEc2Res) Watch() error { func (obj *AwsEc2Res) Watch(ctx context.Context) error {
if obj.WatchListenAddr != "" { if obj.WatchListenAddr != "" {
return obj.snsWatch() return obj.snsWatch(ctx)
} }
return obj.longpollWatch() return obj.longpollWatch(ctx)
} }
// longpollWatch uses the ec2 api's built in methods to watch ec2 resource // longpollWatch uses the ec2 api's built in methods to watch ec2 resource
// state. // state.
func (obj *AwsEc2Res) longpollWatch() error { func (obj *AwsEc2Res) longpollWatch(ctx context.Context) error {
send := false send := false
// We tell the engine that we're running right away. This is not correct, // We tell the engine that we're running right away. This is not correct,
@@ -426,7 +455,7 @@ func (obj *AwsEc2Res) longpollWatch() error {
obj.init.Running() // when started, notify engine that we're running obj.init.Running() // when started, notify engine that we're running
// cancellable context used for exiting cleanly // cancellable context used for exiting cleanly
ctx, cancel := context.WithCancel(context.TODO()) innerCtx, cancel := context.WithCancel(context.TODO())
// clean up when we're done // clean up when we're done
defer obj.wg.Wait() defer obj.wg.Wait()
@@ -461,7 +490,7 @@ func (obj *AwsEc2Res) longpollWatch() error {
} }
// wait for the instance state to change // wait for the instance state to change
state, err := stateWaiter(ctx, instance, obj.client) state, err := stateWaiter(innerCtx, instance, obj.client)
if err != nil { if err != nil {
select { select {
case obj.awsChan <- &chanStruct{ case obj.awsChan <- &chanStruct{
@@ -502,7 +531,7 @@ func (obj *AwsEc2Res) longpollWatch() error {
send = true send = true
} }
case <-obj.init.Done: // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }
@@ -518,7 +547,7 @@ func (obj *AwsEc2Res) longpollWatch() error {
// Init() a CloudWatch rule is created along with a corresponding SNS topic that // Init() a CloudWatch rule is created along with a corresponding SNS topic that
// it can publish to. snsWatch creates an http server which listens for messages // it can publish to. snsWatch creates an http server which listens for messages
// published to the topic and processes them accordingly. // published to the topic and processes them accordingly.
func (obj *AwsEc2Res) snsWatch() error { func (obj *AwsEc2Res) snsWatch(ctx context.Context) error {
send := false send := false
defer obj.wg.Wait() defer obj.wg.Wait()
// create the sns listener // create the sns listener
@@ -533,9 +562,9 @@ func (obj *AwsEc2Res) snsWatch() error {
} }
// close the listener and shutdown the sns server when we're done // close the listener and shutdown the sns server when we're done
defer func() { defer func() {
ctx, cancel := context.WithTimeout(context.TODO(), SnsServerShutdownTimeout*time.Second) innerCtx, cancel := context.WithTimeout(context.TODO(), SnsServerShutdownTimeout*time.Second)
defer cancel() defer cancel()
if err := snsServer.Shutdown(ctx); err != nil { if err := snsServer.Shutdown(innerCtx); err != nil {
if err != context.Canceled { if err != context.Canceled {
obj.init.Logf("error stopping sns endpoint: %s", err) obj.init.Logf("error stopping sns endpoint: %s", err)
return return
@@ -596,7 +625,7 @@ func (obj *AwsEc2Res) snsWatch() error {
obj.init.Logf("State: %v", msg.event) obj.init.Logf("State: %v", msg.event)
send = true send = true
case <-obj.init.Done: // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }
@@ -608,7 +637,7 @@ func (obj *AwsEc2Res) snsWatch() error {
} }
// CheckApply method for AwsEc2 resource. // CheckApply method for AwsEc2 resource.
func (obj *AwsEc2Res) CheckApply(apply bool) (bool, error) { func (obj *AwsEc2Res) CheckApply(ctx context.Context, apply bool) (bool, error) {
obj.init.Logf("CheckApply(%t)", apply) obj.init.Logf("CheckApply(%t)", apply)
// find the instance we need to check // find the instance we need to check
@@ -718,7 +747,7 @@ func (obj *AwsEc2Res) CheckApply(apply bool) (bool, error) {
} }
// context to cancel the waiter if it takes too long // context to cancel the waiter if it takes too long
ctx, cancel := context.WithTimeout(context.TODO(), waitTimeout*time.Second) innerCtx, cancel := context.WithTimeout(ctx, waitTimeout*time.Second)
defer cancel() defer cancel()
// wait until the state converges // wait until the state converges
@@ -727,11 +756,11 @@ func (obj *AwsEc2Res) CheckApply(apply bool) (bool, error) {
} }
switch obj.State { switch obj.State {
case ec2.InstanceStateNameRunning: case ec2.InstanceStateNameRunning:
err = obj.client.WaitUntilInstanceRunningWithContext(ctx, waitInput) err = obj.client.WaitUntilInstanceRunningWithContext(innerCtx, waitInput)
case ec2.InstanceStateNameStopped: case ec2.InstanceStateNameStopped:
err = obj.client.WaitUntilInstanceStoppedWithContext(ctx, waitInput) err = obj.client.WaitUntilInstanceStoppedWithContext(innerCtx, waitInput)
case ec2.InstanceStateNameTerminated: case ec2.InstanceStateNameTerminated:
err = obj.client.WaitUntilInstanceTerminatedWithContext(ctx, waitInput) err = obj.client.WaitUntilInstanceTerminatedWithContext(innerCtx, waitInput)
default: default:
return false, errwrap.Wrapf(err, "unrecognized instance state") return false, errwrap.Wrapf(err, "unrecognized instance state")
} }
@@ -963,7 +992,7 @@ func (obj *AwsEc2Res) snsGetCert(url string) (*x509.Certificate, error) {
return nil, errwrap.Wrapf(err, "http get error") return nil, errwrap.Wrapf(err, "http get error")
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, errwrap.Wrapf(err, "error reading post body") return nil, errwrap.Wrapf(err, "error reading post body")
} }

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package resources package resources
@@ -57,7 +69,6 @@ type ConfigEtcdRes struct {
sizeFlag bool sizeFlag bool
interruptChan chan struct{} interruptChan chan struct{}
wg *sync.WaitGroup
} }
// Default returns some sensible defaults for this resource. // Default returns some sensible defaults for this resource.
@@ -83,26 +94,22 @@ func (obj *ConfigEtcdRes) Init(init *engine.Init) error {
obj.init = init // save for later obj.init = init // save for later
obj.interruptChan = make(chan struct{}) obj.interruptChan = make(chan struct{})
obj.wg = &sync.WaitGroup{}
return nil return nil
} }
// Close is run by the engine to clean up after the resource is done. // Cleanup is run by the engine to clean up after the resource is done.
func (obj *ConfigEtcdRes) Close() error { func (obj *ConfigEtcdRes) Cleanup() error {
obj.wg.Wait() // bonus
return nil return nil
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *ConfigEtcdRes) Watch() error { func (obj *ConfigEtcdRes) Watch(ctx context.Context) error {
obj.wg.Add(1) wg := &sync.WaitGroup{}
defer obj.wg.Done() defer wg.Wait()
// FIXME: add timeout to context innerCtx, cancel := context.WithCancel(ctx)
// The obj.init.Done channel is closed by the engine to signal shutdown.
ctx, cancel := util.ContextWithCloser(context.Background(), obj.init.Done)
defer cancel() defer cancel()
ch, err := obj.init.World.IdealClusterSizeWatch(util.CtxWithWg(ctx, obj.wg)) ch, err := obj.init.World.IdealClusterSizeWatch(util.CtxWithWg(innerCtx, wg))
if err != nil { if err != nil {
return errwrap.Wrapf(err, "could not watch ideal cluster size") return errwrap.Wrapf(err, "could not watch ideal cluster size")
} }
@@ -121,7 +128,8 @@ Loop:
} }
// pass through and send an event // pass through and send an event
case <-obj.init.Done: // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil
} }
obj.init.Event() // notify engine of an event (this can block) obj.init.Event() // notify engine of an event (this can block)
@@ -134,10 +142,10 @@ Loop:
// to zero, then it *won't* try and change it away from zero, because it assumes // to zero, then it *won't* try and change it away from zero, because it assumes
// that someone has requested a shutdown. If the value is seen on first startup, // that someone has requested a shutdown. If the value is seen on first startup,
// then it will change it, because it might be a zero from the previous cluster. // then it will change it, because it might be a zero from the previous cluster.
func (obj *ConfigEtcdRes) sizeCheckApply(apply bool) (bool, error) { func (obj *ConfigEtcdRes) sizeCheckApply(ctx context.Context, apply bool) (bool, error) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
defer wg.Wait() // this must be above the defer cancel() call defer wg.Wait() // this must be above the defer cancel() call
ctx, cancel := context.WithTimeout(context.Background(), sizeCheckApplyTimeout) ctx, cancel := context.WithTimeout(ctx, sizeCheckApplyTimeout)
defer cancel() defer cancel()
wg.Add(1) wg.Add(1)
go func() { go func() {
@@ -186,17 +194,17 @@ func (obj *ConfigEtcdRes) sizeCheckApply(apply bool) (bool, error) {
} }
// CheckApply method for Noop resource. Does nothing, returns happy! // CheckApply method for Noop resource. Does nothing, returns happy!
func (obj *ConfigEtcdRes) CheckApply(apply bool) (bool, error) { func (obj *ConfigEtcdRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
checkOK := true checkOK := true
if c, err := obj.sizeCheckApply(apply); err != nil { if c, err := obj.sizeCheckApply(ctx, apply); err != nil {
return false, err return false, err
} else if !c { } else if !c {
checkOK = false checkOK = false
} }
// TODO: add more config settings management here... // TODO: add more config settings management here...
//if c, err := obj.TODOCheckApply(apply); err != nil { //if c, err := obj.TODOCheckApply(ctx, apply); err != nil {
// return false, err // return false, err
//} else if !c { //} else if !c {
// checkOK = false // checkOK = false

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package resources package resources
@@ -108,8 +120,8 @@ func (obj *ConsulKVRes) Init(init *engine.Init) error {
return errwrap.Wrapf(err, "could not create Consul client") return errwrap.Wrapf(err, "could not create Consul client")
} }
// Close is run by the engine to clean up after the resource is done. // Cleanup is run by the engine to clean up after the resource is done.
func (obj *ConsulKVRes) Close() error { func (obj *ConsulKVRes) Cleanup() error {
if obj.config != nil && obj.config.Transport != nil { if obj.config != nil && obj.config.Transport != nil {
obj.config.Transport.CloseIdleConnections() obj.config.Transport.CloseIdleConnections()
} }
@@ -117,7 +129,7 @@ func (obj *ConsulKVRes) Close() error {
} }
// Watch is the listener and main loop for this resource and it outputs events. // Watch is the listener and main loop for this resource and it outputs events.
func (obj *ConsulKVRes) Watch() error { func (obj *ConsulKVRes) Watch(ctx context.Context) error {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
defer wg.Wait() defer wg.Wait()
@@ -132,9 +144,9 @@ func (obj *ConsulKVRes) Watch() error {
defer wg.Done() defer wg.Done()
opts := &api.QueryOptions{RequireConsistent: true} opts := &api.QueryOptions{RequireConsistent: true}
ctx, cancel := util.ContextWithCloser(context.Background(), exit) innerCtx, cancel := util.ContextWithCloser(context.Background(), exit)
defer cancel() defer cancel()
opts = opts.WithContext(ctx) opts = opts.WithContext(innerCtx)
for { for {
_, meta, err := kv.Get(obj.key, opts) _, meta, err := kv.Get(obj.key, opts)
@@ -162,10 +174,10 @@ func (obj *ConsulKVRes) Watch() error {
// Unexpected situation, bug in consul API... // Unexpected situation, bug in consul API...
select { select {
case ch <- fmt.Errorf("unexpected behaviour in Consul API"): case ch <- fmt.Errorf("unexpected behaviour in Consul API"):
case <-obj.init.Done: // signal for shutdown request case <-ctx.Done(): // signal for shutdown request
} }
case <-obj.init.Done: // signal for shutdown request case <-ctx.Done(): // signal for shutdown request
} }
return return
} }
@@ -186,7 +198,7 @@ func (obj *ConsulKVRes) Watch() error {
} }
obj.init.Event() obj.init.Event()
case <-obj.init.Done: // signal for shutdown request case <-ctx.Done(): // signal for shutdown request
return nil return nil
} }
} }
@@ -195,7 +207,8 @@ func (obj *ConsulKVRes) Watch() error {
// CheckApply is run to check the state and, if apply is true, to apply the // CheckApply is run to check the state and, if apply is true, to apply the
// necessary changes to reach the desired state. This is run before Watch and // necessary changes to reach the desired state. This is run before Watch and
// again if Watch finds a change occurring to the state. // again if Watch finds a change occurring to the state.
func (obj *ConsulKVRes) CheckApply(apply bool) (bool, error) { func (obj *ConsulKVRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
// XXX: use ctx for get and put
if obj.init.Debug { if obj.init.Debug {
obj.init.Logf("consul key: %s", obj.key) obj.init.Logf("consul key: %s", obj.key)
} }

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package resources package resources

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package resources package resources
@@ -29,14 +41,14 @@ import (
"github.com/purpleidea/mgmt/engine" "github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/engine/traits" "github.com/purpleidea/mgmt/engine/traits"
engineUtil "github.com/purpleidea/mgmt/engine/util" engineUtil "github.com/purpleidea/mgmt/engine/util"
"github.com/purpleidea/mgmt/recwatch"
"github.com/purpleidea/mgmt/util" "github.com/purpleidea/mgmt/util"
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
"github.com/purpleidea/mgmt/util/recwatch"
sdbus "github.com/coreos/go-systemd/dbus" sdbus "github.com/coreos/go-systemd/v22/dbus"
"github.com/coreos/go-systemd/unit" "github.com/coreos/go-systemd/v22/unit"
systemdUtil "github.com/coreos/go-systemd/util" systemdUtil "github.com/coreos/go-systemd/v22/util"
"github.com/godbus/dbus" "github.com/godbus/dbus/v5"
) )
const ( const (
@@ -75,6 +87,7 @@ func init() {
} }
// CronRes is a systemd-timer cron resource. // CronRes is a systemd-timer cron resource.
// TODO: If we want to have an actual `crond` resource, name it LegacyCron.
type CronRes struct { type CronRes struct {
traits.Base traits.Base
traits.Edgeable traits.Edgeable
@@ -86,46 +99,52 @@ type CronRes struct {
// Unit is the name of the systemd service unit. It is only necessary to // Unit is the name of the systemd service unit. It is only necessary to
// set if you want to specify a service with a different name than the // set if you want to specify a service with a different name than the
// resource. // resource.
Unit string `yaml:"unit"` Unit string `lang:"unit" yaml:"unit"`
// State must be 'exists' or 'absent'. // State must be 'exists' or 'absent'.
State string `yaml:"state"` State string `lang:"state" yaml:"state"`
// Session, if true, creates the timer as the current user, rather than // Session, if true, creates the timer as the current user, rather than
// root. The service it points to must also be a user unit. It defaults to // root. The service it points to must also be a user unit. It defaults
// false. // to false.
Session bool `yaml:"session"` Session bool `lang:"session" yaml:"session"`
// Trigger is the type of timer. Valid types are 'OnCalendar', // Trigger is the type of timer. Valid types are 'OnCalendar',
// 'OnActiveSec'. 'OnBootSec'. 'OnStartupSec'. 'OnUnitActiveSec', and // 'OnActiveSec'. 'OnBootSec'. 'OnStartupSec'. 'OnUnitActiveSec', and
// 'OnUnitInactiveSec'. For more information see 'man systemd.timer'. // 'OnUnitInactiveSec'. For more information see 'man systemd.timer'.
Trigger string `yaml:"trigger"` Trigger string `lang:"trigger" yaml:"trigger"`
// Time must be used with all triggers. For 'OnCalendar', it must be in // Time must be used with all triggers. For 'OnCalendar', it must be in
// the format defined in 'man systemd-time' under the heading 'Calendar // the format defined in 'man systemd-time' under the heading 'Calendar
// Events'. For all other triggers, time should be a valid time span as // Events'. For all other triggers, time should be a valid time span as
// defined in 'man systemd-time' // defined in 'man systemd-time'
Time string `yaml:"time"` Time string `lang:"time" yaml:"time"`
// AccuracySec is the accuracy of the timer in systemd-time time span // AccuracySec is the accuracy of the timer in systemd-time time span
// format. It defaults to one minute. // format. It defaults to one minute.
AccuracySec string `yaml:"accuracysec"` AccuracySec string `lang:"accuracysec" yaml:"accuracysec"`
// RandomizedDelaySec delays the timer by a randomly selected, evenly // RandomizedDelaySec delays the timer by a randomly selected, evenly
// distributed amount of time between 0 and the specified time value. The // distributed amount of time between 0 and the specified time value.
// value must be a valid systemd-time time span. // The value must be a valid systemd-time time span.
RandomizedDelaySec string `yaml:"randomizeddelaysec"` RandomizedDelaySec string `lang:"randomizeddelaysec" yaml:"randomizeddelaysec"`
// Persistent, if true, means the time when the service unit was last // Persistent, if true, means the time when the service unit was last
// triggered is stored on disk. When the timer is activated, the service // triggered is stored on disk. When the timer is activated, the service
// unit is triggered immediately if it would have been triggered at least // unit is triggered immediately if it would have been triggered at
// once during the time when the timer was inactive. It defaults to false. // least once during the time when the timer was inactive. It defaults
Persistent bool `yaml:"persistent"` // to false.
Persistent bool `lang:"persistent" yaml:"persistent"`
// WakeSystem, if true, will cause the system to resume from suspend, // WakeSystem, if true, will cause the system to resume from suspend,
// should it be suspended and if the system supports this. It defaults to // should it be suspended and if the system supports this. It defaults
// false. // to false.
WakeSystem bool `yaml:"wakesystem"` WakeSystem bool `lang:"wakesystem" yaml:"wakesystem"`
// RemainAfterElapse, if true, means an elapsed timer will stay loaded, and
// its state remains queriable. If false, an elapsed timer unit that cannot // RemainAfterElapse, if true, means an elapsed timer will stay loaded,
// elapse anymore is unloaded. It defaults to true. // and its state remains queriable. If false, an elapsed timer unit that
RemainAfterElapse bool `yaml:"remainafterelapse"` // cannot elapse anymore is unloaded. It defaults to true.
RemainAfterElapse bool `lang:"remainafterelapse" yaml:"remainafterelapse"`
file *FileRes // nested file resource file *FileRes // nested file resource
recWatcher *recwatch.RecWatcher // recwatcher for nested file recWatcher *recwatch.RecWatcher // recwatcher for nested file
@@ -212,16 +231,16 @@ func (obj *CronRes) Init(init *engine.Init) error {
return obj.file.Init(init) return obj.file.Init(init)
} }
// Close is run by the engine to clean up after the resource is done. // Cleanup is run by the engine to clean up after the resource is done.
func (obj *CronRes) Close() error { func (obj *CronRes) Cleanup() error {
if obj.file != nil { if obj.file != nil {
return obj.file.Close() return obj.file.Cleanup()
} }
return nil return nil
} }
// Watch for state changes and sends a message to the bus if there is a change. // Watch for state changes and sends a message to the bus if there is a change.
func (obj *CronRes) Watch() error { func (obj *CronRes) Watch(ctx context.Context) error {
var bus *dbus.Conn var bus *dbus.Conn
var err error var err error
@@ -296,7 +315,7 @@ func (obj *CronRes) Watch() error {
} }
send = true send = true
case <-obj.init.Done: // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }
// do all our event sending all together to avoid duplicate msgs // do all our event sending all together to avoid duplicate msgs
@@ -310,16 +329,16 @@ func (obj *CronRes) Watch() error {
// CheckApply is run to check the state and, if apply is true, to apply the // CheckApply is run to check the state and, if apply is true, to apply the
// necessary changes to reach the desired state. This is run before Watch and // necessary changes to reach the desired state. This is run before Watch and
// again if Watch finds a change occurring to the state. // again if Watch finds a change occurring to the state.
func (obj *CronRes) CheckApply(apply bool) (bool, error) { func (obj *CronRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
checkOK := true checkOK := true
// use the embedded file resource to apply the correct state // use the embedded file resource to apply the correct state
if c, err := obj.file.CheckApply(apply); err != nil { if c, err := obj.file.CheckApply(ctx, apply); err != nil {
return false, errwrap.Wrapf(err, "nested file failed") return false, errwrap.Wrapf(err, "nested file failed")
} else if !c { } else if !c {
checkOK = false checkOK = false
} }
// check timer state and apply the defined state if needed // check timer state and apply the defined state if needed
if c, err := obj.unitCheckApply(apply); err != nil { if c, err := obj.unitCheckApply(ctx, apply); err != nil {
return false, errwrap.Wrapf(err, "unitCheckApply error") return false, errwrap.Wrapf(err, "unitCheckApply error")
} else if !c { } else if !c {
checkOK = false checkOK = false
@@ -329,7 +348,7 @@ func (obj *CronRes) CheckApply(apply bool) (bool, error) {
// unitCheckApply checks the state of the systemd-timer unit and, if apply is // unitCheckApply checks the state of the systemd-timer unit and, if apply is
// true, applies the defined state. // true, applies the defined state.
func (obj *CronRes) unitCheckApply(apply bool) (bool, error) { func (obj *CronRes) unitCheckApply(ctx context.Context, apply bool) (bool, error) {
var conn *sdbus.Conn var conn *sdbus.Conn
var godbusConn *dbus.Conn var godbusConn *dbus.Conn
var err error var err error
@@ -376,7 +395,7 @@ func (obj *CronRes) unitCheckApply(apply bool) (bool, error) {
} }
// context for stopping/restarting the unit // context for stopping/restarting the unit
ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout*time.Second) ctx, cancel := context.WithTimeout(ctx, ctxTimeout*time.Second)
defer cancel() defer cancel()
// godbus connection for stopping/restarting the unit // godbus connection for stopping/restarting the unit

File diff suppressed because it is too large Load Diff

31
engine/resources/doc.go Normal file
View File

@@ -0,0 +1,31 @@
// Mgmt
// Copyright (C) 2013-2024+ 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// Package resources contains the implementations of all the core resources.
package resources

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,16 +13,28 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// +build !nodocker //go:build !nodocker
package resources package resources
import ( import (
"context" "context"
"fmt" "fmt"
"io/ioutil" "io"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@@ -65,22 +77,27 @@ type DockerContainerRes struct {
traits.Edgeable traits.Edgeable
// State of the container must be running, stopped, or removed. // State of the container must be running, stopped, or removed.
State string `yaml:"state"` State string `lang:"state" yaml:"state"`
// Image is a docker image, or image:tag. // Image is a docker image, or image:tag.
Image string `yaml:"image"` Image string `lang:"image" yaml:"image"`
// Cmd is a command, or list of commands to run on the container. // Cmd is a command, or list of commands to run on the container.
Cmd []string `yaml:"cmd"` Cmd []string `lang:"cmd" yaml:"cmd"`
// Env is a list of environment variables. E.g. ["VAR=val",]. // Env is a list of environment variables. E.g. ["VAR=val",].
Env []string `yaml:"env"` Env []string `lang:"env" yaml:"env"`
// Ports is a map of port bindings. E.g. {"tcp" => {80 => 8080},}. // Ports is a map of port bindings. E.g. {"tcp" => {80 => 8080},}.
Ports map[string]map[int64]int64 `yaml:"ports"` Ports map[string]map[int64]int64 `lang:"ports" yaml:"ports"`
// APIVersion allows you to override the host's default client API // APIVersion allows you to override the host's default client API
// version. // version.
APIVersion string `yaml:"apiversion"` APIVersion string `lang:"apiversion" yaml:"apiversion"`
// Force, if true, this will destroy and redeploy the container if the // Force, if true, this will destroy and redeploy the container if the
// image is incorrect. // image is incorrect.
Force bool `yaml:"force"` Force bool `lang:"force" yaml:"force"`
client *client.Client // docker api client client *client.Client // docker api client
@@ -164,17 +181,17 @@ func (obj *DockerContainerRes) Init(init *engine.Init) error {
return nil return nil
} }
// Close is run by the engine to clean up after the resource is done. // Cleanup is run by the engine to clean up after the resource is done.
func (obj *DockerContainerRes) Close() error { func (obj *DockerContainerRes) Cleanup() error {
return obj.client.Close() // close the docker client return obj.client.Close() // close the docker client
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *DockerContainerRes) Watch() error { func (obj *DockerContainerRes) Watch(ctx context.Context) error {
ctx, cancel := context.WithCancel(context.Background()) innerCtx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
eventChan, errChan := obj.client.Events(ctx, types.EventsOptions{}) eventChan, errChan := obj.client.Events(innerCtx, types.EventsOptions{})
obj.init.Running() // when started, notify engine that we're running obj.init.Running() // when started, notify engine that we're running
@@ -196,7 +213,7 @@ func (obj *DockerContainerRes) Watch() error {
} }
return err return err
case <-obj.init.Done: // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }
@@ -209,11 +226,11 @@ func (obj *DockerContainerRes) Watch() error {
} }
// CheckApply method for Docker resource. // CheckApply method for Docker resource.
func (obj *DockerContainerRes) CheckApply(apply bool) (bool, error) { func (obj *DockerContainerRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
var id string var id string
var destroy bool var destroy bool
ctx, cancel := context.WithTimeout(context.Background(), checkApplyCtxTimeout*time.Second) ctx, cancel := context.WithTimeout(ctx, checkApplyCtxTimeout*time.Second)
defer cancel() defer cancel()
// List any container whose name matches this resource. // List any container whose name matches this resource.
@@ -282,7 +299,7 @@ func (obj *DockerContainerRes) CheckApply(apply bool) (bool, error) {
return false, errwrap.Wrapf(err, "error pulling image") return false, errwrap.Wrapf(err, "error pulling image")
} }
// Wait for the image to download, EOF signals that it's done. // Wait for the image to download, EOF signals that it's done.
if _, err := ioutil.ReadAll(p); err != nil { if _, err := io.ReadAll(p); err != nil {
return false, errwrap.Wrapf(err, "error reading image pull result") return false, errwrap.Wrapf(err, "error reading image pull result")
} }
@@ -344,9 +361,12 @@ func (obj *DockerContainerRes) containerStart(ctx context.Context, id string, op
} }
// containerStop stops the specified container and waits for it to stop. // containerStop stops the specified container and waits for it to stop.
func (obj *DockerContainerRes) containerStop(ctx context.Context, id string, timeout *time.Duration) error { func (obj *DockerContainerRes) containerStop(ctx context.Context, id string, timeout *int) error {
ch, errCh := obj.client.ContainerWait(ctx, id, container.WaitConditionNotRunning) ch, errCh := obj.client.ContainerWait(ctx, id, container.WaitConditionNotRunning)
obj.client.ContainerStop(ctx, id, timeout) stopOpts := container.StopOptions{
Timeout: timeout,
}
obj.client.ContainerStop(ctx, id, stopOpts)
select { select {
case <-ch: case <-ch:
case err := <-errCh: case err := <-errCh:

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,16 +13,28 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// +build !nodocker //go:build !nodocker
package resources package resources
import ( import (
"context" "context"
"fmt" "fmt"
"io/ioutil" "io"
"log" "log"
"os" "os"
"testing" "testing"
@@ -37,7 +49,9 @@ var res *DockerContainerRes
var id string var id string
func TestMain(m *testing.M) { // XXX: re-enable once docker is not broken.
// XXX: Error: docker_container_test.go:112: failed to stop container
func BrokenTestDockerMain(m *testing.M) {
var setupCode, testCode, cleanupCode int var setupCode, testCode, cleanupCode int
if err := setup(); err != nil { if err := setup(); err != nil {
@@ -57,7 +71,7 @@ func TestMain(m *testing.M) {
os.Exit(setupCode + testCode + cleanupCode) os.Exit(setupCode + testCode + cleanupCode)
} }
func Test_containerStart(t *testing.T) { func BrokenTestContainerStart(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel() defer cancel()
@@ -85,7 +99,7 @@ func Test_containerStart(t *testing.T) {
} }
} }
func Test_containerStop(t *testing.T) { func BrokenTestContainerStop(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel() defer cancel()
@@ -112,7 +126,7 @@ func Test_containerStop(t *testing.T) {
} }
} }
func Test_containerRemove(t *testing.T) { func BrokenTestContainerRemove(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel() defer cancel()
@@ -153,7 +167,7 @@ func setup() error {
if err != nil { if err != nil {
return fmt.Errorf("error pulling image: %s", err) return fmt.Errorf("error pulling image: %s", err)
} }
if _, err := ioutil.ReadAll(p); err != nil { if _, err := io.ReadAll(p); err != nil {
return fmt.Errorf("error reading image pull result: %s", err) return fmt.Errorf("error reading image pull result: %s", err)
} }
@@ -191,7 +205,8 @@ func cleanup() error {
} }
if len(l) > 0 { if len(l) > 0 {
if err := res.client.ContainerStop(ctx, id, nil); err != nil { stopOpts := container.StopOptions{}
if err := res.client.ContainerStop(ctx, id, stopOpts); err != nil {
return fmt.Errorf("error stopping container: %s", err) return fmt.Errorf("error stopping container: %s", err)
} }
if err := res.client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{}); err != nil { if err := res.client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{}); err != nil {

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,16 +13,28 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
// +build !nodocker //go:build !nodocker
package resources package resources
import ( import (
"context" "context"
"fmt" "fmt"
"io/ioutil" "io"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@@ -56,10 +68,11 @@ type DockerImageRes struct {
traits.Edgeable traits.Edgeable
// State of the image must be exists or absent. // State of the image must be exists or absent.
State string `yaml:"state"` State string `lang:"state" yaml:"state"`
// APIVersion allows you to override the host's default client API // APIVersion allows you to override the host's default client API
// version. // version.
APIVersion string `yaml:"apiversion"` APIVersion string `lang:"apiversion" yaml:"apiversion"`
image string // full image:tag format image string // full image:tag format
client *client.Client // docker api client client *client.Client // docker api client
@@ -125,17 +138,17 @@ func (obj *DockerImageRes) Init(init *engine.Init) error {
return nil return nil
} }
// Close is run by the engine to clean up after the resource is done. // Cleanup is run by the engine to clean up after the resource is done.
func (obj *DockerImageRes) Close() error { func (obj *DockerImageRes) Cleanup() error {
return obj.client.Close() // close the docker client return obj.client.Close() // close the docker client
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *DockerImageRes) Watch() error { func (obj *DockerImageRes) Watch(ctx context.Context) error {
ctx, cancel := context.WithCancel(context.Background()) innerCtx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
eventChan, errChan := obj.client.Events(ctx, types.EventsOptions{}) eventChan, errChan := obj.client.Events(innerCtx, types.EventsOptions{})
// notify engine that we're running // notify engine that we're running
obj.init.Running() obj.init.Running()
@@ -158,7 +171,7 @@ func (obj *DockerImageRes) Watch() error {
} }
return err return err
case <-obj.init.Done: // closed by the engine to signal shutdown case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }
@@ -171,8 +184,8 @@ func (obj *DockerImageRes) Watch() error {
} }
// CheckApply method for Docker resource. // CheckApply method for Docker resource.
func (obj *DockerImageRes) CheckApply(apply bool) (checkOK bool, err error) { func (obj *DockerImageRes) CheckApply(ctx context.Context, apply bool) (checkOK bool, err error) {
ctx, cancel := context.WithTimeout(context.Background(), dockerImageCheckApplyCtxTimeout*time.Second) ctx, cancel := context.WithTimeout(ctx, dockerImageCheckApplyCtxTimeout*time.Second)
defer cancel() defer cancel()
s, err := obj.client.ImageList(ctx, types.ImageListOptions{ s, err := obj.client.ImageList(ctx, types.ImageListOptions{
@@ -210,7 +223,7 @@ func (obj *DockerImageRes) CheckApply(apply bool) (checkOK bool, err error) {
return false, errwrap.Wrapf(err, "error pulling image") return false, errwrap.Wrapf(err, "error pulling image")
} }
// Wait for the image to download, EOF signals that it's done. // Wait for the image to download, EOF signals that it's done.
if _, err := ioutil.ReadAll(p); err != nil { if _, err := io.ReadAll(p); err != nil {
return false, errwrap.Wrapf(err, "error reading image pull result") return false, errwrap.Wrapf(err, "error reading image pull result")
} }

View File

@@ -1,5 +1,5 @@
// Mgmt // Mgmt
// Copyright (C) 2013-2021+ James Shubin and the project contributors // Copyright (C) 2013-2024+ 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
@@ -13,7 +13,19 @@
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU 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 <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.
package resources package resources
@@ -22,6 +34,8 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"io"
"os"
"os/exec" "os/exec"
"os/user" "os/user"
"sort" "sort"
@@ -34,6 +48,7 @@ import (
"github.com/purpleidea/mgmt/engine/traits" "github.com/purpleidea/mgmt/engine/traits"
engineUtil "github.com/purpleidea/mgmt/engine/util" engineUtil "github.com/purpleidea/mgmt/engine/util"
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
"github.com/purpleidea/mgmt/util/recwatch"
) )
func init() { func init() {
@@ -49,51 +64,89 @@ type ExecRes struct {
init *engine.Init init *engine.Init
// Cmd is the command to run. If this is not specified, we use the name. // Cmd is the command to run. If this is not specified, we use the name.
Cmd string `yaml:"cmd"` // Remember that if you're not using `Shell` (the default) then adding
// single quotes around args make them part of the actual values. IOW,
// if your command is: "touch '/tmp/foo'", then (1) it probably won't be
// able to find the "touch" command (use /usr/bin/touch instead) and (2)
// the file won't be in the /tmp/ directory, it will be an oddly named
// file that contains two single quotes, and it will likely error since
// the dir path doesn't exist. In general, it's best to use the `Args`
// field instead of including them here.
// XXX: if not using shell, don't allow args here, force them to args!
Cmd string `lang:"cmd" yaml:"cmd"`
// Args is a list of args to pass to Cmd. This can be used *instead* of // Args is a list of args to pass to Cmd. This can be used *instead* of
// passing the full command and args as a single string to Cmd. It can // passing the full command and args as a single string to Cmd. It can
// only be used when a Shell is *not* specified. The advantage of this // only be used when a Shell is *not* specified. The advantage of this
// is that you don't have to worry about escape characters. // is that you don't have to worry about escape characters.
Args []string `yaml:"args"` Args []string `lang:"args" yaml:"args"`
// Cwd is the dir to run the command in. If empty, then this will use // Cwd is the dir to run the command in. If empty, then this will use
// the working directory of the calling process. (This process is mgmt, // the working directory of the calling process. (This process is mgmt,
// not the process being run here.) // not the process being run here.)
Cwd string `yaml:"cwd"` Cwd string `lang:"cwd" yaml:"cwd"`
// Shell is the (optional) shell to use to run the cmd. If you specify // Shell is the (optional) shell to use to run the cmd. If you specify
// this, then you can't use the Args parameter. // this, then you can't use the Args parameter.
Shell string `yaml:"shell"` Shell string `lang:"shell" yaml:"shell"`
// Timeout is the number of seconds to wait before sending a Kill to the // Timeout is the number of seconds to wait before sending a Kill to the
// running command. If the Kill is received before the process exits, // running command. If the Kill is received before the process exits,
// then this be treated as an error. // then this be treated as an error.
Timeout uint64 `yaml:"timeout"` Timeout uint64 `lang:"timeout" yaml:"timeout"`
// Env allows the user to specify environment variables for script // Env allows the user to specify environment variables for script
// execution. These are taken using a map of format of VAR_NAME -> value. // execution. These are taken using a map of format of VAR_NAME -> value.
Env map[string]string `yaml:"env"` Env map[string]string `lang:"env" yaml:"env"`
// Watch is the command to run to detect event changes. Each line of // WatchCmd is the command to run to detect event changes. Each line of
// output from this command is treated as an event. // output from this command is treated as an event.
WatchCmd string `yaml:"watchcmd"` WatchCmd string `lang:"watchcmd" yaml:"watchcmd"`
// WatchCwd is the Cwd for the WatchCmd. See the docs for Cwd. // WatchCwd is the Cwd for the WatchCmd. See the docs for Cwd.
WatchCwd string `yaml:"watchcwd"` WatchCwd string `lang:"watchcwd" yaml:"watchcwd"`
// WatchShell is the Shell for the WatchCmd. See the docs for Shell. // WatchShell is the Shell for the WatchCmd. See the docs for Shell.
WatchShell string `yaml:"watchshell"` WatchShell string `lang:"watchshell" yaml:"watchshell"`
// IfCmd is the command that runs to guard against running the Cmd. If // IfCmd is the command that runs to guard against running the Cmd. If
// this command succeeds, then Cmd *will* be run. If this command // this command succeeds, then Cmd *will* be run. If this command
// returns a non-zero result, then the Cmd will not be run. Any error // returns a non-zero result, then the Cmd will not be run. Any error
// scenario or timeout will cause the resource to error. // scenario or timeout will cause the resource to error.
IfCmd string `yaml:"ifcmd"` IfCmd string `lang:"ifcmd" yaml:"ifcmd"`
// IfCwd is the Cwd for the IfCmd. See the docs for Cwd. // IfCwd is the Cwd for the IfCmd. See the docs for Cwd.
IfCwd string `yaml:"ifcwd"` IfCwd string `lang:"ifcwd" yaml:"ifcwd"`
// IfShell is the Shell for the IfCmd. See the docs for Shell. // IfShell is the Shell for the IfCmd. See the docs for Shell.
IfShell string `yaml:"ifshell"` IfShell string `lang:"ifshell" yaml:"ifshell"`
// Creates is the absolute file path to check for before running the
// main cmd. If this path exists, then the cmd will not run. More
// precisely we attempt to `stat` the file, so it must succeed for a
// skip. This also adds a watch on this path which re-checks things when
// it changes.
Creates string `lang:"creates" yaml:"creates"`
// DoneCmd is the command that runs after the regular Cmd runs
// successfully. This is a useful pattern to avoid the shelling out to
// bash simply to do `$cmd && echo done > /tmp/donefile`. If this
// command errors, it behaves as if the normal Cmd had errored.
DoneCmd string `lang:"donecmd" yaml:"donecmd"`
// DoneCwd is the Cwd for the DoneCmd. See the docs for Cwd.
DoneCwd string `lang:"donecwd" yaml:"donecwd"`
// DoneShell is the Shell for the DoneCmd. See the docs for Shell.
DoneShell string `lang:"doneshell" yaml:"doneshell"`
// User is the (optional) user to use to execute the command. It is used // User is the (optional) user to use to execute the command. It is used
// for any command being run. // for any command being run.
User string `yaml:"user"` User string `lang:"user" yaml:"user"`
// Group is the (optional) group to use to execute the command. It is // Group is the (optional) group to use to execute the command. It is
// used for any command being run. // used for any command being run.
Group string `yaml:"group"` Group string `lang:"group" yaml:"group"`
output *string // all cmd output, read only, do not set! output *string // all cmd output, read only, do not set!
stdout *string // the cmd stdout, read only, do not set! stdout *string // the cmd stdout, read only, do not set!
@@ -131,6 +184,10 @@ func (obj *ExecRes) Validate() error {
return fmt.Errorf("the Args param can't be used when Cmd has args") return fmt.Errorf("the Args param can't be used when Cmd has args")
} }
if obj.Creates != "" && !strings.HasPrefix(obj.Creates, "/") {
return fmt.Errorf("the Creates param must be an absolute path")
}
// check that, if an user or a group is set, we're running as root // check that, if an user or a group is set, we're running as root
if obj.User != "" || obj.Group != "" { if obj.User != "" || obj.Group != "" {
currentUser, err := user.Current() currentUser, err := user.Current()
@@ -161,16 +218,18 @@ func (obj *ExecRes) Init(init *engine.Init) error {
return nil return nil
} }
// Close is run by the engine to clean up after the resource is done. // Cleanup is run by the engine to clean up after the resource is done.
func (obj *ExecRes) Close() error { func (obj *ExecRes) Cleanup() error {
return nil return nil
} }
// Watch is the primary listener for this resource and it outputs events. // Watch is the primary listener for this resource and it outputs events.
func (obj *ExecRes) Watch() error { func (obj *ExecRes) Watch(ctx context.Context) error {
ioChan := make(chan *cmdOutput)
defer obj.wg.Wait() defer obj.wg.Wait()
ioChan := make(chan *cmdOutput)
rwChan := make(chan recwatch.Event)
var watchCmd *exec.Cmd
if obj.WatchCmd != "" { if obj.WatchCmd != "" {
var cmdName string var cmdName string
var cmdArgs []string var cmdArgs []string
@@ -187,15 +246,16 @@ func (obj *ExecRes) Watch() error {
cmdArgs = []string{"-c", obj.WatchCmd} cmdArgs = []string{"-c", obj.WatchCmd}
} }
ctx, cancel := context.WithCancel(context.Background()) innerCtx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
cmd := exec.CommandContext(ctx, cmdName, cmdArgs...) cmd := exec.CommandContext(innerCtx, cmdName, cmdArgs...)
cmd.Dir = obj.WatchCwd // run program in pwd if "" cmd.Dir = obj.WatchCwd // run program in pwd if ""
// ignore signals sent to parent process (we're in our own group) // ignore signals sent to parent process (we're in our own group)
cmd.SysProcAttr = &syscall.SysProcAttr{ cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true, Setpgid: true,
Pgid: 0, Pgid: 0,
} }
watchCmd = cmd // store for errors
// if we have a user and group, use them // if we have a user and group, use them
var err error var err error
@@ -203,11 +263,20 @@ func (obj *ExecRes) Watch() error {
return errwrap.Wrapf(err, "error while setting credential") return errwrap.Wrapf(err, "error while setting credential")
} }
if ioChan, err = obj.cmdOutputRunner(ctx, cmd); err != nil { if ioChan, err = obj.cmdOutputRunner(innerCtx, cmd); err != nil {
return errwrap.Wrapf(err, "error starting WatchCmd") return errwrap.Wrapf(err, "error starting WatchCmd")
} }
} }
if obj.Creates != "" {
recWatcher, err := recwatch.NewRecWatcher(obj.Creates, false)
if err != nil {
return err
}
defer recWatcher.Close()
rwChan = recWatcher.Events()
}
obj.init.Running() // when started, notify engine that we're running obj.init.Running() // when started, notify engine that we're running
var send = false // send event? var send = false // send event?
@@ -237,22 +306,32 @@ func (obj *ExecRes) Watch() error {
return errwrap.Wrapf(err, "unexpected watchcmd exit status of zero") return errwrap.Wrapf(err, "unexpected watchcmd exit status of zero")
} }
obj.init.Logf("watchcmd: %s", strings.Join(watchCmd.Args, " "))
obj.init.Logf("watchcmd exited with: %d", exitStatus) obj.init.Logf("watchcmd exited with: %d", exitStatus)
return errwrap.Wrapf(err, "watchcmd errored") return errwrap.Wrapf(err, "watchcmd errored")
} }
// each time we get a line of output, we loop! // each time we get a line of output, we loop!
if s := data.text; s == "" { if s := data.text; s == "" {
obj.init.Logf("watch output is empty!") obj.init.Logf("watch out empty!")
} else { } else {
obj.init.Logf("watch output is:") obj.init.Logf("watch out:")
obj.init.Logf(s) obj.init.Logf("%s", s)
} }
if data.text != "" { if data.text != "" {
send = true send = true
} }
case <-obj.init.Done: // closed by the engine to signal shutdown case event, ok := <-rwChan:
if !ok { // channel shutdown
return fmt.Errorf("unexpected recwatch shutdown")
}
if err := event.Error; err != nil {
return errwrap.Wrapf(err, "unknown %s watcher error", obj)
}
send = true
case <-ctx.Done(): // closed by the engine to signal shutdown
return nil return nil
} }
@@ -267,7 +346,7 @@ func (obj *ExecRes) Watch() error {
// CheckApply checks the resource state and applies the resource if the bool // CheckApply checks the resource state and applies the resource if the bool
// input is true. It returns error info and if the state check passed or not. // input is true. It returns error info and if the state check passed or not.
// TODO: expand the IfCmd to be a list of commands // TODO: expand the IfCmd to be a list of commands
func (obj *ExecRes) CheckApply(apply bool) (bool, error) { func (obj *ExecRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
// If we receive a refresh signal, then the engine skips the IsStateOK() // If we receive a refresh signal, then the engine skips the IsStateOK()
// check and this will run. It is still guarded by the IfCmd, but it can // check and this will run. It is still guarded by the IfCmd, but it can
// have a chance to execute, and all without the check of obj.Refresh()! // have a chance to execute, and all without the check of obj.Refresh()!
@@ -323,20 +402,28 @@ func (obj *ExecRes) CheckApply(apply bool) (bool, error) {
return false, errwrap.Wrapf(err, "unexpected ifcmd exit status of zero") return false, errwrap.Wrapf(err, "unexpected ifcmd exit status of zero")
} }
obj.init.Logf("ifcmd exited with: %d", exitStatus) obj.init.Logf("ifcmd: %s", strings.Join(cmd.Args, " "))
obj.init.Logf("ifcmd exited with: %d, skipping cmd", exitStatus)
if s := out.String(); s == "" { if s := out.String(); s == "" {
obj.init.Logf("ifcmd output is empty!") obj.init.Logf("ifcmd out empty!")
} else { } else {
obj.init.Logf("ifcmd output is:") obj.init.Logf("ifcmd out:")
obj.init.Logf(s) obj.init.Logf("%s", s)
} }
return true, nil // don't run return true, nil // don't run
} }
if s := out.String(); s == "" { if s := out.String(); s == "" {
obj.init.Logf("ifcmd output is empty!") obj.init.Logf("ifcmd out empty!")
} else { } else {
obj.init.Logf("ifcmd output is:") obj.init.Logf("ifcmd out:")
obj.init.Logf(s) obj.init.Logf("%s", s)
}
}
if obj.Creates != "" { // gate the extra syscall
if _, err := os.Stat(obj.Creates); err == nil {
obj.init.Logf("creates file exists, skipping cmd")
return true, nil // don't run
} }
} }
@@ -371,15 +458,15 @@ func (obj *ExecRes) CheckApply(apply bool) (bool, error) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
defer wg.Wait() // this must be above the defer cancel() call defer wg.Wait() // this must be above the defer cancel() call
var ctx context.Context var innerCtx context.Context
var cancel context.CancelFunc var cancel context.CancelFunc
if obj.Timeout > 0 { // cmd.Process.Kill() is called on timeout if obj.Timeout > 0 { // cmd.Process.Kill() is called on timeout
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(obj.Timeout)*time.Second) innerCtx, cancel = context.WithTimeout(ctx, time.Duration(obj.Timeout)*time.Second)
} else { // zero timeout means no timer } else { // zero timeout means no timer
ctx, cancel = context.WithCancel(context.Background()) innerCtx, cancel = context.WithCancel(ctx)
} }
defer cancel() defer cancel()
cmd := exec.CommandContext(ctx, cmdName, cmdArgs...) cmd := exec.CommandContext(innerCtx, cmdName, cmdArgs...)
cmd.Dir = obj.Cwd // run program in pwd if "" cmd.Dir = obj.Cwd // run program in pwd if ""
envKeys := []string{} envKeys := []string{}
@@ -412,6 +499,7 @@ func (obj *ExecRes) CheckApply(apply bool) (bool, error) {
cmd.Stdout = out.Stdout cmd.Stdout = out.Stdout
cmd.Stderr = out.Stderr cmd.Stderr = out.Stderr
obj.init.Logf("cmd: %s", strings.Join(cmd.Args, " "))
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return false, errwrap.Wrapf(err, "error starting cmd") return false, errwrap.Wrapf(err, "error starting cmd")
} }
@@ -422,7 +510,7 @@ func (obj *ExecRes) CheckApply(apply bool) (bool, error) {
select { select {
case <-obj.interruptChan: case <-obj.interruptChan:
cancel() cancel()
case <-ctx.Done(): case <-innerCtx.Done():
// let this exit // let this exit
} }
}() }()
@@ -454,7 +542,14 @@ func (obj *ExecRes) CheckApply(apply bool) (bool, error) {
} }
exitStatus := wStatus.ExitStatus() exitStatus := wStatus.ExitStatus()
if !wStatus.Signaled() { // not a timeout or cancel (no signal) if !wStatus.Signaled() { // not a timeout or cancel (no signal)
return false, errwrap.Wrapf(err, "cmd error, exit status: %d", exitStatus) // most commands error in this way
if s := out.String(); s == "" {
obj.init.Logf("exit status %d", exitStatus)
} else {
obj.init.Logf("cmd error: %s", s)
}
return false, errwrap.Wrapf(err, "cmd error") // exit status will be in the error
} }
sig := wStatus.Signal() sig := wStatus.Signal()
@@ -473,10 +568,77 @@ func (obj *ExecRes) CheckApply(apply bool) (bool, error) {
// would be nice, but it would require terminal log output that doesn't // would be nice, but it would require terminal log output that doesn't
// interleave all the parallel parts which would mix it all up... // interleave all the parallel parts which would mix it all up...
if s := out.String(); s == "" { if s := out.String(); s == "" {
obj.init.Logf("command output is empty!") obj.init.Logf("cmd out empty!")
} else { } else {
obj.init.Logf("command output is:") obj.init.Logf("cmd out:")
obj.init.Logf(s) obj.init.Logf("%s", s)
}
if obj.DoneCmd != "" {
var cmdName string
var cmdArgs []string
if obj.DoneShell == "" {
// call without a shell
// FIXME: are there still whitespace splitting issues?
split := strings.Fields(obj.DoneCmd)
cmdName = split[0]
//d, _ := os.Getwd() // TODO: how does this ever error ?
//cmdName = path.Join(d, cmdName)
cmdArgs = split[1:]
} else {
cmdName = obj.DoneShell // usually bash, or sh
cmdArgs = []string{"-c", obj.DoneCmd}
}
cmd := exec.Command(cmdName, cmdArgs...)
cmd.Dir = obj.DoneCwd // run program in pwd if ""
// ignore signals sent to parent process (we're in our own group)
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
Pgid: 0,
}
// if we have an user and group, use them
var err error
if cmd.SysProcAttr.Credential, err = obj.getCredential(); err != nil {
return false, errwrap.Wrapf(err, "error while setting credential")
}
var out splitWriter
out.Init()
cmd.Stdout = out.Stdout
cmd.Stderr = out.Stderr
if err := cmd.Run(); err != nil {
exitErr, ok := err.(*exec.ExitError) // embeds an os.ProcessState
if !ok {
// command failed in some bad way
return false, errwrap.Wrapf(err, "donecmd failed in some bad way")
}
pStateSys := exitErr.Sys() // (*os.ProcessState) Sys
wStatus, ok := pStateSys.(syscall.WaitStatus)
if !ok {
return false, errwrap.Wrapf(err, "could not get exit status of donecmd")
}
exitStatus := wStatus.ExitStatus()
if exitStatus == 0 {
// i'm not sure if this could happen
return false, errwrap.Wrapf(err, "unexpected donecmd exit status of zero")
}
obj.init.Logf("donecmd: %s", strings.Join(cmd.Args, " "))
if s := out.String(); s == "" {
obj.init.Logf("donecmd exit status %d", exitStatus)
} else {
obj.init.Logf("donecmd error: %s", s)
}
return false, errwrap.Wrapf(err, "cmd error") // exit status will be in the error
}
if s := out.String(); s == "" {
obj.init.Logf("donecmd out empty!")
} else {
obj.init.Logf("donecmd out:")
obj.init.Logf("%s", s)
}
} }
if err := obj.init.Send(&ExecSends{ if err := obj.init.Send(&ExecSends{
@@ -544,6 +706,20 @@ func (obj *ExecRes) Cmp(r engine.Res) error {
return fmt.Errorf("the IfShell differs") return fmt.Errorf("the IfShell differs")
} }
if obj.Creates != res.Creates {
return fmt.Errorf("the Creates differs")
}
if obj.DoneCmd != res.DoneCmd {
return fmt.Errorf("the DoneCmd differs")
}
if obj.DoneCwd != res.DoneCwd {
return fmt.Errorf("the DoneCwd differs")
}
if obj.DoneShell != res.DoneShell {
return fmt.Errorf("the DoneShell differs")
}
if obj.User != res.User { if obj.User != res.User {
return fmt.Errorf("the User differs") return fmt.Errorf("the User differs")
} }
@@ -563,8 +739,10 @@ func (obj *ExecRes) Interrupt() error {
// ExecUID is the UID struct for ExecRes. // ExecUID is the UID struct for ExecRes.
type ExecUID struct { type ExecUID struct {
engine.BaseUID engine.BaseUID
Cmd string Cmd string
IfCmd string WatchCmd string
IfCmd string
DoneCmd string
// TODO: add more elements here // TODO: add more elements here
} }
@@ -650,9 +828,11 @@ func (obj *ExecRes) AutoEdges() (engine.AutoEdge, error) {
// resources only return one, although some resources can return multiple. // resources only return one, although some resources can return multiple.
func (obj *ExecRes) UIDs() []engine.ResUID { func (obj *ExecRes) UIDs() []engine.ResUID {
x := &ExecUID{ x := &ExecUID{
BaseUID: engine.BaseUID{Name: obj.Name(), Kind: obj.Kind()}, BaseUID: engine.BaseUID{Name: obj.Name(), Kind: obj.Kind()},
Cmd: obj.getCmd(), Cmd: obj.getCmd(),
IfCmd: obj.IfCmd, WatchCmd: obj.WatchCmd,
IfCmd: obj.IfCmd,
DoneCmd: obj.DoneCmd,
// TODO: add more params here // TODO: add more params here
} }
return []engine.ResUID{x} return []engine.ResUID{x}
@@ -733,18 +913,23 @@ func (obj *ExecRes) cmdFiles() []string {
var paths []string var paths []string
if obj.Shell != "" { if obj.Shell != "" {
paths = append(paths, obj.Shell) paths = append(paths, obj.Shell)
} else if cmdSplit := strings.Fields(obj.getCmd()); len(cmdSplit) > 0 { } else if sp := strings.Fields(obj.getCmd()); len(sp) > 0 {
paths = append(paths, cmdSplit[0]) paths = append(paths, sp[0])
} }
if obj.WatchShell != "" { if obj.WatchShell != "" {
paths = append(paths, obj.WatchShell) paths = append(paths, obj.WatchShell)
} else if watchSplit := strings.Fields(obj.WatchCmd); len(watchSplit) > 0 { } else if sp := strings.Fields(obj.WatchCmd); len(sp) > 0 {
paths = append(paths, watchSplit[0]) paths = append(paths, sp[0])
} }
if obj.IfShell != "" { if obj.IfShell != "" {
paths = append(paths, obj.IfShell) paths = append(paths, obj.IfShell)
} else if ifSplit := strings.Fields(obj.IfCmd); len(ifSplit) > 0 { } else if sp := strings.Fields(obj.IfCmd); len(sp) > 0 {
paths = append(paths, ifSplit[0]) paths = append(paths, sp[0])
}
if obj.DoneShell != "" {
paths = append(paths, obj.DoneShell)
} else if sp := strings.Fields(obj.DoneCmd); len(sp) > 0 {
paths = append(paths, sp[0])
} }
return paths return paths
} }
@@ -764,10 +949,17 @@ type cmdOutput struct {
// Cancelling the context merely unblocks the sending on the output channel, it // Cancelling the context merely unblocks the sending on the output channel, it
// does not Kill the cmd process. For that you must do it yourself elsewhere. // does not Kill the cmd process. For that you must do it yourself elsewhere.
func (obj *ExecRes) cmdOutputRunner(ctx context.Context, cmd *exec.Cmd) (chan *cmdOutput, error) { func (obj *ExecRes) cmdOutputRunner(ctx context.Context, cmd *exec.Cmd) (chan *cmdOutput, error) {
cmdReader, err := cmd.StdoutPipe() stdoutReader, err := cmd.StdoutPipe()
if err != nil { if err != nil {
return nil, errwrap.Wrapf(err, "error creating StdoutPipe for Cmd") return nil, errwrap.Wrapf(err, "error creating StdoutPipe for Cmd")
} }
stderrReader, err := cmd.StderrPipe()
if err != nil {
return nil, errwrap.Wrapf(err, "error creating StderrPipe for Cmd")
}
// XXX: Can io.MultiReader when one of these is still open? Is there an
// issue or race here about calling cmd.Wait() if only one of them dies?
cmdReader := io.MultiReader(stdoutReader, stderrReader)
scanner := bufio.NewScanner(cmdReader) scanner := bufio.NewScanner(cmdReader)
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return nil, errwrap.Wrapf(err, "error starting Cmd") return nil, errwrap.Wrapf(err, "error starting Cmd")

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