common refactor for Pike and Chef 13

- changed the default RDBMS to MariaDB in accordance with install docs[0]
- removed deprecated database, apt and yum cookbooks
- incorporated `database' and MySQL-specific abstractions from database
  cookbook
- implemented foodcritic and cookstyle corrections
- deprecated node.foo.bar method access for node['foo']['bar'] bracket syntax
- updated default recipe for core apt resource
- use /etc/apt/apt.conf.d on Ubuntu instead of passing the dpkg overrides as
  command line options in every cookbook

[0]: https://docs.openstack.org/install-guide/environment-sql-database.html

Implements blueprint modern-chef

Change-Id: I143e0ed0a2bdd76269fc0c402052696426d96d81
Depends-On: I00e2237cef0c9aa35f78d3ccec04a1c7b9271ce8
Depends-On: I7ee0f5eae4e79e5c70ee8de4a0094a7c34fdd18f
This commit is contained in:
Samuel Cassiba 2017-11-26 22:40:50 -08:00
parent ea0365d79a
commit b2881c3a80
26 changed files with 1125 additions and 117 deletions

View File

@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2017-08-17 14:16:01 +0200 using RuboCop version 0.47.1.
# on 2017-11-26 11:38:53 -0800 using RuboCop version 0.49.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@ -11,30 +11,17 @@ Lint/NestedMethodDefinition:
Exclude:
- 'libraries/matchers.rb'
# Offense count: 11
Metrics/AbcSize:
Max: 39
# Offense count: 44
# Configuration parameters: CountComments, ExcludedMethods.
Metrics/BlockLength:
Max: 203
# Offense count: 1
Metrics/PerceivedComplexity:
Max: 13
# Offense count: 9
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: nested, compact
Style/ClassAndModuleChildren:
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles.
# SupportedStyles: predicate, comparison
Style/NumericPredicate:
Exclude:
- 'libraries/cli.rb'
- 'libraries/config_helpers.rb'
- 'libraries/endpoints.rb'
- 'libraries/network.rb'
- 'libraries/parse.rb'
- 'libraries/passwords.rb'
- 'libraries/search.rb'
- 'libraries/uri.rb'
- 'libraries/wrappers.rb'
- 'spec/**/*'
- 'libraries/provider_database_mysql_user.rb'
# Offense count: 2
# Cop supports --auto-correct.
Style/ZeroLengthPredicate:
Exclude:
- 'libraries/provider_database_mysql_user.rb'

View File

@ -1,4 +1,4 @@
source "https://supermarket.chef.io"
source 'https://supermarket.chef.io'
metadata

View File

@ -1,41 +1,41 @@
task default: ["test"]
task default: ['test']
task :test => [:syntax, :lint, :unit]
task test: [:syntax, :lint, :unit]
desc "Vendor the cookbooks in the Berksfile"
desc 'Vendor the cookbooks in the Berksfile'
task :berks_prep do
sh %{chef exec berks vendor}
sh %(chef exec berks vendor)
end
desc "Run FoodCritic (syntax) tests"
desc 'Run FoodCritic (syntax) tests'
task :syntax do
sh %{chef exec foodcritic --exclude spec -f any .}
sh %(chef exec foodcritic --exclude spec -f any .)
end
desc "Run RuboCop (lint) tests"
desc 'Run RuboCop (lint) tests'
task :lint do
sh %{chef exec cookstyle}
sh %(chef exec cookstyle)
end
desc "Run RSpec (unit) tests"
task :unit => :berks_prep do
sh %{chef exec rspec --format documentation}
desc 'Run RSpec (unit) tests'
task unit: :berks_prep do
sh %(chef exec rspec --format documentation)
end
desc "Remove the berks-cookbooks directory and the Berksfile.lock"
desc 'Remove the berks-cookbooks directory and the Berksfile.lock'
task :clean do
rm_rf [
'berks-cookbooks',
'Berksfile.lock'
'Berksfile.lock',
]
end
desc "All-in-One Neutron build"
task :integration => :common_integration do
desc 'All-in-One Neutron build'
task integration: :common_integration do
# Noop
end
desc "Common task used by all cookbooks for integration test"
desc 'Common task used by all cookbooks for integration test'
task :common_integration do
# Use the berksfile support to make use of the existing patch clones.
# Make a sym link from workspace/gate-cookbook-openstack-common-chef-rake-integration

View File

@ -109,7 +109,6 @@ default['openstack']['db']['options'] = {
mysql: "?charset=#{node['openstack']['db']['charset']['mysql']}",
'percona-cluster' => "?charset=#{node['openstack']['db']['charset']['percona-cluster']}",
mariadb: "?charset=#{node['openstack']['db']['charset']['mariadb']}",
postgresql: '',
sqlite: '',
nosql: '',
galera: "?charset=#{node['openstack']['db']['charset']['galera']}",
@ -117,7 +116,7 @@ default['openstack']['db']['options'] = {
# platform and DBMS-specific python client packages
default['openstack']['db']['python_packages'] = {
postgresql: ['python-psycopg2'],
postgresql: [],
sqlite: [],
}
case node['platform_family']
@ -128,7 +127,7 @@ when 'rhel'
default['openstack']['db']['python_packages']['percona-cluster'] = ['MySQL-python']
default['openstack']['db']['python_packages']['galera'] = ['MySQL-python']
when 'debian'
default['openstack']['db']['service_type'] = 'mysql'
default['openstack']['db']['service_type'] = 'mariadb'
default['openstack']['db']['python_packages']['mysql'] = ['python-mysqldb']
default['openstack']['db']['python_packages']['mariadb'] = ['python-mysqldb']
default['openstack']['db']['python_packages']['percona-cluster'] = ['python-mysqldb']
@ -140,7 +139,7 @@ case node['platform_family']
when 'rhel'
default['openstack']['db']['socket'] = '/var/lib/mysql/mysql.sock'
when 'debian'
default['openstack']['db']['socket'] = '/run/mysql-default/mysqld.sock'
default['openstack']['db']['socket'] = '/var/run/mysqld/mysqld.sock'
end
# Database used by the OpenStack services

View File

@ -292,11 +292,13 @@ default['openstack']['sysctl']['net.ipv4.conf.default.rp_filter'] = 0
case node['platform_family']
when 'rhel'
default['openstack']['common']['platform'] = {
'common_client_packages' => ['python-openstackclient'],
'package_overrides' => '',
}
when 'debian'
default['openstack']['common']['platform'] = {
'package_overrides' => "-o Dpkg::Options::='--force-confold' -o Dpkg::Options::='--force-confdef'",
'common_client_packages' => ['python-openstackclient'],
'package_overrides' => '',
}
end

View File

@ -68,8 +68,6 @@ module ::Openstack
# Normalize to the SQLAlchemy standard db type identifier
case type
when 'pgsql'
type = 'postgresql'
when 'mariadb', 'galera', 'percona-cluster'
type = 'mysql'
end
@ -91,7 +89,7 @@ module ::Openstack
# Find the specific endpoint type ('internal', 'admin' or
# 'public') for the given service.
%w(public internal admin).each do |ep_type|
define_method "#{ep_type}_endpoint" do |service|
define_method("#{ep_type}_endpoint") do |service|
uri_from_hash(node['openstack']['endpoints'][ep_type][service])
end
end

View File

@ -0,0 +1,52 @@
#
# Author:: Maksim Horbul (<max@gorbul.net>)
# Cookbook:: openstack-common
# Library:: hashed_password
#
# Copyright:: 2016, Eligible, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# this file is originally from the database cookbook, preserved for legacy
# purposes until the functionality can be refactored into a custom resource.
# Original: https://github.com/chef-boneyard/database
require File.join(File.dirname(__FILE__), 'resource_mysql_database_user')
class HashedPassword
# Initializes an object of the MysqlPassword type
# @param [String] hashed_password mysql native hashed password
# @return [MysqlPassword]
def initialize(hashed_password)
@hashed_password = hashed_password
end
# String representation of the object
# @return [String] hashed password string
def to_s
@hashed_password
end
module Helpers
# helper method wrappers the string into a MysqlPassword object
# @param [String] hashed_password mysql native hashed password
# @return [MysqlPassword] object
def hashed_password(hashed_password)
HashedPassword.new hashed_password
end
# For backward compatibility, because method was renamed
alias_method :mysql_hashed_password, :hashed_password
end
end
::Chef::Resource::MysqlDatabaseUser.send(:include, HashedPassword::Helpers)

View File

@ -95,4 +95,68 @@ if defined?(ChefSpec)
def create_openstack_common_database(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:openstack_common_database, :create, resource_name)
end
# database
#
ChefSpec.define_matcher :database
def create_database(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:database, :create, resource_name)
end
def drop_database(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:database, :drop, resource_name)
end
def query_database(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:database, :query, resource_name)
end
# database user
#
ChefSpec.define_matcher :database_user
def create_database_user(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:database_user, :create, resource_name)
end
def drop_database_user(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:database_user, :drop, resource_name)
end
def grant_database_user(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:database_user, :grant, resource_name)
end
# mysql database
#
ChefSpec.define_matcher :mysql_database
def create_mysql_database(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:mysql_database, :create, resource_name)
end
def drop_mysql_database(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:mysql_database, :drop, resource_name)
end
def query_mysql_database(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:mysql_database, :query, resource_name)
end
# mysql database user
#
ChefSpec.define_matcher :mysql_database_user
def create_mysql_database_user(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:mysql_database_user, :create, resource_name)
end
def drop_mysql_database_user(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:mysql_database_user, :drop, resource_name)
end
def grant_mysql_database_user(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:mysql_database_user, :grant, resource_name)
end
end

View File

@ -43,13 +43,13 @@ module ::Openstack
def encrypted_secret(bag_name, index)
key_path = node['openstack']['secret']['key_path']
::Chef::Log.info "Loading encrypted databag #{bag_name}.#{index} using key at #{key_path}"
secret = ::Chef::EncryptedDataBagItem.load_secret key_path
::Chef::EncryptedDataBagItem.load(bag_name, index, secret)[index]
secret = ::Chef::EncryptedDataBagItem.load_secret key_path # ~FC086
::Chef::EncryptedDataBagItem.load(bag_name, index, secret)[index] # ~FC086
end
def standard_secret(bag_name, index)
::Chef::Log.info "Loading databag #{bag_name}.#{index}"
::Chef::DataBagItem.load(bag_name, index)[index]
::Chef::DataBagItem.load(bag_name, index)[index] # ~FC086
end
def vault_secret(bag_name, index)

View File

@ -0,0 +1,170 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Author:: Sean OMeara (<sean@sean.io>)
# Copyright:: 2011-2016, Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# this file is originally from the database cookbook, preserved for legacy
# purposes until the functionality can be refactored into a custom resource.
# Original: https://github.com/chef-boneyard/database
class Chef
class Provider
class Database
class Mysql < Chef::Provider::LWRPBase
use_inline_resources
def whyrun_supported?
true
end
action :create do
# test
schema_present = nil
begin
test_sql = 'SHOW SCHEMAS;'
Chef::Log.debug("#{new_resource.name}: Performing query [#{test_sql}]")
test_sql_results = test_client.query(test_sql)
test_sql_results.each do |r|
schema_present = true if r['Database'] == new_resource.database_name
end
ensure
close_test_client
end
# repair
unless schema_present
converge_by "Creating schema '#{new_resource.database_name}'" do
begin
repair_sql = "CREATE SCHEMA IF NOT EXISTS `#{new_resource.database_name}`"
repair_sql += " CHARACTER SET = #{new_resource.encoding}" if new_resource.encoding
repair_sql += " COLLATE = #{new_resource.collation}" if new_resource.collation
Chef::Log.debug("#{new_resource.name}: Performing query [#{repair_sql}]")
repair_client.query(repair_sql)
ensure
close_repair_client
end
end
end
end
action :drop do
# test
schema_present = nil
begin
test_sql = 'SHOW SCHEMAS;'
Chef::Log.debug("Performing query [#{test_sql}]")
test_sql_results = test_client.query(test_sql)
test_sql_results.each do |r|
schema_present = true if r['Database'] == new_resource.database_name
end
ensure
close_test_client
end
# repair
if schema_present
converge_by "Dropping schema '#{new_resource.database_name}'" do
begin
repair_sql = "DROP SCHEMA IF EXISTS `#{new_resource.database_name}`"
Chef::Log.debug("Performing query [#{repair_sql}]")
repair_client.query(repair_sql)
ensure
close_repair_client
end
end
end
end
action :query do
begin
query_sql = new_resource.sql_query
Chef::Log.debug("Performing query [#{query_sql}]")
query_client.query(query_sql)
ensure
close_query_client
end
end
private
def test_client
require 'mysql2'
@test_client ||=
Mysql2::Client.new(
host: new_resource.connection[:host],
socket: new_resource.connection[:socket],
username: new_resource.connection[:username],
password: new_resource.connection[:password],
port: new_resource.connection[:port],
default_file: new_resource.connection[:default_file],
default_group: new_resource.connection[:default_group]
)
end
def close_test_client
@test_client.close if @test_client
rescue Mysql2::Error
@test_client = nil
end
def repair_client
require 'mysql2'
@repair_client ||=
Mysql2::Client.new(
host: new_resource.connection[:host],
socket: new_resource.connection[:socket],
username: new_resource.connection[:username],
password: new_resource.connection[:password],
port: new_resource.connection[:port],
default_file: new_resource.connection[:default_file],
default_group: new_resource.connection[:default_group]
)
end
def close_repair_client
@repair_client.close if @repair_client
rescue Mysql2::Error
@repair_client = nil
end
def query_client
require 'mysql2'
@query_client ||=
Mysql2::Client.new(
host: new_resource.connection[:host],
socket: new_resource.connection[:socket],
username: new_resource.connection[:username],
password: new_resource.connection[:password],
port: new_resource.connection[:port],
default_file: new_resource.connection[:default_file],
default_group: new_resource.connection[:default_group],
flags: new_resource.connection[:flags],
database: new_resource.database_name
)
end
def close_query_client
@query_client.close if @query_client
rescue Mysql2::Error
@query_client = nil
end
end
end
end
end

