initial creation

This commit is contained in:
2025-05-20 11:57:43 -04:00
commit c98b612926
8447 changed files with 1862526 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
*.swp
*.*~
project.lock.json
.DS_Store
*.pyc
nupkg/
# Visual Studio Code
.vscode
# Rider
.idea
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
msbuild.log
msbuild.err
msbuild.wrn
# Visual Studio 2015
.vs/
Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

+182
View File
@@ -0,0 +1,182 @@
body {
background-color: #f2f2f2;
margin: 0px;
font-family: tahoma;
color: #282828;
}
.button {
margin: 30px;
background-color: #39495c;
border-radius: 5px;
font-size: 18px;
width: 160px;
height: 60px;
color: white;
padding: 20px;
box-shadow: inset 0 -0.6em 1em -0.35em rgba(0, 0, 0, 0.17),
inset 0 0.6em 2em -0.3em rgba(255, 255, 255, 0.15),
inset 0 0 0em 0.05em rgba(255, 255, 255, 0.12);
text-align: center;
cursor: pointer;
}
.cart {
margin: 25px 100px;
float: right;
border: 1px solid #d8d8d8;
padding: 10px 30px;
background-color: white;
-webkit-box-shadow: 0px 2px 15px -12px rgba(0, 0, 0, 0.57);
-moz-box-shadow: 0px 2px 15px -12px rgba(0, 0, 0, 0.57);
box-shadow: 2px 15px -12px rgba(0, 0, 0, 0.57);
}
.color-circle {
width: 50px;
height: 50px;
margin-top: 8px;
border: 2px solid #d8d8d8;
border-radius: 50%;
}
.disabledButton {
background-color: #d8d8d8;
cursor: not-allowed;
}
h1 {
font-size: 50px;
}
h3 {
font-size: 25px;
}
img {
border: 2px solid #d8d8d8;
width: 70%;
margin: 40px;
padding: 15px;
-webkit-box-shadow: 0px 2px 15px -12px rgba(0, 0, 0, 0.57);
-moz-box-shadow: 0px 2px 15px -12px rgba(0, 0, 0, 0.57);
box-shadow: 2px 15px -12px rgba(0, 0, 0, 0.57);
}
input {
width: 100%;
height: 40px;
margin-bottom: 20px;
}
label {
font-size: 20px;
margin-bottom: 5px;
}
li {
font-size: 18px;
}
.nav-bar {
background: linear-gradient(-90deg, #84cf6a, #16c0b0);
height: 60px;
margin-bottom: 25px;
-webkit-box-shadow: 0px 2px 15px -12px rgba(0, 0, 0, 0.57);
-moz-box-shadow: 0px 2px 15px -12px rgba(0, 0, 0, 0.57);
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.57);
}
.out-of-stock-img {
opacity:0.5
}
p {
font-size: 22px;
}
.product-display {
display: flex;
flex-direction: column;
padding: 1rem;
}
.product-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.product-image,
.product-info {
width: 50%;
}
.review-form {
display: flex;
flex-direction: column;
width: 425px;
padding: 20px;
margin: 40px;
border: 2px solid #d8d8d8;
background-color: white;
-webkit-box-shadow: 0px 2px 15px -12px rgba(0, 0, 0, 0.57);
-moz-box-shadow: 0px 2px 15px -12px rgba(0, 0, 0, 0.57);
box-shadow: 2px 15px -12px rgba(0, 0, 0, 0.57);
}
.review-container {
width: 425px;
padding: 20px;
background-color: white;
-webkit-box-shadow: 0px 2px 20px -12px rgba(0, 0, 0, 0.57);
-moz-box-shadow: 0px 2px 20px -12px rgba(0, 0, 0, 0.57);
box-shadow: 2px 20px -12px rgba(0, 0, 0, 0.57);
margin-left: 40px;
border: 2px solid #d8d8d8;
}
.review-container li {
margin-bottom: 30px;
}
.review-form .button {
display: block;
margin: 30px auto;
}
select {
height: 40px;
font-size: 20px;
background-color: white;
cursor: pointer;
}
textarea {
width: 95%;
height: 70px;
padding: 10px;
font-size: 20px;
margin-bottom: 20px;
}
ul {
list-style-type: none;
}
@media only screen and (max-width: 600px) {
.container {
flex-direction: column;
}
.product-image,
.product-info {
margin-left: 10px;
width: 100%;
}
.review-form {
width: 90%;
}
}
+24
View File
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Vue Mastery</title>
<!-- Import Styles -->
<link rel="stylesheet" href="./assets/styles.css" />
<!-- Import Vue.js -->
<script src="https://unpkg.com/vue@3.0.11/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
<h1>{{ product }}</h1>
</div>
<!-- Import App -->
<script src="./main.js"></script>
<!-- Mount App -->
<script>
const mountedApp = app.mount('#app')
</script>
</body>
</html>
+7
View File
@@ -0,0 +1,7 @@
const app = Vue.createApp({
data() {
return {
product: 'Socks'
}
}
})
+16
View File
@@ -0,0 +1,16 @@
Inventory Challenge
1. Create 3 Individual Hosts:
mail.mycorp.local
smtp.mycorp.local
auth.mycorp.local
2. Group the Domain Controllers:
dc01.mycorp.local
dc02.mycorp.local
3. Group the London App servers:
app01.ldn.mycorp.local
app02.lnd.mycorp.local
4. Group the Texas App Servers:
app03.tx.mycorp.local
app04.tx.mycorp.local
5. Group the London and Texas servers in a single group called: itlappsrv
@@ -0,0 +1,23 @@
fruit:
red:
strawberry
tomato
orange:
- orange
yellow:
- banana
green:
- grapes
- watermelon
total: 6
veg:
yellow:
- peppers
orange:
- carrot
green:
- broccoli
- cabbage
- lettuce
total: 5
+22
View File
@@ -0,0 +1,22 @@
# Create a new file 'completed.yaml'
# Sort the items by catagory and by colour
# Add a total at the end
items:
- banana
- orange
- strawberry
- broccoli
- cabbage
- grapes
- carrot
- tomato
- peppers
- watermelon
- lettuce
fruit:
veg:
+1
View File
@@ -0,0 +1 @@
web ansible_host=ec2-18-130-249-7.eu-west-2.compute.amazonaws.com ansible_port=22 ansible_user=ec2-user ansible_ssh_private_key_file=/Users/Keys/ansible_lnd_key.pem
+7
View File
@@ -0,0 +1,7 @@
all:
hosts:
web:
ansible_host: ec2-3-8-144-7.eu-west-2.compute.amazonaws.com
ansible_port: 22
ansible_user: ec2-user
ansible_ssh_private_key_file: /Users/Keys/ansible_lnd_key.pem
+17
View File
@@ -0,0 +1,17 @@
- hosts: localhost
#gather_facts: no
tasks:
- name: Error Handling
block:
- name: Going to Fail
debug:
msg: "Running the module"
- name: Spanner
command: /bin/hsab
- name: Never Runs
debug:
msg: "This Never Runs!"
rescue:
- name: "Something went wrong..."
debug:
msg: "The error was caught"
+19
View File
@@ -0,0 +1,19 @@
mail.mycorp.local
smtp.mycorp.local
auth.mycorp.local
[dc]
dc01.mycorp.local
dc02.mycorp.local
[ldnappsrv]
app01.ldn.mycorp.local
app02.lnd.mycorp.local
[txappsrv]
app03.tx.mycorp.local
app04.tx.mycorp.local
[itlappsrv:children]
ldnappsrv
txappsrv
+20
View File
@@ -0,0 +1,20 @@
all:
hosts:
mail.mycorp.local:
smtp.mycorp.local:
auth.mycorp.local:
children:
itlappsrv:
children:
lndappsrv:
hosts:
app01.ldn.mycorp.local:
app02.ldn.mycorp.local:
txappsrv:
hosts:
app03.tx.mycorp.local:
app04.tx.mycorp.local:
dc:
children:
dc01.mycorp.local:
dc02.mycorp.local:
+32
View File
@@ -0,0 +1,32 @@
- hosts: web
remote_user: root
become: yes
tasks:
- name: Update All Packages
yum:
name: '*'
state: latest
- name: Install Apache
yum:
name: httpd
state: installed
- name: Create The HTML File
shell: echo "Hello From The Ansible Challenge" > /var/www/html/index.html
args:
executable: /bin/bash
notify:
- Reload Apache
- name: Public IP
shell:
cmd: curl http://169.254.169.254/latest/meta-data/public-ipv4
register: curl
- debug: var=curl.stdout_lines
- name: Uninstall Apache
yum:
name: httpd
state: removed
handlers:
- name: Reload Apache
service:
name: httpd
state: reloaded
+38
View File
@@ -0,0 +1,38 @@
Role Name
=========
A brief description of the role goes here.
Requirements
------------
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
Role Variables
--------------
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
Dependencies
------------
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
Example Playbook
----------------
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
- hosts: servers
roles:
- { role: username.rolename, x: 42 }
License
-------
BSD
Author Information
------------------
An optional section for the role authors to include contact information, or a website (HTML is not allowed).
@@ -0,0 +1,3 @@
---
# defaults file for db
db_server: mariadb
@@ -0,0 +1,6 @@
---
# handlers file for db
- name: Start DB
service:
name: "{{ db_server }}"
state: started
@@ -0,0 +1,53 @@
galaxy_info:
author: your name
description: your role description
company: your company (optional)
# If the issue tracker for your role is not on github, uncomment the
# next line and provide a value
# issue_tracker_url: http://example.com/issue/tracker
# Choose a valid license ID from https://spdx.org - some suggested licenses:
# - BSD-3-Clause (default)
# - MIT
# - GPL-2.0-or-later
# - GPL-3.0-only
# - Apache-2.0
# - CC-BY-4.0
license: license (GPL-2.0-or-later, MIT, etc)
min_ansible_version: 2.9
# If this a Container Enabled role, provide the minimum Ansible Container version.
# min_ansible_container_version:
#
# Provide a list of supported platforms, and for each platform a list of versions.
# If you don't wish to enumerate all versions for a particular platform, use 'all'.
# To view available platforms and versions (or releases), visit:
# https://galaxy.ansible.com/api/v1/platforms/
#
# platforms:
# - name: Fedora
# versions:
# - all
# - 25
# - name: SomePlatform
# versions:
# - all
# - 1.0
# - 7
# - 99.99
galaxy_tags: []
# List tags for your role here, one per line. A tag is a keyword that describes
# and categorizes the role. Users find roles by searching for tags. Be sure to
# remove the '[]' above, if you add tags to this list.
#
# NOTE: A tag is limited to a single word comprised of alphanumeric characters.
# Maximum 20 tags per role.
dependencies: []
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
# if you add dependencies to this list.
@@ -0,0 +1,11 @@
---
# tasks file for db
- name: Install DB
yum:
name: "{{ db_server }}"
state: installed
notify:
- Start DB
- name: Display DB Installed
debug:
msg: "{{ db_server }}"
@@ -0,0 +1,2 @@
localhost
@@ -0,0 +1,5 @@
---
- hosts: localhost
remote_user: root
roles:
- db
@@ -0,0 +1,3 @@
---
# vars file for db
db_server: mysql
+5
View File
@@ -0,0 +1,5 @@
- hosts: web
remote_user: root
become: yes
roles:
- web
+38
View File
@@ -0,0 +1,38 @@
Role Name
=========
A brief description of the role goes here.
Requirements
------------
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
Role Variables
--------------
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
Dependencies
------------
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
Example Playbook
----------------
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
- hosts: servers
roles:
- { role: username.rolename, x: 42 }
License
-------
BSD
Author Information
------------------
An optional section for the role authors to include contact information, or a website (HTML is not allowed).
@@ -0,0 +1,2 @@
---
# defaults file for web
@@ -0,0 +1,6 @@
---
# handlers file for web
- name: Start Web
service:
name: httpd
state: started
@@ -0,0 +1,54 @@
galaxy_info:
author: your name
description: your role description
company: your company (optional)
# If the issue tracker for your role is not on github, uncomment the
# next line and provide a value
# issue_tracker_url: http://example.com/issue/tracker
# Choose a valid license ID from https://spdx.org - some suggested licenses:
# - BSD-3-Clause (default)
# - MIT
# - GPL-2.0-or-later
# - GPL-3.0-only
# - Apache-2.0
# - CC-BY-4.0
license: license (GPL-2.0-or-later, MIT, etc)
min_ansible_version: 2.9
# If this a Container Enabled role, provide the minimum Ansible Container version.
# min_ansible_container_version:
#
# Provide a list of supported platforms, and for each platform a list of versions.
# If you don't wish to enumerate all versions for a particular platform, use 'all'.
# To view available platforms and versions (or releases), visit:
# https://galaxy.ansible.com/api/v1/platforms/
#
# platforms:
# - name: Fedora
# versions:
# - all
# - 25
# - name: SomePlatform
# versions:
# - all
# - 1.0
# - 7
# - 99.99
galaxy_tags: []
# List tags for your role here, one per line. A tag is a keyword that describes
# and categorizes the role. Users find roles by searching for tags. Be sure to
# remove the '[]' above, if you add tags to this list.
#
# NOTE: A tag is limited to a single word comprised of alphanumeric characters.
# Maximum 20 tags per role.
dependencies:
- db
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
# if you add dependencies to this list.
@@ -0,0 +1,8 @@
---
# tasks file for web
- name: Install Apache
yum:
name: httpd
state: installed
notify:
- Start Web
@@ -0,0 +1,2 @@
localhost
@@ -0,0 +1,5 @@
---
- hosts: localhost
remote_user: root
roles:
- web
@@ -0,0 +1,2 @@
---
# vars file for web
+6
View File
@@ -0,0 +1,6 @@
- hosts: localhost
tasks:
- name: Delete EC2 Instance
ec2_instance:
name: "EC2 From Ansible"
state: terminated
+114
View File
@@ -0,0 +1,114 @@
# Ansible EC2 external inventory script settings
#
[ec2]
# to talk to a private eucalyptus instance uncomment these lines
# and edit edit eucalyptus_host to be the host name of your cloud controller
#eucalyptus = True
#eucalyptus_host = clc.cloud.domain.org
# AWS regions to make calls to. Set this to 'all' to make request to all regions
# in AWS and merge the results together. Alternatively, set this to a comma
# separated list of regions. E.g. 'us-east-1,us-west-1,us-west-2'
regions = eu-west-2
regions_exclude = us-gov-west-1,cn-north-1
# When generating inventory, Ansible needs to know how to address a server.
# Each EC2 instance has a lot of variables associated with it. Here is the list:
# http://docs.pythonboto.org/en/latest/ref/ec2.html#module-boto.ec2.instance
# Below are 2 variables that are used as the address of a server:
# - destination_variable
# - vpc_destination_variable
# This is the normal destination variable to use. If you are running Ansible
# from outside EC2, then 'public_dns_name' makes the most sense. If you are
# running Ansible from within EC2, then perhaps you want to use the internal
# address, and should set this to 'private_dns_name'. The key of an EC2 tag
# may optionally be used; however the boto instance variables hold precedence
# in the event of a collision.
destination_variable = public_dns_name
# For server inside a VPC, using DNS names may not make sense. When an instance
# has 'subnet_id' set, this variable is used. If the subnet is public, setting
# this to 'ip_address' will return the public IP address. For instances in a
# private subnet, this should be set to 'private_ip_address', and Ansible must
# be run from with EC2. The key of an EC2 tag may optionally be used; however
# the boto instance variables hold precedence in the event of a collision.
vpc_destination_variable = ip_address
# To tag instances on EC2 with the resource records that point to them from
# Route53, uncomment and set 'route53' to True.
route53 = False
# To exclude RDS instances from the inventory, uncomment and set to False.
#rds = False
# Additionally, you can specify the list of zones to exclude looking up in
# 'route53_excluded_zones' as a comma-separated list.
# route53_excluded_zones = samplezone1.com, samplezone2.com
# By default, only EC2 instances in the 'running' state are returned. Set
# 'all_instances' to True to return all instances regardless of state.
all_instances = False
# By default, only RDS instances in the 'available' state are returned. Set
# 'all_rds_instances' to True return all RDS instances regardless of state.
all_rds_instances = False
# API calls to EC2 are slow. For this reason, we cache the results of an API
# call. Set this to the path you want cache files to be written to. Two files
# will be written to this directory:
# - ansible-ec2.cache
# - ansible-ec2.index
cache_path = ~/.ansible/tmp
# The number of seconds a cache file is considered valid. After this many
# seconds, a new API call will be made, and the cache file will be updated.
# To disable the cache, set this value to 0
cache_max_age = 300
# Organize groups into a nested/hierarchy instead of a flat namespace.
nested_groups = False
# The EC2 inventory output can become very large. To manage its size,
# configure which groups should be created.
group_by_instance_id = False
group_by_region = True
group_by_availability_zone = False
group_by_ami_id = False
group_by_instance_type = True
group_by_key_pair = False
group_by_vpc_id = True
group_by_security_group = False
group_by_tag_keys = False
group_by_tag_none = False
group_by_route53_names = False
group_by_rds_engine = False
group_by_rds_parameter_group = False
# If you only want to include hosts that match a certain regular expression
# pattern_include = stage-*
# If you want to exclude any hosts that match a certain regular expression
# pattern_exclude = stage-*
# Instance filters can be used to control which instances are retrieved for
# inventory. For the full list of possible filters, please read the EC2 API
# docs: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInstances.html#query-DescribeInstances-filters
# Filters are key/value pairs separated by '=', to list multiple filters use
# a list separated by commas. See examples below.
# Retrieve only instances with (key=value) env=stage tag
# instance_filters = tag:env=stage
# Retrieve only instances with role=webservers OR role=dbservers tag
# instance_filters = tag:role=webservers,tag:role=dbservers
# Retrieve only t1.micro instances OR instances with tag env=stage
# instance_filters = instance-type=t1.micro,tag:env=stage
# You can use wildcards in filter values also. Below will list instances which
# tag Name value matches webservers1*
# (ex. webservers15, webservers1a, webservers123 etc)
# instance_filters = tag:Name=webservers1*
+803
View File
@@ -0,0 +1,803 @@
#!/usr/bin/env python
'''
EC2 external inventory script
=================================
Generates inventory that Ansible can understand by making API request to
AWS EC2 using the Boto library.
NOTE: This script assumes Ansible is being executed where the environment
variables needed for Boto have already been set:
export AWS_ACCESS_KEY_ID='AK123'
export AWS_SECRET_ACCESS_KEY='abc123'
This script also assumes there is an ec2.ini file alongside it. To specify a
different path to ec2.ini, define the EC2_INI_PATH environment variable:
export EC2_INI_PATH=/path/to/my_ec2.ini
If you're using eucalyptus you need to set the above variables and
you need to define:
export EC2_URL=http://hostname_of_your_cc:port/services/Eucalyptus
For more details, see: http://docs.pythonboto.org/en/latest/boto_config_tut.html
When run against a specific host, this script returns the following variables:
- ec2_ami_launch_index
- ec2_architecture
- ec2_association
- ec2_attachTime
- ec2_attachment
- ec2_attachmentId
- ec2_client_token
- ec2_deleteOnTermination
- ec2_description
- ec2_deviceIndex
- ec2_dns_name
- ec2_eventsSet
- ec2_group_name
- ec2_hypervisor
- ec2_id
- ec2_image_id
- ec2_instanceState
- ec2_instance_type
- ec2_ipOwnerId
- ec2_ip_address
- ec2_item
- ec2_kernel
- ec2_key_name
- ec2_launch_time
- ec2_monitored
- ec2_monitoring
- ec2_networkInterfaceId
- ec2_ownerId
- ec2_persistent
- ec2_placement
- ec2_platform
- ec2_previous_state
- ec2_private_dns_name
- ec2_private_ip_address
- ec2_publicIp
- ec2_public_dns_name
- ec2_ramdisk
- ec2_reason
- ec2_region
- ec2_requester_id
- ec2_root_device_name
- ec2_root_device_type
- ec2_security_group_ids
- ec2_security_group_names
- ec2_shutdown_state
- ec2_sourceDestCheck
- ec2_spot_instance_request_id
- ec2_state
- ec2_state_code
- ec2_state_reason
- ec2_status
- ec2_subnet_id
- ec2_tenancy
- ec2_virtualization_type
- ec2_vpc_id
These variables are pulled out of a boto.ec2.instance object. There is a lack of
consistency with variable spellings (camelCase and underscores) since this
just loops through all variables the object exposes. It is preferred to use the
ones with underscores when multiple exist.
In addition, if an instance has AWS Tags associated with it, each tag is a new
variable named:
- ec2_tag_[Key] = [Value]
Security groups are comma-separated in 'ec2_security_group_ids' and
'ec2_security_group_names'.
'''
# (c) 2012, Peter Sankauskas
#
# This file is part of Ansible,
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
######################################################################
import sys
import os
import argparse
import re
from time import time
import boto
from boto import ec2
from boto import rds
from boto import route53
import ConfigParser
from collections import defaultdict
try:
import json
except ImportError:
import simplejson as json
class Ec2Inventory(object):
def _empty_inventory(self):
return {"_meta" : {"hostvars" : {}}}
def __init__(self):
''' Main execution path '''
# Inventory grouped by instance IDs, tags, security groups, regions,
# and availability zones
self.inventory = self._empty_inventory()
# Index of hostname (address) to instance ID
self.index = {}
# Read settings and parse CLI arguments
self.read_settings()
self.parse_cli_args()
# Cache
if self.args.refresh_cache:
self.do_api_calls_update_cache()
elif not self.is_cache_valid():
self.do_api_calls_update_cache()
# Data to print
if self.args.host:
data_to_print = self.get_host_info()
elif self.args.list:
# Display list of instances for inventory
if self.inventory == self._empty_inventory():
data_to_print = self.get_inventory_from_cache()
else:
data_to_print = self.json_format_dict(self.inventory, True)
print data_to_print
def is_cache_valid(self):
''' Determines if the cache files have expired, or if it is still valid '''
if os.path.isfile(self.cache_path_cache):
mod_time = os.path.getmtime(self.cache_path_cache)
current_time = time()
if (mod_time + self.cache_max_age) > current_time:
if os.path.isfile(self.cache_path_index):
return True
return False
def read_settings(self):
''' Reads the settings from the ec2.ini file '''
config = ConfigParser.SafeConfigParser()
ec2_default_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ec2.ini')
ec2_ini_path = os.environ.get('EC2_INI_PATH', ec2_default_ini_path)
config.read(ec2_ini_path)
# is eucalyptus?
self.eucalyptus_host = None
self.eucalyptus = False
if config.has_option('ec2', 'eucalyptus'):
self.eucalyptus = config.getboolean('ec2', 'eucalyptus')
if self.eucalyptus and config.has_option('ec2', 'eucalyptus_host'):
self.eucalyptus_host = config.get('ec2', 'eucalyptus_host')
# Regions
self.regions = []
configRegions = config.get('ec2', 'regions')
configRegions_exclude = config.get('ec2', 'regions_exclude')
if (configRegions == 'all'):
if self.eucalyptus_host:
self.regions.append(boto.connect_euca(host=self.eucalyptus_host).region.name)
else:
for regionInfo in ec2.regions():
if regionInfo.name not in configRegions_exclude:
self.regions.append(regionInfo.name)
else:
self.regions = configRegions.split(",")
# Destination addresses
self.destination_variable = config.get('ec2', 'destination_variable')
self.vpc_destination_variable = config.get('ec2', 'vpc_destination_variable')
# Route53
self.route53_enabled = config.getboolean('ec2', 'route53')
self.route53_excluded_zones = []
if config.has_option('ec2', 'route53_excluded_zones'):
self.route53_excluded_zones.extend(
config.get('ec2', 'route53_excluded_zones', '').split(','))
# Include RDS instances?
self.rds_enabled = True
if config.has_option('ec2', 'rds'):
self.rds_enabled = config.getboolean('ec2', 'rds')
# Return all EC2 and RDS instances (if RDS is enabled)
if config.has_option('ec2', 'all_instances'):
self.all_instances = config.getboolean('ec2', 'all_instances')
else:
self.all_instances = False
if config.has_option('ec2', 'all_rds_instances') and self.rds_enabled:
self.all_rds_instances = config.getboolean('ec2', 'all_rds_instances')
else:
self.all_rds_instances = False
# Cache related
cache_dir = os.path.expanduser(config.get('ec2', 'cache_path'))
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
self.cache_path_cache = cache_dir + "/ansible-ec2.cache"
self.cache_path_index = cache_dir + "/ansible-ec2.index"
self.cache_max_age = config.getint('ec2', 'cache_max_age')
# Configure nested groups instead of flat namespace.
if config.has_option('ec2', 'nested_groups'):
self.nested_groups = config.getboolean('ec2', 'nested_groups')
else:
self.nested_groups = False
# Configure which groups should be created.
group_by_options = [
'group_by_instance_id',
'group_by_region',
'group_by_availability_zone',
'group_by_ami_id',
'group_by_instance_type',
'group_by_key_pair',
'group_by_vpc_id',
'group_by_security_group',
'group_by_tag_keys',
'group_by_tag_none',
'group_by_route53_names',
'group_by_rds_engine',
'group_by_rds_parameter_group',
]
for option in group_by_options:
if config.has_option('ec2', option):
setattr(self, option, config.getboolean('ec2', option))
else:
setattr(self, option, True)
# Do we need to just include hosts that match a pattern?
try:
pattern_include = config.get('ec2', 'pattern_include')
if pattern_include and len(pattern_include) > 0:
self.pattern_include = re.compile(pattern_include)
else:
self.pattern_include = None
except ConfigParser.NoOptionError, e:
self.pattern_include = None
# Do we need to exclude hosts that match a pattern?
try:
pattern_exclude = config.get('ec2', 'pattern_exclude');
if pattern_exclude and len(pattern_exclude) > 0:
self.pattern_exclude = re.compile(pattern_exclude)
else:
self.pattern_exclude = None
except ConfigParser.NoOptionError, e:
self.pattern_exclude = None
# Instance filters (see boto and EC2 API docs). Ignore invalid filters.
self.ec2_instance_filters = defaultdict(list)
if config.has_option('ec2', 'instance_filters'):
for instance_filter in config.get('ec2', 'instance_filters', '').split(','):
instance_filter = instance_filter.strip()
if not instance_filter or '=' not in instance_filter:
continue
filter_key, filter_value = [x.strip() for x in instance_filter.split('=', 1)]
if not filter_key:
continue
self.ec2_instance_filters[filter_key].append(filter_value)
def parse_cli_args(self):
''' Command line argument processing '''
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on EC2')
parser.add_argument('--list', action='store_true', default=True,
help='List instances (default: True)')
parser.add_argument('--host', action='store',
help='Get all the variables about a specific instance')
parser.add_argument('--refresh-cache', action='store_true', default=False,
help='Force refresh of cache by making API requests to EC2 (default: False - use cache files)')
self.args = parser.parse_args()
def do_api_calls_update_cache(self):
''' Do API calls to each region, and save data in cache files '''
if self.route53_enabled:
self.get_route53_records()
for region in self.regions:
self.get_instances_by_region(region)
if self.rds_enabled:
self.get_rds_instances_by_region(region)
self.write_to_cache(self.inventory, self.cache_path_cache)
self.write_to_cache(self.index, self.cache_path_index)
def connect(self, region):
''' create connection to api server'''
if self.eucalyptus:
conn = boto.connect_euca(host=self.eucalyptus_host)
conn.APIVersion = '2010-08-31'
else:
conn = ec2.connect_to_region(region)
# connect_to_region will fail "silently" by returning None if the region name is wrong or not supported
if conn is None:
self.fail_with_error("region name: %s likely not supported, or AWS is down. connection to region failed." % region)
return conn
def get_instances_by_region(self, region):
''' Makes an AWS EC2 API call to the list of instances in a particular
region '''
try:
conn = self.connect(region)
reservations = []
if self.ec2_instance_filters:
for filter_key, filter_values in self.ec2_instance_filters.iteritems():
reservations.extend(conn.get_all_instances(filters = { filter_key : filter_values }))
else:
reservations = conn.get_all_instances()
for reservation in reservations:
for instance in reservation.instances:
self.add_instance(instance, region)
except boto.exception.BotoServerError, e:
if e.error_code == 'AuthFailure':
error = self.get_auth_error_message()
else:
backend = 'Eucalyptus' if self.eucalyptus else 'AWS'
error = "Error connecting to %s backend.\n%s" % (backend, e.message)
self.fail_with_error(error)
def get_rds_instances_by_region(self, region):
''' Makes an AWS API call to the list of RDS instances in a particular
region '''
try:
conn = rds.connect_to_region(region)
if conn:
instances = conn.get_all_dbinstances()
for instance in instances:
self.add_rds_instance(instance, region)
except boto.exception.BotoServerError, e:
error = e.message
if e.error_code == 'AuthFailure':
error = self.get_auth_error_message()
if not e.reason == "Forbidden":
error = "Looks like AWS RDS is down:\n%s" % e.message
self.fail_with_error(error)
def get_auth_error_message(self):
''' create an informative error message if there is an issue authenticating'''
errors = ["Authentication error retrieving ec2 inventory."]
if None in [os.environ.get('AWS_ACCESS_KEY_ID'), os.environ.get('AWS_SECRET_ACCESS_KEY')]:
errors.append(' - No AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY environment vars found')
else:
errors.append(' - AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment vars found but may not be correct')
boto_paths = ['/etc/boto.cfg', '~/.boto', '~/.aws/credentials']
boto_config_found = list(p for p in boto_paths if os.path.isfile(os.path.expanduser(p)))
if len(boto_config_found) > 0:
errors.append(" - Boto configs found at '%s', but the credentials contained may not be correct" % ', '.join(boto_config_found))
else:
errors.append(" - No Boto config found at any expected location '%s'" % ', '.join(boto_paths))
return '\n'.join(errors)
def fail_with_error(self, err_msg):
'''log an error to std err for ansible-playbook to consume and exit'''
sys.stderr.write(err_msg)
sys.exit(1)
def get_instance(self, region, instance_id):
conn = self.connect(region)
reservations = conn.get_all_instances([instance_id])
for reservation in reservations:
for instance in reservation.instances:
return instance
def add_instance(self, instance, region):
''' Adds an instance to the inventory and index, as long as it is
addressable '''
# Only want running instances unless all_instances is True
if not self.all_instances and instance.state != 'running':
return
# Select the best destination address
if instance.subnet_id:
dest = getattr(instance, self.vpc_destination_variable, None)
if dest is None:
dest = getattr(instance, 'tags').get(self.vpc_destination_variable, None)
else:
dest = getattr(instance, self.destination_variable, None)
if dest is None:
dest = getattr(instance, 'tags').get(self.destination_variable, None)
if not dest:
# Skip instances we cannot address (e.g. private VPC subnet)
return
# if we only want to include hosts that match a pattern, skip those that don't
if self.pattern_include and not self.pattern_include.match(dest):
return
# if we need to exclude hosts that match a pattern, skip those
if self.pattern_exclude and self.pattern_exclude.match(dest):
return
# Add to index
self.index[dest] = [region, instance.id]
# Inventory: Group by instance ID (always a group of 1)
if self.group_by_instance_id:
self.inventory[instance.id] = [dest]
if self.nested_groups:
self.push_group(self.inventory, 'instances', instance.id)
# Inventory: Group by region
if self.group_by_region:
self.push(self.inventory, region, dest)
if self.nested_groups:
self.push_group(self.inventory, 'regions', region)
# Inventory: Group by availability zone
if self.group_by_availability_zone:
self.push(self.inventory, instance.placement, dest)
if self.nested_groups:
if self.group_by_region:
self.push_group(self.inventory, region, instance.placement)
self.push_group(self.inventory, 'zones', instance.placement)
# Inventory: Group by Amazon Machine Image (AMI) ID
if self.group_by_ami_id:
ami_id = self.to_safe(instance.image_id)
self.push(self.inventory, ami_id, dest)
if self.nested_groups:
self.push_group(self.inventory, 'images', ami_id)
# Inventory: Group by instance type
if self.group_by_instance_type:
type_name = self.to_safe('type_' + instance.instance_type)
self.push(self.inventory, type_name, dest)
if self.nested_groups:
self.push_group(self.inventory, 'types', type_name)
# Inventory: Group by key pair
if self.group_by_key_pair and instance.key_name:
key_name = self.to_safe('key_' + instance.key_name)
self.push(self.inventory, key_name, dest)
if self.nested_groups:
self.push_group(self.inventory, 'keys', key_name)
# Inventory: Group by VPC
if self.group_by_vpc_id and instance.vpc_id:
vpc_id_name = self.to_safe('vpc_id_' + instance.vpc_id)
self.push(self.inventory, vpc_id_name, dest)
if self.nested_groups:
self.push_group(self.inventory, 'vpcs', vpc_id_name)
# Inventory: Group by security group
if self.group_by_security_group:
try:
for group in instance.groups:
key = self.to_safe("security_group_" + group.name)
self.push(self.inventory, key, dest)
if self.nested_groups:
self.push_group(self.inventory, 'security_groups', key)
except AttributeError:
self.fail_with_error('\n'.join(['Package boto seems a bit older.',
'Please upgrade boto >= 2.3.0.']))
# Inventory: Group by tag keys
if self.group_by_tag_keys:
for k, v in instance.tags.iteritems():
key = self.to_safe("tag_" + k + "=" + v)
self.push(self.inventory, key, dest)
if self.nested_groups:
self.push_group(self.inventory, 'tags', self.to_safe("tag_" + k))
self.push_group(self.inventory, self.to_safe("tag_" + k), key)
# Inventory: Group by Route53 domain names if enabled
if self.route53_enabled and self.group_by_route53_names:
route53_names = self.get_instance_route53_names(instance)
for name in route53_names:
self.push(self.inventory, name, dest)
if self.nested_groups:
self.push_group(self.inventory, 'route53', name)
# Global Tag: instances without tags
if self.group_by_tag_none and len(instance.tags) == 0:
self.push(self.inventory, 'tag_none', dest)
if self.nested_groups:
self.push_group(self.inventory, 'tags', 'tag_none')
# Global Tag: tag all EC2 instances
self.push(self.inventory, 'ec2', dest)
self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance)
def add_rds_instance(self, instance, region):
''' Adds an RDS instance to the inventory and index, as long as it is
addressable '''
# Only want available instances unless all_rds_instances is True
if not self.all_rds_instances and instance.status != 'available':
return
# Select the best destination address
dest = instance.endpoint[0]
if not dest:
# Skip instances we cannot address (e.g. private VPC subnet)
return
# Add to index
self.index[dest] = [region, instance.id]
# Inventory: Group by instance ID (always a group of 1)
if self.group_by_instance_id:
self.inventory[instance.id] = [dest]
if self.nested_groups:
self.push_group(self.inventory, 'instances', instance.id)
# Inventory: Group by region
if self.group_by_region:
self.push(self.inventory, region, dest)
if self.nested_groups:
self.push_group(self.inventory, 'regions', region)
# Inventory: Group by availability zone
if self.group_by_availability_zone:
self.push(self.inventory, instance.availability_zone, dest)
if self.nested_groups:
if self.group_by_region:
self.push_group(self.inventory, region, instance.availability_zone)
self.push_group(self.inventory, 'zones', instance.availability_zone)
# Inventory: Group by instance type
if self.group_by_instance_type:
type_name = self.to_safe('type_' + instance.instance_class)
self.push(self.inventory, type_name, dest)
if self.nested_groups:
self.push_group(self.inventory, 'types', type_name)
# Inventory: Group by VPC
if self.group_by_vpc_id and instance.subnet_group and instance.subnet_group.vpc_id:
vpc_id_name = self.to_safe('vpc_id_' + instance.subnet_group.vpc_id)
self.push(self.inventory, vpc_id_name, dest)
if self.nested_groups:
self.push_group(self.inventory, 'vpcs', vpc_id_name)
# Inventory: Group by security group
if self.group_by_security_group:
try:
if instance.security_group:
key = self.to_safe("security_group_" + instance.security_group.name)
self.push(self.inventory, key, dest)
if self.nested_groups:
self.push_group(self.inventory, 'security_groups', key)
except AttributeError:
self.fail_with_error('\n'.join(['Package boto seems a bit older.',
'Please upgrade boto >= 2.3.0.']))
# Inventory: Group by engine
if self.group_by_rds_engine:
self.push(self.inventory, self.to_safe("rds_" + instance.engine), dest)
if self.nested_groups:
self.push_group(self.inventory, 'rds_engines', self.to_safe("rds_" + instance.engine))
# Inventory: Group by parameter group
if self.group_by_rds_parameter_group:
self.push(self.inventory, self.to_safe("rds_parameter_group_" + instance.parameter_group.name), dest)
if self.nested_groups:
self.push_group(self.inventory, 'rds_parameter_groups', self.to_safe("rds_parameter_group_" + instance.parameter_group.name))
# Global Tag: all RDS instances
self.push(self.inventory, 'rds', dest)
self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance)
def get_route53_records(self):
''' Get and store the map of resource records to domain names that
point to them. '''
r53_conn = route53.Route53Connection()
all_zones = r53_conn.get_zones()
route53_zones = [ zone for zone in all_zones if zone.name[:-1]
not in self.route53_excluded_zones ]
self.route53_records = {}
for zone in route53_zones:
rrsets = r53_conn.get_all_rrsets(zone.id)
for record_set in rrsets:
record_name = record_set.name
if record_name.endswith('.'):
record_name = record_name[:-1]
for resource in record_set.resource_records:
self.route53_records.setdefault(resource, set())
self.route53_records[resource].add(record_name)
def get_instance_route53_names(self, instance):
''' Check if an instance is referenced in the records we have from
Route53. If it is, return the list of domain names pointing to said
instance. If nothing points to it, return an empty list. '''
instance_attributes = [ 'public_dns_name', 'private_dns_name',
'ip_address', 'private_ip_address' ]
name_list = set()
for attrib in instance_attributes:
try:
value = getattr(instance, attrib)
except AttributeError:
continue
if value in self.route53_records:
name_list.update(self.route53_records[value])
return list(name_list)
def get_host_info_dict_from_instance(self, instance):
instance_vars = {}
for key in vars(instance):
value = getattr(instance, key)
key = self.to_safe('ec2_' + key)
# Handle complex types
# state/previous_state changed to properties in boto in https://github.com/boto/boto/commit/a23c379837f698212252720d2af8dec0325c9518
if key == 'ec2__state':
instance_vars['ec2_state'] = instance.state or ''
instance_vars['ec2_state_code'] = instance.state_code
elif key == 'ec2__previous_state':
instance_vars['ec2_previous_state'] = instance.previous_state or ''
instance_vars['ec2_previous_state_code'] = instance.previous_state_code
elif type(value) in [int, bool]:
instance_vars[key] = value
elif type(value) in [str, unicode]:
instance_vars[key] = value.strip()
elif type(value) == type(None):
instance_vars[key] = ''
elif key == 'ec2_region':
instance_vars[key] = value.name
elif key == 'ec2__placement':
instance_vars['ec2_placement'] = value.zone
elif key == 'ec2_tags':
for k, v in value.iteritems():
key = self.to_safe('ec2_tag_' + k)
instance_vars[key] = v
elif key == 'ec2_groups':
group_ids = []
group_names = []
for group in value:
group_ids.append(group.id)
group_names.append(group.name)
instance_vars["ec2_security_group_ids"] = ','.join([str(i) for i in group_ids])
instance_vars["ec2_security_group_names"] = ','.join([str(i) for i in group_names])
else:
pass
# TODO Product codes if someone finds them useful
#print key
#print type(value)
#print value
return instance_vars
def get_host_info(self):
''' Get variables about a specific host '''
if len(self.index) == 0:
# Need to load index from cache
self.load_index_from_cache()
if not self.args.host in self.index:
# try updating the cache
self.do_api_calls_update_cache()
if not self.args.host in self.index:
# host might not exist anymore
return self.json_format_dict({}, True)
(region, instance_id) = self.index[self.args.host]
instance = self.get_instance(region, instance_id)
return self.json_format_dict(self.get_host_info_dict_from_instance(instance), True)
def push(self, my_dict, key, element):
''' Push an element onto an array that may not have been defined in
the dict '''
group_info = my_dict.setdefault(key, [])
if isinstance(group_info, dict):
host_list = group_info.setdefault('hosts', [])
host_list.append(element)
else:
group_info.append(element)
def push_group(self, my_dict, key, element):
''' Push a group as a child of another group. '''
parent_group = my_dict.setdefault(key, {})
if not isinstance(parent_group, dict):
parent_group = my_dict[key] = {'hosts': parent_group}
child_groups = parent_group.setdefault('children', [])
if element not in child_groups:
child_groups.append(element)
def get_inventory_from_cache(self):
''' Reads the inventory from the cache file and returns it as a JSON
object '''
cache = open(self.cache_path_cache, 'r')
json_inventory = cache.read()
return json_inventory
def load_index_from_cache(self):
''' Reads the index from the cache file sets self.index '''
cache = open(self.cache_path_index, 'r')
json_index = cache.read()
self.index = json.loads(json_index)
def write_to_cache(self, data, filename):
''' Writes data in JSON format to a file '''
json_data = self.json_format_dict(data, True)
cache = open(filename, 'w')
cache.write(json_data)
cache.close()
def to_safe(self, word):
''' Converts 'bad' characters in a string to underscores so they can be
used as Ansible groups '''
return re.sub("[^A-Za-z0-9\-]", "_", word)
def json_format_dict(self, data, pretty=False):
''' Converts a dict to a JSON object and dumps it as a formatted
string '''
if pretty:
return json.dumps(data, sort_keys=True, indent=2)
else:
return json.dumps(data)
# Run the script
Ec2Inventory()
+19
View File
@@ -0,0 +1,19 @@
- hosts: ec2
remote_user: root
#become: yes
strategy: free
serial:
- "50%"
vars:
ansible_port: 22
ansible_user: ec2-user
ansible_ssh_private_key_file: /Users/Keys/ansible_lnd_key.pem
tasks:
- name: Install Apache
yum:
name: httpd
state: installed
- name: Remove Apache
yum:
name: httpd
state: removed
+14
View File
@@ -0,0 +1,14 @@
- hosts: localhost
tasks:
- name: Create EC2 Instance
ec2_instance:
name: "EC2 From Ansible"
key_name: "ansible_key"
vpc_subnet_id: subnet-33f7f248
instance_type: t2.micro
security_group: ansible-SG
network:
assign_public_ip: true
image_id: ami-0a13d44dccf1f5cf6
tags:
Environment: course
+15
View File
@@ -0,0 +1,15 @@
web.mydomain.local
[db]
db1.mydomain.local
db2.mydomain.local
db3.mydomain.local
[web]
web1.mydomain.local
web2.mydomain.local
web3.mydomain.local
[customer1:children]
db
web
+13
View File
@@ -0,0 +1,13 @@
all:
hosts:
web.mydomain.local:
children:
customer1:
children:
db:
hosts:
db1.mydomain.local:
db2.mydomain.local:
web:
hosts:
web1.mydomain.local:
+12
View File
@@ -0,0 +1,12 @@
- hosts: web
remote_user: root
become: yes
tasks:
- name: Install Apache
yum:
name: httpd
state: installed
- name: Remove Apache
yum:
name: httpd
state: removed
+9
View File
@@ -0,0 +1,9 @@
- hosts: web
remote_user: root
become: yes
tasks:
- name: Curl AWS
shell:
cmd: curl http://169.254.169.254/latest/meta-data/public-ipv4
register: curl
- debug: var=curl.stdout_lines
+40
View File
@@ -0,0 +1,40 @@
- hosts: web
remote_user: root
become: yes
tasks:
- name: Install Apache
yum:
name: httpd
state: installed
notify:
- Restart Apache
- name: Install nano
yum:
name: nano
state: installed
notify:
- Restart Apache
- name: Install MySQL
yum:
name: mysql
state: installed
notify:
- Restart Apache
- name: Remove Apache
yum:
name: httpd
state: removed
- name: Remove nano
yum:
name: nano
state: removed
- name: Remove MySQL
yum:
name: httpd
state: removed
handlers:
- name: Restart Apache
service:
name: httpd
state: restarted
+16
View File
@@ -0,0 +1,16 @@
- hosts: web
remote_user: root
become: yes
tasks:
- name: Install Apache
yum:
name: httpd
state: installed
- name: Restart Apache
service:
name: httpd
state: restarted
- name: Remove Apache
yum:
name: httpd
state: removed
@@ -0,0 +1,754 @@
{
"files": [
{
"name": ".",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": ".yamllint",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "ea9c022c870d63c602c98b0b8a974f3e6df7c8598ed50a17a6f843236e3b6c55",
"format": 1
},
{
"name": ".github",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": ".github/FUNDING.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "79668fbcce7748a95cecebf6f35cfc297d5ffb733aff93c4d82164ee8e967504",
"format": 1
},
{
"name": "molecule",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "molecule/test-manifests",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "molecule/test-manifests/converge.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "e108714d4d02df6baf5b1221b992f975467d3b8766aa5f6f614a7a2103759cfa",
"format": 1
},
{
"name": "molecule/test-manifests/molecule.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "93f518730c4d3e009541ef78757da0b10c7e3af6166540bf3d6c083a8dcafbae",
"format": 1
},
{
"name": "molecule/test-manifests/asserts.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "f8ee48d77d8128e2ed9ce3c1761511c48bc36790dea371a0d801c0b6d59070fc",
"format": 1
},
{
"name": "molecule/test-manifests/prepare.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "0b87913e75935090db08ed033d3b536889dff9532c342155ebae2bb176977d58",
"format": 1
},
{
"name": "molecule/default",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "molecule/default/converge.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "9f6663b4aaa20ab52410d1f85514de6919347bbcbbd684e121ac8ab6abac4dc4",
"format": 1
},
{
"name": "molecule/default/molecule.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "48c7705819bfd476512513c35066cf1cbbb5df689d27984c5175a5c0b2762f37",
"format": 1
},
{
"name": "molecule/default/asserts.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "d39f4ef4ff8b9d484806950abca2fbfc57a80203933fead56185f1fae02fcc2a",
"format": 1
},
{
"name": "molecule/default/prepare.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "ed92dda245eae11ed01d92b01119b9a85de9d31f48873864c9b531cf39764a7f",
"format": 1
},
{
"name": "README.md",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "516bf6f2077c7719805158531e383b84baef29a0dae4cd1afe335b17bbb491d2",
"format": 1
},
{
"name": ".gitignore",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "eae89391a86cb13a7c95f9ba1add1c34bef2952e1905a8716df48bae3c529cdf",
"format": 1
},
{
"name": "scripts",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "scripts/templates",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "scripts/templates/galaxy.yml.j2",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "145622277ffa73288bec3e68485dc2fd4a73e2fc32f39b61bd7344193dd8409b",
"format": 1
},
{
"name": "scripts/deploy.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "9c6cc713d631428a541e49859704ce170a028fe5e971ac95b7a7dcee9ff5fcbe",
"format": 1
},
{
"name": ".dpl",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": ".travis.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "3530916e685c7c8978fc70d068ad5af9014a993097e8951e1fb7771b556248c3",
"format": 1
},
{
"name": "roles",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/k8s_manifests",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/k8s_manifests/.yamllint",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "3254c2ae6aaffcb2c25db3a791e0dbcde9e2ce3a648cf49eb063e282e404f72a",
"format": 1
},
{
"name": "roles/k8s_manifests/tasks",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/k8s_manifests/tasks/deploy-manifest.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "38eeb6ef535cbe5d9d40b5c3dcb70591391cef78c547813b6b29e8751502c21b",
"format": 1
},
{
"name": "roles/k8s_manifests/tasks/main.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "0f14788ff585b03c39f03a816e1056d17437ad1e488ff8c00f20e369c1afbc1d",
"format": 1
},
{
"name": "roles/k8s_manifests/.github",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/k8s_manifests/.github/stale.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "7cc3fcc887d4929ca0421ca13bbb31d03b2c502e41a913d18158bffbee9f7bee",
"format": 1
},
{
"name": "roles/k8s_manifests/.github/FUNDING.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "79668fbcce7748a95cecebf6f35cfc297d5ffb733aff93c4d82164ee8e967504",
"format": 1
},
{
"name": "roles/k8s_manifests/molecule",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/k8s_manifests/molecule/default",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/k8s_manifests/molecule/default/converge.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "9118a5ec853b0f67824e79e6da69f37e817c0fe96cb06a618792ad7052b3e69a",
"format": 1
},
{
"name": "roles/k8s_manifests/molecule/default/molecule.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "02cf56b0d7038605e8524ca6963311dfb52b8b6cb6e85133bc9c665bb3e52fe9",
"format": 1
},
{
"name": "roles/k8s_manifests/README.md",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "60834f55e33f10414af1c0d0ff262634121808f058cecad65bc4acca3dbd7b91",
"format": 1
},
{
"name": "roles/k8s_manifests/k8s-manifests",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/k8s_manifests/k8s-manifests/README.md",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "3a16f73e75b85324a7ae61e584c89babdb9d1116b038e10470296fd6d19f1247",
"format": 1
},
{
"name": "roles/k8s_manifests/k8s-manifests/storageclass",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/k8s_manifests/k8s-manifests/storageclass/vars.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "b6f0c3ca0ab9ecb572cbc3d372a5054bcc854866386ea79f70faeb3340784be0",
"format": 1
},
{
"name": "roles/k8s_manifests/k8s-manifests/storageclass/manifest.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "1060421ceeef207051439bf6cf7bb6ca739c44e2b9b293398f938eefd46b5464",
"format": 1
},
{
"name": "roles/k8s_manifests/defaults",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/k8s_manifests/defaults/main.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "e7eb524eab7fcde17f85a72415c4a7ce27a02ba30a4abb7fde9d94ceffbba67e",
"format": 1
},
{
"name": "roles/k8s_manifests/.travis.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "082e60a441c570863a113532af45ac8a44312c6b29601f30d4162809b3945821",
"format": 1
},
{
"name": "roles/k8s_manifests/.git",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "c3c3193400f2b187b0088935e84666b345513e74ace518ffc3e2a34bc939d759",
"format": 1
},
{
"name": "roles/k8s_manifests/LICENSE",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "18ac8c568f83a3420a1464ac6bb781fdf8af36a4028cbc2646a7cc517c6250ed",
"format": 1
},
{
"name": "roles/k8s_manifests/meta",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/k8s_manifests/meta/main.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "ed967f1680dc478e8343cd7042920c738f89fa6be4d6e3b8d59422232945d6fe",
"format": 1
},
{
"name": "roles/kubernetes",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/kubernetes/.yamllint",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "46f7d2d60930cbc676554897e84a5a721eee195add39a821aa7f5ab9601369ee",
"format": 1
},
{
"name": "roles/kubernetes/tasks",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/kubernetes/tasks/kubelet-setup.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "45fd7d2a5c6a6798cb2423dab4616bd4c76e4e99d84f772bad629665f859cfc8",
"format": 1
},
{
"name": "roles/kubernetes/tasks/setup-RedHat.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "84cc021fcdda8aa7b6d1dab88da4914f1428383d3b7847eb42e9a64dea671589",
"format": 1
},
{
"name": "roles/kubernetes/tasks/setup-Debian.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "6610b5d244d40c0c637e2cfdf310eb10f24975f8fc83d47975e9535e3b020a11",
"format": 1
},
{
"name": "roles/kubernetes/tasks/main.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "5d2790d1ba2c6c8b777297c44899113e10104c3e127ab0cf9f2b2f6f2ccf3040",
"format": 1
},
{
"name": "roles/kubernetes/tasks/node-setup.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "e500e50dae7e909002c5e8f8570272057b544bedd18c22cf2e31c4513141ae75",
"format": 1
},
{
"name": "roles/kubernetes/tasks/master-setup.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "7b653639e38efa1fd0922869506bf593ba4be3b708070d1081c8252c5735dcb6",
"format": 1
},
{
"name": "roles/kubernetes/.github",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/kubernetes/.github/stale.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "7cc3fcc887d4929ca0421ca13bbb31d03b2c502e41a913d18158bffbee9f7bee",
"format": 1
},
{
"name": "roles/kubernetes/.github/FUNDING.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "79668fbcce7748a95cecebf6f35cfc297d5ffb733aff93c4d82164ee8e967504",
"format": 1
},
{
"name": "roles/kubernetes/handlers",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/kubernetes/handlers/main.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "d521381661812b6a286567ec3f1907072c41c040ccdfb02fe2bec7110ad36d44",
"format": 1
},
{
"name": "roles/kubernetes/molecule",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/kubernetes/molecule/default",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/kubernetes/molecule/default/playbook-calico.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "c7a8a2a38beab1dad60d2cc49c884484fb0c9e8c6ce2c4cf5c3bb25fca009527",
"format": 1
},
{
"name": "roles/kubernetes/molecule/default/converge.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "455e49ba404360e9fdcd2cfdf02d7e0046010d094f639e4ec8f95afc13e062d5",
"format": 1
},
{
"name": "roles/kubernetes/molecule/default/molecule.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "e0b7388d112efaf8834c3b3e6bd93ec61dbcd690efccfa271e829f2b21d628a8",
"format": 1
},
{
"name": "roles/kubernetes/molecule/default/requirements.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "cd060eef08692731d163e15650ee3188c238bfb4362f526d58a92c3767f1b311",
"format": 1
},
{
"name": "roles/kubernetes/README.md",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "bf0bbf49d913ba83accc379d54e060edb2cf688831538ce22a8324bcb9e044e0",
"format": 1
},
{
"name": "roles/kubernetes/templates",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/kubernetes/templates/apt-preferences-kubernetes.j2",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "df32074640b46025477657d3321e2d8e3054cfbe1db2c95d282dbc959f6bb130",
"format": 1
},
{
"name": "roles/kubernetes/.gitignore",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "515a0e077dd4e6f58a53b676d5f30c91321197f1f8de671d5ef74760abcbd1a3",
"format": 1
},
{
"name": "roles/kubernetes/defaults",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/kubernetes/defaults/main.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "6d3a3e5393eb66a7b5467ec6a8ea819e4aa72b8218bbe9d7bb7a5f6c9ba23bf0",
"format": 1
},
{
"name": "roles/kubernetes/.travis.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "4c5c9f8166ac0baf468c006c9c281ed58b43e74f649d1284f57353f8bb85ee49",
"format": 1
},
{
"name": "roles/kubernetes/vars",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/kubernetes/vars/Debian.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "bcd0e265e5a81a86b358a725f121026081dbe975b0765acafeb4c824e1e2e574",
"format": 1
},
{
"name": "roles/kubernetes/vars/RedHat.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "1a99231a3d393ab1f4d96375a3e98f249cd30f45f0866db035234c027b3e6526",
"format": 1
},
{
"name": "roles/kubernetes/.git",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "bc1bdb45dc76cb8f8f5ed1ee114b1ab221948a2de2877cf9147d282854863b03",
"format": 1
},
{
"name": "roles/kubernetes/LICENSE",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "18ac8c568f83a3420a1464ac6bb781fdf8af36a4028cbc2646a7cc517c6250ed",
"format": 1
},
{
"name": "roles/kubernetes/.ansible-lint",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "6f10e1d56628fc16fef6b0d982050aa86be30ef8daa64db76d1328ced3526b11",
"format": 1
},
{
"name": "roles/kubernetes/meta",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/kubernetes/meta/main.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "09bbd4f8d280c8dc3d3141ea6e1d93a2182e35d4c91d95904345d965e6ac8b06",
"format": 1
},
{
"name": "roles/helm",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/helm/.yamllint",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "35f4560713b00048070a781811c50a7d3c207dada2dc98e44e182ee550517f9f",
"format": 1
},
{
"name": "roles/helm/tasks",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/helm/tasks/main.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "af9fbaba952b120c6e2f1d7973252aeb8fd4a07da1b30ae90d69c8c2a76241da",
"format": 1
},
{
"name": "roles/helm/.github",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/helm/.github/stale.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "7cc3fcc887d4929ca0421ca13bbb31d03b2c502e41a913d18158bffbee9f7bee",
"format": 1
},
{
"name": "roles/helm/.github/FUNDING.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "79668fbcce7748a95cecebf6f35cfc297d5ffb733aff93c4d82164ee8e967504",
"format": 1
},
{
"name": "roles/helm/molecule",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/helm/molecule/default",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/helm/molecule/default/converge.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "2075e9724a3afa9bf029941876c9aaedb378e4b36d14adde90071da8e4e7f0c7",
"format": 1
},
{
"name": "roles/helm/molecule/default/molecule.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "505b9f83556e6a40ceb9255cc041e7232da5f3ff5ed421a7826c4780f180c6a5",
"format": 1
},
{
"name": "roles/helm/molecule/default/verify.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "7dcca61dd24e4a806c423aa110efd47db2470c0a28f25e585ef6687307563c44",
"format": 1
},
{
"name": "roles/helm/README.md",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "59d20a88e78f80749c4913b3e5a34b7cc4e8422dbfec2b6c4b59610a3849dec9",
"format": 1
},
{
"name": "roles/helm/.gitignore",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "515a0e077dd4e6f58a53b676d5f30c91321197f1f8de671d5ef74760abcbd1a3",
"format": 1
},
{
"name": "roles/helm/defaults",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/helm/defaults/main.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "5ddea14d078f069c2964277b198ca65d072f178f99387c8f2f92b2b63bff75f0",
"format": 1
},
{
"name": "roles/helm/.travis.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "781012541239ba0ffb019ac2fbf36b9cc694f4bb7aa738091e0d34148be7bc99",
"format": 1
},
{
"name": "roles/helm/.git",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "a6c7e74772634b52664bf576ad99af7a428b86208340038c59fc684dd9cfe53d",
"format": 1
},
{
"name": "roles/helm/LICENSE",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "7fd504840df9d5e3e34c2e2b2c6c471df010ae723bb2527b6cbe40dd3b2dfdfd",
"format": 1
},
{
"name": "roles/helm/meta",
"ftype": "dir",
"chksum_type": null,
"chksum_sha256": null,
"format": 1
},
{
"name": "roles/helm/meta/main.yml",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "7061885e5eafc225f6bccea3093ac8929137999ee8aa5d5be4cbc8415fa6bf77",
"format": 1
},
{
"name": ".ansible-lint",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "46531e6d2938566e8295b7fdc2cb610e286d0ec7a6eed795ea6d815b95e23bdf",
"format": 1
},
{
"name": ".gitmodules",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "06c5d51f914f1cd344001cea0071a8a8c3e3fe7cf0a5239d7333d5db944eec7c",
"format": 1
}
],
"format": 1
}
@@ -0,0 +1,47 @@
{
"collection_info": {
"namespace": "geerlingguy",
"name": "k8s",
"version": "0.10.0",
"authors": [
"geerlingguy (https://www.jeffgeerling.com/)"
],
"readme": "README.md",
"tags": [
"kubernetes",
"k8s",
"infrastructure",
"cloud",
"containers",
"eks",
"aks",
"gke",
"digitalocean",
"docker",
"debian",
"redhat",
"ubuntu",
"amazon",
"linux",
"native"
],
"description": "An elegant collection of Kuberenetes automation tools.",
"license": [
"MIT"
],
"license_file": null,
"dependencies": {},
"repository": "https://github.com/geerlingguy/ansible-collection-k8s",
"documentation": "https://github.com/geerlingguy/ansible-collection-k8s",
"homepage": null,
"issues": "https://github.com/geerlingguy/ansible-collection-k8s/issues"
},
"file_manifest_file": {
"name": "FILES.json",
"ftype": "file",
"chksum_type": "sha256",
"chksum_sha256": "a58316b86ac6df72143ed495141cc7bbe5d3cb61df99b08effe87bed366ef9df",
"format": 1
},
"format": 1
}
@@ -0,0 +1,70 @@
# Kubernetes Collection for Ansible
[![Build Status](https://travis-ci.com/geerlingguy/ansible-collection-k8s.svg?branch=master)](https://travis-ci.com/geerlingguy/ansible-collection-k8s)
This collection contains Kubernetes-related roles and Ansible plugins and modules maintained by Jeff Geerling (geerlingguy).
It includes:
- [geerlingguy.kubernetes](https://github.com/geerlingguy/ansible-role-kubernetes)
- [geerlingguy.k8s_manifests](https://github.com/geerlingguy/ansible-role-k8s_manifests)
- [geerlingguy.helm](https://github.com/geerlingguy/ansible-role-helm)
## Usage
Install this collection locally:
ansible-galaxy collection install geerlingguy.k8s -p ./collections
Then you can use the roles from the collection in your playbooks:
---
- hosts: all
collections:
- geerlingguy.k8s
roles:
- kubernetes
- helm
- role: k8s_manifests
vars:
k8s_manifests_base_dir: ''
k8s_manifests:
- monitoring/prometheus
- dir: docker-registry
namespace: registry
> If you want to be more explicit, you can use the fully-qualified role name when referring to a role in this collection, like `geerlingguy.k8s.kubernetes` instead of just `kubernetes`. This could be helpful if, for example, you maintain a separate `kubernetes` role in another place on your local workstation.
## Development
Currently, all the Kubernetes roles (inside `roles/`) are Git submodules, and work on the roles themselves should take place in the upstream Role repository. At some point, the roles might move into this repository for their canonical home.
This collection has some integration tests (inside `molecule/`), however, which pull all the roles together and ensure they work in tandem on the latest supported platforms.
To run a particular test scenario, you need to install molecule and the OpenShift Python client (`pip install molecule openshift`), then you can run:
molecule test -s [scenario]
For example, to run the k8s_manifests role test scenario:
molecule test -s test-manifests
If you would like to develop the collection or leave a scenario running for debugging, use `molecule converge` to run the scenario but not tear down the local test environment.
### Pushing a new version
Before tagging a new version, make sure all the git submodules are up to date:
git submodule update --recursive --remote
Then commit and push all changes, and make sure all tests are passing.
Then tag the new version of the collection and push the tag.
Once pushed, if tests pass, Travis CI will deploy the new collection version using the playbook in `scripts/deploy.yml`. That directory also contains the `galaxy.yml` template that will be used to build the collection metadata.
## Author
This collection was created in 2019 by [Jeff Geerling](https://www.jeffgeerling.com/), author of [Ansible for DevOps](https://www.ansiblefordevops.com/) and [Ansible for Kubernetes](https://www.ansibleforkubernetes.com).
@@ -0,0 +1,14 @@
---
- name: Verify
hosts: k8s
gather_facts: false
vars:
ansible_python_interpreter: '{{ ansible_playbook_python }}'
deploy_dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/deploy"
custom_resource: "{{ lookup('file', '/'.join([deploy_dir, 'crds/drupal_v1alpha1_drupal_cr.yaml'])) | from_yaml }}"
tasks:
- name: Don't do much here.
debug:
msg: "Not implemented at this time."
@@ -0,0 +1,13 @@
---
- name: Converge
hosts: localhost
connection: local
vars:
ansible_python_interpreter: '{{ ansible_playbook_python }}'
roles:
- helm
- k8s_manifests
- import_playbook: '{{ playbook_dir }}/asserts.yml'
@@ -0,0 +1,34 @@
---
dependency:
name: galaxy
driver:
name: docker
lint: |
set -e
yamllint .
ansible-lint
platforms:
- name: kind-default
groups:
- k8s
image: bsycorp/kind:latest-1.15
privileged: True
override_command: no
exposed_ports:
- 8443/tcp
- 10080/tcp
published_ports:
- 0.0.0.0:${TEST_CLUSTER_PORT:-9443}:8443/tcp
pre_build_image: yes
provisioner:
name: ansible
log: True
inventory:
group_vars:
all:
test_namespace: ${TEST_NAMESPACE:-default}
env:
K8S_AUTH_KUBECONFIG: /tmp/molecule/kind-default/kubeconfig
KUBECONFIG: /tmp/molecule/kind-default/kubeconfig
ANSIBLE_ROLES_PATH: ${MOLECULE_PROJECT_DIRECTORY}/roles
KIND_PORT: '${TEST_CLUSTER_PORT:-9443}'
@@ -0,0 +1,37 @@
---
- name: Prepare
hosts: k8s
gather_facts: no
vars:
kubeconfig: "{{ lookup('env', 'KUBECONFIG') }}"
tasks:
- name: delete the kubeconfig if present
file:
path: '{{ kubeconfig }}'
state: absent
delegate_to: localhost
- name: Fetch the kubeconfig
fetch:
dest: '{{ kubeconfig }}'
flat: yes
src: /root/.kube/config
- name: Change the kubeconfig port to the proper value
replace:
regexp: "8443"
replace: "{{ lookup('env', 'KIND_PORT') }}"
path: '{{ kubeconfig }}'
delegate_to: localhost
- name: Wait for the Kubernetes API to become available (this could take a minute)
uri:
url: "http://localhost:10080/kubernetes-ready"
status_code: 200
validate_certs: no
register: result
until: (result.status|default(-1)) == 200
retries: 60
delay: 5
@@ -0,0 +1,29 @@
---
- name: Verify
hosts: k8s
gather_facts: false
vars:
ansible_python_interpreter: '{{ ansible_playbook_python }}'
deploy_dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/deploy"
custom_resource: "{{ lookup('file', '/'.join([deploy_dir, 'crds/drupal_v1alpha1_drupal_cr.yaml'])) | from_yaml }}"
tasks:
- name: Get all StorageClass objects.
k8s_info:
api_version: v1
kind: StorageClass
namespace: '{{ default_namespace }}'
register: storageclass
delegate_to: localhost
- name: Verify the custom EBS StorageClass is present.
assert:
that: "{{ 'kubernetes.io/aws-ebs' in storageclass | to_nice_json }}"
- name: Get all namespaces.
k8s_info:
api_version: v1
kind: Namespace
register: all_namespaces
delegate_to: localhost
@@ -0,0 +1,25 @@
---
- name: Converge
hosts: localhost
connection: local
collections:
- geerlingguy.k8s
vars:
ansible_python_interpreter: '{{ ansible_playbook_python }}'
k8s_manifests_base_dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/roles/k8s_manifests/k8s-manifests/"
k8s_kubeconfig: "{{ lookup('env', 'KUBECONFIG') }}"
k8s_no_log: false
tasks:
- name: Test k8s_manifests role.
import_role:
name: k8s_manifests
vars:
k8s_manage_namespace: false
k8s_manifests:
- dir: storageclass
namespace: default
- import_playbook: '{{ playbook_dir }}/asserts.yml'
@@ -0,0 +1,49 @@
---
dependency:
name: galaxy
driver:
name: docker
lint: |
set -e
yamllint .
ansible-lint
platforms:
- name: kind-test-local
groups:
- k8s
image: bsycorp/kind:latest-1.15
privileged: True
override_command: no
exposed_ports:
- 8443/tcp
- 10080/tcp
published_ports:
- 0.0.0.0:${TEST_CLUSTER_PORT:-10443}:8443/tcp
pre_build_image: yes
volumes:
- ${MOLECULE_PROJECT_DIRECTORY}:/build:Z
provisioner:
name: ansible
log: True
inventory:
group_vars:
all:
default_namespace: ${TEST_NAMESPACE:-default}
env:
K8S_AUTH_KUBECONFIG: /tmp/molecule/kind-test-local/kubeconfig
KUBECONFIG: /tmp/molecule/kind-test-local/kubeconfig
ANSIBLE_ROLES_PATH: ${MOLECULE_PROJECT_DIRECTORY}/roles
ANSIBLE_COLLECTIONS_PATHS: "/home/travis/build/geerlingguy:~/.ansible/collections"
KIND_PORT: '${TEST_CLUSTER_PORT:-10443}'
scenario:
test_sequence:
- lint
- destroy
- dependency
- syntax
- create
- prepare
- converge
- side_effect
- verify
- destroy
@@ -0,0 +1,17 @@
---
- import_playbook: ../default/prepare.yml
- name: Prepare operator resources
hosts: localhost
connection: local
vars:
ansible_python_interpreter: '{{ ansible_playbook_python }}'
deploy_dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/deploy"
tasks:
- name: Ensure default namespace is present
k8s:
api_version: v1
kind: Namespace
name: '{{ default_namespace }}'
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2020 Jeff Geerling
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,41 @@
# Ansible Role: Helm
[![Build Status](https://travis-ci.com/geerlingguy/ansible-role-helm.svg?branch=master)](https://travis-ci.com/geerlingguy/ansible-role-helm)
This role installs the [Helm](https://helm.sh) binary on any supported host.
## Requirements
N/A
## Role Variables
Available variables are listed below, along with default values (see `defaults/main.yml`):
helm_version: 'v3.2.1'
helm_platform: linux
helm_arch: amd64
Controls for the version of Helm to be installed. See [available Helm releases](https://github.com/helm/helm/releases/). You can upgrade or downgrade versions by changing the `helm_version`.
helm_bin_path: /usr/local/bin/helm
The location where the Helm binary will be installed.
## Dependencies
None.
## Example Playbook
- hosts: all
roles:
- role: geerlingguy.helm
## License
MIT / BSD
## Author Information
This role was created in 2020 by [Jeff Geerling](https://www.jeffgeerling.com/), author of [Ansible for DevOps](https://www.ansiblefordevops.com/).
@@ -0,0 +1,7 @@
---
# See available releases: https://github.com/helm/helm/releases/
helm_version: 'v3.2.1'
helm_platform: linux
helm_arch: amd64
helm_bin_path: /usr/local/bin/helm
@@ -0,0 +1,47 @@
---
dependencies: []
galaxy_info:
author: Jeff Geerling
role_name: helm
description: Helm for Kubernetes.
company: Midwestern Mac, LLC
license: license (MIT)
min_ansible_version: 2.7
platforms:
- name: EL
versions:
- all
- name: GenericUNIX
versions:
- all
- name: Fedora
versions:
- all
- name: opensuse
versions:
- all
- name: GenericBSD
versions:
- all
- name: FreeBSD
versions:
- all
- name: Ubuntu
versions:
- all
- name: SLES
versions:
- all
- name: GenericLinux
versions:
- all
- name: Debian
versions:
- all
galaxy_tags:
- kubernetes
- k8s
- cloud
- containers
- helm
- deployment
@@ -0,0 +1,13 @@
---
- name: Converge
hosts: all
become: true
pre_tasks:
- name: Update apt cache.
apt: update_cache=yes cache_valid_time=600
when: ansible_os_family == 'Debian'
changed_when: false
roles:
- role: geerlingguy.helm
@@ -0,0 +1,21 @@
---
dependency:
name: galaxy
driver:
name: docker
lint: |
set -e
yamllint .
ansible-lint
platforms:
- name: instance
image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos8}-ansible:latest"
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
privileged: true
pre_build_image: true
provisioner:
name: ansible
playbooks:
converge: ${MOLECULE_PLAYBOOK:-converge.yml}
@@ -0,0 +1,18 @@
---
- name: Verify
hosts: all
gather_facts: false
vars_files:
- ../../defaults/main.yml
tasks:
- name: Check Helm version.
command: "{{ helm_bin_path }} version"
changed_when: false
register: helm_verify_version
- name: Verify Helm's version is {{ helm_version }}.
assert:
that:
- helm_version in helm_verify_version.stdout
@@ -0,0 +1,30 @@
---
- name: Check if Helm binary exists.
stat:
path: "{{ helm_bin_path }}"
register: helm_check
- name: Check Helm version.
command: "{{ helm_bin_path }} version"
failed_when: false
changed_when: false
register: helm_existing_version
- name: Download helm.
unarchive:
src: https://get.helm.sh/helm-{{ helm_version }}-{{ helm_platform }}-{{ helm_arch }}.tar.gz
dest: /tmp
remote_src: true
register: helm_download
when: >
not helm_check.stat.exists
or helm_version not in helm_existing_version.stdout
- name: Copy helm binary into place.
copy:
src: "/tmp/{{ helm_platform }}-{{ helm_arch }}/helm"
dest: "{{ helm_bin_path }}"
mode: 0755
remote_src: true
become: true
when: helm_download is changed
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2018 Jeff Geerling
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,118 @@
# Ansible Role: K8s Manifests
[![Build Status](https://travis-ci.org/geerlingguy/ansible-role-k8s_manifests.svg?branch=master)](https://travis-ci.org/geerlingguy/ansible-role-k8s_manifests)
An Ansible Role that applies Kubernetes manifests (either templated, or directly) to Kubernetes clusters.
## Requirements
- Pip package: `openshift`
- If running on localhost (e.g. with `connection: local`), you may need to set `ansible_python_interpreter: "{{ ansible_playbook_python }}"` for the role to work correctly.
## Role Variables
Available variables are listed below, along with default values (see `defaults/main.yml`):
k8s_manifests:
# Can be a path inside the `k8s_manifests_base_dir`.
- monitoring/prometheus
# Use a `file` lookup if you don't want to template the manifest file.
- dir: monitoring/grafana-configmap
lookup_type: 'file'
# You can set a namespace per manifest (templated to `manifest_namespace`).
- dir: docker-registry
namespace: registry
A list of Kubernetes manifest directories to apply to a Kubernetes cluster. This list can either be raw directory paths or folder names, or can be a dict with `dir` (the directory path/folder name), optional `lookup_type` (the Ansible lookup type used for the `manifest.yml` file), and optional `namespace` (templated to `manifest_namespace`).
This role then looks inside the specified directory for each manifest, and applies a `manifest.yml` file (and all it's contents) using the Ansible `k8s` module.
If you need to template the file, this role templates the `manifest.yml` file by default (and automatically adds any variables in a `vars.yml` file alongside the `manifest.yml` file). But you can also disable templating and have the manifest applied directly by setting `lookup_type: file`.
k8s_manifests_base_dir: '' # include trailing /, e.g. 'base_dir/'
If set, this string will be prepended to each manifest `dir`/path specified in `k8s_manifests`. This is useful if you store all your Kubernetes manifests in a directory outside the Ansible playbook directory, so you don't have to include the full path in each defined `k8s_manifests` list item.
k8s_manifests_state: present
Whether the `state` for the `k8s` module should be `present` or `absent`. Note that using `absent` doesn't _always_ delete all Kubernetes resources defined in a manifest.
k8s_force: false
If set to `true`, and `k8s_manifests_state` is set to `present`, an existing object will be replaced. Otherwise the default behavior for Ansible's `k8s` module and Kubernetes itself (e.g. if using `apply`) is to _patch_ the resource.
k8s_kubeconfig: ~/.kube/config
The path to the `kubeconfig` file to use to connect to the Kubernetes cluster.
k8s_resource_namespace: ''
k8s_manage_namespace: true
By default, this role assumes you will be deploying resources into a particular namespace. So if you set `k8s_resource_namespace` to the namespace you are operating on, the role will ensure that namespace exists before applying any manifests. You can disable this role's namespace management (e.g. if the manifest you're applying should not be namespaced, or if you are applying namespaces per-manifest) by setting `k8s_manage_namespace: false`.
k8s_no_log: true
Whether to log the details of each manifest's application to the cluster in the Ansible output. Secrets and other sensitive data could be part of a manifest, so this is set to be secure by default. Set to `false` for debugging purposes.
## Dependencies
None.
## Example Playbooks
### Simple example - running on localhost
---
- hosts: localhost
connection: local
gather_facts: no
vars:
ansible_python_interpreter: "{{ ansible_playbook_python }}"
k8s_kubeconfig: ~/.kube/config-my-cluster
k8s_manifests_base_dir: k8s-manifests/
k8s_manifests:
- storageclass
roles:
- role: geerlingguy.k8s_manifests
See the `k8s-manifests` directory and it's README for an example templated manifest layout with a vars file defined alongside it.
### Running as part of a larger play
---
- hosts: k8s_cluster
become: true
vars:
ansible_python_interpreter: python
k8s_manage_namespace: false
k8s_no_log: false
k8s_manifests_base_dir: k8s-manifests/
k8s_manifests:
- storageclass
- dir: docker-registry
namespace: registry
tasks:
- name: Set the python interpreter appropriately.
set_fact:
ansible_python_interpreter: "{{ ansible_playbook_python }}"
- import_role:
name: geerlingguy.k8s_manifests
tags: ['kubernetes', 'nfs', 'drupal', 'registry']
delegate_to: localhost
become: false
run_once: true
## License
MIT / BSD
## Author Information
This role was created in 2018 by [Jeff Geerling](https://www.jeffgeerling.com/), author of [Ansible for DevOps](https://www.ansiblefordevops.com/).
@@ -0,0 +1,14 @@
---
k8s_manifests: []
# - monitoring/prometheus
# - dir: monitoring/grafana-configmap
# lookup_type: 'file'
k8s_manifests_base_dir: ''
k8s_manifests_state: present
k8s_force: false
k8s_kubeconfig: ~/.kube/config
k8s_resource_namespace: ''
k8s_manage_namespace: true
k8s_no_log: true
@@ -0,0 +1,18 @@
# K8s Manifest Example Directory
The 'storageclass' example in this directory illustrates the structure of Kubernetes manifest files as consumed by the `geerlingguy.k8s_manifests` role.
To apply the `storageclass` manifest, you would call this role with the variable:
k8s_manifests:
- k8s-manifests/storageclass
When the role runs, it will template the `manifest.yml` file inside the manifest directory, and apply any variables defined in an optional `vars.yml` file alongside the manifest (along with any other variables the Ansible playbook has already loaded, e.g. from inventory or dynamically).
You can also apply a manifest directly, with no templating, using:
k8s_manifests:
- dir: k8s-manifests/storageclass
lookup_type: file
This can be useful for raw manifests which do not need any templating, or manifest files which contain Jinja-like syntax which is hard to escape properly.
@@ -0,0 +1,13 @@
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: "{{ storageclass_name }}"
annotations:
storageclass.beta.kubernetes.io/is-default-class: "true"
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
reclaimPolicy: Delete
mountOptions:
- debug
@@ -0,0 +1,34 @@
---
dependencies: []
galaxy_info:
author: geerlingguy
role_name: k8s_manifests
description: Kubernetes manifest management role.
company: "Midwestern Mac, LLC"
license: "license (BSD, MIT)"
min_ansible_version: 2.7
platforms:
- name: EL
versions:
- all
- name: Fedora
versions:
- all
- name: Debian
versions:
- all
- name: Ubuntu
versions:
- all
- name: GenericUNIX
versions:
- all
galaxy_tags:
- k8s
- kubernetes
- infrastructure
- eks
- aks
- gks
- minikube
@@ -0,0 +1,7 @@
---
- name: Converge
hosts: all
become: true
roles:
- role: geerlingguy.k8s_manifests
@@ -0,0 +1,25 @@
---
dependency:
name: galaxy
driver:
name: docker
lint: |
set -e
yamllint .
ansible-lint
platforms:
- name: instance
image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos7}-ansible:latest"
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
privileged: true
pre_build_image: true
provisioner:
name: ansible
playbooks:
converge: ${MOLECULE_PLAYBOOK:-converge.yml}
scenario:
test_sequence:
- lint
- syntax
@@ -0,0 +1,38 @@
---
- name: Set variables for this iteration of the loop.
set_fact:
manifest_directory: "{{ k8s_manifests_base_dir }}{{ outer_item.dir | default(outer_item) }}"
manifest_lookup_type: "{{ outer_item.lookup_type | default('template') }}"
manifest_namespace: "{{ outer_item.namespace | default('') }}"
- name: Print current manifest directory.
debug: var=manifest_directory
- name: Ensure manifest-specific namespace exists.
k8s:
api_version: v1
kind: Namespace
name: "{{ manifest_namespace }}"
state: present
kubeconfig: "{{ k8s_kubeconfig }}"
when: manifest_namespace | default(false)
- name: Include vars specific to this manifest.
include_vars: "{{ item }}"
with_first_found:
- files:
- "{{ manifest_directory }}/vars.yml"
skip: true
- name: Deploy the resources defined inside the manifest.
k8s:
definition: "{{ item }}"
kubeconfig: "{{ k8s_kubeconfig }}"
state: "{{ k8s_manifests_state }}"
force: "{{ k8s_force }}"
loop: "{{ lookup(manifest_lookup_type, manifest_directory + '/manifest.yml') | from_yaml_all | list }}"
register: k8s_result
until: k8s_result is success
retries: 10
delay: 2
no_log: "{{ k8s_no_log }}"
@@ -0,0 +1,27 @@
---
- name: Ensure at least one manifest is in the list.
fail:
msg: "Please supply one or more k8s_manifests to apply."
when: k8s_manifests == []
- name: Ensure a namespace is set.
fail:
msg: "The k8s_resource_namespace variable is not set."
when:
- k8s_manage_namespace
- k8s_resource_namespace | default(true)
- name: Ensure namespace exists.
k8s:
api_version: v1
kind: Namespace
name: "{{ k8s_resource_namespace }}"
state: present
kubeconfig: "{{ k8s_kubeconfig }}"
when: k8s_manage_namespace
- name: Deploy manifests defined in k8s_manifests.
include_tasks: deploy-manifest.yml
loop: "{{ k8s_manifests }}"
loop_control:
loop_var: outer_item
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2018 Jeff Geerling
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,148 @@
# Ansible Role: Kubernetes
[![Build Status](https://travis-ci.org/geerlingguy/ansible-role-kubernetes.svg?branch=master)](https://travis-ci.org/geerlingguy/ansible-role-kubernetes)
An Ansible Role that installs [Kubernetes](https://kubernetes.io) on Linux.
## Requirements
Requires Docker; recommended role for Docker installation: `geerlingguy.docker`.
## Role Variables
Available variables are listed below, along with default values (see `defaults/main.yml`):
kubernetes_packages:
- name: kubelet
state: present
- name: kubectl
state: present
- name: kubeadm
state: present
- name: kubernetes-cni
state: present
Kubernetes packages to be installed on the server. You can either provide a list of package names, or set `name` and `state` to have more control over whether the package is `present`, `absent`, `latest`, etc.
kubernetes_version: '1.17'
kubernetes_version_rhel_package: '1.17.2'
The minor version of Kubernetes to install. The plain `kubernetes_version` is used to pin an apt package version on Debian, and as the Kubernetes version passed into the `kubeadm init` command (see `kubernetes_version_kubeadm`). The `kubernetes_version_rhel_package` variable must be a specific Kubernetes release, and is used to pin the version on Red Hat / CentOS servers.
kubernetes_role: master
Whether the particular server will serve as a Kubernetes `master` (default) or `node`. The master will have `kubeadm init` run on it to intialize the entire K8s control plane, while `node`s will have `kubeadm join` run on them to join them to the `master`.
kubernetes_kubelet_extra_args: ""
kubernetes_kubelet_extra_args_config_file: /etc/default/kubelet
Extra args to pass to `kubelet` during startup. E.g. to allow `kubelet` to start up even if there is swap is enabled on your server, set this to: `"--fail-swap-on=false"`. Or to specify the node-ip advertised by `kubelet`, set this to `"--node-ip={{ ansible_host }}"`.
kubernetes_kubeadm_init_extra_opts: ""
Extra args to pass to `kubeadm init` during K8s control plane initialization. E.g. to specify extra Subject Alternative Names for API server certificate, set this to: `"--apiserver-cert-extra-sans my-custom.host"`
kubernetes_join_command_extra_opts: ""
Extra args to pass to the generated `kubeadm join` command during K8s node initialization. E.g. to ignore certain preflight errors like swap being enabled, set this to: `--ignore-preflight-errors=Swap`
kubernetes_allow_pods_on_master: true
Whether to remove the taint that denies pods from being deployed to the Kubernetes master. If you have a single-node cluster, this should definitely be `True`. Otherwise, set to `False` if you want a dedicated Kubernetes master which doesn't run any other pods.
kubernetes_enable_web_ui: false
kubernetes_web_ui_manifest_file: https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
Whether to enable the Kubernetes web dashboard UI (only accessible on the master itself, or proxied), and the file containing the web dashboard UI manifest.
kubernetes_pod_network:
# Flannel CNI.
cni: 'flannel'
cidr: '10.244.0.0/16'
#
# Calico CNI.
# cni: 'calico'
# cidr: '192.168.0.0/16'
#
# Weave CNI.
# cni: 'weave'
# cidr: '192.168.0.0/16'
This role currently supports `flannel` (default), `calico` or `weave` for cluster pod networking. Choose only one for your cluster; converting between them is not done automatically and could result in broken networking; if you need to switch from one to another, it should be done outside of this role.
kubernetes_apiserver_advertise_address: ''
kubernetes_version_kubeadm: 'stable-{{ kubernetes_version }}'
kubernetes_ignore_preflight_errors: 'all'
Options passed to `kubeadm init` when initializing the Kubernetes master. The `kubernetes_apiserver_advertise_address` defaults to `ansible_default_ipv4.address` if it's left empty.
kubernetes_apt_release_channel: main
kubernetes_apt_repository: "deb http://apt.kubernetes.io/ kubernetes-xenial {{ kubernetes_apt_release_channel }}"
kubernetes_apt_ignore_key_error: false
Apt repository options for Kubernetes installation.
kubernetes_yum_arch: x86_64
Yum repository options for Kubernetes installation.
kubernetes_flannel_manifest_file_rbac: https://raw.githubusercontent.com/coreos/flannel/master/Documentation/k8s-manifests/kube-flannel-rbac.yml
kubernetes_flannel_manifest_file: https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
Flannel manifest files to apply to the Kubernetes cluster to enable networking. You can copy your own files to your server and apply them instead, if you need to customize the Flannel networking configuration.
## Dependencies
None.
## Example Playbooks
### Single node (master-only) cluster
```yaml
- hosts: all
vars:
kubernetes_allow_pods_on_master: true
roles:
- geerlingguy.docker
- geerlingguy.kubernetes
```
### Two or more nodes (single master) cluster
Master inventory vars:
```yaml
kubernetes_role: "master"
```
Node(s) inventory vars:
```yaml
kubernetes_role: "node"
```
Playbook:
```yaml
- hosts: all
vars:
kubernetes_allow_pods_on_master: true
roles:
- geerlingguy.docker
- geerlingguy.kubernetes
```
Then, log into the Kubernetes master, and run `kubectl get nodes` as root, and you should see a list of all the servers.
## License
MIT / BSD
## Author Information
This role was created in 2018 by [Jeff Geerling](https://www.jeffgeerling.com/), author of [Ansible for DevOps](https://www.ansiblefordevops.com/).
@@ -0,0 +1,49 @@
---
kubernetes_packages:
- name: kubelet
state: present
- name: kubectl
state: present
- name: kubeadm
state: present
- name: kubernetes-cni
state: present
kubernetes_version: '1.17'
kubernetes_version_rhel_package: '1.17.2'
kubernetes_role: master
kubernetes_kubelet_extra_args: ""
kubernetes_kubeadm_init_extra_opts: ""
kubernetes_join_command_extra_opts: ""
kubernetes_allow_pods_on_master: true
kubernetes_enable_web_ui: true
kubernetes_web_ui_manifest_file: https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml
kubernetes_pod_network:
# Flannel CNI.
cni: 'flannel'
cidr: '10.244.0.0/16'
# Calico CNI.
# cni: 'calico'
# cidr: '192.168.0.0/16'
kubernetes_apiserver_advertise_address: ''
kubernetes_version_kubeadm: 'stable-{{ kubernetes_version }}'
kubernetes_ignore_preflight_errors: 'all'
kubernetes_apt_release_channel: main
# Note that xenial repo is used for all Debian derivatives at this time.
kubernetes_apt_repository: "deb http://apt.kubernetes.io/ kubernetes-xenial {{ kubernetes_apt_release_channel }}"
kubernetes_apt_ignore_key_error: false
kubernetes_yum_arch: x86_64
# Flannel config files.
kubernetes_flannel_manifest_file_rbac: https://raw.githubusercontent.com/coreos/flannel/master/Documentation/k8s-manifests/kube-flannel-rbac.yml
kubernetes_flannel_manifest_file: https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
# Calico config files
kubernetes_calico_manifest_file: https://docs.projectcalico.org/v3.10/manifests/calico.yaml
@@ -0,0 +1,3 @@
---
- name: restart kubelet
service: name=kubelet state=restarted
@@ -0,0 +1,33 @@
---
dependencies: []
galaxy_info:
author: geerlingguy
description: Kubernetes for Linux.
company: "Midwestern Mac, LLC"
license: "license (BSD, MIT)"
min_ansible_version: 2.4
platforms:
- name: EL
versions:
- 7
- 8
- name: Debian
versions:
- stretch
- buster
- name: Ubuntu
versions:
- xenial
- bionic
galaxy_tags:
- system
- containers
- docker
- rkt
- orchestration
- kubernetes
- k8s
- paas
- saas
- hosting
@@ -0,0 +1,46 @@
---
- name: Converge
hosts: all
become: true
vars:
# Allow swap in test environments (hard to control in some Docker envs).
kubernetes_kubelet_extra_args: "--fail-swap-on=false --cgroup-driver=cgroupfs"
docker_install_compose: false
pre_tasks:
- name: Update apt cache.
apt: update_cache=true cache_valid_time=600
when: ansible_os_family == 'Debian'
- name: Ensure test dependencies are installed (RedHat).
package: name=iproute state=present
when: ansible_os_family == 'RedHat'
- name: Ensure test dependencies are installed (Debian).
package: name=iproute2 state=present
when: ansible_os_family == 'Debian'
- name: Gather facts.
action: setup
roles:
- role: geerlingguy.docker
- role: geerlingguy.kubernetes
post_tasks:
- name: Get cluster info.
command: kubectl cluster-info
changed_when: false
register: kubernetes_info
- name: Print cluster info.
debug: var=kubernetes_info.stdout
- name: Get all running pods.
command: kubectl get pods --all-namespaces
changed_when: false
register: kubernetes_pods
- name: Print list of running pods.
debug: var=kubernetes_pods.stdout
@@ -0,0 +1,22 @@
---
dependency:
name: galaxy
driver:
name: docker
lint: |
set -e
yamllint .
ansible-lint
platforms:
- name: instance
image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos7}-ansible:latest"
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
- /var/lib/docker
privileged: true
pre_build_image: true
provisioner:
name: ansible
playbooks:
converge: ${MOLECULE_PLAYBOOK:-converge.yml}
@@ -0,0 +1,50 @@
---
- name: Converge
hosts: all
become: true
vars:
kubernetes_pod_network:
cni: 'calico'
cidr: '192.168.0.0/16'
# Allow swap in test environments (hard to control in some Docker envs).
kubernetes_kubelet_extra_args: "--fail-swap-on=false --cgroup-driver=cgroupfs"
docker_install_compose: false
pre_tasks:
- name: Update apt cache.
apt: update_cache=true cache_valid_time=600
when: ansible_os_family == 'Debian'
- name: Ensure test dependencies are installed (RedHat).
package: name=iproute state=present
when: ansible_os_family == 'RedHat'
- name: Ensure test dependencies are installed (Debian).
package: name=iproute2 state=present
when: ansible_os_family == 'Debian'
- name: Gather facts.
action: setup
roles:
- role: geerlingguy.docker
- role: geerlingguy.kubernetes
post_tasks:
- name: Get cluster info.
command: kubectl cluster-info
changed_when: false
register: kubernetes_info
- name: Print cluster info.
debug: var=kubernetes_info.stdout
- name: Get all running pods.
command: kubectl get pods --all-namespaces
changed_when: false
register: kubernetes_pods
- name: Print list of running pods.
debug: var=kubernetes_pods.stdout
@@ -0,0 +1,2 @@
---
- src: geerlingguy.docker
@@ -0,0 +1,34 @@
---
- name: Check for existence of kubelet environment file.
stat:
path: '{{ kubelet_environment_file_path }}'
register: kubelet_environment_file
- name: Set facts for KUBELET_EXTRA_ARGS task if environment file exists.
set_fact:
kubelet_args_path: '{{ kubelet_environment_file_path }}'
kubelet_args_line: "{{ 'KUBELET_EXTRA_ARGS=' + kubernetes_kubelet_extra_args }}"
kubelet_args_regexp: '^KUBELET_EXTRA_ARGS='
when: kubelet_environment_file.stat.exists
- name: Set facts for KUBELET_EXTRA_ARGS task if environment file doesn't exist.
set_fact:
kubelet_args_path: '/etc/systemd/system/kubelet.service.d/10-kubeadm.conf'
kubelet_args_line: "{{ 'Environment=\"KUBELET_EXTRA_ARGS=' + kubernetes_kubelet_extra_args + '\"' }}"
kubelet_args_regexp: '^Environment="KUBELET_EXTRA_ARGS='
when: not kubelet_environment_file.stat.exists
- name: Configure KUBELET_EXTRA_ARGS.
lineinfile:
path: '{{ kubelet_args_path }}'
line: '{{ kubelet_args_line }}'
regexp: '{{ kubelet_args_regexp }}'
state: present
register: kubelet_config_file
- name: Reload systemd unit if args were changed.
systemd:
state: restarted
daemon_reload: true
name: kubelet
when: kubelet_config_file is changed
@@ -0,0 +1,56 @@
---
- name: Include OS-specific variables.
include_vars: "{{ ansible_os_family }}.yml"
- include_tasks: setup-RedHat.yml
when: ansible_os_family == 'RedHat'
- include_tasks: setup-Debian.yml
when: ansible_os_family == 'Debian'
- name: Ensure dependencies are installed.
package: name=curl state=present
- name: Install Kubernetes packages.
package:
name: "{{ item.name | default(item) }}"
state: "{{ item.state | default('present') }}"
notify: restart kubelet
with_items: "{{ kubernetes_packages }}"
- include_tasks: kubelet-setup.yml
- name: Ensure kubelet is started and enabled at boot.
service:
name: kubelet
state: started
enabled: true
- name: Check if Kubernetes has already been initialized.
stat:
path: /etc/kubernetes/admin.conf
register: kubernetes_init_stat
# Set up master.
- include_tasks: master-setup.yml
when: kubernetes_role == 'master'
# Set up nodes.
- name: Get the kubeadm join command from the Kubernetes master.
command: kubeadm token create --print-join-command
changed_when: false
when: kubernetes_role == 'master'
register: kubernetes_join_command_result
- name: Set the kubeadm join command globally.
set_fact:
kubernetes_join_command: >
{{ kubernetes_join_command_result.stdout }}
{{ kubernetes_join_command_extra_opts }}
when: kubernetes_join_command_result.stdout is defined
delegate_to: "{{ item }}"
delegate_facts: true
with_items: "{{ groups['all'] }}"
- include_tasks: node-setup.yml
when: kubernetes_role == 'node'
@@ -0,0 +1,80 @@
---
- name: Initialize Kubernetes master with kubeadm init.
command: >
kubeadm init
--pod-network-cidr={{ kubernetes_pod_network.cidr }}
--apiserver-advertise-address={{ kubernetes_apiserver_advertise_address | default(ansible_default_ipv4.address, true) }}
--kubernetes-version {{ kubernetes_version_kubeadm }}
--ignore-preflight-errors={{ kubernetes_ignore_preflight_errors }}
{{ kubernetes_kubeadm_init_extra_opts }}
register: kubeadmin_init
when: not kubernetes_init_stat.stat.exists
- name: Print the init output to screen.
debug:
var: kubeadmin_init.stdout
verbosity: 2
when: not kubernetes_init_stat.stat.exists
- name: Ensure .kube directory exists.
file:
path: ~/.kube
state: directory
- name: Symlink the kubectl admin.conf to ~/.kube/conf.
file:
src: /etc/kubernetes/admin.conf
dest: ~/.kube/config
state: link
- name: Configure Flannel networking.
command: "{{ item }}"
with_items:
- kubectl apply -f {{ kubernetes_flannel_manifest_file_rbac }}
- kubectl apply -f {{ kubernetes_flannel_manifest_file }}
register: flannel_result
changed_when: "'created' in flannel_result.stdout"
when: kubernetes_pod_network.cni == 'flannel'
- name: Configure Calico networking.
command: "{{ item }}"
with_items:
- kubectl apply -f {{ kubernetes_calico_manifest_file }}
register: calico_result
changed_when: "'created' in calico_result.stdout"
when: kubernetes_pod_network.cni == 'calico'
- name: Get Kubernetes version for Weave installation.
shell: kubectl version | base64 | tr -d '\n'
changed_when: false
register: kubectl_version
when: kubernetes_pod_network.cni == 'weave'
- name: Configure Weave networking.
command: "{{ item }}"
with_items:
- "kubectl apply -f https://cloud.weave.works/k8s/net?k8s-version={{ kubectl_version.stdout_lines[0] }}"
register: weave_result
changed_when: "'created' in weave_result.stdout"
when: kubernetes_pod_network.cni == 'weave'
# TODO: Check if taint exists with something like `kubectl describe nodes`
# instead of using kubernetes_init_stat.stat.exists check.
- name: Allow pods on master node (if configured).
command: "kubectl taint nodes --all node-role.kubernetes.io/master-"
when:
- kubernetes_allow_pods_on_master | bool
- not kubernetes_init_stat.stat.exists
- name: Check if Kubernetes Dashboard UI service already exists.
shell: kubectl get services --namespace kube-system | grep -q kubernetes-dashboard
changed_when: false
failed_when: false
register: kubernetes_dashboard_service
when: kubernetes_enable_web_ui | bool
- name: Enable the Kubernetes Web Dashboard UI (if configured).
command: "kubectl create -f {{ kubernetes_web_ui_manifest_file }}"
when:
- kubernetes_enable_web_ui | bool
- kubernetes_dashboard_service is failed
@@ -0,0 +1,6 @@
---
- name: Join node to Kubernetes master
shell: >
{{ kubernetes_join_command }}
creates=/etc/kubernetes/kubelet.conf
tags: ['skip_ansible_lint']
@@ -0,0 +1,25 @@
---
- name: Ensure dependencies are installed.
apt:
name:
- apt-transport-https
- ca-certificates
state: present
- name: Add Kubernetes apt key.
apt_key:
url: https://packages.cloud.google.com/apt/doc/apt-key.gpg
state: present
register: add_repository_key
ignore_errors: "{{ kubernetes_apt_ignore_key_error }}"
- name: Add Kubernetes repository.
apt_repository:
repo: "{{ kubernetes_apt_repository }}"
state: present
update_cache: true
- name: Add Kubernetes apt preferences file to pin a version.
template:
src: apt-preferences-kubernetes.j2
dest: /etc/apt/preferences.d/kubernetes
@@ -0,0 +1,27 @@
---
- name: Ensure Kubernetes repository exists.
yum_repository:
name: kubernetes
description: Kubernetes
enabled: true
gpgcheck: true
repo_gpgcheck: true
baseurl: https://packages.cloud.google.com/yum/repos/kubernetes-el7-{{ kubernetes_yum_arch }}
gpgkey:
- https://packages.cloud.google.com/yum/doc/yum-key.gpg
- https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
- name: Add Kubernetes GPG keys.
rpm_key:
key: "{{ item }}"
state: present
register: kubernetes_rpm_key
with_items:
- https://packages.cloud.google.com/yum/doc/yum-key.gpg
- https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
- name: Make cache if Kubernetes GPG key changed.
command: "yum -q makecache -y --disablerepo='*' --enablerepo='kubernetes'"
when: kubernetes_rpm_key is changed
args:
warn: false
@@ -0,0 +1,11 @@
Package: kubectl
Pin: version {{ kubernetes_version }}.*
Pin-Priority: 1000
Package: kubeadm
Pin: version {{ kubernetes_version }}.*
Pin-Priority: 1000
Package: kubelet
Pin: version {{ kubernetes_version }}.*
Pin-Priority: 1000
@@ -0,0 +1,2 @@
---
kubelet_environment_file_path: /etc/default/kubelet
@@ -0,0 +1,11 @@
---
kubelet_environment_file_path: /etc/sysconfig/kubelet
kubernetes_packages:
- name: kubelet-{{ kubernetes_version_rhel_package }}-0
state: present
- name: kubectl-{{ kubernetes_version_rhel_package }}-0
state: present
- name: kubeadm-{{ kubernetes_version_rhel_package }}-0
state: present
- name: kubernetes-cni
state: present
@@ -0,0 +1,48 @@
---
- hosts: localhost
connection: local
gather_facts: false
vars:
# This should be set via the command line at runtime.
tag: ''
pre_tasks:
- name: Verify none of the git submodules need updates.
command: >
git submodule update --recursive --remote
chdir=../
register: git_update
failed_when: git_update.stdout != ''
- name: Ensure the ANSIBLE_GALAXY_TOKEN environment variable is set.
fail:
msg: ANSIBLE_GALAXY_TOKEN is not set.
when: "lookup('env','ANSIBLE_GALAXY_TOKEN') == ''"
- name: Ensure the ~/.ansible directory exists.
file:
path: ~/.ansible
state: directory
- name: Write the Galaxy token to ~/.ansible/galaxy_token
copy:
content: |
token: {{ lookup('env','ANSIBLE_GALAXY_TOKEN') }}
dest: ~/.ansible/galaxy_token
tasks:
- name: Template out the galaxy.yml file.
template:
src: templates/galaxy.yml.j2
dest: ../galaxy.yml
- name: Build the collection.
command: >
ansible-galaxy collection build
chdir=../
- name: Publish the collection.
command: >
ansible-galaxy collection publish ./geerlingguy-k8s-{{ tag }}.tar.gz
chdir=../
@@ -0,0 +1,36 @@
---
namespace: geerlingguy
name: k8s
description: An elegant collection of Kuberenetes automation tools.
version: "{{ tag }}"
readme: README.md
authors:
- geerlingguy (https://www.jeffgeerling.com/)
dependencies: { }
license:
- MIT
tags:
- kubernetes
- k8s
- infrastructure
- cloud
- containers
- eks
- aks
- gke
- digitalocean
- docker
- debian
- redhat
- ubuntu
- amazon
- linux
- native
repository: https://github.com/geerlingguy/ansible-collection-k8s
documentation: https://github.com/geerlingguy/ansible-collection-k8s
# homepage: ""
issues: https://github.com/geerlingguy/ansible-collection-k8s/issues
build_ignore:
- scripts
- molecule
- .DS_Store
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Crown Copyright (Companies House)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+45
View File
@@ -0,0 +1,45 @@
Ansible Role: Apache
====================
An ansible role for installing Apache and ModPerl on EL6.
Requirements
------------
This role requires epel repo to be set up prior to run. This can easily be accomplished by using a role like [geerlingguy.repo-epel](https://galaxy.ansible.com/geerlingguy/repo-epel/) or another equivalent.
Role Variables
--------------
Variable |Default | Description
----------------|:-------:|-------------
apache_version |`2.2.15` |The Apache version to install
install_modperl |`true` |Install ModPerl as well?
modperl_version |`2.0.4` |The ModPerl version to install
apache_user |`apache` |Create an apache user that can be configured for use in your httpd.conf
Dependencies
------------
None.
Example Requirements File
-------------------------
```
- src: https://github.com/companieshouse/ansible-role-apache
name: apache
```
Example Playbook
----------------
```
- hosts: servers
roles:
- role: apache
apache_version: 2.2.15
modperl_version: 2.0.4
```
License
-------
MIT
@@ -0,0 +1,8 @@
---
apache_version: 2.2.15
install_modperl: true
modperl_version: 2.0.4
apache_user: apache
@@ -0,0 +1,20 @@
galaxy_info:
author: Companies House
description: Installs Apache and ModPerl rpms on EL6 systems.
license: MIT
min_ansible_version: 2.0
github_branch: master
platforms:
- name: EL
versions:
- 6
galaxy_tags:
- apache
- modperl
- mod_perl
dependencies: []
@@ -0,0 +1,16 @@
---
driver:
name: docker
ansible:
playbook: tests/test.yml
docker:
containers:
- name: apache_centos6
image: centos
image_version: 6
privileged: true
ansible_groups:
- all
@@ -0,0 +1,23 @@
---
- name: Create a user for apache
user:
name: "{{ apache_user }}"
state: present
- name: Install apache and mod_perl
yum:
name: "httpd-{{ apache_version }}"
state: present
- name: Install ModPerl
yum:
name: "mod_perl-{{ modperl_version }}"
state: present
when: install_modperl
- name: Disable the 'main' apache
service:
name: httpd
state: stopped
enabled: no
@@ -0,0 +1,4 @@
---
- hosts: all
roles:
- role: ansible-role-apache

Some files were not shown because too many files have changed in this diff Show More