515 Commits

Author SHA1 Message Date
James Shubin
e40819d617 modules: Add a small stub for CUPS
This is definitely not perfect, but it's a simple stub which we can
expand on.
2025-01-31 00:52:24 -05:00
James Shubin
7331d3a7ee util: pprof: Improve pprof for ease of use
This makes it slightly cleaner.
2025-01-31 00:00:05 -05:00
James Shubin
95f353c6a4 modules: virtualization: Add some simpler helpers
You don't need much to build a vm host. Here's the start.
2025-01-30 23:04:12 -05:00
James Shubin
5044ef4e8a engine: resources: Add a virt builder password selector
This is very helpful for debugging, particularly with broken Debian
installs.
2025-01-30 22:56:24 -05:00
James Shubin
3c61d088ab test: Make sure to test the docs
I can't believe I forgot to enable this earlier.
2025-01-29 12:18:55 -05:00
James Shubin
315a493565 lang: Add a few more tests 2025-01-26 19:48:17 -05:00
James Shubin
6268b61a7d lang: core: Lookup function (with default) can be more precise 2025-01-26 19:12:13 -05:00
James Shubin
3f202c6a7a lang: core: Fix struct lookup corner case
We forgot to reject this corner case which could lead to a runtime error
since the expected type from the incoming struct would not match what
we're handling.
2025-01-26 19:12:13 -05:00
James Shubin
d46c43df5a lang: core: Let lookup function specialize earlier
If we happen to know some information, we can specialize early and help
type unification solve things.
2025-01-26 18:21:54 -05:00
James Shubin
1538befc93 lang: ast, parser: Allow calling anonymous functions
I forgot to plumb this in through the parser. Pretty easy to add,
hopefully I didn't forget any weird corner scope cases here.
2025-01-26 17:21:11 -05:00
James Shubin
1af334f2ce misc: Update to golang 1.23
The 1.22 version has some issues that makes it more difficult to
workaround, so we'll move right away to 1.23
2025-01-26 17:17:02 -05:00
James Shubin
d30ea571f1 misc: Update to golang 1.22
Ran:

go get -u ./...
go mod tidy

We also got rid of travis and simplified things a bit.
2025-01-26 17:16:40 -05:00
James Shubin
d30ff6cfae legal: Remove year
Instead of constantly making these updates, let's just remove the year
since things are stored in git anyways, and this is not an actual modern
legal risk anymore.
2025-01-26 16:24:51 -05:00
James Shubin
1d3f2dbe3c util: Add a pprof helper package
As it turns out, there are other helper packages out there. I wrote this
before I realized that, and it didn't take me that long anyways.
2025-01-24 03:06:31 -05:00
James Shubin
ca6e7ad432 lang: types, lang: core: strings: Add a new join_nonempty function
Along with a helper function for when we need a []string from a Value.
2025-01-24 00:07:23 -05:00
James Shubin
f92afe9ae4 modules: meta: Add a router meta module
Useful for setting up simple routers. Some auto-provisioning polish
would definitely help, but this is pretty useful already.
2025-01-18 01:22:33 -05:00
James Shubin
483cc22c32 lang: core: embedded: provisioner: Add IPXE support
This lets you boot from ipxe. You can run the ipxe shell from their
stock image or the netboot.xyz one. For the latter, press "m", then type
"dhcp" (machine is now pingable!) then type "route" to check the ip.

To boot type:

chain http://192.168.42.1:4280/menu.ipxe

and you're off!

Thanks to frebib for finding the workaround to the VFS bug. The answer
is you need to run the imgfree command to unblock the initrd.
2025-01-18 01:07:19 -05:00
James Shubin
2f3bd72491 lang: core: embedded: provisioner: Fix regression in Fedora 41
Unfortunately with Fedora 41 and DNF5, this breaks what our host machine
expects. So you now need dnf5+ to make this work, or you can revert this
commit.
2025-01-18 01:02:17 -05:00
James Shubin
6499fcb1e0 modules: dhcp: Support the authoritative setting
Quite useful if we plumb it all the way through!
2025-01-18 00:30:46 -05:00
James Shubin
12a0600d38 engine: resources: Improve hostname change message
Make it clearer where empty strings are, and that it already happened.
2025-01-17 23:58:26 -05:00
James Shubin
cace2bacb8 lang: core: embedded: provisioner: Add support for module path
This is needed for bigger code bases. Remember: this is consulted at the
deploy stage, and the deploy contains the entire tree (including
modules) of everything it needs to run. This is why you don't need to
add a --module-path arg when running mgmt in systemd with the "empty"
frontend.
2025-01-17 23:54:30 -05:00
James Shubin
05d440114a lang: core: embedded: provisioner: Remove stale comment 2025-01-17 23:49:21 -05:00
James Shubin
b392285e1d gapi, lang: gapi: Make dir to avoid errors
It seems we sometimes need to make the intermediate dir.
2025-01-17 23:32:13 -05:00
James Shubin
a713c08585 engine: resources: Make net and firewalld resources quieter
They are too noisy!
2025-01-17 23:32:13 -05:00
James Shubin
8e8e831e73 modules: misc: Switch type to list of strings
The DNS entry should be a list of strings. We would have caught this
earlier but this helped us find a type unification issue.
2025-01-17 23:28:05 -05:00
James Shubin
86b95b2c0b modules: shorewall: Fix type on stoppedrules file 2025-01-17 22:12:55 -05:00
James Shubin
4a578ca40c lang: core: embedded: provisioner: Handoff a hostname
This makes it easier to run config tools if the hostname is already set.
2025-01-17 19:48:23 -05:00
James Shubin
a60148f370 lang: core: net: Add more ip utilities 2025-01-17 18:58:21 -05:00
James Shubin
00366de67b modules: purpleidea: Update my personal packages 2025-01-17 18:57:18 -05:00
James Shubin
a08ba0b0e9 engine: resources: Tar now accepts dirs without a trailing slash
If these are found, then the dir path itself is copied in as well.
2025-01-17 18:15:45 -05:00
James Shubin
81b102ed7f lang: ast: Allow multiple star imports
If more than one star import is present in the same scope, allow it. If
one star import could overwrite something, ordering is not guaranteed.
We allow this for now, but we might create a compiler fix to stop it.
This adds a test to notice both of these behaviours.
2025-01-17 14:03:48 -05:00
James Shubin
c8f911ec5d lang: core: embedded: provisioner: Fix panic
The conditions changed for this one, make it stricter.
2025-01-17 12:35:30 -05:00
James Shubin
7694da4241 lang: core: embedded: provisioner: Skip bmc if empty 2025-01-17 12:34:27 -05:00
James Shubin
a0d500a602 lang: interfaces: Remove dangerous init method
This can cause the source to get overwritten and changed and is usually
unnecessary.
2025-01-04 20:56:11 -05:00
James Shubin
553172992f lang: parser: Typo fix 2025-01-04 20:55:41 -05:00
James Shubin
e6d614f4dd engine: resources: Add a bmc resource
This resource manages bmc devices in servers or elsewhere. This also
integrates with the provisioner code.
2025-01-03 18:19:31 -05:00
James Shubin
3107dfbd08 modules: shorewall: Small fixups to improve the module
These are some common fixes and improvements for normal shorewall usage.
As we shake out more uses of this, we find small issues. This lets us
have long rules, and a better default config.
2025-01-02 15:43:27 -05:00
James Shubin
802823dcb0 modules: misc: Support VIP's in network config 2025-01-02 15:42:40 -05:00
James Shubin
5858c8b501 pgraph: Panic if vertex is nil
These should be caught early.
2024-12-18 13:49:13 -05:00
James Shubin
2561dba8f5 lang: Increase debugging timeout 2024-12-15 10:39:46 -05:00
James Shubin
f5806e0617 lang: funcs: structs: Add Call and CallStruct methods for composite
It might turn out the CallStruct is the API we want. This will depend on
the future iterations of the function engine.
2024-12-08 17:43:41 -05:00
James Shubin
e9dbb7b86c lang: funcs: structs: Add Call method for const 2024-12-08 16:48:04 -05:00
James Shubin
28f5b8331a lang: funcs: structs: Improve naming
These could print nicer for debugging.
2024-12-08 16:24:42 -05:00
James Shubin
5ff4f0456a lang: Add mode to overwrite tests
This is useful if we need to reformat a bunch of previously passing
tests.
2024-12-08 15:34:28 -05:00
James Shubin
82c614f2d9 engine: resources: Workaround broken debian package when building images 2024-12-06 15:59:39 -05:00
James Shubin
50265d2303 engine: resources: Pull out a distro function for guest 2024-12-06 15:58:51 -05:00
James Shubin
ecee84aa28 docs: New blog post about modules and imports 2024-12-03 03:00:21 -05:00
James Shubin
2e146e8c8e engine: resources: Fix up some issues with cron
This really needs looking at again, since it's so old and buggy or
broken.
2024-12-03 01:52:23 -05:00
James Shubin
097efdd66a engine: graph: autoedge: Clean up redundant logs
They repeat themselves, this is cleaner.
2024-12-03 00:56:22 -05:00
James Shubin
5764c977f1 modules: misc: Don't ignore the router setting
This mistake caused us to ignore the router setting when we wanted it!
Woops =D
2024-12-03 00:43:32 -05:00
James Shubin
4d30772b3b lang: Add test for unused include statements
Even if they have no side effect, they aren't legal, since it would be
surprising if suddenly something new got added in one which then broke
the imports.
2024-11-28 19:17:33 -05:00
James Shubin
8472b1ebf2 lang: gapi: Convenience the user by allowing relative dirs in cli 2024-11-28 19:17:17 -05:00
James Shubin
e1070d3e13 lang: ast, download: Improve error messages 2024-11-28 19:17:17 -05:00
James Shubin
98d7f294eb lang: download: Improve git reliability
Fix a small panic that could happen if we had a bad clone, and make
visibility into this operation better. We also make room for future
context cancellation since the library now supports this.
2024-11-28 16:38:33 -05:00
James Shubin
517fc1e05b lang: gapi: Remap the module path correctly
If we've set the --module-path arg, then we expect to rebase that path
out of the deploy, and instead it should show up as /modules/ which is
the standard. Handle this scenario.
2024-11-28 16:29:33 -05:00
James Shubin
c2f75d64a6 util: arch: The Any value should be the same everywhere
In 80e8c9cadc when this was ported, the
"Any" value diverged accidentally. This would cause some packages to not
be found, since they didn't match any arch.

Thanks to karpfen to digging into the issue.
2024-11-28 14:55:30 -05:00
James Shubin
380004b1dc readme, docs: New docs available 2024-11-23 01:25:40 -05:00
James Shubin
28a443d11d docs: Add a hack for golang functions 2024-11-22 14:20:24 -05:00
James Shubin
a600e11100 cli, docs: Add a docs command for doc generation
This took a lot longer than it looks to get right. It's not perfect, but
it now reliably generates documentation which we can put into gohugo.
2024-11-22 14:20:16 -05:00
James Shubin
7b45f94bb0 lang: core: Remove the unnecessary func suffix
We don't really need these, it's clear what things are.
2024-11-22 01:18:19 -05:00
James Shubin
acdd6476f2 test: Remove empty variable
Copy-pasta bug!
2024-11-21 23:49:32 -05:00
James Shubin
018d3efc90 lang: funcs: Move standalone functions into core
Everything should be all together.
2024-11-21 22:56:17 -05:00
James Shubin
b40d10a366 util: Add a generic map key and value swapping function
Fun little utility function which is useful.
2024-11-21 02:54:10 -05:00
James Shubin
a88034ab06 modules: misc: Add standard header 2024-11-20 23:47:17 -05:00
James Shubin
907d2ad1a1 modules: dhcp: Add an mgmt module for managing dhcpd
This is not perfect, but it's a good start, and it shows how a module
might be structured.
2024-11-18 15:12:01 -05:00
James Shubin
3bd6986fde modules: shorewall: Add an mgmt module for managing shorewall
This is not perfect, but it's a good start, and it shows how a module
might be structured.
2024-11-18 15:11:31 -05:00
James Shubin
43bd847bad modules: misc: Improvements on ip address setting 2024-11-08 14:12:02 -05:00
James Shubin
0c0583adc8 modules: misc: Add network manipulation helpers
This is common functionality which we might want to use on new machines.
2024-11-06 22:13:31 -05:00
James Shubin
c642b5eeae lang: core: net: Add new function to get cidr prefix 2024-11-06 21:10:04 -05:00
James Shubin
69e84fbbed engine: resources: cron: Add ctx where possible.
Lots of the API's here now support this. Here's an example, work on the
others too.
2024-11-06 21:09:50 -05:00
James Shubin
f8b06f32ec engine: resources: Remove unused wait group 2024-11-06 21:09:50 -05:00
James Shubin
59a20f53eb lang: core: sys, engine: resource: Update hostname functionality
We didn't have a solid resource and sys.hostname() didn't have events!
2024-11-06 21:09:50 -05:00
James Shubin
83fd8b7e54 engine: util: Add more cmp utility functions 2024-11-06 20:02:10 -05:00
James Shubin
098ab20ec9 lang: gapi: Duplicates are possible if we have a diamond dag
Allow this, just remove them...
2024-11-06 20:02:10 -05:00
James Shubin
a2ce9e890d lang: core: net: Add a way to get the machine mac addresses 2024-11-05 14:55:58 -05:00
James Shubin
be7a5399e3 lang: core: util: Add hostname mapper function
This adds a new util package with some useful functionality which could
be implemented as pure mcl, but instead we add it here as a good place
to help with code reuse.
2024-11-05 14:55:58 -05:00
James Shubin
3fb492f6aa util: Add a TLS helper
Make it easier to build TLS stuff in pure golang.
2024-11-01 19:41:35 -04:00
James Shubin
e4f062b006 engine: resources: Parse distro properly
I regressed here when patching this. Here's the fix. Would be beautiful
to have hardware to run end-to-end testing on. If you want to sponsor
this, please let me know!
2024-10-30 16:04:01 -04:00
James Shubin
422719c345 lang: core: map: Add functions to extract keys and values
Simple stuff, but now it's done!
2024-10-30 00:58:10 -04:00
James Shubin
71a1efde99 examples: tls: Add a simple TLS example
Was useful for testing things...
2024-10-30 00:41:26 -04:00
James Shubin
ed84c5460c lang: core: embedded: provisioner: Workaround bad mirrors
With the release of Fedora 41, I was getting lots of mirror errors.
Hopefully this helps make it more robust. It was failing repeatedly
while trying to download packages, and I kept having to restart things,
but once I added this option things worked. Hopefully they're related.
2024-10-29 18:53:58 -04:00
James Shubin
0222a682fc lang: core: embedded: provisioner: Not sure we need this package
Seems to cause some issues. Remove it for now.
2024-10-29 18:53:36 -04:00
James Shubin
1cd4af5838 lang: core: embedded: provisioner: Keep this message separate
Don't autogroup it with others.
2024-10-29 16:58:06 -04:00
James Shubin
d1aaf6e82b lang: core: embedded: provisioner: Handle spurious failures
Not sure why this happen, I think it's just random network blips. Simple
retry should be used for now.
2024-10-29 16:58:06 -04:00
James Shubin
52a71f9515 lang: core: embedded: provisioner: Add unused repo generation
I was playing around with generating repo's, but they didn't turn out to
be needed at this time. Committing this anyways for future reference.
2024-10-29 16:58:06 -04:00
James Shubin
3c665174cc lang: core: embedded: provisioner: Implement handoff
Here's a good first way to implement handoff. What's particularly
elegant about handoff here, is that this is the first form of it I know,
where handoff happens between a provisioning tool and a configuration
management tool and those are the same tool! As a result, this can allow
for some really elegant integration, and the end-user never has to deal
with the combinatorial explosion of the N * M scenario of gluing each
provisioning tool to each different configuration management tool.

We'll have other forms of handoff in the future, but this simple
approach is useful already.
2024-10-29 16:58:06 -04:00
James Shubin
93eb8b2b76 lang: core: embedded: provisioner: Host an available deploy
This makes the current deploy available. This is likely not useful when
this is used from the embedded provisioner cli tool, since it would
contain cli functions that won't run, but it is useful when it's used as
a library.
2024-10-29 16:42:15 -04:00
James Shubin
1692235498 lang: core: embedded: provisioner: Log output of post
This is useful for debugging and for knowing what we really did to the
machine.
2024-10-29 16:42:15 -04:00
James Shubin
a6bcd4b92b lang: core: embedded: provisioner: Reduce amount of log noise
We don't need to refresh the leases so often.
2024-10-29 16:42:15 -04:00
James Shubin
d065cddf5e lang: core: embedded: provisioner: Remove old package
Doesn't seem to be used for anything at the moment.
2024-10-29 16:42:15 -04:00
James Shubin
20d4809e8e engine: resources: Print netmask nicely for our DHCP resource
Makes it easier to see what's going on.
2024-10-29 16:42:15 -04:00
James Shubin
b074386c26 cli: Add setup and firstboot commands
This adds two new top-level commands: setup and firstboot.

Firstboot is pure-golang implementation of a service that runs some
commands once when a system first boots. You need to install this
service, and put the scripts to run in a special directory. This is
inspired by the virt-builder --firstboot mechanism.

Setup is a general purpose command that makes it easy to setup certain
facilities on a new machine. These include the mgmt package dependencies
it might need, a service to run it from, and the necessary service to
use the mgmt firstboot service as well.

All of this has been built to facilitate handoff between provisioning a
new machine and running configuration management on it.
2024-10-29 16:42:15 -04:00
James Shubin
b140b2dfeb util: Move executable path finding into a helper function 2024-10-29 16:41:37 -04:00
James Shubin
8e3d959500 util: We prefer to append rather than truncate
This makes this utility function more useful.
2024-10-29 16:41:37 -04:00
James Shubin
8c886bbe7c util: Nil input to our simple cmd helper should be allowed
Don't panic here!
2024-10-29 16:41:37 -04:00
James Shubin
7d204dfb74 util: Add a simple append file write function
Similar to the golang os lib version except we append.
2024-10-29 16:39:46 -04:00
James Shubin
583f90dc7b util: distro: Rename functions to avoid golang stutter warning 2024-10-25 02:56:12 -04:00
James Shubin
85e1d6c0e8 engine: resources: Make sure to set the netmask
Some clients would DECLINE if this was not set. This was reproduced my
using the stock coredhcp DHCPv4 server and disabling the netmask plugin.
One of the clients that would DECLINE is a Lenovo ThinkCentre m90n doing
a UEFI (PXE) netboot.

