Ansible Modules with Go
2019-06-30
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.ChangedResponse.Changed to true in your logic.
package mainimport ( "encoding/json" "fmt" "io/ioutil" "math/rand" "os" "time")const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"const numbers = "0123456789"const symbols = `!@#$%^&*()\/><}{[]`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)}
package mainimport ( "encoding/json" "fmt" "io/ioutil" "math/rand" "os" "time")const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"const numbers = "0123456789"const symbols = `!@#$%^&*()\/><}{[]`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.gogo build random.go and put the executable in the libraries directory that you’ve defined. Then you just call the module like normal:
playbook
- name: Generate Password hosts: localhost tasks: - name: Generate random pass random: length: 125 type: full register: password - name: Print password debug: var: password.password
- name: Generate Password hosts: localhost tasks: - name: Generate random pass random: length: 125 type: full register: password - name: Print password debug: var: password.password
output
PLAY [localhost] *****************************************************************************************************************************************************************************************************************************TASK [Generate random pass] ******************************************************************************************************************************************************************************************************************ok: [localhost]TASK [Print password] *********************************************************************************************************************************************************************************************************************************ok: [localhost] => { "password.password": "ylXFw0gtZ[ch/gc>rO7fd/A}gC5{Oe\\o<6MAf5ByHyM7vPIr<)u44!<2eGR)%T&*h8DAYIm[>$^%YSI#$9ES$ma#lLqXm]StW!WA4/xmz3juDjm@JkYDn@dBK/0<a"}PLAY RECAP ***********************************************************************************************************************************************************************************************************************************localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
PLAY [localhost] *****************************************************************************************************************************************************************************************************************************TASK [Generate random pass] ******************************************************************************************************************************************************************************************************************ok: [localhost]TASK [Print password] *********************************************************************************************************************************************************************************************************************************ok: [localhost] => { "password.password": "ylXFw0gtZ[ch/gc>rO7fd/A}gC5{Oe\\o<6MAf5ByHyM7vPIr<)u44!<2eGR)%T&*h8DAYIm[>$^%YSI#$9ES$ma#lLqXm]StW!WA4/xmz3juDjm@JkYDn@dBK/0<a"}PLAY RECAP ***********************************************************************************************************************************************************************************************************************************localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0