View File

@ -0,0 +1,388 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Author:: Sean OMeara (<sean@sean.io>)
# Copyright:: 2011-2016, Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# this file is originally from the database cookbook, preserved for legacy
# purposes until the functionality can be refactored into a custom resource.
# Original: https://github.com/chef-boneyard/database
require File.join(File.dirname(__FILE__), 'provider_database_mysql')
class Chef
class Provider
class Database
class MysqlUser < Chef::Provider::Database::Mysql
use_inline_resources
def whyrun_supported?
true
end
action :create do
# test
user_present = nil
begin
test_sql = "SELECT User,Host from mysql.user WHERE User='#{new_resource.username}' AND Host='#{new_resource.host}';"
test_sql_results = test_client.query(test_sql)
test_sql_results.each do |r|
user_present = true if r['User'] == new_resource.username
end
password_up_to_date = !user_present || test_user_password
ensure
close_test_client
end
# repair
unless user_present
converge_by "Creating user '#{new_resource.username}'@'#{new_resource.host}'" do
begin
repair_sql = "CREATE USER '#{new_resource.username}'@'#{new_resource.host}'"
if new_resource.password
repair_sql += ' IDENTIFIED BY '
repair_sql += if new_resource.password.is_a?(HashedPassword)
" PASSWORD '#{new_resource.password}'"
else
" '#{new_resource.password}'"
end
end
repair_client.query(repair_sql)
ensure
close_repair_client
end
end
end
update_user_password unless password_up_to_date
end
action :drop do
# test
user_present = nil
begin
test_sql = 'SELECT User,Host'
test_sql += ' from mysql.user'
test_sql += " WHERE User='#{new_resource.username}'"
test_sql += " AND Host='#{new_resource.host}'"
test_sql_results = test_client.query test_sql
test_sql_results.each do |r|
user_present = true if r['User'] == new_resource.username
end
ensure
close_test_client
end
# repair
if user_present
converge_by "Dropping user '#{new_resource.username}'@'#{new_resource.host}'" do
begin
repair_sql = 'DROP USER'
repair_sql += " '#{new_resource.username}'@'#{new_resource.host}'"
repair_client.query repair_sql
ensure
close_repair_client
end
end
end
end
action :grant do
# gratuitous function
def ishash?
return true if /(\A\*[0-9A-F]{40}\z)/i =~ new_resource.password
end
db_name = new_resource.database_name ? "`#{new_resource.database_name}`" : '*'
tbl_name = new_resource.table ? new_resource.table : '*'
test_table = new_resource.database_name ? 'mysql.db' : 'mysql.user'
# Test
incorrect_privs = nil
begin
test_sql = "SELECT * from #{test_table}"
test_sql += " WHERE User='#{new_resource.username}'"
test_sql += " AND Host='#{new_resource.host}'"
test_sql += " AND Db='#{new_resource.database_name}'" if new_resource.database_name
test_sql_results = test_client.query test_sql
incorrect_privs = true if test_sql_results.size.zero?
# These should all be 'Y'
test_sql_results.each do |r|
desired_privs.each do |p|
key = p.to_s.capitalize.tr(' ', '_').gsub('Replication_', 'Repl_').gsub('Create_temporary_tables', 'Create_tmp_table').gsub('Show_databases', 'Show_db')
key = "#{key}_priv"
incorrect_privs = true if r[key] != 'Y'
end
end
password_up_to_date = incorrect_privs || test_user_password
ensure
close_test_client
end
# Repair
if incorrect_privs
converge_by "Granting privs for '#{new_resource.username}'@'#{new_resource.host}'" do
begin
repair_sql = "GRANT #{new_resource.privileges.join(',')}"
repair_sql += " ON #{db_name}.#{tbl_name}"
repair_sql += " TO '#{new_resource.username}'@'#{new_resource.host}' IDENTIFIED BY"
repair_sql += if new_resource.password.is_a?(HashedPassword)
" PASSWORD '#{new_resource.password}'"
else
" '#{new_resource.password}'"
end
repair_sql += ' REQUIRE SSL' if new_resource.require_ssl
repair_sql += ' REQUIRE X509' if new_resource.require_x509
repair_sql += ' WITH GRANT OPTION' if new_resource.grant_option
redacted_sql = redact_password(repair_sql, new_resource.password)
Chef::Log.debug("#{@new_resource}: granting with sql [#{redacted_sql}]")
repair_client.query(repair_sql)
repair_client.query('FLUSH PRIVILEGES')
ensure
close_repair_client
end
end
else
# The grants are correct, but perhaps the password needs updating?
update_user_password unless password_up_to_date
end
end
action :revoke do
db_name = new_resource.database_name ? "`#{new_resource.database_name}`" : '*'
tbl_name = new_resource.table ? new_resource.table : '*'
test_table = new_resource.database_name ? 'mysql.db' : 'mysql.user'
privs_to_revoke = []
begin
test_sql = "SELECT * from #{test_table}"
test_sql += " WHERE User='#{new_resource.username}'"
test_sql += " AND Host='#{new_resource.host}'"
test_sql += " AND Db='#{new_resource.database_name}'" if new_resource.database_name
test_sql_results = test_client.query test_sql
# These should all be 'N'
test_sql_results.each do |r|
desired_privs.each do |p|
key = p.to_s.capitalize.tr(' ', '_').gsub('Replication_', 'Repl_').gsub('Create_temporary_tables', 'Create_tmp_table').gsub('Show_databases', 'Show_db')
key = "#{key}_priv"
privs_to_revoke << revokify_key(p) if r[key] != 'N'
end
end
ensure
close_test_client
end
# Repair
unless privs_to_revoke.empty?
converge_by "Revoking privs for '#{new_resource.username}'@'#{new_resource.host}'" do
begin
revoke_statement = "REVOKE #{privs_to_revoke.join(',')}"
revoke_statement += " ON #{db_name}.#{tbl_name}"
revoke_statement += " FROM `#{@new_resource.username}`@`#{@new_resource.host}` "
Chef::Log.debug("#{@new_resource}: revoking access with statement [#{revoke_statement}]")
repair_client.query(revoke_statement)
repair_client.query('FLUSH PRIVILEGES')
ensure
close_repair_client
end
end
end
end
private
def desired_privs
possible_global_privs = [
:select,
:insert,
:update,
:delete,
:create,
:drop,
:references,
:index,
:alter,
:create_tmp_table,
:lock_tables,
:create_view,
:show_view,
:create_routine,
:alter_routine,
:execute,
:event,
:trigger,
:reload,
:shutdown,
:process,
:file,
:show_db,
:super,
:repl_slave,
:repl_client,
:create_user,
]
possible_db_privs = [
:select,
:insert,
:update,
:delete,
:create,
:drop,
:references,
:index,
:alter,
:create_tmp_table,
:lock_tables,
:create_view,
:show_view,
:create_routine,
:alter_routine,
:execute,
:event,
:trigger,
]
# convert :all to the individual db or global privs
desired_privs = if new_resource.privileges == [:all] && new_resource.database_name
possible_db_privs
elsif new_resource.privileges == [:all]
possible_global_privs
else
new_resource.privileges
end
desired_privs
end
def test_client
require 'mysql2'
@test_client ||=
Mysql2::Client.new(
host: new_resource.connection[:host],
socket: new_resource.connection[:socket],
username: new_resource.connection[:username],
password: new_resource.connection[:password],
port: new_resource.connection[:port],
default_file: new_resource.connection[:default_file],
default_group: new_resource.connection[:default_group]
)
end
def close_test_client
@test_client.close if @test_client
rescue Mysql2::Error
@test_client = nil
end
def repair_client
require 'mysql2'
@repair_client ||=
Mysql2::Client.new(
host: new_resource.connection[:host],
socket: new_resource.connection[:socket],
username: new_resource.connection[:username],
password: new_resource.connection[:password],
port: new_resource.connection[:port],
default_file: new_resource.connection[:default_file],
default_group: new_resource.connection[:default_group]
)
end
def close_repair_client
@repair_client.close if @repair_client
rescue Mysql2::Error
@repair_client = nil
end
def revokify_key(key)
return '' if key.nil?
# Some keys need to be translated as outlined by the table found here:
# https://dev.mysql.com/doc/refman/5.7/en/privileges-provided.html
result = key.to_s.downcase.tr('_', ' ').gsub('repl ', 'replication ').gsub('create tmp table', 'create temporary tables').gsub('show db', 'show databases')
result = result.gsub(/ priv$/, '')
result
end
def test_user_password
if database_has_password_column(test_client)
test_sql = 'SELECT User,Host,Password FROM mysql.user ' \
"WHERE User='#{new_resource.username}' AND Host='#{new_resource.host}' "
test_sql += if new_resource.password.is_a? HashedPassword
"AND Password='#{new_resource.password}'"
else
"AND Password=PASSWORD('#{new_resource.password}')"
end
else
test_sql = 'SELECT User,Host,authentication_string FROM mysql.user ' \
"WHERE User='#{new_resource.username}' AND Host='#{new_resource.host}' " \
"AND plugin='mysql_native_password' "
test_sql += if new_resource.password.is_a? HashedPassword
"AND authentication_string='#{new_resource.password}'"
else
"AND authentication_string=PASSWORD('#{new_resource.password}')"
end
end
test_client.query(test_sql).size > 0
end
def update_user_password
converge_by "Updating password of user '#{new_resource.username}'@'#{new_resource.host}'" do
begin
if database_has_password_column(repair_client)
repair_sql = "SET PASSWORD FOR '#{new_resource.username}'@'#{new_resource.host}' = "
repair_sql += if new_resource.password.is_a? HashedPassword
"'#{new_resource.password}'"
else
" PASSWORD('#{new_resource.password}')"
end
else
# "ALTER USER is now the preferred statement for assigning passwords."
# http://dev.mysql.com/doc/refman/5.7/en/set-password.html
repair_sql = "ALTER USER '#{new_resource.username}'@'#{new_resource.host}' "
repair_sql += if new_resource.password.is_a? HashedPassword
"IDENTIFIED WITH mysql_native_password AS '#{new_resource.password}'"
else
"IDENTIFIED BY '#{new_resource.password}'"
end
end
repair_client.query(repair_sql)
ensure
close_repair_client
end
end
end
def database_has_password_column(client)
client.query('SHOW COLUMNS FROM mysql.user WHERE Field="Password"').size > 0
end
def redact_password(query, password)
if password.nil? || password == ''
query
else
query.gsub(password, 'REDACTED')
end
end
end
end
end
end

