├── .ansiblespec ├── .rspec ├── README.md ├── Rakefile ├── Vagrantfile ├── exec_hosts.sh ├── hosts ├── nginx.yml ├── roles ├── mariadb │ ├── spec │ │ └── mariadb_spec.rb │ ├── tasks │ │ └── main.yml │ └── templates │ │ ├── .gitkeep │ │ └── mariadb.repo └── nginx │ ├── defaults │ └── main.yml │ ├── handlers │ └── main.yml │ ├── spec │ └── nginx_spec.rb │ ├── tasks │ └── main.yml │ └── templates │ └── nginx.repo ├── site.yml └── spec └── spec_helper.rb /.ansiblespec: -------------------------------------------------------------------------------- 1 | --- 2 | - 3 | playbook: site.yml 4 | inventory: hosts 5 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ansible-sample-tdd 2 | 3 | Test Driven Development for Ansible by ServerSpec. It's Sample 4 | 5 | ServerSpec is a test framework based on Ruby. 6 | 7 | **NOTICE** 8 | If you want to use these specfiles on other project, 9 | Please install `ansible_spec` ruby-gem to parse Ansible playbook & inventory files. 10 | - [Rubygems - ansible_spec](http://rubygems.org/gems/ansible_spec) 11 | - [Github - ansible_spec](https://github.com/volanja/ansible_spec) 12 | 13 | # Environment 14 | 15 | ``` 16 | $ ruby -v 17 | ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17] 18 | 19 | $ gem list (needs) 20 | ansible_spec (0.3) 21 | ansible-vault (0.2.1) 22 | serverspec (2.41.3) 23 | specinfra (2.75.1) 24 | hostlist_expression (0.2.1) 25 | ``` 26 | 27 | # Important( from v0.1) 28 | this sample use `(Rubygem) ansible_spec` 29 | 30 | ``` 31 | gem install ansible_spec 32 | ``` 33 | 34 | # Directory 35 | 36 | ``` 37 | . 38 | ├── .ansiblespec #Create file (use Serverspec) 39 | ├── README.md 40 | ├── hosts #use Ansible and Serverspec if `.ansiblespec` do not exist. 41 | ├── exec_hosts.sh #Dynamic Inventory sample. if you use DynamicInventory, change `.ansiblespec` 42 | ├── site.yml #use Ansible and Serverspec if `.ansiblespec` do not exist. 43 | ├── nginx.yml #(comment-out) included by site.yml 44 | ├── roles 45 | │   ├── mariadb 46 | │   │   ├── spec 47 | │   │   │   └── mariadb_spec.rb 48 | │   │   ├── tasks 49 | │   │   │   └── main.yml 50 | │   │   └── templates 51 | │   │   └── mariadb.repo 52 | │   └── nginx 53 | │   ├── handlers 54 | │   │   └── main.yml 55 | │   ├── spec #use Serverspec 56 | │   │   └── nginx_spec.rb 57 | │   ├── tasks 58 | │   │   └── main.yml 59 | │   ├── templates 60 | │   │   └── nginx.repo 61 | │   └── vars 62 | │   └── main.yml 63 | ├── Rakefile #use Serverspec 64 | └── spec #use Serverspec 65 | └── spec_helper.rb 66 | ``` 67 | 68 | ## Change .ansiblespec(v0.0.1.3) 69 | read [this section](https://github.com/volanja/ansible_spec#change-ansiblespecv0013) 70 | If `.ansiblespec` exist, use variables(playbook and inventory). 71 | So, If you don't use `site.yml` and `hosts`, you need to change this file. 72 | If `.ansiblespec` is not found, use `site.yml` as playbook and `hosts` as inventory. 73 | 74 | ```.ansiblespec 75 | --- 76 | - 77 | playbook: site.yml 78 | inventory: hosts 79 | ``` 80 | 81 | # Run Playbook 82 | 83 | Please change the target IP address of server -> hosts (default is 192.168.0.103 and 104) 84 | 85 | ``` 86 | $ ansible-playbook site.yml -i hosts 87 | ``` 88 | 89 | #Test 90 | ## Serverspec with Ansible 91 | Serverspec use this file. (Rakefile understand the syntax of Ansible.) 92 | 93 | * hosts 94 | hosts can use [group_name] 95 | 96 | ```hosts 97 | [server] 98 | 192.168.100.203 99 | 100 | # under sample 101 | 102 | #192.168.0.103:22 103 | #192.168.0.103 ansible_ssh_port=22 104 | #192.168.0.103 ansible_ssh_private_key_file=~/.ssh/id_rsa 105 | #test ansible_ssh_host=192.168.0.103 106 | #192.168.0.103 ansible_ssh_user=root 107 | #jumper ansible_ssh_port=22 ansible_ssh_host=192.168.0.103 108 | 109 | #[sample] 110 | #(comment) www1.example.com to www99.example.com 111 | #www[1:99].example.com 112 | 113 | #(comment) www01.example.com to www99.example.com 114 | #www[01:99].example.com 115 | 116 | #(comment) db-a.example.com to db-z.example.com 117 | #db-[a:z].example.com 118 | 119 | #(comment) db-A.example.com to db-Z.example.com 120 | #db-[A:Z].example.com 121 | 122 | #[databases] 123 | #192.168.0.103 124 | 125 | #(comment) Multi Group. use server & databases 126 | #[group:children] 127 | #sample 128 | #databases 129 | ``` 130 | 131 | * site.yml 132 | site.yml can use ```include``` 133 | 134 | ```site.yml 135 | - name: Ansible-Sample-TDD 136 | hosts: server 137 | user: root 138 | roles: 139 | - nginx 140 | - mariadb 141 | ``` 142 | 143 | ## Run Test 144 | 145 | ``` 146 | $ rake -T 147 | rake serverspec:Ansible-Sample-TDD # Run serverspec for Ansible-Sample-TDD / Run serverspec for Ansible-Sample-TDD 148 | 149 | $ rake serverspec:Ansible-Sample-TDD 150 | Run serverspec for Ansible-Sample-TDD to 192.168.0.103 151 | /Users/Adr/.rvm/rubies/ruby-1.9.2-p320/bin/ruby -S rspec roles/mariadb/spec/mariadb_spec.rb roles/nginx/spec/nginx_spec.rb 152 | ........... 153 | 154 | Finished in 0.40289 seconds 155 | 11 examples, 0 failures 156 | Run serverspec for Ansible-Sample-TDD to 192.168.0.104 157 | /Users/Adr/.rvm/rubies/ruby-1.9.2-p320/bin/ruby -S rspec roles/mariadb/spec/mariadb_spec.rb roles/nginx/spec/nginx_spec.rb 158 | ........... 159 | 160 | Finished in 0.4004 seconds 161 | 11 examples, 0 failures 162 | ``` 163 | 164 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rspec/core/rake_task' 3 | require 'yaml' 4 | require 'ansible_spec' 5 | 6 | properties = AnsibleSpec.get_properties 7 | # {"name"=>"Ansible-Sample-TDD", "hosts"=>["192.168.0.103","192.168.0.103"], "user"=>"root", "roles"=>["nginx", "mariadb"]} 8 | # {"name"=>"Ansible-Sample-TDD", "hosts"=>[{"name" => "192.168.0.103:22","uri"=>"192.168.0.103","port"=>22, "private_key"=> "~/.ssh/id_rsa"}], "user"=>"root", "roles"=>["nginx", "mariadb"]} 9 | cfg = AnsibleSpec::AnsibleCfg.new 10 | 11 | desc "Run serverspec to all test" 12 | task :all => "serverspec:all" 13 | 14 | namespace :serverspec do 15 | properties = properties.compact.reject{|e| e["hosts"].length == 0} 16 | task :all => properties.map {|v| 'serverspec:' + v["name"] } 17 | properties.each_with_index.map do |property, index| 18 | property["hosts"].each do |host| 19 | desc "Run serverspec for #{property["name"]}" 20 | RSpec::Core::RakeTask.new(property["name"].to_sym) do |t| 21 | puts "Run serverspec for #{property["name"]} to #{host}" 22 | ENV['TARGET_HOSTS'] = host["hosts"] 23 | ENV['TARGET_HOST'] = host["uri"] 24 | ENV['TARGET_PORT'] = host["port"].to_s 25 | ENV['TARGET_GROUP_INDEX'] = index.to_s 26 | ENV['TARGET_PRIVATE_KEY'] = host["private_key"] 27 | unless host["user"].nil? 28 | ENV['TARGET_USER'] = host["user"] 29 | else 30 | ENV['TARGET_USER'] = property["user"] 31 | end 32 | ENV['TARGET_PASSWORD'] = host["pass"] 33 | ENV['TARGET_CONNECTION'] = host["connection"] 34 | 35 | roles = property["roles"] 36 | for role in property["roles"] 37 | for rolepath in cfg.roles_path 38 | deps = AnsibleSpec.load_dependencies(role, rolepath) 39 | if deps != [] 40 | roles += deps 41 | break 42 | end 43 | end 44 | end 45 | t.pattern = '{' + cfg.roles_path.join(',') + '}/{' + roles.join(',') + '}/spec/*_spec.rb' 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # environment 5 | # Vagrant 2.2.5 6 | # macOS High Sierra v10.13.6 7 | 8 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 9 | # configures the configuration version (we support older styles for 10 | # backwards compatibility). Please don't change it unless you know what 11 | # you're doing. 12 | Vagrant.configure("2") do |config| 13 | # The most common configuration options are documented and commented below. 14 | # For a complete reference, please see the online documentation at 15 | # https://docs.vagrantup.com. 16 | 17 | # Every Vagrant development environment requires a box. You can search for 18 | # boxes at https://vagrantcloud.com/search. 19 | # config.vm.box = "base" 20 | config.vm.box = "centos/7" 21 | 22 | # Disable automatic box update checking. If you disable this, then 23 | # boxes will only be checked for updates when the user runs 24 | # `vagrant box outdated`. This is not recommended. 25 | # config.vm.box_check_update = false 26 | 27 | # Create a forwarded port mapping which allows access to a specific port 28 | # within the machine from a port on the host machine. In the example below, 29 | # accessing "localhost:8080" will access port 80 on the guest machine. 30 | # NOTE: This will enable public access to the opened port 31 | # config.vm.network "forwarded_port", guest: 80, host: 8080 32 | 33 | # Create a forwarded port mapping which allows access to a specific port 34 | # within the machine from a port on the host machine and only allow access 35 | # via 127.0.0.1 to disable public access 36 | # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" 37 | 38 | # Create a private network, which allows host-only access to the machine 39 | # using a specific IP. 40 | # config.vm.network "private_network", ip: "192.168.0.103" 41 | 42 | # Create a public network, which generally matched to bridged network. 43 | # Bridged networks make the machine appear as another physical device on 44 | # your network. 45 | config.vm.network "public_network", ip: "192.168.100.203", bridge: "en0: Wi-Fi (Wireless)" 46 | 47 | # Share an additional folder to the guest VM. The first argument is 48 | # the path on the host to the actual folder. The second argument is 49 | # the path on the guest to mount the folder. And the optional third 50 | # argument is a set of non-required options. 51 | # config.vm.synced_folder "../data", "/vagrant_data" 52 | 53 | # Provider-specific configuration so you can fine-tune various 54 | # backing providers for Vagrant. These expose provider-specific options. 55 | # Example for VirtualBox: 56 | # 57 | # config.vm.provider "virtualbox" do |vb| 58 | # # Display the VirtualBox GUI when booting the machine 59 | # vb.gui = true 60 | # 61 | # # Customize the amount of memory on the VM: 62 | # vb.memory = "1024" 63 | # end 64 | # 65 | # View the documentation for the provider you are using for more 66 | # information on available options. 67 | 68 | # Enable provisioning with a shell script. Additional provisioners such as 69 | # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the 70 | # documentation for more information about their specific syntax and use. 71 | # config.vm.provision "shell", inline: <<-SHELL 72 | # apt-get update 73 | # apt-get install -y apache2 74 | # SHELL 75 | end 76 | -------------------------------------------------------------------------------- /exec_hosts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo '{"server": {"hosts": ["192.168.0.103","192.168.0.103"]}}' 3 | -------------------------------------------------------------------------------- /hosts: -------------------------------------------------------------------------------- 1 | [server] 2 | 192.168.100.203 3 | 4 | # under sample 5 | 6 | #192.168.0.103:22 7 | #192.168.0.103 ansible_ssh_port=22 8 | #192.168.0.103 ansible_ssh_private_key_file=~/.ssh/id_rsa 9 | #test ansible_ssh_host=192.168.0.103 10 | #192.168.0.103 ansible_ssh_user=root 11 | #jumper ansible_ssh_port=22 ansible_ssh_host=192.168.0.103 12 | 13 | #[sample] 14 | #(comment) www1.example.com to www99.example.com 15 | #www[1:99].example.com 16 | 17 | #(comment) www01.example.com to www99.example.com 18 | #www[01:99].example.com 19 | 20 | #(comment) db-a.example.com to db-z.example.com 21 | #db-[a:z].example.com 22 | 23 | #(comment) db-A.example.com to db-Z.example.com 24 | #db-[A:Z].example.com 25 | 26 | #[databases] 27 | #192.168.0.103 28 | 29 | #(comment) Multi Group. use server & databases 30 | #[group:children] 31 | #sample 32 | #databases 33 | -------------------------------------------------------------------------------- /nginx.yml: -------------------------------------------------------------------------------- 1 | - name: Ansible-Sample-TDD 2 | hosts: server 3 | user: root 4 | roles: 5 | - nginx 6 | -------------------------------------------------------------------------------- /roles/mariadb/spec/mariadb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe package('mariadb-server') do 4 | it { should be_installed } 5 | end 6 | 7 | describe service('mariadb') do 8 | it { should be_enabled } 9 | it { should be_running } 10 | end 11 | 12 | describe port(3306) do 13 | it { should be_listening } 14 | end 15 | 16 | describe file('/etc/my.cnf') do 17 | it { should be_file } 18 | end 19 | -------------------------------------------------------------------------------- /roles/mariadb/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Install MariaDB 2 | yum: pkg={{ item }} state=present 3 | with_items: 4 | - mariadb 5 | - mariadb-server 6 | - mariadb-devel 7 | - MySQL-python 8 | 9 | - name: mariadb service enable and start 10 | service: name=mariadb state=started enabled=yes 11 | -------------------------------------------------------------------------------- /roles/mariadb/templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/volanja/ansible-sample-tdd/feea6385b059687d5c4618e3e2d9e5048e48efe3/roles/mariadb/templates/.gitkeep -------------------------------------------------------------------------------- /roles/mariadb/templates/mariadb.repo: -------------------------------------------------------------------------------- 1 | # vi /etc/yum.repos.d/mariadb.repo 2 | [mariadb] 3 | name = MariaDB 4 | baseurl = http://yum.mariadb.org/10.0/centos6-amd64 5 | gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB 6 | gpgcheck=1 7 | enabled=1 8 | -------------------------------------------------------------------------------- /roles/nginx/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | www_port: 80 3 | worker_connections: 1024 4 | -------------------------------------------------------------------------------- /roles/nginx/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart iptables 3 | service: name=iptables state=restarted 4 | -------------------------------------------------------------------------------- /roles/nginx/spec/nginx_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe package('nginx') do 4 | it { should be_installed } 5 | end 6 | 7 | describe service('nginx') do 8 | it { should be_enabled } 9 | it { should be_running } 10 | end 11 | 12 | describe port(property['www_port']) do 13 | it { should be_listening } 14 | end 15 | 16 | describe file('/etc/nginx/nginx.conf') do 17 | it { should be_file } 18 | it { should contain "worker_connections #{property['worker_connections']};" } 19 | end 20 | -------------------------------------------------------------------------------- /roles/nginx/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Nginx 3 | 4 | - name: Template nginx.repo 5 | template: src=nginx.repo dest=/etc/yum.repos.d/nginx.repo mode=0644 6 | 7 | - name: install Nginx 8 | yum: pkg={{ item }} state=installed enablerepo=nginx 9 | with_items: 10 | - nginx 11 | 12 | - name: insert iptables rule 13 | lineinfile: 14 | dest=/etc/nginx/nginx.conf 15 | regexp="^\\s+worker_connections " 16 | line=" worker_connections {{worker_connections}};" 17 | 18 | - name: ensure nginx is running automatically at boot time 19 | service: name=nginx state=restarted enabled=yes 20 | 21 | - name: insert iptables rule 22 | command: firewall-cmd --add-port "{{ www_port }}/tcp" --permanent 23 | 24 | -------------------------------------------------------------------------------- /roles/nginx/templates/nginx.repo: -------------------------------------------------------------------------------- 1 | [nginx] 2 | name=nginx repo 3 | baseurl=http://nginx.org/packages/centos/$releasever/$basearch/ 4 | gpgcheck=0 5 | enabled=0 6 | -------------------------------------------------------------------------------- /site.yml: -------------------------------------------------------------------------------- 1 | - name: Ansible-Sample-TDD 2 | hosts: server 3 | user: root 4 | vars: 5 | worker_connections: 2048 6 | roles: 7 | - nginx 8 | - mariadb 9 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'serverspec' 2 | require 'net/ssh' 3 | require 'ansible_spec' 4 | require 'winrm' 5 | 6 | # 7 | # Set ansible variables to serverspec property 8 | # 9 | host = ENV['TARGET_HOST'] 10 | hosts = ENV["TARGET_HOSTS"] 11 | 12 | group_idx = ENV['TARGET_GROUP_INDEX'].to_i 13 | vars = AnsibleSpec.get_variables(host, group_idx,hosts) 14 | ssh_config_file = AnsibleSpec.get_ssh_config_file 15 | set_property vars 16 | 17 | connection = ENV['TARGET_CONNECTION'] 18 | 19 | case connection 20 | when 'ssh' 21 | # 22 | # OS type: UN*X 23 | # 24 | set :backend, :ssh 25 | 26 | # Ansible use `BECOME`, But Serverspec use `SUDO`. 27 | if ENV['ASK_BECOME_PASSWORD'] 28 | begin 29 | require 'highline/import' 30 | rescue LoadError 31 | fail "highline is not available. Try installing it." 32 | end 33 | set :sudo_password, ask("Enter become password: ") { |q| q.echo = false } 34 | else 35 | set :sudo_password, ENV['BECOME_PASSWORD'] 36 | end 37 | 38 | options = Net::SSH::Config.for(host) 39 | 40 | options[:user] = ENV['TARGET_USER'] || options[:user] 41 | options[:port] = ENV['TARGET_PORT'] || options[:port] 42 | options[:keys] = ENV['TARGET_PRIVATE_KEY'] || options[:keys] 43 | 44 | if ssh_config_file 45 | from_config_file = Net::SSH::Config.for(host,files=[ssh_config_file]) 46 | options.merge!(from_config_file) 47 | end 48 | 49 | set :host, options[:host_name] || host 50 | set :ssh_options, options 51 | 52 | # Disable become (Serverspec use sudo) 53 | # set :disable_sudo, true 54 | 55 | 56 | # Set environment variables 57 | # set :env, :LANG => 'C', :LC_MESSAGES => 'C' 58 | 59 | # Set PATH 60 | # set :path, '/sbin:/usr/local/sbin:$PATH' 61 | when 'winrm' 62 | # 63 | # OS type: Windows 64 | # 65 | set :backend, :winrm 66 | set :os, :family => 'windows' 67 | 68 | user = ENV['TARGET_USER'] 69 | port = ENV['TARGET_PORT'] 70 | pass = ENV['TARGET_PASSWORD'] 71 | 72 | if user.nil? 73 | begin 74 | require 'highline/import' 75 | rescue LoadError 76 | fail "highline is not available. Try installing it." 77 | end 78 | user = ask("\nEnter #{host}'s login user: ") { |q| q.echo = true } 79 | end 80 | if pass.nil? 81 | begin 82 | require 'highline/import' 83 | rescue LoadError 84 | fail "highline is not available. Try installing it." 85 | end 86 | pass = ask("\nEnter #{user}@#{host}'s login password: ") { |q| q.echo = false } 87 | end 88 | 89 | endpoint = "http://#{host}:#{port}/wsman" 90 | 91 | winrm = ::WinRM::WinRMWebService.new(endpoint, :ssl, :user => user, :pass => pass, :basic_auth_only => true) 92 | winrm.set_timeout 300 # 5 minutes max timeout for any operation 93 | Specinfra.configuration.winrm = winrm 94 | 95 | when 'local' 96 | # 97 | # local connection 98 | # 99 | set :backend, :exec 100 | end 101 | --------------------------------------------------------------------------------