693 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
James Shubin
9c75c55fa4 lib: Update the help text
Give some longer descriptions so they show up nicely for the user.
2021-09-29 02:11:19 -04:00
Joe Groocock
b9741e87bd lang: interpolate: Fix string interpolation of dollar symbols
Dollar symbols were failing to parse when not followed by a non-brace,
non-dollar, non-EOF token and causing expected tests to fail. This
simplifies the rules to allow the remaining tests to succeed.

Fix and reinstate the final few failing tests, and add another.

Allow any escape sequence to be matched so that invalid sequences
produce a meaningful error message instead of a generic "cannot parse":

    ast: interpolate: interpolating: V: \?
    unhandled escape sequence token: \?

Tidy the related Makefile rule for generating the ragel parser.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-09-28 21:40:49 +00:00
James Shubin
c555478b54 engine, lang: Misc fixes for golang lint 2021-08-09 16:55:31 -04:00
Joe Groocock
3718372288 docs: Provide Libera.Chat webchat links over ircs URIs
Only a few days after updating the documentation[1] following the move
to libera.chat, the webchat client was added, mirroring the behaviour of
the documentation prior to the change. Replace the ircs:// links with
clickable URLs to a usable browser chat client, which is more ideal for
beginners. Advanced users will know what to do to connect using their
external client as normal

[1]: 7d7e225823

Signed-off-by: Joe Groocock <me@frebib.net>
2021-07-12 20:57:44 +00:00
James Shubin
390b41bc26 test: Add small test for weird bash spacing 2021-06-21 18:28:05 -04:00
James Shubin
530c5a64fb vendor: Pin version of consul until we're on golang 1.16
The builds broke because the consul dependency now requires golang 1.16,
so let's pin it for now.
2021-06-20 21:53:01 -04:00
Matthew Lesko-Krleza
d285aaedc9 test: Add a test for AddEdge 2021-05-30 21:08:37 -04:00
James Shubin
453fe18d7f lang: Move the Arg type into the common interface package
This lets it get used in multiple places.
2021-05-30 17:59:50 -04:00
James Shubin
5fae5cd308 lang: Fix grammar typos
Woops!
2021-05-30 17:16:49 -04:00
Joe Groocock
7d7e225823 docs: Update IRC links to Libera.Chat
#mgmtconfig has moved to Libera.Chat as the primary channel for IRC
communications. Update the documentation to reflect this.
Libera.Chat doesn't provide a first-party web portal but does recommend
a few in the linked documentation on the website. As there is no
suitable replacement for webchat.freenode.net, link to the "Choosing an
IRC client" page instead.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-05-27 08:50:24 +01:00
viq
19f404799d test: Fix awk usage for printing refs
This should print more fields than just 3rd one if they're present.
2021-05-24 23:49:19 +02:00
James Shubin
3e4652dca3 lang: funcs: Fix structlookup unification bug
We had mapped the field type to a dummy type instead of to T2 the return
type. Fixed now and added some tests.

This broke the unification for the load function lookups.
2021-05-23 22:52:50 -04:00
James Shubin
45b08de874 docs: Update the function guide
Hopefully this makes it easier for new function authors to get going
faster!
2021-05-23 20:21:14 -04:00
James Shubin
310e26dda9 lang: Switch over to the new PolyFunc interface
This isn't perfect yet, but we're trying to do this incrementally, and
merge whatever we can as early as possible.

During this work, I realized that the Simplify method of the exclusive
could probably be improved, and possibly receive a better signature.
This work will have to happen later.
2021-05-23 20:03:10 -04:00
James Shubin
f4eb54b835 lang: funcs: Add more invariants to contains func
This adds even more invariants to contains that I might have missed. It
may be redundant, or it may help. It also adds some tests.
2021-05-23 20:03:10 -04:00
James Shubin
3968c12947 lang: funcs: simplepoly: Support variant's in func definitions
This adds support for variant types in the simple poly definitions. It
is recommended that you avoid using these as much as possible, because
they're a bit harder for the type unification to solve for them. The way
this works is that these functions look at the available input types and
then generate a (recursive) set of invariants which might hold true. It
filters out any impossible ones, which is where this variant matching is
done. It's less likely that you'll get a solution with this mechanism,
but it is possible.
2021-05-23 20:03:10 -04:00
James Shubin
21c97d255f lang: funcs: simple: Check for function signatures
Make sure that we actually get function types here. This is just an
extra safety check.
2021-05-23 20:03:10 -04:00
James Shubin
eb1053607a lang: funcs: simple: Check for variant signatures
This adds a safety check in case someone sneaks in a variant type in the
simple function signature. These might be sneaky to detect, and it's
simpler to catch them right here.

From a design point of view, we might consider actually permitting
these, like we did with the simple poly API, but it's probably better
for them to get implemented in that API instead (if we decide to allow
this long-term) and keep this simple API very simple.
2021-05-23 20:03:10 -04:00
James Shubin
de7198e9dc lang: funcs: Check for functions that haven't been migrated
All polymorphic functions should use the new API, at least until we
either implement a compat wrapper. But it's probably best if we get rid
of the old API as soon as we make all this type unification work
properly.
2021-05-23 20:03:10 -04:00
James Shubin
0f30f47249 lang: funcs: core: world: Add unification to schedule return expr
This adds a sneaky unification between the expression of the function
return value in the unification. I am not entirely sure how often this
will get used, but it could be valuable in the right instance if this
isn't already learned through other sources. I'm fairly confident that
it isn't incorrect, so in the worst case scenario it's redundant
information for the unification solver.

This is being added as a separate commit so that it's obvious how this
type of unification invariant can be applied.
2021-05-23 20:03:10 -04:00
James Shubin
6b2ad8ebc8 lang: funcs: core: world: Add Unify method for schedule function
We should probably add some tests for this function because it once had
type unification ghosts, and while adding this new API method, I somehow
hit some temporary new ghosts that have since been killed.
2021-05-23 20:03:10 -04:00
James Shubin
1f302144ef lang: funcs: core: world: Move schedule func arg names to a const
This is a bit safer and cleaner.
2021-05-23 20:03:10 -04:00
James Shubin
d04c7a6ae4 lang: funcs: Add Unify method for history function
This could use some tests.
2021-05-23 20:03:10 -04:00
James Shubin
9ca2cda8c7 lang: funcs: core: Add more invariants to template func
This adds even more invariants to template that I might have missed. It
may be redundant, or it may help.
2021-05-23 20:03:10 -04:00
James Shubin
1fd06ecbf9 lang: funcs: core: fmt: Add more invariants to printf
This adds even more invariants to print that I might have missed. It may
be redundant, or it may help.
2021-05-23 20:03:10 -04:00
James Shubin
97baad4cb1 lang: funcs: Add Unify method for maplookup function
This also adds a few tests.
2021-05-23 20:03:10 -04:00
James Shubin
fbd93ecf0d lang: funcs: Add Unify method for structlookup function
This also adds a few tests.
2021-05-23 20:03:10 -04:00
James Shubin
e941ccea92 lang: funcs: Add Unify method for the simplepoly API
This is an implementation of the Unify approach for the simplepoly
function API, which wraps the full function API. It is unique in that a
lot of different functions use it, and it is easy to build functions
with it. It needs to use exclusives to represent the different options,
but at least it filters out any that aren't viable.

The Unify implementation here is fairly similar to the patterns in the
template() function.

To improve the filtering, it would be excellent if we could examine the
return type in `solved` somehow (if it is known) and use that to trim
our list of exclusives down even further! The smaller exclusives are,
the faster everything in the solver can run.
2021-05-23 20:03:10 -04:00
James Shubin
d692483bc3 lang: funcs: Add Unify method for operator function
This is an implementation of the Unify approach for the operator
function. It is unique in that it is a wrapper around the simple
operator function API.

To improve the filtering, it would be excellent if we could examine the
return type in `solved` somehow (if it is known) and use that to trim
our list of exclusives down even further! The smaller exclusives are,
the faster everything in the solver can run.
2021-05-23 20:03:10 -04:00
James Shubin
95cfbd0fff lang: funcs: Ensure that Info sig's are invalid if not built yet
In case something in the type unification tries to speculatively call
Info before it's ready to produce a valid sig, make sure we only return
a definitive answer (non-nil, and no variant types) once we've
conclusively finished defining the signature.
2021-05-23 20:03:10 -04:00
James Shubin
b3d1ed9e65 lang: funcs: core: math: Add a fortytwo function
This is mainly meant as a useful test case, but might as well have it be
fun too. As an aside, it taught me a surprising result about the %v verb
in printf, and we'll have to decide if it's an issue we care about.

https://github.com/golang/go/issues/46118

The interesting thing about this method is that it uses the simplepoly
API but has no input args-- only the output types are different. If it
had identical types in the input args, that might also have been
interesting, but it's more rare to have none. Hopefully this exercises
our type unification logic.
2021-05-12 03:30:25 -04:00
Joe Groocock
fe2b8c9fee engine: resources: exec: AutoEdge to User/Group/File
Fixes https://github.com/purpleidea/mgmt/issues/221

Signed-off-by: Joe Groocock <me@frebib.net>
2021-05-11 16:51:02 -04:00
James Shubin
2d7deef4e2 lang: unification: Don't stall the solver over generators
If we have a solution, and all that remains are generators, then feel
free to remove them and win.
2021-05-11 05:23:00 -04:00
James Shubin
b4a70b02e3 lang: funcs: Add Unify method for contains function
This is an implementation of the Unify approach for the contains
function. It is unique in that its generator invariant can recursively
generate a new generator invariant once.
2021-05-11 04:41:32 -04:00
James Shubin
c5c2364ed4 lang: funcs: core: fmt: Add an additional invariant to printf
This adds an invariant for printf that I might have missed. It may be
redundant, or it may help.
2021-05-11 03:23:33 -04:00
James Shubin
efcc4291a3 lang: funcs: core: Add Unify method for template function
This is an implementation of the Unify approach for the template
function.
2021-05-11 03:22:27 -04:00
James Shubin
6ea6ee264d lang: Add new unification rules for functions
This is meant as an incremental step into the new unification. Hopefully
it doesn't break anything and that we can rip out the old polymorphisms
work soon.
2021-05-11 02:52:35 -04:00
James Shubin
2865ba7632 lang: unification: Improve our simple solver
This removed a bug in the InvariantCall stuff, and also hopefully made
it more robust to actually solving when it had a solution.
2021-05-11 02:47:24 -04:00
James Shubin
2bed668d31 lang: interfaces: Small fixups to make unification work for now
This is all hacks until it works. Sorry that I am not a type unification
expert. If you are, please send us some patches =D
2021-05-11 01:31:10 -04:00
James Shubin
9dc24860f3 lang: interfaces: Add a new poly func interface
This new interface is subject to change and will probably be renamed if
we decide to keep it.
2021-05-11 00:45:25 -04:00
James Shubin
f01377b3bc lang: funcs: core: fmt: Add Unify method for printf
This is an implementation of the Unify approach for the printf function.
2021-05-11 00:33:50 -04:00
Joe Groocock
7443dfac4c misc: Run apt update before installing packages
Sometimes the package repo may be out of date and installing required
packages can return 404 because the version in the stale database has
been removed.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-05-09 19:22:40 +01:00
James Shubin
e6408e187c lang: Rename old fail5 and fail6 variables
Last of the numbered error scenario cleanups...
2021-05-08 05:19:36 -04:00
James Shubin
a02d282d3e lang: Rename old fail8 variable to failInterpolate
Eight... Interpolate?

Cleanups...
2021-05-08 04:30:17 -04:00
James Shubin
f778f53744 lang: Rename old fail9 variable to failInit
Cleanups...
2021-05-08 04:25:56 -04:00
James Shubin
95ea93564e lang: Rename old fail4 variable to failGraph
Cleanups...
2021-05-08 04:20:05 -04:00
James Shubin
d51029e86c lang: Rename old fail3 variable to failUnify
Cleanups...
2021-05-08 04:17:21 -04:00
James Shubin
1016699c94 lang: Rename old fail2 variable to failSetScope
Cleanups...
2021-05-08 04:13:33 -04:00
James Shubin
63f63955e7 lang: Replace numbered errors with named ones
This makes the tests easier to read and modify without having out of
order numbers. When writing the tests, you'll remember more easily which
section you're erroring in too!
2021-05-07 23:41:00 -04:00
James Shubin
37be9fda9f lang: Catch duplicate resource fields or meta entries statically
This teaches the compiler to catch entries with duplicate fields, and
duplicate meta entries, because it could be ambiguous to determine which
should take precedence. For example, if you specified `content` to a
file resource twice, this should error. This is known statically, so we
can catch it. If you specified two `Meta:noop` entries, this can also be
caught.

The interesting part happens when you specify one `Meta:noop` entry, and
one `Meta` entry which happens to contain a noop field in the struct.
For this, we actually have to wait until type unification is finished,
and catch the error there. This is because after type unification we
will know the precise type of the struct being passed to `Meta`, and so
we can look at its field names, even if their values aren't yet known
because the graph hasn't run yet.
2021-05-07 23:41:00 -04:00
James Shubin
0756133a7e lang: Add a named error for catching errors in test on Init
This makes it so that we can catch errors that happen in Init. We also
name the errors so that number sequence doesn't matter.
2021-05-07 23:14:49 -04:00
Joe Groocock
83c5ab318b lang: types: Clear map/list types during Into()
Map and list types are now unconditionally initialised during an Into()
call to ensure that the only data within them after the operation is
that added by the Into() function.

Prior to this change, map/list types would likely not be cleared prior
to the data being inserted into them with a few exceptions. Nil
pointers or maps/lists that were not sufficient in capacity would be
reinitialised and used to replace the existing backing data store. In
some cases this wouldn't occur meaning any residual data existing in the
container before the Into() call could persist after the data copy
completes. This behaviour is wildly inconsistent and not ideal in the
vast majority of cases. It should be assumed that the Into() call will
preserve nothing and always produce a consistent and deterministic
output.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-05-05 10:41:48 +01:00
Joe Groocock
0c28957016 lang: funcs: Funcs that never load are fatal
If there is a programming error in any func Stream() implementation then
the node could never output anything, causing the engine to hang
indefinitely waiting for an initial value that will never come,

Nodes keep track of whether they are loaded, so testing for this
occurence is pretty simple. Any nodes that do not return output at least
once before they close their output channel can be considered a fatal
error on which the engine will exit.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-05-04 11:39:34 -04:00
James Shubin
959084040d lang: Don't block the engine for empty values
If the user passed an empty list or map, we should send that and not
block. This also includes a simple test to ensure this keeps working.
2021-05-04 11:27:58 -04:00
James Shubin
8a428c6936 lang: Add a test timeout to catch blocked cases
This should catch any blocked tests and report an error. The timeout is
arbitrary.
2021-05-04 11:26:17 -04:00
Joe Groocock
48da23226c project: Add frebib to AUTHORS
Signed-off-by: Joe Groocock <me@frebib.net>
2021-05-04 14:16:54 +01:00
James Shubin
5f0c6e5102 lang: types: Add extra ValueOf test
Just to double check weird behaviours of the golang reflect lib.
2021-05-04 06:26:33 -04:00
Joe Groocock
29f1c6f50e lang: types: Fix ValueOf() panic with nil pointer values
Some forms of reflect.Value can cause ValueOf() to panic when there is a
nil pointer somewhere within the reflect.Value, whether that be a
container type like a struct, list or map, or just a raw nil pointer.

In these cases, ValueOf() attempted to dereference the pointer without
ever checking if it was nil. mgmt lang doesn't have pointers of any
kind, so these Golang values cannot be represented in mcl types in the
current form so return a helpful error to the user.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-05-04 06:26:23 -04:00
James Shubin
4d187419ac engine: Small typo/cleanups in autogrouping code 2021-05-04 05:30:27 -04:00
James Shubin
58998f9cab engine: Transform the send/recv init functions into helpers
Since we'll want to use them elsewhere, we should make these helper
functions. It also makes the code look a lot neater. Unfortunately, it
adds a bit more indirection, but this isn't a critical flaw here.
2021-05-04 05:30:27 -04:00
James Shubin
cdc5ca8854 util: Add a simple log wrapper for io.Writer
This lets logger interfaces that use io.Writer get met by our logging
interface.
2021-05-04 05:27:07 -04:00
James Shubin
44e1e41266 lang: types: Improve documentation for ValueOf functions
A reminder that nil's in golang don't map to anything in mcl.
2021-05-04 05:27:07 -04:00
James Shubin
33fda8605a lang: types: Add new ValueOf tests
Hopefully this makes all of this a bit more obvious.
2021-05-04 04:27:33 -04:00
Joe Groocock
5f9ed69299 misc: Replace go-bindata with maintained fork
As per [1] go-bindata was removed from GitHub and later replaced by the
community. jteeuwen/go-bindata has since been archived to represent this
state and now most communities use kevinburke/go-bindata instead as it
is more actively maintained.

[1]: https://github.com/jteeuwen/go-bindata/issues/5

Signed-off-by: Joe Groocock <me@frebib.net>
2021-05-04 04:10:05 -04:00
Joe Groocock
7f1baea3b0 engine: resources: docker: Replace deprecated NewClient() with NewClientWithOpts()
docker/client.NewClient() is deprecated in favour of NewClientWithOpts()
which takes a series of client.Opt functions to configure the API
client. As mgmt only passes the API version through, this simplifies the
NewClient() calls.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-05-02 10:39:04 +01:00
James Shubin
f75026e4b2 lang: unification: Teach the solver about new invariants
This extends our simple solver so that it can understand the new
invariants. For the Value holding invariants, it doesn't do much-- those
are expected to be used within the execution of the GeneratorInvariant
so they are passed through untouched. For the GeneratorInvariant it must
actually try running this periodically to see if it produces new
invariants that are helpful for the whole solution. Since this could get
expensive quickly, the logic is to only try running these once we've
entered steady state, but before we've tried to reach for the
ExclusiveInvariant's. The exclusives are the most expensive so we run
these last, and the generators are run late because they won't usually
produce anything helpful unless some of the basic solving has already
happened. If they could produce useful things right away, then there
wouldn't be a need for them!
2021-05-02 00:52:57 -04:00
James Shubin
ce7a1a9c67 lang: Add a CallFuncArgsValueInvariant invariant
This is a new invariant that I realized might be useful. It's not
guaranteed that it will be used or useful, but I wanted to get it out of
my WIP branch, to keep that work cleaner.
2021-05-02 00:52:57 -04:00
James Shubin
a62056fb19 lang: Add a GeneratorInvariant invariant
This is a new invariant that I realized might be useful. It's not
guaranteed that it will be used or useful, but I wanted to get it out of
my WIP branch, to keep that work cleaner.
2021-05-02 00:52:57 -04:00
James Shubin
f3434a8155 lang: Add a ValueInvariant invariant
This is a new invariant that I realized might be useful. It's not
guaranteed that it will be used or useful, but I wanted to get it out of
my WIP branch, to keep that work cleaner.
2021-05-02 00:52:57 -04:00
James Shubin
4e023ef517 lang: Move the ExprAny to the interfaces package
Having this special "placeholder" interface is useful for more than one
package.
2021-05-02 00:52:57 -04:00
James Shubin
97b80cb930 lang: unification: Move the InvariantSolution struct
We are just relocating this in the file for consistency.
2021-05-02 00:52:57 -04:00
James Shubin
525b4e6a53 lang: Move core unification structs into shared interfaces package
We should probably move these into the central interfaces package so
that these can be used from multiple places. They don't have any
dependencies, and it doesn't make sense to have the solver code mixed in
to the same package. Overall the interface being implemented here could
probably be improved, but that's a project for another day.
2021-05-02 00:52:57 -04:00
James Shubin
054eaf65b8 util: safepath: Add a new safe path helper library
This is a new path manipulation library that is designed to be safer
than using simple strings for everything. It is more work to use, but it
can help you keep track of the different path types.

It has been sitting unused in a git branch for too long, and I figured
it should see the light of day in case someone wants to start using it.
2021-05-02 00:52:57 -04:00
James Shubin
48fa796ab1 test: Disable failing test
Hit another intermittent failed test in GH CI.
2021-03-10 03:36:27 -05:00
Jean-Philippe Evrard
1873e022cc test: Add guard when no commit needs testing
Without this patch, github actions fail.

It's a temporary workaround until [1] is done.

[1]: https://github.com/purpleidea/mgmt/issues/643
2021-03-03 10:49:22 +01:00
James Shubin
35a8062b58 test: Disable travis IRC notifications for now
One of our contributors is unusually annoyed by them, and it's important
to keep your contributors happy!
2021-03-03 04:28:53 -05:00
Jean-Philippe Evrard
636248ad67 test: Ensure branches are also testable
test-commit-message runs on PR, but also on push in other branches
which aren't PRs. We need to test those too.

This is fixed by ensuring the same kind of behaviour than travis CI:
When a patch is put on a branch, it's using the branchname for
testing [1].

[1]:
https://docs-staging.travis-ci.com/user/environment-variables/#default-environment-variables
2021-03-03 09:52:49 +01:00
Jean-Philippe Evrard
4511c54fad test: Ensure github CI tests commit messages
Without this patch, the travis var is empty, and we just pass.
This is a problem, as we are using github CI nowadays.

This should fix it.
2021-03-03 02:47:30 -05:00
James Shubin
7f3970541b test: Skip more tests
I think some of these fail due to shared environments and noisy
neighbours in github. We'll have to fix that eventually or test
elsewhere.
2021-03-02 13:41:00 -05:00
James Shubin
4040f4d151 test: Skip yet another intermittent test
We shall not have intermittent tests!
2021-03-02 12:48:44 -05:00
James Shubin
887d374c53 lang: funcs: Catch simple function api usage without types
In case a programmer makes a mistake and passes in a function using the
simple function API without a type or even without the entire value,
we'll now return a sensible error message and panic in init() instead of
requiring a test to catch this alone.
2021-02-28 22:50:51 -05:00
James Shubin
be4b87155d test: Skip another intermittent test
I think this might be related to multiple jobs running at the same time
on the same host. Not sure though.
2021-02-24 04:00:34 -05:00
James Shubin
b987a7da4c examples: tftp: Fix missing error checking in example 2021-02-20 13:31:45 -05:00
James Shubin
7153fe5ad2 test: Skip intermittent tests
It would be great to fix some rare races or debug what's wrong in CI,
but for now let's get rid of these fails so that we can get better data
for when we break something more serious. We'll need to revisit all of
this for sure.
2021-02-19 21:17:57 -05:00
James Shubin
ccd8ba44d9 test: Exclude generated ragel parser from golint 2021-02-17 22:01:34 -05:00
James Shubin
e7ef0f7a6c test: Set default column size if $TERM env var isn't set
Seems Github actions breaks or unsets this, leading to the errors:

tput: No value for $TERM and no -T specified
seq: missing operand
Try 'seq --help' for more information.

Hopefully this makes things a bit more robust.
2021-02-17 04:07:30 -05:00
James Shubin
400b58c0e9 lang: Improve string interpolation
The original string interpolation was based on hil which didn't allow
proper escaping, since they used a different escape pattern. Secondly,
the golang Unquote function didn't deal with the variable substitution,
which meant it had to be performed in a second step.

Most importantly, because we did this partial job in Unquote (the fact
that is strips the leading and trailing quotes tricked me into thinking
I was done with interpolation!) it was impossible to remedy the
remaining parts in a second pass with hil. Both operations needs to be
done in a single step. This is logical when you aren't tunnel visioned.

This patch replaces both of these so that string interpolation works
properly. This removes the ability to allow inline function calls in a
string, however this was an incidental feature, and it's not clear that
having it is a good idea. It also requires you wrap the var name with
curly braces. (They are not optional.)

This comes with a load of tests, but I think I got some of it wrong,
since I'm quite new at ragel. If you find something, please say so =D In
any case, this is much better than the original hil implementation, and
easy for a new contributor to patch to make the necessary fixes.
2021-02-17 03:35:12 -05:00
James Shubin
5257496214 test: Make a few cosmetic changes and enable race testing 2021-02-17 02:45:47 -05:00
Jean-Philippe Evrard
e1bfe4a3ce test: Add GitHub Actions test support
Authored-By: Jean-Philippe Evrard <open-source@a.spamming.party>
Co-Authored-By: James Shubin <james@shubin.ca>
Signed-off-by: Joe Groocock <me@frebib.net>
2021-02-14 21:41:02 -05:00
James Shubin
f31cce8ec2 test: misc: Clarify golang wording 2021-02-14 21:39:01 -05:00
Joe Groocock
169ebfa72c test: make-deps: Add folds around tests and dep blocks
Improves readability of CI test output and hides away the complexity
when in most cases it is not required. Retain fold behaviour for both
Travis and GitHub Actions in case both are used in any capacity.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-02-12 16:44:12 +00:00
Joe Groocock
7cace52ab5 test: prevent LinuxBrew in GitHub Actions CI
Ubuntu-latest in GitHub Actions provides linuxbrew, so the tests install
both the native Debian dependency packages, and also the linuxbrew
variants which is slower and entirely redundant.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-02-12 16:44:12 +00:00
Joe Groocock
95b93c60d9 test: Invert negative bash assertions
In bash `-n` is `non zero length` which is the opposite of `-z` meaning
`zero length`. `-n` is semantically identical to `! -z` but `-n`.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-02-12 16:44:12 +00:00
Joe Groocock
5af1dcb8b1 test: Add in_ci utility test function
in_ci checks for environment variables set by a selection of CI systems
and returns true if the test appears to be running in CI. Additionally
it can test for specific CI systems, and returns true if the CI system
is listed.