View File

@ -0,0 +1,122 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Copyright:: 2011-2016, Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# this file is originally from the database cookbook, preserved for legacy
# purposes until the functionality can be refactored into a custom resource.
# Original: https://github.com/chef-boneyard/database
require 'chef/resource'
class Chef
class Resource
class Database < Chef::Resource
def initialize(name, run_context = nil)
super
@resource_name = :database
@database_name = name
@allowed_actions.push(:create, :drop, :query)
@action = :create
end
def database_name(arg = nil)
set_or_return(
:database_name,
arg,
kind_of: String
)
end
def connection(arg = nil)
set_or_return(
:connection,
arg,
required: true
)
end
def sql(arg = nil, &block)
arg ||= block
set_or_return(
:sql,
arg,
kind_of: [String, Proc]
)
end
def sql_query
if sql.is_a?(Proc)
sql.call
else
sql
end
end
def template(arg = nil)
set_or_return(
:template,
arg,
kind_of: String,
default: 'DEFAULT'
)
end
def collation(arg = nil)
set_or_return(
:collation,
arg,
kind_of: String
)
end
def encoding(arg = nil)
set_or_return(
:encoding,
arg,
kind_of: String,
default: 'DEFAULT'
)
end
def tablespace(arg = nil)
set_or_return(
:tablespace,
arg,
kind_of: String,
default: 'DEFAULT'
)
end
def connection_limit(arg = nil)
set_or_return(
:connection_limit,
arg,
kind_of: String,
default: '-1'
)
end
def owner(arg = nil)
set_or_return(
:owner,
arg,
kind_of: String
)
end
end
end
end

