Getting started (Kotlin)
The purpose of this tutorial is to ilustrate how to set up environment for working with jancy. We will install and set up the necessary tools, write a simple playbook, and apply it to a virtual machine.
With jancy, you write your playbook as plain Kotlin code, using module wrappers from the jancy-common
jar. Then you package your code in a jar and feed it to the jancy
compiler, which converts it to a YAML playbooks which can be applied to your target machines with the ansible-playbook
command.
If you have a feeling of deja vu, you might have recently read Chef tutorial, on which the example playbook was based.
In this tutorial, Kotlin and Gradle is used. If you would like to use another language or build tool, please see other versions.
You can find source code for this tutorial on Github.
Contents
Getting the tools
Make sure you have a JDK, Ansible and Gradle installed. All of them should be available for installation from your distribution's software repository.
# on Fedora
sudo dnf install ansible java-1.8.0-openjdk gradle
#on Ubuntu
sudo apt install ansible openjdk-8-jdk gradle
Next, download the jancy
compiler to a directory included in the $PATH
and make it executable:
sudo wget https://jancy.tznvy.eu/download/jancy-transpiler-latest -O /usr/local/bin/jancy
sudo chmod 755 /usr/local/bin/jancy
Creating a new project
Now that we have all the tools installed, let's create a directory for the project.
mkdir helloworld-kotlin
cd helloworld-kotlin
There are two things we need to set up in Gradle. First, we must tell it where to get the jancy-common
jar from:
repositories {
mavenCentral()
maven {
url {
"https://jancy.tznvy.eu/m2"
}
}
}
dependencies {
compile group: 'eu.tznvy', name: 'jancy-common', version: '0.1.0-SNAPSHOT'
}
Second, we need to create a fat jar from our code and its dependencies.
jar {
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
Now, let's create a dummy playbook factory:
import eu.tznvy.jancy.core.Playbook
import eu.tznvy.jancy.core.PlaybookFactory
class HelloWorldPlaybookFactory : PlaybookFactory {
override fun build(): Playbook {
return Playbook("helloworld")
}
}
and make sure it compiles and packages correctly:
gradle build
We point jancy to our jar and tell it to save the output in a particular directory:
jancy -j build/libs/helloworld-kotlin.jar -o target/
jancy
will search your jar for classes implementing the PlaybookFactory
interface and instantiate each of them using the default constructors. It will then invoke the build
method on them and inspect the returned Playbook
objects. Based on them, the YAML configuration will be generated and saved in appropriate directories.
Since our PlaybookFactory
returns an empty playbook, jancy
will just create an empty directory target/helloworld
.
Writing the playbook
It's best to experiment with Ansible on a separate virtual machine that one can throw away or revert to a previous state in case we want to start with a blank state. Let's assume we have one with sshd enabled and running, it shares a NAT network with the host and has the ssh port forwarded to 2222 on localhost. If your setup is different, set ansible_ssh_host
and ansible_ssh_port
accordingly.
val vm = Host("vm")
.vars(
mapOf(
"ansible_host" to "localhost",
"ansible_port" to "2222"
)
)
We also have to create an inventory. Ansible uses inventories to hosts from different environments. So, for instance, one could have different inventories for production, staging and dev machines and apply playbooks to each inventory separately.
val inventory = Inventory("inventory")
.hosts(vm)
Now, let's say we would like to set the MOTD (Message Of The Day - the message displayed when a user logs in) on our virtual machine. This involves editing the /etc/motd
file, which, in Ansible, is achieved with the copy
module:
- name: Set up motd
copy:
dest=/etc/motd
content="Hello world!\n"
where dest
specifies the target directory, and content
specifies, well, the file content.
To do this with jancy, we need to create a Task
and set its action to the Copy
module.
val motdTask = Task("Set up motd")
.action(
Copy()
.dest("/etc/motd")
.content("Hello world!\n"))
You can check the available parameters in the official documentation or the jancy-common javadoc pages.
The names follow the Java naming conventions, so module wrappers are pascal-case (e.g. Copy
, IniFile
, ZabbixHost
), the module parameters are camel-case (dest
, directoryMode
, unsafeWrite
), and the constants are all-caps snake-case (Service.State.STARTED
, VmvareVmShell.VmIdType.DNS_NAME
).
However, for readability, the parameter setters are not prefixed with set
.
If you don't like the amount of indentation, you can first build the action and then convert it to a task:
val motdTask = Copy()
.dest("/etc/motd")
.content("Hello world!\n")
.toTask("Set up motd")
Now that we have a task and a host, we instruct Ansible to apply the first to the second:
val play = Play("")
.hosts(vm)
.tasks(motdTask)
And add the resulting play to the playbook:
return Playbook("helloworld")
.inventories(inventory)
.plays(play)
The code should now look like this:
import eu.tznvy.jancy.core.Playbook
import eu.tznvy.jancy.core.PlaybookFactory
import eu.tznvy.jancy.core.Host
import eu.tznvy.jancy.core.Play
import eu.tznvy.jancy.core.Task
import eu.tznvy.jancy.core.Inventory
import eu.tznvy.jancy.modules.files.Copy
class HelloWorldPlaybookFactory : PlaybookFactory {
override fun build(): Playbook {
val vm = Host("vm")
.vars(
mapOf(
"ansible_host" to "localhost",
"ansible_port" to "2222"
)
)
val inventory = Inventory("inventory")
.hosts(vm)
val motdTask = Copy()
.dest("/etc/motd")
.content("Hello world!\n")
.toTask("Set up motd")
val play = Play("")
.hosts(vm)
.tasks(motdTask)
return Playbook("helloworld")
.inventories(inventory)
.plays(play)
}
}
Applying the playbook
Now that our playbook is ready, let's repackage the jar and run jancy again:
gradle build
jancy -j build/libs/helloworld-kotlin.jar -o target/
This time jancy created several files in target/helloworld
:
tree target/helloworld
target/helloworld
├── host_vars
│ └── vm
├── inventory
└── site.yml
1 directory, 3 files
- name: ''
hosts: vm
tasks:
- name: Set up motd
copy: |-
content='Hello world!
'
dest='/etc/motd'
ansible_ssh_host: localhost
ansible_ssh_port: '2222'
vm
We can now apply the generated playbook to our vm:
ansible-playbook
--ask-pass \
--user root \
-i target/helloworld/inventory \
target/helloworld/site.yml
The --ask-pass
parameter forces Ansible to ask you for the root password. If omitted, ansible will try to use public-key authentication, which is reasonable in a production environment, but unnecessary when testing out things.
If everything goes well, after a series of cows, Ansible will report success:
SSH password:
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [vm]
TASK [Set up motd] *************************************************************
ok: [vm]
PLAY RECAP *********************************************************************
vm : ok=2 changed=1 unreachable=0 failed=0
We can now ssh into the machine to be greeted by our new MOTD:
ssh localhost -p 2222
root@localhost's password:
Last login: Fri Apr 7 10:41:56 2017 from gateway
Hello world!
[root@localhost ~]# _