Deduplicate existing environment checks for Travis and Jenkins.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-02-12 16:44:11 +00:00
Joe Groocock
6a61774fb7 docker: Bump docker dependencies, add containerd
These dependencies are maintained because the upstream repos bundle
vendor directories into the repos, which cause namespacing issues during
build. Git submodules don't strip the vendor directory whereas most
vendoring tools would.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-02-09 21:14:34 +00:00
James Shubin
ccbaca24f1 gopath: Remove this unused directory
I had this symlink hack a long time ago. I don't think it's being used
anymore.
2021-02-07 21:23:49 -05:00
Joe Groocock
07b6048dc5 etcd: Bump etcd + friends to the latest upstream version
This allows dropping the pinned grpc-prometheus and grpc-gateway
libraries as git master works fine for now.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-02-07 12:55:02 +00:00
Joe Groocock
60dd34d066 make: Drop support for Go 1.9 in make build
docs/development.md says the minimum required Golang version is 1.13 at
the time of writing.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-02-07 00:08:51 -05:00
James Shubin
28451d1e14 lang: funcs: Fixup race in vumeter example
The vumeter example was written quickly and without much care. This
fixes a possible race (panic) and also removes the busy loop that wastes
CPU while we're waiting for the first value to come in.
2021-02-06 23:59:06 -05:00
Joe Groocock
db95b6381f examples: lang: Reinstate mcl as unification bug is fixed
Struct field names now correctly map based on their `lang` tags in Go
structs, so this example now works as originally intended.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-02-06 16:57:01 +00:00
Joe Groocock
6b14c9bea4 lang: Map Go struct fields using lang struct tag
Converting a reflect.Type of KindStruct did not respect the `lang` tag
on struct fields incidating how fields from mcl structs should be mapped
even though resource field names did. This patch should allow structs
with mapped fields to be respected.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-02-06 16:57:01 +00:00
Joe Groocock
742adc00fe lang: Convert StmtRes to engine.Res with types.Into()
Replace existing field-mapping code with calls to types.Into() to
reflect the mcl data into the Go resource struct with finer granularity
and accuracy, and less reliance on the magic reflect.Set() function.

One major advantage over reflect.Value.Set() is Into() allows tailoring
the data that is set, specifically when coercing mcl struct values into
Golang struct values, the fields can be appropriately mapped based on
the lang tag on the struct field. With reflect.Value.Set() this was not
at all possible as there was a contradiction of logic given the
following rules:

- mcl struct fields must have lowercase names
- Golang struct fields with lowercase names are unexported
- Golang reflection does not allow modifying unexported fields

Prior to this change, it was impossible to map an mcl inline struct in a
resource to the matched Golang counterpart, even if the lang tag was
present on the struct field. This can be demonstrated with the following
trivial example:

    test "name" {
        key => struct{
            name => "hello",
        },
    }

and the accompanying Golang resource definition:

    type TestRes struct {
        traits.Base
        traits.Edgeable

        Key struct {
            Name string `lang:"name"`
        } `lang:"key"`
    }

Due to the mismatch in field names in the embedded struct, the type
unifier failed and refused to match mcl 'name' to Go 'Name' due to the
missing mapping logic.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-02-06 16:57:00 +00:00
Joe Groocock
52897cc16c lang: Implement Into() to set types.Value from reflect.Value
Into() mutates a given reflect.Value and sets the data represented by
the types.Value into the storage represented by the reflect.Value.

Into() is the opposite to ValueOf() which converts reflect.Value into
types.Value, and in theory they should be (almost) bijective with some
edge case exceptions where the conversion is lossy.

Simply, it replaces reflect.Value.Set() in the broad case, giving finer
control of how the reflect.Value is modified and how the data is set.
types.Value.Value() is now also a redundant function that achieves the
same outcome as Into(), but with less type specificity.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-02-06 16:57:00 +00:00
Joe Groocock
c950568f1b lang: Move StructTag const into lang/types
This constant value is strongly tied to the language, and little to do
with the engine. Move the definition into the lang/types package to
prevent circular imports between lang/types and engine/util.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-02-06 16:56:57 +00:00
Joe Groocock
845d7ff188 lang: Fix panic in lang/types/ValueOf() for Struct
Replace use of reflect.Value.Len() with NumField() which is intended to
return the number of fields in reflected Struct value.

Len should only be used for Array, Chan, Map, Slice and String types.

Add some trivial sanity check tests for ValueOf() for the simple and
complex container types.

Signed-off-by: Joe Groocock <me@frebib.net>
2021-01-31 23:10:16 +00:00
James Shubin
3bd8658da6 art: Use a transparent logo for dark themes
As helpful user frebib pointed out, our logo doesn't render nicely when
github users use the dark theme. This has already been complained about
by others in:

https://github.community/t/support-theme-context-for-images-in-light-vs-dark-mode/147981/2

For now, we'll switch to a transparent background.
2021-01-31 18:00:47 -05:00
James Shubin
336a38081a legal: Happy 2021 everyone...
Done with:

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

Checked manually with:

git add -p

Hello to future James from 2022, and Happy Hacking!
2021-01-31 16:52:46 -05:00
Jean-Philippe Evrard
01c2131436 misc: Make-deps should assume go vet present
While the code comment says to check if go vet command is present,
it actually tests if go vet command returns properly.

This is a problem if go vet is _not_ returning 0 due to a
failure while go vet is present: it will try to install the
legacy go vet.

This fixes it by removing this block of code completely,
as we assume a golang version which contains it anyway.
2020-12-09 19:25:19 -05:00
Joe Groocock
c274231544 test: Fix implicit test fail in test-markdownlint
Fix the silent test failure by catching the uncaught error from
`command`, handling the failure gracefully.

    $ bash -x test/test-markdownlint.sh
    ...
    ++ command -v mdl
    + MDL=
    $ echo $?
    1

    $ bash -x test/test-markdownlint.sh
    ...
    ++ command -v mdl
    + MDL=
    + true
    + '[' -z '' ']'
    + fail_test 'The '\''mdl'\'' utility can'\''t be found.'
    + echo -e 'FAIL: The '\''mdl'\'' utility can'\''t be found.'
    FAIL: The 'mdl' utility can't be found.

Fix a couple of glaring shellcheck warnings and errors mostly
surrounding variable quoting.

Signed-off-by: Joe Groocock <me@frebib.net>
2020-12-08 15:51:52 +00:00
Joe Groocock
4a2864701c lang: Assert that 'metadata.yaml' is not parsed as raw mcl
Contradictory to expectations, `mgmt run lang metadata.yaml` would
produce an error similar to the following, which is likely unexpected
from the user perspective:

    2020-11-24 12:24:08.330968 I | cli: lang: lexing/parsing...
    2020-11-24 12:24:08.331153 I | run: error: cli parse error: could not generate AST: parser: `syntax error: unexpected DOT` @1:1

Produce a user-friendly warning instead, hinting with the supported
usage:

    2020-11-24 13:15:01.686659 I | run: error: cli parse error: could not activate an input parser: unexpected raw code 'metadata.yaml'. Did you mean './metadata.yaml'?

Signed-off-by: Joe Groocock <me@frebib.net>
2020-11-24 13:56:08 +00:00
James Shubin
76ede10e0a misc: Update path to go-fuzz
They moved it :/
2020-09-23 13:42:51 -04:00
Ahmed Al-Hulaibi
274e01bb75 misc, docs: Update minimum required golang version to 1.13 2020-09-23 12:34:54 -04:00
James Shubin
d75f763c99 misc, docs: Move to golang 1.12 2020-09-23 12:34:54 -04:00
Donald Bakong
5bc985663c docs: Add underscore issue to FAQ 2020-09-23 11:25:46 -04:00
James Shubin
df9e2e853f docs: Update state of remote execution and resource 2020-09-23 11:21:03 -04:00
Ivan Pejić
b4828a6f0a docs: Update FAQ to mention temp absence of remote 2020-09-23 11:15:44 -04:00
Donald Bakong
e99dd749a0 engine: resources: Fixing return bug 2020-09-23 11:10:38 -04:00
David Randall
10ce7178c0 misc: Incorrect dependency path for dvyukov/go-fuzz
Fix the dependency path for dvyukov/go-fuzz to github.com from golang.org.

Fixes #601
2020-06-07 15:05:46 -04:00
Donald Bakong
5c6a66eaf5 lang: funcs: Add cidr_to_ip function 2020-06-03 22:01:01 -04:00
Francois Rompre-Lanctot
36d30bc985 lang: funcs: Add macfmt function 2020-06-03 17:46:39 -04:00
Adam Sigal
a5152b82e9 engine: resources: exec: Add Env
Add functionality to specify environment variables in exec.
2020-04-19 20:30:43 -06:00
James Shubin
e9af8a2595 engine: resources: exec: Clean up error handling
Some quick fixes, this whole resource should be looked at for
discrepancies, since it was written very early.
2020-04-14 22:59:33 -04:00
Adam Sigal
84b5b60d49 engine: resources: exec: Fix typo
Typo in description of cwd field fixed.
2020-04-14 22:36:25 -04:00
James Shubin
8f60f42be3 engine: resources: Add http:server and http:file resources
This adds a new http server resource, as well as a http file resource
that is used to specify files to serve in that server. This allows you
to have an http server that is entirely server from memory without
needing files on disk.

It does this by using the autogrouping magic that is already available
in the engine.

The http resource is not meant to be a full-featured http server
replacement, and it might still be useful to use the venerable webserver
of your choice, however for integrated, pure-golang bootstrapping
environments, this might prove to be very useful.

It can be combined with the tftp and dhcp resources to build PXE setups
with mgmt!

This resource can be extended further to support an http:flag endpoint,
an http:ui endpoint, automatic edges, and more!
2020-04-11 03:23:04 -04:00
James Shubin
583344138a engine: resources: Add dhcp:server and dhcp:host resources
This adds a new dhcp server resource, as well as a dhcp host resource
used to specify the static mapping between mac address and ip address.
It also adds a simple, pure-golang example dhcp client which might make
testing easier.

The dhcp resource is not meant to be a full-featured dhcp server
replacement, and it might still be useful to use the venerable dhcpd,
however for integrated, pure-golang bootstrapping environments, this
might prove to be very useful.

It can be combined with the tftp resource to build PXE setups with mgmt.

This resource can be extended further to support a dhcp:range directive,
automatic edges, and more!
2020-04-11 02:45:18 -04:00
James Shubin
016d021d5a engine: resources: tftp: Improve validation and error messages
Just some small cleanups for our tftp resource. We also rename the
struct to make it consistent, since golint complains about similar
protocols when it is not all capitalized.
2020-04-11 02:45:18 -04:00
James Shubin
115dc4bfa4 engine: resources: net: Add an IP forward field
This adds an IP forward field (boolean) and improves the documentation.
2020-04-11 02:02:35 -04:00
James Shubin
5b83febb23 etcd: Log an error that wasn't getting seen
We would error when the address could not bind, but it was for
non-standard reasons, and we didn't see the specific reason why.
2020-04-11 02:02:35 -04:00
James Shubin
c9d5c50402 test: Improve our test for long lines
This now allows long URL's to start part way through a sentence instead
of requiring them to start on the beginning of a new line.
2020-04-03 01:15:57 -04:00
James Shubin
fc839d2983 vendor: Fix broken upstream hashicorp module
This module broke compat with old versions of golang. Vendor it until
we're at a minimum of golang 1.13.x everywhere.
2020-04-02 22:27:06 -04:00
James Shubin
3bce96bbd5 docs: Add new talk from cfgmgmtcamp 2020
Those first ten seconds of the video are awesome!
2020-02-29 19:40:26 -05:00
Patrick Meyer
6279be073b lang: Prevent struct types with duplicate field names
The previous fix for #591 in 70eecd5 didn't address all issues
concerning duplicate struct field names. It still crashed for inputs
like `$d []struct{x int; x float}`. Note the different types but
duplicate names.
2020-02-29 19:11:51 -05:00
Patrick Meyer
ea37132ce4 lang: Fix wrong go-fuzz bin name
I missed renaming this after moving the fuzz.go from the lang package
to its own fuzz package.
2020-02-29 05:28:38 +01:00
James Shubin
70eecd5289 lang: Prevent struct types with duplicate fields
Struct types with duplicate fields are invalid types and weren't caught
by the parser. This fixes the issue and adds some associated tests. It
also checks and tests for duplicate struct value field names.

As a technical side-note, this doesn't change the lang/types/ functions
to remove panics-- the signatures are simplified to make their use
simple, and we intentionally panic if they're used incorrectly. In this
case, one was being used without having previously validated the input.

Thanks to Patrick Meyer for finding this issue via fuzzing!
2020-02-27 18:52:02 -05:00
James Shubin
380d03257f misc, lang: Small fixups
Change some minor style issues.
2020-02-27 17:34:03 -05:00
Patrick Meyer
006de6da14 lang: Add fuzz target to lang Makefile 2020-02-26 12:07:00 +01:00
Kenneth Hoste
10aa80e8f5 docs: Add link to recording of James' FOSDEM 2020 talks 2020-02-17 14:23:20 -05:00
Felix Frank
013439af6d test: Make prometheus tests safer and more verbose 2020-02-16 18:38:58 -05:00
Felix Frank
3408961155 test: Fix syntax in the loadavg test 2020-02-16 18:38:43 -05:00
Francois Rompre-Lanctot
f3b4a8d055 engine: resources: Add a test case for resource owner check
This adds FileOwnerExpect as a new Step which allows validating if the
owner was set properly on a resource.
2020-02-10 22:17:42 -05:00
Francois Rompre-Lanctot
104af7e86f engine: resources: Fix typo 2020-02-03 22:57:13 -05:00
James Shubin
be39fbeff6 examples: lang: Update examples 2020-02-01 16:48:23 -05:00
James Shubin
4109045fa4 github: Add new needinfo tag 2020-01-29 11:38:53 -05:00
James Shubin
90fd8023dd lang, engine: Add a facility for resources to export constants
Since we focus on safety, it would be nice to reduce the chance of any
runtime errors if we made a typo for a resource parameter. With this
patch, each resource can export constants into the global namespace so
that typos would cause a compile error.

Of course in the future if we had a more advanced type system, then we
could support precise types for each individual resource param, but in
an attempt to keep things simple, we'll leave that for another day. It
would add complexity too if we ever wanted to store a parameter
externally.

Lastly, we might consider adding "special case" parsing so that directly
specified fields would parse intelligently. For example, we could allow:

	file "/tmp/hello" {
		state => exists,	# magic sugar!
	}

This isn't supported for now, but if it works after all the other parser
changes have been made, it might be something to consider.
2020-01-29 11:16:04 -05:00
James Shubin
f67ad9c061 test: Add a check for too long or badly reflowed docstrings
This ensures that docstring comments are wrapped to 80 chars. ffrank
seemed to be making this mistake far too often, and it's a silly thing
to look for manually. As it turns out, I've made it too, as have many
others. Now we have a test that checks for most cases. There are still a
few stray cases that aren't checked automatically, but this can be
improved upon if someone is motivated to do so.

Before anyone complains about the 80 character limit: this only checks
docstring comments, not source code length or inline source code
comments. There's no excuse for having docstrings that are badly
reflowed or over 80 chars, particularly if you have an automated test.
2020-01-25 04:43:33 -05:00
James Shubin
525e2bafee misc: Let sigtee work on older golang versions 2020-01-25 04:43:33 -05:00
James Shubin
b65a9abf8e misc: Add two scripts to help debug things
This adds two new helper scripts that are good for debugging mgmt. Just
build and add `sigtee` to your ~/bin/ along with filter-golang-stack.py
and mgmt_debug.sh and use the later to call mgmt as you would normally.

For example, I might do:

$ mgmt_debug.sh ./mgmt run --tmp-prefix lang examples/lang/hello0.mcl

And if I kill it with ^\ then I'll get a filtered trace at the end in my
$PAGER (which is assumed to be `less`) and this should make my life
easier.

As a cool bonus, this means we use bash, python, and golang all
together!
2020-01-13 01:07:00 -05:00
James Shubin
fec94aa53a engine, lang: Fix simple test failures
Two bugs sneaked in while pushing old stuff.
2020-01-12 19:35:11 -05:00
James Shubin
3d4b345728 examples: lang: Improve reverse example
It's cool to show just the mode changes.
2020-01-12 17:42:20 -05:00
James Shubin
579975f08d engine: graph: Don't error when state file is missing
For some reason we get errors when we try to remove a non-existent state
file. There's a slight possibility that it could be a bug we're working
around, but it's not clear that this is the case, and I think it's
possible that a state file could have gotten nuked by the user somehow,
although this was occurring "naturally" when running reverse1.mcl so
let's keep that working for now.
2020-01-12 16:41:09 -05:00
James Shubin
3707b39fef engine: graph: Improve comments
Clarify that we're referring to cycles in the graph, since it needs to
be a DAG.
2020-01-12 16:39:32 -05:00
James Shubin
f07387225b engine: resources: Log more info about tftp errors
This helps for debugging this kind of issue:
https://github.com/pin/tftp/issues/41#issuecomment-570744056
2020-01-03 20:42:34 -05:00
James Shubin
2648fb1bb1 legal: Happy 2020 everyone...
Done with:

ack '2019+' -l | xargs sed -i -e 's/2019+/2020+/g'

Checked manually with:

git add -p

Hello to future James from 2021, and Happy Hacking!
2020-01-03 20:08:37 -05:00
James Shubin
d34715b4ba engine: resources: pippet: Cleanup and proper wrapping
Felix, please configure your editor to wrap at 80 chars and/or help us
write a test for this please =D
2020-01-03 01:20:00 -05:00
Felix Frank
63af50bf98 engine: resources: pippet: Initial implementation for new resource type
The pippet resource implements faster integration of Puppet resources
in mgmt at runtime, by piping synchronization commands to a Puppet
process that keeps running alongside mgmt. This avoids huge overhead
through launching a Puppet process for each operation on a resource
that is delegated to Puppet.
2020-01-03 01:19:37 -05:00
James Shubin
456550c1d4 engine: resources: docker: Make a few fixups
Here are a few fixups to the docker resources. All miscellaneous stuff,
nothing major.
2020-01-03 00:53:20 -05:00
Jonathan Gold
8174b88ec3 engine: resources: docker: Add sensible defaults 2020-01-03 00:30:01 -05:00
Jonathan Gold
3233973748 engine: resources: docker: Add AutoEdges between container and image 2020-01-03 00:30:01 -05:00
Jonathan Gold
bdfb1cf33e engine: resources: docker: Ensure image is specified for containers 2020-01-03 00:30:01 -05:00
Jonathan Gold
1c5fcd59e7 engine: resources: docker: Add a docker image resource 2020-01-03 00:30:01 -05:00
James Shubin
5cc960527e lang: funcs: Differentiate between empty and nil values
It would be good to differentiate between receiving an empty value or
not having received a value yet. This is similar to the previous commit.
2020-01-03 00:28:54 -05:00
James Shubin
762c53fb8d lang: funcs: Send empty values when appropriate
I seem to have forgotten to differentiate between the empty string and
no data because the zero value for the stored result was the empty
string. This turns it into a pointer so that we don't block the function
engine if a template or one of the other patched functions sends an
empty string as the first value.
2019-12-30 12:35:08 -05:00
James Shubin
ff20e67d07 lang: funcs: Improve template function
Template function should be able to be called with just one arg (no
input vars) and we should correctly use the known arg name and not the
string "a" or "b".
2019-12-30 11:21:43 -05:00
Francois Rompre-Lanctot
c0cea013d1 pgraph: Add test for SetValue function 2019-12-18 20:00:47 -05:00
James Shubin
5526bbba64 engine: resources: Add a tftp server and tftp file resource
This adds a tftp server and tftp file resource to help you run a small
pure golang tftp server embedded inside the mgmt resource model.
2019-12-17 03:41:45 -05:00
James Shubin
f0aa96ea8c etcd: Remove the capnslog stuff and switch to zap
Unfortunately, this doesn't give us a way to pass in our own logger
function, and afaict by reading the source, it's not possible because
the necessary methods are private. In any case, this is left as a future
exercise.
2019-12-17 03:40:44 -05:00
James Shubin
e73007c398 etcd: Bump to new 3.4.x version
This moves to the newest etcd release, and also updates the imports to
the new go.etcd.io path. I think this is a bit of a pain, but might as
well get it done.
2019-12-17 02:45:38 -05:00
Jonathan Gold
fdc459ec5b vagrant: Update Vagrantfile
This patch updates the Vagrantfile to Fedora 31, and updates the
install process to match the quick start guide.
2019-11-17 20:25:06 -05:00
Felix Frank
bdb523ece1 lang: funcs: funcgen: Suppress informational messages
Send non-error log messages to stdout rather than stderr. Any messages
outside the main function are expected to be purely informational. By
sending to stdout rather than stderr, they can be discarded during the
build.

Fixes #568
2019-11-17 20:21:52 -05:00
James Shubin
164a9479ad test: Add a new test to the commit message checking
Fix the missing "s" bug.
2019-11-17 20:21:52 -05:00
James Shubin
e18adc781f git: Add a simple gitignore helper
Run `touch 1 && chmod ugo-w 1` to prevent silly scripting bugs from
running without being caught.
2019-11-12 17:33:58 -05:00
Felix Frank
33d89c2739 examples: lib: Update code for urfave/cli v2 2019-11-12 21:07:38 +01:00
Felix Frank
7cc9ab9083 lib: Update for urfave/cli v2 2019-11-12 21:07:38 +01:00
Donald Bakong
4b4b7dc169 engine: resources: Adding tests to file mode 2019-11-08 10:02:30 -05:00
Yohan Belval
71ad5c5f05 examples: lang: Added os check for pkg example fix 2019-11-06 10:20:32 -05:00
Yohan Belval
39368bb5cb examples: lang: Fixed pkg example with cowsay 2019-11-06 10:03:20 -05:00
James Shubin
7a587ee8d1 misc: mkosi: Switch to copy-git-more
I added a new feature in mkosi which got merged in:

31801e89e77188e697ed937ca6b8668fde4c4a4d

This allows us to pull in all of the git repository so that we can see
the version number that comes from git.
2019-11-02 07:34:04 -04:00
James Shubin
77346527f3 docs: Update style guide with more review items
Hopefully this helps new contributors understand review changes and
avoid making them too!
2019-11-01 22:01:38 -04:00
James Shubin
1eba5833d5 engine: resources: Consistency changes and cleanup for file mode
This makes a few consistency changes and cleanups to the file mode
feature so that it's more in style with the rest of the code base.
2019-11-01 22:01:38 -04:00
Derek Buckley
83a747794e engine: resources: Adds symbolic mode to file resource
Adds a symbolic parsing function to the util package for parsing in the
file resource.
2019-11-01 21:57:10 -04:00
Julien Pivotto
3e16d1da46 engine: resources: Add new consul resource
This commit adds a new consul:kv resource which allows us to set and
watch keys inside a consul kv datastore.

This was started by roidelapluie, and was finished during pair
programming with purpleidea.

Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu>
Signed-off-by: James Shubin <james@shubin.ca>
2019-11-01 21:38:08 -04:00
Julien Pivotto
ae1860e859 lang: funcs: Add datetime.format function
Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu>
2019-11-01 19:26:17 -04:00
Julien Pivotto
2ebc8fdf2a lang: funcs: Add datetime.hour function
Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu>
2019-11-01 20:45:45 +01:00
Julien Pivotto
be4023be66 docs: Update resource-guide.md 2019-11-01 10:03:36 -04:00
Julien Pivotto
7f4ad76298 lang: funcs: Fix autogenerated comments
Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu>
2019-11-01 11:18:48 +01:00
Julien Pivotto
0cbfaf98f3 lang: funcs: Support for []byte
Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu>
2019-10-30 22:14:08 +01:00
James Shubin
631124e658 lang: funcs: Add nitpicks from funcgen
Discussed nitpicks with roidelapluie to clean up slightly for
consistency.
2019-10-30 08:51:11 -04:00
Julien Pivotto
1685ee1ecb lang: funcs: Autogenerated a lot of new functions
Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu>
2019-10-30 08:42:53 -04:00
James Shubin
9b4d11f220 lang: funcs: Move convert into correct folder
This got merged in the wrong folder by accident.
2019-10-30 06:21:34 -04:00
James Shubin
46a71296a9 engine: resources: Add a Purge option to the file resource
This adds a "purge" parameter to the file resource. To do this, we have
to add the API hooks so the file resource can query other resources in
the graph to know if they are present, and as a result whether they
should be excluded from the purge or not.

This is useful for when we have a managed directory with some managed
contents. If a managed file is removed from the directory, then it will
be removed by the file (directory) resource if it has Purge set.
Alternatively, you can use the Reverse meta param, which is sometimes
preferable for this use case and sometimes not. This will be discussed
elsewhere.

This also adds a bunch of tests for this feature.