View File

@ -0,0 +1,118 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Copyright:: 2011-2016, Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# this file is originally from the database cookbook, preserved for legacy
# purposes until the functionality can be refactored into a custom resource.
# Original: https://github.com/chef-boneyard/database
require File.join(File.dirname(__FILE__), 'resource_database')
class Chef
class Resource
class DatabaseUser < Chef::Resource::Database
def initialize(name, run_context = nil)
super
@resource_name = :database_user
@username = name
@database_name = nil
@table = nil
@host = 'localhost'
@privileges = [:all]
@grant_option = false
@require_ssl = false
@require_x509 = false
@allowed_actions.push(:create, :drop, :grant, :revoke)
@action = :create
end
def database_name(arg = nil)
set_or_return(
:database_name,
arg,
kind_of: String
)
end
def username(arg = nil)
set_or_return(
:username,
arg,
kind_of: String
)
end
def require_ssl(arg = nil)
set_or_return(
:require_ssl,
arg,
kind_of: [TrueClass, FalseClass]
)
end
def require_x509(arg = nil)
set_or_return(
:require_x509,
arg,
kind_of: [TrueClass, FalseClass]
)
end
def password(arg = nil)
set_or_return(
:password,
arg,
kind_of: String
)
end
def table(arg = nil)
set_or_return(
:table,
arg,
kind_of: String
)
end
def host(arg = nil)
set_or_return(
:host,
arg,
kind_of: String
)
end
def privileges(arg = nil)
set_or_return(
:privileges,
arg,
kind_of: Array
)
end
def grant_option(arg = nil)
set_or_return(
:grant_option,
arg,
kind_of: [TrueClass, FalseClass], default: false
)
end
end
end
end

