Since Ansible 2.2 you can use binary applications as modules for Ansible. This means you can write modules in languages other than Python. The downside is that the modules aren’t integrated as well as if they were written in Python with Ansiballz. The binary modules only takes the filename as an argument which is a temporary file containing the JSON data of the modules parameters.

I took the boilerplate code that Ansible had here and created a small module to generate a random password. The only parameters for the module are the password length and the type of password (alpha, numeric, alphanumeric, full). If you don’t pass any parameters, it defaults to full (numbers, letters, and symbols) and 20 characters. The way it is written right now, it won’t show a change when it generates a password. To show a change just set Response.Changed to true in your logic.

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math/rand"
	"os"
	"time"
)

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const numbers = "0123456789"
const symbols = `[email protected]#$%^&*()\/><}{[]`

type ModuleArgs struct {
	Length int
	Type   string
}

type Response struct {
	Password string `json:"password"`
	Changed  bool   `json:"changed"`
	Failed   bool   `json:"failed"`
}

func ExitJson(responseBody Response) {
	returnResponse(responseBody)
}

func FailJson(responseBody Response) {
	responseBody.Failed = true
	returnResponse(responseBody)
}

func returnResponse(responseBody Response) {
	var response []byte
	var err error
	response, err = json.Marshal(responseBody)
	if err != nil {
		response, _ = json.Marshal(Response{Password: "Invalid response object"})
	}
	fmt.Println(string(response))
	if responseBody.Failed {
		os.Exit(1)
	} else {
		os.Exit(0)
	}
}

func GeneratePassword(l int, s string) string {
	data := make([]byte, l)
	var characters string
	switch s {
	case "full":
		characters = letters + numbers + symbols
	case "alpha":
		characters = letters
	case "numeric":
		characters = numbers
	case "alphanumeric":
		characters = letters + numbers
	}

	n := len(characters)

	for i := range data {
		data[i] = characters[rand.Intn(n)]
	}

	return string(data)
}

func main() {
	var response Response

	rand.Seed(time.Now().UTC().UnixNano())

	if len(os.Args) != 2 {
		response.Password = "No argument file provided"
		FailJson(response)
	}

	argsFile := os.Args[1]

	text, err := ioutil.ReadFile(argsFile)
	if err != nil {
		response.Password = "Could not read configuration file: " + argsFile
		FailJson(response)
	}

	var moduleArgs ModuleArgs
	err = json.Unmarshal(text, &moduleArgs)
	if err != nil {
		response.Password = "Configuration file not valid JSON: " + argsFile
		FailJson(response)
	}

	var length = 20
	if moduleArgs.Length != 0 {
		length = moduleArgs.Length
	}

	var style = "full"
	if moduleArgs.Type != "" {
		style = moduleArgs.Type
	}

	password := GeneratePassword(length, style)

	response.Password = password
	ExitJson(response)
}

From here you can just go build random.go and put the executable in the libraries directory that you’ve defined. Then you just call the module like normal:

module-test
  • playbook
  • output
1
2
3
4
5
6
7
8
9
10
11
12
13
- name: Generate Password
  hosts: localhost

  tasks:
    - name: Generate random pass
      random:
        length: 125
        type: full
      register: password

    - name: Print password
      debug:
        var: password.password