This also makes a few somewhat related cleanups in the file code.
2019-10-29 11:54:08 -04:00
James Shubin
1285588b62 engine: resources: Fix file absent helper
We should check this nil case too.
2019-10-29 07:15:43 -04:00
James Shubin
d96392f65e engine: resources: Improve error message in test
Nothing major, just improve testing here.
2019-10-29 07:15:43 -04:00
James Shubin
d1c5a736ae engine: resources: Allow nil helper functions in tests
This reduces some of the boilerplate when writing resource tests.
2019-10-29 07:15:43 -04:00
James Shubin
6b1e038c5c engine: resources: Add file exists helper
This will allow us to test even more things!
2019-10-29 07:15:43 -04:00
James Shubin
eaab1aae28 engine: graph, resources: Add filtered graph function
This lets a resource query the resource graph in a controlled way.
2019-10-29 07:15:43 -04:00
James Shubin
31030343a2 engine: Add graph queryable trait
This is a trait that specifies that your resource can be queried by
others. You can either enable it plainly to add wholesale access by
everyone, or you can implement your own Allow method to limit what is
permitted.
2019-10-29 07:15:43 -04:00
James Shubin
325ca03a13 engine: graph: Pass through the graph struct
We want to use it in the resources.
2019-10-29 07:15:43 -04:00
James Shubin
dea8e63df2 util: Add more tests to HasPathPrefix
We need to ensure this behaviour in a future commit, so might as well
double check that this works as expected!
2019-10-29 07:15:43 -04:00
James Shubin
58421fd31a engine: resources: Add fragments support to file resource
This adds a "fragments" mode to the file resource. In addition to
"content" and "source", you can now alternatively build a file from the
file fragments of other files.
2019-10-29 07:15:43 -04:00
James Shubin
b961c96862 lang: Include automatic edges in our test case
When running this test, we didn't attempt to build any automatic edges.
Since we'd like to test this here as well, let's add it.
2019-10-24 04:30:49 -04:00
Donald Bakong
2d23c1b0f3 lang: funcs: Add to_int and to_float functions 2019-10-22 08:34:29 -04:00
James Shubin
06952c224b engine: resources: nspawn: Use populate variable
We referred to the wrong variable. Not a major bug, but would produce a
useless or confusing error message otherwise.
2019-10-21 07:38:42 -04:00
Donald Bakong
2ea492c965 docs: Fix error on language-guide.md 2019-10-19 18:44:56 -04:00
Donald Bakong
dbf84f6879 docs: Fix typo on language-guide.md 2019-10-19 18:44:11 -04:00
James Shubin
0fa3d6c462 github: Update funding information file
Just got added to the GH sponsors thing and there are more fields
available in this file now. Let's hope this works!
2019-10-09 19:56:19 -04:00
James Shubin
d57f7aa03f misc: Specific mkosi fixes for centos-7
Seems we need golang from epel, to mask out the old git version, and to
workaround mkosi bugs.
2019-10-05 01:49:42 -04:00
Jimmy Tang
d64f9f5401 misc: Add CentOS 7 rpm build 2019-10-04 23:47:12 -04:00
James Shubin
a3029afc41 art: Rename art file to clarify it's about Winnie the Pooh
Pretty cool that we have our first meme =D Have a look in the FAQ to see
the real reason we watch our resources. (TL;DR: It allows us to make
graph changes very fast.)
2019-10-04 08:01:23 -04:00
James Shubin
6a7d904fae misc: Improve tagging script
This way we can push the tag *after* all the builds succeed. If
something goes wrong, we can always delete our local tag and try again.
2019-10-04 06:49:04 -04:00
James Shubin
d4043d3f86 misc, make: Add full file path into fpm script
This is needed for our fancier, unique file names.
2019-10-04 06:43:17 -04:00
James Shubin
b4902a4f58 make: Add a unique token to the package file name
This unique token is necessary so that storing the files in the same dir
(basically a GitHub release) or in the SHA1SUMS file doesn't cause a
conflict.
2019-10-04 06:06:44 -04:00
James Shubin
ffe402f201 misc: Add fedora-30 mkosi+fpm build environment
Good example of how to add a new distro or version.
2019-10-04 06:02:08 -04:00
James Shubin
09cc7da282 misc: Add proper archlinux prefix in build script 2019-10-04 06:01:23 -04:00
James Shubin
2d2dad41f4 todo: Update the TODO file so that it has a sane purpose
We stored some stuff in GitHub, and some stuff here. We can keep using
this, but let's do it for the stuff that hasn't changed in a while.
2019-10-04 04:11:26 -04:00
bjanssens
5f7c0a86dd art: Add the requested art
Signed-off-by: bjanssens <bjanssens@inuits.eu>
2019-10-04 09:23:20 +02:00
Donald Bakong
fc1c631c98 engine: resources: Change Res API from Compare to Cmp
This will be done by refactoring the current method, to return an error
message instead of a boolean value. This will also update a typo on the
user res.
2019-09-27 18:10:58 -04:00
James Shubin
89bdafacb8 misc: Refactor Makefile slightly
We could make this even better in the future with lists.
2019-09-23 06:57:26 -04:00
James Shubin
73b6b3f129 misc: Remove old image building cruft 2019-09-23 06:50:26 -04:00
James Shubin
b2a495f593 misc: Add mkosi target for ubuntu bionic
The name of these is pretty weird.
2019-09-23 06:50:00 -04:00
James Shubin
65ee904377 misc: Work around old golang in ubuntu
Hopefully this helps.
2019-09-23 06:48:55 -04:00
James Shubin
13f59230b5 misc: Split Makefile PHONY target into multiple lines
AIUI this is valid make. Please correct me if I'm wrong.
2019-09-23 06:48:55 -04:00
James Shubin
36d2a0de1e misc: Make mkosi building suitable for different distro versions
We'd like to be able to build both Fedora N and N-1 at the same time if
possible. This makes it more generally applicable for this scenario, as
well as for other distros.
2019-09-23 06:48:55 -04:00
James Shubin
a4db9fc8e5 misc: Add mkosi based package building with fpm
Building distro packages is great, however if they aren't built in the
correct environment with the associated dependencies, then they won't
work properly on those distros.

This patch adds an `mkosi` based image building environment that builds
the packages in their respective distros, and then copies them out into
our releases directory.

You'll now want to `make tag && make mkosi && make release` to get a new
release out. We use a small hack to trick the `make release` portion to
not re-build the distro packages if they're already present in the
releases/ directory for that version.

This commit depends on a very recent version of mkosi (it was tested
with git master) and also depends on two currently unmerged patches:
https://github.com/systemd/mkosi/pull/363 and
https://github.com/systemd/mkosi/pull/365
2019-09-20 12:32:41 -04:00
James Shubin
9dae5ef83b engine: resources: Improve the file res and add strict state
This might be slightly controversial, in that you must specify the state
if a file would need to be created to perform the action. We no longer
implicitly assume that just specifying content is enough. As it turns
out, I believe this is safer and more correct. The code to implement
this turns out to be much more logical and simplified, and does this
removes an ambiguous corner case from the reversed resource code.

Some discussion in: https://github.com/purpleidea/mgmt/issues/540

This patch also does a bit of related cleanup.
2019-09-14 16:07:53 -04:00
James Shubin
e8842a740c lang: Remove duplicate log message
Looks like we had two copies of the same message by accident.
2019-09-11 04:26:15 -04:00
James Shubin
0d3807ad09 lang, test: Fix copy paste error with log message
This changes this to the correct error message.
2019-09-11 04:26:15 -04:00
James Shubin
5c27a249b7 engine: resources: Add reversible API and file resource
This adds the first reversible resource (file) and the necessary engine
API hooks to make it all work. This allows a special "reversed" resource
to be added to the subsequent graph in the stream when an earlier
version "disappears". This disappearance can happen if it was previously
in an if statement that then becomes false.

It might be wise to combine the use of this meta parameter with the use
of the `realize` meta parameter to ensure that your reversed resource
actually runs at least once, if there's a chance that it might be gone
for a while.

This patch also adds a new test harness for testing resources. It
doesn't test the "live" aspect of resources, as it doesn't run Watch,
but it was designed to ensure CheckApply works as intended, and it runs
very quickly with a simplified timeline of happenings.
2019-09-11 03:40:22 -04:00
James Shubin
7e41860b28 docs: Add missing docs on the rewatch and realize meta params
Sometimes it's hard to keep this in sync.
2019-09-11 03:40:22 -04:00
James Shubin
43ff92bbe7 engine: resources: Clean up test log message 2019-09-11 03:40:22 -04:00
James Shubin
28adc7e563 engine: resource: Refactor helper functions
Maybe we can use them in other tests too.
2019-09-11 03:40:22 -04:00
James Shubin
9788411995 engine: resources: Add another validation check
This simple check should prevent some silly mistakes and make the logic
easier for other parts of the code that won't have to worry about this
pattern.
2019-09-11 03:40:22 -04:00
James Shubin
0c9e8cc50e engine: resources: Change the default file state
The default file state should be undefined. This is important because if
a reverse scenario that doesn't specify the state gets given this
default, it will be as if it was specified explicitly, which wouldn't
necessarily be what we want. Instead, an undefined state should
implicitly cause a file to get created if there's a reason to do so,
such as if content or another attribute is specified.

Hopefully this change doesn't introduce any bugs in the CheckApply code,
if it does, then it was due to a lack of implicit file creation.
2019-09-11 03:16:57 -04:00
James Shubin
34d572c523 engine: Improve the way we make a unique res path token
This is needed in the state directory.
2019-09-11 03:16:57 -04:00
James Shubin
011b496b3f engine: resources: Ensure the Kind and Name methods work
Triple check these work after decoding, by adding a test.
2019-09-11 03:16:57 -04:00
James Shubin
12b906eac6 engine: Refactor state dir into a separate function
This lets us re-use it, and know the path is fixed.
2019-09-11 03:16:57 -04:00
James Shubin
20937d05c3 engine: resources: file: Add undefined file state and validate it
We should consider using *string instead of the empty string, but let's
keep the diff smaller for now.
2019-09-11 03:16:57 -04:00
James Shubin
4943d37ccf engine: resources: file: Use constants for state values
More robustness is yay!
2019-09-11 03:16:57 -04:00
James Shubin
3a8fd215de engine: resources: file: Add Copy method to file res
This lets us implement the CopyableRes interface.
2019-09-11 03:16:57 -04:00
James Shubin
87572e8922 test: Catch capitalized error messages in tests 2019-09-06 03:28:49 -04:00
James Shubin
f1eedc7a01 lang: Clarify error message about missing field
User probably just mistyped a field name. Make that clear.
2019-09-06 03:28:49 -04:00
Donald Bakong
b79e48dd77 docs: Fix typo on quick-start-guide.md 2019-08-25 22:53:43 -04:00
James Shubin
18872194af misc: Warn users with weird computers
A user seemed to experience a weird golang issue when they had deps from
both package managers installed. I won't block or fail their install,
but we can print a warning message so that someone sees it in their
logs.
2019-08-23 22:10:34 -04:00
James Shubin
bafd7ba282 misc: Use apt install of apt-get where possible
The future is now!
2019-08-23 22:10:12 -04:00
Donald Bakong
b186481181 pgraph: Add a test for FindEdge() function 2019-08-08 00:43:26 -04:00
James Shubin
09ca6d11ad lang: funcs: Module name should be public
For consistency with the rest of the core functions.
2019-07-29 11:17:43 -04:00
James Shubin
e68e4e786d docs: Add newly recorded talks and blog post 2019-07-26 06:52:01 -04:00
James Shubin
ee638254c3 lang: Remove the specialized info structs
Since this was an early form of the modern data struct, remove those and
pass in the correct data. This is also important in case we have
something more complex inside our string interpolation!
2019-07-26 04:20:04 -04:00
James Shubin
1e678905c4 util: Fix typo 2019-07-26 04:20:04 -04:00
James Shubin
10804c4b25 lang: Improve the gapi copying
We hit a weird bug where dirs would not get copied properly. I thought
the solution might be to add the missing dirs so they'd get a proper
mkdir, but in the end that didn't work well, so we just use `mkdirall`
and that seems to work. Let's leave it like this for now. Some of the
previous work for that is in the previous commit.
2019-07-26 04:20:04 -04:00
James Shubin
4bf9b4d41b util: Add some path helper functions
In the end, I'm not sure how useful these will be, but we'll keep them
in for now.
2019-07-26 04:20:04 -04:00
James Shubin
1161872324 etcd: fs: Errors should start with lower case 2019-07-26 04:20:04 -04:00
James Shubin
98cb570896 util: Add new mkdirall variants for the copy functions
This adds versions that recursively `mkdir` and all don't error as
easily. This works around some bugs we were having with file copying.
2019-07-26 04:20:04 -04:00
James Shubin
ed4ee3b58e lang: funcs: Add deploy package with readfile related functions
This adds a readfile function to actually access files from our deploy.
A fun side effect is that we can even access our own code! In general,
it's a good reminder that you should only run trusted code on your own
infrastructure. This also includes a fancy new test case.
2019-07-26 03:38:26 -04:00
James Shubin
066048f4de lang: Pass through the Fs and the FsURI
This should give us options as to how a function should interact with an
FS. I feel like it's cleaner to go through the World API, and passing in
the FsURI lets us do that, but I passed in the Fs at the same time in
case it's useful for some reason. I think using it is a boundary
violation, but it's just a hunch. Does anything break when we move from
one deploy to the next?
2019-07-26 03:07:08 -04:00
James Shubin
4b6b91c08b lang: Make sure to call Init for functions that arrive via import
We weren't calling Init on some functions which should have had this
done. I'm not sure whether this is the right place, or if it should be
elsewhere as part of the scope building process. Good enough for now.
2019-07-22 06:49:02 -04:00
James Shubin
2980523a5b lang: Add a new function interface to accept data
Sometimes certain internal functions might want to get some data from
the AST or from something relating to the state of the language. This
adds a method to pass in that data. For now it's a very simple method,
but we could generalize it in the future if it becomes more useful.
2019-07-22 06:46:04 -04:00
James Shubin
f2f9c043bf lang, gapi: Work around a copy bug in the deploy
It seems when we had a files/ dir that we added to our deploy, it would
get copied into /files/files/whatever instead of /files/whatever where
it should be. Hopefully this works around the issue forever.
2019-07-22 06:40:47 -04:00
James Shubin
5d59cfd2c9 util: Ensure the afero copy function is working as intended
The destination should be a dir sometimes.
2019-07-22 06:38:02 -04:00
James Shubin
f94474e24f lang: Add the world implementation to our test suite
This allows our tests to actual run the World API in them.
2019-07-22 06:36:37 -04:00
James Shubin
a63fc6d9ba util: Add a remove path suffix util function
This pairs with a similar one we already had.
2019-07-22 06:35:13 -04:00
James Shubin
076adeef80 lang: funcs: Fix a copypasta error with the not equals operator
Woops, sorry!
2019-07-22 06:08:37 -04:00
James Shubin
a0e756317c lang: Add tests for slow unification
These used to be cases where our algorithm was unusably slow.

Thanks to foxxx0 for the report!
2019-07-21 03:15:06 -04:00
James Shubin
252cb5f2f3 lang: Detect windows style CR and return a better error
If you get a sneaky \r in your code, the error just looks like
whitespace, so this way we can warn you explicitly.
2019-07-21 03:10:21 -04:00
James Shubin
64288b4914 lang, test: Inline some overly indented tests
Sometimes you're busy hacking and it's nice for future you to fix up
your code!
2019-07-21 01:19:15 -04:00
James Shubin
9ca6c6a315 test: Split up long tests into multiple sub tests again
I think we need this for non --race tests too.
2019-07-21 00:55:36 -04:00
James Shubin
3651ab5c0c lang: Add more tests for function 2019-07-20 22:27:21 -04:00
James Shubin
b3f15e1ddc lang: Add more tests for class and include 2019-07-20 01:33:42 -04:00
1120 changed files with 68468 additions and 17132 deletions

3
.ackrc
View File

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

View File

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

5
.github/FUNDING.yml vendored
View File

@@ -1,2 +1,5 @@
# You can add one username per supported platform and one custom link
# You can add one username per supported platform and one custom link.
custom: "https://paypal.me/purpleidea"
github: purpleidea
liberapay: purpleidea
patreon: purpleidea

View File

@@ -68,6 +68,8 @@ labels:
color: e11d21
- name: question
color: cc317c
- name: needinfo
color: fbca04
- name: wontfix
color: ffffff
# - name: first-timers-only

68
.github/workflows/test.yaml vendored Normal file
View File

@@ -0,0 +1,68 @@
# Docs: https://help.github.com/en/articles/workflow-syntax-for-github-actions
# If the name is omitted, it uses the filename instead.
#name: Test
on:
# Run on all pull requests.
pull_request:
#branches:
#- master
# Run on all pushes.
push:
# Run daily at 4am.
schedule:
- cron: 0 4 * * *
jobs:
maketest:
name: Test (${{ matrix.test_block }}) on ${{ matrix.os }} with golang ${{ matrix.golang_version }}
runs-on: ${{ matrix.os }}
env:
GOPATH: /home/runner/work/mgmt/mgmt/go
strategy:
matrix:
# TODO: Add tip when it's supported: https://github.com/actions/setup-go/issues/21
os:
- ubuntu-latest
# macos tests are currently failing in CI
#- macos-latest
golang_version:
# TODO: add 1.21.x and tip
# minimum required and latest published go_version
- "1.20"
test_block:
- basic
- shell
- race
fail-fast: false
steps:
# Do not shallow fetch. The path can't be absolute, so we need to move it
# to the expected location later.
- name: Clone mgmt
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
path: ./go/src/github.com/purpleidea/mgmt
- name: Install Go ${{ matrix.golang_version }}
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.golang_version }}
# Install & configure ruby, fixes gem permissions error
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: head
- name: Install dependencies
working-directory: ./go/src/github.com/purpleidea/mgmt
run: |
make deps
- name: Run test
working-directory: ./go/src/github.com/purpleidea/mgmt
run: |
TEST_BLOCK="${{ matrix.test_block }}" make test

3
.gitignore vendored
View File