View File

@ -0,0 +1,36 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Author:: Sean OMeara (<sean@sean.io>)
# Copyright:: 2011-2016, Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# this file is originally from the database cookbook, preserved for legacy
# purposes until the functionality can be refactored into a custom resource.
# Original: https://github.com/chef-boneyard/database
require File.join(File.dirname(__FILE__), 'provider_database_mysql')
class Chef
class Resource
class MysqlDatabase < Chef::Resource::Database
def initialize(name, run_context = nil)
super
@resource_name = :mysql_database
@provider = Chef::Provider::Database::Mysql
end
end
end
end

View File

@ -0,0 +1,44 @@
#
# Author:: Seth Chisamore (<schisamo@chef.io>)
# Copyright:: 2011-2016, Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# this file is originally from the database cookbook, preserved for legacy
# purposes until the functionality can be refactored into a custom resource.
# Original: https://github.com/chef-boneyard/database
require File.join(File.dirname(__FILE__), 'resource_database_user')
require File.join(File.dirname(__FILE__), 'provider_database_mysql_user')
class Chef
class Resource
class MysqlDatabaseUser < Chef::Resource::DatabaseUser
def initialize(name, run_context = nil)
super
@resource_name = :mysql_database_user
@provider = Chef::Provider::Database::MysqlUser
end
def password(arg = nil)
set_or_return(
:password,
arg,
kind_of: [String, HashedPassword]
)
end
end
end
end

