Skip to content

Commit

Permalink
Create package to install Ansible roles through ansible-galaxy
Browse files Browse the repository at this point in the history
  • Loading branch information
apenella committed Apr 2, 2024
1 parent a8ce3f7 commit d927a62
Show file tree
Hide file tree
Showing 16 changed files with 990 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Before you proceed further, please take note of the following considerations to

When executing _Ansible_ commands using the _go-ansible_ library inside a container, ensure that the container has configured an init system. The init system is necessary to manage the child processes created by the _Ansible_ commands. If the container does not have an init system, the child processes may not be correctly managed, leading to unexpected behavior such as zombie processes.

You can read more about that in the issue [139](https://github.com/apenella/go-ansible/issues/139).
You can read more about that in the issue [139](https://github.com/apenella/go-ansible/issues/139) and [here](https://github.com/ansible/ansible/issues/49270#issuecomment-462306244).

### Disable pseudo-terminal allocation

Expand Down
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Version 2.0.0 of *go-ansible* introduces several disruptive changes. Read the up
- `github.com/apenella/go-ansible/v2/pkg/execute/result/json` package has been introduced. This package offers the component for printing execution results from the JSON stdout callback. It supersedes the `JSONStdoutCallbackResults` function that was previously defined in the `github.com/apenella/go-ansible/v2/pkg/stdoutcallback` package.
- `github.com/apenella/go-ansible/v2/pkg/execute/stdoutcallback`. package has been introduced and offers multiple decorators designed to set the stdout callback for Ansible executions.
- `github.com/apenella/go-ansible/v2/pkg/execute/workflow` package has been introduced and allows you to define a workflow for executing multiple commands in a sequence.
- `github.com/apenella/go-ansible/v2/pkg/galaxy/role/install` package has been introduced. This package allows you to install Ansible roles from the Ansible Galaxy.
- `NewAnsibleAdhocCmd`, `NewAnsibleInventoryCmd` and `NewAnsiblePlaybookCmd` functions have been introduced. These functions are responsible for creating the `AnsibleAdhocCmd`, `AnsibleInventoryCmd` and `AnsiblePlaybookCmd` structs, respectively.
- `ResultsOutputer` interface has been introduced in the `github.com/apenella/go-ansible/v2/pkg/execute/result` package. This interface defines the criteria for a struct to be compliant in printing execution results.
- A utility to generate the code for the configuration package has been introduced. This utility is located in the `utils/cmd/configGenerator.go`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

DOCKER_COMPOSE_BINARY := $(shell docker compose version > /dev/null 2>&1 && echo "docker compose" || (which docker-compose > /dev/null 2>&1 && echo "docker-compose" || (echo "docker compose not found. Aborting." >&2; exit 1)))

PROJECT_NAME := go-ansible-$(shell basename ${PWD})

# dafault target
.DEFAULT_GOAL: help

help: ## Lists available targets
@echo
@echo "Makefile usage:"
@grep -E '^[a-zA-Z1-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[1;32m%-20s\033[0m %s\n", $$1, $$2}'
@echo

build: ## Build the Docker compose environment
@$(DOCKER_COMPOSE_BINARY) build

generate-keys: cleanup-keys ## Generate the SSH key pair required to autheneticate to SSH server
@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) run --rm openssh-client -t rsa -q -N "" -f id_rsa -C "apenella@$(PROJECT_NAME).test"

cleanup-keys: ## Cleanup the SSH key pair
@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) run --rm --entrypoint /bin/sh openssh-client -c 'rm -rf $$(ls)'

up: generate-keys ## Create and start containers
@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) up --detach --build

down: ## Stop and remove containers, networks, and volumes
@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) down --volumes --remove-orphans --timeout 3

restart: down up ## Restart the containers

ps: ## List containers
@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) ps

logs: ## Show all logs
@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) logs

attach-ansible: ## Attach to the ansible container
@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) exec --workdir /code/examples/$$(basename $$(pwd)) ansible /bin/sh

attach-server: ## Attach to the server container
@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) exec server /bin/sh

project-name: ## Show the project name
@echo $(PROJECT_NAME)

run: ## Run the example
@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) exec --workdir /code/examples/$$(basename $$(pwd)) ansible go run $$(basename $$(pwd)).go

start-and-run: up run ## Start the environment and run the example
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: ansibleplaybook-ssh