@@ -7,7 +7,6 @@ old/
tmp/
*WIP
*_stringer.go
bindata/*.go
mgmt
mgmt.static
# crossbuild artifacts
@@ -17,3 +16,5 @@ rpmbuild/
releases/
# vim swap files
.*.sw[op]
# prevent `echo foo 2>1` typo errors by making this file read-only
1

33
.gitmodules vendored
View File

@@ -1,33 +0,0 @@
[submodule "vendor/github.com/coreos/etcd"]
path = vendor/github.com/coreos/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/purpleidea/go-connections"]
path = vendor/github.com/docker/go-connections
url = https://github.com/docker/go-connections

View File

@@ -24,21 +24,21 @@ install: 'make deps'
matrix:
fast_finish: false
allow_failures:
- go: 1.12.x
- go: 1.21.x
- go: tip
- os: osx
# include only one build for osx for a quicker build as the nr. of these runners are sparse
include:
- name: "basic tests"
go: 1.11.x
go: 1.20.x
env: TEST_BLOCK=basic
- name: "shell tests"
go: 1.11.x
go: 1.20.x
env: TEST_BLOCK=shell
- name: "race tests"
go: 1.11.x
go: 1.20.x
env: TEST_BLOCK=race
- go: 1.12.x
- go: 1.21.x
- go: tip
- os: osx
script: 'TEST_BLOCK="$TEST_BLOCK" make test'
@@ -47,8 +47,8 @@ script: 'TEST_BLOCK="$TEST_BLOCK" make test'
# with a value of: irc.freenode.net#mgmtconfig to eliminate noise from forks...
notifications:
irc:
channels:
- secure: htcuWAczm3C1zKC9vUfdRzhIXM1vtF+q0cLlQFXK1IQQlk693/pM30Mmf2L/9V2DVDeps+GyLdip0ARXD1DZEJV0lK+Ca1qbHdFP1r4Xv6l5+jaDb5Y88YU5LI8K758QShiZJojuQ1aO2j8xmmt9V0/5y5QwlpPeHbKYBOFPBX3HvlT9DhvwZNKGhBb4qJOEaPVOwq9IkN3DyQ456MHcJ3q3vF9Lb440uTuLsJNof2AbYZH8ZIHCSG2N8tBj2qhJOpWQboYtQJzE2pRaGkGBL4kYcHZSZMXX8sl4cBM1vx/IRUkvBxJUpLJz2gn/eRI+/gr59juZE2K0+FOLlx9dLnX626Y9xSViopBI6JsIoHJDqNC7aGaF2qaYulGYN65VNKVqmghjgt6JLmmiKeH10hYrJMMvt2rms8l4+5iwmCwXvhH/WU9edzk2p5wqERMnostJFEJib0zI3yzLoF0sdJs+veKtagzfayY2d2l7hlmt951IpqqVWldVgWUcQKVvi8gmRarbwFlK+5D7BEnkUDcLNly/cqf7BgEeX6YfF+FiR4pgfOhYvGCD+2q91NgWQXHBCxbyN0be1TVdkXD94f0Lkn94VyEJJ+PkPlG+rPgFwGcjqN4oEGkJeJmES2If05q2Ms1dJLwYQDL3+Py4lNMSdSWj24TzlFVhtwHepuw=
#channels:
# - secure: htcuWAczm3C1zKC9vUfdRzhIXM1vtF+q0cLlQFXK1IQQlk693/pM30Mmf2L/9V2DVDeps+GyLdip0ARXD1DZEJV0lK+Ca1qbHdFP1r4Xv6l5+jaDb5Y88YU5LI8K758QShiZJojuQ1aO2j8xmmt9V0/5y5QwlpPeHbKYBOFPBX3HvlT9DhvwZNKGhBb4qJOEaPVOwq9IkN3DyQ456MHcJ3q3vF9Lb440uTuLsJNof2AbYZH8ZIHCSG2N8tBj2qhJOpWQboYtQJzE2pRaGkGBL4kYcHZSZMXX8sl4cBM1vx/IRUkvBxJUpLJz2gn/eRI+/gr59juZE2K0+FOLlx9dLnX626Y9xSViopBI6JsIoHJDqNC7aGaF2qaYulGYN65VNKVqmghjgt6JLmmiKeH10hYrJMMvt2rms8l4+5iwmCwXvhH/WU9edzk2p5wqERMnostJFEJib0zI3yzLoF0sdJs+veKtagzfayY2d2l7hlmt951IpqqVWldVgWUcQKVvi8gmRarbwFlK+5D7BEnkUDcLNly/cqf7BgEeX6YfF+FiR4pgfOhYvGCD+2q91NgWQXHBCxbyN0be1TVdkXD94f0Lkn94VyEJJ+PkPlG+rPgFwGcjqN4oEGkJeJmES2If05q2Ms1dJLwYQDL3+Py4lNMSdSWj24TzlFVhtwHepuw=
template:
- "%{repository} (%{commit}: %{author}): %{message}"
- "More info : %{build_url}"

View File

@@ -6,7 +6,9 @@ This list is sorted alphabetically by first name.
Felix Frank
James Shubin
Joe Groocock
Johan Bloemberg
Jonathan Gold
Julien Pivotto
Paul Morgan
Samuel Gélineau

View File

@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
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
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.
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.
@@ -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,
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
<http://www.gnu.org/licenses/>.
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
<https://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -1,5 +1,5 @@
Mgmt
Copyright (C) 2013-2019+ 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
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.
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.

331
Makefile
View File

@@ -1,5 +1,5 @@
# Mgmt
# Copyright (C) 2013-2019+ 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
#
# This program is free software: you can redistribute it and/or modify
@@ -13,15 +13,31 @@
# 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/>.
# 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
.PHONY: all art cleanart version program lang path deps run race bindata generate build build-debug crossbuild clean test gofmt yamlfmt format docs rpmbuild mkdirs rpm srpm spec tar upload upload-sources upload-srpms upload-rpms upload-releases copr tag release funcgen
.SILENT: clean bindata
.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: mkosi mkosi_fedora-latest mkosi_fedora-older mkosi_stream-latest mkosi_debian-stable mkosi_ubuntu-latest mkosi_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
.SILENT: clean
# 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/*')
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))
VERSION := $(or $(VERSION),$(shell git describe --match '[0-9]*\.[0-9]*\.[0-9]*' --tags --abbrev=0))
@@ -32,6 +48,13 @@ ifeq ($(VERSION),$(SVERSION))
else
RELEASE = untagged
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)
SPEC = rpmbuild/SPECS/$(PROGRAM).spec
SOURCE = rpmbuild/SOURCES/$(PROGRAM)-$(VERSION).tar.bz2
@@ -49,9 +72,68 @@ GOOSARCHES ?= linux/amd64 linux/ppc64 linux/ppc64le linux/arm64 darwin/amd64
GOHOSTOS = $(shell go env GOHOSTOS)
GOHOSTARCH = $(shell go env GOHOSTARCH)
RPM_PKG = releases/$(VERSION)/rpm/mgmt-$(VERSION)-1.x86_64.rpm
DEB_PKG = releases/$(VERSION)/deb/mgmt_$(VERSION)_amd64.deb
PACMAN_PKG = releases/$(VERSION)/pacman/mgmt-$(VERSION)-1-x86_64.pkg.tar.xz
# The underscores separate the prefix name ("TOKEN") the distro ("BINARY",
# "FEDORA-LATEST", etc...) and the arch ("AMD64"). The distro can have a dash.
TOKEN_BINARY_AMD64 = $(shell grep -v '#' releases/binary-amd64.release)
TOKEN_BINARY_ARM64 = $(shell grep -v '#' releases/binary-arm64.release)
TOKEN_FEDORA-LATEST = $(shell grep -v '#' releases/fedora-latest.release)
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_BINARY_AMD64 = mgmt-linux-amd64-$(VERSION)
FILE_BINARY_ARM64 = mgmt-linux-arm64-$(VERSION)
# TODO: add ARCH onto these at the end, eg _AMD64
FILE_FEDORA-LATEST = mgmt-$(TOKEN_FEDORA-LATEST)-$(VERSION)-1.x86_64.rpm
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
PKG_BINARY_AMD64 = releases/$(VERSION)/$(TOKEN_BINARY_AMD64)/$(FILE_BINARY_AMD64)
PKG_BINARY_ARM64 = releases/$(VERSION)/$(TOKEN_BINARY_ARM64)/$(FILE_BINARY_ARM64)
PKG_FEDORA-LATEST = releases/$(VERSION)/$(TOKEN_FEDORA-LATEST)/$(FILE_FEDORA-LATEST)
PKG_FEDORA-OLDER = releases/$(VERSION)/$(TOKEN_FEDORA-OLDER)/$(FILE_FEDORA-OLDER)
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)
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_ASC = $(SHA256SUMS).asc
@@ -116,11 +198,6 @@ run: ## run mgmt
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)"
# 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:
go generate
@@ -132,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
cp -a $< $@
$(PROGRAM).static: $(GO_FILES) $(MCL_FILES)
$(PROGRAM).static: $(GO_FILES) $(MCL_FILES) go.mod go.sum
@echo "Building: $(PROGRAM).static, version: $(SVERSION)..."
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: $(PROGRAM)
@@ -147,26 +224,23 @@ build-debug: $(PROGRAM)
# extract os and arch from target pattern
GOOS=$(firstword $(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)..."
@# reassigning GOOS and GOARCH to make build command copy/pastable
@# go 1.10+ requires specifying the package for ldflags
@if go version | grep -qE 'go1.9'; then \
time env GOOS=${GOOS} GOARCH=${GOARCH} go build -i -ldflags "-X main.program=$(PROGRAM) -X main.version=$(SVERSION) ${LDFLAGS}" -o $@ $(BUILD_FLAGS); \
else \
time env GOOS=${GOOS} GOARCH=${GOARCH} go build -i -ldflags=$(PKGNAME)="-X main.program=$(PROGRAM) -X main.version=$(SVERSION) ${LDFLAGS}" -o $@ $(BUILD_FLAGS); \
fi
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
# 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: ${crossbuild_targets}
clean: ## clean things up
$(MAKE) --quiet -C bindata clean
$(MAKE) --quiet -C lang/funcs clean
$(MAKE) --quiet -C test clean
$(MAKE) --quiet -C lang clean
rm -f lang/funcs/core/generated_funcs.go || true
rm -f lang/funcs/core/generated_funcs_test.go || true
$(MAKE) --quiet -C misc/mkosi clean
rm -f lang/core/generated_funcs.go || true
rm -f lang/core/generated_funcs_test.go || true
[ ! -e $(PROGRAM) ] || rm $(PROGRAM)
rm -f *_stringer.go # generated by `go generate`
rm -f *_mock.go # generated by `go generate`
@@ -174,6 +248,8 @@ clean: ## clean things up
rm -f build/mgmt-*
test: build ## run tests
@# recursively run make in child dir named test
@$(MAKE) --quiet -C test
./test.sh
# create all test targets for make tab completion (eg: make test-gofmt)
@@ -343,18 +419,105 @@ copr: upload-srpms ## build in copr
tag: ## tags a new release
./misc/tag.sh
#
# 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-latest: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
mkosi_fedora-older: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
mkosi_stream-latest: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
mkosi_debian-stable: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
mkosi_ubuntu-latest: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
mkosi_archlinux: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
#
# release
#
release: TRIMPATH = -trimpath
release: releases/$(VERSION)/mgmt-release.url ## generates and uploads a release
releases/$(VERSION)/mgmt-release.url: $(RPM_PKG) $(DEB_PKG) $(PACMAN_PKG) $(SHA256SUMS_ASC)
releases_path:
@#Don't put any other output or dependencies in here or they'll show!
@echo "releases/$(VERSION)/"
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)
@echo '$$< denotes the first dependency of the current rule.'
@echo '> '"$<"
@echo
@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)
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..."
git push origin $(VERSION)
@echo "Creating github release..."
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." ) \
-a $(RPM_PKG) \
-a $(DEB_PKG) \
-a $(PACMAN_PKG) \
` [ -e $(PKG_BINARY_AMD64) ] && printf -- "-a $(PKG_BINARY_AMD64)" ` \
` [ -e $(PKG_BINARY_ARM64) ] && printf -- "-a $(PKG_BINARY_ARM64)" ` \
` [ -e $(PKG_FEDORA-LATEST) ] && printf -- "-a $(PKG_FEDORA-LATEST)" ` \
` [ -e $(PKG_FEDORA-OLDER) ] && printf -- "-a $(PKG_FEDORA-OLDER)" ` \
` [ -e $(PKG_STREAM-LATEST) ] && printf -- "-a $(PKG_STREAM-LATEST)" ` \
` [ -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) \
$(VERSION) \
> releases/$(VERSION)/mgmt-release.url \
@@ -362,32 +525,92 @@ releases/$(VERSION)/mgmt-release.url: $(RPM_PKG) $(DEB_PKG) $(PACMAN_PKG) $(SHA2
|| rm -f releases/$(VERSION)/mgmt-release.url
releases/$(VERSION)/.mkdir:
mkdir -p releases/$(VERSION)/{deb,rpm,pacman}/ && 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)/rpm/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
@echo "Generating: rpm changelog..."
./misc/make-rpm-changelog.sh $(VERSION)
# 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)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
$(RPM_PKG): releases/$(VERSION)/rpm/changelog
@echo "Building: rpm package..."
./misc/fpm-pack.sh rpm $(VERSION) libvirt-devel augeas-devel
$(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)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_FEDORA-LATEST)" libvirt-devel augeas-devel
endif
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)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
releases/$(VERSION)/deb/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
@echo "Generating: deb changelog..."
./misc/make-deb-changelog.sh $(VERSION)
$(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)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_FEDORA-OLDER)" libvirt-devel augeas-devel
endif
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)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
$(DEB_PKG): releases/$(VERSION)/deb/changelog
@echo "Building: deb package..."
./misc/fpm-pack.sh deb $(VERSION) libvirt-dev libaugeas-dev
$(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)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_STREAM-LATEST)" libvirt-devel augeas-devel
endif
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)/'} ; ./misc/make-deb-changelog.sh "$${distro}" $(VERSION)
$(PACMAN_PKG): $(PROGRAM) releases/$(VERSION)/.mkdir
@echo "Building: pacman package..."
./misc/fpm-pack.sh pacman $(VERSION) libvirt augeas
$(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)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_DEBIAN-STABLE)" libvirt-dev libaugeas-dev
endif
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)/'} ; ./misc/make-deb-changelog.sh "$${distro}" $(VERSION)
$(SHA256SUMS): $(RPM_PKG) $(DEB_PKG) $(PACMAN_PKG)
$(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)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_UBUNTU-LATEST)" libvirt-dev libaugeas-dev
endif
ifneq ($(TOKEN_ARCHLINUX),)
$(PKG_ARCHLINUX): $(PROGRAM) releases/$(VERSION)/.mkdir
@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
endif
$(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
@echo "Generating: sha256 sum..."
sha256sum $(RPM_PKG) $(DEB_PKG) $(PACMAN_PKG) | 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)
@echo "Signing sha256 sum..."
@@ -413,14 +636,10 @@ help: ## show this help screen
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
@echo ''
funcgen: lang/funcs/core/generated_funcs_test.go lang/funcs/core/generated_funcs.go
funcgen: lang/core/generated_funcs.go
lang/funcs/core/generated_funcs_test.go: lang/funcs/funcgen/*.go lang/funcs/core/funcgen.yaml lang/funcs/funcgen/templates/generated_funcs_test.go.tpl
@echo "Generating: funcs test..."
@go run lang/funcs/funcgen/*.go -templates lang/funcs/funcgen/templates/generated_funcs_test.go.tpl 2>/dev/null
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..."
@go run lang/funcs/funcgen/*.go -templates lang/funcs/funcgen/templates/generated_funcs.go.tpl 2>/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
# vim: ts=8

View File

@@ -3,9 +3,10 @@
[![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)
[![Build Status](https://img.shields.io/travis/purpleidea/mgmt/master.svg?style=flat-square)](http://travis-ci.org/purpleidea/mgmt)
[![GoDoc](https://img.shields.io/badge/godoc-reference-5272B4.svg?style=flat-square)](https://godoc.org/github.com/purpleidea/mgmt)
[![IRC](https://img.shields.io/badge/irc-%23mgmtconfig-orange.svg?style=flat-square)](https://webchat.freenode.net/?channels=#mgmtconfig)
[![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://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)
[![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)
@@ -21,7 +22,7 @@ ensure that your file server is set to read-only when it's friday.
import "datetime"
$is_friday = datetime.weekday(datetime.now()) == "friday"
file "/srv/files/" {
state => "exists",
state => $const.res.file.state.exists,
mode => if $is_friday { # this updates the mode, the instant it changes!
"0550"
} else {
@@ -65,11 +66,11 @@ Come join us in the `mgmt` community!
| Medium | Link |
|---|---|
| IRC | [#mgmtconfig](https://webchat.freenode.net/?channels=#mgmtconfig) on Freenode |
| Matrix | [#mgmtconfig](https://matrix.to/#/#mgmtconfig:matrix.org) on Matrix.org |
| IRC | [#mgmtconfig](https://web.libera.chat/?channels=#mgmtconfig) on Libera.Chat |
| 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 |
| Liberapay | [purpleidea](https://liberapay.com/purpleidea/donate) on Liberapay |
## Status:
@@ -97,8 +98,9 @@ Please read, enjoy and help improve our documentation!
| [style guide](docs/style-guide.md) | for mgmt developers |
| [godoc API reference](https://godoc.org/github.com/purpleidea/mgmt) | for mgmt developers |
| [prometheus guide](docs/prometheus.md) | for everyone |
| [puppet guide](docs/puppet-guide.md) | for puppet sysadmins |
| [development](docs/development.md) | for mgmt developers |
| [videos](docs/on-the-web.md) | for everyone |
| [blogs](docs/on-the-web.md) | for everyone |
## Questions:
@@ -107,12 +109,13 @@ If you have a well phrased question that might benefit others, consider asking
it by sending a patch to the [FAQ](docs/faq.md) section. I'll merge your
question, and a patch with the answer!
## Roadmap:
## Get involved:
Feel free to grab one of the straightforward [#mgmtlove](https://github.com/purpleidea/mgmt/labels/mgmtlove)
issues if you're a first time contributor to the project or if you're unsure
about what to hack on! Please get involved by working on one of these items or
by suggesting something else!
by suggesting something else! There are some lower priority issues and harder
issues available in our [TODO](TODO.md) file. Please have a look.
## Bugs:
@@ -126,6 +129,6 @@ We'd love to have your patches! Please send them by email, or as a pull request.
## 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!

65
TODO.md
View File

@@ -1,10 +1,18 @@
# TODO
If you're looking for something to do, look here!
Let us know if you're working on one of the items.
If you'd like something to work on, ping @purpleidea and I'll create an issue
tailored especially for you! Just let me know your approximate golang skill
level and how many hours you'd like to spend on the patch.
Here is a TODO list of longstanding items that are either lower-priority, or
more involved in terms of time, skill-level, and/or motivation.
Please have a look, and let us know if you're working on one of the items. It's
best to open an issue to track your progress and to discuss any implementation
questions you might have.
Lastly, if you'd like something different to work on, please ping @purpleidea
and I'll create an issue tailored especially for your approximate golang skill
level and available time commitment in terms of hours you'd need to spend on the
patch.
Happy Hacking!
## Package resource
@@ -19,7 +27,7 @@ level and how many hours you'd like to spend on the patch.
## Svc resource
- [ ] base resource improvements
- [ ] refreshonly support [:heart:](https://github.com/purpleidea/mgmt/issues/464)
## Exec resource
@@ -33,33 +41,14 @@ level and how many hours you'd like to spend on the patch.
- [ ] automatic edges to file resource [:heart:](https://github.com/purpleidea/mgmt/labels/mgmtlove)
## Virt (libvirt) resource
- [ ] base resource improvements [:heart:](https://github.com/purpleidea/mgmt/labels/mgmtlove)
## Net (systemd-networkd) resource
- [ ] base resource
## Nspawn (systemd-nspawn) resource
- [ ] base resource [:heart:](https://github.com/purpleidea/mgmt/labels/mgmtlove)
## Mount (systemd-mount) resource
- [ ] base resource [:heart:](https://github.com/purpleidea/mgmt/labels/mgmtlove)
## Cron (systemd-timer) resource
- [ ] base resource [:heart:](https://github.com/purpleidea/mgmt/labels/mgmtlove)
## Http resource
- [ ] base resource [:heart:](https://github.com/purpleidea/mgmt/labels/mgmtlove)
## Etcd improvements
- [ ] fix embedded etcd master race
- [ ] fix etcd race bug that only happens during CI testing (intermittently
failing test case issue)
## Torrent/dht file transfer
@@ -69,17 +58,33 @@ level and how many hours you'd like to spend on the patch.
- [ ] base plumbing
## Resource improvements
- [ ] more reversible resources implemented
- [ ] more "cloud" resources
## Language improvements
- [ ] more core functions
- [ ] automatic language formatter, ala `gofmt`
- [ ] gedit/gnome-builder/gtksourceview syntax highlighting
- [ ] vim syntax highlighting
- [x] emacs syntax highlighting: see `misc/emacs/`
- [ ] emacs syntax highlighting: see `misc/emacs/` (needs updating)
- [ ] exposed $error variable for feedback in the language
- [ ] improve the printf function to add %[]s, %[]f ([]str, []float) and map,
struct, nested etc... %v would be nice too!
- [ ] add line/col/file annotations to AST so we can get locations of errors
that the parser finds
- [ ] add more error messages with the `%error` pattern in parser.y
- [ ] we should have helper functions or language sugar to pull a field out of a
struct, or a value out of a map, or an index out of a list, etc...
## Engine improvements
- [ ] add a "waiting for func" message in the func engine to notify the user
about slow functions...
## Other
- [ ] better error/retry handling
- [ ] deb package target in Makefile
- [ ] reproducible builds
- [ ] add your suggestions!

11
Vagrantfile vendored
View File

@@ -6,7 +6,7 @@ Vagrant.configure(2) do |config|
config.vm.synced_folder ".", "/vagrant", disabled: true
config.vm.define "mgmt-dev" do |instance|
instance.vm.box = "fedora/28-cloud-base"
instance.vm.box = "bento/fedora-31"
end
config.vm.provider "virtualbox" do |v|
@@ -23,8 +23,7 @@ Vagrant.configure(2) do |config|
config.vm.provision "file", source: "vagrant/mgmt.bashrc", destination: ".mgmt.bashrc"
config.vm.provision "file", source: "~/.gitconfig", destination: ".gitconfig"
# copied from make-deps.sh (with added git)
config.vm.provision "shell", inline: "dnf install -y libvirt-devel golang golang-googlecode-tools-stringer hg git make gem"
config.vm.provision "shell", inline: "dnf install -y golang git make"
# set up packagekit
config.vm.provision "shell" do |shell|
@@ -39,8 +38,10 @@ Vagrant.configure(2) do |config|
script = <<-SCRIPT
grep -q 'mgmt\.bashrc' ~/.bashrc || echo '. ~/.mgmt.bashrc' >>~/.bashrc
. ~/.mgmt.bashrc
go get -u github.com/purpleidea/mgmt
cd ~/gopath/src/github.com/purpleidea/mgmt
mkdir -p ~/gopath/src/github.com/purpleidea
cd ~/gopath/src/github.com/purpleidea
git clone https://github.com/purpleidea/mgmt --recursive
cd mgmt
make deps
SCRIPT
config.vm.provision "shell" do |shell|

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 683 KiB

BIN
art/mgmt_poohbear_meme.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -1,42 +0,0 @@
# Mgmt
# Copyright (C) 2013-2019+ 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
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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
@@ -29,7 +41,7 @@ import (
)
// New builds a new converger coordinator.
func New(timeout int64) *Coordinator {
func New(timeout int) *Coordinator {
return &Coordinator{
timeout: timeout,
@@ -61,7 +73,7 @@ func New(timeout int64) *Coordinator {
type Coordinator struct {
// timeout must be zero (instant) or greater seconds to run. If it's -1
// then this is disabled, and we never run stateFns.
timeout int64
timeout int
// mutex is used for controlling access to status and lastid.
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
// is useful to avoid passing in the timeout value separately when you're
// already passing in the Coordinator struct.
func (obj *Coordinator) Timeout() int64 {
func (obj *Coordinator) Timeout() int {
return obj.timeout
}
@@ -375,7 +387,7 @@ func (obj *Coordinator) Timeout() int64 {
type UID struct {
// timeout is a copy of the main timeout. It could eventually be used
// for per-UID timeouts too.
timeout int64
timeout int
// isConverged stores the convergence state of this particular UID.
isConverged bool

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,9 +13,21 @@
// 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/>.
// 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

14
debian/copyright vendored
View File

@@ -3,7 +3,7 @@ Upstream-Name: mgmt
Source: <https://github.com/purpleidea/mgmt>
Files: *
Copyright: Copyright (C) 2013-2019+ 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
@@ -19,3 +19,15 @@ License: GPL-3.0
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.

16
doc.go
View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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

View File

@@ -1,10 +1,10 @@
FROM golang:1.9
FROM golang:1.20
MAINTAINER Michał Czeraszkiewicz <contact@czerasz.com>
# Set the reset cache variable
# Read more here: http://czerasz.com/2014/11/13/docker-tip-and-tricks/#use-refreshedat-variable-for-better-cache-control
ENV REFRESHED_AT 2017-11-16
ENV REFRESHED_AT 2020-09-23
# Update the package list to be able to use required packages
RUN apt-get update

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 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 go get -u gopkg.in/alecthomas/gometalinter.v1 && cd $GOPATH/src/github.com/purpleidea/mgmt && make deps && make build
CMD ["/bin/bash"]

View File

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

View File

@@ -51,7 +51,7 @@ master_doc = 'index'
# General information about the project.
project = u'mgmt'
copyright = u'2013-2019+ James Shubin and the project contributors'
copyright = u'2013-2024+ James Shubin and the project contributors'
author = u'James Shubin'
# 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
* `golang` 1.11 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/))
### Runtime

View File

@@ -122,38 +122,15 @@ entire set of running mgmt agents will need to all simultaneously converge for
the group to exit. This is particularly useful for bootstrapping new clusters
which need to exchange information that is only available at run time.
This existed in earlier versions of mgmt as a `--remote` option, but it has been
removed and is being ported to a more powerful variant where you can remote
execute via a `remote` resource.
#### Blog post
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/)
### 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
Please note that there are a number of undocumented options. For more
@@ -250,6 +227,43 @@ integer, then that value is the max size for that semaphore. Valid semaphore
id's include: `some_id`, `hello:42`, `not:smart:4` and `:13`. It is expected
that the last bare example be only used by the engine to add a global semaphore.
#### Rewatch
Boolean. Rewatch specifies whether we re-run the Watch worker during a graph
swap if it has errored. When doing a graph compare to swap the graphs, if this
is true, and this particular worker has errored, then we'll remove it 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 execution. It is only when
this is exhausted that we're in permanent worker failure, and only then can we
rely on this metaparam.
#### Realize
Boolean. Realize ensures that the resource is guaranteed to converge at least
once before a potential graph swap removes or changes it. This guarantee is
useful for fast changing graphs, to ensure that the brief creation of a resource
is seen. This guarantee does not prevent against the engine quitting normally,
and it can't guarantee it if the resource is blocked because of a failed
pre-requisite resource.
*XXX: This is currently not implemented!*
#### Reverse
Boolean. Reverse is a property that some resources can implement that specifies
that some "reverse" operation should happen when that resource "disappears". A
disappearance happens when a resource is defined in one instance of the graph,
and is gone in the subsequent one. This disappearance can happen if it was
previously in an if statement that then becomes false.
This is helpful for building robust programs with the engine. The engine adds a
"reversed" resource to that subsequent graph to accomplish the desired "reverse"
mechanics. The specifics of what this entails is a property of the particular
resource that is being "reversed".
It might be wise to combine the use of this meta parameter with the use of the
`realize` meta parameter to ensure that your reversed resource actually runs at
least once, if there's a chance that it might be gone for a while.
### Lang metadata file
Any module *must* have a metadata file in its root. It must be named
@@ -323,33 +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
management tools such as `Puppet` can be obtained with `--sema 1`.
#### `--remote <graph.yaml>`
Point to a graph file to run on the remote host specified within. This parameter
can be used multiple times if you'd like to remotely run on multiple hosts in
parallel.
#### `--allow-interactive`
Allow interactive prompting for SSH passwords if there is no authentication
method that works.
#### `--ssh-priv-id-rsa`
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.
#### `--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>`
Specify a path to a custom working directory prefix. This directory will get
@@ -367,8 +359,8 @@ default prefix. This can't be combined with the `--prefix` option.
If this option is specified, we will attempt to fall back to a temporary prefix
if the primary prefix couldn't be created. This is useful for avoiding failures
in environments where the primary prefix may or may not be available, but you'd
like to try. The canonical example is when running `mgmt` with `--remote` there
might be a cached copy of the binary in the primary prefix, but in case there's
like to try. The canonical example is when running `mgmt` with remote execution
there might be a cached copy of the binary in the primary prefix, but if there's
no binary available continue working in a temporary directory to avoid failure.
### Compilation options
@@ -451,7 +443,7 @@ To report any bugs, please file a ticket at: [https://github.com/purpleidea/mgmt
## Authors
Copyright (C) 2013-2019+ James Shubin and the project contributors
Copyright (C) 2013-2024+ James Shubin and the project contributors
Please see the
[AUTHORS](https://github.com/purpleidea/mgmt/tree/master/AUTHORS) file

View File

@@ -53,10 +53,11 @@ find a number of tutorials online.
3. Spend between four to six hours with the [golang tour](https://tour.golang.org/).
Skip over the longer problems, but try and get a solid overview of everything.
If you forget something, you can always go back and repeat those parts.
4. Connect to our [#mgmtconfig](https://webchat.freenode.net/?channels=#mgmtconfig)
IRC channel on the [Freenode](https://freenode.net/) network. You can use any
IRC client that you'd like, but the [hosted web portal](https://webchat.freenode.net/?channels=#mgmtconfig)
will suffice if you don't know what else to use.
4. Connect to our [#mgmtconfig](https://web.libera.chat/?channels=#mgmtconfig)
IRC channel on the [Libera.Chat](https://libera.chat/) network. You can use any
IRC client that you'd like, but the [hosted web portal](https://web.libera.chat/?channels=#mgmtconfig)
will suffice if you don't know what else to use. [Here are a few suggestions for
alternative clients.](https://libera.chat/guides/clients)
5. Now it's time to try and starting writing a patch! We have tagged a bunch of
[open issues as #mgmtlove](https://github.com/purpleidea/mgmt/issues?q=is%3Aissue+is%3Aopen+label%3Amgmtlove)
for new users to have somewhere to get involved. Look through them to see if
@@ -215,16 +216,76 @@ requires a number of seconds as an argument.
./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
directory must be inside your `$GOPATH`, and at the appropriate FQDN. This is:
`$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
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
project directory.
Yes, you can, add the `--only-unify` option to the lang frontend while using the
`run` command, and it will exit after type unification.
#### Example:
```
./mgmt run --tmp-prefix lang --only-unify examples/lang/hello0.mcl
```
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`?
If you create a file resource and only specify the content like this:
```
file "/tmp/foo" {
content => "hello world\n",
}
```
Then this will attempt to set the contents of that file to the desired string,
but *only* if that file already exists. If you'd like to ensure that it also
gets created in case it is not present, then you must also specify the state:
```
file "/tmp/foo" {
state => $const.res.file.state.exists,
content => "hello world\n",
}
```
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
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
remove an ambiguous scenario with the reversible file resource.
### Why do function names inside of templates include underscores?
The golang template library which we use to implement the template() function
doesn't support the dot notation, so we import all our normal functions, and
just replace dots with underscores. As an example, the standard `datetime.print`
function is shown within mcl scripts as datetime_print after being imported.
### On startup `mgmt` hangs after: `etcd: server: starting...`.
@@ -259,6 +320,65 @@ 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,
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 `--remote` flag existed in an earlier version of mgmt. It was removed and
will be replaced with a more powerful version, which is a "remote" resource. The
code is mostly ready but it's not finished. If you'd like to help finish it or
sponsor the work, please let me know.
### Does this support Windows? OSX? GNU Hurd?
Mgmt probably works best on Linux, because that's what most developers use for
@@ -321,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
`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`?
I didn't realize this when naming the project, and it is accidental. After much
@@ -334,7 +462,7 @@ which definitely existed before the band did.
### You didn't answer my question, or I have a question!
It's best to ask on [IRC](https://webchat.freenode.net/?channels=#mgmtconfig)
It's best to ask on [IRC](https://web.libera.chat/?channels=#mgmtconfig)
to see if someone can help you. If you don't get a response from IRC, you can
contact me through my [technical blog](https://purpleidea.com/contact/) and I'll
do my best to help. If you have a good question, please add it as a patch to

View File

@@ -37,10 +37,12 @@ available types and values in the mgmt language. It is very easy to use, and
should be fairly intuitive. Most of what you'll need to know can be inferred
from looking at example code.
To implement a function, you'll need to create a file in
[`lang/funcs/simple/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/simple/).
The function should be implemented as a `FuncValue` in our type system. It is
then registered with the engine during `init()`. An example explains it best:
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/)
module. It should probably get created in the correct directory inside of:
[`lang/core/`](https://github.com/purpleidea/mgmt/tree/master/lang/core/). The
function should be implemented as a `FuncValue` in our type system. It is then
registered with the engine during `init()`. An example explains it best:
### Example
@@ -50,14 +52,15 @@ package simple
import (
"fmt"
"github.com/purpleidea/mgmt/lang/funcs/simple"
"github.com/purpleidea/mgmt/lang/types"
)
// you must register your functions in init when the program starts up
func init() {
// Example function that squares an int and prints out answer as an str.
Register("talkingsquare", &types.FuncValue{
T: types.NewType("func(a int) str"), // declare the signature
simple.ModuleRegister(ModuleName, "talkingsquare", &types.FuncValue{
T: types.NewType("func(int) str"), // declare the signature
V: func(input []types.Value) (types.Value, error) {
i := input[0].Int() // get first arg as an int64
// must return the above specified value
@@ -109,15 +112,20 @@ As with the simple, non-polymorphic API, you can only implement [pure](https://e
functions, without writing too much boilerplate code. They will be automatically
re-evaluated as needed when their input values change.
To implement a function, you'll need to create a file in
[`lang/funcs/simplepoly/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/simplepoly/).
The function should be implemented as a list of `FuncValue`'s in our type
system. It is then registered with the engine during `init()`. You may also use
the `variant` type in your type definitions. This special type will never be
seen inside a running program, and will get converted to a concrete type if a
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/)
module. It should probably get created in the correct directory inside of:
[`lang/core/`](https://github.com/purpleidea/mgmt/tree/master/lang/core/). The
function should be implemented as a list of `FuncValue`'s in our type system. It
is then registered with the engine during `init()`. You may also use the
`variant` type in your type definitions. This special type will never be seen
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
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.
compiler to match, and ambiguous type graphs make for user compiler errors. The
top-level type must still be a function type, it may only contain variants as
part of its signature. It is probably more difficult to unify a function if its
return type is a variant, as opposed to if one of its args was.
An example explains it best:
@@ -127,11 +135,13 @@ An example explains it best:
import (
"fmt"
"github.com/purpleidea/mgmt/lang/types"
"github.com/purpleidea/mgmt/lang/funcs/simplepoly"
"github.com/purpleidea/mgmt/lang/types"
)
func init() {
// You may use the simplepoly.ModuleRegister method to register your
// function if it's in a module, as seen in the simple function example.
simplepoly.Register("len", []*types.FuncValue{
{
T: types.NewType("func([]variant) int"),
@@ -190,7 +200,7 @@ if it meets your needs. Most functions will be able to use that API. If you
really need something more powerful, then you can use the regular function API.
What follows are each of the method signatures and a description of each.
### Default
### Info
```golang
Info() *interfaces.Info
@@ -229,27 +239,6 @@ use in the other methods.
// Init runs some startup code for this function.
func (obj *FooFunc) Init(init *interfaces.Init) error {
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
}
```
@@ -257,23 +246,24 @@ func (obj *FooFunc) Close() error {
### Stream
```golang
Stream() error
Stream(context.Context) error
```
`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
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
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
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
```golang
// 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
var result string
for {
@@ -290,7 +280,7 @@ func (obj *FooFunc) Stream() error {
result = fmt.Sprintf("the input is: %d", ix)
case <-obj.closeChan:
case <-ctx.Done():
return nil
}
@@ -299,7 +289,7 @@ func (obj *FooFunc) Stream() error {
V: result,
}:
case <-obj.closeChan:
case <-ctx.Done():
return nil
}
}
@@ -330,8 +320,6 @@ type FooFunc struct {
init *interfaces.Init
// this space can be used if needed
closeChan chan struct{} // shutdown signal
}
```
@@ -354,7 +342,9 @@ method instead.
```golang
// 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
@@ -435,6 +425,11 @@ generator to build your `FuncValue` implementations, and pass in the unique
signature to each one as you are building them. Using a generator is a common
technique which was mentioned previously.
One obvious situation where this might occur is if your function doesn't take
any inputs! An example `math.fortytwo()` function was implemented that
demonstrates the use of function generators to pass the type signatures into the
implementations.
### Where can I find more information about mgmt?
Additional blog posts, videos and other material [is available!](https://github.com/purpleidea/mgmt/blob/master/docs/on-the-web.md).

View File

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

View File

@@ -54,7 +54,7 @@ can be impossible to infer the item's type.
An unordered set of unique keys of the same type and corresponding value pairs
of another type, eg:
`{"boiling" => 100, "freezing" => 0, "room" => "25", "house" => 22, "canada" => -30,}`.
`{"boiling" => 100, "freezing" => 0, "room" => 25, "house" => 22, "canada" => -30,}`.
That is to say, all of the keys must have the same type, and all of the values
must have the same type. You can use any type for either, although it is
probably advisable to avoid using very complex types as map keys.
@@ -206,7 +206,7 @@ value to use if that boolean is true. You can do this with the resource-specific
$b = true # change me to false and then try editing the file manually
file "/tmp/mgmt-elvis" {
content => $b ?: "hello world\n",
state => "exists",
state => $const.res.file.state.exists,
}
```
@@ -293,7 +293,7 @@ send notifications. You may have multiples of these per resource, including
multiple `Depend` lines if necessary. Each of these properties also supports the
conditional inclusion `elvis` operator as well.
For example, you may write is:
For example, you may write:
```mcl
$b = true # for example purposes
@@ -402,6 +402,38 @@ time.
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.
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
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
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
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
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 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
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
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
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?)
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
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
corresponding functions must exist for each expression node and are produced by
calling that expression's `Func` method. These are usually called by the
function engine during function creation and validation.
corresponding functions must exist for each expression node and are produced as
the vertices of this returned graph. This is built for the function engine.
#### Function engine creation and validation
@@ -677,13 +763,13 @@ one value must be produced.
```golang
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
```golang
Stream() error
Stream(context.Context) error
```
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`
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
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
```golang
Please see the example functions in
[lang/funcs/core/](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/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/).
[lang/core/](https://github.com/purpleidea/mgmt/tree/master/lang/core/).
```
### Polymorphic Function API

View File

@@ -44,3 +44,18 @@ if we missed something that you think is relevant!
| James Shubin | blog | [Mgmt Configuration Language](https://purpleidea.com/blog/2018/02/05/mgmt-configuration-language/) |
| James Shubin | video | [Recording from CfgMgmtCamp.eu 2018](https://www.youtube.com/watch?v=NxObmwZDyrI) |
| Jonathan Gold | blog | [Go Netlink and Select](https://jonathangold.ca/blog/go-netlink-and-select/) |
| James Shubin | video | [Recording from DevOpsDays Montreal 2018](https://www.youtube.com/watch?v=1i38c5cooHo) |
| James Shubin | video | [Recording from FOSDEM Minimalistic Languages Devroom 2019](https://video.fosdem.org/2019/K.4.201/mgmtconfig.webm) |
| James Shubin | video | [Recording from FOSDEM Infra Management Devroom 2019](https://video.fosdem.org/2019/UB2.252A/mgmt.webm) |
| James Shubin | video | [Recording from FOSDEM Graph Processing Devroom 2019](https://video.fosdem.org/2019/H.1308/graph_mgmt_config.webm) |
| James Shubin | video | [Recording from FOSDEM Virtualization Devroom 2019](https://video.fosdem.org/2019/H.2213/vai_real_time_virtualization_automation.webm) |
| James Shubin | video | [Recording from FOSDEM Containers Devroom 2019](https://video.fosdem.org/2019/UA2.114/containers_mgmt.webm) |
| James Shubin | video | [Recording from FOSDEM Monitoring Devroom 2019](https://video.fosdem.org/2019/UB2.252A/real_time_merging_of_config_management_and_monitoring.webm) |
| James Shubin | blog | [Mgmt Configuration Language: Class and Include](https://purpleidea.com/blog/2019/07/26/class-and-include-in-mgmt/) |
| James Shubin | video | [Recording from FOSDEM 2020, Main Track (History)](https://video.fosdem.org/2020/Janson/automation.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 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:
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/).
An alternate mirror is available [here](https://dl.fedoraproject.org/pub/alt/purpleidea/mgmt/releases/).
@@ -37,13 +39,13 @@ You'll need some dependencies, including `golang`, and some associated tools.
#### Installing golang
* You need golang version 1.11 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 apt style systems: `sudo apt install golang`
* To install on macOS systems install [Homebrew](https://brew.sh)
and run: `brew install go`
* You can run `go version` to check the golang version.
* If your distro is tool old, you may need to [download](https://golang.org/dl/)
* If your distro is too old, you may need to [download](https://golang.org/dl/)
a newer golang version.
#### Setting up golang
@@ -63,13 +65,11 @@ export GOPATH=$HOME/gopath
#### 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
[ -z "$GOPATH" ] && mkdir ~/go/ || mkdir -p $GOPATH/src/github.com/purpleidea/
cd $GOPATH/src/github.com/purpleidea/ || cd ~/go/
git clone --recursive https://github.com/purpleidea/mgmt/
cd $GOPATH/src/github.com/purpleidea/mgmt/ || cd ~/go/src/github.com/purpleidea/mgmt/
git clone --recursive https://github.com/purpleidea/mgmt/ ~/mgmt/
cd ~/mgmt/
```
* 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
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
* 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.
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
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.
then you'll need to include the appropriate YAML fields as shown below.
#### Example
@@ -105,7 +102,7 @@ when parameters take a zero value, whenever this is possible.)
```golang
// Default returns some sensible defaults for this resource.
func (obj *FooRes) Default() Res {
func (obj *FooRes) Default() engine.Res {
return &FooRes{
Answer: 42, // sometimes, defaults shouldn't be the zero value
}
@@ -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
checks `$the_world` in `Validate`. Remember to always program safely!
### Close
### Cleanup
```golang
Close() error
Cleanup() error
```
This is called to cleanup after the resource. It is usually not necessary, but
@@ -192,8 +189,8 @@ loop.
#### Example
```golang
// Close runs some cleanup code for this resource.
func (obj *FooRes) Close() error {
// Cleanup is run by the engine to clean up after the resource is done.
func (obj *FooRes) Cleanup() error {
err := obj.conn.Close() // close some internal connection
obj.someMap = nil // free up some large data structure from memory
return err
@@ -206,7 +203,7 @@ on an error if something went wrong.
### CheckApply
```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
@@ -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
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
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
the desired state, or after having detected that the state was incorrect, but
@@ -234,7 +232,7 @@ to `CheckApply`.
```golang
// 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
if state_is_okay { return true, nil } // done early! :)
@@ -277,7 +275,7 @@ will likely find the state to now be correct.
### Watch
```golang
Watch() error
Watch(ctx context.Context) error
```
`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
running.
You must make sure to cleanup any running code or goroutines before Watch exits.
#### Select
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
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
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,
and let `Watch` close.
#### Events
If the `<-obj.init.Done` channel closes, we should shutdown our resource. When
When we want to send an event, we use the `Event` helper function. This
automatically marks the resource state as `dirty`. If you're unsure, it's not
harmful to send the event. This will ultimately cause `CheckApply` to run. This
method can block if the resource is being paused.
If the `<-ctx.Done()` channel closes, we should shutdown our resource. When we
want to send an event, we use the `Event` helper function. This automatically
marks the resource state as `dirty`. If you're unsure, it's not harmful to send
the event. This will ultimately cause `CheckApply` to run. This method can block
if the resource is being paused.
#### Startup
@@ -347,7 +347,7 @@ sending out erroneous `Event` messages to keep things alive until it finishes.
```golang
// 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
var err error
if err, obj.foo = OpenFoo(); err != nil {
@@ -371,7 +371,7 @@ func (obj *FooRes) Watch() error {
case err := <-obj.foo.Errors:
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
}
@@ -553,11 +553,6 @@ ready to detect changes.
Event sends an event notifying the engine of a possible state change. It is
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 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
communicating with the distributed database. It can be used in `Init`,
`CheckApply` and `Watch`. Use with discretion and understanding of the internals
if needed in `Close`.
if needed in `Cleanup`.
### VarDir
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
resource `Close` if the resource would like to delete the contents. The resource
should not assume that the initial directory is empty, and it should be cleaned
on `Init` if that is a requirement.
resource `Cleanup` if the resource would like to delete the contents. The
resource should not assume that the initial directory is empty, and it should be
cleaned on `Init` if that is a requirement.
### Debug
@@ -625,7 +620,7 @@ func init() { // special golang method that runs once
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
`Puppet` compiler.
`yaml` compiler.
```golang
UnmarshalYAML(unmarshal func(interface{}) error) error // optional
@@ -642,8 +637,8 @@ The signature intentionally matches what is required to satisfy the `go-yaml`
#### Example
```golang
// UnmarshalYAML is the custom unmarshal handler for this struct.
// It is primarily useful for setting the defaults.
// UnmarshalYAML is the custom unmarshal handler for this struct. It is
// primarily useful for setting the defaults.
func (obj *FooRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
type rawRes FooRes // indirection to avoid infinite recursion
@@ -680,10 +675,10 @@ receiving one. This can _only_ be done inside of the `CheckApply` function!
```golang
// inside CheckApply, probably near the top
if val, exists := obj.init.Recv()["SomeKey"]; exists {
obj.init.Logf("the SomeKey param was sent to us from: %s.%s", val.Res, val.Key)
if val, exists := obj.init.Recv()["some_key"]; exists {
obj.init.Logf("the some_key param was sent to us from: %s.%s", val.Res, val.Key)
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
}
}
@@ -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
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
`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.
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.
It would look approximately like this:
```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
if c, err := obj.check(); err != nil {
if c, err := obj.check(ctx); err != nil {
return false, err // we errored
} else if c {
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
}
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
}

View File

@@ -17,6 +17,7 @@ You might want to look at the [generated documentation](https://godoc.org/github
for more up-to-date information about these resources.
* [Augeas](#Augeas): Manipulate files using augeas.
* [Consul:KV](#ConsulKV): Set keys in a Consul datastore.
* [Docker](#Docker):[Container](#Container) Manage docker containers.
* [Exec](#Exec): Execute shell commands on the system.
* [File](#File): Manage files and directories.
@@ -32,6 +33,8 @@ for more up-to-date information about these resources.
* [Print](#Print): Print messages to the console.
* [Svc](#Svc): Manage system systemd services.
* [Test](#Test): A mostly harmless resource that is used for internal testing.
* [Tftp:File](#TftpFile): Add files to the small embedded embedded tftp server.
* [Tftp:Server](#TftpServer): Run a small embedded tftp server.
* [Timer](#Timer): Manage system systemd services.
* [User](#User): Manage system users.
* [Virt](#Virt): Manage virtual machines with libvirt.
@@ -69,9 +72,9 @@ identified by a trailing slash in their path name. File have no such slash.
It has the following properties:
* `path`: absolute file path (directories have a trailing slash here)
* `state`: either `exists`, `absent`, or undefined
* `content`: raw file content
* `state`: either `exists` (the default value) or `absent`
* `mode`: octal unix file permissions
* `mode`: octal unix file permissions or symbolic string
* `owner`: username or uid for the file owner
* `group`: group name or gid for the file group
@@ -79,6 +82,16 @@ It has the following properties:
The path property specifies the file or directory that we are managing.
### State
The state property describes the action we'd like to apply for the resource. The
possible values are: `exists` and `absent`. If you do not specify either of
these, it is undefined. Without specifying this value as `exists`, another param
cannot cause a file to get implicitly created. When specifying this value as
`absent`, you should not specify any other params that would normally change the
file. For example, if you specify `content` and this param is `absent`, then you
will get an engine validation error.
### Content
The content property is a string that specifies the desired file contents.
@@ -88,10 +101,12 @@ The content property is a string that specifies the desired file contents.
The source property points to a source file or directory path that we wish to
copy over and use as the desired contents for our resource.
### State
### Fragments
The state property describes the action we'd like to apply for the resource. The
possible values are: `exists` and `absent`.
The fragments property lets you specify a list of files to concatenate together
to make up the contents of this file. They will be combined in the order that
they are listed in. If one of the files specified is a directory, then the
files in that top-level directory will be themselves combined together and used.
### Recurse
@@ -104,6 +119,12 @@ The force property is required if we want the file resource to be able to change
a file into a directory or vice-versa. If such a change is needed, but the force
property is not set to `true`, then this file resource will error.
### Purge
The purge property is used when this file represents a directory, and we'd like
to remove any unmanaged files from within it. Please note that any unmanaged
files in a directory with this flag set will be irreversibly deleted.
## Group
The group resource manages the system groups from `/etc/group`.
@@ -206,6 +227,16 @@ The service resource is still very WIP. Please help us by improving it!
The test resource is mostly harmless and is used for internal tests.
## Tftp:File
This adds files to the running tftp server. It's useful because it allows you to
add individual files without needing to create them on disk.
## Tftp:Server
Run a small embedded tftp server. This doesn't apply any state, but instead runs
a pure golang tftp server in the Watch loop.
## Timer
This resource needs better documentation. Please help us by improving it!

View File

@@ -61,6 +61,12 @@ Occasionally inline, two line source code comments are used within a function.
These should usually be balanced so that you don't have one line with 78
characters and the second with only four. Split the comment between the two.
### Default values
Whenever a constant or function parameter is defined, try and have the safer or
default value be the `zero` value. For example, instead of `const NoDanger`, use
`const AllowDanger` so that the `false` value is the safe scenario.
### Method receiver naming
[Contrary](https://github.com/golang/go/wiki/CodeReviewComments#receiver-names)
@@ -84,6 +90,75 @@ func (obj *Foo) Bar(baz string) int {
}
```
### Variable naming
We prefer shorter, scoped variables rather than `unnecessarilyLongIdentifiers`.
Remember the scoping rules and feel free to use new variables where appropriate.
For example, in a short string snippet you can use `s` instead of `myString`, as
well as other common choices. `i` is a common `int` counter, `f` for files, `fn`
for functions, `x` for something else and so on.
### Variable re-use
Feel free to create and use new variables instead of attempting to re-use the
same string. For example, if a function input arg is named `s`, you can use a
new variable to receive the first computation result on `s` instead of storing
it back into the original `s`. This avoids confusion if a different part of the
code wants to read the original input, and it avoids any chance of edit by
reference of the original callers copy of the variable.
#### Example
```golang
MyNotIdealFunc(s string, b bool) string {
if !b {
return s + "hey"
}
s = strings.Replace(s, "blah", "", -1) // not ideal (re-use of `s` var)
return s
}
MyOkayFunc(s string, b bool) string {
if !b {
return s + "hey"
}
s2 := strings.Replace(s, "blah", "", -1) // doesn't re-use `s` variable
return s2
}
MyGreatFunc(s string, b bool) string {
if !b {
return s + "hey"
}
return strings.Replace(s, "blah", "", -1) // even cleaner
}
```
### Constants in code
If a function takes a specifier (often a bool) it's sometimes better to name
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
`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
In general we try to preserve a logical ordering in source files which usually
@@ -96,6 +171,23 @@ declared in the interface.
When implementing code for the various types in the language, please follow this
order: `bool`, `str`, `int`, `float`, `list`, `map`, `struct`, `func`.
For other aspects where you have a set of items, try to be internally consistent
as well. For example, if you have two switch statements with `A`, `B`, and `C`,
please use the same ordering for these elements elsewhere that they appear in
the code and in the commentary if it is not illogical to do so.
### Product identifiers
Try to avoid references in the code to `mgmt` or a specific program name string
if possible. This makes it easier to rename code if we ever pick a better name
or support `libmgmt` better if we embed it. You can use the `Program` variable
which is available in numerous places if you want a string to put in the logs.
It is also recommended to avoid the `go` (programming language name) string if
possible. Try to use `golang` if required, since the word `go` is already
overloaded, and in particular it was even already used by the
[`go!`](https://en.wikipedia.org/wiki/Go!_(programming_language)).
## Overview for mcl code
The `mcl` language is quite new, so this guide will probably change over time as

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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
@@ -68,7 +80,8 @@ type AutoEdge interface {
Test([]bool) bool // call until false
}
// ResUID is a unique identifier for a resource, namely it's name, and the kind ("type").
// ResUID is a unique identifier for a resource, namely it's name, and the kind
// ("type").
type ResUID interface {
fmt.Stringer // String() string
@@ -104,9 +117,9 @@ func (obj *BaseUID) String() string {
}
// IFF looks at two UID's and if and only if they are equivalent, returns true.
// If they are not equivalent, it returns false.
// Most resources will want to override this method, since it does the important
// work of actually discerning if two resources are identical in function.
// If they are not equivalent, it returns false. Most resources will want to
// override this method, since it does the important work of actually discerning
// if two resources are identical in function.
func (obj *BaseUID) IFF(uid ResUID) bool {
res, ok := uid.(*BaseUID)
if !ok {

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,9 +13,21 @@
// 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/>.
// 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

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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
@@ -39,10 +51,11 @@ type GroupableRes interface {
SetAutoGroupMeta(*AutoGroupMeta)
// GroupCmp compares two resources and decides if they're suitable for
//grouping. This usually needs to be unique to your resource.
// grouping. This usually needs to be unique to your resource.
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
// IsGrouped determines if we are grouped.
@@ -54,8 +67,15 @@ type GroupableRes interface {
// GetGroup returns 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)
// 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.

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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
@@ -21,6 +33,7 @@ import (
"fmt"
"github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util/errwrap"
)
// 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 {
// compare sub resources
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
}
}
@@ -121,14 +135,29 @@ func ResCmp(r1, r2 Res) error {
v1 := r1r.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
return fmt.Errorf("recv params differ")
//return fmt.Errorf("recv params differ")
}
if v1 != nil && v2 != nil {
// TODO: until we hit this code path, don't allow
// comparing anything that has this set to non-zero
if len(v1) != 0 || len(v2) != 0 {
return fmt.Errorf("recv params exist")
if len(v1) != len(v2) {
//return fmt.Errorf("recv param lengths differ")
}
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,29 @@ func ResCmp(r1, r2 Res) error {
s1 := r1s.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
return fmt.Errorf("send params differ")
//return fmt.Errorf("send params differ")
}
if s1 != nil && s2 != nil {
// TODO: until we hit this code path, don't allow
// adapting anything that has this set to non-nil
return fmt.Errorf("send params exist")
// TODO: reflect.DeepEqual?
//return fmt.Errorf("send params exist")
}
}
// compare meta params for resources with reversible traits
r1v, ok1 := r1.(ReversibleRes)
r2v, ok2 := r2.(ReversibleRes)
if ok1 != ok2 {
return fmt.Errorf("reversible differs") // they must be different (optional)
}
if ok1 && ok2 {
if r1v.ReversibleMeta().Cmp(r2v.ReversibleMeta()) != nil {
return fmt.Errorf("reversible differs")
}
}
@@ -249,14 +294,29 @@ func AdaptCmp(r1, r2 CompatibleRes) error {
v1 := r1r.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
return fmt.Errorf("recv params differ")
//return fmt.Errorf("recv params differ")
}
if v1 != nil && v2 != nil {
// TODO: until we hit this code path, don't allow
// adapting anything that has this set to non-zero
if len(v1) != 0 || len(v2) != 0 {
return fmt.Errorf("recv params exist")
if len(v1) != len(v2) {
//return fmt.Errorf("recv param lengths differ")
}
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)
}
}
}
}
@@ -270,13 +330,29 @@ func AdaptCmp(r1, r2 CompatibleRes) error {
s1 := r1s.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
return fmt.Errorf("send params differ")
//return fmt.Errorf("send params differ")
}
if s1 != nil && s2 != nil {
// TODO: until we hit this code path, don't allow
// adapting anything that has this set to non-nil
return fmt.Errorf("send params exist")
// TODO: reflect.DeepEqual?
//return fmt.Errorf("send params exist")
}
}
// compare meta params for resources with reversible traits
r1v, ok1 := r1.(ReversibleRes)
r2v, ok2 := r2.(ReversibleRes)
if ok1 != ok2 {
return fmt.Errorf("reversible differs") // they must be different (optional)
}
if ok1 && ok2 {
if r1v.ReversibleMeta().Cmp(r2v.ReversibleMeta()) != nil {
return fmt.Errorf("reversible differs")
}
}
@@ -296,9 +372,11 @@ func VertexCmpFn(v1, v2 pgraph.Vertex) (bool, error) {
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
}
//fmt.Printf("ok Cmp: %p %+v <> %p %+v\n", r1, r1, r2, r2)
return true, nil
}
@@ -317,3 +395,55 @@ func EdgeCmpFn(e1, e2 pgraph.Edge) (bool, error) {
}
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
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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
@@ -81,6 +93,7 @@ func ResCopy(r CopyableRes) (CopyableRes, error) {
if !ok {
return nil, fmt.Errorf("resource wasn't groupable")
}
g2.SetParent(dst) // store who my parent is
grouped = append(grouped, g2)
}
dst.SetGroup(grouped)
@@ -106,6 +119,16 @@ func ResCopy(r CopyableRes) (CopyableRes, error) {
}
}
// copy meta params for resources with reversible traits
if x, ok := r.(ReversibleRes); ok {
dst, ok := res.(ReversibleRes)
if !ok {
// programming error
panic("reversible interfaces are illogical")
}
dst.SetReversibleMeta(x.ReversibleMeta()) // no need to copy atm
}
return res, nil
}

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
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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
@@ -26,4 +38,7 @@ func (e Error) Error() string { return string(e) }
const (
// ErrClosed means we couldn't complete a task because we had closed.
ErrClosed = Error("closed")
// ErrBackPoke means we're postponing due to a needed backpoke.
ErrBackPoke = Error("backpoke")
)

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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
@@ -23,39 +35,64 @@ import (
"github.com/spf13/afero"
)
// from the ioutil package:
// NopCloser(r io.Reader) io.ReadCloser // not implemented here
// 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.
// Fs is an interface that represents the file system API that we support.
// TODO: rename this to FS for consistency with the io/fs.FS naming scheme
type Fs interface {
//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
//DirExists(path string) (bool, error)
//Exists(path string) (bool, error)
//FileContainsAnyBytes(filename string, subslices [][]byte) (bool, error)
//FileContainsBytes(filename string, subslice []byte) (bool, error)
//FullBaseFsPath(basePathFs *BasePathFs, relativePath string) string
//GetTempDir(subPath string) string
//IsDir(path string) (bool, error)
//IsEmpty(path string) (bool, error)
//NeuterAccents(s string) string
//ReadAll(r io.Reader) ([]byte, error) // not needed, same as ioutil
afero.Fs // TODO: why doesn't this interface exist in the os pkg?
// FS is the read-only filesystem interface from the io/fs.FS package.
//fs.FS // io/fs.FS
// ReadDir reads the named directory and returns a list of directory
// entries sorted by filename.
//
// This mimics the signature from io/fs.ReadDirFS and has the same docs.
//
// 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)
ReadFile(filename string) ([]byte, error)
//SafeWriteReader(path string, r io.Reader) (err error)
TempDir(dir, prefix string) (name string, err error)
TempFile(dir, prefix string) (f afero.File, err error) // slightly different from upstream
//UnicodeSanitize(s string) string
//Walk(root string, walkFn filepath.WalkFunc) error
WriteFile(filename string, data []byte, perm os.FileMode) error
//WriteReader(path string, r io.Reader) (err error)
}
// WriteableFS is our internal filesystem interface for filesystems we write to.
// It can wrap whatever implementations we want.
type WriteableFS interface {
Fs
// 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
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,17 +13,31 @@
// 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/>.
// 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
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/purpleidea/mgmt/engine"
engineUtil "github.com/purpleidea/mgmt/engine/util"
"github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util/errwrap"
@@ -39,14 +53,18 @@ func (obj *Engine) OKTimestamp(vertex pgraph.Vertex) bool {
// be bad.
func (obj *Engine) BadTimestamps(vertex pgraph.Vertex) []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
for _, v := range obj.graph.IncomingGraphVertices(vertex) {
// If the vertex has a greater timestamp than any prerequisite,
// 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
// 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 {
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.
func (obj *Engine) Process(vertex pgraph.Vertex) error {
func (obj *Engine) Process(ctx context.Context, vertex pgraph.Vertex) error {
res, isRes := vertex.(engine.Res)
if !isRes {
return fmt.Errorf("vertex is not a Res")
@@ -83,7 +101,9 @@ func (obj *Engine) Process(vertex pgraph.Vertex) error {
}
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!
@@ -108,21 +128,41 @@ func (obj *Engine) Process(vertex pgraph.Vertex) error {
// sendrecv!
// 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 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")
} else if len(updated) > 0 {
for _, changed := range updated {
if changed { // at least one was updated
// invalidate cache, mark as dirty
obj.state[vertex].tuid.StopTimer()
obj.state[vertex].isStateOK = false
break
//for _, s := range graph.UpdatedStrings(updated) {
// obj.Logf("SendRecv: %s", s)
//}
for r, m := range updated { // map[engine.RecvableRes]map[string]*engine.Send
v, ok := r.(pgraph.Vertex)
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!
// If the resource doesn't implement refresh, skip the refresh test.
// 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
} else if noop && (refresh && isRefreshableRes) { // had a refresh to do w/ noop!
checkOK, err = false, nil // therefore the state is wrong
// run the CheckApply!
} else {
// run the CheckApply!
obj.Logf("%s: CheckApply(%t)", res, !noop)
// if this fails, don't UpdateTimestamp()
checkOK, err = res.CheckApply(!noop)
obj.Logf("%s: CheckApply(%t): Return(%t, %+v)", res, !noop, checkOK, err)
checkOK, err = res.CheckApply(ctx, !noop)
obj.Logf("%s: CheckApply(%t): Return(%t, %s)", res, !noop, checkOK, engineUtil.CleanError(err))
}
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 !noop && err == nil { // aka !noop || checkOK
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 {
obj.SetUpstreamRefresh(vertex, false) // refresh happened, clear the request
if isRefreshableRes {
@@ -209,7 +251,9 @@ func (obj *Engine) Process(vertex pgraph.Vertex) error {
wg := &sync.WaitGroup{}
// update this timestamp *before* we poke or the poked
// 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) {
if !obj.OKTimestamp(v) {
// 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)")
}
// 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
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
// 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
// every worrying about duplicate channel close panics.
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].watchDone:
case <-obj.state[vertex].limitDone:
case <-obj.state[vertex].retryDone:
case <-obj.state[vertex].removeDone:
case <-obj.state[vertex].eventsDone:
}
// the main "done" signal gets activated here!
close(obj.state[vertex].doneChan)
obj.state[vertex].doneCtxCancel() // cancels doneCtx
}()
var err error
@@ -308,7 +363,7 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
case <-timer.C: // the wait is over
return errDelayExpired // special
case <-obj.state[vertex].init.Done:
case <-obj.state[vertex].doneCtx.Done():
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 :(
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
} else {
obj.state[vertex].cuid.StartTimer()
obj.Logf("Watch(%s)", vertex)
err = res.Watch() // run the watch normally
obj.Logf("Watch(%s): Exited(%+v)", vertex, err)
if obj.Debug {
obj.Logf("%s: Watch...", vertex)
}
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
}
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.
// 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)
var reserv *rate.Reservation
var reterr error
var failed bool // has Process permanently failed?
var closed bool // has the resumeSignal channel closed?
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 {
case err, ok := <-obj.state[vertex].eventsChan: // read from watch channel
if !ok {
@@ -376,7 +445,7 @@ Loop:
// we then save so we can return it to the caller of us.
if err != nil {
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
continue
}
@@ -394,9 +463,33 @@ Loop:
obj.Logf("poke received")
}
reserv = nil // we didn't receive a real event here...
}
if failed { // don't Process anymore if we've already failed...
continue Loop
case _, ok := <-obj.state[vertex].pauseSignal: // one message
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
@@ -408,31 +501,8 @@ Loop:
}
}
// pause if one was requested...
select {
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...
// don't Process anymore if we've already failed or shutdown...
if failed || closed {
continue Loop
}
@@ -442,10 +512,13 @@ Loop:
d = reserv.DelayFrom(time.Now())
}
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)
LimitWait:
for {
// This "satellite" select doesn't need a poke
// channel because we're already in "event
// received" mode, and poke doesn't block.
select {
case <-timer.C: // the wait is over
break LimitWait
@@ -457,7 +530,7 @@ Loop:
}
if e != nil {
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
break LimitWait
}
@@ -466,19 +539,34 @@ Loop:
}
// TODO: does this get added in properly?
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
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
}
// end of limit delay
// retry...
var err error
var retry = res.MetaParams().Retry // lookup the retry value
//var retry = res.MetaParams().Retry // lookup the retry value
var delay uint64
RetryLoop:
for { // retry loop
@@ -486,6 +574,10 @@ Loop:
timer := time.NewTimer(time.Duration(delay) * time.Millisecond)
RetryWait:
for {
// This "satellite" select doesn't need
// a poke channel because we're already
// in "event received" mode, and poke
// doesn't block.
select {
case <-timer.C: // the wait is over
break RetryWait
@@ -497,7 +589,7 @@ Loop:
}
if e != nil {
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
break RetryWait
}
@@ -506,38 +598,68 @@ Loop:
}
// TODO: does this get added in properly?
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
delay = 0 // reset
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
}
if obj.Debug {
obj.Logf("Process(%s)", vertex)
}
err = obj.Process(vertex)
if obj.Debug {
obj.Logf("Process(%s): Return(%+v)", vertex, err)
backPoke := false
err = obj.Process(obj.state[vertex].doneCtx, vertex)
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
}
// we've got an error...
delay = res.MetaParams().Delay
if retry < 0 { // infinite retries
if metas.CheckApplyRetry < 0 { // infinite retries
continue
}
if retry > 0 { // don't decrement past 0
retry--
obj.state[vertex].init.Logf("retrying CheckApply after %.4f seconds (%d left)", float64(delay)/1000, retry)
if metas.CheckApplyRetry > 0 { // don't decrement past 0
metas.CheckApplyRetry--
obj.state[vertex].init.Logf(
"retrying CheckApply after %.4f seconds (%d left)",
float64(delay)/1000,
metas.CheckApplyRetry,
)
continue
}
//if retry == 0 { // optional
//if metas.CheckApplyRetry == 0 { // optional
// err = errwrap.Wrapf(err, "permanent process error")
//}
@@ -545,7 +667,7 @@ Loop:
// this dies. If Process fails permanently, we ask it
// to exit right here... (It happens when we loop...)
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
continue

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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
@@ -89,6 +101,9 @@ func AutoEdge(graph *pgraph.Graph, debug bool, logf func(format string, v ...int
}
}
}
// It would be great to ensure we didn't add any graph cycles here, but
// instead of checking now, we'll move the check into the main loop.
return nil
}

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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
@@ -74,10 +86,10 @@ func (obj *wrappedGrouper) VertexCmp(v1, v2 pgraph.Vertex) error {
return fmt.Errorf("v2 is not a GroupableRes")
}
if r1.Kind() != r2.Kind() { // we must group similar kinds
// TODO: maybe future resources won't need this limitation?
return fmt.Errorf("the two resources aren't the same kind")
}
// Some resources of different kinds can now group together!
//if r1.Kind() != r2.Kind() { // we must group similar kinds
// return fmt.Errorf("the two resources aren't the same kind")
//}
// someone doesn't want to group!
if r1.AutoGroupMeta().Disabled || r2.AutoGroupMeta().Disabled {
return fmt.Errorf("one of the autogroup flags is false")
@@ -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
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
if semas := r2.MetaParams().Sema; len(semas) > 0 {

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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
@@ -54,7 +66,7 @@ func AutoGroup(ag engine.AutoGrouper, g *pgraph.Graph, debug bool, logf func(for
logf("!VertexMerge for: %s into: %s", wStr, vStr)
} else { // success!
logf("success for: %s into: %s", wStr, vStr)
logf("%s into %s", wStr, vStr)
merged = true // woo
}
@@ -66,5 +78,8 @@ func AutoGroup(ag engine.AutoGrouper, g *pgraph.Graph, debug bool, logf func(for
}
}
// It would be great to ensure we didn't add any graph cycles here, but
// instead of checking now, we'll move the check into the main loop.
return nil
}

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,13 +13,26 @@
// 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/>.
// 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
import (
"context"
"fmt"
"reflect"
"sort"
@@ -62,15 +75,15 @@ func (obj *NoopResTest) Init(init *engine.Init) error {
return nil
}
func (obj *NoopResTest) Close() error {
func (obj *NoopResTest) Cleanup() error {
return nil
}
func (obj *NoopResTest) Watch() error {
func (obj *NoopResTest) Watch(context.Context) error {
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
}
@@ -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
return nil, err
}
r2.SetParent(r1) // store who my parent is
// HACK: update the name so it matches full list of self+grouped
res := v1.(engine.GroupableRes)
names := strings.Split(res.Name(), ",") // load in stored names
@@ -595,10 +610,12 @@ func TestPgraphGrouping11(t *testing.T) {
runGraphCmp(t, g1, g2)
}
/*
// simple merge 1
// a1 a2 a1,a2
// \ / >>> | (arrows point downwards)
// b b
*/
func TestPgraphGrouping12(t *testing.T) {
g1, _ := pgraph.NewGraph("g1") // original graph
{
@@ -620,10 +637,12 @@ func TestPgraphGrouping12(t *testing.T) {
runGraphCmp(t, g1, g2)
}
/*
// simple merge 2
// b b
// / \ >>> | (arrows point downwards)
// a1 a2 a1,a2
*/
func TestPgraphGrouping13(t *testing.T) {
g1, _ := pgraph.NewGraph("g1") // original graph
{
@@ -645,10 +664,12 @@ func TestPgraphGrouping13(t *testing.T) {
runGraphCmp(t, g1, g2)
}
/*
// triple merge
// a1 a2 a3 a1,a2,a3
// \ | / >>> | (arrows point downwards)
// b b
*/
func TestPgraphGrouping14(t *testing.T) {
g1, _ := pgraph.NewGraph("g1") // original graph
{
@@ -673,12 +694,14 @@ func TestPgraphGrouping14(t *testing.T) {
runGraphCmp(t, g1, g2)
}
/*
// chain merge
// a1 a1
// / \ |
// b1 b2 >>> b1,b2 (arrows point downwards)
// \ / |
// c1 c1
*/
func TestPgraphGrouping15(t *testing.T) {
g1, _ := pgraph.NewGraph("g1") // original graph
{
@@ -708,6 +731,7 @@ func TestPgraphGrouping15(t *testing.T) {
runGraphCmp(t, g1, g2)
}
/*
// re-attach 1 (outer)
// 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!
@@ -716,6 +740,7 @@ func TestPgraphGrouping15(t *testing.T) {
// b1 / >>> b1 OR b1 / (arrows point downwards)
// | / | | /
// c1 c1 c1
*/
func TestPgraphGrouping16(t *testing.T) {
g1, _ := pgraph.NewGraph("g1") // original graph
{
@@ -730,25 +755,40 @@ func TestPgraphGrouping16(t *testing.T) {
g1.AddEdge(b1, c1, e2)
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")
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)
e1 := NE("e1")
e2 := NE("e2")
e3 := NE("e3")
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)
// a1 b2 a1
// | / |
// b1 / >>> b1,b2 (arrows point downwards)
// | / |
// c1 c1
*/
func TestPgraphGrouping17(t *testing.T) {
g1, _ := pgraph.NewGraph("g1") // original graph
{
@@ -776,13 +816,16 @@ func TestPgraphGrouping17(t *testing.T) {
runGraphCmp(t, g1, g2)
}
/*
// re-attach 3 (double)
// similar to "re-attach 1", technically there is a second possibility for this
// a2 a1 b2 a1,a2
// \ | / |
// \ b1 / >>> b1,b2 (arrows point downwards)
// \ | / |
// c1 c1
// TODO: verify this second possibility manually
// a2 a1 b2 a1,a2 a1,a2
// \ | / | | \
// \ b1 / >>> b1,b2 OR b1,b2 / (arrows point downwards)
// \ | / | | /
// c1 c1 c1
*/
func TestPgraphGrouping18(t *testing.T) {
g1, _ := pgraph.NewGraph("g1") // original graph
{
@@ -800,23 +843,38 @@ func TestPgraphGrouping18(t *testing.T) {
g1.AddEdge(a2, c1, e3)
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")
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)
e1 := NE("e1")
e2 := NE("e2,e4")
e3 := NE("e3")
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!)
// a1 a1
// \ >>> \ (arrows point downwards)
// a2 a2
*/
func TestPgraphGroupingConnected0(t *testing.T) {
g1, _ := pgraph.NewGraph("g1") // original graph
{
@@ -835,12 +893,14 @@ func TestPgraphGroupingConnected0(t *testing.T) {
runGraphCmp(t, g1, g2)
}
/*
// connected merge 1, (no change!)
// a1 a1
// \ \
// b >>> b (arrows point downwards)
// \ \
// a2 a2
*/
func TestPgraphGroupingConnected1(t *testing.T) {
g1, _ := pgraph.NewGraph("g1") // original graph
{

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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
@@ -43,8 +55,24 @@ func (ag *baseGrouper) Init(g *pgraph.Graph) error {
if ag.graph != nil {
return fmt.Errorf("the init method has already been called")
}
ag.graph = g // pointer
ag.vertices = ag.graph.VerticesSorted() // cache in deterministic order!
ag.graph = g // pointer
// 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.j = 0
if len(ag.vertices) == 0 { // empty graph
@@ -89,6 +117,19 @@ func (ag *baseGrouper) VertexNext() (v1, v2 pgraph.Vertex, err error) {
ag.done = true
}
}
// TODO: is this index swap better or even valid?
//if ag.i < l {
// ag.i++
//}
//if ag.i == l {
// ag.i = 0
// if ag.j < l {
// ag.j++
// }
// if ag.j == l {
// ag.done = true
// }
//}
return
}
@@ -110,7 +151,7 @@ func (ag *baseGrouper) VertexMerge(v1, v2 pgraph.Vertex) (v pgraph.Vertex, err e
return nil, fmt.Errorf("vertexMerge needs to be overridden")
}
// EdgeMerge can be overridden, since it just simple returns the first edge.
// EdgeMerge can be overridden, since it just simply returns the first edge.
func (ag *baseGrouper) EdgeMerge(e1, e2 pgraph.Edge) pgraph.Edge {
return e1 // noop
}

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,18 +13,35 @@
// 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/>.
// 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
import (
"fmt"
"sort"
"strings"
"github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util/errwrap"
)
// VertexMerge merges v2 into v1 by reattaching the edges where appropriate,
// and then by deleting v2 from the graph. Since more than one edge between two
// vertices is not allowed, duplicate edges are merged as well. an edge merge
// VertexMerge merges v2 into v1 by reattaching the edges where appropriate, and
// then by deleting v2 from the graph. Since more than one edge between two
// vertices is not allowed, duplicate edges are merged as well. An edge merge
// function can be provided if you'd like to control how you merge the edges!
func VertexMerge(g *pgraph.Graph, v1, v2 pgraph.Vertex, vertexMergeFn func(pgraph.Vertex, pgraph.Vertex) (pgraph.Vertex, error), edgeMergeFn func(pgraph.Edge, pgraph.Edge) pgraph.Edge) error {
// methodology
@@ -112,8 +129,17 @@ func VertexMerge(g *pgraph.Graph, v1, v2 pgraph.Vertex, vertexMergeFn func(pgrap
// note: This branch isn't used if the vertexMergeFn
// decides to just merge logically on its own instead
// of actually returning something that we then merge.
v1 = v // TODO: ineffassign?
v1 = v // XXX: ineffassign?
//*v1 = *v
// Ensure that everything still validates. (For safety!)
r, ok := v1.(engine.Res) // TODO: v ?
if !ok {
return fmt.Errorf("not a Res")
}
if err := engine.Validate(r); err != nil {
return errwrap.Wrapf(err, "the Res did not Validate")
}
}
}
g.DeleteVertex(v2) // remove grouped vertex
@@ -124,3 +150,67 @@ func VertexMerge(g *pgraph.Graph, v1, v2 pgraph.Vertex, vertexMergeFn func(pgrap
}
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
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,8 +13,23 @@
// 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/>.
// 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
import (
@@ -25,24 +40,34 @@ import (
"github.com/purpleidea/mgmt/converger"
"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/util/errwrap"
"github.com/purpleidea/mgmt/util/semaphore"
)
const (
// StateDir is the name of the sub directory where all the local
// resource state is stored.
StateDir = "state"
)
// Engine encapsulates a generic graph and manages its operations.
type Engine struct {
Program string
Version 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
// created if needed.
Prefix string
Converger *converger.Coordinator
Debug bool
Logf func(format string, v ...interface{})
Prefix string
Debug bool
Logf func(format string, v ...interface{})
graph *pgraph.Graph
nextGraph *pgraph.Graph
@@ -50,6 +75,9 @@ type Engine struct {
waits map[pgraph.Vertex]*sync.WaitGroup // wg for the Worker func
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
semas map[string]*semaphore.Semaphore
@@ -86,6 +114,9 @@ func (obj *Engine) Init() error {
obj.waits = make(map[pgraph.Vertex]*sync.WaitGroup)
obj.wlock = &sync.Mutex{}
obj.mlock = &sync.Mutex{}
obj.metas = make(map[engine.ResPtrUID]*engine.MetaState)
obj.slock = &sync.Mutex{}
obj.semas = make(map[string]*semaphore.Semaphore)
@@ -127,7 +158,7 @@ func (obj *Engine) Validate() error {
}
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
@@ -144,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
// pause the engine before running this, and resume it after you're done.
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 ?
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...
vertexAddFn := func(vertex pgraph.Vertex) error {
// some of these validation steps happen before this Commit step
@@ -163,20 +207,22 @@ func (obj *Engine) Commit() error {
return fmt.Errorf("the Res state already exists")
}
activeMetas[engine.PtrUID(res)] = struct{}{} // add
if obj.Debug {
obj.Logf("Validate(%s)", res)
}
err := engine.Validate(res)
if obj.Debug {
obj.Logf("Validate(%s): Return(%+v)", res, err)
obj.Logf("Validate(%s): Return(%s)", res, engineUtil.CleanError(err))
}
if err != nil {
return errwrap.Wrapf(err, "the Res did not Validate")
}
// FIXME: is res.Name() sufficiently unique to use as a UID here?
pathUID := fmt.Sprintf("%s-%s", res.Kind(), res.Name())
statePrefix := fmt.Sprintf("%s/", path.Join(obj.Prefix, "state", pathUID))
pathUID := engineUtil.ResPathUID(res)
statePrefix := fmt.Sprintf("%s/", path.Join(obj.statePrefix(), pathUID))
// don't create this unless it *will* be used
//if err := os.MkdirAll(statePrefix, 0770); err != nil {
// return errwrap.Wrapf(err, "can't create state prefix")
@@ -184,15 +230,17 @@ func (obj *Engine) Commit() error {
obj.waits[vertex] = &sync.WaitGroup{}
obj.state[vertex] = &State{
//Graph: obj.graph, // TODO: what happens if we swap the graph?
Graph: obj.graph, // Update if we swap the graph!
Vertex: vertex,
Program: obj.Program,
Version: obj.Version,
Hostname: obj.Hostname,
//Converger: obj.Converger,
Local: obj.Local,
World: obj.World,
Prefix: statePrefix,
//Converger: obj.Converger,
Debug: obj.Debug,
Logf: func(format string, v ...interface{}) {
@@ -220,10 +268,18 @@ func (obj *Engine) Commit() error {
obj.wlock.Unlock()
}()
obj.Logf("Worker(%s)", v)
if obj.Debug || true {
obj.Logf("%s: Working...", v)
}
// contains the Watch and CheckApply loops
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
// If the Rewatch metaparam is true, then this will get
// restarted if we do a graph cmp swap. This is why the
@@ -238,15 +294,21 @@ func (obj *Engine) Commit() error {
free := []func() error{} // functions to run after graphsync to reset...
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!
close(obj.state[vertex].removeDone) // causes doneChan to close
obj.state[vertex].Resume() // unblock from resume
obj.waits[vertex].Wait() // sync
close(obj.state[vertex].removeDone) // causes doneCtx to cancel
close(obj.state[vertex].resumeSignal) // unblock (it only closes here)
obj.waits[vertex].Wait() // sync
// close the state and resource
// FIXME: will this mess up the sync and block the engine?
if err := obj.state[vertex].Close(); err != nil {
return errwrap.Wrapf(err, "the Res did not Close")
if err := obj.state[vertex].Cleanup(); err != nil {
return errwrap.Wrapf(err, "the Res did not Cleanup")
}
// delete to free up memory from old graphs
@@ -302,6 +364,23 @@ func (obj *Engine) Commit() error {
if err := obj.graph.GraphSync(obj.nextGraph, vertexCmpFn, vertexAddFn, vertexRemoveFn, engine.EdgeCmpFn); err != nil {
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
// if GraphSync failed in some way. Otherwise we'd have to do clean up!
for _, fn := range start {
@@ -322,14 +401,14 @@ func (obj *Engine) Commit() error {
// the changes that we'd made to the previously primary graph. This is
// because this function is meant to atomically swap the graphs safely.
// TODO: update all the `State` structs with the new Graph pointer
//for _, vertex := range obj.graph.Vertices() {
// state, exists := obj.state[vertex]
// if !exists {
// continue
// }
// state.Graph = obj.graph // update pointer to graph
//}
// Update all the `State` structs with the new Graph pointer.
for _, vertex := range obj.graph.Vertices() {
state, exists := obj.state[vertex]
if !exists {
continue
}
state.Graph = obj.graph // update pointer to graph
}
return nil
}
@@ -337,8 +416,13 @@ func (obj *Engine) Commit() error {
// 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
// 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 {
// 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 {
return fmt.Errorf("already resumed")
}
@@ -351,8 +435,22 @@ func (obj *Engine) Resume() error {
reversed := pgraph.Reverse(topoSort)
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].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!
obj.paused = false
@@ -373,6 +471,10 @@ func (obj *Engine) SetFastPause() {
// Pause the active, running graph.
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 {
return fmt.Errorf("already paused")
}
@@ -394,8 +496,10 @@ func (obj *Engine) Pause(fastPause bool) error {
return nil
}
// Close triggers a shutdown. Engine must be already paused before this is run.
func (obj *Engine) Close() error {
// Shutdown the engine. Engine must be already paused before this is run. It is
// 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")
// this is a graph switch (graph sync) that switches to an empty graph!
@@ -416,3 +520,8 @@ func (obj *Engine) Close() error {
func (obj *Engine) Graph() *pgraph.Graph {
return obj.graph
}
// statePrefix returns the dir where all the resource state is stored locally.
func (obj *Engine) statePrefix() string {
return fmt.Sprintf("%s/", path.Join(obj.Prefix, StateDir))
}

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,9 +13,21 @@
// 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/>.
// 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

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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

311
engine/graph/reverse.go Normal file
View File

@@ -0,0 +1,311 @@
// 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 graph
import (
"fmt"
"os"
"path"
"sort"
"github.com/purpleidea/mgmt/engine"
engineUtil "github.com/purpleidea/mgmt/engine/util"
"github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util/errwrap"
)
const (
// ReverseFile is the file name in the resource state dir where any
// reversal information is stored.
ReverseFile = "reverse"
// ReversePerm is the permissions mode used to create the ReverseFile.
ReversePerm = 0600
)
// Reversals adds the reversals onto the loaded graph. This should happen last,
// and before Commit.
func (obj *Engine) Reversals() error {
if obj.nextGraph == nil {
return fmt.Errorf("there is no active graph to add reversals to")
}
// Initially get all of the reversals to seek out all possible errors.
// XXX: The engine needs to know where data might have been stored if we
// XXX: want to potentially allow alternate read/write paths, like etcd.
// XXX: In this scenario, we'd have to store a token somewhere to let us
// XXX: know to look elsewhere for the special ReversalList read method.
data, err := obj.ReversalList() // (map[string]string, error)
if err != nil {
return errwrap.Wrapf(err, "the reversals had errors")
}
if len(data) == 0 {
return nil // end early
}
resMatch := func(r1, r2 engine.Res) bool { // simple match on UID only!
if r1.Kind() != r2.Kind() {
return false
}
if r1.Name() != r2.Name() {
return false
}
return true
}
resInList := func(needle engine.Res, haystack []engine.Res) bool {
for _, res := range haystack {
if resMatch(needle, res) {
return true
}
}
return false
}
if obj.Debug {
obj.Logf("decoding %d reversals...", len(data))
}
resources := []engine.Res{}
// do this in a sorted order so that it errors deterministically
sorted := []string{}
for key := range data {
sorted = append(sorted, key)
}
sort.Strings(sorted)
for _, key := range sorted {
val := data[key]
// XXX: replace this ResToB64 method with one that stores it in
// a human readable format, in case someone wants to hack and
// edit it manually.
// XXX: we probably want this to be YAML, it works with the diff
// too...
r, err := engineUtil.B64ToRes(val)
if err != nil {
return errwrap.Wrapf(err, "error decoding res with UID: `%s`", key)
}
res, ok := r.(engine.ReversibleRes)
if !ok {
// this requirement is here to keep things simpler...
return errwrap.Wrapf(err, "decoded res with UID: `%s` was not reversible", key)
}
matchFn := func(vertex pgraph.Vertex) (bool, error) {
r, ok := vertex.(engine.Res)
if !ok {
return false, fmt.Errorf("not a Res")
}
if !resMatch(r, res) {
return false, nil
}
return true, nil
}
// FIXME: not efficient, we could build a cache-map first
vertex, err := obj.nextGraph.VertexMatchFn(matchFn) // (Vertex, error)
if err != nil {
return errwrap.Wrapf(err, "error searching graph for match")
}
if vertex != nil { // found one!
continue // it doesn't need reversing yet
}
// TODO: check for (incompatible?) duplicates instead
if resInList(res, resources) { // we've already got this one...
continue
}
// We set this in two different places to be safe. It ensures
// that we erase the reversal state file after we've used it.
res.ReversibleMeta().Reversal = true // set this for later...
resources = append(resources, res)
}
if len(resources) == 0 {
return nil // end early
}
// Now that we've passed the chance of any errors, we modify the graph.
obj.Logf("adding %d reversals...", len(resources))
for _, res := range resources {
obj.nextGraph.AddVertex(res)
}
// TODO: Do we want a way for stored reversals to add edges too?
// It would be great to ensure we didn't add any graph cycles here, but
// instead of checking now, we'll move the check into the main loop.
return nil
}
// ReversalList returns all the available pending reversal data on this host. It
// can then be decoded by whatever method is appropriate for.
func (obj *Engine) ReversalList() (map[string]string, error) {
result := make(map[string]string) // some key to contents
dir := obj.statePrefix() // loop through this dir...
files, err := os.ReadDir(dir)
if err != nil && !os.IsNotExist(err) {
return nil, errwrap.Wrapf(err, "error reading list of state dirs")
} else if err != nil {
return result, nil // nothing found, no state dir exists yet
}
for _, x := range files {
key := x.Name() // some uid for the resource
file := path.Join(dir, key, ReverseFile)
content, err := os.ReadFile(file)
if err != nil && !os.IsNotExist(err) {
return nil, errwrap.Wrapf(err, "could not read reverse file: %s", file)
} else if err != nil {
continue // file does not exist, skip
}
// file exists!
str := string(content)
result[key] = str // save
}
return result, nil
}
// ReversalInit performs the reversal initialization steps if necessary for this
// resource.
func (obj *State) ReversalInit() error {
res, ok := obj.Vertex.(engine.ReversibleRes)
if !ok {
return nil // nothing to do
}
if res.ReversibleMeta().Disabled {
return nil // nothing to do, reversal isn't enabled
}
// If the reversal is enabled, but we are the result of a previous
// reversal, then this will overwrite that older reversal request, and
// our resource should be designed to deal with that. This happens if we
// return a reversible resource as the reverse of a resource that was
// reversed. It's probably fairly rare.
if res.ReversibleMeta().Reversal {
obj.Logf("triangle reversal") // warn!
}
r, err := res.Reversed()
if err != nil {
return errwrap.Wrapf(err, "could not reverse: %s", res.String())
}
if r == nil {
return nil // this can't be reversed, or isn't implemented here
}
// We set this in two different places to be safe. It ensures that we
// erase the reversal state file after we've used it.
r.ReversibleMeta().Reversal = true // set this for later...
// XXX: replace this ResToB64 method with one that stores it in a human
// readable format, in case someone wants to hack and edit it manually.
// XXX: we probably want this to be YAML, it works with the diff too...
str, err := engineUtil.ResToB64(r)
if err != nil {
return errwrap.Wrapf(err, "could not encode: %s", res.String())
}
// TODO: put this method on traits.Reversible as part of the interface?
return obj.ReversalWrite(str, res.ReversibleMeta().Overwrite) // Store!
}
// ReversalCleanup performs the reversal shutdown steps if necessary for this
// resource.
func (obj *State) ReversalCleanup() error {
res, ok := obj.Vertex.(engine.ReversibleRes)
if !ok {
return nil // nothing to do
}
// Don't check res.ReversibleMeta().Disabled because we're removing the
// previous one. That value only applies if we're doing a new reversal.
if !res.ReversibleMeta().Reversal {
return nil // nothing to erase, we're not a reversal resource
}
if !obj.isStateOK.Load() { // did we successfully reverse? (mutex RLock/RUnlock)
obj.Logf("did not complete reversal") // warn
return nil
}
// TODO: put this method on traits.Reversible as part of the interface?
return obj.ReversalDelete() // Erase our reversal instructions.
}
// ReversalWrite stores the reversal state information for this resource.
func (obj *State) ReversalWrite(str string, overwrite bool) error {
dir, err := obj.varDir("") // private version
if err != nil {
return errwrap.Wrapf(err, "could not get VarDir for reverse")
}
file := path.Join(dir, ReverseFile) // return a unique file
content, err := os.ReadFile(file)
if err != nil && !os.IsNotExist(err) {
return errwrap.Wrapf(err, "could not read reverse file: %s", file)
}
// file exists and we shouldn't overwrite if different
if err == nil && !overwrite {
// compare to existing file
oldStr := string(content)
if str != oldStr {
obj.Logf("existing, pending, reversible resource exists")
//obj.Logf("diff:")
//obj.Logf("") // TODO: print the diff w/o secret values
return fmt.Errorf("existing, pending, reversible resource exists")
}
}
return os.WriteFile(file, []byte(str), ReversePerm)
}
// ReversalDelete removes the reversal state information for this resource.
func (obj *State) ReversalDelete() error {
dir, err := obj.varDir("") // private version
if err != nil {
return errwrap.Wrapf(err, "could not get VarDir for reverse")
}
file := path.Join(dir, ReverseFile) // return a unique file
// FIXME: why do we see these removals when there isn't a state file?
if err = os.Remove(file); os.IsNotExist(err) {
return nil // ignore missing files
}
return errwrap.Wrapf(err, "could not remove reverse state file")
}

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,9 +13,21 @@
// 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/>.
// 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

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,33 +13,114 @@
// 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/>.
// 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
import (
"fmt"
"reflect"
"sort"
"github.com/purpleidea/mgmt/engine"
engineUtil "github.com/purpleidea/mgmt/engine/util"
"github.com/purpleidea/mgmt/lang/types"
"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
// receiver and must be given as input the full resource struct to receive on.
// It applies the loaded values to the resource.
func (obj *Engine) SendRecv(res engine.RecvableRes) (map[string]bool, error) {
recv := res.Recv()
if obj.Debug {
// NOTE: this could expose private resource data like passwords
obj.Logf("%s: SendRecv: %+v", res, recv)
// It applies the loaded values to the resource. It is called recursively, as it
// recurses into any grouped resources found within the first receiver. It
// returns a map of resource pointer, to resource field key, to changed boolean.
func SendRecv(res engine.RecvableRes, fn RecvFn) (map[engine.RecvableRes]map[string]*engine.Send, error) {
updated := make(map[engine.RecvableRes]map[string]*engine.Send) // list of updated keys
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
for k, v := range recv {
updated[k] = false // default
v.Changed = false // reset to the default
recv := res.Recv()
if fn != nil {
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
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))
type1 := obj1.Type()
//type1 := obj1.Type()
value1 := obj1.FieldByName(key1)
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
type2 := obj2.Type()
//type2 := obj2.Type()
value2 := obj2.FieldByName(key2)
kind2 := value2.Kind()
if obj.Debug {
obj.Logf("Send(%s) has %v: %v", type1, kind1, value1)
obj.Logf("Recv(%s) has %v: %v", type2, kind2, value2)
//orig := value1
dest := value2 // save the o.g. because we need the real dest!
// 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...
if kind1 != kind2 {
e := fmt.Errorf("kind mismatch between %s: %s and %s: %s", v.Res, kind1, res, kind2)
err = errwrap.Append(err, e) // list of errors
continue
// This second block is identical, but it's just accidentally
// symmetrical. The types of input structs are different shapes.
// for kind2 == reflect.Interface || kind2 == reflect.Ptr // wrong
// if kind2 == reflect.Interface || kind2 == reflect.Ptr // wrong
// 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
// FIXME: do we want to relax this for string -> *string ?
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 obj.Debug {
// obj.Logf("Send(%s) has %v: %v", type1, kind1, value1)
// obj.Logf("Recv(%s) has %v: %v", type2, kind2, value2)
//}
// 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 !value2.CanSet() {
if !dest.CanSet() {
e := fmt.Errorf("can't set %s.%s", res, k)
err = errwrap.Append(err, e) // list of errors
continue
}
// 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)
err = errwrap.Append(err, e) // list of errors
continue
}
// if the values aren't equal, we're changing the receiver
if !reflect.DeepEqual(value1.Interface(), value2.Interface()) {
// TODO: can we catch the panics here in case they happen?
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)
if reflect.DeepEqual(value1.Interface(), value2.Interface()) {
continue // skip as they're the same, no error needed
}
// 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
}
@@ -150,3 +289,19 @@ func TypeCmp(a, b reflect.Value) error {
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
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,26 +13,41 @@
// 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/>.
// 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
import (
"context"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/purpleidea/mgmt/converger"
"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/util"
"github.com/purpleidea/mgmt/util/errwrap"
)
// State stores some state about the resource it is mapped to.
type State struct {
// Graph is a pointer to the graph that this vertex is part of.
//Graph pgraph.Graph
Graph *pgraph.Graph
// Vertex is the pointer in the graph that this state corresponds to. It
// can be converted to a `Res` if necessary.
@@ -40,39 +55,51 @@ type State struct {
Vertex pgraph.Vertex
Program string
Version 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
// created if needed.
Prefix string
//Converger *converger.Coordinator
// Debug turns on additional output and behaviours.
Debug bool
// Logf is the logging function that should be used to display messages.
Logf func(format string, v ...interface{})
timestamp int64 // last updated timestamp
isStateOK bool // is state OK or do we need to run CheckApply ?
workerErr error // did the Worker error?
timestamp int64 // last updated timestamp
isStateOK *atomic.Bool // is state OK or do we need to run CheckApply ?
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.
doneChan chan struct{}
doneCtx context.Context
// doneCtxCancel is the cancel function for doneCtx.
doneCtxCancel func()
// processDone is closed when the Process/CheckApply function fails
// permanently, and wants to cause Watch to exit.
processDone chan struct{}
// watchDone is closed when the Watch function fails permanently, and we
// 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
// close this to signal we should definitely exit. This happens inside
// 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.
// This happens when we're switching graphs. The switch to an "empty" is
// the equivalent of asking for a final shutdown.
@@ -94,14 +121,15 @@ type State struct {
// to send on since it is buffered.
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
// pauseSignal closes to request a pause of this resource.
// pauseSignal receives a message to request a pause of this resource.
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{}
// 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
@@ -130,11 +158,15 @@ func (obj *State) Init() error {
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.watchDone = make(chan struct{})
obj.limitDone = make(chan struct{})
obj.retryDone = make(chan struct{})
obj.removeDone = 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.pauseSignal = make(chan struct{})
//obj.resumeSignal = make(chan struct{}) // happens on pause
//obj.pausedAck = util.NewEasyAck() // happens on pause
obj.resumeSignal = make(chan struct{})
obj.wg = &sync.WaitGroup{}
@@ -154,12 +185,12 @@ func (obj *State) Init() error {
obj.init = &engine.Init{
Program: obj.Program,
Version: obj.Version,
Hostname: obj.Hostname,
// Watch:
Running: obj.event,
Event: obj.event,
Done: obj.doneChan,
// CheckApply:
Refresh: func() bool {
@@ -169,33 +200,72 @@ func (obj *State) Init() error {
}
return res.Refresh()
},
Send: func(st interface{}) error {
res, ok := obj.Vertex.(engine.SendableRes)
if !ok {
panic("res does not support the Sendable trait")
}
// XXX: type check this
//expected := res.Sends()
//if err := XXX_TYPE_CHECK(expected, st); err != nil {
// return err
//}
return res.Send(st) // send the struct
},
Recv: func() map[string]*engine.Send { // TODO: change this API?
res, ok := obj.Vertex.(engine.RecvableRes)
if !ok {
panic("res does not support the Recvable trait")
Send: engine.GenerateSendFunc(res),
Recv: engine.GenerateRecvFunc(res),
// FIXME: pass in a safe, limited query func instead?
// TODO: not implemented, use FilteredGraph
//Graph: func() *pgraph.Graph {
// _, ok := obj.Vertex.(engine.CanGraphQueryRes)
// if !ok {
// panic("res does not support the GraphQuery trait")
// }
// return obj.Graph // we return in a func so it's fresh!
//},
FilteredGraph: func() (*pgraph.Graph, error) {
graph, err := pgraph.NewGraph("filtered")
if err != nil {
return nil, err
}
return res.Recv()
// filter graph and build a new one...
adjacency := obj.Graph.Adjacency()
for v1 := range adjacency {
// check we're allowed
r1, ok := v1.(engine.GraphQueryableRes)
if !ok {
continue
}
// pass in information on requestor...
if err := r1.GraphQueryAllowed(
engine.GraphQueryableOptionKind(res.Kind()),
engine.GraphQueryableOptionName(res.Name()),
// TODO: add more information...
); err != nil {
continue
}
graph.AddVertex(v1)
for v2, edge := range adjacency[v1] {
r2, ok := v2.(engine.GraphQueryableRes)
if !ok {
continue
}
// pass in information on requestor...
if err := r2.GraphQueryAllowed(
engine.GraphQueryableOptionKind(res.Kind()),
engine.GraphQueryableOptionName(res.Name()),
// TODO: add more information...
); err != nil {
continue
}
//graph.AddVertex(v2) // redundant
graph.AddEdge(v1, v2, edge)
}
}
return graph, nil // we return in a func so it's fresh!
},
Local: obj.Local,
World: obj.World,
VarDir: obj.varDir,
Debug: obj.Debug,
Logf: func(format string, v ...interface{}) {
obj.Logf("resource: "+format, v...)
obj.Logf(format, v...)
},
}
@@ -203,9 +273,15 @@ func (obj *State) Init() error {
if obj.Debug {
obj.Logf("Init(%s)", res)
}
// write the reverse request to the disk...
if err := obj.ReversalInit(); err != nil {
return err // TODO: test this code path...
}
err := res.Init(obj.init)
if obj.Debug {
obj.Logf("Init(%s): Return(%+v)", res, err)
obj.Logf("Init(%s): Return(%s)", res, engineUtil.CleanError(err))
}
if err != nil {
return errwrap.Wrapf(err, "could not Init() resource")
@@ -214,9 +290,9 @@ func (obj *State) Init() error {
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.
func (obj *State) Close() error {
func (obj *State) Cleanup() error {
res, isRes := obj.Vertex.(engine.Res)
if !isRes {
return fmt.Errorf("vertex is not a Res")
@@ -236,12 +312,23 @@ func (obj *State) Close() error {
if obj.Debug {
obj.Logf("Close(%s)", res)
}
err := res.Close()
if obj.Debug {
obj.Logf("Close(%s): Return(%+v)", res, err)
var reverr error
// clear the reverse request from the disk...
if err := obj.ReversalCleanup(); err != nil {
// TODO: test this code path...
// TODO: should this be an error or a warning?
reverr = err
}
return err
reterr := res.Cleanup()
if obj.Debug {
obj.Logf("Close(%s): Return(%s)", res, engineUtil.CleanError(reterr))
}
reterr = errwrap.Append(reterr, reverr)
return reterr
}
// Poke sends a notification on the poke channel. This channel is used to notify
@@ -251,37 +338,28 @@ func (obj *State) Close() error {
// callers are expected to make sure that they don't leave any of these running
// by the time the Worker() shuts down.
func (obj *State) Poke() {
// redundant
//if len(obj.pokeChan) > 0 {
// return
//}
select {
case obj.pokeChan <- struct{}{}:
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
// 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
// time.
// It must not be called concurrently with either the Resume() method or itself,
// so only call these one at a time and alternate between the two.
func (obj *State) Pause() error {
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)
select {
case <-obj.pausedAck.Wait(): // we got it!
case obj.pauseSignal <- struct{}{}:
// we're paused
case <-obj.doneChan:
case <-obj.doneCtx.Done():
return engine.ErrClosed
}
obj.paused = true
@@ -289,23 +367,22 @@ func (obj *State) Pause() error {
return nil
}
// Resume unpauses this resource. It can be safely called on a brand-new
// resource that has just started running without incident. It is NOT
// thread-safe with the Pause() method, so only call either one at a time.
// Resume unpauses this resource. It can be safely called once on a brand-new
// resource that has just started running, without incident. It must not be
// 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() {
// 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
return
}
obj.pauseSignal = make(chan struct{}) // rebuild for next pause
close(obj.resumeSignal)
//obj.Poke() // not needed, we're already waiting for resume
select {
case obj.resumeSignal <- struct{}{}:
}
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.
@@ -317,7 +394,7 @@ func (obj *State) event() {
obj.setDirty() // assume we're initially dirty
select {
case obj.eventsChan <- nil:
case obj.eventsChan <- nil: // blocks! (this is unbuffered)
// send!
}
@@ -328,11 +405,13 @@ func (obj *State) event() {
// CheckApply will have some work to do in order to converge it.
func (obj *State) setDirty() {
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.
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
ticker := time.NewTicker(time.Duration(interval) * time.Second)
defer ticker.Stop()
@@ -344,7 +423,7 @@ func (obj *State) poll(interval uint32) error {
case <-ticker.C: // received the timer event
obj.init.Logf("polling...")
case <-obj.init.Done: // signal for shutdown request
case <-ctx.Done(): // signal for shutdown request
return nil
}

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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

82
engine/graphqueryable.go Normal file
View File

@@ -0,0 +1,82 @@
// 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
// GraphQueryableRes is the interface that must be implemented if you want your
// resource to be allowed to be queried from another resource in the graph. This
// is done as a form of explicit authorization tracking so that we can consider
// security aspects more easily. Ultimately, all resource code should be
// trusted, but it's still a good idea to know if a particular resource is even
// able to access information about another one, and if your resource doesn't
// add the trait supporting this, then it won't be allowed.
type GraphQueryableRes interface {
Res // implement everything in Res but add the additional requirements
// GraphQueryAllowed returns nil if you're allowed to query the graph.
GraphQueryAllowed(...GraphQueryableOption) error
}
// GraphQueryableOption is an option that can be used to specify the
// authentication.
type GraphQueryableOption func(*GraphQueryableOptions)
// GraphQueryableOptions represents the different possible configurable options.
type GraphQueryableOptions struct {
// Kind is the kind of the resource making the access.
Kind string
// Name is the name of the resource making the access.
Name string
// TODO: add more options if needed
}
// Apply is a helper function to apply a list of options to the struct. You
// should initialize it with defaults you want, and then apply any you've
// received like this.
func (obj *GraphQueryableOptions) Apply(opts ...GraphQueryableOption) {
for _, optionFunc := range opts { // apply the options
optionFunc(obj)
}
}
// GraphQueryableOptionKind tells the GraphQueryAllowed function what the
// resource kind is.
func GraphQueryableOptionKind(kind string) GraphQueryableOption {
return func(gqo *GraphQueryableOptions) {
gqo.Kind = kind
}
}
// GraphQueryableOptionName tells the GraphQueryAllowed function what the
// resource name is.
func GraphQueryableOptionName(name string) GraphQueryableOption {
return func(gqo *GraphQueryableOptions) {
gqo.Name = name
}
}

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
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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
@@ -36,8 +48,9 @@ var DefaultMetaParams = &MetaParams{
Poll: 0, // defaults to watching for events
Limit: rate.Inf, // defaults to no limit
Burst: 0, // no burst needed on an infinite rate
Reset: false,
//Sema: []string{},
Rewatch: true,
Rewatch: false,
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.
// 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"`
// 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"`
// 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 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
// 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
@@ -90,7 +119,9 @@ type MetaParams struct {
// 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
// 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"`
// 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 {
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 {
return errwrap.Wrapf(err, "values for Sema are different")
@@ -180,6 +214,7 @@ func (obj *MetaParams) Copy() *MetaParams {
Poll: obj.Poll,
Limit: obj.Limit, // FIXME: can we copy this type like this? test me!
Burst: obj.Burst,
Reset: obj.Reset,
Sema: sema,
Rewatch: obj.Rewatch,
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!
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
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,9 +13,21 @@
// 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/>.
// 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

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,14 +13,29 @@
// 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/>.
// 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
import (
"context"
"encoding/gob"
"fmt"
"github.com/purpleidea/mgmt/engine/local"
"github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util/errwrap"
"gopkg.in/yaml.v2"
@@ -29,8 +44,8 @@ import (
// TODO: should each resource be a sub-package?
var registeredResources = map[string]func() Res{}
// RegisterResource registers a new resource by providing a constructor
// function that returns a resource object ready to be unmarshalled from YAML.
// RegisterResource registers a new resource by providing a constructor function
// that returns a resource object ready to be unmarshalled from YAML.
func RegisterResource(kind string, fn func() Res) {
f := fn()
if kind == "" {
@@ -86,6 +101,9 @@ type Init struct {
// Program is the name of the program.
Program string
// Version is the version of the program.
Version string
// Hostname is the uuid for the host.
Hostname string
@@ -97,10 +115,6 @@ type Init struct {
// Event sends an event notifying the engine of a possible state change.
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:
// Refresh returns whether the resource received a notification. This
@@ -120,6 +134,25 @@ type Init struct {
// Other functionality:
// Graph is a function that returns the current graph. The returned
// value won't be valid after a graphsync so make sure to call this when
// you are about to use it, and discard it right after.
// FIXME: it might be better to offer a safer, more limited, GraphQuery?
//Graph func() *pgraph.Graph // TODO: not implemented, use FilteredGraph
// FilteredGraph is a function that returns a filtered variant of the
// current graph. Only resource that have allowed themselves to be added
// into this graph will appear. If they did not consent, then those
// vertices and any associated edges, will not be present.
FilteredGraph func() (*pgraph.Graph, error)
// 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
// used for communicating with the distributed database.
World World
@@ -175,17 +208,21 @@ type Res interface {
// and data from the engine.
Init(*Init) error
// Close is run by the engine to clean up after the resource is done.
Close() error
// Cleanup is run by the engine to clean up after the resource is done.
Cleanup() error
// Watch is run by the engine to monitor for state changes. If it
// detects any, it notifies the engine which will usually run CheckApply
// in response.
Watch() error
// in response. If the input context cancels, we must shutdown.
Watch(context.Context) error
// CheckApply determines if the state of the resource is correct and if
// asked to with the `apply` variable, applies the requested state.
CheckApply(apply bool) (checkOK bool, err error)
// asked to with the `apply` variable, applies the requested state. If
// 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
// are not equivalent. This is more strict than the Adapts method of the
@@ -205,6 +242,22 @@ func Stringer(res Res) string {
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
// entry point for running all the validation steps on a resource.
func Validate(res Res) error {
@@ -227,8 +280,8 @@ func Validate(res Res) error {
// the Interrupt method to shutdown the resource quickly. Running this method
// may leave the resource in a partial state, however this may be desired if you
// want a faster exit or if you'd prefer a partial state over letting the
// resource complete in a situation where you made an error and you wish to
// exit quickly to avoid data loss. It is usually triggered after multiple ^C
// resource complete in a situation where you made an error and you wish to exit
// quickly to avoid data loss. It is usually triggered after multiple ^C
// signals.
type InterruptableRes interface {
Res

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,24 +13,35 @@
// 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/>.
// 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
import (
"context"
"fmt"
"os"
"strings"
"github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/engine/traits"
"github.com/purpleidea/mgmt/recwatch"
"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"
)
@@ -51,24 +62,24 @@ type AugeasRes struct {
init *engine.Init
// 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
// 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
// ["path", "value"]. mgmt will run augeas.Get() before augeas.Set(), to
// prevent changing the file when it is not needed.
Sets []*AugeasSet `yaml:"sets"`
// Sets is a list of changes that will be applied to the file, in the
// form of ["path", "value"]. mgmt will run augeas.Get() before
// augeas.Set(), to prevent changing the file when it is not needed.
Sets []*AugeasSet `lang:"sets" yaml:"sets"`
recWatcher *recwatch.RecWatcher // used to watch the changed files
}
// AugeasSet represents a key/value pair of settings to be applied.
type AugeasSet struct {
Path string `yaml:"path"` // The relative path to the value to be changed.
Value string `yaml:"value"` // The value to be set on the given Path.
Path string `lang:"path" yaml:"path"` // The relative path to the value to be changed.
Value string `lang:"value" yaml:"value"` // The value to be set on the given Path.
}
// Cmp compares this set with another one.
@@ -119,15 +130,15 @@ func (obj *AugeasRes) Init(init *engine.Init) error {
return nil
}
// Close is run by the engine to clean up after the resource is done.
func (obj *AugeasRes) Close() error {
// Cleanup is run by the engine to clean up after the resource is done.
func (obj *AugeasRes) Cleanup() error {
return nil
}
// Watch is the primary listener for this resource and it outputs events.
// Taken from the File resource.
// Watch is the primary listener for this resource and it outputs events. This
// was 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
obj.recWatcher, err = recwatch.NewRecWatcher(obj.File, false)
if err != nil {
@@ -156,7 +167,7 @@ func (obj *AugeasRes) Watch() error {
}
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
}
@@ -169,7 +180,7 @@ func (obj *AugeasRes) Watch() error {
}
// 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)
// 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.
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)
// By default we do not set any option to augeas, we use the defaults.
opts := augeas.None
@@ -231,7 +242,7 @@ func (obj *AugeasRes) CheckApply(apply bool) (bool, error) {
checkOK := true
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")
} else if !setCheckOK {
checkOK = false
@@ -301,8 +312,8 @@ func (obj *AugeasRes) UIDs() []engine.ResUID {
return []engine.ResUID{x}
}
// UnmarshalYAML is the custom unmarshal handler for this struct.
// It is primarily useful for setting the defaults.
// UnmarshalYAML is the custom unmarshal handler for this struct. It is
// primarily useful for setting the defaults.
func (obj *AugeasRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
type rawRes AugeasRes // indirection to avoid infinite recursion

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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
@@ -24,7 +36,7 @@ import (
"encoding/json"
"encoding/pem"
"fmt"
"io/ioutil"
"io"
"net"
"net/http"
"regexp"
@@ -121,8 +133,8 @@ const (
)
// AwsRegions is a list of all AWS regions generated using ec2.DescribeRegions.
// cn-north-1 and us-gov-west-1 are not returned, probably due to security.
// List available at http://docs.aws.amazon.com/general/latest/gr/rande.html
// cn-north-1 and us-gov-west-1 are not returned, probably due to security. List
// available at http://docs.aws.amazon.com/general/latest/gr/rande.html
var AwsRegions = []string{
"ap-northeast-1",
"ap-northeast-2",
@@ -147,26 +159,42 @@ var AwsRegions = []string{
// http://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html
type AwsEc2Res struct {
traits.Base // add the base methods without re-implementation
traits.Sendable
init *engine.Init
State string `yaml:"state"` // state: running, stopped, terminated
Region string `yaml:"region"` // region must match an element of AwsRegions
Type string `yaml:"type"` // type of ec2 instance, eg: t2.micro
ImageID string `yaml:"imageid"` // imageid must be available on the chosen region
// State must be running, stopped, or terminated.
State string `lang:"state" yaml:"state"`
// 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
// requests, that cause JSON decoder errors, will also make the engine
// shut down. If ErrorOnMalformedPost set to true and an error occurs,
// 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.
// See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html
// for documantation and examples.
UserData string `yaml:"userdata"`
UserData string `lang:"userdata" yaml:"userdata"`
client *ec2.EC2 // client session for AWS API calls
@@ -187,7 +215,8 @@ type AwsEc2Res struct {
InstanceID string
}
// chanStruct defines the type for a channel used to pass events and errors to watch.
// chanStruct defines the type for a channel used to pass events and errors to
// watch.
type chanStruct struct {
event awsEc2Event
state string
@@ -233,7 +262,8 @@ type ruleDetail struct {
State []string `json:"state"`
}
// postData is the format of the messages received and decoded by snsPostHandler().
// postData is the format of the messages received and decoded by
// snsPostHandler().
type postData struct {
Type string `json:"Type"`
MessageID string `json:"MessageId"`
@@ -247,7 +277,8 @@ type postData struct {
SigningCertURL string `json:"SigningCertURL"`
}
// postMsg is used to unmarshal the postData message if it's an event notification.
// postMsg is used to unmarshal the postData message if it's an event
// notification.
type postMsg struct {
InstanceID string `json:"instance-id"`
State string `json:"state"`
@@ -385,10 +416,11 @@ func (obj *AwsEc2Res) Init(init *engine.Init) error {
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.
func (obj *AwsEc2Res) Close() error {
func (obj *AwsEc2Res) Cleanup() error {
var errList error
// XXX: do these in a defer of Watch instead.
// clean up sns objects created by Init/snsWatch
if obj.snsClient != nil {
// delete the topic and associated subscriptions
@@ -406,15 +438,16 @@ func (obj *AwsEc2Res) Close() error {
}
// 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 != "" {
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 state.
func (obj *AwsEc2Res) longpollWatch() error {
// longpollWatch uses the ec2 api's built in methods to watch ec2 resource
// state.
func (obj *AwsEc2Res) longpollWatch(ctx context.Context) error {
send := false
// We tell the engine that we're running right away. This is not correct,
@@ -422,7 +455,7 @@ func (obj *AwsEc2Res) longpollWatch() error {
obj.init.Running() // when started, notify engine that we're running
// cancellable context used for exiting cleanly
ctx, cancel := context.WithCancel(context.TODO())
innerCtx, cancel := context.WithCancel(context.TODO())
// clean up when we're done
defer obj.wg.Wait()
@@ -457,7 +490,7 @@ func (obj *AwsEc2Res) longpollWatch() error {
}
// wait for the instance state to change
state, err := stateWaiter(ctx, instance, obj.client)
state, err := stateWaiter(innerCtx, instance, obj.client)
if err != nil {
select {
case obj.awsChan <- &chanStruct{
@@ -498,7 +531,7 @@ func (obj *AwsEc2Res) longpollWatch() error {
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
}
@@ -510,11 +543,11 @@ func (obj *AwsEc2Res) longpollWatch() error {
}
// snsWatch uses amazon's SNS and CloudWatchEvents APIs to get instance state-
// change notifications pushed to the http endpoint (snsServer) set up below.
// In 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 published to the topic and processes them accordingly.
func (obj *AwsEc2Res) snsWatch() error {
// change notifications pushed to the http endpoint (snsServer) set up below. In
// 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
// published to the topic and processes them accordingly.
func (obj *AwsEc2Res) snsWatch(ctx context.Context) error {
send := false
defer obj.wg.Wait()
// create the sns listener
@@ -529,9 +562,9 @@ func (obj *AwsEc2Res) snsWatch() error {
}
// close the listener and shutdown the sns server when we're done
defer func() {
ctx, cancel := context.WithTimeout(context.TODO(), SnsServerShutdownTimeout*time.Second)
innerCtx, cancel := context.WithTimeout(context.TODO(), SnsServerShutdownTimeout*time.Second)
defer cancel()
if err := snsServer.Shutdown(ctx); err != nil {
if err := snsServer.Shutdown(innerCtx); err != nil {
if err != context.Canceled {
obj.init.Logf("error stopping sns endpoint: %s", err)
return
@@ -592,7 +625,7 @@ func (obj *AwsEc2Res) snsWatch() error {
obj.init.Logf("State: %v", msg.event)
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
}
@@ -604,7 +637,7 @@ func (obj *AwsEc2Res) snsWatch() error {
}
// 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)
// find the instance we need to check
@@ -714,7 +747,7 @@ func (obj *AwsEc2Res) CheckApply(apply bool) (bool, error) {
}
// 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()
// wait until the state converges
@@ -723,11 +756,11 @@ func (obj *AwsEc2Res) CheckApply(apply bool) (bool, error) {
}
switch obj.State {
case ec2.InstanceStateNameRunning:
err = obj.client.WaitUntilInstanceRunningWithContext(ctx, waitInput)
err = obj.client.WaitUntilInstanceRunningWithContext(innerCtx, waitInput)
case ec2.InstanceStateNameStopped:
err = obj.client.WaitUntilInstanceStoppedWithContext(ctx, waitInput)
err = obj.client.WaitUntilInstanceStoppedWithContext(innerCtx, waitInput)
case ec2.InstanceStateNameTerminated:
err = obj.client.WaitUntilInstanceTerminatedWithContext(ctx, waitInput)
err = obj.client.WaitUntilInstanceTerminatedWithContext(innerCtx, waitInput)
default:
return false, errwrap.Wrapf(err, "unrecognized instance state")
}
@@ -751,45 +784,37 @@ func (obj *AwsEc2Res) CheckApply(apply bool) (bool, error) {
// Cmp compares two resources and returns an error if they are not equivalent.
func (obj *AwsEc2Res) Cmp(r engine.Res) error {
if !obj.Compare(r) {
return fmt.Errorf("did not compare")
}
return nil
}
// Compare two resources and return if they are equivalent.
func (obj *AwsEc2Res) Compare(r engine.Res) bool {
// we can only compare AwsEc2Res to others of the same resource kind
res, ok := r.(*AwsEc2Res)
if !ok {
return false
return fmt.Errorf("not a %s", obj.Kind())
}
if obj.State != res.State {
return false
return fmt.Errorf("the State differs")
}
if obj.Region != res.Region {
return false
return fmt.Errorf("the Region differs")
}
if obj.Type != res.Type {
return false
return fmt.Errorf("the Type differs")
}
if obj.ImageID != res.ImageID {
return false
return fmt.Errorf("the ImageID differs")
}
if obj.WatchEndpoint != res.WatchEndpoint {
return false
return fmt.Errorf("the WatchEndpoint differs")
}
if obj.WatchListenAddr != res.WatchListenAddr {
return false
return fmt.Errorf("the WatchListenAddr differs")
}
if obj.ErrorOnMalformedPost != res.ErrorOnMalformedPost {
return false
return fmt.Errorf("the ErrorOnMalformedPost differs")
}
if obj.UserData != res.UserData {
return false
return fmt.Errorf("the UserData differs")
}
return true
return nil
}
func (obj *AwsEc2Res) prependName() string {
@@ -803,8 +828,8 @@ type AwsEc2UID struct {
name string
}
// UIDs includes all params to make a unique identification of this object.
// Most resources only return one, although some resources can return multiple.
// UIDs includes all params to make a unique identification of this object. Most
// resources only return one, although some resources can return multiple.
func (obj *AwsEc2Res) UIDs() []engine.ResUID {
x := &AwsEc2UID{
BaseUID: engine.BaseUID{Name: obj.Name(), Kind: obj.Kind()},
@@ -813,8 +838,8 @@ func (obj *AwsEc2Res) UIDs() []engine.ResUID {
return []engine.ResUID{x}
}
// UnmarshalYAML is the custom unmarshal handler for this struct.
// It is primarily useful for setting the defaults.
// UnmarshalYAML is the custom unmarshal handler for this struct. It is
// primarily useful for setting the defaults.
func (obj *AwsEc2Res) UnmarshalYAML(unmarshal func(interface{}) error) error {
type rawRes AwsEc2Res // indirection to avoid infinite recursion
@@ -950,8 +975,8 @@ func (obj *AwsEc2Res) snsVerifySignature(post postData) error {
return nil
}
// snsGetCert downloads and parses the signing certificate from the provided
// URL for message verification.
// snsGetCert downloads and parses the signing certificate from the provided URL
// for message verification.
func (obj *AwsEc2Res) snsGetCert(url string) (*x509.Certificate, error) {
// only download valid certificates from amazon
matchURL, err := regexp.MatchString(SnsCertURLRegex, url)
@@ -967,7 +992,7 @@ func (obj *AwsEc2Res) snsGetCert(url string) (*x509.Certificate, error) {
return nil, errwrap.Wrapf(err, "http get error")
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errwrap.Wrapf(err, "error reading post body")
}
@@ -1025,7 +1050,7 @@ func (obj *AwsEc2Res) snsMakeTopic() (string, error) {
}
obj.init.Logf("Created SNS Topic")
if topic.TopicArn == nil {
return "", fmt.Errorf("TopicArn is nil")
return "", fmt.Errorf("the TopicArn is nil")
}
return *topic.TopicArn, nil
}
@@ -1043,8 +1068,8 @@ func (obj *AwsEc2Res) snsDeleteTopic(topicArn string) error {
return nil
}
// snsSubscribe subscribes the endpoint to the sns topic.
// Returning SubscriptionArn here is useless as it is still pending confirmation.
// snsSubscribe subscribes the endpoint to the sns topic. Returning
// SubscriptionArn here is useless as it is still pending confirmation.
func (obj *AwsEc2Res) snsSubscribe(endpoint string, topicArn string) error {
// subscribe to the topic
subInput := &sns.SubscribeInput{
@@ -1060,8 +1085,8 @@ func (obj *AwsEc2Res) snsSubscribe(endpoint string, topicArn string) error {
return nil
}
// snsConfirmSubscription confirms the sns subscription.
// Returning SubscriptionArn here is useless as it is still pending confirmation.
// snsConfirmSubscription confirms the sns subscription. Returning
// SubscriptionArn here is useless as it is still pending confirmation.
func (obj *AwsEc2Res) snsConfirmSubscription(topicArn string, token string) error {
// confirm the subscription
csInput := &sns.ConfirmSubscriptionInput{
@@ -1113,7 +1138,8 @@ func (obj *AwsEc2Res) snsProcessEvent(message, instanceName string) (awsEc2Event
return awsEc2EventNone, nil
}
// snsAuthorize adds the necessary permission for cloudwatch to publish to the SNS topic.
// snsAuthorize adds the necessary permission for cloudwatch to publish to the
// SNS topic.
func (obj *AwsEc2Res) snsAuthorizeCloudWatch(topicArn string) error {
// get the topic attributes, including the security policy
gaInput := &sns.GetTopicAttributesInput{

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2019+ 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
//
// This program is free software: you can redistribute it and/or modify
@@ -13,7 +13,19 @@
// 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/>.
// 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
@@ -57,7 +69,6 @@ type ConfigEtcdRes struct {
sizeFlag bool
interruptChan chan struct{}
wg *sync.WaitGroup
}
// 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.interruptChan = make(chan struct{})
obj.wg = &sync.WaitGroup{}
return nil
}
// Close is run by the engine to clean up after the resource is done.
func (obj *ConfigEtcdRes) Close() error {
obj.wg.Wait() // bonus
// Cleanup is run by the engine to clean up after the resource is done.
func (obj *ConfigEtcdRes) Cleanup() error {
return nil
}
// Watch is the primary listener for this resource and it outputs events.
func (obj *ConfigEtcdRes) Watch() error {
obj.wg.Add(1)
defer obj.wg.Done()
// FIXME: add timeout to context
// The obj.init.Done channel is closed by the engine to signal shutdown.
ctx, cancel := util.ContextWithCloser(context.Background(), obj.init.Done)
func (obj *ConfigEtcdRes) Watch(ctx context.Context) error {
wg := &sync.WaitGroup{}
defer wg.Wait()
innerCtx, cancel := context.WithCancel(ctx)
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 {
return errwrap.Wrapf(err, "could not watch ideal cluster size")
}
@@ -121,7 +128,8 @@ Loop:
}
// 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)
@@ -134,10 +142,10 @@ Loop:
// 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,
// 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{}
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()
wg.Add(1)
go func() {
@@ -186,17 +194,17 @@ func (obj *ConfigEtcdRes) sizeCheckApply(apply bool) (bool, error) {
}
// 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
if c, err := obj.sizeCheckApply(apply); err != nil {
if c, err := obj.sizeCheckApply(ctx, apply); err != nil {
return false, err
} else if !c {
checkOK = false
}
// 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
//} else if !c {
// checkOK = false
@@ -229,8 +237,8 @@ func (obj *ConfigEtcdRes) Interrupt() error {
return nil
}
// UnmarshalYAML is the custom unmarshal handler for this struct.
// It is primarily useful for setting the defaults.
// UnmarshalYAML is the custom unmarshal handler for this struct. It is
// primarily useful for setting the defaults.
func (obj *ConfigEtcdRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
type rawRes ConfigEtcdRes // indirection to avoid infinite recursion

View File

@@ -0,0 +1,296 @@
// 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
import (
"context"
"fmt"
"net/url"
"sync"
"github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/engine/traits"
"github.com/purpleidea/mgmt/util"
"github.com/purpleidea/mgmt/util/errwrap"
"github.com/hashicorp/consul/api"
)
func init() {
engine.RegisterResource("consul:kv", func() engine.Res { return &ConsulKVRes{} })
}
// ConsulKVRes is a resource that writes a value into a Consul datastore. The
// name of the resource can either be the key name, or the concatenation of the
// server address and the key name: http://127.0.0.1:8500/my-key. If the param
// keys are specified, then those are used. If the Name cannot be properly
// parsed by url.Parse, then it will be considered as the Key's value. If the
// Key is specified explicitly, then we won't use anything from the Name.
type ConsulKVRes struct {
traits.Base
init *engine.Init
// Key is the name of the key. Defaults to the name of the resource.
Key string `lang:"key" yaml:"key"`
// Value is the value for the key.
Value string `lang:"value" yaml:"value"`
// Scheme is the URI scheme for the Consul server. Default: http.
Scheme string `lang:"scheme" yaml:"scheme"`
// Address is the address of the Consul server. Default: 127.0.0.1:8500.
Address string `lang:"address" yaml:"address"`
// Token is used to provide an ACL token to use for this resource.
Token string `lang:"token" yaml:"token"`
client *api.Client
config *api.Config // needed to close the idle connections
once bool // safety token
key string // cache the key name to avoid re-running the parser
}
// Default returns some sensible defaults for this resource.
func (obj *ConsulKVRes) Default() engine.Res {
return &ConsulKVRes{}
}
// Validate if the params passed in are valid data.
func (obj *ConsulKVRes) Validate() error {
s, _, k := obj.inputParser()
if k == "" {
return fmt.Errorf("the Key is empty")
}
if s != "" && s != "http" && s != "https" {
return fmt.Errorf("unknown Scheme")
}
return nil
}
// Init runs some startup code for this resource.
func (obj *ConsulKVRes) Init(init *engine.Init) error {
obj.init = init // save for later
s, a, k := obj.inputParser()
obj.config = api.DefaultConfig()
if s != "" {
obj.config.Scheme = s
}
if a != "" {
obj.config.Address = obj.Address
}
obj.key = k // store the key
obj.init.Logf("using consul key: %s", obj.key)
if obj.Token != "" {
obj.config.Token = obj.Token
}
var err error
obj.client, err = api.NewClient(obj.config)
return errwrap.Wrapf(err, "could not create Consul client")
}
// Cleanup is run by the engine to clean up after the resource is done.
func (obj *ConsulKVRes) Cleanup() error {
if obj.config != nil && obj.config.Transport != nil {
obj.config.Transport.CloseIdleConnections()
}
return nil
}
// Watch is the listener and main loop for this resource and it outputs events.
func (obj *ConsulKVRes) Watch(ctx context.Context) error {
wg := &sync.WaitGroup{}
defer wg.Wait()
ch := make(chan error)
exit := make(chan struct{})
kv := obj.client.KV()
wg.Add(1)
go func() {
defer close(ch)
defer wg.Done()
opts := &api.QueryOptions{RequireConsistent: true}
innerCtx, cancel := util.ContextWithCloser(context.Background(), exit)
defer cancel()
opts = opts.WithContext(innerCtx)
for {
_, meta, err := kv.Get(obj.key, opts)
select {
case ch <- err: // send
if err != nil {
return
}
// WaitIndex = 0, which means that it is the
// first time we run the query, as we are about
// to change the WaitIndex to make a blocking
// query, we can consider the watch started.
opts.WaitIndex = meta.LastIndex
if opts.WaitIndex != 0 {
continue
}
if !obj.once {
obj.init.Running()
obj.once = true
continue
}
// Unexpected situation, bug in consul API...
select {
case ch <- fmt.Errorf("unexpected behaviour in Consul API"):
case <-ctx.Done(): // signal for shutdown request
}
case <-ctx.Done(): // signal for shutdown request
}
return
}
}()
defer close(exit)
for {
select {
case err, ok := <-ch:
if !ok { // channel shutdown
return nil
}
if err != nil {
return errwrap.Wrapf(err, "unknown %s watcher error", obj)
}
if obj.init.Debug {
obj.init.Logf("event!")
}
obj.init.Event()
case <-ctx.Done(): // signal for shutdown request
return nil
}
}
}
// 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
// again if Watch finds a change occurring to the state.
func (obj *ConsulKVRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
// XXX: use ctx for get and put
if obj.init.Debug {
obj.init.Logf("consul key: %s", obj.key)
}
kv := obj.client.KV()
pair, _, err := kv.Get(obj.key, nil)
if err != nil {
return false, err
}
if pair != nil && string(pair.Value) == obj.Value {
return true, nil
}
if !apply {
return false, nil
}
p := &api.KVPair{Key: obj.key, Value: []byte(obj.Value)}
_, err = kv.Put(p, nil)
return false, err
}
// Cmp compares two resources and return if they are equivalent.
func (obj *ConsulKVRes) Cmp(r engine.Res) error {
res, ok := r.(*ConsulKVRes)
if !ok {
return fmt.Errorf("not a %s", obj.Kind())
}
if obj.Key != res.Key {
return fmt.Errorf("the Key param differs")
}
if obj.Value != res.Value {
return fmt.Errorf("the Value param differs")
}
if obj.Scheme != res.Scheme {
return fmt.Errorf("the Scheme param differs")
}
if obj.Address != res.Address {
return fmt.Errorf("the Address param differs")
}
if obj.Token != res.Token {
return fmt.Errorf("the Token param differs")
}
return nil
}
// inputParser parses the Name() of a resource and extracts the scheme, address,
// and key name of a consul key. We don't have an error, because if we have one,
// then it means the input must be a raw key. Output of this function is scheme,
// address (includes hostname and port), and key. This also takes our parameters
// in to account, and applies the correct overrides if they are specified there.
func (obj *ConsulKVRes) inputParser() (string, string, string) {
// If the key is specified explicitly, then we're not going to parse the
// resource name for a pattern, and we use our given params as they are.
if obj.Key != "" {
return obj.Scheme, obj.Address, obj.Key
}
// Now we parse...
u, err := url.Parse(obj.Name())
if err != nil {
// If this didn't work, then we know it's explicitly a raw key.
return obj.Scheme, obj.Address, obj.Name()
}
// Otherwise, we use the parse result, and we overwrite any of the
// fields if we have an explicit param that was specified.
k := u.Path
s := u.Scheme
a := u.Host
//if obj.Key != "" { // this is now guaranteed to never happen
// k = obj.Key
//}
if obj.Scheme != "" {
s = obj.Scheme
}
if obj.Address != "" {
a = obj.Address
}
return s, a, k
}

View File

@@ -0,0 +1,83 @@
// 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
import (
"fmt"
"testing"
"github.com/purpleidea/mgmt/engine"
)
func createConsulRes(name string) *ConsulKVRes {
r, err := engine.NewNamedResource("consul:kv", name)
if err != nil {
panic(fmt.Sprintf("could not create resource: %+v", err))
}
res := r.(*ConsulKVRes) // if this panics, the test will panic
return res
}
func TestParseConsulName(t *testing.T) {
n1 := "test"
r1 := createConsulRes(n1)
if s, a, k := r1.inputParser(); s != "" || a != "" || k != "test" {
t.Errorf("unexpected output while parsing `%s`: %s, %s, %s", n1, s, a, k)
}
n2 := "http://127.0.0.1:8500/test"
r2 := createConsulRes(n2)
if s, a, k := r2.inputParser(); s != "http" || a != "127.0.0.1:8500" || k != "/test" {
t.Errorf("unexpected output while parsing `%s`: %s, %s, %s", n2, s, a, k)
}
n3 := "http://127.0.0.1:8500/test"
r3 := createConsulRes(n3)
r3.Scheme = "https"
r3.Address = "example.com"
if s, a, k := r3.inputParser(); s != "https" || a != "example.com" || k != "/test" {
t.Errorf("unexpected output while parsing `%s`: %s, %s, %s", n3, s, a, k)
}
n4 := "http:://127.0.0.1..5:8500/test" // wtf, url.Parse is on drugs...
r4 := createConsulRes(n4)
//if s, a, k := r4.inputParser(); s != "" || a != "" || k != n4 { // what i really expect
if s, a, k := r4.inputParser(); s != "http" || a != "" || k != "" { // what i get
t.Errorf("unexpected output while parsing `%s`: %s, %s, %s", n4, s, a, k)
}
n5 := "http://127.0.0.1:8500/test" // whatever, it's ignored
r5 := createConsulRes(n3)
r5.Key = "some key"
if s, a, k := r5.inputParser(); s != "" || a != "" || k != "some key" {
t.Errorf("unexpected output while parsing `%s`: %s, %s, %s", n5, s, a, k)
}
}

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