This was found in 1327752725 and is
hopefully now completely fixed!
2024-10-25 00:57:50 -04:00
James Shubin
2c967e3897 util: Add a simple template system for systemd unit files
Just the basics for what we need, nothing more. Not intended as a
general-purpose library for use elsewhere.
2024-10-24 17:29:00 -04:00
James Shubin
202a8e1fba util: Add a small helper to exec commands simply 2024-10-18 10:02:29 -04:00
James Shubin
e6085d77ff util: Add an flock utility for lock file type things
Useful for ensuring only one binary runs at the same time.
2024-10-18 10:02:29 -04:00
James Shubin
10f82c6566 lang: core: list: Add a concat function
It works with arbitrary numbers of arguments too!
2024-10-18 10:02:29 -04:00
James Shubin
3d11b2caaf lang: core: deploy: Add a function to help in obtaining bootstrap deps
This should make it easier to implement handoff.
2024-10-15 20:54:33 -04:00
James Shubin
f8037a1f99 lang: types: Add a small helper function for common type conversions 2024-10-15 20:53:39 -04:00
James Shubin
067eef9007 util: distro, engine: resources: Update virt-builder res
A little easier to maintain if we support more distros eventually.
2024-10-15 20:36:51 -04:00
James Shubin
e45d9be065 util: distro: Parse the os-release file 2024-10-15 20:36:51 -04:00
James Shubin
d24149518c util: distro: Refactor family and distro code
I hate writing abstraction code like this, but I'm hoping it will be
useful.
2024-10-15 20:36:50 -04:00
James Shubin
d403f18b2a util: distro: Put distro specific data in this util package
Try and see if we can put all our distro specific stuff in here...
2024-10-15 19:04:17 -04:00
James Shubin
1f12150d8f engine: resources, examples: lang: Expand on future deploy ideas
This is one idea for reference. But I doubt this is needed anytime soon
since we have a good working solution in the examples.
2024-10-13 20:47:47 -04:00
James Shubin
d3a7cefcc6 engine: resources: Add an archiving, deploy resource
This makes a bundle out of the code in the current deploy. Hopefully
this is useful for handoff!
2024-10-13 16:44:23 -04:00
James Shubin
a8c8f09aa3 gapi: Plumb through a URI mechanism
This is at least a stop-gap until we redo the whole filesystem API mess.
I think golang is partly to blame because they don't have proper API's
merged yet.
2024-10-13 16:40:50 -04:00
James Shubin
b03fdeccae lang: interfaces: Nil input means no args 2024-10-07 00:00:19 -04:00
James Shubin
6c12e8a29b lang: gapi: The module path needs separate rebasing
If we're using a module path which doesn't share the same root, rebase
it separately.
2024-10-07 00:00:19 -04:00
James Shubin
310452542b lang: gapi: Rebase to the common path prefix
When the modules dir is not within the main base, we don't correctly
choose the common base path. As a result, we should choose something
common for our internal path representation.
2024-10-05 01:02:03 -04:00
James Shubin
b514022713 util: Add some path manipulation algorithms
These could use some optimization by an algorithmist! Not urgent right
now since they're not currently in any fast paths in the code.
2024-10-05 01:02:03 -04:00
James Shubin
c937280664 engine: local, lang: core: local: Add a pool function
This adds a new local API for pool allocation, and with it a
corresponding function in the "local" import.
2024-10-04 22:37:39 -04:00
James Shubin
898b58e3e7 lang: core: strings: Add a substring function named substr 2024-10-03 13:49:08 -04:00
James Shubin
74119a0a53 lang: core: strings, util: Add left and right padding functions
Golang didn't want these in the standard library, but they are useful.
2024-10-03 13:49:08 -04:00
James Shubin
d6914d3437 lang: core: net: Improve formatting of the mac functions 2024-10-03 13:49:08 -04:00
James Shubin
fdfa03685c lang: core: net: Add is_mac function 2024-10-02 14:31:20 -04:00
James Shubin
149a85fcde modules: misc: Ensure the ssh key has a folder 2024-09-29 00:53:11 -04:00
James Shubin
65f26769ae lang: core: net: Add a cidr to mask function
And tidy things up slightly to remove buggy tests.
2024-09-28 22:54:06 -04:00
James Shubin
6397c8f930 lang: Clean up import logs
We get a lot of useless noise here, make it neater.
2024-09-28 21:56:53 -04:00
James Shubin
761030b5b8 modules: misc: Make the ssh keygen module more useful 2024-09-26 12:52:27 -04:00
James Shubin
9a752da13d modules: misc: Add a small helper module
Maybe I'll collect enough small snippets that I can keep them in here
until they get split out elsewhere to more appropriate places.
2024-09-26 12:43:26 -04:00
James Shubin
13fc711657 util: Expanding home directory should preserve trailing slash
Because we consider slashes as a directory identifier when needed.
2024-09-26 12:29:43 -04:00
James Shubin
6419f931ee engine: resources: Add a virt-builder resource
This wraps the excellent virt-builder utility which makes setting up new
virtual machines a breeze.
2024-09-26 11:47:40 -04:00
James Shubin
562138cb74 lang: core: os: Add expand_home function
Simple helper that works with ~/foo/ or ~james/foo/ type patterns.
2024-09-25 20:31:18 -04:00
James Shubin
8aac770bcb engine: resources: pkg: Add a small helper for simple installs
Add this utility function for doing single package installs.
2024-09-25 16:26:32 -04:00
James Shubin
80e8c9cadc util: arch: Use small arch util library
Put all of the arch stuff that we can into this library.
2024-09-25 16:25:55 -04:00
James Shubin
87b3dda867 readme: Fixup typo
Reported in GH#775.
2024-09-19 13:22:00 -04:00
James Shubin
b9e093cd6b engine: resources: svc: Reduce unnecessary logging 2024-09-18 21:47:29 -04:00
James Shubin
06a023ca66 engine: resources: sysctl: Be more careful about dir path
More validation is not bad!
2024-09-18 21:39:33 -04:00
James Shubin
ccb4c6244d engine: resources: exec: Improve the docs for a common scenario
I knew this, and now you know it too!
2024-09-18 21:39:03 -04:00
James Shubin
4489e5ce6e engine: graph: autoedge: Quiet down the useless logs
These are so useful, let's silence them.
2024-09-18 21:38:29 -04:00
James Shubin
8df82f0301 docs: Add a new faq entry about deploy.readfile
This may be a common thing people forget.
2024-09-18 21:38:08 -04:00
James Shubin
57b4a7efce lib, engine: graph: Let children directories be readable
We want to be able to put useful scripts in $vardir type places, but if
the perms at the higher levels block this, then that can't work. The
top-level should always be more permissive, and then it grows more
restricted as we descend.
2024-09-18 21:03:58 -04:00
James Shubin
fd508fbc0d engine: resources: Fix typo in svc
It's a typo, right?
2024-09-17 18:10:13 -04:00
James Shubin
a4f368fc9f engine: resources: Add a sysctl resource
Very useful since these are easy to forget!
2024-09-15 23:07:03 -04:00
James Shubin
e7b57a32fd engine: resources: Add a tar resource
This makes tar archives from a list of files and/or directories. It
correctly includes empty directories as well. The code structure is
similar to the gzip resource. While this resource is arguably more
useful than gzip, it was invaluable to write the gzip resource first
since that made writing this one much easier.
2024-09-13 20:04:53 -04:00
James Shubin
06cc63fcb6 util: recwatch: Add a helper function for merging these
I should really rework the recwatch package and API, but I wasn't in the
mood to touch this code today, so this will have to do for now.
2024-09-13 19:48:00 -04:00
James Shubin
e34212a10b engine: resources: gzip: Check unhandled error
This is probably inconsequential, but let's do it since it's not in a
defer.
2024-09-13 19:48:00 -04:00
James Shubin
5f6e07b5e8 engine: resources: gzip: Fix typo 2024-09-13 16:24:01 -04:00
James Shubin
1465c5cdc9 engine: resource: gzip: Remove unneeded waitgroup
I think this was a copy+pasta mistake.
2024-09-13 16:13:03 -04:00
James Shubin
29eebd0d07 lang: core: Move template to golang namespace
I don't think this template function should be in any way authoritative,
so let's namespace it.
2024-09-13 15:51:24 -04:00
James Shubin
5bbc06d8bc engine: resources: Add new gzip resource
This may have lots of uses, particularly for bootstrapping and handoff
if we want to compress payloads. It is also a good model resource for
how to implement such a resource to avoid re-computing the result on
every CheckApply call. Of course if the computation is cheaper than the
hashing of the data this isn't the optimal approach.
2024-09-13 03:32:10 -04:00
James Shubin
9a5f6a5bd3 lang: core, funcs: Use the correct zero type
I wasn't using the correct contained type here.
2024-09-10 23:23:00 -04:00
James Shubin
2e774215e4 lang: core: deploy: Add a function to get the binary path
Useful for bootstrapping new machines.
2024-09-10 23:22:53 -04:00
James Shubin
1327752725 engine: resources: Special log message for unhandled decline
Not sure why a PXE client is sending these... Not sure if it's buggy
firmware or my inability to handle a DHCP corner case.
2024-09-10 23:22:47 -04:00
James Shubin
118f266211 lang: core: local: Add a new vardir function
This gives us a function to return a created vardir folder. It is not
locally namespaced, and a future function will have to namespace one to
each scope.
2024-09-09 18:04:18 -04:00
James Shubin
87a2dfc8f9 engine: local: Add a vardir API to our local API collection 2024-09-09 17:41:12 -04:00
James Shubin
b88ac4603f lang: interfaces Add CallableFunc interface
Add a new interface for callable functions. This will likely be useful
for future versions of the function engine and for the "timeless" work.
2024-09-09 15:49:57 -04:00
James Shubin
28e81bcca3 modules: Add a modules directory for mcl code
Details in the README file.
2024-09-09 15:14:33 -04:00
James Shubin
3d0660559e examples: lang: Add a join example
Good reminder that a lot of the golang stdlib functions are available.
2024-09-09 14:54:45 -04:00
James Shubin
48dc9ad099 test: shell: Disable another flaky test
We need to fix these all eventually, but that day is not today.
2024-09-06 16:24:28 -04:00
James Shubin
fd3a2a1f0f engine: resources: Make consul optional
Licensing has made this non-free. Let's put that behind a build tag for
now, and remove it entirely if no suitable libre replacement is found.
2024-09-03 20:26:38 -04:00
James Shubin
c6e9175e3f engine: resources: Add missing build tag 2024-09-03 20:21:31 -04:00
James Shubin
1a39472734 lang: core: embedded: provisioner: Sometimes this is used
We need better overview of all the PXE/netboot stuff, probably we should
read a spec, but until an expert comes along, we'll have to proceed
incrementally.
2024-09-03 14:48:38 -04:00
James Shubin
bfa88e9b1c engine: resources: Workaround regression in wget2
Apparently wget2 has a serious regression that the HTTP 102 header
throws it off... So let's not send this for now... I'm pretty unhappy
about this, wget used to always be rock solid. Maybe curl deserves a
chance? (This works fine with curl btw.)
2024-08-30 20:33:41 -04:00
James Shubin
a0972c0752 lang, engine: Add a metaparam for catching accidental dollar signs
Let's make our life easier for users!
2024-08-22 20:41:48 -04:00
James Shubin
8dc0d44513 lang: Add an extra fail scenario to our test suite
Let's us write tests for Validate failures.
2024-08-22 20:12:59 -04:00
James Shubin
8594b6e2a9 lang: funcs: Hint the struct_lookup functions better
If we have static information, use it to help unification.
2024-08-21 19:00:51 -04:00
James Shubin
82cac572ca lang: core: fmt: Allow type unification variables for format
This allows some simple cases.
2024-08-21 19:00:50 -04:00
James Shubin
da4f69cd87 lang: ast, core: fmt: Allow unification variables for fmt
This lets us pass through unification variables into the fmt function. I
hope this doesn't break anything, but it's worth trying for now.
2024-08-21 18:52:24 -04:00
James Shubin
e6cb776eb6 lang: ast, core: fmt: Catch invalid nil signatures
We accidentally had a bad error triggered.
2024-08-21 18:50:11 -04:00
James Shubin
7557114b4e lang: ast: Don't send empty ord names for partials
We would accidentally send some empty partials, woops! This reinforces
my belief that we should never pre-allocate list size unless we notice a
performance issue.
2024-08-21 18:00:44 -04:00
James Shubin
001e1a5da0 lang: Remove some error wrapping
Makes errors cleaner to read. The extra context wasn't very helpful.
2024-08-18 19:07:27 -04:00
James Shubin
6f3c3c318b lang: core: Shorten functions with wrapper
This demonstrates how to write a function with the wrapper. Note that
you must not include Init if you're not calling the nested wrapper
function.
2024-08-18 18:29:01 -04:00
James Shubin
654e376be7 lang: core: Add list and map packages
Put the common functionality for those types in there.
2024-08-18 18:28:26 -04:00
James Shubin
211121cdca lang: funcs: Use correct constant 2024-08-18 17:31:58 -04:00
James Shubin
f2d4cac92d docs: Add a short contributing guide
I think this is all common sense, but I thought it might be helpful for
anyone that might not be well-versed with how such projects run.
2024-08-16 23:57:38 -04:00
James Shubin
c5dc9c7650 docs: Add a guide for writing API services
Hopefully this is useful to companies who want to design their services
properly to support modern tooling.
2024-08-16 23:38:27 -04:00
James Shubin
7596f5b572 lang: core: os: Add family functions and variables
Make it easier to do os-specific stuff.
2024-08-07 17:30:15 -04:00
James Shubin
8e9c3b6c1e lang: funcs: vars: Include system package variables in the scope 2024-08-07 17:30:13 -04:00
James Shubin
a93c98402a lang: ast: Add better logging about scope issues
This may help out programmers who aren't sure what's going on.
2024-08-07 17:17:57 -04:00
James Shubin
b04ee4ba22 lang: ast: Pass through the data field for vars 2024-08-07 17:17:57 -04:00
James Shubin
65b104ea55 lang: ast: Split off helpers into util file 2024-08-07 17:17:57 -04:00
James Shubin
562eb643fc engine: resources: Display bytes copied when making a file 2024-08-06 15:24:45 -04:00
James Shubin
80178422db engine: graph, resources: Clean up log messages
The idea is to have a better user experience in the terminal.
2024-08-06 15:12:10 -04:00
James Shubin
e94f39bf2c engine: resources: Cleanups to the svc resource
Some new API's exist that take a context now too!
2024-08-06 14:31:39 -04:00
James Shubin
6c1a33066a engine: resources: The svc resource should reload on notification
Missing feature that is finally landing. I wish this wasn't needed, but
we need fancier plumbing to avoid it.
2024-08-06 14:22:15 -04:00
James Shubin
beca0c3ae6 engine: resources: Plumb through the context and constants
Basically a small cleanup.
2024-08-06 14:13:39 -04:00
James Shubin
7517c83953 engine: resources: Add the systemd service constants
Basically a cleanup to avoid duplicate strings everywhere. This makes it
easier to follow the code too.
2024-08-06 14:02:17 -04:00
James Shubin
0354082f89 engine: resources: Allow symbolic modes for missing files
In 83a747794e a bug was introduced with
the implementation of symbolic modes, that would prevent a file resource
from passing the Validate step if you were using a symbolic mode, and
the file didn't already exist. If you didn't use symbolic modes and
those files weren't absent, then you wouldn't have noticed.

It might be worth looking into the API for symbolic parsing as well.
2024-08-06 13:24:25 -04:00
James Shubin
4abcd9cf01 lang: core: Quiet down the template function by default
We don't need to know this most of the time.
2024-08-01 20:54:55 -04:00
James Shubin
c974820c56 engine: resources: Add log messages for chmod and chown 2024-08-01 20:32:56 -04:00
James Shubin
88670ae7a1 engine: resources: Improve output of log messages
I don't remember ever having this display a pointer address, but it is
now, so let's make this cleaner.
2024-08-01 18:44:44 -04:00
James Shubin
d0ed004b24 examples: lang: Test that each of the mcl examples compiles
We let these rot, so fixup the issues and test them!
2024-07-31 17:29:42 -04:00
James Shubin
6de7d8b254 lang: funcs: Catch non-specific type build error
If you had ambiguous code, and specified an invalid type, this could
sneak through and become a runtime error, instead of a compile-time
error. We fix this and add a test.
2024-07-31 17:29:42 -04:00
James Shubin
bfb5d983c1 lang: types, unification: Don't recurse into private fields
We forgot to omit looking deeper into private struct fields. I don't
know why we didn't catch this earlier, I can only assume some subtlety
changed, since we've previously used many of the resources this would
fail on. Maybe golang broke some API that they didn't consider stable?

This also adds a new test for this, and ensures each resource can be
inspected too!
2024-07-31 17:29:42 -04:00
James Shubin
0a183dfff9 lang: funcs: txn, util: Fix typos 2024-07-25 12:44:58 -04:00
James Shubin
8b54306eb9 examples: lib: Fix these rotted tests
I think the mgmt lib approach is a good idea, even though I'm not
putting much energy into keeping these up to date. Let's at least
re-enable the tests for now, after a few fixups.
2024-07-25 12:39:43 -04:00
James Shubin
fd86b35ce3 docs: Improve the FAQ 2024-07-23 17:26:57 -04:00
James Shubin
d9f8dd53c1 test: Add comment explaining the line length rule issue better 2024-07-23 17:26:18 -04:00
Omar Al-Shuha
ccb0e55d5a examples: lang: Fix env0 example
Change function calls to the correct
one, remove extra argument in getenv
call, and fix typo.
2024-07-08 02:22:57 +02:00
James Shubin
74f747e80b util: password: Fix suspicious dep issue
It seems that without warning, the author of this dep has nuked the old
version, and reorganized the source tree significantly. I'm not an
expert and cryptography routines, but this doesn't make me feel warm
inside. I hope more expert researchers could look into this so that we
avoid supply chain attacks.
2024-07-07 12:47:14 -04:00
James Shubin
aa03b5ce2f lang: core: iter: Add filter iterator function
This was fun to write and adds a new core iterator function.
2024-07-03 21:25:19 -04:00
James Shubin
e747e12002 examples: lang: Fixup a few examples
We might change unification to allow naked single strings with fancier
unification, but let's leave it as is for now and see how often it comes
up.
2024-07-02 23:50:24 -04:00
James Shubin
d1753c592a lang: core: iter: Misc formatting fixes
Also fix up the examples.
2024-07-02 23:49:56 -04:00
James Shubin
7a35bef7ac test: Improve comment parser to skip code blocks
It might be nicer to have some code blocks all by themselves on a single
line.
2024-07-02 13:24:55 -04:00
James Shubin
e10e92596f lang: types: Add stringer information manually
This lets us get the more correct lowercase versions of type kinds in
error messages. (These match what the user would type.)
2024-07-01 18:35:20 -04:00
James Shubin
28253c4bd2 lang: Move stateful test objects into a per-test mode
Was this causing failures? Does this make things much slower?
2024-07-01 18:34:42 -04:00
James Shubin
f2976deb02 pgraph, lang: ast: Fix failing tests due to non-deterministic topo sort
This causes inconsistent type unification when running our tests. It's a
bad user experience too.
2024-07-01 18:34:24 -04:00
James Shubin
14577a0c46 lang: Add modern type unification implementation
This adds a modern type unification algorithm, which drastically
improves performance, particularly for bigger programs.

This required a change to the AST to add TypeCheck methods (for Stmt)
and Infer/Check methods (for Expr). This also changed how the functions
express their invariants, and as a result this was changed as well.

This greatly improves the way we express these invariants, and as a
result it makes adding new polymorphic functions significantly easier.

This also makes error output for the user a lot better in pretty much
all scenarios.

The one downside of this patch is that a good chunk of it is merged in
this giant single commit since it was hard to do it step-wise. That's
not the end of the world.

This couldn't be done without the guidance of Sam who helped me in
explaining, debugging, and writing all the sneaky algorithmic parts and
much more. Thanks again Sam!

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2024-07-01 18:33:47 -04:00
James Shubin
4e18c9c67a lang: Plumb through the unified state facility 2024-07-01 16:07:14 -04:00
James Shubin
d326917432 lang: interfaces: Add some unification basics
This includes the GenericCheck helper which we'll use everywhere, and
the standard single invariant which we use throughout.
2024-07-01 16:07:14 -04:00
James Shubin
ad4eb86262 lang: unification: util: Add the core unification helpers
This adds the core unification helper functions that do the core work of
solving the invariants. This includes the actual Unify, OccursCheck, and
Extract which is sometimes known as "zonk".

A few other small functions are also included.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
2024-07-01 16:06:58 -04:00
James Shubin
5c73e7c582 lang: types: Add a facility for printing consistent unification vars
When we look at unification variables from two different places, the
default printer will always start numbering them from ?1 and therefore
if we look at two unrelated systems, they might both print as ?1 when
they are in fact different pointers.

We don't collect them all by default since it's usually not necessary
except for debugging, but in those situations, we want a consistent
unification store which we can pass around to get sensible debug output.
2024-07-01 14:16:11 -04:00
James Shubin
dc33d9aab7 lang: types: Add a comparable helper to our types library 2024-07-01 14:14:20 -04:00
James Shubin
cdc2439f89 lang: types: Add an iterator helper to our types lib 2024-07-01 14:13:41 -04:00
James Shubin
318ee0d002 lang: types: Fixup small log messages 2024-07-01 14:12:22 -04:00
James Shubin
653299a88f lang: types: Plumb in a unification variable into our type
This is used for representing a unification variable in our type during
type unification. For example, this allows us to have a [?1] or a
map{?1:[?2]} and so on...
2024-07-01 14:11:03 -04:00
James Shubin
6066cbf075 util: Add disjoint package to implement a union find datastructure
This is a fascinating, and incredibly simple data structure. I hope I
can end up using it for more than just type unification!

Thanks to Sam who taught me about its existence.
2024-07-01 14:05:48 -04:00
James Shubin
2b3a41fefa pgraph: Fix rare panic if Sprint is badly used
Be nicer.
2024-07-01 14:05:48 -04:00
James Shubin
5ca9f7fa38 test: Skip link check by default
We should run this periodically or put it in a separate job, as it's
causing the tests to fail all the time. I expect those sites may be
blocking github as they see it as a DOS.
2024-07-01 13:40:14 -04:00
xlai89
201cf091d5 test: Add a links checker and fix some links 2024-06-17 14:17:39 -04:00
Cian Yong Leow
09e53bfd3f engine: resources: file: Remove Validate owner/group Checks
The owner/group of a file should not be validated on the host until runtime. This removes the checks in Validate() that were happening before the execution of the resource graph (and therefore bound to fail if the system was being bootstrapped).
2024-06-17 13:52:04 +01:00
James Shubin
3c661ab674 lang: core: embedded: provisioner: Version flag needs a unique name
Conflicts with the stock --version.
2024-05-23 07:57:30 -04:00
James Shubin
415e22abe2 lang: core, funcs, types: Add ctx to simple func
Plumb through the standard context.Context so that a function can be
cancelled if someone requests this. It makes it less awkward to write
simple functions that might depend on io or network access.
2024-05-09 19:25:46 -04:00
James Shubin
3b754d5324 docs: Fix markdown failing
It fails locally, but not in CI, and I don't know why.
2024-05-05 15:34:04 -04:00
James Shubin
7a568627e9 docs: Update dead links 2024-05-05 15:34:00 -04:00
James Shubin
328360eea8 docs: Add addition to style guide for pointer receivers 2024-04-28 16:14:05 -04:00
James Shubin
7ae3ba4483 mod: Run go mod commands
This was done with go get -u ./... followed by go mod tidy.
2024-04-27 13:31:32 -04:00
Joe Groocock
351a61c0cd engine: resources: docker: Update docker
Several types were renamed and moved

Signed-off-by: Joe Groocock <me@frebib.net>
2024-04-27 10:52:29 +00:00
James Shubin
c12452b3ce misc: Move to golang 1.21
Unfortunately, this also breaks go-mod-upgrade with:

upgrade failed error=Error running go command to discover modules: exit
status 1 stderr=go: loading module retractions for
golang.org/x/mod@v0.16.0: version "v0.17.0" invalid: resolves to version
v0.17.1-0.20240315155916-aa51b25a4485 (v0.17.0 is not a tag) go: loading
module retractions for golang.org/x/sync@v0.6.0: version "v0.7.0"
invalid: resolves to version v0.7.1-0.20240304172602-14be23e5b48b
(v0.7.0 is not a tag)
2024-04-25 13:01:41 -04:00
James Shubin
0e92d190cc make: Add easy error message for common issue
This can happen if the golang tools are angry. Make it easier for the
user to debug and fix.
2024-04-25 12:38:50 -04:00
James Shubin
453cd4409e lang: ast: Remove unnecessary metaparam exclusive
Originally, I considered having more than one way to express the meta
param. After thinking about it for longer, it probably makes sense to
have a second meta param if necessary, and to avoid the exclusive.
2024-04-18 00:44:34 -04:00
James Shubin
51cf1e2921 lang: ast: The res and edge names should not use exclusives
This removes the exclusive from the res names and edge names. We now
require that the names should be lists of strings, however they can
still be single strings if that can be determined statically.
Programmers should explicitly wrap their variables in a string by
interpolation to force this, or in square brackets to force a list. The
former is generally preferable because it generates a small function
graph since it doesn't need to build a list.
2024-04-18 00:07:53 -04:00
James Shubin
dc45c90ccd lang: Add common type to global variables
We use the list of strings so often, we might as well give it a global
variable.
2024-04-16 14:31:03 -04:00
James Shubin
6782d65577 test: shell, lang: core: embedded: provisioner: Check it compiles
Add a test to guarantee we continue to keep compiling, in case something
in the language changes.
2024-04-16 14:30:56 -04:00
James Shubin
68ee163eb1 entry, lang: core: embedded: provisioner: Allow more than one entry
This changes the entry API slightly to allow for more than one entry
registered, which makes building, testing and user tooling easier.
2024-04-16 14:11:34 -04:00
James Shubin
bc4b5d96b0 lang: core: embedded: provisioner: Better name for firewall entries 2024-04-15 15:03:27 -04:00
James Shubin
909dbb531d lang: ast: Fix small typos 2024-04-06 15:32:31 -04:00
Felix Frank
a2654bdc69 test: Send error messages to stderr, where they belong
When error messages are written to stdout, they will be considered as
output in case we want to fail from inside $( ) or backticks, and then
the error does not end up on the terminal.
2024-04-02 21:32:05 -04:00
Felix Frank
edcb04d1a9 misc, test: Some quality of life improvements
Add a fold in github actions output around the ragel build.

Run the commit-message test locally, so that error can be detected
before pushing to CI. We also now accept two-letter topics.

Some minor improvement in the testing scripts.
2024-04-02 21:15:51 -04:00
Felix Frank
29ec867ac7 gapi: Bring back puppet and langpuppet
This reverts commit e767655ede.

In addition, it applies required changes to function with the new CLI backend.
2024-04-02 21:07:02 -04:00
Julian Rüth
22873b3c3f docs: Fix docker build instructions
fixes #752
2024-04-03 01:01:47 +03:00
Julian Rüth
ede5db18d7 docs: Binaries are not outdated currently 2024-04-03 01:01:37 +03:00
James Shubin
964b1dc58a docs: Add release notes for 0.0.26
I send these out by email and then archive a copy here. Sign up to the
mgmt partner program for early access. Ping me for details.
2024-03-30 19:03:31 -04:00
James Shubin
1b00af6926 lang: Add unification optimizations
This adds a unification optimizations API, and uses it to optimize the
embedded provisioner. With these turned on, type unification drops from
around 1m45s to 2.5s which is a 40x speedup.
2024-03-30 18:05:16 -04:00
James Shubin
ddf1be653e lang: Plumb data and unification strategy through the lang struct
This adds some plumbing to pass values into the lang struct.
2024-03-30 17:36:49 -04:00
James Shubin
cede7e5ac0 lang: Structurally refactor type unification
This will make it easier to add new solvers and also cleans up some
pending issues.
2024-03-30 16:55:20 -04:00
James Shubin
964bd8ba61 util: Skip badly numbered versions
This let's us skip over pre-release versions like "40 Beta" or similar.
2024-03-30 13:54:09 -04:00
James Shubin
a1db219fd2 docs: Add release notes for 0.0.25
I send these out by email and then archive a copy here. Sign up to the
mgmt partner program for early access. Ping me for details.
2024-03-27 17:21:08 -04:00
James Shubin
241be1801b make: Update remote path 2024-03-27 00:25:23 -04:00
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
1004 changed files with 53357 additions and 18380 deletions

