Skip to content

Commit

Permalink
#110: Allow to "start", "stop" and "rm" the container for local proje…
Browse files Browse the repository at this point in the history
…ct and "cikit ssh" inside of it
  • Loading branch information
BR0kEN- committed Mar 19, 2018
1 parent 3b812ca commit ce39e71
Show file tree
Hide file tree
Showing 15 changed files with 191 additions and 66 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
!lib/
!cmf/
!docs/
!env/
docs/_site/
!tests/
!scripts/
Expand Down
3 changes: 3 additions & 0 deletions env/rm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# requires-project-root
---
- import_playbook: start.yml __action=rm
28 changes: 28 additions & 0 deletions env/start.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# requires-project-root
---
- hosts: localhost
connection: local
gather_facts: no

vars_files:
- "{{ __targetdir__ }}/.cikit/config.yml"

vars:
__action: start

tasks:
- name: Define the hostname
set_fact:
hostname: "{{ site_url.split('//') | last }}"
mounts: ""
ports: ""

- name: Check whether container exists
shell: "docker ps -qaf name='^/{{ hostname }}'"
register: container_exists

- include_tasks: tasks/create.yml
when: "not container_exists.stdout and 'start' == __action"

- include_tasks: "tasks/{{ __action }}.yml"
when: container_exists.stdout
3 changes: 3 additions & 0 deletions env/stop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# requires-project-root
---
- import_playbook: start.yml __action=stop
35 changes: 35 additions & 0 deletions env/tasks/create.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
- name: Define the mounts
set_fact:
mounts: |-
{{ mounts }} -v '{{ item.source if item.source[0] == '/' else __targetdir__ + '/' + item.source }}':'{{ item['target' if item.target else 'source'] }}'
with_items: "{{ vm.folders }}"

- name: Define the ports
set_fact:
ports: |-
{{ ports }} -p {{ item }}
with_items: "{{ vm.ports }}"

- name: Create the container
# @todo These options aren't working on macOS.
# -v /sys/fs/cgroup:/sys/fs/cgroup:ro \
# --tmpfs /run \
# --tmpfs /run/lock \
# --security-opt seccomp=unconfined \
shell: |-
docker run \
-d \
-h '{{ hostname }}' \
--name '{{ hostname }}' \
{{ mounts }} \
{{ ports }} \
solita/ubuntu-systemd
- name: Install the requirements
shell: |-
docker exec -t {{ hostname }} bash -c 'apt update -y && apt install sudo dbus python-minimal -y && service dbus restart'
- name: Print the name of created container
debug:
msg: "The '{{ hostname }}' just have been created."
3 changes: 3 additions & 0 deletions env/tasks/do.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
- name: "{{ operation | default(command | capitalize) }} the '{{ hostname }}' container"
shell: docker {{ command }} {{ hostname }}
4 changes: 4 additions & 0 deletions env/tasks/is-running.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
- name: Check whether container is running
shell: "docker inspect -f {% raw %}{{.State.Running}}{% endraw %} {{ hostname }}"
register: container_running
7 changes: 7 additions & 0 deletions env/tasks/rm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
- include_tasks: stop.yml

- include_tasks: do.yml
args:
command: rm
operation: Remove
5 changes: 5 additions & 0 deletions env/tasks/start.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
- include_tasks: is-running.yml

- include_tasks: do.yml command=start
when: not container_running.stdout | bool
5 changes: 5 additions & 0 deletions env/tasks/stop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
- include_tasks: is-running.yml

- include_tasks: do.yml command=stop
when: container_running.stdout | bool
89 changes: 30 additions & 59 deletions lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,24 @@
from re import search

PARAMS = []
COMMAND = 'ansible-playbook'

if variables.INSIDE_VM_OR_CI and not variables.INSIDE_PROJECT_DIR:
functions.error('The "%s" directory does not store CIKit project.' % variables.dirs['project'], errno.ENOTDIR)

if '' == args.playbook:
if not variables.INSIDE_VM_OR_CI:
for group in ['host', 'matrix']:
for group in ['env', 'host', 'matrix']:
functions.playbooks_print(variables.dirs['self'], '%s/' % group)

