libral icon indicating copy to clipboard operation
libral copied to clipboard

Example of a provider exposing resources from multiple places

Open garethr opened this issue 8 years ago • 3 comments

I'm not expecting this to be merged, just to provide code others can look at and play around with to get a feel for how something like this might work. The code itself (apart from how little of it there is) isn't the point I'm trying to make.

@gertd @kbarber FYI, I'll send a separate email with other thoughts too.

Note the Dockerfile in this PR used for the demo builds on an image built from the one in this PR https://github.com/puppetlabs/libral/pull/35

What

This PR demonstrates combining three things that we've touched on in conversation:

  • The conversation about remote resources, ie. that don't require additional software on the target machine
  • The use of Libral for discovery
  • @mullr's proposal for modelling scope

How this works and why it might be interesting is probably best seen in an example:

Running the example

First lets launch a few nodes from which we're interested in gathering information about. For this I'm using Docker, specifically Docker in Docker. This makes the whole thing self-contained and simple to launch. This isn't a requirement, just the simplest approach I came up with to make it easy to demo. I've written this using docker exec rather than SSH for exactly the same reason - simplicity of proving the concept. The concept itself is not container specific in any way.

docker run --privileged --name some-docker -d docker:dind
docker run -it --rm --link some-docker:docker docker run -d debian /bin/sh -c "while true; do echo hello world; sleep 1; done"
docker run -it --rm --link some-docker:docker docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"

The above should give you a container running a docker daemon called some-docker which you can see with docker ps, that in turn should also be running two other containers.

Now you'll need to build the libral image. Grab the Dockerfile from #35 and the Dockerfile.withdocker from this PR and run:

docker build -t puppet/libral .
docker build -f Dockerfile.withdocker -t puppet/libral-with-docker .

The second of these is just so we can connect to the nested containers we built above for the sake of keeping this demo self-contained. All we're doing is adding a docker client to the image. The libral provider shells out to this, again purely for the purposes of the fastest route to proving the concept.

And then lets run ralsh with the provider from this PR. I've suppressed warnings and just grabbed the top 12 lines:

$ docker run -it --link some-docker:docker --rm -v `pwd`/examples:/usr/share/libral/data puppet/libral-with-docker -lerror --include /usr/share/libral/data/ package::dpkg | head -n 12
package::dpkg { 'adduser':
  ensure => '3.113+nmu3ubuntu4',
  scope  => 'd2000c7fb334 (local)',
}
package::dpkg { 'apparmor':
  ensure => '2.10.95-0ubuntu2.6',
  scope  => 'd2000c7fb334 (local)',
}
package::dpkg { 'apt':
  ensure => '1.2.20',
  scope  => 'd2000c7fb334 (local)',
}

The only addition here is the scope attribute. I've suffixed this with local for clarify but this will be the hostname of the container in which this command ran.

Now lets run it again and grab the tail:

$ docker run -it --link some-docker:docker --rm -v `pwd`/examples:/usr/share/libral/data puppet/libral-with-docker -lerror --include /usr/share/libral/data/ package::dpkg | tail -n 12
package::dpkg { 'ubuntu-keyring':
  ensure => '2012.05.19',
  scope  => '2caee8781b88',
}
package::dpkg { 'util-linux':
  ensure => '2.27.1-6ubuntu3.2',
  scope  => '2caee8781b88',
}
package::dpkg { 'zlib1g':
  ensure => '1:1.2.8.dfsg-2ubuntu4.1',
  scope  => '2caee8781b88',
}

The scope value should be different. This should be the hostname/container id of one of the other containers running in docker-in-docker. The entire output if you scroll through should contain three different scope. If that sounds like a lot of scrolling, and you have jq locally then you can use librals JSON output to do something like:

$ docker run -it --link some-docker:docker --rm -v `pwd`/examples:/usr/share/libral/data puppet/libral-with-docker -lerror --include /usr/share/libral/data/ package::dpkg --json | jq ".resources[]|.scope" | uniq
"cd0bfdcd5eb8 (local)"
"5198a7bf6b4d"
"2caee8781b88"

What does this demonstrate?

Probably a few other things too but at least:

  • That writing libral providers is pretty trivial
  • That with the addition of the scope attribute, nothing about libral has strong assumptions about a node or host that I can see

Thinking a bit further forward

How this is implemented means that the provider is actually dealing with two things:

  • The dpkg packages (which you'd expect from a dpkg provider)
  • The nodes on which to discover dpkg packages

(The later is just docker containers in this demonstrate, but could just as easily have been a combination of nmap and ssh for instance, or AWS API calls and SSH, etc.)

Pushing the second of these down to all providers would work, but would likely end up with lots of duplication between first and third party providers. However, we've talked before about the order of elements running in the PIPELINE being important. ie. You might first run something which discovers nodes, and EC2 instances, and then on those run commands which discover dpkg packages.

So I think libral is all about individual types. I really like that. That leaves it to higher-level tools to make use of those resources. @lutter, have you given any thought to how a user of Libral might pass in context to aid in the job of the provider? ie. in this case if the dpkg::ssh provider was passed a list of hostnames, or the dpkg::docker provider was passed a list of docker ids?

garethr avatar Jun 23 '17 10:06 garethr

So, partly answering my last question here, I think this is already captured in https://github.com/puppetlabs/libral/issues/30

With quite a few providers, get can only retrieve resources if it is given additional parameters

I think the only added detail is that, in this case, the value of the additional parameter is a list.

garethr avatar Jun 23 '17 15:06 garethr

This is quite nice in terms of demonstration, though it would be cleaner to split this into multiple providers; and those could just be pretty short shell scripts that use the simple calling convention, like, e.g., the systemd provider

And yes, as you noticed, we need a way to plumb configuration parameters through to get; it's partially done internally, but not exposed yet.

lutter avatar Jun 23 '17 17:06 lutter

@garethr I think its premature to see the final shape of what input data should look like, for sure, but yes agree 100% we need to think about this requirement now in this world.

Rather the premature part is the format that we might want to think in, rather than what libral might require.

kbarber avatar Jun 23 '17 17:06 kbarber