1
.ackrc
View File

@@ -3,3 +3,4 @@
--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

View File

@@ -27,9 +27,9 @@ jobs:
# macos tests are currently failing in CI
#- macos-latest
golang_version:
# TODO: add 1.20.x and tip
# TODO: add 1.24.x and tip
# minimum required and latest published go_version
- 1.19
- "1.23"
test_block:
- basic
- shell
@@ -40,14 +40,14 @@ jobs:
# 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@v2
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@v2
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.golang_version }}

3
.lycheeignore Normal file
View File

@@ -0,0 +1,3 @@
# list URLs that should be excluded for lychee link checher
https://roidelapluie.be
https://github.com/purpleidea/mgmt/commit

View File

@@ -1,63 +0,0 @@
language: go
os:
- linux
go_import_path: github.com/purpleidea/mgmt
sudo: true
dist: xenial
# travis requires that you update manually, and provides this key to trigger it
apt:
update: true
before_install:
# print some debug information to help catch the constant travis regressions
- if [ -e /etc/apt/sources.list.d/ ]; then sudo ls -l /etc/apt/sources.list.d/; fi
# workaround broken travis NO_PUBKEY errors
- if [ -e /etc/apt/sources.list.d/rabbitmq_rabbitmq-server.list ]; then sudo rm -f /etc/apt/sources.list.d/rabbitmq_rabbitmq-server.list; fi
- if [ -e /etc/apt/sources.list.d/github_git-lfs.list ]; then sudo rm -f /etc/apt/sources.list.d/github_git-lfs.list; fi
# as per a number of comments online, this might mitigate some flaky fails...
- if [[ "$TRAVIS_OS_NAME" != "osx" ]]; then sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6; fi
# apt update tends to be flaky in travis, retry up to 3 times on failure
# https://docs.travis-ci.com/user/common-build-problems/#travis_retry
- if [[ "$TRAVIS_OS_NAME" != "osx" ]]; then travis_retry travis_retry sudo apt update; fi
- git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
- git fetch --unshallow
install: 'make deps'
matrix:
fast_finish: false
allow_failures:
- go: 1.20.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.19.x
env: TEST_BLOCK=basic
- name: "shell tests"
go: 1.19.x
env: TEST_BLOCK=shell
- name: "race tests"
go: 1.19.x
env: TEST_BLOCK=race
- go: 1.20.x
- go: tip
- os: osx
script: 'TEST_BLOCK="$TEST_BLOCK" make test'
# the "secure" channel value is the result of running: ./misc/travis-encrypt.sh
# 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=
template:
- "%{repository} (%{commit}: %{author}): %{message}"
- "More info : %{build_url}"
on_success: always
on_failure: always
use_notice: false
skip_join: false
email:
recipients:
- secure: qNkgP6QLl6VXpFQIxas2wggxvIiOmm1/hGRXm4BXsSFzHsJPvMamA3E1HEC7H+luiWTny1jtGSGgTJPV9CX1LtQV0g0S4ThaAvWuKvk3rXO8IVd++iA/Lh1s1H6JdKM0dJtLqFICawjeci4tOQzSvrM2eCBWqT0UYsrQsGHB6AF31GNAH0Acqd5cYeL+ZpbCN+hQEznAZQ7546N25TwqieI8Lg7nisA+lwYYwsaC2+f5RIeyvvKjQv3wzEdBAQ9CI9WQiTOUBnUnyYxMrdomQ/XGF66QnZy9vq5nEP83IFtuhPvSamL7ceT+yJW0jDyBi8sYEV7On7eXzjyHbiYpF4YHcJrFnf5RyV4kQGd6/SC8iZwK4Is4eyeAjDFTC+JafLajw9R9x9bK43BwlRAWOZxjFKe0cU/BVAjmlz87vHgUho2P41+0a5XfajfU6VhA5QFPK6rNH7W1CnA7D/0LmS0yaqJM1OCrm6LfoZEMhe0DxTJ9uWJbr0x1sYao6q8H4xYk+fyRgoBAr2TxYU7kXx8ThiRdzuQ8izdbojlzTYLe8liZMIsjL0axLsLK7YBWrjJUcDFDjR/DqmVxPrvbVFbCi9ChmBw0WmbJvDY0FV8T8dO8wCjg9JEmprAmWPyq0g/F87LFK4tAZqQFJGjP1qwsR9jdwdNTKeCdY656f/Y=
on_failure: change
on_success: change

View File

@@ -11,3 +11,4 @@ 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-2023+ James Shubin and the project contributors
Copyright (C) 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.

267
Makefile
View File

