Question: For my work, we are trying to spin up a docker swarm cluster with Puppet. We use puppetlabs-docker for this, which has a module `docker::swarm`. This module allows you to instantiate a docker swarm manager on your master node. This works so far.

On the docker workers you can join to docker swarm manager with exported resources:

```
node 'manager' {
  @@docker::swarm {'cluster_worker':
    join           => true,
    advertise_addr => '192.168.1.2',
    listen_addr    => '192.168.1.2',
    manager_ip     => '192.168.1.1',
    token          => 'your_join_token'
    tag            => 'docker-join'
  }
}

```

However, the `your_join_token` needs to be retrieved from the docker swarm manager with `docker swarm join-token worker -q`. This is possible with `Exec`.

My question is: is there a way (without breaking Puppet philosophy on idempotent and convergence) to get the output from the join-token `Exec` and pass this along to the exported resource, so that my workers can join master?

Answer: 
> 
> My question is: is there a way (without breaking Puppet philosophy on
> idempotent and convergence) to get the output from the join-token Exec
> and pass this along to the exported resource, so that my workers can
> join master?
> 
> 
> 

No, because the properties of resource declarations, exported or otherwise, are determined when the target node's catalog is built (on the master), whereas the command of an `Exec` resource is run only later, when the fully-built catalog is applied to the target node.

I'm uncertain about the detailed requirements for token generation, but possibly you could use Puppet's [generate()](https://puppet.com/docs/puppet/6.17/function.html#generate) function to obtain one at need, during catalog building on the master.

**Update**

Another alternative would be an external (or custom) fact. This is the conventional mechanism for gathering information from a node to be used during catalog building for that node, and as such, it might be more suited to your particular needs. There are some potential issues with this, but I'm unsure how many actually apply:

* The fact has to know for which nodes to generate join tokens. This might be easier / more robust or trickier / more brittle depending on factors including

	+ whether join tokens are node-specific (harder if they are)
	+ whether it is important to avoid generating multiple join tokens for the same node (over multiple Puppet runs; harder if this is important)
	+ notwithstanding the preceding, whether there is ever a need to generate a new join token for a node for which one was already generated (harder if this is a requirement)
* If implemented as a dynamically-generated external fact -- which seems a reasonable option -- then when a new node is added to the list, the fact implementation will be updated on the manager's next puppet run, but the data will not be available until the following one. This is not necessarily a big deal, however, as it is a one-time delay with respect to each new node, and you can always manually perform a catalog run on the manager node to move things along more quickly.
* It has more moving parts, with more complex relationships among them, hence there is a larger cross-section for bugs and unexpected behavior.
Thanks to @John Bollinger I seem to have fixed my issue. In the end, it was a bit more worked than I envisioned, but this is the idea:

1. My puppet setup now uses PuppetDB for storing facts and sharing exported resources.
2. I have added an additional custom fact to the code base of Docker (in `./lib/facter/docker.rb`).
3. The bare minimum in the `site.pp` file, now contains:

```
node 'manager' {
  docker::swarm {'cluster_manager':
    init           => true,
    advertise_addr => "${::ipaddress}",
    listen_addr    => "${::ipaddress}",
    require        => Class['docker'],
  }

  @@docker::swarm {'cluster_worker':
    join           => true,
    manager_ip     => "${::ipaddress}",
    token          => "${worker_join_token}",
    tag            => "cluster_join_command",
    require        => Class['docker'],
  }
}

node 'worker' {
  Docker::Swarm<<| tag == 'cluster_join_command' |>> {
    advertise_addr => "${::ipaddress}",
    listen_addr    => "${::ipaddress}",
  }
}

```

Do keep in mind that for this to work, `puppet agent -t` has to be run twice on the `manager` node, and once (after this) on the `worker` node. The first run on the manager will start the `cluster_manager`, while the second one will fetch the `worker_join_token` and upload it to PuppetDB. After this fact is set, the manifest for the worker can be properly compiled and run.

In the case of a different module, you have to add a custom fact yourself. When I was researching how to do this, I added the custom fact to the `LOAD_PATH` of ruby, but was unable to find it in my PuppetDB. After some browsing I found that facts from a module are uploaded to PuppetDB, which is the reason that I tweaked the upstream Docker module.