functions.playbooks_print(variables.dirs['scripts'])

sys.exit(0)
elif 'ssh' == args.playbook:
# Read the configuration of a project we're currently in.
hostname = functions.get_hostname(variables.read_yaml(variables.dirs['cikit'] + '/config.yml'))

if '' != hostname:
sys.exit(call(['docker exec -it %s bash' % hostname], shell=True))

PLAYBOOK = functions.playbooks_find(
variables.dirs['scripts'] + '/' + args.playbook,
Expand All @@ -37,6 +42,23 @@
if 'CIKIT_LIST_TAGS' in os.environ:
PARAMS.append('--list-tags')
else:
# The "cikit provision" run without required "--limit" option.
if args.playbook.endswith('provision') and not args.limit:
hostname = functions.get_hostname(variables.read_yaml(variables.dirs['cikit'] + '/config.yml'))

if '' != hostname:
# http://blog.oddbit.com/2015/10/13/ansible-20-the-docker-connection-driver
args.limit = hostname + ','

PARAMS.append("-i '%s'" % args.limit)
PARAMS.append("-c 'docker'")
PARAMS.append("-u 'root'")

# Duplicate the "limit" option as "extra" because some playbooks may
# require it and required options are checked within the "extra" only.
if args.limit:
args.extra['limit'] = args.limit

for line in open(PLAYBOOK):
if search('^# requires-project-root$', line) and not variables.INSIDE_PROJECT_DIR:
functions.error(
Expand Down Expand Up @@ -64,62 +86,11 @@
errno.EPERM
)

ENV_CONFIG = variables.dirs['cikit'] + '/environment.yml'

if os.path.isfile(ENV_CONFIG):
ansible_executable = functions.call('which', COMMAND)

if '' == ansible_executable:
# Only warn and do not fail the execution because it's possible to continue without
# these values. The command may be not found in a case it is set as an "alias".
functions.warn(
(
'Cannot read environment configuration from "%s". Looks '
'like Python setup cannot provide Ansible operability.'
)
%
(
ENV_CONFIG
)
)
else:
# It's an interesting trick for detecting Python interpreter. Sometimes it
# may differ. Especially on MacOS, when Ansible installed via Homebrew. For
# instance, "which python" returns the "/usr/local/Cellar/python/2.7.13/
# Frameworks/Python.framework/Versions/2.7/bin/python2.7", but this particular
# setup may not have necessary packages for full Ansible operability. Since
# Ansible - is a Python scripts, they must have a shadebag line with a path
# to interpreter they should run by. Grab it and try!
# Given:
# $(realpath $(which python)) -c 'import yaml'
# Ends by:
# Traceback (most recent call last):
# File "<string>", line 1, in <module>
# ImportError: No module named yaml
# But:
# $(cat $(which "ansible-playbook") | head -n1 | tr -d '#!') -c 'import yaml'
# Just works.
with open(ansible_executable) as ansible_executable:
python_ansible = ansible_executable.readline().lstrip('#!').strip()

# Do not apply the workaround if an exactly same interpreter is used for
# running CIKit and Ansible.
if functions.call('which', 'python').strip() == python_ansible:
import yaml

ENV_CONFIG = json.dumps(yaml.load(open(ENV_CONFIG)))
else:
ENV_CONFIG = functions.call(
python_ansible,
'-c',
'import yaml, json\nprint json.dumps(yaml.load(open(\'%s\')))' % ENV_CONFIG,
)

for key, value in json.loads(ENV_CONFIG).iteritems():
# Add the value from environment config only if it's not specified as
# an option to the command.
if key not in args.extra:
args.extra[key] = value
for key, value in variables.read_yaml(variables.dirs['cikit'] + '/environment.yml').iteritems():
# Add the value from environment config only if it's not specified as
# an option to the command.
if key not in args.extra:
args.extra[key] = value

if 'EXTRA_VARS' in os.environ:
functions.parse_extra_vars(shlex.split(os.environ['EXTRA_VARS']), args.extra)
Expand Down Expand Up @@ -163,7 +134,7 @@
os.environ['DISPLAY_SKIPPED_HOSTS'] = '0'
os.environ['ANSIBLE_RETRY_FILES_ENABLED'] = '0'

COMMAND = "%s '%s' %s" % (COMMAND, PLAYBOOK, ' '.join(PARAMS))
COMMAND = "%s '%s' %s" % (variables.ANSIBLE_COMMAND, PLAYBOOK, ' '.join(PARAMS))

# Print entire command if verbosity requested.
if 'ANSIBLE_VERBOSITY' in os.environ:
Expand Down
5 changes: 0 additions & 5 deletions lib/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,4 @@

parse_extra_vars(argv, args.extra)

# Duplicate the "limit" option as "extra" because some playbooks may
# require it and required options are checked within the "extra" only.
if args.limit:
args.extra['limit'] = args.limit

del argv
11 changes: 10 additions & 1 deletion lib/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,16 @@ def parse_extra_vars(args, bag):
bag[arg[0].replace('-', '_')] = arg[1]


def error(message, code):
def get_hostname(config):
# The name of Docker container for local development is forming based on the
# hostname that is taken from the "site_url".
if 'site_url' in config:
return config['site_url'].split('//')[-1]

return ''


def error(message, code=1):
print('\033[91mERROR: ' + message + '\033[0m')
exit(code)

Expand Down
56 changes: 56 additions & 0 deletions lib/variables.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import os
import json
import functions

ANSIBLE_COMMAND = 'ansible-playbook'
ANSIBLE_EXECUTABLE = functions.call('which', ANSIBLE_COMMAND)

dirs = {
'lib': os.path.realpath(__file__ + '/..'),
'self': os.path.realpath(__file__ + '/../..'),
Expand Down Expand Up @@ -31,3 +35,55 @@

dirs['scripts'] += '/scripts'
dirs['credentials'] += '/credentials'

if '' == ANSIBLE_EXECUTABLE:
functions.error(
(
'An executable for the "%s" command cannot be found. '
'Looks like Python setup cannot provide Ansible operability.'
)
%
(
ANSIBLE_COMMAND
)
)

# It's an interesting trick for detecting Python interpreter. Sometimes it
# may differ. Especially on MacOS, when Ansible installed via Homebrew. For
# instance, "which python" returns the "/usr/local/Cellar/python/2.7.13/
# Frameworks/Python.framework/Versions/2.7/bin/python2.7", but this particular
# setup may not have necessary packages for full Ansible operability. Since
# Ansible - is a Python scripts, they must have a shadebag line with a path
# to interpreter they should run by. Grab it and try!
# Given:
# $(realpath $(which python)) -c 'import yaml'
# Ends by:
# Traceback (most recent call last):
# File "<string>", line 1, in <module>
# ImportError: No module named yaml
# But:
# $(cat $(which "ansible-playbook") | head -n1 | tr -d '#!') -c 'import yaml'
# Just works.
with open(ANSIBLE_EXECUTABLE) as ANSIBLE_EXECUTABLE:
python_ansible = ANSIBLE_EXECUTABLE.readline().lstrip('#!').strip()

# Do not apply the workaround if an exactly same interpreter is used for
# running CIKit and Ansible.
if functions.call('which', 'python').strip() == python_ansible:
import yaml

def read_yaml(path):
if os.path.isfile(path):
return json.loads(json.dumps(yaml.load(open(path))))

return {}
else:
def read_yaml(path):
if os.path.isfile(path):
return json.loads(functions.call(
python_ansible,
'-c',
'import yaml, json\nprint json.dumps(yaml.load(open(\'%s\')))' % path,
))

return {}
2 changes: 1 addition & 1 deletion scripts/provision.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@

- name: Check whether provision running for local environment
set_fact:
localhost: "{{ ansible_host in ['127.0.0.1', 'localhost'] }}"
localhost: "{{ ansible_host in ['127.0.0.1', 'localhost'] or ansible_host == site_url.split('//') | last }}"
tags: ["always"]

- include_tasks: tasks/password.yml
Expand Down

0 comments on commit ce39e71

Please sign in to comment.