@@ -1,5 +1,5 @@
# Mgmt
# Copyright (C) 2013-2023+ James Shubin and the project contributors
# Copyright (C) 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,19 +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 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-30 mkosi_fedora-29 mkosi_centos-7 mkosi_debian-10 mkosi_ubuntu-bionic mkosi_archlinux
.PHONY: release releases_path release_fedora-30 release_fedora-29 release_centos-7 release_debian-10 release_ubuntu-bionic release_archlinux
.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))
@@ -51,7 +63,7 @@ SRPM_BASE = $(PROGRAM)-$(VERSION)-$(RELEASE).src.rpm
RPM = rpmbuild/RPMS/$(PROGRAM)-$(VERSION)-$(RELEASE).$(ARCH).rpm
USERNAME := $(shell cat ~/.config/copr 2>/dev/null | grep username | awk -F '=' '{print $$2}' | tr -d ' ')
SERVER = 'dl.fedoraproject.org'
REMOTE_PATH = 'pub/alt/$(USERNAME)/$(PROGRAM)'
REMOTE_PATH = '/srv/pub/alt/$(USERNAME)/$(PROGRAM)'
ifneq ($(GOTAGS),)
BUILD_FLAGS = -tags '$(GOTAGS)'
endif
@@ -60,27 +72,69 @@ GOOSARCHES ?= linux/amd64 linux/ppc64 linux/ppc64le linux/arm64 darwin/amd64
GOHOSTOS = $(shell go env GOHOSTOS)
GOHOSTARCH = $(shell go env GOHOSTARCH)
TOKEN_FEDORA-30 = fedora-30
TOKEN_FEDORA-29 = fedora-29
TOKEN_CENTOS-7 = centos-7
TOKEN_DEBIAN-10 = debian-10
TOKEN_UBUNTU-BIONIC = ubuntu-bionic
TOKEN_ARCHLINUX = archlinux
# 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_FEDORA-30 = mgmt-$(TOKEN_FEDORA-30)-$(VERSION)-1.x86_64.rpm
FILE_FEDORA-29 = mgmt-$(TOKEN_FEDORA-29)-$(VERSION)-1.x86_64.rpm
FILE_CENTOS-7 = mgmt-$(TOKEN_CENTOS-7)-$(VERSION)-1.x86_64.rpm
FILE_DEBIAN-10 = mgmt_$(TOKEN_DEBIAN-10)_$(VERSION)_amd64.deb
FILE_UBUNTU-BIONIC = mgmt_$(TOKEN_UBUNTU-BIONIC)_$(VERSION)_amd64.deb
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_FEDORA-30 = releases/$(VERSION)/$(TOKEN_FEDORA-30)/$(FILE_FEDORA-30)
PKG_FEDORA-29 = releases/$(VERSION)/$(TOKEN_FEDORA-29)/$(FILE_FEDORA-29)
PKG_CENTOS-7 = releases/$(VERSION)/$(TOKEN_CENTOS-7)/$(FILE_CENTOS-7)
PKG_DEBIAN-10 = releases/$(VERSION)/$(TOKEN_DEBIAN-10)/$(FILE_DEBIAN-10)
PKG_UBUNTU-BIONIC = releases/$(VERSION)/$(TOKEN_UBUNTU-BIONIC)/$(FILE_UBUNTU-BIONIC)
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
@@ -171,6 +225,8 @@ build-debug: $(PROGRAM)
GOOS=$(firstword $(subst -, ,$*))
GOARCH=$(lastword $(subst -, ,$*))
build/mgmt-%: $(GO_FILES) $(MCL_FILES) go.mod go.sum | lang funcgen
@# If you need to run `go mod tidy` then this can trigger.
@if [ "$(PKGNAME)" = "" ]; then echo "\$$(PKGNAME) is empty, test with: go list ."; exit 42; fi
@echo "Building: $(PROGRAM), os/arch: $*, version: $(SVERSION)..."
time env GOOS=${GOOS} GOARCH=${GOARCH} go build $(TRIMPATH) -ldflags=$(PKGNAME)="-X main.program=$(PROGRAM) -X main.version=$(SVERSION) ${LDFLAGS}" -o $@ $(BUILD_FLAGS)
@@ -185,8 +241,8 @@ clean: ## clean things up
$(MAKE) --quiet -C test clean
$(MAKE) --quiet -C lang clean
$(MAKE) --quiet -C misc/mkosi clean
rm -f lang/funcs/core/generated_funcs.go || true
rm -f lang/funcs/core/generated_funcs_test.go || true
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`
@@ -368,25 +424,25 @@ tag: ## tags a new release
#
# mkosi
#
mkosi: mkosi_fedora-30 mkosi_fedora-29 mkosi_centos-7 mkosi_debian-10 mkosi_ubuntu-bionic mkosi_archlinux ## builds distro packages via mkosi
mkosi: mkosi_fedora-latest mkosi_fedora-older mkosi_stream-latest mkosi_debian-stable mkosi_ubuntu-latest mkosi_archlinux ## builds distro packages via mkosi
mkosi_fedora-30: releases/$(VERSION)/.mkdir
mkosi_fedora-latest: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
mkosi_fedora-29: releases/$(VERSION)/.mkdir
mkosi_fedora-older: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
mkosi_centos-7: releases/$(VERSION)/.mkdir
mkosi_stream-latest: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
mkosi_debian-10: releases/$(VERSION)/.mkdir
mkosi_debian-stable: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
mkosi_ubuntu-bionic: releases/$(VERSION)/.mkdir
mkosi_ubuntu-latest: releases/$(VERSION)/.mkdir
@title='$@' ; echo "Generating: $${title#'mkosi_'} via mkosi..."
@title='$@' ; distro=$${title#'mkosi_'} ; ./misc/mkosi/make.sh $${distro} `realpath "releases/$(VERSION)/"`
@@ -404,25 +460,66 @@ releases_path:
@#Don't put any other output or dependencies in here or they'll show!
@echo "releases/$(VERSION)/"
release_fedora-30: $(PKG_FEDORA-30)
release_fedora-29: $(PKG_FEDORA-29)
release_centos-7: $(PKG_CENTOS-7)
release_debian-10: $(PKG_DEBIAN-10)
release_ubuntu-bionic: $(PKG_UBUNTU-BIONIC)
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: $(PKG_FEDORA-30) $(PKG_FEDORA-29) $(PKG_CENTOS-7) $(PKG_DEBIAN-10) $(PKG_UBUNTU-BIONIC) $(PKG_ARCHLINUX) $(SHA256SUMS_ASC)
releases/$(VERSION)/mgmt-release.url: $(DEP_BINARY_AMD64) $(DEP_BINARY_ARM64) $(DEP_FEDORA-LATEST) $(DEP_FEDORA-OLDER) $(DEP_STREAM-LATEST) $(DEP_DEBIAN-STABLE) $(DEP_UBUNTU-LATEST) $(DEP_ARCHLINUX) $(SHA256SUMS_ASC)
@echo "Pushing git tag $(VERSION) to origin..."
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 $(PKG_FEDORA-30) \
-a $(PKG_FEDORA-29) \
-a $(PKG_CENTOS-7) \
-a $(PKG_DEBIAN-10) \
-a $(PKG_UBUNTU-BIONIC) \
-a $(PKG_ARCHLINUX) \
` [ -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 \
@@ -430,56 +527,92 @@ releases/$(VERSION)/mgmt-release.url: $(PKG_FEDORA-30) $(PKG_FEDORA-29) $(PKG_CE
|| rm -f releases/$(VERSION)/mgmt-release.url
releases/$(VERSION)/.mkdir:
mkdir -p releases/$(VERSION)/{$(TOKEN_FEDORA-30),$(TOKEN_FEDORA-29),$(TOKEN_CENTOS-7),$(TOKEN_DEBIAN-10),$(TOKEN_UBUNTU-BIONIC),$(TOKEN_ARCHLINUX)}/ && touch releases/$(VERSION)/.mkdir
mkdir -p \
` [ "$(TOKEN_BINARY_AMD64)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_BINARY_AMD64)/" ` \
` [ "$(TOKEN_BINARY_ARM64)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_BINARY_ARM64)/" ` \
` [ "$(TOKEN_FEDORA-LATEST)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_FEDORA-LATEST)/" ` \
` [ "$(TOKEN_FEDORA-OLDER)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_FEDORA-OLDER)/" ` \
` [ "$(TOKEN_STREAM-LATEST)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_STREAM-LATEST)/" ` \
` [ "$(TOKEN_DEBIAN-STABLE)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_DEBIAN-STABLE)/" ` \
` [ "$(TOKEN_UBUNTU-LATEST)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_UBUNTU-LATEST)/" ` \
` [ "$(TOKEN_ARCHLINUX)" != "" ] && printf -- "releases/$(VERSION)/$(TOKEN_ARCHLINUX)/" ` \
&& touch releases/$(VERSION)/.mkdir
releases/$(VERSION)/$(TOKEN_FEDORA-30)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
# These are defined conditionally, since if the token is empty, they warn!
ifneq ($(TOKEN_BINARY_AMD64),)
$(PKG_BINARY_AMD64): build/mgmt-linux-amd64 releases/$(VERSION)/.mkdir
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; cp -a build/mgmt-linux-amd64 $(PKG_BINARY_AMD64)
endif
ifneq ($(TOKEN_BINARY_ARM64),)
$(PKG_BINARY_ARM64): build/mgmt-linux-arm64 releases/$(VERSION)/.mkdir
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; cp -a build/mgmt-linux-arm64 $(PKG_BINARY_ARM64)
endif
ifneq ($(TOKEN_FEDORA-LATEST),)
releases/$(VERSION)/$(TOKEN_FEDORA-LATEST)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Generating: $${distro} changelog..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/make-rpm-changelog.sh "$${distro}" $(VERSION)
$(PKG_FEDORA-30): releases/$(VERSION)/$(TOKEN_FEDORA-30)/changelog
$(PKG_FEDORA-LATEST): releases/$(VERSION)/$(TOKEN_FEDORA-LATEST)/changelog
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_FEDORA-30)" libvirt-devel augeas-devel
releases/$(VERSION)/$(TOKEN_FEDORA-29)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
@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)
$(PKG_FEDORA-29): releases/$(VERSION)/$(TOKEN_FEDORA-29)/changelog
$(PKG_FEDORA-OLDER): releases/$(VERSION)/$(TOKEN_FEDORA-OLDER)/changelog
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_FEDORA-29)" libvirt-devel augeas-devel
releases/$(VERSION)/$(TOKEN_CENTOS-7)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
@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)
$(PKG_CENTOS-7): releases/$(VERSION)/$(TOKEN_CENTOS-7)/changelog
$(PKG_STREAM-LATEST): releases/$(VERSION)/$(TOKEN_STREAM-LATEST)/changelog
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_CENTOS-7)" libvirt-devel augeas-devel
releases/$(VERSION)/$(TOKEN_DEBIAN-10)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
@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)
$(PKG_DEBIAN-10): releases/$(VERSION)/$(TOKEN_DEBIAN-10)/changelog
$(PKG_DEBIAN-STABLE): releases/$(VERSION)/$(TOKEN_DEBIAN-STABLE)/changelog
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_DEBIAN-10)" libvirt-dev libaugeas-dev
releases/$(VERSION)/$(TOKEN_UBUNTU-BIONIC)/changelog: $(PROGRAM) releases/$(VERSION)/.mkdir
@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)
$(PKG_UBUNTU-BIONIC): releases/$(VERSION)/$(TOKEN_UBUNTU-BIONIC)/changelog
$(PKG_UBUNTU-LATEST): releases/$(VERSION)/$(TOKEN_UBUNTU-LATEST)/changelog
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; echo "Building: $${distro} package..."
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_UBUNTU-BIONIC)" libvirt-dev libaugeas-dev
@title='$(@D)' ; distro=$${title#'releases/$(VERSION)/'} ; ./misc/fpm-pack.sh $${distro} $(VERSION) "$(FILE_UBUNTU-LATEST)" libvirt-dev libaugeas-dev
endif
ifneq ($(TOKEN_ARCHLINUX),)
$(PKG_ARCHLINUX): $(PROGRAM) releases/$(VERSION)/.mkdir
@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): $(PKG_FEDORA-30) $(PKG_FEDORA-29) $(PKG_CENTOS-7) $(PKG_DEBIAN-10) $(PKG_UBUNTU-BIONIC) $(PKG_ARCHLINUX)
$(SHA256SUMS): $(DEP_BINARY_AMD64) $(DEP_BINARY_ARM64) $(DEP_FEDORA-LATEST) $(DEP_FEDORA-OLDER) $(DEP_STREAM-LATEST) $(DEP_DEBIAN-STABLE) $(DEP_UBUNTU-LATEST) $(DEP_ARCHLINUX)
@# remove the directory separator in the SHA256SUMS file
@echo "Generating: sha256 sum..."
sha256sum $(PKG_FEDORA-30) $(PKG_FEDORA-29) $(PKG_CENTOS-7) $(PKG_DEBIAN-10) $(PKG_UBUNTU-BIONIC) $(PKG_ARCHLINUX) | awk -F '/| ' '{print $$1" "$$6}' > $(SHA256SUMS)
sha256sum \
` [ -e $(PKG_BINARY_AMD64) ] && printf -- "$(PKG_BINARY_AMD64)" ` \
` [ -e $(PKG_BINARY_ARM64) ] && printf -- "$(PKG_BINARY_ARM64)" ` \
` [ -e $(PKG_FEDORA-LATEST) ] && printf -- "$(PKG_FEDORA-LATEST)" ` \
` [ -e $(PKG_FEDORA-OLDER) ] && printf -- "$(PKG_FEDORA-OLDER)" ` \
` [ -e $(PKG_STREAM-LATEST) ] && printf -- "$(PKG_STREAM-LATEST)" ` \
` [ -e $(PKG_DEBIAN-STABLE) ] && printf -- "$(PKG_DEBIAN-STABLE)" ` \
` [ -e $(PKG_UBUNTU-LATEST) ] && printf -- "$(PKG_UBUNTU-LATEST)" ` \
` [ -e $(PKG_ARCHLINUX) ] && printf -- "$(PKG_ARCHLINUX)" ` \
| awk -F '/| ' '{print $$1" "$$6}' > $(SHA256SUMS)
$(SHA256SUMS_ASC): $(SHA256SUMS)
@echo "Signing sha256 sum..."
@@ -505,9 +638,9 @@ 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.go
funcgen: lang/core/generated_funcs.go
lang/funcs/core/generated_funcs.go: lang/funcs/funcgen/*.go lang/funcs/core/funcgen.yaml lang/funcs/funcgen/templates/generated_funcs.go.tpl
lang/core/generated_funcs.go: lang/funcs/funcgen/*.go lang/core/funcgen.yaml lang/funcs/funcgen/templates/generated_funcs.go.tpl
@echo "Generating: funcs..."
@go run `find lang/funcs/funcgen/ -maxdepth 1 -type f -name '*.go' -not -name '*_test.go'` -templates=lang/funcs/funcgen/templates/generated_funcs.go.tpl >/dev/null

View File

@@ -5,17 +5,24 @@
[![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://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)
> [!TIP]
> [Resource reference guide now available!](https://mgmtconfig.com/docs/resources/)
> [!TIP]
> [Function reference guide now available!](https://mgmtconfig.com/docs/functions/)
## About:
`Mgmt` is a real-time automation tool. It is familiar to existing configuration
management software, but is drastically more powerful as it can allow you to
build real-time, closed-loop feedback systems, in a very safe way, and with a
surprisingly small amout of our `mcl` code. For example, the following code will
ensure that your file server is set to read-only when it's friday.
surprisingly small amount of our `mcl` code. For example, the following code
will ensure that your file server is set to read-only when it's friday.
```mcl
import "datetime"
@@ -65,11 +72,11 @@ Come join us in the `mgmt` community!
| Medium | Link |
|---|---|
| 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:
@@ -91,10 +98,14 @@ Please read, enjoy and help improve our documentation!
| [quick start guide](docs/quick-start-guide.md) | for everyone |
| [frequently asked questions](docs/faq.md) | for everyone |
| [general documentation](docs/documentation.md) | for everyone |
| [resource reference](https://mgmtconfig.com/docs/resources/) | for everyone |
| [function reference](https://mgmtconfig.com/docs/functions/) | for everyone |
| [language guide](docs/language-guide.md) | for everyone |
| [function guide](docs/function-guide.md) | for mgmt developers |
| [resource guide](docs/resource-guide.md) | for mgmt developers |
| [style guide](docs/style-guide.md) | for mgmt developers |
| [contributing guide](docs/contributing.md) | for mgmt contributors |
| [service API guide](docs/service-guide.md) | for external 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 |

183
cli/cli.go Normal file
View File

@@ -0,0 +1,183 @@
// Mgmt
// Copyright (C) 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"`
SetupCmd *SetupArgs `arg:"subcommand:setup" help:"setup some bootstrapping tasks"`
FirstbootCmd *FirstbootArgs `arg:"subcommand:firstboot" help:"run some tasks on first boot"`
DocsCmd *DocsGenerateArgs `arg:"subcommand:docs" help:"generate documentation"`
// 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)
}
if cmd := obj.SetupCmd; cmd != nil {
return cmd.Run(ctx, data)
}
if cmd := obj.FirstbootCmd; cmd != nil {
return cmd.Run(ctx, data)
}
if cmd := obj.DocsCmd; 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{}

265
cli/deploy.go Normal file
View File

@@ -0,0 +1,265 @@
// Mgmt
// Copyright (C) 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"`
DeployPuppet *cliUtil.PuppetArgs `arg:"subcommand:puppet" help:"deploy puppet graph payload"`
DeployLangPuppet *cliUtil.LangPuppetArgs `arg:"subcommand:langpuppet" help:"deploy langpuppet 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
}
if cmd := obj.DeployPuppet; cmd != nil {
name = cliUtil.LookupSubcommand(obj, cmd) // "puppet"
args = cmd
}
if cmd := obj.DeployLangPuppet; cmd != nil {
name = cliUtil.LookupSubcommand(obj, cmd) // "langpuppet"
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
}

150
cli/docs.go Normal file
View File

@@ -0,0 +1,150 @@
// Mgmt
// Copyright (C) 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"
"os"
"os/signal"
"sync"
"syscall"
cliUtil "github.com/purpleidea/mgmt/cli/util"
"github.com/purpleidea/mgmt/docs"
)
// DocsGenerateArgs is the CLI parsing structure and type of the parsed result.
// This particular one contains all the common flags for the `docs generate`
// subcommand.
type DocsGenerateArgs struct {
docs.Config // embedded config (can't be a pointer) https://github.com/alexflint/go-arg/issues/240
DocsGenerate *cliUtil.DocsGenerateArgs `arg:"subcommand:generate" help:"generate documentation"`
}
// 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 `docs` subcommand.
func (obj *DocsGenerateArgs) Run(ctx context.Context, data *cliUtil.Data) (bool, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
var name string
var args interface{}
if cmd := obj.DocsGenerate; cmd != nil {
name = cliUtil.LookupSubcommand(obj, cmd) // "generate"
args = cmd
}
_ = name
Logf := func(format string, v ...interface{}) {
// Don't block this globally...
//if !data.Flags.Debug {
// return
//}
data.Flags.Logf("main: "+format, v...)
}
var api docs.API
if cmd := obj.DocsGenerate; cmd != nil {
api = &docs.Generate{
DocsGenerateArgs: args.(*cliUtil.DocsGenerateArgs),
Config: obj.Config,
Program: data.Program,
Version: data.Version,
Debug: data.Flags.Debug,
Logf: Logf,
}
}
if api == nil {
return false, nil // nothing found (display help!)
}
// We don't use these for the setup command in normal operation.
if data.Flags.Debug {
cliUtil.Hello(data.Program, data.Version, data.Flags) // say hello!
defer Logf("goodbye!")
}
// install the exit signal handler
wg := &sync.WaitGroup{}
defer wg.Wait()
exit := make(chan struct{})
defer close(exit)
wg.Add(1)
go func() {
defer cancel()
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")
return
}
switch count {
case 0:
data.Flags.Logf("interrupted by ^C")
cancel()
case 1:
data.Flags.Logf("interrupted by ^C (fast pause)")
cancel()
case 2:
data.Flags.Logf("interrupted by ^C (hard interrupt)")
cancel()
}
count++
case <-exit:
return
}
}
}()
if err := api.Main(ctx); err != nil {
if data.Flags.Debug {
data.Flags.Logf("main: %+v", err)
}
return false, err
}
return true, nil
}

151
cli/firstboot.go Normal file
View File

@@ -0,0 +1,151 @@
// Mgmt
// Copyright (C) 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"
"os"
"os/signal"
"sync"
"syscall"
cliUtil "github.com/purpleidea/mgmt/cli/util"
"github.com/purpleidea/mgmt/firstboot"
)
// FirstbootArgs is the CLI parsing structure and type of the parsed result.
// This particular one contains all the common flags for the `firstboot`
// subcommand.
type FirstbootArgs struct {
firstboot.Config // embedded config (can't be a pointer) https://github.com/alexflint/go-arg/issues/240
FirstbootStart *cliUtil.FirstbootStartArgs `arg:"subcommand:start" help:"start firstboot service"`
}
// 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 `firstboot` subcommand. The firstboot command as a
// service that lets you run commands once on the first boot of a system.
func (obj *FirstbootArgs) Run(ctx context.Context, data *cliUtil.Data) (bool, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
var name string
var args interface{}
if cmd := obj.FirstbootStart; cmd != nil {
name = cliUtil.LookupSubcommand(obj, cmd) // "pkg"
args = cmd
}
_ = name
Logf := func(format string, v ...interface{}) {
// Don't block this globally...
//if !data.Flags.Debug {
// return
//}
data.Flags.Logf("main: "+format, v...)
}
var api firstboot.API
if cmd := obj.FirstbootStart; cmd != nil {
api = &firstboot.Start{
FirstbootStartArgs: args.(*cliUtil.FirstbootStartArgs),
Config: obj.Config,
Program: data.Program,
Version: data.Version,
Debug: data.Flags.Debug,
Logf: Logf,
}
}
if api == nil {
return false, nil // nothing found (display help!)
}
// We don't use these for the setup command in normal operation.
if data.Flags.Debug {
cliUtil.Hello(data.Program, data.Version, data.Flags) // say hello!
defer Logf("goodbye!")
}
// install the exit signal handler
wg := &sync.WaitGroup{}
defer wg.Wait()
exit := make(chan struct{})
defer close(exit)
wg.Add(1)
go func() {
defer cancel()
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")
return
}
switch count {
case 0:
data.Flags.Logf("interrupted by ^C")
cancel()
case 1:
data.Flags.Logf("interrupted by ^C (fast pause)")
cancel()
case 2:
data.Flags.Logf("interrupted by ^C (hard interrupt)")
cancel()
}
count++
case <-exit:
return
}
}
}()
if err := api.Main(ctx); err != nil {
if data.Flags.Debug {
data.Flags.Logf("main: %+v", err)
}
return false, err
}
return true, nil
}

243
cli/run.go Normal file
View File

@@ -0,0 +1,243 @@
// Mgmt
// Copyright (C) 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"`
RunPuppet *cliUtil.PuppetArgs `arg:"subcommand:puppet" help:"run puppet graph payload"`
RunLangPuppet *cliUtil.LangPuppetArgs `arg:"subcommand:langpuppet" help:"run a combined lang/puppet 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
}
if cmd := obj.RunPuppet; cmd != nil {
name = cliUtil.LookupSubcommand(obj, cmd) // "puppet"
args = cmd
}
if cmd := obj.RunLangPuppet; cmd != nil {
name = cliUtil.LookupSubcommand(obj, cmd) // "langpuppet"
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
}

180
cli/setup.go Normal file
View File

@@ -0,0 +1,180 @@
// Mgmt
// Copyright (C) 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"
"os"
"os/signal"
"sync"
"syscall"
cliUtil "github.com/purpleidea/mgmt/cli/util"
"github.com/purpleidea/mgmt/setup"
)
// SetupArgs is the CLI parsing structure and type of the parsed result. This
// particular one contains all the common flags for the `setup` subcommand.
type SetupArgs struct {
setup.Config // embedded config (can't be a pointer) https://github.com/alexflint/go-arg/issues/240
SetupPkg *cliUtil.SetupPkgArgs `arg:"subcommand:pkg" help:"setup packages"`
SetupSvc *cliUtil.SetupSvcArgs `arg:"subcommand:svc" help:"setup services"`
SetupFirstboot *cliUtil.SetupFirstbootArgs `arg:"subcommand:firstboot" help:"setup firstboot"`
}
// 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 `setup` subcommand. The setup command does some
// bootstrap work to help get things going.
func (obj *SetupArgs) Run(ctx context.Context, data *cliUtil.Data) (bool, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
var name string
var args interface{}
if cmd := obj.SetupPkg; cmd != nil {
name = cliUtil.LookupSubcommand(obj, cmd) // "pkg"
args = cmd
}
if cmd := obj.SetupSvc; cmd != nil {
name = cliUtil.LookupSubcommand(obj, cmd) // "svc"
args = cmd
}
if cmd := obj.SetupFirstboot; cmd != nil {
name = cliUtil.LookupSubcommand(obj, cmd) // "firstboot"
args = cmd
}
_ = name
Logf := func(format string, v ...interface{}) {
// Don't block this globally...
//if !data.Flags.Debug {
// return
//}
data.Flags.Logf("main: "+format, v...)
}
var api setup.API
if cmd := obj.SetupPkg; cmd != nil {
api = &setup.Pkg{
SetupPkgArgs: args.(*cliUtil.SetupPkgArgs),
Config: obj.Config,
Program: data.Program,
Version: data.Version,
Debug: data.Flags.Debug,
Logf: Logf,
}
}
if cmd := obj.SetupSvc; cmd != nil {
api = &setup.Svc{
SetupSvcArgs: args.(*cliUtil.SetupSvcArgs),
Config: obj.Config,
Program: data.Program,
Version: data.Version,
Debug: data.Flags.Debug,
Logf: Logf,
}
}
if cmd := obj.SetupFirstboot; cmd != nil {
api = &setup.Firstboot{
SetupFirstbootArgs: args.(*cliUtil.SetupFirstbootArgs),
Config: obj.Config,
Program: data.Program,
Version: data.Version,
Debug: data.Flags.Debug,
Logf: Logf,
}
}
if api == nil {
return false, nil // nothing found (display help!)
}
// We don't use these for the setup command in normal operation.
if data.Flags.Debug {
cliUtil.Hello(data.Program, data.Version, data.Flags) // say hello!
defer Logf("goodbye!")
}
// install the exit signal handler
wg := &sync.WaitGroup{}
defer wg.Wait()
exit := make(chan struct{})
defer close(exit)
wg.Add(1)
go func() {
defer cancel()
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")
return
}
switch count {
case 0:
data.Flags.Logf("interrupted by ^C")
cancel()
case 1:
data.Flags.Logf("interrupted by ^C (fast pause)")
cancel()
case 2:
data.Flags.Logf("interrupted by ^C (hard interrupt)")
cancel()
}
count++
case <-exit:
return
}
}
}()
if err := api.Main(ctx); err != nil {
if data.Flags.Debug {
data.Flags.Logf("main: %+v", err)
}
return false, err
}
return true, nil
}

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

@@ -0,0 +1,198 @@
// Mgmt
// Copyright (C) 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"`
UnifySolver *string `arg:"--unify-name" help:"pick a specific unification solver"`
UnifyOptimizations []string `arg:"--unify-optimizations" help:"list of unification optimizations to request (experts only)"`
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"`
}
// PuppetArgs is the puppet CLI parsing structure and type of the parsed result.
type PuppetArgs struct {
// Input is the input puppet code or file path or just "agent".
Input string `arg:"positional,required"`
// PuppetConf is the optional path to a puppet.conf config file.
PuppetConf string `arg:"--puppet-conf" help:"full path to the puppet.conf file to use"`
}
// LangPuppetArgs is the langpuppet CLI parsing structure and type of the parsed
// result.
type LangPuppetArgs struct {
// LangInput is the input mcl code or file path or any input specification.
LangInput string `arg:"--lang,required" help:"the input parameter for the lang module"`
// PuppetInput is the input puppet code or file path or just "agent".
PuppetInput string `arg:"--puppet,required" help:"the input parameter for the puppet module"`
// copy-pasted from PuppetArgs
// PuppetConf is the optional path to a puppet.conf config file.
PuppetConf string `arg:"--puppet-conf" help:"full path to the puppet.conf file to use"`
// end PuppetArgs
// copy-pasted from LangArgs
// 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)"`
// end LangArgs
}
// SetupPkgArgs is the setup service CLI parsing structure and type of the
// parsed result.
type SetupPkgArgs struct {
Distro string `arg:"--distro" help:"build for this distro"`
Sudo bool `arg:"--sudo" help:"include sudo in the command"`
Exec bool `arg:"--exec" help:"actually run these commands"`
}
// SetupSvcArgs is the setup service CLI parsing structure and type of the
// parsed result.
type SetupSvcArgs struct {
BinaryPath string `arg:"--binary-path" help:"path to the binary"`
Install bool `arg:"--install" help:"install the systemd mgmt service"`
Start bool `arg:"--start" help:"start the mgmt service"`
Enable bool `arg:"--enable" help:"enable the mgmt service"`
}
// SetupFirstbootArgs is the setup service CLI parsing structure and type of the
// parsed result.
type SetupFirstbootArgs struct {
BinaryPath string `arg:"--binary-path" help:"path to the binary"`
Mkdir bool `arg:"--mkdir" help:"make the necessary firstboot dirs"`
Install bool `arg:"--install" help:"install the systemd firstboot service"`
Start bool `arg:"--start" help:"start the firstboot service (typically not used)"`
Enable bool `arg:"--enable" help:"enable the firstboot service"`
FirstbootStartArgs // Include these options if we want to specify them.
}
// FirstbootStartArgs is the firstboot service CLI parsing structure and type of
// the parsed result.
type FirstbootStartArgs struct {
LockFilePath string `arg:"--lock-file-path" help:"path to the lock file"`
ScriptsDir string `arg:"--scripts-dir" help:"path to the scripts dir"`
DoneDir string `arg:"--done-dir" help:"dir to move done scripts to"`
LoggingDir string `arg:"--logging-dir" help:"directory to store logs in"`
}
// DocsGenerateArgs is the docgen utility CLI parsing structure and type of the
// parsed result.
type DocsGenerateArgs struct {
Output string `arg:"--output" help:"output path to write to"`
RootDir string `arg:"--root-dir" help:"path to mgmt source dir"`
NoResources bool `arg:"--no-resources" help:"skip resource doc generation"`
NoFunctions bool `arg:"--no-functions" help:"skip function doc generation"`
}

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

@@ -0,0 +1,47 @@
// Mgmt
// Copyright (C) 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) 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) 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-2023+ James Shubin and the project contributors
// Copyright (C) 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-2023+ James Shubin and the project contributors
// Copyright (C) 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.
//go:build !root

14
debian/copyright vendored
View File

@@ -3,7 +3,7 @@ Upstream-Name: mgmt
Source: <https://github.com/purpleidea/mgmt>
Files: *
Copyright: Copyright (C) 2013-2023+ James Shubin and the project contributors
Copyright: Copyright (C) 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-2023+ James Shubin and the project contributors
// Copyright (C) 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,4 +1,4 @@
FROM golang:1.19
FROM golang:1.23
MAINTAINER Michał Czeraszkiewicz <contact@czerasz.com>

View File

@@ -1,4 +1,4 @@
FROM fedora:38
FROM fedora:41
LABEL org.opencontainers.image.authors="laurent.indermuehle@pm.me"
ENV GOPATH=/root/gopath

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.19.12.linux-amd64.tar.gz https://storage.googleapis.com/golang/go1.19.12.linux-amd64.tar.gz && tar -C /usr/local -xzf /opt/go1.19.12.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.23.5.linux-amd64.tar.gz https://storage.googleapis.com/golang/go1.23.5.linux-amd64.tar.gz && tar -C /usr/local -xzf /opt/go1.23.5.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.19
FROM golang:1.23
MAINTAINER Michał Czeraszkiewicz <contact@czerasz.com>

View File

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

96
docs/contributing.md Normal file
View File

@@ -0,0 +1,96 @@
# Contributing
What follows is a short guide with information for participants who wish to
contribute to the project. It hopes to set both some expectations and boundaries
so that we both benefit.
## Small patches
If you have a small patch which you believe is straightforward, should be easy
to merge, and isn't overly onerous on your time to write, please feel free to
send it our way without asking first. Bug fixes are excellent examples of small
patches. Please make sure to familiarize yourself with the rough coding style of
the project first, and read through the [style guide](style-guide.md).
## Making an excellent small patch
As a special case: We'd like to avoid minimal effort, one-off, drive-by patches
by bots and contributors looking to increase their "activity" numbers. As an
example: a patch which fixes a small linting issue isn't rousing, but a patch
that adds a linter test _and_ fixes a small linting issue is, because it shows
you put in more effort.
## Medium patches
Medium sized patches are especially welcome. Good examples of these patches
can include writing a new `mgmt` resource or function. You'll generally need
some knowledge of golang interfaces and concurrency to write these patches.
Before writing one of these, please make sure you understand some basics about
the project and how the tool works. After this, it is recommended that you join
our discussion channel to suggest the idea, and ideally include the actual API
you'd like to propose before writing the code and sending a patch.
## Making an excellent medium patch proposal
The "API" of a resource is the type signature of the resource struct, and the
"API" of a function is the type signature or signatures that it supports. (Since
functions can be polymorphic, more than one signature can be possible!) A good
proposal would likely also comment on the mechanisms the resources or functions
would use to watch for events, to check state, and to apply changes. If these
mechanisms need new dependencies, a brief survey of which dependencies are
available and why you recommend a particular one is encouraged.
## Large patches or structural and core patches
Please do not send us large, core or structurally significant patches without
first getting our approval and without getting some medium patches in first.
These patches take a lot of effort to review, and we don't want to skimp on our
commitment to that if we can't muster it. Instead grow our relationship with you
on the medium-sized patches first. (A core patch might refer to something that
touches either the function engine, resource engine, compiler internals, or
something that is part of one of the internal API's.)
## Expectations and boundaries
When interacting with the project and soliciting feedback (either for design or
during a code review) please keep in mind that the project (unfortunately!) has
time constraints and so must prioritize how it handles workloads. If you are
someone who has successfully sent in small patches, we will be more willing to
spend time mentoring your medium sized patches and so on. Think of it this way:
as you show that you're contributing to the project, we'll contribute more to
you. Put another way: we can't afford to spend large amounts of time discussing
potential patches with you, just to end up nowhere. Build up your reputation
with us, and we hope to help grow our symbiosis with you all the while as you
grow too!
## Energy output
The same goes for users and issue creators. There are times when we simply don't
have the cycles to discuss or litigate an issue with you. We wish we did have
more time, but it is finite, and running a project is not free. Therefore,
please keep in mind that you don't automatically qualify for free support or
attention.
## Attention seeking behaviours
Some folks spend too much time starting discussions, commenting on issues,
"planning" and otherwise displaying attention seeking behaviours. Please avoid
doing this as much as possible, especially if you are not already a major
contributor to the project. While it may be well intentioned, if it is
indistinguishable to us from intentional interference, then it's not welcome
behaviour. Remember that Free Software is not free to write. If you require more
attention, then either contribute more to the project, or consider paying for a
[support contract](https://mgmtconfig.com/).
## Consulting
Having said all that, there are some folks who want to do some longer-term
planning to decide if our core design and architecture is right for them to
invest in. If that's the case, and you aren't already a well-known project
contributor, please [contact](https://mgmtconfig.com/) us for a consulting
quote. We have packages available for both individuals and businesses.
## Respect
Please be mindful and respectful of others when interacting with the project and
its contributors. If you cannot abide by that, you may no longer be welcome.

View File

@@ -16,7 +16,7 @@ be working properly.
## Using Docker
Alternatively, you can check out the [docker-guide](docker-guide.md) in order to
Alternatively, you can check out the [docker folder](../docker/) in order to
develop or deploy using docker. This method is not endorsed or supported, so use
at your own risk, as it might not be working properly.
@@ -28,8 +28,9 @@ required for running the _test_ suite.
### Build
* `golang` 1.19 or higher (required, available in some distros and distributed
as a binary officially by [golang.org](https://golang.org/dl/))
* A modern `golang` version. The version available in the current Fedora
releases is usually supported. This is also distributed as a binary officially
by [golang.org](https://golang.org/dl/).
### Runtime

50
docs/docs.go Normal file
View File

@@ -0,0 +1,50 @@
// Mgmt
// Copyright (C) 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 docs provides a tool that generates documentation from the source.
//
// ./mgmt docs generate --output /tmp/docs.json && cat /tmp/docs.json | jq
package docs
import (
"context"
)
// API is the simple interface we expect for any setup items.
type API interface {
// Main runs everything for this setup item.
Main(context.Context) error
}
// Config is a struct of all the configuration values which are shared by all of
// the setup utilities. By including this as a separate struct, it can be used
// as part of the API if we want.
type Config struct {
//Foo string `arg:"--foo,env:MGMT_DOCGEN_FOO" help:"Foo..."` // TODO: foo
}

View File

@@ -133,13 +133,13 @@ You can read the introductory blog post about this topic here:
### Puppet support
You can supply a Puppet manifest instead of creating the (YAML) graph manually.
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).
[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)
1. Request the configuration from the puppet server (like `puppet agent` does)
`mgmt run puppet --puppet agent`
@@ -155,7 +155,7 @@ For more details and caveats see [puppet-guide.md](puppet-guide.md).
#### Blog post
An introductory post on the Puppet support is on
An introductory post on the puppet support is on
[Felix's blog](http://ffrank.github.io/features/2016/06/19/puppet-powered-mgmt/).
## Reference
@@ -274,6 +274,29 @@ and it can't guarantee it if the resource is blocked because of a failed
pre-requisite resource.
*XXX: This is currently not implemented!*
#### Dollar
Boolean. Dollar allows you to have a resource name that starts with a `$` sign.
This is false by default. This helps you catch cases when you write code like:
```mcl
$foo = "/tmp/file1"
file "$foo" {} # incorrect!
```
The above code would ignore the `$foo` variable and attempt to make a file named
`$foo` which would obviously not work. To correctly interpolate a variable, you
need to surround the name with curly braces.
```mcl
$foo = "/tmp/file1"
file "${foo}" {} # correct!
```
This meta param is a safety measure to make your life easier. It works for all
resources. If someone comes up with a resource which would routinely start with
a dollar sign, then we can revisit the default for this resource kind.
#### Reverse
Boolean. Reverse is a property that some resources can implement that specifies
@@ -362,29 +385,13 @@ size of 42, you can expect a semaphore if named: `:42`. It is expected that
consumers of the semaphore metaparameter always include a prefix to avoid a
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`.
#### `--allow-interactive`
Allow interactive prompting for SSH passwords if there is no authentication
method that works.
management tools such as `puppet` can be obtained with `--sema 1`.
#### `--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
@@ -453,7 +460,7 @@ directory in the git source repository. It is available from:
### Systemd:
See [`misc/mgmt.service`](misc/mgmt.service) for a sample systemd unit file.
See [`misc/mgmt.service`](../misc/mgmt.service) for a sample systemd unit file.
This unit file is part of the RPM.
To specify your custom options for `mgmt` on a systemd distro:
@@ -486,7 +493,7 @@ To report any bugs, please file a ticket at: [https://github.com/purpleidea/mgmt
## Authors
Copyright (C) 2013-2023+ James Shubin and the project contributors
Copyright (C) James Shubin and the project contributors
Please see the
[AUTHORS](https://github.com/purpleidea/mgmt/tree/master/AUTHORS) file

View File

@@ -216,6 +216,42 @@ requires a number of seconds as an argument.
./mgmt run lang examples/lang/hello0.mcl --converged-timeout=5
```
### Can I run `mgmt` for type-checking only?
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:
@@ -244,9 +280,76 @@ 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.
### Package resources error with: "The name is not activatable", what's wrong?
You may see an error like:
`main: error running auto edges: The name is not activatable`
This can happen because the mgmt `pkg` resource uses a library and daemon called
`PackageKit` to install packages. If it is not installed, then it cannot do its
work. On Fedora system you may wish to run `dnf install /usr/bin/pkcon` or on a
Debian system you may wish to run `apt install packagekit-tools`.
PackageKit is excellent because it provides both an API and an event system to
watch the package database for changes, and it abstracts away the differences
between the various package managers. If you'd prefer to not need to install
this tool, then you can contribute a native `pkg:rpm` and `pkg:deb` resource to
mgmt!
### When running mgmt, it says: "module path error: can't find a module path".
You might get an error along the lines of:
```
could not set scope: import scope `git://github.com/purpleidea/mgmt/modules/some_module_name/` failed: module path error: can't find a module path
```
This usually means that you haven't specified the directory that mgmt should use
when looking for modules. This could happen when using mgmt interactively or
when it's being run as a service. In such cases you may want the main invocation
to look something like:
```
mgmt run lang --module-path '/etc/mgmt/modules/' /etc/mgmt/main.mcl
```
### I get an error: "cannot open shared object file: No such file or directory".
Mgmt currently uses two libraries that depend on `.so` files being installed on
the host. Those are for `augeas` and `libvirt`. If those dependencies are not
present, then mgmt will not run. The complete error might look like:
```
mgmt: error while loading shared libraries: libvirt-lxc.so.0: cannot open shared object file: No such file or directory
```
or:
```
mgmt: error while loading shared libraries: libaugeas.so.0: cannot open shared object file: No such file or directory
```
or something similar. There are two solutions to this:
1. Use a build that doesn't include one or both of those features. You can build
that like: `GOTAGS="noaugeas novirt nodocker" make build`.
2. Install those dependencies. On a Fedora machine you might want to run:
```
dnf install libvirt-devel augeas-devel
```
On a Debian machine you might want to run:
```
apt install libvirt-dev libaugeas-dev
```
### Why do function names inside of templates include underscores?
The golang template library which we use to implement the template() function
The golang template library which we use to implement the golang.template() func
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.
@@ -284,6 +387,63 @@ 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 with string interpolation.
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}",
}
```
The first example will usually error with something along the lines of:
`unify error with: topLevel(func() { <built-in:concat> }): type error: int != str`
Now you know why this specific case doesn't work! We may reconsider allowing
other types to be pulled into interpolation in the future. If you have a good
case for this, then let us know.
### 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

View File

@@ -40,9 +40,9 @@ from looking at example code.
To implement a function, you'll need to create a file that imports the
[`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/funcs/core/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/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:
[`lang/core/`](https://github.com/purpleidea/mgmt/tree/master/lang/core/). The
function should be implemented as a `simple.Scaffold` in our API. It is then
registered with the engine during `init()`. An example explains it best:
### Example
@@ -50,6 +50,7 @@ then registered with the engine during `init()`. An example explains it best:
package simple
import (
"context"
"fmt"
"github.com/purpleidea/mgmt/lang/funcs/simple"
@@ -59,9 +60,10 @@ import (
// 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.
simple.ModuleRegister(ModuleName, "talkingsquare", &types.FuncValue{
simple.ModuleRegister(ModuleName, "talkingsquare", &simple.Scaffold{
T: types.NewType("func(int) str"), // declare the signature
V: func(input []types.Value) (types.Value, error) {
F: func(ctx context.Context, input []types.Value) (types.Value, error) {
i := input[0].Int() // get first arg as an int64
// must return the above specified value
return &types.StrValue{
@@ -87,109 +89,41 @@ mgmt engine to shutdown. It should be seen as the equivalent to calling a
Ideally, your functions should never need to error. You should never cause a
real `panic()`, since this could have negative consequences to the system.
## Simple Polymorphic Function API
Most functions should be implemented using the simple function API. If they need
to have multiple polymorphic forms under the same name, then you can use this
API. This is useful for situations when it would be unhelpful to name the
functions differently, or when the number of possible signatures for the
function would be infinite.
The canonical example of this is the `len` function which returns the number of
elements in either a `list` or a `map`. Since lists and maps are two different
types, you can see that polymorphism is more convenient than requiring a
`listlen` and `maplen` function. Nevertheless, it is also required because a
`list of int` is a different type than a `list of str`, which is a different
type than a `list of list of str` and so on. As you can see the number of
possible input types for such a `len` function is infinite.
Another downside to implementing your functions with this API is that they will
*not* be made available for use inside templates. This is a limitation of the
`golang` template library. In the future if this limitation proves to be
significantly annoying, we might consider writing our own template library.
As with the simple, non-polymorphic API, you can only implement [pure](https://en.wikipedia.org/wiki/Pure_function)
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 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/funcs/core/`](https://github.com/purpleidea/mgmt/tree/master/lang/funcs/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. 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:
### Example
```golang
package simple
import (
"context"
"fmt"
"github.com/purpleidea/mgmt/lang/funcs/simplepoly"
"github.com/purpleidea/mgmt/lang/funcs/simple"
"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"),
V: Len,
},
{
T: types.NewType("func({variant: variant}) int"),
V: Len,
},
// This is the actual definition of the `len` function.
simple.Register("len", &simple.Scaffold{
T: types.NewType("func(?1) int"), // contains a unification var
C: simple.TypeMatch([]string{ // match on any of these sigs
"func(str) int",
"func([]?1) int",
"func(map{?1: ?2}) int",
}),
// The implementation is left as an exercise for the reader.
F: Len,
})
}
// Len returns the number of elements in a list or the number of key pairs in a
// map. It can operate on either of these types.
func Len(input []types.Value) (types.Value, error) {
var length int
switch k := input[0].Type().Kind; k {
case types.KindList:
length = len(input[0].List())
case types.KindMap:
length = len(input[0].Map())
default:
return nil, fmt.Errorf("unsupported kind: %+v", k)
}
return &types.IntValue{
V: int64(length),
}, nil
}
```
This simple polymorphic function can accept an infinite number of signatures, of
which there are two basic forms. Both forms return an `int` as is seen above.
The first form takes a `[]variant` which means a `list` of `variant`'s, which
means that it can be a list of any type, since `variant` itself is not a
concrete type. The second form accepts a `{variant: variant}`, which means that
it accepts any form of `map` as input.
## Simple Polymorphic Function API
The implementation for both of these forms is the same: it is handled by the
same `Len` function which is clever enough to be able to deal with any of the
type signatures possible from those two patterns.
At compile time, if your `mcl` code type checks correctly, a concrete type will
be known for each and every usage of the `len` function, and specific values
will be passed in for this code to compute the length of. As usual, make sure to
only write safe code that will not panic! A panic is a bug. If you really cannot
continue, then you must return an error.
Most functions should be implemented using the simple function API. If they need
to have multiple polymorphic forms under the same name, with each resultant type
match needing to be paired to a different implementation, then you can use this
API. This is useful for situations when the functions differ in output type
only.
## Function API
@@ -342,7 +276,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
@@ -356,23 +292,6 @@ We don't expect this functionality to be particularly useful or common, as it's
probably easier and preferable to simply import common golang library code into
multiple different functions instead.
## Polymorphic Function API
The polymorphic function API is an API that lets you implement functions which
do not necessarily have a single static function signature. After compile time,
all functions must have a static function signature. We also know that there
might be different ways you would want to call `printf`, such as:
`printf("the %s is %d", "answer", 42)` or `printf("3 * 2 = %d", 3 * 2)`. Since
you couldn't implement the infinite number of possible signatures, this API lets
you write code which can be coerced into different forms. This makes
implementing what would appear to be generic or polymorphic, instead of
something that is actually static and that still has the static type safety
properties that were guaranteed by the mgmt language.
Since this is an advanced topic, it is not described in full at this time. For
more information please have a look at the source code comments, some of the
existing implementations, and ask around in the community.
## Frequently asked questions
(Send your questions as a patch to this FAQ! I'll review it, merge it, and

795
docs/generate.go Normal file
View File

@@ -0,0 +1,795 @@
// Mgmt
// Copyright (C) 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 docs
import (
"context"
"encoding/json"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"sort"
"strings"
cliUtil "github.com/purpleidea/mgmt/cli/util"
docsUtil "github.com/purpleidea/mgmt/docs/util"
"github.com/purpleidea/mgmt/engine"
engineUtil "github.com/purpleidea/mgmt/engine/util"
"github.com/purpleidea/mgmt/lang/funcs"
"github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/util"
)
const (
// JSONSuffix is the output extension for the generated documentation.
JSONSuffix = ".json"
)
// Generate is the main entrypoint for this command. It generates everything.
type Generate struct {
*cliUtil.DocsGenerateArgs // embedded config
Config // embedded Config
// Program is the name of this program, usually set at compile time.
Program string
// Version is the version of this program, usually set at compile time.
Version string
// Debug represents if we're running in debug mode or not.
Debug bool
// Logf is a logger which should be used.
Logf func(format string, v ...interface{})
}
// Main runs everything for this setup item.
func (obj *Generate) Main(ctx context.Context) error {
if err := obj.Validate(); err != nil {
return err
}
if err := obj.Run(ctx); err != nil {
return err
}
return nil
}
// Validate verifies that the structure has acceptable data stored within.
func (obj *Generate) Validate() error {
if obj == nil {
return fmt.Errorf("data is nil")
}
if obj.Program == "" {
return fmt.Errorf("program is empty")
}
if obj.Version == "" {
return fmt.Errorf("version is empty")
}
return nil
}
// Run performs the desired actions to generate the documentation.
func (obj *Generate) Run(ctx context.Context) error {
outputFile := obj.DocsGenerateArgs.Output
if outputFile == "" || !strings.HasSuffix(outputFile, JSONSuffix) {
return fmt.Errorf("must specify output")
}
// support relative paths too!
if !strings.HasPrefix(outputFile, "/") {
wd, err := os.Getwd()
if err != nil {
return err
}
outputFile = filepath.Join(wd, outputFile)
}
if obj.Debug {
obj.Logf("output: %s", outputFile)
}
// Ensure the directory exists.
//d := filepath.Dir(outputFile)
//if err := os.MkdirAll(d, 0750); err != nil {
// return fmt.Errorf("could not make output dir at: %s", d)
//}
resources, err := obj.genResources()
if err != nil {
return err
}
functions, err := obj.genFunctions()
if err != nil {
return err
}
data := &Output{
Version: safeVersion(obj.Version),
Resources: resources,
Functions: functions,
}
b, err := json.Marshal(data)
if err != nil {
return err
}
b = append(b, '\n') // needs a trailing newline
if err := os.WriteFile(outputFile, b, 0600); err != nil {
return err
}
obj.Logf("wrote: %s", outputFile)
return nil
}
func (obj *Generate) getResourceInfo(kind, filename, structName string) (*ResourceInfo, error) {
rootDir := obj.DocsGenerateArgs.RootDir
if rootDir == "" {
wd, err := os.Getwd()
if err != nil {
return nil, err
}
rootDir = wd + "/" // add a trailing slash
}
if !strings.HasPrefix(rootDir, "/") || !strings.HasSuffix(rootDir, "/") {
return nil, fmt.Errorf("bad root dir: %s", rootDir)
}
// filename might be "noop.go" for example
p := filepath.Join(rootDir, engine.ResourcesRelDir, filename)
fset := token.NewFileSet()
// f is a: https://golang.org/pkg/go/ast/#File
f, err := parser.ParseFile(fset, p, nil, parser.ParseComments)
if err != nil {
return nil, err
}
// mcl field name to golang field name
mapping, err := engineUtil.LangFieldNameToStructFieldName(kind)
if err != nil {
return nil, err
}
// golang field name to mcl field name
nameMap, err := util.MapSwap(mapping)
if err != nil {
return nil, err
}
// mcl field name to mcl type
typMap, err := engineUtil.LangFieldNameToStructType(kind)
if err != nil {
return nil, err
}
ri := &ResourceInfo{}
// Populate the fields, even if they don't have a comment.
ri.Name = structName // golang name
ri.Kind = kind // duplicate data
ri.File = filename
ri.Fields = make(map[string]*ResourceFieldInfo)
for mclFieldName, fieldName := range mapping {
typ, exists := typMap[mclFieldName]
if !exists {
continue
}
ri.Fields[mclFieldName] = &ResourceFieldInfo{
Name: fieldName,
Type: typ.String(),
Desc: "", // empty for now
}
}
var previousComment *ast.CommentGroup
// Walk through the AST...
ast.Inspect(f, func(node ast.Node) bool {
// Comments above the struct appear as a node right _before_ we
// find the struct, so if we see one, save it for later...
if cg, ok := node.(*ast.CommentGroup); ok {
previousComment = cg
return true
}
typeSpec, ok := node.(*ast.TypeSpec)
if !ok {
return true
}
name := typeSpec.Name.Name // name is now known!
// If the struct isn't what we're expecting, then move on...
if name != structName {
return true
}
// Check if the TypeSpec is a named struct type that we want...
st, ok := typeSpec.Type.(*ast.StructType)
if !ok {
return true
}
// At this point, we have the struct we want...
var comment *ast.CommentGroup
if typeSpec.Doc != nil {
// I don't know how to even get here...
comment = typeSpec.Doc // found!
} else if previousComment != nil {
comment = previousComment // found!
previousComment = nil
}
ri.Desc = commentCleaner(comment)
// Iterate over the fields of the struct
for _, field := range st.Fields.List {
// Check if the field has a comment associated with it
if field.Doc == nil {
continue
}
if len(field.Names) < 1 { // XXX: why does this happen?
continue
}
fieldName := field.Names[0].Name
if fieldName == "" { // Can this happen?
continue
}
if isPrivate(fieldName) {
continue
}
mclFieldName, exists := nameMap[fieldName]
if !exists {
continue
}
ri.Fields[mclFieldName].Desc = commentCleaner(field.Doc)
}
return true
})
return ri, nil
}
func (obj *Generate) genResources() (map[string]*ResourceInfo, error) {
resources := make(map[string]*ResourceInfo)
if obj.DocsGenerateArgs.NoResources {
return resources, nil
}
r := engine.RegisteredResourcesNames()
sort.Strings(r)
for _, kind := range r {
metadata, err := docsUtil.LookupResource(kind)
if err != nil {
return nil, err
}
if strings.HasPrefix(kind, "_") {
// TODO: Should we display these somehow?
// built-in resource
continue
}
ri, err := obj.getResourceInfo(kind, metadata.Filename, metadata.Typename)
if err != nil {
return nil, err
}
if ri.Name == "" {
return nil, fmt.Errorf("empty resource name: %s", kind)
}
if ri.File == "" {
return nil, fmt.Errorf("empty resource file: %s", kind)
}
if ri.Desc == "" {
obj.Logf("empty resource desc: %s", kind)
}
fields := []string{}
for field := range ri.Fields {
fields = append(fields, field)
}
sort.Strings(fields)
for _, field := range fields {
if ri.Fields[field].Desc == "" {
obj.Logf("empty resource (%s) field desc: %s", kind, field)
}
}
resources[kind] = ri
}
return resources, nil
}
func (obj *Generate) getFunctionInfo(pkg, name string, metadata *docsUtil.Metadata) (*FunctionInfo, error) {
rootDir := obj.DocsGenerateArgs.RootDir
if rootDir == "" {
wd, err := os.Getwd()
if err != nil {
return nil, err
}
rootDir = wd + "/" // add a trailing slash
}
if !strings.HasPrefix(rootDir, "/") || !strings.HasSuffix(rootDir, "/") {
return nil, fmt.Errorf("bad root dir: %s", rootDir)
}
if metadata.Filename == "" {
return nil, fmt.Errorf("empty filename for: %s.%s", pkg, name)
}
// filename might be "pow.go" for example and contain a rel dir
p := filepath.Join(rootDir, funcs.FunctionsRelDir, metadata.Filename)
fset := token.NewFileSet()
// f is a: https://golang.org/pkg/go/ast/#File
f, err := parser.ParseFile(fset, p, nil, parser.ParseComments)
if err != nil {
return nil, err
}
fi := &FunctionInfo{}
fi.Name = metadata.Typename
fi.File = metadata.Filename
var previousComment *ast.CommentGroup
found := false
rawFunc := func(node ast.Node) (*ast.CommentGroup, string) {
fd, ok := node.(*ast.FuncDecl)
if !ok {
return nil, ""
}
return fd.Doc, fd.Name.Name // name is now known!
}
rawStruct := func(node ast.Node) (*ast.CommentGroup, string) {
typeSpec, ok := node.(*ast.TypeSpec)
if !ok {
return nil, ""
}
// Check if the TypeSpec is a named struct type that we want...
if _, ok := typeSpec.Type.(*ast.StructType); !ok {
return nil, ""
}
return typeSpec.Doc, typeSpec.Name.Name // name is now known!
}
// Walk through the AST...
ast.Inspect(f, func(node ast.Node) bool {
// Comments above the struct appear as a node right _before_ we
// find the struct, so if we see one, save it for later...
if cg, ok := node.(*ast.CommentGroup); ok {
previousComment = cg
return true
}
doc, name := rawFunc(node) // First see if it's a raw func.
if name == "" {
doc, name = rawStruct(node) // Otherwise it's a struct.
}
// If the func isn't what we're expecting, then move on...
if name != metadata.Typename {
return true
}
var comment *ast.CommentGroup
if doc != nil {
// I don't know how to even get here...
comment = doc // found!
} else if previousComment != nil {
comment = previousComment // found!
previousComment = nil
}
fi.Desc = commentCleaner(comment)
found = true
return true
})
if !found {
//return nil, nil
}
return fi, nil
}
func (obj *Generate) genFunctions() (map[string]*FunctionInfo, error) {
functions := make(map[string]*FunctionInfo)
if obj.DocsGenerateArgs.NoFunctions {
return functions, nil
}
m := funcs.Map() // map[string]func() interfaces.Func
names := []string{}
for name := range m {
names = append(names, name)
}
sort.Slice(names, func(i, j int) bool {
a := names[i]
b := names[j]
// TODO: do a sorted-by-package order.
return a < b
})
for _, name := range names {
//v := m[name]
//fn := v()
fn := m[name]()
// eg: golang/strings.has_suffix
sp := strings.Split(name, ".")
if len(sp) == 0 {
return nil, fmt.Errorf("unexpected empty function")
}
if len(sp) > 2 {
return nil, fmt.Errorf("unexpected function name: %s", name)
}
n := sp[0]
p := sp[0]
if len(sp) == 1 { // built-in
p = "" // no package!
}
if len(sp) == 2 { // normal import
n = sp[1]
}
if strings.HasPrefix(n, "_") {
// TODO: Should we display these somehow?
// built-in function
continue
}
var sig *string
//iface := ""
if x := fn.Info().Sig; x != nil {
s := x.String()
sig = &s
//iface = "simple"
}
metadata := &docsUtil.Metadata{}
// XXX: maybe we need a better way to get this?
mdFunc, ok := fn.(interfaces.MetadataFunc)
if !ok {
// Function doesn't tell us what the data is, let's try
// to get it automatically...
metadata.Typename = funcs.GetFunctionName(fn) // works!
metadata.Filename = "" // XXX: How can we get this?
// XXX: We only need this back-channel metadata store
// because we don't know how to get the filename without
// manually writing code in each function. Alternatively
// we could add a New() method to each struct and then
// we could modify the struct instead of having it be
// behind a copy which is needed to get new copies!
var err error
metadata, err = docsUtil.LookupFunction(name)
if err != nil {
return nil, err
}
} else if mdFunc == nil {
// programming error
return nil, fmt.Errorf("unexpected empty metadata for function: %s", name)
} else {
metadata = mdFunc.GetMetadata()
}
if metadata == nil {
return nil, fmt.Errorf("unexpected nil metadata for function: %s", name)
}
// This may be an empty func name if the function did not know
// how to get it. (This is normal for automatic regular funcs.)
if metadata.Typename == "" {
metadata.Typename = funcs.GetFunctionName(fn) // works!
}
fi, err := obj.getFunctionInfo(p, n, metadata)
if err != nil {
return nil, err
}
// We may not get any fields added if we can't find anything...
fi.Name = metadata.Typename
fi.Package = p
fi.Func = n
fi.File = metadata.Filename
//fi.Desc = desc
fi.Signature = sig
// Hack for golang generated functions!
if strings.HasPrefix(fi.Package, "golang/") && fi.File == "generated_funcs.go" {
pkg := fi.Package[len("golang/"):]
frag := strings.TrimPrefix(fi.Name, strings.Title(strings.Join(strings.Split(pkg, "/"), ""))) // yuck
fi.File = fmt.Sprintf("https://pkg.go.dev/%s#%s", pkg, frag)
}
if fi.Func == "" {
return nil, fmt.Errorf("empty function name: %s", name)
}
if fi.File == "" {
return nil, fmt.Errorf("empty function file: %s", name)
}
if fi.Desc == "" {
obj.Logf("empty function desc: %s", name)
}
if fi.Signature == nil {
obj.Logf("empty function sig: %s", name)
}
functions[name] = fi
}
return functions, nil
}
// Output is the type of the final data that will be for the json output.
type Output struct {
// Version is the sha1 or ref name of this specific version. This is
// used if we want to generate documentation with links matching the
// correct version. If unspecified then this assumes git master.
Version string `json:"version"`
// Resources contains the collection of every available resource!
// FIXME: should this be a list instead?
Resources map[string]*ResourceInfo `json:"resources"`
// Functions contains the collection of every available function!
// FIXME: should this be a list instead?
Functions map[string]*FunctionInfo `json:"functions"`
}
// ResourceInfo stores some information about each resource.
type ResourceInfo struct {
// Name is the golang name of this resource.
Name string `json:"name"`
// Kind is the kind of this resource.
Kind string `json:"kind"`
// File is the file name where this resource exists.
File string `json:"file"`
// Desc explains what this resource does.
Desc string `json:"description"`
// Fields is a collection of each resource field and corresponding info.
Fields map[string]*ResourceFieldInfo `json:"fields"`
}
// ResourceFieldInfo stores some information about each field in each resource.
type ResourceFieldInfo struct {
// Name is what this field is called in golang format.
Name string `json:"name"`
// Type is the mcl type for this field.
Type string `json:"type"`
// Desc explains what this field does.
Desc string `json:"description"`
}
// FunctionInfo stores some information about each function.
type FunctionInfo struct {
// Name is the golang name of this function. This may be an actual
// function if used by the simple API, or the name of a struct.
Name string `json:"name"`
// Package is the import name to use to get to this function.
Package string `json:"package"`
// Func is the name of the function in that package.
Func string `json:"func"`
// File is the file name where this function exists.
File string `json:"file"`
// Desc explains what this function does.
Desc string `json:"description"`
// Signature is the type signature of this function. If empty then the
// signature is not known statically and it may be polymorphic.
Signature *string `json:"signature,omitempty"`
}
// commentCleaner takes a comment group and returns it as a clean string. It
// removes the spurious newlines and programmer-focused comments. If there are
// blank lines, it replaces them with a single newline. The idea is that the
// webpage formatter would replace the newline with a <br /> or similar. This
// code is a modified alternative of the ast.CommentGroup.Text() function.
func commentCleaner(g *ast.CommentGroup) string {
if g == nil {
return ""
}
comments := make([]string, len(g.List))
for i, c := range g.List {
comments[i] = c.Text
}
lines := make([]string, 0, 10) // most comments are less than 10 lines
for _, c := range comments {
// Remove comment markers.
// The parser has given us exactly the comment text.
switch c[1] {
case '/':
//-style comment (no newline at the end)
c = c[2:]
if len(c) == 0 {
// empty line
break
}
if isDevComment(c[1:]) { // get rid of one space
continue
}
if c[0] == ' ' {
// strip first space - required for Example tests
c = c[1:]
break
}
//if isDirective(c) {
// // Ignore //go:noinline, //line, and so on.
// continue
//}
case '*':
/*-style comment */
c = c[2 : len(c)-2]
}
// Split on newlines.
cl := strings.Split(c, "\n")
// Walk lines, stripping trailing white space and adding to list.
for _, l := range cl {
lines = append(lines, stripTrailingWhitespace(l))
}
}
// Remove leading blank lines; convert runs of interior blank lines to a
// single blank line.
n := 0
for _, line := range lines {
if line != "" || n > 0 && lines[n-1] != "" {
lines[n] = line
n++
}
}
lines = lines[0:n]
// Concatenate all of these together. Blank lines should be a newline.
s := ""
for i, line := range lines {
if line == "" {
continue
}
s += line
if i < len(lines)-1 { // Is there another line?
if lines[i+1] == "" {
s += "\n" // Will eventually be a line break.
} else {
s += " "
}
}
}
return s
}
// TODO: should we use unicode.IsSpace instead?
func isWhitespace(ch byte) bool { return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' }
// TODO: should we replace with a strings package stdlib function?
func stripTrailingWhitespace(s string) string {
i := len(s)
for i > 0 && isWhitespace(s[i-1]) {
i--
}
return s[0:i]
}
// isPrivate specifies if a field name is "private" or not.
func isPrivate(fieldName string) bool {
if fieldName == "" {
panic("invalid field name")
}
x := fieldName[0:1]
if strings.ToLower(x) == x {
return true // it was already private
}
return false
}
// isDevComment tells us that the comment is for developers only!
func isDevComment(comment string) bool {
if strings.HasPrefix(comment, "TODO:") {
return true
}
if strings.HasPrefix(comment, "FIXME:") {
return true
}
if strings.HasPrefix(comment, "XXX:") {
return true
}
return false
}
// safeVersion parses the main version string and returns a short hash for us.
// For example, we might get a string of 0.0.26-176-gabcdef012-dirty as input,
// and we'd want to return abcdef012.
func safeVersion(version string) string {
const dirty = "-dirty"
s := version
if strings.HasSuffix(s, dirty) { // helpful dirty remover
s = s[0 : len(s)-len(dirty)]
}
ix := strings.LastIndex(s, "-")
if ix == -1 { // assume we have a standalone version (future proofing?)
return s
}
s = s[ix+1:]
// From the `git describe` man page: The "g" prefix stands for "git" and
// is used to allow describing the version of a software depending on
// the SCM the software is managed with. This is useful in an
// environment where people may use different SCMs.
const g = "g"
if strings.HasPrefix(s, g) {
s = s[len(g):]
}
return s
}

View File

@@ -283,6 +283,14 @@ one of many ways you can perform iterative tasks that you might have
traditionally used a `for` loop for instead. This is preferred, because flow
control is error-prone and can make for less readable code.
The single `str` variation, may only be used when it is possible for the
compiler to determine statically that the value is of that type. Otherwise, it
will assume it to be a list of strings. Programmers should explicitly wrap their
variables in a string by interpolation to force this static `str` determination,
or in square brackets to force a list. The former is generally preferable
because it generates a smaller function graph since it doesn't need to build a
list.
##### Internal edges
Resources may also declare edges internally. The edges may point to or from
@@ -337,6 +345,28 @@ to express a relationship between three resources. The first character in the
resource kind must be capitalized so that the parser can't ascertain
unambiguously that we are referring to a dependency relationship.
##### Edge naming
Each edge must have a unique name of type `str` that is used to uniquely
identify that edge, and can be used in the functioning of the edge at its
discretion.
Alternatively, the name value may be a list of strings `[]str` to build a list
of edges, each with a name from that list.
Using this construct is a veiled form of looping (iteration). This technique is
one of many ways you can perform iterative tasks that you might have
traditionally used a `for` loop for instead. This is preferred, because flow
control is error-prone and can make for less readable code.
The single `str` variation, may only be used when it is possible for the
compiler to determine statically that the value is of that type. Otherwise, it
will assume it to be a list of strings. Programmers should explicitly wrap their
variables in a string by interpolation to force this static `str` determination,
or in square brackets to force a list. The former is generally preferable
because it generates a smaller function graph since it doesn't need to build a
list.
#### Class
A class is a grouping structure that bind's a list of statements to a name in
@@ -402,6 +432,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 +476,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
@@ -478,7 +591,7 @@ Lexing is done using [nex](https://github.com/blynn/nex). It is a pure-golang
implementation which is similar to _Lex_ or _Flex_, but which produces golang
code instead of C. It integrates reasonably well with golang's _yacc_ which is
used for parsing. The token definitions are in:
[lang/lexer.nex](https://github.com/purpleidea/mgmt/tree/master/lang/lexer.nex).
[lang/lexer.nex](https://github.com/purpleidea/mgmt/tree/master/lang/parser/lexer.nex).
Lexing and parsing run together by calling the `LexParse` method.
#### Parsing
@@ -490,7 +603,7 @@ and trial and error. One small advantage yacc has over standard yacc is that it
can produce error messages from examples. The best documentation is to examine
the source. There is a short write up available [here](https://research.swtch.com/yyerror).
The yacc file exists at:
[lang/parser.y](https://github.com/purpleidea/mgmt/tree/master/lang/parser.y).
[lang/parser.y](https://github.com/purpleidea/mgmt/tree/master/lang/parser/parser.y).
Lexing and parsing run together by calling the `LexParse` method.
#### Interpolation
@@ -502,6 +615,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
@@ -522,23 +639,27 @@ so that each `Expr` node in the AST knows what to expect. Type annotation is
allowed in situations when you want to explicitly specify a type, or when the
compiler cannot deduce it, however, most of it can usually be inferred.
For type inferrence to work, each node in the AST implements a `Unify` method
which is able to return a list of invariants that must hold true. This starts at
the top most AST node, and gets called through to it's children to assemble a
giant list of invariants. The invariants can take different forms. They can
specify that a particular expression must have a particular type, or they can
specify that two expressions must have the same types. More complex invariants
allow you to specify relationships between different types and expressions.
Furthermore, invariants can allow you to specify that only one invariant out of
a set must hold true.
For type inference to work, each `Stmt` node in the AST implements a `TypeCheck`
method which is able to return a list of invariants that must hold true. This
starts at the top most AST node, and gets called through to it's children to
assemble a giant list of invariants. The invariants all have the same form. They
specify that a particular expression corresponds to two particular types which
may both contain unification variables.
Each `Expr` node in the AST implements an `Infer` and `Check` method. The
`Infer` method returns the type of that node along with a list of invariants as
described above. Unification variables can of course be used throughout. The
`Check` method always uses a generic check implementation and generally doesn't
need to be implemented by the user.
Once the list of invariants has been collected, they are run through an
invariant solver. The solver can return either return successfully or with an
error. If the solver returns successfully, it means that it has found a trivial
error. If the solver returns successfully, it means that it has found a single
mapping between every expression and it's corresponding type. At this point it
is a simple task to run `SetType` on every expression so that the types are
known. If the solver returns in error, it is usually due to one of two
possibilities:
known. During this stage, each SetType method verifies that it's a compatible
type that it can use. If either that method or if the solver returns in error,
it is usually due to one of two possibilities:
1. Ambiguity
@@ -558,8 +679,8 @@ possibilities:
always happens if the user has made a type error in their program.
Only one solver currently exists, but it is possible to easily plug in an
alternate implementation if someone more skilled in the art of solver design
would like to propose a more logical or performant variant.
alternate implementation if someone wants to experiment with the art of solver
design and would like to propose a more logical or performant variant.
#### Function graph generation
@@ -600,8 +721,9 @@ If you'd like to create a built-in, core function, you'll need to implement the
function API interface named `Func`. It can be found in
[lang/interfaces/func.go](https://github.com/purpleidea/mgmt/tree/master/lang/interfaces/func.go).
Your function must have a specific type. For example, a simple math function
might have a signature of `func(x int, y int) int`. As you can see, all the
types are known _before_ compile time.
might have a signature of `func(x int, y int) int`. The simple functions have
their types known _before_ compile time. You may also include unification
variables in the function signature as long as the top-level type is a function.
A separate discussion on this matter can be found in the [function guide](function-guide.md).
@@ -629,6 +751,12 @@ added in the future. This method is usually called before any other, and should
not depend on any other method being called first. Other methods must not depend
on this method being called first.
If you use any unification variables in the function signature, then your
function will *not* be made available for use inside templates. This is a
limitation of the `golang` templating library. In the future if this limitation
proves to be significantly annoying, we might consider writing our own template
library.
#### Example
```golang
@@ -639,6 +767,18 @@ func (obj *FooFunc) Info() *interfaces.Info {
}
```
#### Example
This example contains unification variables.
```golang
func (obj *FooFunc) Info() *interfaces.Info {
return &interfaces.Info{
Sig: types.NewType("func(a ?1, b ?2, foo [?3]) ?1"),
}
}
```
### Init
```golang
@@ -676,7 +816,7 @@ 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
@@ -698,52 +838,70 @@ context cancels.
```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
### BuildableFunc Function API
For some functions, it might be helpful to be able to implement a function once,
but to have multiple polymorphic variants that can be chosen at compile time.
For this more advanced topic, you will need to use the
[Polymorphic Function API](#polymorphic-function-api). This will help with code
reuse when you have a small, finite number of possible type signatures, and also
for more complicated cases where you might have an infinite number of possible
type signatures. (eg: `[]str`, or `[][]str`, or `[][][]str`, etc...)
For some functions, it might be helpful to have a function which needs a "build"
step which is run after type unification. This step can be used to build the
function using the determined type, but it may also just be used for checking
that unification picked a valid solution.
Suppose you want to implement a function which can assume different type
signatures. The mgmt language does not support polymorphic types-- you must use
static types throughout the language, however, it is legal to implement a
function which can take different specific type signatures based on how it is
used. For example, you might wish to add a math function which could take the
form of `func(x int, x int) int` or `func(x float, x float) float` depending on
the input values. You might also want to implement a function which takes an
arbitrary number of input arguments (the number must be statically fixed at the
compile time of your program though) and which returns a string.
form of `func(x int, y int) int` or `func(x float, y float) float` depending on
the input values. For this case you could use a signature containing unification
variables, eg: `func(x ?1, y ?1) ?1`. At the end the buildable function would
need to check that it received a `?1` type of either `int` or `float`, since
this function might not support doing math on strings. Remember that type
unification can only return zero or one solutions, it's not possible to return
more than one, which is why this secondary validation step is a brilliant way to
filter out invalid solutions without needing to encode them as algebraic
conditions during the solver state, which would otherwise make it exponential.
The `PolyFunc` interface adds additional methods which you must implement to
satisfy such a function implementation. If you'd like to implement such a
function, then please notify the project authors, and they will expand this
section with a longer description of the process.
### InferableFunc Function API
#### Examples
You might also want to implement a function which takes an arbitrary number of
input arguments (the number must be statically fixed at the compile time of your
program though) and which returns a string or something else.
What follows are a few examples that might help you understand some of the
language details.
The `InferableFunc` interface adds ad additional `FuncInfer` method which you
must implement to satisfy such a function implementation. This lets you
dynamically generate a type signature (including unification variables) and a
list of invariants before running the type unification solver. It takes as input
a list of the statically known input types and input values (if any) and as well
the number of input arguments specified. This is usually enough information to
generate a fixed type signature of a fixed size.
##### Example Foo
TODO: please add an example here!
##### Example Bar
TODO: please add an example here!
Using this API should generally be pretty rare, but it is how certain special
functions such as `fmt.printf` are built. If you'd like to implement such a
function, then please notify the project authors as we're curious about your
use case.
## Frequently asked questions
(Send your questions as a patch to this FAQ! I'll review it, merge it, and
respond by commit with the answer.)
### Why am I getting a deploy.readfile error when the file actually exists?
You may be seeing an error like:
`readfile`: open /*/files/foo: file does not exist can't read file `/files/foo`?
If you look, the `foo` file is indeed in the `files/` directory. The problem is
that the `files/` directory won't be seen if you didn't specify to include it as
part of your deploy. To do so, chances are that all you need to do is add a
`metadata.yaml` file into the parent directory to that files folder. This will
be used as the entrypoint instead of the naked `main.mcl` file that you have
there, and with that metadata entrypoint, you get a default `files/` directory
added. You can of course change the `files/` path by setting a key in the
`metadata.yaml` file, but we recommend you leave it as the default.
### What is the difference between `ExprIf` and `StmtIf`?
The language contains both an `if` expression, and and `if` statement. An `if`

View File

@@ -21,15 +21,15 @@ if we missed something that you think is relevant!
| Felix Frank | blog | [Puppet Powered Mgmt (puppet to mgmt tl;dr)](https://ffrank.github.io/features/2016/06/19/puppet-powered-mgmt/) |
| James Shubin | blog | [Automatic clustering in mgmt](https://purpleidea.com/blog/2016/06/20/automatic-clustering-in-mgmt/) |
| James Shubin | video | [Recording from CoreOSFest 2016](https://www.youtube.com/watch?v=KVmDCUA42wc&html5=1) |
| James Shubin | video | [Recording from DebConf16](http://meetings-archive.debian.net/pub/debian-meetings/2016/debconf16/Next_Generation_Config_Mgmt.webm) ([Slides](https://annex.debconf.org//debconf-share/debconf16/slides/15-next-generation-config-mgmt.pdf)) |
| James Shubin | video | [Recording from DebConf16](http://meetings-archive.debian.net/pub/debian-meetings/2016/debconf16/Next_Generation_Config_Mgmt.webm) |
| Felix Frank | blog | [Edging It All In (puppet and mgmt edges)](https://ffrank.github.io/features/2016/07/12/edging-it-all-in/) |
| Felix Frank | blog | [Translating All The Things (puppet to mgmt translation warnings)](https://ffrank.github.io/features/2016/08/19/translating-all-the-things/) |
| James Shubin | video | [Recording from systemd.conf 2016](https://www.youtube.com/watch?v=jB992Zb3nH0&html5=1) |
| James Shubin | video | [Recording from systemd.conf 2016](https://www.youtube.com/watch?v=_TowsFAWWRA) |
| James Shubin | blog | [Remote execution in mgmt](https://purpleidea.com/blog/2016/10/07/remote-execution-in-mgmt/) |
| James Shubin | video | [Recording from High Load Strategy 2016](https://vimeo.com/191493409) |
| James Shubin | video | [Recording from NLUUG 2016](https://www.youtube.com/watch?v=MmpwOQAb_SE&html5=1) |
| James Shubin | video | [Recording from High Load Strategy 2016](https://www.youtube.com/watch?v=-4g14KUVPVk) |
| James Shubin | video | [Recording from NLUUG 2016](https://www.youtube.com/watch?v=0vO93ni1zos) |
| James Shubin | blog | [Send/Recv in mgmt](https://purpleidea.com/blog/2016/12/07/sendrecv-in-mgmt/) |
| Julien Pivotto | blog | [Augeas resource for mgmt](https://roidelapluie.be/blog/2017/02/14/mgmt-augeas/) |
| Julien Pivotto | blog | [Augeas resource for mgmt](https://purpleidea.com/cached/mgmt-augeas.html) (Cached from: https://roidelapluie.be/blog/2017/02/14/mgmt-augeas/) |
| James Shubin | blog | [Metaparameters in mgmt](https://purpleidea.com/blog/2017/03/01/metaparameters-in-mgmt/) |
| James Shubin | video | [Recording from Incontro DevOps 2017](https://vimeo.com/212241877) |
| Yves Brissaud | blog | [mgmt aux HumanTalks Grenoble (french)](http://log.winsos.net/2017/04/12/mgmt-aux-human-talks-grenoble.html) |
@@ -57,3 +57,7 @@ if we missed something that you think is relevant!
| 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) |
| James Shubin | blog | [Mgmt Configuration Language: Functions](https://purpleidea.com/blog/2024/11/22/functions-in-mgmt/) |
| James Shubin | blog | [Modules and imports in mgmt](https://purpleidea.com/blog/2024/12/03/modules-and-imports-in-mgmt/) |

View File

@@ -69,7 +69,7 @@ be avoided.
### Unsupported resources
Puppet has a fairly large number of
[built-in types](https://docs.puppet.com/puppet/latest/reference/type.html),
[built-in types](https://www.puppet.com/docs/puppet/8/cheatsheet_core_types.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`.
@@ -95,7 +95,8 @@ 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!
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
@@ -114,7 +115,7 @@ 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)
following [resource default](https://www.puppet.com/docs/puppet/8/lang_defaults)
anywhere on the top scope of your manifest:
```puppet

View File

@@ -21,8 +21,6 @@ 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/).
@@ -39,7 +37,7 @@ You'll need some dependencies, including `golang`, and some associated tools.
#### Installing golang
* You need golang version 1.19 or greater installed.
* You need a modern golang version 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)
@@ -103,13 +101,14 @@ 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
git clone --recursive https://github.com/purpleidea/mgmt/
cd mgmt
docker build -t mgmt -f docker/Dockerfile .
docker run --rm --entrypoint cat mgmt mgmt > mgmt
chmod +x mgmt
./mgmt --version
# you could now copy the mgmt binary somewhere into your $PATH
# e.g., /usr/local/bin/ to make it accessible from anywhere
```
## Running mgmt

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

343
docs/release-notes/0.0.25 Normal file
View File

@@ -0,0 +1,343 @@
I've just released version 0.0.25 of mgmt!
> 686 files changed, 28391 insertions(+), 6935 deletions(-)
This is the first release that I consider to be generally useful at
solving real-world problems, without needing to be an mgmt expert. It's
also the first release that includes a very real `mcl` codebase. An
accompanying blog post is also available:
https://purpleidea.com/blog/2024/03/27/a-new-provisioning-tool/
With that, here are a few highlights from the release:
* We have a new mgmt partner program. Please sign-up for early access
to these release notes, along with other special privileges. Details
at: https://bit.ly/mgmt-partner-program
* You can build self-contained mgmt binaries that contain a custom
application. An initial "provisioning tool" has been built in this way.
Please see the blog post for more details.
* Default lookup functions exist in the language, with syntactic sugar
(the || operator) , so you can get a default value if one doesn't
exist, eg: $some_struct->some_struct_field || "some_default_value".
* Resource fields can now accept interface{} (any) types.
* A panic feature now exists in the language.
* The exec resource has new `donecmd` and `creates` fields. Of note,
`creates` supports watches too!
* Send/recv now works for autogrouped resources!
* Added `include as` (for classes) to the language. Nested
(sugar/hierarchical) classes are now supported to make this more
powerful!
* Stats are printed if the function engine is waiting for too long.
* There's a new http:flag resource, and also an http:proxy resource so
that we can have caching http proxies!
* Added a firewalld resource for opening ports!
* Added a dhcp:range resource which is very powerful and has a fun API!
* Added the "embedded" and "entry" packages, for building standalone
tools. This goes perfectly with the new CLI library that we ported
everything to.
And much more...
DOWNLOAD
Prebuilt binaries are available here for this release:
https://github.com/purpleidea/mgmt/releases/tag/0.0.25
They can also be found on the Fedora mirror:
https://dl.fedoraproject.org/pub/alt/purpleidea/mgmt/releases/0.0.25/
NEWS
* We changed the logical operators in mcl to use well-known English
tokens: OR, AND, NOT. (but in lowercase of course)
* The history function has been temporarily removed from the syntactic
core. We'll add it back if we find it's useful to have sugar!
* A bunch of lexer/parser cleanups and improvements were made.
* Default lookup functions for lists, maps, and structs have been
added. These come with syntactic sugar as mentioned above. (We plan to
keep this syntax, but we're open to feedback and changes if they're
good.)
* Resources can accept the interface{} (any) type, although this should
be used sparingly.
* We added a new mcl test suite that checks resource output too!
* Added a new `value` resource. This is a special resource kind that
can be used for building some powerful state machines. Recommended for
experienced users only.
* Improved the golang function generation to allow functions that take
[]str, so now we have a bunch more functions (like join) in our stdlib
for free.
* Add some mac address formatting functions. (core/net)
* Added a panic resource and panic function into the core language.
This is useful for safely shutting down a running mcl program to
prevent something disastrous or unhandled.
* Added a `donecmd` field to the exec resource. This runs a command
after a successful CheckApply. This replaces the `&& echo foo > done`
pattern that you'd see in some code.
* Added a new internal `local` API that can be used for local machine
operations. So far, read/writing/watching values that are stored
locally.
* Added `value` functions which bridge the `value` resource via the
`local` API. To be used sparingly!
* Bumped to golang 1.20, and we'll probably move again before the next
release.
* Allow send/recv with autogrouped resources. This adds many
possibilities, in particular with the server style resources.
* Added a bunch of tests for sneaky corner cases. Some of these were
hard to write, but I think they're worth it.
* ExprBind is now monomorphic! This was a design mistake that we
introduced, but have since repaired. We now have far fewer copies
running in the function graph, and things are much more efficient. This
means lambdas can only have one type when used at two different call
sites, which is much more logical, safer, faster and memory efficient.
* Added an --only-unify option if you want to test your code but not
run it.
* Added a concat function for the common case of interpolation. This
makes type unification significantly faster.
* Eliminated some "benign" races. You might find this commit
interesting to read: bc63b7608e84f60bf9d568188814d411a0688738
* A pgraph bug was found and fixed. A test was added too! It's amazing
this was here for so long, it just shows how subtle graph
datastructures can be.
* Added `include as` (for classes) to the language which lets our
classes produce values which can then be used elsewhere. I decided this
feature would be necessary after writing a bunch of mcl. It does have
an extraneous scoping bug, but not anything that causes problems.
* Nested classes are now supported. This lets you write the equivalent
of nested classes, without actually having to nest them! This is not
inheritance, but rather a way of handling scope and passing it
downwards.
* Improved the Ordering compiler step to catch a bunch of unhandled
bugs. Sam is a genius and was able to figure out some of these using
wizardry.
* Added some convert functions to the mcl package.
* Allow edges with colons...
* ...Because we now support a new hierarchical autogrouping algorithm!
This let's us have some very powerful resources.
* ...Like http:*, dhcp:*, and so on, but we could even go deeper!
* Fixed a super sneaky bug with resource swapping. Due to how we Cmp,
this now preserves state more often, and in particular when we need it.
I'm fairly certain that some code in a WIP branch of mine was actually
blocked because of this issue. Pleased to run into it again, but now
with a fix in place!
* Added an http:flag resource. This let's a `wget` or similar call back
to the http:server to kick off an action.
* The http:flag resource supports directories now.
* Stats are printed if the function engine is waiting for too long.
This is mostly useful for developers who are building new functions and
have a bug in their midst!
* We added a --skip-unify option to prevent the double unification when
running locally. When using `mgmt run` to test locally, we type check,
and then deploy to ourselves, which then naturally type checks again.
This skips the first one, which would be unsafe generally, but is
perfectly safe when we're running a single instance.
* Added a new http:proxy resource, and then tweaked it's API, and then
added http streaming. This is an incredibly powerful resource that lets
us build a caching http proxy with a single resource. I can't wait to
see what else it gets used for. I'm using it for provisioning. It's not
performance optimized at the moment as it uses a single mutex for
everything, but this could be extended if we wanted to scale this out.
* Added a ton of measuring/timing of common operations. This confirmed
my belief that autoedges were slower than necessary. There are two ways
to improve this. We might end up doing either one or both. Autogrouping
is currently much faster than needed, so no improvements planned for
now!
* Started to clean up the internal FS API's. It would be really great
if the core golang team would add something so we could get rid of the
afero external interfaces.
* Added an "embedded" package to offer API's related to embedded mcl
programs! This lets us build standalone binaries which are powered by
mcl.
* Moved to a new CLI (go-arg) library. This has a few downsides, but
they are fixable upstream, and this vastly improved our code quality
and API's. This needed to happen, what with the mess that was
urfave/cli. Look at our diff's, they're really elegant! This let us
clean up our lib structs as well!
* Added an "entry" package to kick-off the embedded API stuff. This
uses the new CLI API's that we just built. The end-user now has a
really easy time building new tools.
* Added a bunch of util functions to aid in building certain standalone
tools. I'm willing to accept more contributions in this space if
they're sane, and related to our general mission. Please ask and then
send patches if you're unsure.
* Added a firewalld resource which makes opening up ports automatic
when we need them. Perfect for the standalone laptop use-case.
* Made type unification cancellable in case you get into a long-running
scenario and want to end early.
* Added a `creates` field to the exec resource. Very useful, and also
supports watches! This is very useful for the common uses of exec.
* Added a dhcp:range resource to offer any number of IP addresses to
devices that we don't know the mac addresses of in advance. This makes
building a provisioning tool even more ergonomic.
* Optimized the name invariants since we can usually avoid an exclusive
invariant in the common case. This roughly halved the type unification
time. More improvements coming too!
* Caught a sneaky list type that could get through type unification
when it was interpolated alone. This now enforces the string invariant
when we specify it, which is an important language design distinction.
We added tests for this of course too!
* The "log" package has been entirely refactored and is only visible in
one place at the top of the program. Nice! I have a design for a
"better logger / user interface" if we ever want to improve on this.
* Added release targets for standalone binary builds. I also improved
the Makefile release magic significantly.
* Made a lot of small "polish" improvements to various resources.
* Most interestingly, an embedded provisioner application has been
built and made available in full. Please test and share with others.
Hopefully this will encourage more interest in the project.
* 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.
* Type unification performance can be improved drastically. I will have
to implement the fast algorithm so that we can scale to very large mcl
programs. Help is wanted if you are familiar with "unionfind" and/or
type unification.
TALKS
I don't have anything planned until CfgMgmtCamp 2025. If you'd like to
book me for a private event, or sponsor my travel for your conference,
please let me know.
I recently gave two talks: one at CfgMgmtCamp 2024, and one at FOSDEM
in the golang room. Both are available online and demonstrated an
earlier version of the provisioning tool which is fully available
today. The talks can be found here: https://purpleidea.com/talks/
PARTNER PROGRAM
We have a new mgmt partner program which gets you early access to
releases, bug fixes, support, and many other goodies. Please sign-up
today: https://bit.ly/mgmt-partner-program
MISC
Our mailing list host (Red Hat) is no longer letting non-Red Hat
employees use their infrastructure. We're looking for a new home. I've
opened a ticket with Freedesktop. If you have any sway with them or
other recommendations, please let me know:
https://gitlab.freedesktop.org/freedesktop/freedesktop/-/issues/1082
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 (preferred) and
ping us 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:
Eng Zer Jun, James Shubin, Oliver Lowe, Samuel Gélineau
We had 4 unique committers since 0.0.24, and have had 90 overall.
run 'git log 0.0.24..0.0.25' to see what has changed since 0.0.24
Happy hacking,
James
@purpleidea

142
docs/release-notes/0.0.26 Normal file
View File

@@ -0,0 +1,142 @@
I've just released version 0.0.26 of mgmt!
> 16 files changed, 869 insertions(+), 181 deletions(-)
Hot off the heels of the recent large release (0.0.25) I've just
released an incremental update...
See more here:
https://purpleidea.com/blog/2024/03/27/a-new-provisioning-tool/
With that, here are a few highlights from the release:
* We have a new mgmt partner program. Please sign-up for early access
to these release notes, along with other special privileges. Details
at: https://bit.ly/mgmt-partner-program
* Type unification for the provisioning tool is about 40x faster.
* We fix a small bug related to the upcoming fedora 40 release.
And much more...
DOWNLOAD
Prebuilt binaries are available here for this release:
https://github.com/purpleidea/mgmt/releases/tag/0.0.26
They can also be found on the Fedora mirror:
https://dl.fedoraproject.org/pub/alt/purpleidea/mgmt/releases/0.0.26/
NEWS
* Added old release notes into git
* We now skip over unreleased Fedora versions (like "40 Beta") when
trying to automatically determine the latest stable release.
* Type unification was structurally refactored to make way for a bunch
of future improvements and generally to modernize the code.
* Added some unification optimizations and a unification flag
optimizations system to allow solvers to support special flags. One of
these new flags was used for the provisioner code with a substantial
improvement in type unification time by about 40x.
* New cli args are also available for using these flags.
* 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.
* General type unification performance can be improved drastically. I
will have to implement the fast algorithm so that we can scale to very
large mcl programs. Help is wanted if you are familiar with "unionfind"
and/or type unification.
TALKS
I don't have anything planned until CfgMgmtCamp 2025. If you'd like to
book me for a private event, or sponsor my travel for your conference,
please let me know.
I recently gave two talks: one at CfgMgmtCamp 2024, and one at FOSDEM
in the golang room. Both are available online and demonstrated an
earlier version of the provisioning tool which is fully available
today. The talks can be found here: https://purpleidea.com/talks/
PARTNER PROGRAM
We have a new mgmt partner program which gets you early access to
releases, bug fixes, support, and many other goodies. Please sign-up
today: https://bit.ly/mgmt-partner-program
MISC
Our mailing list host (Red Hat) is no longer letting non-Red Hat
employees use their infrastructure. We're looking for a new home. I've
opened a ticket with Freedesktop. If you have any sway with them or
other recommendations, please let me know:
https://gitlab.freedesktop.org/freedesktop/freedesktop/-/issues/1082
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 (preferred) and
ping us 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
We had 1 unique committers since 0.0.25, and have had 90 overall.
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

@@ -61,8 +61,8 @@ 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`
used by the `puppet` compiler as well, so make sure you include these struct
tags if you want existing `puppet` code to be able to run using the `mgmt`
engine.
#### Example
@@ -623,7 +623,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.
`puppet` compiler.
```golang
UnmarshalYAML(unmarshal func(interface{}) error) error // optional
@@ -678,10 +678,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
}
}
@@ -716,7 +716,7 @@ Higher level resource collections will be possible once the `mgmt` DSL is ready.
### Why does the resource API have `CheckApply` instead of two separate methods?
In an early version we actually had both "parts" as separate methods, namely:
`StateOK` (Check) and `Apply`, but the [decision](58f41eddd9c06b183f889f15d7c97af81b0331cc)
`StateOK` (Check) and `Apply`, but the [decision](https://github.com/purpleidea/mgmt/commit/58f41eddd9c06b183f889f15d7c97af81b0331cc)
was made to merge the two into a single method. There are two reasons for this:
1. Many situations would involve the engine running both `Check` and `Apply`. If

145
docs/service-guide.md Normal file
View File

@@ -0,0 +1,145 @@
# Service API design guide
This document is intended as a short instructional design guide in building a
service management API. It is certainly intended for someone who wishes to use
`mgmt` resources and functions to interact with their facilities, however it may
be of more general use as well. Hopefully this will help you make smarter design
considerations early on, and prevent some amount of unnecessary technical debt.
## Main aspects
What follows are some of the most common considerations which you may wish to
take into account when building your service. This list is non-exhaustive. Of
particular note, as of the writing of this document, many of these designs are
not taken into account or not well-handled or implemented by the major API
("cloud") providers.
### Authentication
#### The status-quo
Many services naturally require you to authenticate yourself. Usually the
initial user who sets up the account and provides credit card details will need
to download secret credentials in order to access the service. The onus is on
the user to keep those credentials private, and to prevent leaking them. It is
convenient (and insecure) to store them in `git` repositories containing scripts
and configuration management code. Since it's likely you will use multiple
different services, it also means you will have a ton of different credentials
to guard.
#### An alternative
Instead, build your service to accept a public key that you store in the users
account. Only consumers that can correctly sign messages matching this public
key should be authorized. This mechanism is well-understood by anyone who has
ever uploaded their public SSH key to a server. You can use SSH keys, GPG keys,
or even get into Kerberos if that's appropriate. Best of all, if you and other
services use a standardized mechanism like GPG, a user might only need to keep
track of their single key-pair, even when they're using multiple services!
### Events
#### The problem
People have been building "[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete)"
and "[REST](https://en.wikipedia.org/wiki/REST)"ful API's for years. The biggest
missing part that most of them don't provide is events. If users want to know
when a resource changes, they have to repeatedly poll the server, which is both
network intensive, and introduces latency. When services were simpler, this
wasn't as much of a consideration, but these days it matters. An embarrassingly
small number of major software vendors implement these correctly, if at all.
#### Why events?
The `mgmt` tool is different from most other static tools in that it allows
reading streams of incoming data, and stream of change events from resources we
are managing. If an event API is not available, we can still poll, but this is
not as desirable. An event-capable API doesn't prevent polling if that's
preferred, you can always repeat a read request periodically.
#### Variants
The two common mechanisms for receiving events are "callbacks" and
"long-polling". In the former, the service contacts the consumer when something
happens. In the latter, the consumer opens a connection, and the service either
closes the connection or sends the reply, when it's ready. Long-polling is often
preferred since it doesn't require an open firewall on the consumers side.
Callbacks are preferred because it's often cheaper for the service to implement
that. It's also less reliable since it's hard to know if the callback message
wasn't received because it was dropped, or if there just wasn't an event. And it
requires static timeouts when retrying a callback message, and so on. It's best
to implement long-polling or something equivalent at a minimum.
#### "Since" requests
When making an event request, some API's will let you tack on a "since" style
parameter that tells the endpoint that we're interested in all of the events
_since_ a particular timestamp, or _since_ a particular sequence ID. This can be
very useful if missing an intermediate event is a concern. Implement this if you
can, but it's better for all concerned if purely declarative facilities are all
that is required. It also forces the endpoint to maintain some state, which may
be undesirable for them.
#### Out of band
Some providers have the event system tacked on to a separate facility. If it's
not part of the core API, then it's not useful. You shouldn't have to configure
a separate system in order to start getting events.
### Batching
With so many resources, you might expect to have 1000's of long-polling
connections all sitting open and idle. That can't be efficient! It's not, which
is why good API's need a batching facility. This lets the consumer group
together many watches (all waiting on a long-poll) inside of a single call. That
way, a single connection might only be needed for a large amount of information.
### Don't auto-generate junk
Please build an elegant API. Many services auto-generate a "phone book" SDK of
junk. It might seem inevitable, so if you absolutely need to do this, then put
some extra effort into making it idiomatic. If I'm using an SDK generated for
`golang` and I see an internal `foo.String` wrapper, then chances are you have
designed your API and code to be easier to maintain for you, instead of
prioritizing your customers. Surely the total volume of all customer code is
more than your own, so why optimize for that instead of the putting the customer
first?
### Resources and functions
`Mgmt` has a concept of "resources" and "functions". Resources are used in an
idempotent model to express desired state and perform that work, and "functions"
are used to receive and pull data into the system. That separation has shown to
be an elegant one. Consider it when designing your API's. For example, if some
vital information can only be obtained after performing a modifying operation,
then it might signal that you're missing some sort of a lookup or event-log
system. Design your API's to be idempotent, this solves many distributed-system
problems involving receiving duplicate messages, and so on.
## Using mgmt as a library
Instead of building a new service from scratch, and re-inventing the typical
management and CLI layer, consider using `mgmt` as a library, and directly
benefiting from that work. This has not been done for a large production
service, but the author believes it would be quite efficient, particularly if
your application is written in golang. It's equivalently easy to do it for other
languages as well, you just end up with two binaries instead of one. (Or you can
embed the other binary into the new golang management tool.)
## Cloud API considerations
Many "cloud" companies have a lot of technical debt and a lot of customers. As a
result, it might be very hard for them to improve their API's, particularly
without breaking compatibility promises for their existing customers. As a
result, they should either add a versioned API, which lets newer consumers get
the benefit, or add new parallel services which offer the modern features. If
they don't, the only solution is for new competitors to build-in these better
efficiencies, eventually offering better value to cost ratios, which will then
make legacy products less lucrative and therefore unmaintainable as compared to
their competitors.
## Suggestions
If you have any ideas for suggestions or other improvements to this guide,
please let us know! I hope this was helpful. Please reach out if you are
building an API that you might like to have `mgmt` consume!

View File

@@ -67,6 +67,37 @@ 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 pointers
You almost always want any method receivers to be declared on the pointer to the
struct. There are only a few rare situations where this is not the case. This
makes it easier to merge future changes that mutate the state without wondering
why you now have two different copies of a struct. When you do need to copy a
a struct, you can add a `Copy()` method to it. It's true that in many situations
adding the pointer adds a small performance penalty, but we haven't found them
to be significant in practice. If you do have a performance sensitive patch
which benefits from skipping the pointer, please demonstrate this need with
data first.
#### Example
```golang
type Foo struct {
Whatever string
// ...
}
// Bar is implemented correctly as a pointer on Foo.
func (obj *Foo) Bar(baz string) int {
// ...
}
// Bar is implemented *incorrectly* without a pointer to Foo.
func (obj Foo) Bar(baz string) int {
// ...
}
```
### Method receiver naming
[Contrary](https://github.com/golang/go/wiki/CodeReviewComments#receiver-names)
@@ -141,6 +172,24 @@ that variable (often with a `const`) rather than leaving a naked `bool` in the
code. For example, `x := MyFoo("blah", false)` is less clear than
`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

103
docs/util/metadata.go Normal file
View File

@@ -0,0 +1,103 @@
// Mgmt
// Copyright (C) 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 handles metadata for documentation generation.
package util
import (
"fmt"
)
var (
registeredResourceMetadata = make(map[string]*Metadata) // must initialize
registeredFunctionMetadata = make(map[string]*Metadata) // must initialize
)
// RegisterResource records the metadata for a resource of this kind.
func RegisterResource(kind string, metadata *Metadata) error {
if _, exists := registeredResourceMetadata[kind]; exists {
return fmt.Errorf("metadata kind %s is already registered", kind)
}
registeredResourceMetadata[kind] = metadata
return nil
}
// LookupResource looks up the metadata for a resource of this kind.
func LookupResource(kind string) (*Metadata, error) {
metadata, exists := registeredResourceMetadata[kind]
if !exists {
return nil, fmt.Errorf("not found")
}
return metadata, nil
}
// RegisterFunction records the metadata for a function of this name.
func RegisterFunction(name string, metadata *Metadata) error {
if _, exists := registeredFunctionMetadata[name]; exists {
return fmt.Errorf("metadata named %s is already registered", name)
}
registeredFunctionMetadata[name] = metadata
return nil
}
// LookupFunction looks up the metadata for a function of this name.
func LookupFunction(name string) (*Metadata, error) {
metadata, exists := registeredFunctionMetadata[name]
if !exists {
return nil, fmt.Errorf("not found")
}
return metadata, nil
}
// Metadata stores some additional information about the function or resource.
// This is used to automatically generate documentation.
type Metadata struct {
// Filename is the filename (without any base dir path) that this is in.
Filename string
// Typename is the string name of the main resource struct or function.
Typename string
}
// GetMetadata returns some metadata about the func. It can be called at any
// time. This must not be named the same as the struct it's on or using it as an
// anonymous embedded struct will stop us from being able to call this method.
func (obj *Metadata) GetMetadata() *Metadata {
//if obj == nil { // TODO: Do I need this?
// return nil
//}
return &Metadata{
Filename: obj.Filename,
Typename: obj.Typename,
}
}

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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-2023+ James Shubin and the project contributors
// Copyright (C) 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.
//go:build !root

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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
@@ -42,7 +54,8 @@ type GroupableRes interface {
// 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-2023+ James Shubin and the project contributors
// Copyright (C) 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,17 @@ 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")
}
}
@@ -261,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)
}
}
}
}
@@ -282,13 +330,17 @@ 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")
}
}
@@ -320,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
}
@@ -341,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-2023+ James Shubin and the project contributors
// Copyright (C) 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)

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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 represents the implementation of the resource engine that runs
// the graph of resources in real-time. This package has the common imports that

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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-2023+ James Shubin and the project contributors
// Copyright (C) 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-2023+ James Shubin and the project contributors
// Copyright (C) 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-2023+ James Shubin and the project contributors
// Copyright (C) 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
@@ -41,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)
}
@@ -112,21 +128,41 @@ func (obj *Engine) Process(ctx context.Context, 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")
}
}
}
@@ -148,7 +184,7 @@ func (obj *Engine) Process(ctx context.Context, 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!
@@ -156,10 +192,14 @@ func (obj *Engine) Process(ctx context.Context, vertex pgraph.Vertex) error {
} else {
// run the CheckApply!
obj.Logf("%s: CheckApply(%t)", res, !noop)
if obj.Debug {
obj.Logf("%s: CheckApply(%t)", res, !noop)
}
// if this fails, don't UpdateTimestamp()
checkOK, err = res.CheckApply(ctx, !noop)
obj.Logf("%s: CheckApply(%t): Return(%t, %s)", res, !noop, checkOK, engineUtil.CleanError(err))
if !checkOK && obj.Debug { // don't log on (checkOK == true)
obj.Logf("%s: CheckApply(%t): Return(%t, %s)", res, !noop, checkOK, engineUtil.CleanError(err))
}
}
if checkOK && err != nil { // should never return this way
@@ -176,7 +216,9 @@ func (obj *Engine) Process(ctx context.Context, 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 {
@@ -213,7 +255,9 @@ func (obj *Engine) Process(ctx context.Context, 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...
@@ -338,9 +382,17 @@ func (obj *Engine) Worker(vertex pgraph.Vertex) error {
obj.state[vertex].cuid.StopTimer() // clean up nicely
} else {
obj.state[vertex].cuid.StartTimer()
obj.Logf("Watch(%s)", vertex)
if obj.Debug {
obj.Logf("%s: Watch...", vertex)
}
err = res.Watch(obj.state[vertex].doneCtx) // run the watch normally
obj.Logf("Watch(%s): Exited(%s)", vertex, engineUtil.CleanError(err))
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
@@ -430,7 +482,10 @@ Loop:
// we are paused now, and waiting for resume or exit...
select {
case _, closed = <-obj.state[vertex].resumeSignal: // channel closes
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...
@@ -496,7 +551,10 @@ Loop:
break LimitWait
}
select {
case _, closed = <-obj.state[vertex].resumeSignal: // channel closes
case _, ok := <-obj.state[vertex].resumeSignal: // channel closes
if !ok {
closed = true
}
// resumed!
}
}
@@ -552,7 +610,10 @@ Loop:
break RetryWait
}
select {
case _, closed = <-obj.state[vertex].resumeSignal: // channel closes
case _, ok := <-obj.state[vertex].resumeSignal: // channel closes
if !ok {
closed = true
}
// resumed!
}
}

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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-2023+ James Shubin and the project contributors
// Copyright (C) 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
@@ -27,7 +39,7 @@ import (
// AutoEdge adds the automatic edges to the graph.
func AutoEdge(graph *pgraph.Graph, debug bool, logf func(format string, v ...interface{})) error {
logf("adding autoedges...")
logf("building...")
// initially get all of the autoedges to seek out all possible errors
var err error
@@ -51,7 +63,9 @@ func AutoEdge(graph *pgraph.Graph, debug bool, logf func(format string, v ...int
continue
}
if autoEdgeObj == nil {
logf("no auto edges were found for: %s", res)
if debug {
logf("no auto edges were found for: %s", res)
}
continue // next vertex
}
autoEdgeObjMap[res] = autoEdgeObj // save for next loop
@@ -74,9 +88,9 @@ func AutoEdge(graph *pgraph.Graph, debug bool, logf func(format string, v ...int
break // inner loop
}
if debug {
logf("autoedge: UIDS:")
logf("UIDS:")
for i, u := range uids {
logf("autoedge: UID%d: %v", i, u)
logf("UID%d: %v", i, u)
}
}
@@ -117,7 +131,7 @@ func addEdgesByMatchingUIDS(res engine.EdgeableRes, uids []engine.ResUID, graph
continue
}
if debug {
logf("autoedge: Match: %s with UID: %s", r, uid)
logf("match: %s with UID: %s", r, uid)
}
// we must match to an effective UID for the resource,
// that is to say, the name value of a res is a helpful
@@ -126,13 +140,13 @@ func addEdgesByMatchingUIDS(res engine.EdgeableRes, uids []engine.ResUID, graph
if UIDExistsInUIDs(uid, r.UIDs()) {
// add edge from: r -> res
if uid.IsReversed() {
txt := fmt.Sprintf("%s -> %s (autoedge)", r, res)
logf("autoedge: adding: %s", txt)
txt := fmt.Sprintf("%s -> %s", r, res)
logf("adding: %s", txt)
edge := &engine.Edge{Name: txt}
graph.AddEdge(r, res, edge)
} else { // edges go the "normal" way, eg: pkg resource
txt := fmt.Sprintf("%s -> %s (autoedge)", res, r)
logf("autoedge: adding: %s", txt)
txt := fmt.Sprintf("%s -> %s", res, r)
logf("adding: %s", txt)
edge := &engine.Edge{Name: txt}
graph.AddEdge(res, r, edge)
}

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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
@@ -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-2023+ James Shubin and the project contributors
// Copyright (C) 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-2023+ James Shubin and the project contributors
// Copyright (C) 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.
//go:build !root
@@ -190,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
@@ -741,17 +755,30 @@ 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)
}
/*
@@ -792,11 +819,12 @@ func TestPgraphGrouping17(t *testing.T) {
/*
// 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
@@ -815,17 +843,30 @@ 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)
}
/*

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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-2023+ James Shubin and the project contributors
// Copyright (C) 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,12 +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.
package autogroup
import (
"fmt"
"sort"
"strings"
"github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/pgraph"
@@ -136,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-2023+ James Shubin and the project contributors
// Copyright (C) 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 contains the actual implementation of the resource graph engine
// that runs the graph of resources in real-time. This package has the algorithm
@@ -28,6 +40,7 @@ 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"
@@ -45,15 +58,16 @@ 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
@@ -92,7 +106,8 @@ func (obj *Engine) Init() error {
if obj.Prefix == "" || obj.Prefix == "/" {
return fmt.Errorf("the prefix of `%s` is invalid", obj.Prefix)
}
if err := os.MkdirAll(obj.Prefix, 0770); err != nil {
// 0775 since we want children to be able to read this!
if err := os.MkdirAll(obj.Prefix, 0775); err != nil {
return errwrap.Wrapf(err, "can't create prefix")
}
@@ -144,7 +159,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
@@ -210,7 +225,7 @@ func (obj *Engine) Commit() error {
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 {
//if err := os.MkdirAll(statePrefix, 0775); err != nil {
// return errwrap.Wrapf(err, "can't create state prefix")
//}
@@ -223,9 +238,10 @@ func (obj *Engine) Commit() error {
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{}) {
@@ -253,10 +269,18 @@ func (obj *Engine) Commit() error {
obj.wlock.Unlock()
}()
obj.Logf("Worker(%s)", v)
if obj.Debug {
obj.Logf("%s: Working...", v)
}
// contains the Watch and CheckApply loops
err := obj.Worker(v)
obj.Logf("Worker(%s): Exited(%s)", v, engineUtil.CleanError(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

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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.
//go:build !root

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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-2023+ James Shubin and the project contributors
// Copyright (C) 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,24 @@
// 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"
"io/ioutil"
"os"
"path"
"sort"
@@ -161,7 +172,7 @@ 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 := ioutil.ReadDir(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 {
@@ -171,7 +182,7 @@ func (obj *Engine) ReversalList() (map[string]string, error) {
for _, x := range files {
key := x.Name() // some uid for the resource
file := path.Join(dir, key, ReverseFile)
content, err := ioutil.ReadFile(file)
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 {
@@ -246,7 +257,7 @@ func (obj *State) ReversalCleanup() error {
return nil // nothing to erase, we're not a reversal resource
}
if !obj.isStateOK { // did we successfully reverse?
if !obj.isStateOK.Load() { // did we successfully reverse? (mutex RLock/RUnlock)
obj.Logf("did not complete reversal") // warn
return nil
}
@@ -263,7 +274,7 @@ func (obj *State) ReversalWrite(str string, overwrite bool) error {
}
file := path.Join(dir, ReverseFile) // return a unique file
content, err := ioutil.ReadFile(file)
content, err := os.ReadFile(file)
if err != nil && !os.IsNotExist(err) {
return errwrap.Wrapf(err, "could not read reverse file: %s", file)
}
@@ -275,12 +286,12 @@ func (obj *State) ReversalWrite(str string, overwrite bool) error {
if str != oldStr {
obj.Logf("existing, pending, reversible resource exists")
//obj.Logf("diff:")
//obj.Logf("") // TODO: print the diff w/o and secret values
//obj.Logf("") // TODO: print the diff w/o secret values
return fmt.Errorf("existing, pending, reversible resource exists")
}
}
return ioutil.WriteFile(file, []byte(str), ReversePerm)
return os.WriteFile(file, []byte(str), ReversePerm)
}
// ReversalDelete removes the reversal state information for this resource.

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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-2023+ James Shubin and the project contributors
// Copyright (C) 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.
//go:build !root

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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-2023+ James Shubin and the project contributors
// Copyright (C) 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
@@ -21,10 +33,12 @@ 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/errwrap"
@@ -43,23 +57,27 @@ type State 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 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?
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.
@@ -140,6 +158,9 @@ func (obj *State) Init() error {
return fmt.Errorf("the Logf function is missing")
}
obj.isStateOK = &atomic.Bool{}
obj.mutex = &sync.RWMutex{}
obj.doneCtx, obj.doneCtxCancel = context.WithCancel(context.Background())
obj.processDone = make(chan struct{})
@@ -238,12 +259,13 @@ func (obj *State) Init() error {
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...)
},
}
@@ -383,7 +405,9 @@ 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.

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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
@@ -42,7 +54,8 @@ func (obj *State) varDir(extra string) (string, error) {
// an empty string at the end has no effect
p := fmt.Sprintf("%s/", path.Join(obj.Prefix, extra))
if err := os.MkdirAll(p, 0770); err != nil {
// 0775 since we want children to be able to read this!
if err := os.MkdirAll(p, 0775); err != nil {
return "", errwrap.Wrapf(err, "can't create prefix in: %s", p)
}

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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

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

@@ -0,0 +1,594 @@
// Mgmt
// Copyright (C) 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"
"strconv"
"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 // TODO: Rename to ValueImpl?
// VarDirImpl is the implementation for the VarDir API's. The API's are
// the collection of public methods that exist on this struct.
*VarDirImpl
// PoolImpl is the implementation for the Pool API's. The API's are the
// collection of public methods that exist on this struct.
*PoolImpl
}
// 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,
})
obj.VarDirImpl = &VarDirImpl{}
obj.VarDirImpl.Init(&VarDirInit{
Prefix: obj.Prefix,
Debug: obj.Debug,
Logf: obj.Logf,
})
obj.PoolImpl = &PoolImpl{}
obj.PoolImpl.Init(&PoolInit{
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
}
// VarDirInit are the init values that the VarDir API needs to work correctly.
type VarDirInit struct {
Prefix string
Debug bool
Logf func(format string, v ...interface{})
}
// VarDirImpl is the implementation for the VarDir API's. The API's are the
// collection of public methods that exist on this struct.
type VarDirImpl struct {
init *VarDirInit
mutex *sync.Mutex
prefix string
prefixExists bool // is it okay to use the prefix?
}
// Init runs some initialization code for the VarDir API.
func (obj *VarDirImpl) Init(init *VarDirInit) {
obj.init = init
obj.mutex = &sync.Mutex{}
obj.prefix = fmt.Sprintf("%s/", path.Join(obj.init.Prefix, "vardir"))
}
// VarDir returns a directory rooted at the internal prefix.
func (obj *VarDirImpl) VarDir(ctx context.Context, reldir string) (string, error) {
if strings.HasPrefix(reldir, "/") {
return "", fmt.Errorf("path must be relative")
}
if !strings.HasSuffix(reldir, "/") {
return "", fmt.Errorf("path must be a dir")
}
// NOTE: The above checks ensure we don't get either "" or "/" as input!
prefix, err := obj.getPrefix()
if err != nil {
return "", err
}
result := fmt.Sprintf("%s/", path.Join(prefix, reldir))
// TODO: Should we mkdir this?
obj.mutex.Lock()
defer obj.mutex.Unlock()
if err := os.MkdirAll(result, 0755); err != nil {
return "", err
}
return result, 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 *VarDirImpl) 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
}
// PoolInit are the init values that the Pool API needs to work correctly.
type PoolInit struct {
Prefix string
Debug bool
Logf func(format string, v ...interface{})
}
// PoolConfig configures how the Pool operates.
// XXX: These are not implemented yet.
type PoolConfig struct {
// Expiry specifies that we expire old values that have not been read
// for this many seconds. Zero disables this and they never expire.
Expiry int64 // TODO: or time.Time ?
// Random lets you allocate a random integer instead of sequential ones.
Random bool
// Max specifies the maximum integer to allocate.
Max int
}
// PoolImpl is the implementation for the Pool API's. The API's are the
// collection of public methods that exist on this struct.
type PoolImpl struct {
init *PoolInit
mutex *sync.Mutex
prefix string
prefixExists bool // is it okay to use the prefix?
}
// Init runs some initialization code for the Pool API.
func (obj *PoolImpl) Init(init *PoolInit) {
obj.init = init
obj.mutex = &sync.Mutex{}
obj.prefix = fmt.Sprintf("%s/", path.Join(obj.init.Prefix, "pool"))
}
// Pool returns a unique integer from a pool of numbers. Within a given
// namespace, it returns the same integer for a given name. It is a simple
// mechanism to allocate numbers to different inputs when we don't have a
// hashing alternative. It does not allocate zero.
func (obj *PoolImpl) Pool(ctx context.Context, namespace, uid string, config *PoolConfig) (int, error) {
if namespace == "" {
return 0, fmt.Errorf("namespace is empty")
}
if strings.Contains(namespace, "/") {
return 0, fmt.Errorf("namespace contains slash")
}
if uid == "" {
return 0, fmt.Errorf("uid is empty")
}
if strings.Contains(uid, "/") {
return 0, fmt.Errorf("uid contains slash")
}
prefix, err := obj.getPrefix()
if err != nil {
return 0, err
}
dir := fmt.Sprintf("%s/", path.Join(prefix, namespace))
file := fmt.Sprintf("%s.uid", path.Join(dir, uid)) // file
// TODO: Run clean up funcs here to get rid of any stale/expired values.
// TODO: This will happen based on the future config options we build...
obj.mutex.Lock()
defer obj.mutex.Unlock()
if err := os.MkdirAll(dir, 0755); err != nil {
return 0, err
}
fn := func(p string) (int, error) {
b, err := os.ReadFile(p)
if err != nil && !os.IsNotExist(err) {
return 0, err // real error
}
if err != nil {
return 0, nil // absent!
}
// File exists!
d, err := strconv.Atoi(strings.TrimSpace(string(b)))
if err != nil {
// Someone put corrupt data in a uid file.
return 0, err // real error
}
return d, nil // value already allocated!
}
d, err := fn(file)
if err != nil {
return 0, err // real error
}
if d != 0 {
return d, nil // Value already allocated! We're done early.
}
// Not found, so find the max. (0 without error means not found!)
files, err := os.ReadDir(dir) // ([]os.DirEntry, error)
if err != nil {
return 0, err // real error
}
m := 0
for _, f := range files {
if f.IsDir() {
continue // unexpected!
}
d, err := fn(path.Join(dir, f.Name()))
if err != nil {
return 0, err // real error
}
if d == 0 {
// Must be someone deleting files without our mutex!
return 0, fmt.Errorf("unexpected missing file")
}
m = max(m, d)
}
m++ // increment
data := []byte(fmt.Sprintf("%d\n", m)) // it's polite to end with \n
if err := os.WriteFile(file, data, 0600); err != nil {
return 0, err
}
return m, 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 *PoolImpl) 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
}

View File

@@ -0,0 +1,62 @@
// Mgmt
// Copyright (C) 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-2023+ James Shubin and the project contributors
// Copyright (C) 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
@@ -40,6 +52,7 @@ var DefaultMetaParams = &MetaParams{
//Sema: []string{},
Rewatch: false,
Realize: false, // true would be more awesome, but unexpected for users
Dollar: false,
}
// MetaRes is the interface a resource must implement to support meta params.
@@ -120,6 +133,13 @@ type MetaParams struct {
// the resource is blocked because of a failed pre-requisite resource.
// XXX: Not implemented!
Realize bool `yaml:"realize"`
// Dollar allows you to name a resource to start with the dollar
// character. We don't allow this by default since it's probably not
// needed, and is more likely to be a typo where the user forgot to
// interpolate a variable name. In the rare case when it's needed, you
// can disable that check with this meta param.
Dollar bool `yaml:"dollar"`
}
// Cmp compares two AutoGroupMeta structs and determines if they're equivalent.
@@ -166,6 +186,9 @@ func (obj *MetaParams) Cmp(meta *MetaParams) error {
if obj.Realize != meta.Realize {
return fmt.Errorf("values for Realize are different")
}
if obj.Dollar != meta.Dollar {
return fmt.Errorf("values for Dollar are different")
}
return nil
}
@@ -206,6 +229,7 @@ func (obj *MetaParams) Copy() *MetaParams {
Sema: sema,
Rewatch: obj.Rewatch,
Realize: obj.Realize,
Dollar: obj.Dollar,
}
}

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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.
//go:build !root

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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-2023+ James Shubin and the project contributors
// Copyright (C) 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,13 +33,25 @@ import (
"context"
"encoding/gob"
"fmt"
"path/filepath"
"reflect"
"runtime"
"strings"
docsUtil "github.com/purpleidea/mgmt/docs/util"
"github.com/purpleidea/mgmt/engine/local"
"github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util/errwrap"
"gopkg.in/yaml.v2"
)
const (
// ResourcesRelDir is the path where the resources are kept, relative to
// the main source code root.
ResourcesRelDir = "engine/resources/"
)
// TODO: should each resource be a sub-package?
var registeredResources = map[string]func() Res{}
@@ -43,6 +67,23 @@ func RegisterResource(kind string, fn func() Res) {
}
gob.Register(f)
registeredResources[kind] = fn
// Additional metadata for documentation generation!
_, filename, _, ok := runtime.Caller(1)
if !ok {
panic(fmt.Sprintf("could not locate resource filename for %s", kind))
}
sp := strings.Split(reflect.TypeOf(f).String(), ".")
if len(sp) != 2 {
panic(fmt.Sprintf("could not parse resource struct name for %s", kind))
}
if err := docsUtil.RegisterResource(kind, &docsUtil.Metadata{
Filename: filepath.Base(filename),
Typename: sp[1],
}); err != nil {
panic(fmt.Sprintf("could not register resource metadata for %s", kind))
}
}
// RegisteredResourcesNames returns the kind of the registered resources.
@@ -135,6 +176,11 @@ type Init struct {
// 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
@@ -254,6 +300,12 @@ func Validate(res Res) error {
return errwrap.Wrapf(err, "the Res has an invalid meta param")
}
// TODO: pull dollar prefix from a constant
// This catches typos where the user meant to use ${var} interpolation.
if !res.MetaParams().Dollar && strings.HasPrefix(res.Name(), "$") {
return fmt.Errorf("the Res name starts with a $")
}
return res.Validate()
}

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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.
//go:build !noaugeas
@@ -27,8 +39,8 @@ import (
"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"
"honnef.co/go/augeas"
)
@@ -194,7 +206,6 @@ func (obj *AugeasRes) checkApplySet(ctx context.Context, apply bool, ag *augeas.
// CheckApply method for Augeas resource.
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
if obj.Lens != "" {

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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"
@@ -626,7 +638,7 @@ func (obj *AwsEc2Res) snsWatch(ctx context.Context) error {
// CheckApply method for AwsEc2 resource.
func (obj *AwsEc2Res) CheckApply(ctx context.Context, apply bool) (bool, error) {
obj.init.Logf("CheckApply(%t)", apply)
obj.init.Logf("CheckApply(%t)", apply) // XXX: replace with logf on change
// find the instance we need to check
instance, err := describeInstanceByName(obj.client, obj.prependName())
@@ -980,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")
}

View File

@@ -0,0 +1,466 @@
// Mgmt
// Copyright (C) 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"
"net/url"
"strconv"
"strings"
"github.com/purpleidea/mgmt/engine"
"github.com/purpleidea/mgmt/engine/traits"
bmclib "github.com/bmc-toolbox/bmclib/v2"
"github.com/bmc-toolbox/bmclib/v2/providers/rpc"
)
func init() {
engine.RegisterResource("bmc:power", func() engine.Res { return &BmcPowerRes{} })
}
const (
// DefaultBmcPowerPort is the default port we try to connect on.
DefaultBmcPowerPort = 443
// BmcDriverSecureSuffix is the magic char we append to a driver name to
// specify we want the SSL/TLS variant.
BmcDriverSecureSuffix = "s"
// BmcDriverRPC is the RPC driver.
BmcDriverRPC = "rpc"
// BmcDriverGofish is the gofish driver.
BmcDriverGofish = "gofish"
)
// BmcPowerRes is a resource that manages power state of a BMC. This is usually
// used for turning computers on and off. The name value can be a big URL string
// in the form: `driver://user:pass@hostname:port` for example you may see:
// gofishs://ADMIN:hunter2@127.0.0.1:8800 to use the "https" variant of the
// gofish driver.
//
// NOTE: New drivers should either not end in "s" or at least not be identical
// to the name of another driver an "s" is added or removed to the end.
type BmcPowerRes struct {
traits.Base // add the base methods without re-implementation
init *engine.Init
// Hostname to connect to. If not specified, we parse this from the
// Name.
Hostname string `lang:"hostname" yaml:"hostname"`
// Port to connect to. If not specified, we parse this from the Name.
Port int `lang:"port" yaml:"port"`
// Username to use to connect. If not specified, we parse this from the
// Name.
// TODO: If the Username field is not set, should we parse from the
// Name? It's not really part of the BMC unique identifier so maybe we
// shouldn't use that.
Username string `lang:"username" yaml:"username"`
// Password to use to connect. We do NOT parse this from the Name unless
// you set InsecurePassword to true.
// XXX: Use mgmt magic credentials in the future.
Password string `lang:"password" yaml:"password"`
// InsecurePassword can be set to true to allow a password in the Name.
InsecurePassword bool `lang:"insecure_password" yaml:"insecure_password"`
// Driver to use, such as: "gofish" or "rpc". This is a different
// concept than the "bmclib" driver vs provider distinction. Here we
// just statically pick what we're using without any magic. If not
// specified, we parse this from the Name scheme. If this ends with an
// extra "s" then we use https instead of http.
Driver string `lang:"driver" yaml:"driver"`
// State of machine power. Can be "on" or "off".
State string `lang:"state" yaml:"state"`
driver string
scheme string
}
// validDriver determines if we are using a valid drive. This does not include
// the magic "s" bits. This function need to be expanded as we support more
// drivers.
func (obj *BmcPowerRes) validDriver(driver string) error {
if driver == BmcDriverRPC {
return nil
}
if driver == BmcDriverGofish {
return nil
}
return fmt.Errorf("unknown driver: %s", driver)
}
// getHostname returns the hostname that we want to connect to. If the Hostname
// field is set, we use that, otherwise we parse from the Name.
func (obj *BmcPowerRes) getHostname() string {
if obj.Hostname != "" {
return obj.Hostname
}
u, err := url.Parse(obj.Name())
if err != nil || u == nil {
return ""
}
// SplitHostPort splits a network address of the form "host:port",
// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or
// host%zone and port.
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
return u.Host // must be a naked hostname or ip w/o port
}
_ = port
return host
}
// getPort returns the port that we want to connect to. If the Port field is
// set, we use that, otherwise we parse from the Name.
//
// NOTE: We return a string since all the bmclib things usually expect a string,
// but if that gets fixed we should return an int here instead.
func (obj *BmcPowerRes) getPort() string {
if obj.Port != 0 {
return strconv.Itoa(obj.Port)
}
u, err := url.Parse(obj.Name())
if err != nil || u == nil {
return ""
}
// SplitHostPort splits a network address of the form "host:port",
// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or
// host%zone and port.
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
return strconv.Itoa(DefaultBmcPowerPort) // default port
}
_ = host
return port
}
// getUsername returns the username that we want to connect with. If the
// Username field is set, we use that, otherwise we parse from the Name.
// TODO: If the Username field is not set, should we parse from the Name? It's
// not really part of the BMC unique identifier so maybe we shouldn't use that.
func (obj *BmcPowerRes) getUsername() string {
if obj.Username != "" {
return obj.Username
}
u, err := url.Parse(obj.Name())
if err != nil || u == nil || u.User == nil {
return ""
}
return u.User.Username()
}
// getPassword returns the password that we want to connect with.
// XXX: Use mgmt magic credentials in the future.
func (obj *BmcPowerRes) getPassword() string {
if obj.Password != "" || !obj.InsecurePassword {
return obj.Password
}
// NOTE: We don't look at any password string from the name unless the
// InsecurePassword field is true.
u, err := url.Parse(obj.Name())
if err != nil || u == nil || u.User == nil {
return ""
}
password, ok := u.User.Password()
if !ok {
return ""
}
return password
}
// getRawDriver returns the raw magic driver string. If the Driver field is set,
// we use that, otherwise we parse from the Name. This version may include the
// magic "s" at the end.
func (obj *BmcPowerRes) getRawDriver() string {
if obj.Driver != "" {
return obj.Driver
}
u, err := url.Parse(obj.Name())
if err != nil || u == nil {
return ""
}
return u.Scheme
}
// getDriverAndScheme figures out which driver and scheme we want to use.
func (obj *BmcPowerRes) getDriverAndScheme() (string, string, error) {
driver := obj.getRawDriver()
err := obj.validDriver(driver)
if err == nil {
return driver, "http", nil
}
driver = strings.TrimSuffix(driver, BmcDriverSecureSuffix)
if err := obj.validDriver(driver); err == nil {
return driver, "https", nil
}
return "", "", err // return the first error
}
// getDriver returns the actual driver that we want to connect with. If the
// Driver field is set, we use that, otherwise we parse from the Name. This
// version does NOT include the magic "s" at the end.
func (obj *BmcPowerRes) getDriver() string {
return obj.driver
}
// getScheme figures out which scheme we want to use.
func (obj *BmcPowerRes) getScheme() string {
return obj.scheme
}
// Default returns some sensible defaults for this resource.
func (obj *BmcPowerRes) Default() engine.Res {
return &BmcPowerRes{}
}
// Validate if the params passed in are valid data.
func (obj *BmcPowerRes) Validate() error {
// XXX: Force polling until we have real events...
if obj.MetaParams().Poll == 0 {
return fmt.Errorf("events are not yet supported, use polling")
}
if obj.getHostname() == "" {
return fmt.Errorf("need a Hostname")
}
//if obj.getUsername() == "" {
// return fmt.Errorf("need a Username")
//}
if obj.getRawDriver() == "" {
return fmt.Errorf("need a Driver")
}
if _, _, err := obj.getDriverAndScheme(); err != nil {
return err
}
return nil
}
// Init runs some startup code for this resource.
func (obj *BmcPowerRes) Init(init *engine.Init) error {
obj.init = init // save for later
driver, scheme, err := obj.getDriverAndScheme()
if err != nil {
// programming error (we checked in Validate)
return err
}
obj.driver = driver
obj.scheme = scheme
return nil
}
// Cleanup is run by the engine to clean up after the resource is done.
func (obj *BmcPowerRes) Cleanup() error {
return nil
}
// client builds the bmclib client. The API to build it is complicated.
func (obj *BmcPowerRes) client() *bmclib.Client {
// NOTE: The bmclib API is weird, you can't put the port in this string!
u := fmt.Sprintf("%s://%s", obj.getScheme(), obj.getHostname())
uPort := u
if p := obj.getPort(); p != "" {
uPort = u + ":" + p
}
opts := []bmclib.Option{}
if obj.getDriver() == BmcDriverRPC {
opts = append(opts, bmclib.WithRPCOpt(rpc.Provider{
// NOTE: The main API cannot take a port, but here we do!
ConsumerURL: uPort,
}))
}
if p := obj.getPort(); p != "" {
switch obj.getDriver() {
case BmcDriverRPC:
// TODO: ???
case BmcDriverGofish:
// XXX: Why doesn't this accept an int?
opts = append(opts, bmclib.WithRedfishPort(p))
//case BmcDriverOpenbmc:
// // XXX: Why doesn't this accept an int?
// opts = append(opts, openbmc.WithPort(p))
default:
// TODO: error or pass through?
obj.init.Logf("unhandled driver: %s", obj.getDriver())
}
}
client := bmclib.NewClient(u, obj.getUsername(), obj.Password, opts...)
if obj.getDriver() != "" && obj.getDriver() != BmcDriverRPC {
client = client.For(obj.getDriver()) // limit to this provider
}
return client
}
// Watch is the primary listener for this resource and it outputs events.
func (obj *BmcPowerRes) Watch(ctx context.Context) error {
obj.init.Running() // when started, notify engine that we're running
select {
case <-ctx.Done(): // closed by the engine to signal shutdown
}
//obj.init.Event() // notify engine of an event (this can block)
return nil
}
// CheckApply method for BmcPower resource. Does nothing, returns happy!
func (obj *BmcPowerRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
client := obj.client()
if err := client.Open(ctx); err != nil {
return false, err
}
defer client.Close(ctx) // (err error)
if obj.init.Debug {
obj.init.Logf("connected ok")
}
state, err := client.GetPowerState(ctx)
if err != nil {
return false, err
}
state = strings.ToLower(state) // normalize
obj.init.Logf("get state: %s", state)
if !apply {
return false, nil
}
if obj.State == state {
return true, nil
}
// TODO: should this be "On" and "Off"? Does case matter?
ok, err := client.SetPowerState(ctx, obj.State)
if err != nil {
return false, err
}
if !ok {
// TODO: When is this ever false?
}
obj.init.Logf("set state: %s", obj.State)
return false, nil
}
// Cmp compares two resources and returns an error if they are not equivalent.
func (obj *BmcPowerRes) Cmp(r engine.Res) error {
// we can only compare BmcPowerRes to others of the same resource kind
res, ok := r.(*BmcPowerRes)
if !ok {
return fmt.Errorf("not a %s", obj.Kind())
}
if obj.Hostname != res.Hostname {
return fmt.Errorf("the Hostname differs")
}
if obj.Port != res.Port {
return fmt.Errorf("the Port differs")
}
if obj.Username != res.Username {
return fmt.Errorf("the Username differs")
}
if obj.Password != res.Password {
return fmt.Errorf("the Password differs")
}
if obj.InsecurePassword != res.InsecurePassword {
return fmt.Errorf("the InsecurePassword differs")
}
if obj.Driver != res.Driver {
return fmt.Errorf("the Driver differs")
}
if obj.State != res.State {
return fmt.Errorf("the State differs")
}
return nil
}
// UnmarshalYAML is the custom unmarshal handler for this struct. It is
// primarily useful for setting the defaults.
func (obj *BmcPowerRes) UnmarshalYAML(unmarshal func(interface{}) error) error {
type rawRes BmcPowerRes // indirection to avoid infinite recursion
def := obj.Default() // get the default
res, ok := def.(*BmcPowerRes) // put in the right format
if !ok {
return fmt.Errorf("could not convert to BmcPowerRes")
}
raw := rawRes(*res) // convert; the defaults go here
if err := unmarshal(&raw); err != nil {
return err
}
*obj = BmcPowerRes(raw) // restore from indirection with type conversion!
return nil
}

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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

View File

@@ -1,5 +1,5 @@
// Mgmt
// Copyright (C) 2013-2023+ James Shubin and the project contributors
// Copyright (C) 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,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.
//go:build !noconsul
package resources

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