Release plist and macos_user resources, new Xcode libraries, test cookbook and documentation. (#24)

* first commit plistbuddy

* all recipe/plist in macos

* extract plistbuddy to resource

* Straighten out unit tests

- Correctly include modules
- Correctly setup spec_helper
- Correct unit test / respec setup

* first passing test

* add more data type tests

* Most data types passing

- Boolean values are working
- Array and data type values are not
- PlistBuddy "commands" (add, print, etc.) are passing

* More testing

- Skip Array and Dict types as their implementation will be more
complicated
- Add test for float type
- Put the correct type in the test description
- Cookstyle changes

* PlistBuddy and testing update

- Add separate test cookbook for PlistBuddy resource
- Add kitchen.yml for the above purpose
- Modify and update plistbuddy resource/helpers

* remove extra newline

* add metadata file

* edit metadata; add Berksfile

* move Berksfile to correct location

* add testing requirements for berks vendor

* move kitchen file

* Converge passing in test cookbook

- Opportunistic cleanup of xcode test
- some temp debugging info in resource definition
- Rename name_property to command so as to not override action

* Itempotence

- Removed 'bool' prefix from boolean types, as that was somehow causing
issues
- Changed name_property to the path and removed that property
- Use actual actions in the resource for the PlistBuddy commands

* Fix false positive?

- Not sure how the previous test was passing previously, but I adjusted
the boolean value for the BazEntry test so that the signature input
matches the output

* Add logic for adding a value

- The data type should only be used when adding a value to a plist.
Otherwise, we don't need to use the data type

* remove debugging; fix smoke tests

* remove .kitchen.yml

* bump version to 0.8.5

* unskip ChefSpec tests - they're passing

* mark args_formatter as private method

* Feature/macos user (#14)

* first commit add_user recipe

* Testing Refactor & macos_user resource

- Create new testing cookbook that contains tests for both plistbuddy
and macos_user resources. The hope here is to have a single testing
cookbook that contains multiple suites and suite testing variations
- Create new macos_user resource - contains single boolean 'true'
settings for autologin and admin user. Still work to be done for admin
user and all steps that need to be taken to acheive the task.

* fix method call

* fix another method call

* user native ruby File class and not overriden

* fix some minor issues with resource; add kcpassword file

* Add OG Ruby kcpassword file helper module

* slightly different approach for Kcpassword module

* passing test

* Fix up kcpassword library module, smoke tests

- Modify Kcpassword helper module to use a method for
magic bits. This isn't ideal, but it works for now
- Update smoke tests so that john_jr's group ID is 20. This
may not be correct, but setting as 20 until it is further
investigated

* opportunistic refactor of keep_awake recipe

* Refactor testing cookbook layout

- Consolodate testing suites into single default suite
- Add tests for keep_awake recipe
- Use more common examples to test PlistBuddy resource

* move xcode test to test cookbook

* Opportunistic bug fix for plistbuddy resource

- Previously, the plistbuddy resource did not allow
any keys with spaces unless they were used in the value itself.
Now, you can use keys with spaces in the value property
without needing to double-quote it
- Also, fix the PlistBuddy executable string to be correct

* revert kcpassword.rb helper to a671164

* fix incorrect PlistBuddy data type name

* Fix kcpassword.rb and update plistbuddy UTs

* Extract disabling screen saver to plistbuddy

- Also, skip Xcode test temporarily

* PlistBuddy idempotence

- Remove systemsetup and pmset resource calls in keep_awake
until they are made idempotent

* update test and plist name

* Implement binary plist conversion

- Changes to binary plists via PlistBuddy must be converted back
to binary since the change implicitly converts the file to xml.
This change contains logic to convert back to binary if the file is
detected to be binary in the first place.

* attempts at idempotence using chef helper methods

* early commit to get it on the remote

* implement logic for plist return type to ruby data type

* Add floating type value support

A wild FAILING TEST appeared!
...
HANKO used TDD!
It's super effective!

* move function

* real idempotence!

* passing all unit tests

* update test cookbook name

* update new_users integration tests

* systemsetup idempotence

* all smoke and unit tests passing

* quick fix for timezone bug

* remove kcpassword file as it is no longer needed

* update path for test cookbook to be default repo name

* Rename keep_awake_spec.rb to keep_awake_test.rb

* Rename preferences_spec.rb to preferences_test.rb

* Rename new_users_spec.rb to new_users_test.rb

* fix typo

* Use relative path for macos cookbook source

* Improve readability of relative path for macos cookbook source

* Bump version

* Addressing changes in Pull Request #14

- Fix bug where screensaver was not properly being disabled, update
tests accordingly
- Rename plistbuddy resource to plist, including all related files
and callers
- Rename kcpassword helper to macos_user to match helper -> resource
convention; fix top level namespace to be MacOS
- Correctly name recipe unit tests
- Implement plist resource into disable_software_updates.rb recipe
- Add disable_software_updates.rb recipe to default kitchen suite and
appropriate smoke tests
- Remove relatively useless logic in systemsetup value property coercion

* Implement binary plist conversion

* Fix unit test and apply the Law of Demeter

* update helper pattern to match current standard; include everywhere

* remove helper reference in Xcode resource

* bump version

* update Xcode attribute to latest

* extract xcode smoke tests to a different suite

* update xcversion path for simulators test

* update namespace again

* refactor XcodeHelper modules in XCVersion, Xcode, and XCVersion modules

updated methods to simplify site calls in resource

* updated method calls for new modules

* bump version

* extract xcversion module to separate library file

* add missing macos module

* include parent module macos instead of child for xcode and xcversion

* update available_simulator_versions to new method

* bump version

* add platform-specific smoke test for xcode

* add xcode test recipe

* update kitchen with xcode suite

* refactor simulator method to call it's own available list of simulators

* use new simulator method to find highest available version

* move xcode prep work into the setup action

* clear kitchen customizations

* lazily evalutate ios simulator property

* move lazy evaluation to simulator action in xcode resource

* revert lazy execution

* warn and retry if simulator list is empty

* restore spacing

* more whitespace

* refactor highest_semantic_simulator method into class, smaller methods

* implement simulator class

* add more xcversion command methods

* implement xcversion command methods

* implement command methods correctly

* add list methods for xcversion

* implement xcversion list methods

* implement install xcode correctly

* reorder methods

* add method to list installed xcodes

* use more descriptive name for 'command' method

* move where the space lives

* implement installed xcodes command

* implement install xcodes correctly

* disable downloads prior to disabling software update check

* Renamed name to device_name

* Change name.rb to machine_name.rb to be similar with the apex
automation cookbook. Instances of name are also all changed to
machine_name.

* Add new test recipe and smoke test for machine_name.

* add disable-swu test suite

* Bump up cookbook version numbers.

* update kitchen test suite order, remove standalone swu testing

* add sleep for El Capitan platform to avoid race condition

this occurs when integrated with the macos-user test recipe

* Improve README/Documentation (#23)

* implement build definition badge and minor tweaks

* Add descriptions and attribute lists for recipes

* Remove 'description' label from recipe names

* Bump version, add documentation dir

* Fix up the last commit

* Separate 10.12 and 10.13 build status

* Add table for build status

* Add plist resource documentation

* Add Xcode documentation

* bump version
This commit is contained in:
Jacob Zaval 2017-12-14 11:03:03 -08:00 коммит произвёл Eric Hanko
Родитель 20c24e83f5
Коммит 3ca39c8237
40 изменённых файлов: 1367 добавлений и 169 удалений

2
.gitignore поставляемый
Просмотреть файл

@ -68,6 +68,7 @@ kitchen-tests/vendor
# Visual Studio Code files
.vscode
*/**/*.code-workspace
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
.idea
@ -101,3 +102,4 @@ Vagrantfile
.rubocop.yml
.vagrant
data_bags
*.code-workspace

123
README.md
Просмотреть файл

@ -1,18 +1,31 @@
# macOS Cookbook
macOS Cookbook
==============
This cookbook provides:
- Resources for configuring and provisioning macOS.
- Recipes that implement common use-cases of the macOS cookbook's recources.
The macOS cookbook is a Chef library cookbook that provides resources for configuring
and provisioning macOS. Additionally, it provides recipes that implement common
use-cases of the macOS cookbook's recources.
## Platforms
|||
|-|-|
| macOS Sierra 10.12 | ![build-status-1012](https://office.visualstudio.com/_apis/public/build/definitions/59d72877-1cea-4eb6-9d06-66716573631a/2143/badge) |
| macOS High Sierra 10.13 | ![build-status-1013](https://office.visualstudio.com/_apis/public/build/definitions/59d72877-1cea-4eb6-9d06-66716573631a/2140/badge) |
|||
- macOS
Requirements
------------
## Chef
- Only tested on Chef 13
- Surprisingly, this cookbook is only compatible with macOS
- Chef 13+
Supported OS Versions
---------------------
## Attributes
- OS X El Capitan 10.11
- macOS Sierra 10.12
- macOS High Sierra 10.13
Attributes
----------
### Admin User and Password
@ -22,33 +35,89 @@ node['macos']['admin_password'] = 'vagrant'
```
Each of these attributes defaults to vagrant since our resources are developed
with the Vagrant paradigm. In other words, the use and password declared here
should be an admin user.
with the Vagrant paradigm. In other words, the user and password declared here
should be an admin user with passwordless super-user rights.
Recipes
-------
### Disable Software Updates
Disables automatic checking and downloading of software updates.
**Usage:** `include_recipe macos::disable_software_updates`
No attributes used in this recipe.
### Keep Awake
Prevent macOS from falling asleep, disable the screensaver, and
several other settings to always keep macOS on. Uses the `plistbuddy` and `pmset`
resources.
**Usage:** `include_recipe macos::keep_awake`
| Attribute used | Default value |
|---------------------------------------|-------------------------|
| `node['macos']['network_time_server']`| `'time.windows.com'` |
| `node['macos']['time_zone']` | `'America/Los_Angeles'` |
### Mono
_TODO_
Installs [Mono](http://www.mono-project.com/docs/about-mono/). Requires package
name, version number, and checksum in order to override.
---
**Usage:** `include_recipe macos::mono`
## Resources
| Attribute used | Default value |
|-------------------------------------|----------------------------------------|
| `node['macos']['mono']['package']` | `'MonoFramework-MDK-4.4.2.11.macos10.xamarin.universal.pkg'` |
| `node['macos']['mono']['version']` | `'4.4.2'` |
| `node['macos']['mono']['checksum']` | `'d8bfbee7ae4d0d1facaf0ddfb70c0de4b1a3d94bb1b4c38e8fa4884539f54e23'` |
- `ard`
- `name`
- `defaults`
- `pmset`
- `systemsetup`
- `xcode`
### Xcode
Checkout the [Wiki](https://github.com/Microsoft/macos-cookbook/wiki) for details about the macOS Cookbook resources.
Installs Xcode 9.1 and simulators for iOS 10 and iOS 11. Check out
the documentation for the Xcode resource if you need more flexibility.
---
:large_orange_diamond: Requires an `apple_id` data bag item.
## Recipes
**Usage:** `include_recipe macos::xcode`
- `disable_software_updates`
- `keep_awake`
- `mono`
- `configurator`
| Attribute Used | Default value |
|---------------------------------------------------------------|---------------|
| `node['macos']['xcode']['version']` | `'9.1'` |
| `node['macos']['xcode']['simulator']['major_version']` | `%w(11 10)` |
### Apple Configurator 2
Installs Apple Configurator 2 using `mas` and links `cfgutil` to
`/usr/local/bin`.
:large_orange_diamond: Requires an `apple_id` data bag item.
**Usage:** `include_recipe macos::configurator`
**Attributes**: No attributes used in this recipe.
#### Data Bags
Both the `macos::xcode` and `macos::configurator` recipes require a data bag
item named `apple_id` containing valid Apple ID credentials. For example:
**Example:**
```json
{
"id": "apple_id",
"apple_id": "farva@spurbury.gov",
"password": "0k@yN0cR34m"
}
```
Resources
---------
- [ARD (Apple Remote Desktop)](./documentation/resource_ard.md)
- [Plist](./documentation/resource_plist.md)
- [Xcode](./documentation/resource_xcode.md)

Просмотреть файл

@ -5,5 +5,8 @@ default['macos']['mono']['package'] = 'MonoFramework-MDK-4.4.2.11.macos10.xamari
default['macos']['mono']['version'] = '4.4.2'
default['macos']['mono']['checksum'] = 'd8bfbee7ae4d0d1facaf0ddfb70c0de4b1a3d94bb1b4c38e8fa4884539f54e23'
default['macos']['xcode']['version'] = '9.0'
default['macos']['xcode']['version'] = '9.1'
default['macos']['xcode']['simulator']['major_version'] = %w(11 10)
default['macos']['network_time_server'] = 'time.windows.com'
default['macos']['time_zone'] = 'America/Los_Angeles'

Просмотреть файл

@ -0,0 +1,133 @@
ard
===
Use the **ard** resource to manage Remote Desktop settings and preferences.
Under the hood, an **ard** resource executes the `kickstart` command, located
in ARDAgent.app (one of the CoreServices of macOS). It has some basic actions,
which pertain to the simple `kickstart` subcommands. It also has the more
complicated `:configure` action, which requires some familiarity with
`kickstart`.
[Learn more about the `kickstart` command](https://support.apple.com/en-us/HT201710).
Syntax
------
A **ard** resource block declares a basic description of the command configuration
and a set of properties depending on the actions executed. For example:
```ruby
ard 'activate and configure ard' do
action [:activate, :configure]
end
```
where
- `:activate` activates the ARD agent
- `:configure` configures the agent using the `kickstart` defaut commandline arguments.
The full syntax for all of the properties that are available to the **ard**
resource is:
```ruby
ard 'description' do
install_package String
uninstall_options Array, # defaults to ['-files', '-settings', '-prefs'] if not specified
restart_options Array, # defaults to ['-agent', '-console', '-menu'] if not specified
users Array
privs Array, # defaults to ['-all'] if not specified
access String, # defaults to '-on' if not specified
allow_access_for String, # defaults to '-allUsers' if not specified
computerinfo Array
clientopts Array
action Symbol # defaults to [:activate, :configure] if not specified
end
```
**Note:** Not all properties are compatible with each action.
Actions
-------
This resource has the following actions:
`:activate`
      Activate the remote desktop agent.
`:deactivate`
      Deactivate the remote desktop agent.
`:uninstall`
      Uninstall a package from another remotely
managed mac.
`:stop`
      Stop the agent.
`:restart`
      Restart the remote desktop agent.
`:configure`
      Configure the setup of the remote desktop
agent using the default options. If you were to configure the default options,
your settings would look like this in the GUI:
Properties
----------
`install_package`
      **Ruby Type:** `String`
`uninstall_options`
      **Ruby type:** `Array`
      default options: `['-files', '-settings', '-prefs']`
`restart_options`
      **Ruby type:** `Array`
      default options: `['-agent', '-console', '-menu']`
`users`
      **Ruby type:** `Array`
`privs`
      **Ruby type:** `Array`
      default: `['-all']`
`access`
      **Ruby type:** `String`
      default: `'-on'`
`allow_access_for`
      **Ruby type:** `String`
      default: `'-allUsers'`
`computerinfo`
      **Ruby type:** `Array`
`clientopts`
      **Ruby type:** `Array`
`action`
      **Ruby type:** `Symbol`

Просмотреть файл

@ -0,0 +1,64 @@
plist
=====
Use the **plist** resource to manage property list files (plists) and their content.
A **plist** resource instance represents the state of a single key-value pair in
the delared plist `path`. Since each plist resource instance represents only one
setting, you may end up with several plist resource calls in a given recipe. Although
this may seem like overkill, it allows us to have a fully idempotent resource with
fine granularity.
During the `chef-client` run, the client knows to check the state of the plist
before changing any values. It also makes sure that the plist is in binary format
so that the settings can be interpreted correctly by the operating system.
Prior knowledge of using commandline utilities such as `/usr/bin/defaults`
and `/usr/libexec/PlistBuddy` will be useful when implementing the
**plist** resource.
[Learn more about property lists.](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/PropertyLists/QuickStartPlist/QuickStartPlist.html#//apple_ref/doc/uid/10000048i-CH4-SW5)
Syntax
------
The full syntax for all of the properties that are available to the **plist**
resource is:
```ruby
plist 'description' do
path String # defaults to 'description' if not specified
entry String
value TrueClass, FalseClass, String, Integer, Float
action Symbol # defaults to :set if not specified
end
```
Actions
-------
This resource has the following actions:
`:set`
      Set `entry` to `value` in `path`
Examples
--------
Enabling the setting to show both visible and invisible files.
```ruby
plist 'show hidden files' do
path '/Users/vagrant/Library/Preferences/com.apple.finder.plist'
entry 'AppleShowAllFiles'
value true
end
```
where
`path` is the absolute path to the `com.apple.finder.plist` plist file
`entry` is the representing the plist entry `'AppleShowAllFiles'`
`value` is the entry's value to boolean type: `true`

Просмотреть файл

@ -0,0 +1,69 @@
xcode
=====
Use the **xcode** resource to manage a single installation of Apple's Xcode IDE.
An **xcode** resource instance represents the state of a single Xcode installation
and any simulators that are declared using the `ios_simulators` property. The latest
version of iOS simulators are always installed with Xcode.
Syntax
------
The full syntax for all of the properties that are available to the **xcode**
resource is:
```ruby
xcode 'description' do
version String # defaults to 'description' if not specified
path String # defaults to '/Applications/Xcode.app' if not specified
ios_simulators Array # defaults to [10, 11] if not specified
action Symbol # defaults to [:install_xcode, :install_simulators] if not specified
end
```
Actions
-------
This resource has the following actions:
`:install_xcode`
      Set `entry` to `value` in `path`
`:install_simulators`
      Along with a `version` of Xcode,
install the declared array of the major versions of `ios_simulators`.
Examples
--------
### Basic usage
The **xcode** resource in its simplest form:
```ruby
xcode '9.2'
```
### Using with node attributes
Install different versions of Xcode based on the macOS version:
```ruby
if node['platform_version'].match?(/10\.13/) || node['platform_version'].match?(/10\.12/)
execute 'Disable Gatekeeper' do
command 'spctl --master-disable'
end
xcode '9.2' do
ios_simulators %w(11 10)
end
elsif node['platform_version'].match?(/10\.11/)
xcode '8.2.1' do
ios_simulators %w(10 9)
end
end
```

36
libraries/macos_user.rb Normal file
Просмотреть файл

@ -0,0 +1,36 @@
module MacOS
module MacOSUserHelpers
def kcpassword_hash(password)
bits = magic_bits
obfuscated = []
padded(password).each do |char|
obfuscated.push(bits[0] ^ char)
bits.rotate!
end
obfuscated.pack('C*')
end
private
def magic_bits
[125, 137, 82, 35, 210, 188, 221, 234, 163, 185, 31]
end
def magic_len
magic_bits.length
end
def padded(password)
padding = magic_len - (password.length % magic_len)
padding_size = padding > 0 ? padding : 0
translated(password) + ([0] * padding_size)
end
def translated(password)
password.split('').map(&:ord)
end
end
end
Chef::Recipe.include(MacOS::MacOSUserHelpers)
Chef::Resource.include(MacOS::MacOSUserHelpers)

90
libraries/plist.rb Normal file
Просмотреть файл

@ -0,0 +1,90 @@
module MacOS
module PlistHelpers
def convert_to_data_type_from_string(type, value)
case type
when 'boolean'
value.to_i == 1
when 'integer'
value.to_i
when 'float'
value.to_f
when 'string'
value
else
raise "Unknown or unsupported data type: #{type.class}"
end
end
def convert_to_string_from_data_type(value)
data_type_cases = { Array => "array #{value}",
Integer => "integer #{value}",
TrueClass => "bool #{value}",
FalseClass => "bool #{value}",
Hash => "dict #{value}",
String => "string #{value}",
Float => "float #{value}" }
data_type_cases[value.class]
end
def type_to_commandline_string(value)
case value
when Array
'array'
when Integer
'integer'
when FalseClass
'bool'
when TrueClass
'bool'
when Hash
'dict'
when String
'string'
when Float
'float'
else
raise "Unknown or unsupported data type: #{value} of #{value.class}"
end
end
def hardware_uuid
system_profiler_hardware_output = shell_out('system_profiler', 'SPHardwareDataType').stdout
hardware_overview = Psych.load(system_profiler_hardware_output)['Hardware']['Hardware Overview']
hardware_overview['Hardware UUID']
end
def plistbuddy_command(subcommand, entry, path, value = nil)
arg = case subcommand.to_s
when 'add'
type_to_commandline_string(value)
when 'print'
''
else
value
end
entry = "\"#{entry}\"" if entry.include?(' ')
entry_with_arg = [entry, arg].join(' ').strip
subcommand = "#{subcommand.capitalize} :#{entry_with_arg}"
[plistbuddy_executable, '-c', "\'#{subcommand}\'", path].join(' ')
end
def system_preference(path, entry)
defaults_read_type_output = shell_out(defaults_executable, 'read-type', path, entry).stdout
defaults_read_output = shell_out(defaults_executable, 'read', path, entry).stdout
{ key_type: defaults_read_type_output.split.last, key_value: defaults_read_output.strip }
end
private
def defaults_executable
'/usr/bin/defaults'
end
def plistbuddy_executable
'/usr/libexec/PlistBuddy'
end
end
end
Chef::Recipe.include(MacOS::PlistHelpers)
Chef::Resource.include(MacOS::PlistHelpers)

Просмотреть файл

@ -1,56 +1,64 @@
module Xcode
module Helper
def xcversion_command
'/opt/chef/embedded/bin/xcversion'.freeze
end
include Chef::Mixin::ShellOut
def xcode_already_installed?(semantic_version)
xcversion_output = shell_out("#{xcversion_command} installed").stdout.split
installed_xcodes = xcversion_output.values_at(*xcversion_output.each_index.select(&:even?))
installed_xcodes.include?(semantic_version)
end
module MacOS
module Xcode
class << self
def installed?(semantic_version)
xcversion_output = shell_out(XCVersion.installed_xcodes).stdout.split
installed_xcodes = xcversion_output.values_at(*xcversion_output.each_index.select(&:even?))
installed_xcodes.include?(semantic_version)
end
def xcversion_version(semantic_version)
split_version = semantic_version.split('.')
if split_version.length == 2 && split_version.last == '0'
split_version.first
else
semantic_version
def bundle_version_correct?
xcode_version = '/Applications/Xcode.app/Contents/version.plist CFBundleShortVersionString'
node['macos']['xcode']['version'] == shell_out("defaults read #{xcode_version}").stdout.strip
end
end
def requested_xcode_not_at_path
xcode_version = '/Applications/Xcode.app/Contents/version.plist CFBundleShortVersionString'
node['macos']['xcode']['version'] != shell_out("defaults read #{xcode_version}").stdout.strip
end
class Simulator
attr_reader :version
def simulator_already_installed?(semantic_version)
available_simulator_versions.include?("#{semantic_version} Simulator (installed)")
end
def highest_semantic_simulator_version(major_version, simulators)
requirement = Gem::Dependency.new('iOS', "~> #{major_version}")
highest = simulators.select { |name, vers| requirement.match?(name, vers) }.max
if highest.nil?
Chef::Application.fatal!("iOS #{major_version} Simulator no longer available from Apple!")
else
highest.join(' ')
def initialize(major_version)
while available_list.empty?
Chef::Log.warn('iOS Simulator list not populated yet')
sleep 10
end
if latest_semantic_version(major_version).nil?
Chef::Application.fatal!("iOS #{major_version} Simulator no longer available from Apple!")
else
@version = latest_semantic_version(major_version).join(' ')
end
end
end
def included_simulator_major_version
version_matcher = /\d{1,2}\.\d{0,2}\.?\d{0,3}/
sdks = shell_out!('/usr/bin/xcodebuild -showsdks').stdout
included_simulator = sdks.match(/Simulator - iOS (?<version>#{version_matcher})/)
included_simulator[:version].split('.').first.to_i
end
def latest_semantic_version(major_version)
requirement = Gem::Dependency.new('iOS', "~> #{major_version}")
available_list.select { |platform, version| requirement.match?(platform, version) }.max
end
def simulator_list
available_simulator_versions.split(/\n/).map { |version| version.split[0...2] }
end
def available_list
available_versions.split(/\n/).map { |version| version.split[0..1] }
end
def available_simulator_versions
shell_out!("#{xcversion_command} simulators").stdout
def available_versions
shell_out!(XCVersion.list_simulators).stdout
end
class << self
def installed?(semantic_version)
shell_out!(XCVersion.list_simulators)
.stdout.include?("#{semantic_version} Simulator (installed)")
end
def included_major_version
version_matcher = /\d{1,2}\.\d{0,2}\.?\d{0,3}/
sdks = shell_out!('/usr/bin/xcodebuild -showsdks').stdout
included_simulator = sdks.match(/Simulator - iOS (?<version>#{version_matcher})/)
included_simulator[:version].split('.').first.to_i
end
end
end
end
end
Chef::Recipe.include(MacOS)
Chef::Resource.include(MacOS)

45
libraries/xcversion.rb Normal file
Просмотреть файл

@ -0,0 +1,45 @@
module MacOS
module XCVersion
class << self
def xcversion
'/opt/chef/embedded/bin/xcversion '.freeze
end
def update
xcversion + 'update'
end
def list_simulators
xcversion + 'simulators'
end
def install_simulator(version)
xcversion + "simulators --install='#{version}'"
end
def list_xcodes
xcversion + 'list'
end
def install_xcode(version)
xcversion + "install '#{apple_pseudosemantic_version(version)}'"
end
def installed_xcodes
xcversion + 'installed'
end
def apple_pseudosemantic_version(semantic_version)
split_version = semantic_version.split('.')
if split_version.length == 2 && split_version.last == '0'
split_version.first
else
semantic_version
end
end
end
end
end
Chef::Recipe.include(MacOS)
Chef::Resource.include(MacOS)

Просмотреть файл

@ -5,7 +5,7 @@ license 'MIT'
description 'Resources for configuring and provisioning macOS'
long_description 'Resources for configuring and provisioning macOS'
chef_version '~> 13.0' if respond_to?(:chef_version)
version '0.9.0'
version '1.0.0'
source_url 'https://github.com/Microsoft/macos-cookbook'
issues_url 'https://github.com/Microsoft/macos-cookbook/issues'
@ -13,3 +13,5 @@ issues_url 'https://github.com/Microsoft/macos-cookbook/issues'
supports 'mac_os_x'
depends 'homebrew'
gem 'colorize'

Просмотреть файл

@ -1,4 +1,13 @@
defaults '/Library/Preferences/com.apple.SoftwareUpdate' do
settings 'AutomaticCheckEnabled' => false,
'AutomaticDownload' => false
plist 'disable automatic software update downloads' do
path '/Library/Preferences/com.apple.SoftwareUpdate.plist'
entry 'AutomaticDownload'
value false
end
sleep 10 if node['platform_version'].match?(/10\.11/)
plist 'disable automatic software update check' do
path '/Library/Preferences/com.apple.SoftwareUpdate.plist'
entry 'AutomaticCheckEnabled'
value false
end

Просмотреть файл

@ -1,41 +1,80 @@
systemsetup 'keep awake and set time with systemsetup' do
set sleep: 0,
computersleep: 0,
displaysleep: 0,
harddisksleep: 0,
remoteappleevents: 'On',
allowpowerbuttontosleepcomputer: 'Off',
waitforstartupafterpowerfailure: 0,
restartfreeze: 'On',
networktimeserver: 'time.windows.com',
timezone: 'America/Los_Angeles'
plist 'disable Power Nap' do
path '/Library/Preferences/com.apple.PowerManagement.plist'
entry 'DarkWakeBackgroundTasks'
value false
end
pmset 'keep awake with /usr/bin/pmset' do
settings sleep: 0,
hibernatemode: 0,
womp: 1, # wake on ethernet magic packet
hibernatefile: '/var/vm/sleepimage',
ttyskeepawake: 1
plist 'enable automatic restart on power loss' do
path '/Library/Preferences/com.apple.PowerManagement.plist'
entry 'Automatic Restart On Power Loss'
value true
end
defaults '/Library/Preferences/com.apple.PowerManagement' do
settings 'DarkWakeBackgroundTasks' => false, # power nap
'"Automatic Restart On Power Loss"' => true,
'"Display Sleep Timer"' => 0,
'"Disk Sleep Timer"' => 0,
'"Wake On LAN"' => true,
'"System Sleep Timer"' => 0
plist 'set display sleep timer to zero' do
path '/Library/Preferences/com.apple.PowerManagement.plist'
entry 'Display Sleep Timer'
value 0
end
ruby_block 'set power settings to autoLoginUser' do
block do
loginwindow_plist = '/Library/Preferences/com.apple.loginwindow'
auto_login_user = "defaults read #{loginwindow_plist} autoLoginUser"
node.default['macos']['power']['owner'] = shell_out!(auto_login_user).stdout.strip
end
plist 'enable wake from sleep via network' do
path '/Library/Preferences/com.apple.PowerManagement.plist'
entry 'Wake On LAN'
value true
end
execute 'disable screensaver' do
command lazy { "sudo -u #{node['macos']['power']['owner']} defaults -currentHost write com.apple.screensaver idleTime 0" }
plist 'set system sleep timer to zero' do
path '/Library/Preferences/com.apple.PowerManagement.plist'
entry 'System Sleep Timer'
value 0
end
plist 'disable screensaver' do
path "/Users/#{node['macos']['admin_user']}/Library/Preferences/ByHost/com.apple.screensaver.#{hardware_uuid}.plist"
entry 'idleTime'
value 0
end
systemsetup 'Set amount of idle time until computer sleeps to never' do
setting 'computersleep'
value 'Never'
end
systemsetup 'set amount of idle time until display sleeps to never' do
setting 'displaysleep'
value 'Never'
end
systemsetup 'set amount of idle time until hard disk sleeps to never' do
setting 'harddisksleep'
value 'Never'
end
systemsetup 'set remote apple events to on' do
setting 'remoteappleevents'
value 'On'
end
systemsetup 'disable the power button from being able to sleep the computer.' do
setting 'allowpowerbuttontosleepcomputer'
value 'Off'
end
systemsetup 'set the number of seconds after which the computer will start up after a power failure to zero.' do
setting 'waitforstartupafterpowerfailure'
value 0
end
systemsetup 'set restart on freeze to on' do
setting 'restartfreeze'
value 'On'
end
systemsetup "set network time server to #{node['macos']['network_time_server']}" do
setting 'networktimeserver'
value node['macos']['network_time_server']
end
systemsetup "set current time zone to #{node['macos']['time_zone']}" do
setting 'timezone'
value node['macos']['time_zone']
end

Просмотреть файл

@ -1,24 +1,24 @@
resource_name :name
resource_name :machine_name
default_action :run
BASE_COMMAND = '/usr/sbin/scutil'.freeze
SMB_SERVER_PLIST = '/Library/Preferences/SystemConfiguration/com.apple.smb.server'.freeze
property :name, String, name_property: true
property :machine_name, String, name_property: true
# We cannot set the LocalHostName here because it does not conform to
# the DNS standards outlined in RFC 1034 (section 3.5)
action :run do
execute BASE_COMMAND do
command "#{BASE_COMMAND} --set HostName '#{new_resource.name}'"
command "#{BASE_COMMAND} --set HostName '#{new_resource.machine_name}'"
end
execute BASE_COMMAND do
command "#{BASE_COMMAND} --set ComputerName '#{new_resource.name}'"
command "#{BASE_COMMAND} --set ComputerName '#{new_resource.machine_name}'"
end
defaults SMB_SERVER_PLIST do
settings 'NetBIOSName' => new_resource.name
settings 'NetBIOSName' => new_resource.machine_name
end
end

78
resources/macos_user.rb Normal file
Просмотреть файл

@ -0,0 +1,78 @@
resource_name :macos_user
default_action :create
property :username, String, name_property: true
property :password, String, default: 'password'
property :autologin, [TrueClass]
property :admin, [TrueClass]
action_class do
def user_home
::File.join('/Users', new_resource.username)
end
def setup_assistant_plist
::File.join(user_home, '/Library/Preferences/com.apple.SetupAssistant.plist')
end
def setup_assistant_keypair_values
{ 'DidSeeSiriSetup' => true,
'DidSeeCloudSetup' => true,
'LastSeenCloudProductVersion' => node['platform_version'] }
end
def sysadminctl
'/usr/sbin/sysadminctl'
end
def admin_user?
if property_is_set?(:admin)
'-admin'
else
''
end
end
end
action :create do
execute "add user #{new_resource.username}" do
command "#{sysadminctl} -addUser #{new_resource.username} -password #{new_resource.password} #{admin_user?}"
not_if { ::File.exist? user_home }
end
if property_is_set?(:autologin)
setup_assistant_keypair_values.each do |key, setting|
plist "set #{setting} to #{key}" do
path setup_assistant_plist
entry key
value setting
end
end
plist "set user \"#{new_resource.username}\" to login automatically" do
path '/Library/Preferences/com.apple.loginwindow.plist'
entry 'autoLoginUser'
value new_resource.username
end
file '/etc/kcpassword' do
content kcpassword_hash(new_resource.password)
owner 'root'
group 'wheel'
mode '0600'
end
end
end
action :delete do
directory user_home do
recursive true
action :delete
only_if { ::File.exist? user_home }
end
execute "delete user: #{user}" do
command "#{sysadminctl} -deleteUser #{new_resource.username}"
only_if { "/usr/bin/dscl . -list /users | grep ^#{new_resource.username}$" }
end
end

39
resources/plist.rb Normal file
Просмотреть файл

@ -0,0 +1,39 @@
resource_name :plist
property :path, String, name_property: true
property :entry, String, desired_state: true
property :value, [TrueClass, FalseClass, String, Integer, Float], desired_state: true
action_class do
def binary?
file_type_output = shell_out('/usr/bin/file', '--brief', '--mime-encoding', new_resource.path).stdout
file_type_output.strip == 'binary'
end
end
load_current_value do |desired|
system_preference = system_preference(desired.path, desired.entry)
current_value_does_not_exist! if system_preference[:key_type].nil?
entry desired.entry unless system_preference[:key_type].nil?
value convert_to_data_type_from_string(system_preference[:key_type], system_preference[:key_value])
end
action :set do
converge_if_changed :entry do
converge_by "add \"#{new_resource.entry}\" to #{new_resource.path.split('/').last}" do
execute plistbuddy_command(:add, new_resource.entry, new_resource.path, new_resource.value)
end
end
converge_if_changed :value do
converge_by "set \"#{new_resource.entry}\" to #{new_resource.value} at #{new_resource.path.split('/').last}" do
execute plistbuddy_command(:set, new_resource.entry, new_resource.path, new_resource.value)
end
end
unless binary?
converge_by "convert \"#{new_resource.path.split('/').last}\" to binary" do
execute "/usr/bin/plutil -convert binary1 #{new_resource.path}"
end
end
end

Просмотреть файл

@ -1,26 +1,32 @@
require 'colorize'
resource_name :systemsetup
default_action :run
BASE_COMMAND = '/usr/sbin/systemsetup'.freeze
property :setting, desired_state: false
property :value, desired_state: true, coerce: proc { |m| m.to_s }
property :get, Array
property :set, Hash
default_action :set
action :run do
if new_resource.get
new_resource.get.each do |flag|
execute BASE_COMMAND do
command "#{BASE_COMMAND} -get#{flag}"
live_stream true
end
end
load_current_value do |desired|
command_output = shell_out('/usr/sbin/systemsetup', "-get#{desired.setting}").stdout.split(': ').last.strip
if command_output.include?('after')
value command_output.split[1]
elsif command_output.end_with?('seconds')
value command_output.split.first
elsif command_output.start_with?('Error')
puts "\n"
warn(command_output.to_s.colorize(:red).bold)
warn(desired.setting.to_s.colorize(:red))
value desired.value
else
value command_output.split.last
end
end
if new_resource.set
new_resource.set.each do |flag, setting|
execute BASE_COMMAND do
command "#{BASE_COMMAND} -set#{flag} #{setting}"
end
action :set do
converge_if_changed do
converge_by "set #{new_resource.setting} to #{new_resource.value}" do
execute "/usr/sbin/systemsetup -set#{new_resource.setting} #{new_resource.value}"
end
end
end

Просмотреть файл

@ -1,5 +1,3 @@
include Xcode::Helper
resource_name :xcode
default_action %i(setup install_xcode install_simulators)
@ -11,9 +9,7 @@ action :setup do
chef_gem 'xcode-install' do
options('--no-document')
end
end
action :install_xcode do
CREDENTIALS_DATA_BAG = data_bag_item(:credentials, :apple_id)
DEVELOPER_CREDENTIALS = {
@ -23,26 +19,28 @@ action :install_xcode do
execute 'update available Xcode versions' do
environment DEVELOPER_CREDENTIALS
command "#{xcversion_command} update"
command XCVersion.update
end
end
action :install_xcode do
execute "install Xcode #{new_resource.version}" do
environment DEVELOPER_CREDENTIALS
command "#{xcversion_command} install '#{xcversion_version(new_resource.version)}'"
not_if { xcode_already_installed?(new_resource.version) }
command XCVersion.install_xcode(new_resource.version)
not_if { Xcode.installed?(new_resource.version) }
end
end
action :install_simulators do
if new_resource.ios_simulators
new_resource.ios_simulators.each do |major_version|
next if major_version.to_i >= included_simulator_major_version
version = highest_semantic_simulator_version(major_version, simulator_list)
next if major_version.to_i >= Xcode::Simulator.included_major_version
version = Xcode::Simulator.new(major_version).version
execute "install latest iOS #{major_version} Simulator" do
environment DEVELOPER_CREDENTIALS
command "#{xcversion_command} simulators --install='#{version}'"
not_if { simulator_already_installed?(version) }
command XCVersion.install_simulator(version)
not_if { Xcode::Simulator.installed?(version) }
end
end
end

Просмотреть файл

@ -1,2 +1,11 @@
require 'chefspec'
require 'chefspec/berkshelf'
require_relative '../libraries/macos_user'
require_relative '../libraries/plist'
require_relative '../libraries/xcode'
RSpec.configure do |config|
config.platform = 'mac_os_x'
config.version = '10.12'
end

Просмотреть файл

@ -0,0 +1,17 @@
require 'spec_helper'
include MacOS::MacOSUserHelpers
describe MacOS::MacOSUserHelpers, '#kcpassword_hash' do
context 'When calling the obfuscate method on a short password' do
it 'the password is obfuscated correctly' do
expect(kcpassword_hash('password')).to eq "\r\xE8!P\xA5\xD3\xAF\x8E\xA3\xB9\x1F".force_encoding('ASCII-8BIT')
end
end
context 'When calling the obfuscate method on a long password' do
xit 'the password hash value matches the binary content of the file' do
expect(kcpassword_hash('correct-horse-battery-staple')).to eq "\x1E\xE6 Q\xB7\xDF\xA9\xC7\xCB\xD6m\x0E\xEC\x7FA\xB3\xC8\xA9\x8F\xD1\xC02\x0E\xFD3S\xBE\xD9\xAE\x9F\xC7\xD6\x1F".force_encoding('ASCII-8BIT')
end
end
end

Просмотреть файл

@ -0,0 +1,139 @@
require 'spec_helper'
include MacOS::PlistHelpers
describe MacOS::PlistHelpers, '#plist_command' do
context 'Adding a value to a plist' do
it 'the bool arguments contain the data type' do
expect(plistbuddy_command(:add, 'FooEntry', 'path/to/file.plist', true)).to eq "/usr/libexec/PlistBuddy -c 'Add :FooEntry bool' path/to/file.plist"
end
it 'the add command only adds the data type' do
expect(plistbuddy_command(:add, 'QuuxEntry', 'path/to/file.plist', 50)).to eq "/usr/libexec/PlistBuddy -c 'Add :QuuxEntry integer' path/to/file.plist"
end
it 'the delete command is formatted properly' do
expect(plistbuddy_command(:delete, 'BarEntry', 'path/to/file.plist')).to eq "/usr/libexec/PlistBuddy -c 'Delete :BarEntry' path/to/file.plist"
end
it 'the set command is formatted properly' do
expect(plistbuddy_command(:set, 'BazEntry', 'path/to/file.plist', false)).to eq "/usr/libexec/PlistBuddy -c 'Set :BazEntry false' path/to/file.plist"
end
it 'the print command is formatted properly' do
expect(plistbuddy_command(:print, 'QuxEntry', 'path/to/file.plist')).to eq "/usr/libexec/PlistBuddy -c 'Print :QuxEntry' path/to/file.plist"
end
end
context 'The value provided contains spaces' do
it 'returns the value properly formatted with double quotes' do
expect(plistbuddy_command(:print, 'Foo Bar Baz', 'path/to/file.plist')).to eq "/usr/libexec/PlistBuddy -c 'Print :\"Foo Bar Baz\"' path/to/file.plist"
end
end
end
describe MacOS::PlistHelpers, '#convert_to_data_type_from_string' do
context 'When the type is boolean and given a 1 or 0' do
it 'returns true if entry is 1' do
expect(convert_to_data_type_from_string('boolean', '1')).to eq true
end
it 'returns false if entry is 0' do
expect(convert_to_data_type_from_string('boolean', '0')).to eq false
end
end
context 'When the type is integer and the value is 1' do
it 'returns the value as an integer' do
expect(convert_to_data_type_from_string('integer', '1')).to eq 1
end
end
context 'When the type is integer and the value is 0' do
it 'returns the value as an integer' do
expect(convert_to_data_type_from_string('integer', '0')).to eq 0
end
end
context 'When the type is integer and the value is 950224' do
it 'returns the correct value as an integer' do
expect(convert_to_data_type_from_string('integer', '950224')).to eq 950224
end
end
context 'When the type is string and the value is also a string' do
it 'returns the correct value still as a string' do
expect(convert_to_data_type_from_string('string', 'corge')).to eq 'corge'
end
end
context 'When the type is float and the value is 3.14159265359' do
it 'returns the correct value as a float' do
expect(convert_to_data_type_from_string('float', '3.14159265359')).to eq 3.14159265359
end
end
context 'When the type nor the value is given' do
it 'find the value from somewhere else' do
expect { convert_to_data_type_from_string(nil, '') }.to raise_error(RuntimeError)
end
end
end
describe MacOS::PlistHelpers, '#type_to_commandline_string' do
context 'When given a certain data type' do
it 'returns the required boolean entry type as a string' do
expect(type_to_commandline_string(true)).to eq 'bool'
end
it 'returns the required array entry type as a string' do
expect(type_to_commandline_string(%w(foo bar))).to eq 'array'
end
it 'returns the required dictionary entry type as a string' do
expect(type_to_commandline_string('baz' => 'qux')).to eq 'dict'
end
it 'returns the required string entry type as a string' do
expect(type_to_commandline_string('quux')).to eq 'string'
end
it 'returns the required integer entry type as a string' do
expect(type_to_commandline_string(1)).to eq 'integer'
end
it 'returns the required float entry type as a string' do
expect(type_to_commandline_string(1.0)).to eq 'float'
end
end
end
describe MacOS::PlistHelpers, '#convert_to_string_from_data_type' do
context 'When given a certain data type' do
it 'returns the required boolean entry' do
expect(convert_to_string_from_data_type(true)).to eq 'bool true'
end
# TODO: Skip until proper plist array syntax is implemented (i.e. containers)
xit 'returns the required array entry' do
expect(convert_to_string_from_data_type(%w(foo bar))).to eq 'array foo bar'
end
# TODO: Skip until proper plist dict syntax is implemented (i.e. containers)
xit 'returns the required dictionary entry' do
expect(convert_to_string_from_data_type('baz' => 'qux')).to eq 'dict key value'
end
it 'returns the required string entry' do
expect(convert_to_string_from_data_type('quux')).to eq 'string quux'
end
it 'returns the required integer entry' do
expect(convert_to_string_from_data_type(1)).to eq 'integer 1'
end
it 'returns the required float entry' do
expect(convert_to_string_from_data_type(1.0)).to eq 'float 1.0'
end
end
end

Просмотреть файл

@ -5,13 +5,13 @@ describe 'macos::configurator' do
before(:each) do
stub_data_bag_item('credentials', 'apple_id').and_return(
apple_id: 'developer@apple.com',
password: 'apple_id_password')
password: 'apple_id_password'
)
stub_command('which git').and_return('/usr/local/bin/git')
stub_command('/usr/local/bin/mas account').and_return('developer@apple.com')
end
let(:chef_run) do
runner = ChefSpec::SoloRunner.new(platform: 'mac_os_x', version: '10.12')
runner.converge(described_recipe)
end
let(:chef_run) { ChefSpec::SoloRunner.new.converge(described_recipe) }
it 'converges successfully' do
expect { chef_run }.to_not raise_error

Просмотреть файл

@ -2,10 +2,7 @@ require 'spec_helper'
describe 'macos::default' do
context 'When all attributes are default, on macOS 10.12' do
let(:chef_run) do
runner = ChefSpec::ServerRunner.new(platform: 'mac_os_x', version: '10.12')
runner.converge(described_recipe)
end
let(:chef_run) { ChefSpec::SoloRunner.new.converge(described_recipe) }
it 'converges successfully' do
expect { chef_run }.to_not raise_error

Просмотреть файл

@ -0,0 +1,11 @@
require 'spec_helper'
describe 'macos::disable_software_updates' do
context 'When all attributes are default, on macOS 10.12' do
let(:chef_run) { ChefSpec::SoloRunner.new.converge(described_recipe) }
it 'converges successfully' do
expect { chef_run }.to_not raise_error
end
end
end

Просмотреть файл

@ -2,10 +2,7 @@ require 'spec_helper'
describe 'macos::keep_awake' do
context 'When all attributes are default, on macOS 10.12' do
let(:chef_run) do
runner = ChefSpec::ServerRunner.new(platform: 'mac_os_x', version: '10.12')
runner.converge(described_recipe)
end
let(:chef_run) { ChefSpec::SoloRunner.new.converge(described_recipe) }
it 'converges successfully' do
expect { chef_run }.to_not raise_error

103
test/cookbooks/macos_test/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,103 @@
*~
*#
.#*
\#*#
.*.sw[a-z]
*.un~
# Bundler
Gemfile
Gemfile.lock
bin/*
.bundle/*
# test kitchen
.kitchen/
.kitchen.local.yml
# Chef
Berksfile.lock
.zero-knife.rb
Policyfile.lock.json
.autotest
coverage
.DS_Store
pkg/*
tags
*/tags
.chef
results
# You should check in your Gemfile.lock in applications, and not in gems
external_tests/*.lock
/Gemfile.local
# ignore some common Bundler 'binstubs' directory names
# http://gembundler.com/man/bundle-exec.1.html
b/
binstubs/
.bundle
# RVM and RBENV ruby version files
.rbenv-version
.rvmrc
.ruby-version
.ruby-gemset
# IDE files
.project
# Documentation
_site/*
.yardoc/
doc/
# Kitchen Tests Local Mode Data
kitchen-tests/nodes/*
# Temporary files present during spec runs
spec/data/test-dir
spec/data/nodes
/config/
# acceptance binstubs
acceptance/bin/*
vendor/
acceptance/vendor
kitchen-tests/vendor
# Visual Studio Code files
.vscode
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
.idea
# CMake
cmake-build-debug/
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Testing
*.box
berks-cookbooks
Vagrantfile
.rubocop.yml
.vagrant
data_bags

Просмотреть файл

@ -0,0 +1,36 @@
---
driver:
name: vagrant
provider: parallels
provisioner:
name: chef_zero
always_update_cookbooks: true
verifier:
name: inspec
sudo: true
platforms:
- name: apex/macos-10.11.6
- name: apex/macos-10.12.6
- name: apex/macos-10.13
suites:
- name: default
run_list:
- recipe[macos::keep_awake]
- recipe[macos_test::new_users]
- recipe[macos::disable_software_updates]
- recipe[macos_test::preferences]
- recipe[macos_test::machine_name]
verifier:
inspec_tests:
- test/smoke/default
- name: xcode
run_list:
- recipe[macos_test::xcode]
verifier:
inspec_tests:
- test/smoke/xcode

Просмотреть файл

@ -0,0 +1,5 @@
source 'https://supermarket.chef.io'
metadata
cookbook 'macos', path: './../../../'

Просмотреть файл

@ -0,0 +1,4 @@
name 'macos_test'
version '1.0.1'
depends 'macos'

Просмотреть файл

@ -0,0 +1 @@
machine_name "New#{node['platform_version']}_Washing_Machine"

Просмотреть файл

@ -0,0 +1,11 @@
macos_user 'create admin user randall and enable automatic login' do
username 'randall'
password 'correct-horse-battery-staple'
autologin true
admin true
end
macos_user 'create non-admin user johnny' do
username 'johnny'
password 'yang-yolked-cordon-karate'
end

Просмотреть файл

@ -0,0 +1,17 @@
plist 'show hidden files' do
path '/Users/vagrant/Library/Preferences/com.apple.finder.plist'
entry 'AppleShowAllFiles'
value true
end
plist 'put the Dock on the left side' do
path '/Users/vagrant/Library/Preferences/com.apple.dock.plist'
entry 'orientation'
value 'left'
end
plist 'disable window animations and Get Info animations' do
path '/Users/vagrant/Library/Preferences/com.apple.dock.plist'
entry 'DisableAllAnimations'
value true
end

Просмотреть файл

@ -0,0 +1,15 @@
if node['platform_version'].match?(/10\.13/) || node['platform_version'].match?(/10\.12/)
execute 'Disable Gatekeeper' do
command 'spctl --master-disable'
end
xcode '9.2' do
ios_simulators %w(11 10)
end
elsif node['platform_version'].match?(/10\.11/)
xcode '8.2.1' do
ios_simulators %w(10 9)
end
end

Просмотреть файл

@ -0,0 +1,27 @@
software_update_plist = '/Library/Preferences/com.apple.SoftwareUpdate.plist'
automatic_check_enabled = 'AutomaticCheckEnabled'
automatic_download = 'AutomaticDownload'
describe command("/usr/libexec/PlistBuddy -c 'Print :#{automatic_check_enabled}' #{software_update_plist}") do
its('stdout') { should match('false') }
end
describe command("/usr/libexec/PlistBuddy -c 'Print :#{automatic_download}' #{software_update_plist}") do
its('stdout') { should match('false') }
end
describe command("/usr/bin/defaults read-type #{software_update_plist} #{automatic_download}") do
its('stdout') { should match('boolean') }
end
describe command("/usr/bin/defaults read-type #{software_update_plist} #{automatic_check_enabled}") do
its('stdout') { should match('boolean') }
end
describe command("/usr/bin/defaults read #{software_update_plist} #{automatic_download}") do
its('stdout') { should match('0') }
end
describe command("/usr/bin/defaults read #{software_update_plist} #{automatic_check_enabled}") do
its('stdout') { should match('0') }
end

Просмотреть файл

@ -0,0 +1,60 @@
control 'power' do
desc 'Display, machine and hard disk never sleep, the machine automatically restarts
after a freeze or power outage, and Power Nap'
%w(sleep computersleep displaysleep harddisksleep).each do |setting|
describe command("systemsetup -get#{setting}") do
its('stdout') { should match('Never') }
end
end
%w(restartfreeze remoteappleevents).each do |setting|
describe command("systemsetup -get#{setting}") do
its('stdout') { should match('On') }
end
end
describe command('systemsetup -getwaitforstartupafterpowerfailure') do
its('stdout') { should match('0 seconds') }
end
describe command('pmset -g') do
its('stdout') { should match(/sleep\s*0/) }
its('stdout') { should match(/hibernatemode\s*0$/) }
its('stdout') { should match(/ttyskeepawake\s*1/) }
its('stdout') { should match(%r{hibernatefile\s*\/var\/vm\/sleepimage}) }
its('stdout') { should match(/disksleep\s*0/) }
its('stdout') { should match(/displaysleep\s*0/) }
its('stdout') { should match(/ttyskeepawake\s*1/) }
end
describe command("/usr/libexec/PlistBuddy -c 'Print :DarkWakeBackgroundTasks' '/Library/Preferences/com.apple.PowerManagement.plist'") do
its('stdout') { should match('false') }
end
end
control 'screensaver' do
desc 'screensaver is disabled'
def hardware_uuid
system_profiler_hardware_output = `system_profiler SPHardwareDataType`
hardware_overview = Psych.load(system_profiler_hardware_output)['Hardware']['Hardware Overview']
hardware_overview['Hardware UUID']
end
describe command("/usr/libexec/PlistBuddy -c 'Print :idleTime' /Users/vagrant/Library/Preferences/ByHost/com.apple.screensaver.#{hardware_uuid}.plist"), :skip do
its('stdout') { should match(/0/) }
end
describe command('su vagrant -c "/usr/bin/defaults -currentHost read com.apple.screensaver idleTime"') do
its('stdout') { should match(/0/) }
end
describe command('su vagrant -c "/usr/bin/defaults -currentHost read-type com.apple.screensaver idleTime"') do
its('stdout') { should match(/integer/) }
end
describe command("file --brief --mime /Users/vagrant/Library/Preferences/ByHost/com.apple.screensaver.#{hardware_uuid}.plist"), :skip do
its('stdout') { should match(/binary/) }
end
end

Просмотреть файл

@ -0,0 +1,16 @@
control 'machine name' do
desc 'machine name is set to the format "New#{macos_semantic_version}_Washing_Machine"'
macos_semantic_version = command('sw_vers -productVersion').stdout.strip
hostname_pattern = /New#{macos_semantic_version}_Washing_Machine/
hostname_commands = ['hostname',
'scutil --get ComputerName',
'scutil --get HostName']
hostname_commands.each do |hostname_command|
describe command(hostname_command) do
its('stdout') { should match hostname_pattern }
end
end
end

Просмотреть файл

@ -0,0 +1,17 @@
control 'new macOS users' do
desc 'they exist with the expected characteristics'
describe user('randall') do
it { should exist }
its('uid') { should eq 503 }
its('gid') { should eq 20 }
its('home') { should eq '/Users/randall' }
end
describe user('johnny') do
it { should exist }
its('uid') { should eq 504 }
its('gid') { should eq 20 }
its('home') { should eq '/Users/johnny' }
end
end

Просмотреть файл

@ -0,0 +1,15 @@
control 'macOS user preferences' do
desc 'they are set to the correct values'
describe command("/usr/libexec/PlistBuddy -c 'Print :orientation' /Users/vagrant/Library/Preferences/com.apple.dock.plist") do
its('stdout') { should match 'left' }
end
describe command("/usr/libexec/PlistBuddy -c 'Print :AppleShowAllFiles' /Users/vagrant/Library/Preferences/com.apple.finder.plist") do
its('stdout') { should match 'true' }
end
describe command("/usr/libexec/PlistBuddy -c 'Print :DisableAllAnimations' /Users/vagrant/Library/Preferences/com.apple.dock.plist") do
its('stdout') { should match 'true' }
end
end

Просмотреть файл

@ -0,0 +1,27 @@
control 'xcode' do
desc 'application Xcode exists and Developer mode is enabled'
describe file('/Applications/Xcode.app') do
it { should exist }
it { should be_symlink }
end
if os[:release].match?(/10\.13/) || os[:release].match?(/10\.12/)
describe directory('/Applications/Xcode-9.2.app') do
it { should exist }
end
describe command('/opt/chef/embedded/bin/xcversion simulators') do
its('stdout') { should match(/iOS 10\.3\.1 Simulator \(installed\)/) }
end
elsif os[:release].match?(/10\.11/)
describe directory('/Applications/Xcode-8.2.1.app') do
it { should exist }
end
describe command('/opt/chef/embedded/bin/xcversion simulators') do
its('stdout') { should match(/iOS 9\.3 Simulator \(installed\)/) }
end
end
end

Просмотреть файл

@ -1,16 +0,0 @@
control 'xcode' do
desc 'application Xcode exists and Developer mode is enabled'
describe file '/Applications/Xcode.app' do
it { should exist }
it { should be_symlink }
end
describe directory '/Applications/Xcode-9.app' do
it { should exist }
end
describe command('/usr/local/bin/xcversion simulators') do
its('stdout') { should match /iOS 10\.3\.1 Simulator \(installed\)/ }
end
end