View File

@ -1,7 +1,7 @@
name 'openstack-common'
maintainer 'openstack-chef'
maintainer_email 'openstack-dev@lists.openstack.org'
license 'Apache 2.0'
license 'Apache-2.0'
description 'Common OpenStack attributes, libraries and recipes.'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version '16.0.0'
@ -14,14 +14,12 @@ recipe 'openstack-common::sysctl', 'Configures sysctl settings'
supports os
end
depends 'apt', '~> 5.0'
depends 'database', '~> 6.1'
depends 'etcd', '~> 3.0'
depends 'mariadb', '~> 1.5'
depends 'memcached', '~> 4.1'
depends 'mysql', '~> 8.2'
depends 'yum', '~> 3.13'
depends 'yum-epel', '~> 2.0'
depends 'poise-python', '~> 1.5'
depends 'selinux'
issues_url 'https://launchpad.net/openstack-chef' if respond_to?(:issues_url)
source_url 'https://github.com/openstack/cookbook-openstack-common' if respond_to?(:source_url)

View File

@ -38,10 +38,6 @@ end
def db_types
case @db_type
when 'postgresql', 'pgsql'
@db_prov = ::Chef::Provider::Database::Postgresql
@user_prov = ::Chef::Provider::Database::PostgresqlUser
@super_user = 'postgres'
when 'mysql', 'mariadb', 'percona-cluster', 'galera'
@db_prov = ::Chef::Provider::Database::Mysql
@user_prov = ::Chef::Provider::Database::MysqlUser

View File

@ -19,4 +19,10 @@
# limitations under the License.
#
package 'python-openstackclient'
platform_options = node['openstack']['common']['platform']
platform_options['common_client_packages'].each do |pkg|
package pkg do
options platform_options['package_overrides']
action :upgrade
end
end

View File

@ -22,9 +22,26 @@ platform_options = node['openstack']['common']['platform']
case node['platform_family']
when 'debian'
if node['openstack']['apt']['update_apt_cache']
# Ensure we've done an apt-update first or packages won't be found.
include_recipe 'apt'
# update the apt cache before installing anything
apt_update 'default' do
action :update
end
end
# populate the necessary apt options
# by default, do not overwrite existing configuration files
# this alleviates the need to populate package_overrides in every cookbook
file '/etc/apt/apt.conf.d/confdef' do
owner 'root'
group 'root'
mode 00644
content 'Dpkg::Options {
"--force-confdef";
"--force-confold";
}'
action :create
end
package 'ubuntu-cloud-keyring' do
options platform_options['package_overrides']
action :upgrade
@ -36,6 +53,7 @@ when 'debian'
uri node['openstack']['apt']['uri']
distribution "#{node['lsb']['codename']}-updates/#{node['openstack']['release']}"
components apt_components
cache_rebuild true # update the cache after a new repo is added
end
# add in the proposed repo, but only if we're in development
@ -50,19 +68,18 @@ when 'debian'
distribution "#{node['lsb']['codename']}-proposed/#{node['openstack']['release']}"
components apt_components
action proposed_action
cache_rebuild true # update the cache after a new repo is added
end
end
when 'rhel'
include_recipe 'yum' if node['openstack']['yum']['update_yum_cache']
if node['openstack']['yum']['rdo_enabled']
repo_action = :add
include_recipe 'yum-epel'
elsif FileTest.exist? "/etc/yum.repos.d/RDO-#{node['openstack']['release']}.repo"
repo_action = :remove
else
repo_action = :nothing
end
when 'rhel'
repo_action = if node['openstack']['yum']['rdo_enabled']
:add
elsif FileTest.exist? "/etc/yum.repos.d/RDO-#{node['openstack']['release']}.repo"
:remove
else
:nothing
end
yum_repository "RDO-#{node['openstack']['release']}" do
description "OpenStack RDO repo for #{node['openstack']['release']}"

View File