services:
ansible:
build:
context: docker/ansible
command: ["tail", "-f", "/dev/null"]
# command: ["ansible-playbook", "--help"]
volumes:
- ssh:/ssh
- ../..:/code
working_dir: /code
## Set the init flag to true lets the process 1 to reap all the zombie processes
init: true
depends_on:
- server

server:
build:
context: docker/server
# command: ["cat", "/etc/ssh/sshd_config"]
volumes:
- ssh:/ssh
depends_on:
- openssh-client
# ports:
# - "22220:22"

openssh-client:
build:
context: docker/openssh-client
working_dir: /ssh
volumes:
- ssh:/ssh
deploy:
resources:
limits:
memory: 10M

volumes:
ssh:
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM python:3.12-alpine3.19

RUN apk add --update --no-cache \
openssh-client \
git \
&& rm -rf /var/cache/apk/*

RUN pip3 install -U pip setuptools \
&& pip3 install --no-cache-dir \
setuptools-rust \
cryptography \
# Required library to execute ansible community.general.dig plugin
dnspython \
ansible \
&& ln /usr/local/bin/ansible-playbook /usr/bin/ansible-playbook

RUN apk add --update --no-cache \
go

# Configure Go
ENV GOROOT /usr/lib/go
ENV GOPATH /go
ENV PATH /go/bin:$PATH

RUN mkdir -p ${GOPATH}/src ${GOPATH}/bin
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM alpine:3.19

RUN apk add --update --no-cache \
openssh-client \
git \
&& rm -rf /var/cache/apk/*

ENTRYPOINT [ "/usr/bin/ssh-keygen" ]
CMD [ "--help" ]
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM python:bookworm


RUN adduser --disabled-password --gecos "" ansible

RUN apt-get update \
&& apt-get install -y \
openssh-server \
&& rm -rf /var/lib/apt/lists/*

RUN mkdir /var/run/sshd \
&& ssh-keygen -A

COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

# Expose the SSH port
EXPOSE 22

CMD ["/usr/local/bin/entrypoint.sh"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh

home=$(getent passwd "$(whoami)" | cut -d: -f6)

mkdir -p "${home}/.ssh"
cat /ssh/id_rsa.pub > "${home}/.ssh/authorized_keys"

/usr/sbin/sshd -D
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---

all:
hosts:
server:
ansible_host: server
ansible_user: root
ansible_ssh_private_key_file: /ssh/id_rsa

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---

- hosts: server

tasks:
- name: Install nginx
ansible.builtin.import_role:
name: geerlingguy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import (
"context"
"fmt"
"os"

"github.com/apenella/go-ansible/v2/pkg/execute"
"github.com/apenella/go-ansible/v2/pkg/execute/workflow"
galaxy "github.com/apenella/go-ansible/v2/pkg/galaxy/role/install"
"github.com/apenella/go-ansible/v2/pkg/playbook"
)

func main() {

ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
Inventory: "inventory.yml",
SSHCommonArgs: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null",
}

galaxyInstallRolesCmd := galaxy.NewAnsibleGalaxyRoleInstallCmd(
galaxy.WithRoleNames("geerlingguy.go"),
galaxy.WithGalaxyRoleInstallOptions(&galaxy.AnsibleGalaxyRoleInstallOptions{
Force: true,
}),
)

galaxyInstallRolesExec := execute.NewDefaultExecute(
execute.WithCmd(galaxyInstallRolesCmd),
)

playbookCmd := playbook.NewAnsiblePlaybookExecute("site.yml").
WithPlaybookOptions(ansiblePlaybookOptions)

err := workflow.NewWorkflowExecute(galaxyInstallRolesExec, playbookCmd).WithTrace().Execute(context.TODO())
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}
16 changes: 15 additions & 1 deletion pkg/execute/workflow/workflowExecute.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import (
"fmt"

"github.com/apenella/go-ansible/v2/pkg/execute"
"github.com/fatih/color"
)

type WorkflowExecute struct {
// ExecutorList is a list of executors
ExecutorList []execute.Executor
// ContinueOnError is a flag to continue on error
ContinueOnError bool
// Trace is a flag to trace the execution
Trace bool
}

// NewWorkflowExecute creates a new WorkflowExecute
Expand All @@ -33,11 +36,22 @@ func (e *WorkflowExecute) WithContinueOnError() *WorkflowExecute {
return e
}

// WithTrace sets the trace flag to true
func (e *WorkflowExecute) WithTrace() *WorkflowExecute {
e.Trace = true
return e
}

// Execute runs the executors
func (e *WorkflowExecute) Execute(ctx context.Context) error {
var errList []error = make([]error, 0)

for _, executor := range e.ExecutorList {
for executionNum, executor := range e.ExecutorList {

if e.Trace {
color.Blue(fmt.Sprintf("\n\u2022 executing task %d out of %d\n", executionNum+1, len(e.ExecutorList)))
}

err := executor.Execute(ctx)
if err != nil {
errList = append(errList, err)
Expand Down
108 changes: 108 additions & 0 deletions pkg/galaxy/role/install/ansibleGalaxyRoleInstallCmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package galaxyroleinstall

import (
"fmt"
)

const (
// DefaultAnsibleGalaxyRoleInstallBinary is the ansible-galaxy binary file default value
DefaultAnsibleGalaxyRoleInstallBinary = "ansible-galaxy"

//AnsibleGalaxyRoleInstallCommand is the ansible-galaxy command to install roles
AnsibleGalaxyRoleInstallCommand = "role install"
)

// AnsibleGalaxyRoleInstallOptionsFunc is a function to set executor options
type AnsibleGalaxyRoleInstallOptionsFunc func(*AnsibleGalaxyRoleInstallCmd)

// AnsibleGalaxyRoleInstallCmd object is the main object which defines the `ansible-galaxy` command to install roles.
type AnsibleGalaxyRoleInstallCmd struct {
// Binary is the ansible-galaxy binary file
Binary string

// RoleNames is the ansible-galaxy's role names to be installed
RoleNames []string

// GalaxyRoleInstallOptions are the ansible-galaxy's role install options
GalaxyRoleInstallOptions *AnsibleGalaxyRoleInstallOptions
}

// NewAnsibleGalaxyRoleInstallCmd creates a new AnsibleGalaxyRoleInstallCmd instance
func NewAnsibleGalaxyRoleInstallCmd(options ...AnsibleGalaxyRoleInstallOptionsFunc) *AnsibleGalaxyRoleInstallCmd {
cmd := &AnsibleGalaxyRoleInstallCmd{}

for _, option := range options {
option(cmd)
}

return cmd
}

// WithBinary set the ansible-galaxy binary file
func WithBinary(binary string) AnsibleGalaxyRoleInstallOptionsFunc {
return func(p *AnsibleGalaxyRoleInstallCmd) {
p.Binary = binary
}
}

// WithGalaxyRoleInstallOptions set the ansible-galaxy role install options
func WithGalaxyRoleInstallOptions(options *AnsibleGalaxyRoleInstallOptions) AnsibleGalaxyRoleInstallOptionsFunc {
return func(p *AnsibleGalaxyRoleInstallCmd) {
p.GalaxyRoleInstallOptions = options
}
}

// WithRoleNames set the ansible-galaxy role names
func WithRoleNames(roleNames ...string) AnsibleGalaxyRoleInstallOptionsFunc {
return func(p *AnsibleGalaxyRoleInstallCmd) {
p.RoleNames = append([]string{}, roleNames...)
}
}

// Command generate the ansible-galaxy role install command which will be executed
func (p *AnsibleGalaxyRoleInstallCmd) Command() ([]string, error) {
cmd := []string{}

// Use default binary when it is not already defined
if p.Binary == "" {
p.Binary = DefaultAnsibleGalaxyRoleInstallBinary
}

cmd = append(cmd, p.Binary, "role", "install")

// Add the options
if p.GalaxyRoleInstallOptions != nil {
options, err := p.GalaxyRoleInstallOptions.GenerateCommandOptions()
if err != nil {
return nil, err
}
cmd = append(cmd, options...)
}

// Add the role names
cmd = append(cmd, p.RoleNames...)

return cmd, nil
}

// String returns the ansible-galaxy role install command as a string
func (p *AnsibleGalaxyRoleInstallCmd) String() string {

// Use default binary when it is not already defined
if p.Binary == "" {
p.Binary = DefaultAnsibleGalaxyRoleInstallBinary
}

str := fmt.Sprintf("%s %s", p.Binary, AnsibleGalaxyRoleInstallCommand)

if p.GalaxyRoleInstallOptions != nil {
str = fmt.Sprintf("%s %s", str, p.GalaxyRoleInstallOptions.String())
}

// Include the role names
for _, roleName := range p.RoleNames {
str = fmt.Sprintf("%s %s", str, roleName)
}

return str
}
Loading

0 comments on commit d927a62

Please sign in to comment.