@ -23,9 +23,8 @@ describe 'openstack-common::default' do
}
end
it 'returns cli enviroment' do
allow(subject).to receive(:get_password)
.with('user', 'name')
.and_return('pass')
allow(subject).to receive(:get_password).with(
'user', 'name').and_return('pass')
expect(
subject.openstack_command_env('name', 'project', 'default', 'default')
@ -112,9 +111,10 @@ describe 'openstack-common::default' do
'OS_AUTH_URL' => 'http://127.0.0.1:35357/v3',
'OS_IDENTITY_API_VERSION' => 3,
}
allow(subject).to receive(:openstack_command).with('openstack', 'user list', env, {})
allow(subject).to receive(:prettytable_to_array)
.and_return([{ 'name' => 'user1', 'id' => '1234567890ABCDEFGH' }])
allow(subject).to receive(:openstack_command).with(
'openstack', 'user list', env, {})
allow(subject).to receive(:prettytable_to_array).and_return(
[{ 'name' => 'user1', 'id' => '1234567890ABCDEFGH' }])
result = subject.identity_uuid('user', 'name', 'user1', env)
expect(result).to eq('1234567890ABCDEFGH')
@ -135,17 +135,19 @@ describe 'openstack-common::default' do
end
it 'runs glance command to query valid id' do
allow(subject).to receive(:openstack_command).with('openstack', 'image list', :env, {})
allow(subject).to receive(:prettytable_to_array)
.and_return([{ 'ID' => '87f38e15-9737-46cc-a612-7c67ee29a24f', 'Name' => 'cirros' }])
allow(subject).to receive(:openstack_command).with(
'openstack', 'image list', :env, {})
allow(subject).to receive(:prettytable_to_array).and_return(
[{ 'ID' => '87f38e15-9737-46cc-a612-7c67ee29a24f', 'Name' => 'cirros' }])
result = subject.image_id('cirros', :env)
expect(result).to eq('87f38e15-9737-46cc-a612-7c67ee29a24f')
end
it 'runs glance command to query invalid id' do
allow(subject).to receive(:openstack_command).with('openstack', 'image list', :env, {})
.and_raise("No image with a name or ID of 'test' exists. (1)")
allow(subject).to receive(:openstack_command).with(
'openstack', 'image list', :env, {}).and_raise(
"No image with a name or ID of 'test' exists. (1)")
expect { subject.image_id('test', :env) }.to raise_error(RuntimeError)
end
@ -163,9 +165,10 @@ describe 'openstack-common::default' do
'OS_AUTH_URL' => 'http://127.0.0.1:35357/v3',
'OS_IDENTITY_API_VERSION' => 3,
}
allow(subject).to receive(:openstack_command).with('openstack', 'network list', env, {})
allow(subject).to receive(:prettytable_to_array)
.and_return([{ 'name' => 'net1', 'id' => '1234567890ABCDEFGH' }])
allow(subject).to receive(:openstack_command).with(
'openstack', 'network list', env, {})
allow(subject).to receive(:prettytable_to_array).and_return(
[{ 'name' => 'net1', 'id' => '1234567890ABCDEFGH' }])
result = subject.network_uuid('network', 'name', 'net1', env)
expect(result).to eq('1234567890ABCDEFGH')

View File

@ -10,7 +10,7 @@ describe 'openstack-common::client' do
end
it do
expect(chef_run).to install_package('python-openstackclient')
expect(chef_run).to upgrade_package('python-openstackclient')
end
end
end

View File

@ -30,7 +30,7 @@ describe 'test-openstack-common-database::default' do
expect(chef_run).to create_database('create database service_db')
.with(
provider: ::Chef::Provider::Database::Mysql,
connection: { host: 'localhost123', port: 3306, username: 'root', password: 'root_pass', socket: '/run/mysql-default/mysqld.sock' },
connection: { host: 'localhost123', port: 3306, username: 'root', password: 'root_pass', socket: '/var/run/mysqld/mysqld.sock' },
database_name: 'service_db',
encoding: 'utf8'
)
@ -41,7 +41,7 @@ describe 'test-openstack-common-database::default' do
expect(chef_run).to create_database('create database service_db')
.with(
provider: ::Chef::Provider::Database::Mysql,
connection: { host: 'localhost', port: 3306, username: 'root', password: 'root_pass', socket: '/run/mysql-default/mysqld.sock' },
connection: { host: 'localhost', port: 3306, username: 'root', password: 'root_pass', socket: '/var/run/mysqld/mysqld.sock' },
database_name: 'service_db',
encoding: 'utf8'
)
@ -51,7 +51,7 @@ describe 'test-openstack-common-database::default' do
expect(chef_run).to create_database_user('create database user db_user')
.with(
provider: ::Chef::Provider::Database::MysqlUser,
connection: { host: 'localhost', port: 3306, username: 'root', password: 'root_pass', socket: '/run/mysql-default/mysqld.sock' },
connection: { host: 'localhost', port: 3306, username: 'root', password: 'root_pass', socket: '/var/run/mysqld/mysqld.sock' },
username: 'db_user',
password: 'db_pass'
)
@ -61,7 +61,7 @@ describe 'test-openstack-common-database::default' do
expect(chef_run).to grant_database_user('grant database user db_user')
.with(
provider: ::Chef::Provider::Database::MysqlUser,
connection: { host: 'localhost', port: 3306, username: 'root', password: 'root_pass', socket: '/run/mysql-default/mysqld.sock' },
connection: { host: 'localhost', port: 3306, username: 'root', password: 'root_pass', socket: '/var/run/mysqld/mysqld.sock' },
username: 'db_user',
password: 'db_pass',
database_name: 'service_db',

View File

@ -19,7 +19,6 @@ describe 'openstack-common::default' do
before do
node.set['openstack']['yum']['rdo_enabled'] = true
node.set['openstack']['yum']['gpgcheck'] = true
node.set['openstack']['yum']['update_yum_cache'] = true
end
it 'adds RDO yum repository' do
@ -29,12 +28,12 @@ describe 'openstack-common::default' do
.with(gpgcheck: true)
end
it 'includes yum recipe' do
expect(chef_run).to include_recipe('yum')
it 'does not include yum recipe' do
expect(chef_run).to_not include_recipe('yum')
end
it 'includes yum-epel recipe' do
expect(chef_run).to include_recipe('yum-epel')
it 'does not include yum-epel recipe' do
expect(chef_run).to_not include_recipe('yum-epel')
end
end
@ -49,8 +48,8 @@ describe 'openstack-common::default' do
.with(gpgcheck: false)
end
it 'includes yum-epel recipe' do
expect(chef_run).to include_recipe('yum-epel')
it 'does not include yum-epel recipe' do
expect(chef_run).to_not include_recipe('yum-epel')
end
end

View File

@ -10,13 +10,13 @@ describe 'openstack-common::default' do
runner.converge(described_recipe)
end
it 'includes apt for apt-get update' do
node.set['openstack']['apt']['update_apt_cache'] = 'true'
expect(chef_run).to include_recipe 'apt'
it 'does not include apt for apt-get update' do
expect(chef_run).to_not include_recipe 'apt'
end
it 'doesnt include apt for apt-get update' do
expect(chef_run).to_not include_recipe 'apt'
it 'updates apt cache before installing packages' do
node.override['openstack']['apt']['update_apt_cache'] = true
expect(chef_run).to update_apt_update 'default'
end
it 'upgrades ubuntu-cloud-keyring package' do
@ -30,7 +30,8 @@ describe 'openstack-common::default' do
expect(chef_run).to add_apt_repository('openstack-ppa').with(
uri: 'http://ubuntu-cloud.archive.canonical.com/ubuntu',
distribution: 'xenial-updates/pike',
components: ['main']
components: ['main'],
cache_rebuild: true
)
end
@ -47,7 +48,8 @@ describe 'openstack-common::default' do
expect(chef_run).to add_apt_repository('openstack-ppa-proposed').with(
uri: 'http://ubuntu-cloud.archive.canonical.com/ubuntu',
distribution: 'xenial-proposed/pike',
components: ['main']
components: ['main'],
cache_rebuild: true
)
end

View File

@ -16,8 +16,10 @@ describe 'openstack-common::default' do
describe '#secret' do
it 'returns databag' do
value = { 'nova' => 'this' }
allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with('/etc/chef/openstack_data_bag_secret').and_return('secret')
allow(Chef::EncryptedDataBagItem).to receive(:load).with('passwords', 'nova', 'secret').and_return(value)
allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
'/etc/chef/openstack_data_bag_secret').and_return('secret')
allow(Chef::EncryptedDataBagItem).to receive(:load).with(
'passwords', 'nova', 'secret').and_return(value)
expect(subject.secret('passwords', 'nova')).to eq('this')
end
end
@ -27,7 +29,8 @@ describe 'openstack-common::default' do
node.set['openstack']['databag_type'] = 'vault'
end
it 'returns the data from a chef vault item' do
allow(ChefVault::Item).to receive(:load).with('vault_passwords', 'nova')
allow(ChefVault::Item).to receive(:load)
.with('vault_passwords', 'nova')
.and_return('nova' => 'novapassword')
expect(subject.secret('passwords', 'nova')).to eq('novapassword')
end
@ -37,8 +40,10 @@ describe 'openstack-common::default' do
%w(service db user).each do |type|
it "returns databag value for #{type}" do
value = { 'nova' => 'this' }
allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with('/etc/chef/openstack_data_bag_secret').and_return('secret')
allow(Chef::EncryptedDataBagItem).to receive(:load).with("#{type}_passwords", 'nova', 'secret').and_return(value)
allow(Chef::EncryptedDataBagItem).to receive(:load_secret).with(
'/etc/chef/openstack_data_bag_secret').and_return('secret')
allow(Chef::EncryptedDataBagItem).to receive(:load).with(
"#{type}_passwords", 'nova', 'secret').and_return(value)
expect(subject.get_password(type, 'nova')).to eq('this')
end
end
@ -65,7 +70,8 @@ describe 'openstack-common::default' do
describe '#secret' do
it 'returns databag' do
value = { 'nova' => 'this' }
allow(Chef::DataBagItem).to receive(:load).with('passwords', 'nova').and_return(value)
allow(Chef::DataBagItem).to receive(:load)
.with('passwords', 'nova').and_return(value)
expect(subject.secret('passwords', 'nova')).to eq('this')
end
end
@ -74,7 +80,8 @@ describe 'openstack-common::default' do
%w(service db user).each do |type|
it "returns databag value for #{type}" do
value = { 'nova' => 'this' }
allow(Chef::DataBagItem).to receive(:load).with("#{type}_passwords", 'nova').and_return(value)
allow(Chef::DataBagItem).to receive(:load).with(
"#{type}_passwords", 'nova').and_return(value)
expect(subject.get_password(type, 'nova')).to eq('this')
end
end

View File

@ -12,7 +12,7 @@ UBUNTU_OPTS = {
}.freeze
REDHAT_OPTS = {
platform: 'redhat',
version: '7.1',
version: '7.3',
log_level: LOG_LEVEL,
}.freeze
# We set a default platform for non-platform specific test cases