From 2f40674d787da2216772e5d5f2f64bcd963c63dc Mon Sep 17 00:00:00 2001 From: goodrum Date: Mon, 7 May 2018 12:38:54 -0500 Subject: [PATCH] Version 2.0 - Proxy and Upgrade Support (#2) ADD: Recipe::ProxyServer to handle installing proxy prerequisites and registration of the Proxy Server ADD: Recipe::ProxyRemove to handle proxy unregistration ADD: Resource::VeeamProxy ADD: Spec::VeeamProxy to test the custom_resource UPDATE: Rubocop.yml to set the TargetVersion to 2.3 and disable Style/FrozenStringLiteralComment ADD: Recipe::Upgrade to perform upgrades of Veeam components ADD: Resource::Upgrade ADD: Spec::Upgrade to test the custom_resource Refactor Veeam::Helper library and methods. Convert from Class to Module UPDATE: Resources to leverage DRY changes to Veeam::Helper UPDATE: Recipe::StandaloneComplete to include new VeeamUpgrade resource UPDATE: Resource::Prequisites to leverage a Windows Task for installation of SQL Express ADD: CHANGELOG.md BUMP: Metadata to version 2.0.0 --- .kitchen.yml | 40 +++ .rubocop.yml | 4 + CHANGELOG.md | 46 +++ README.md | 305 +++++++++++++++-- Rakefile | 6 + attributes/default.rb | 12 + attributes/proxy.rb | 21 ++ libraries/helper.rb | 197 ++++++++++- libraries/matchers.rb | 14 + metadata.rb | 2 +- recipes/proxy_remove.rb | 30 ++ recipes/proxy_server.rb | 68 ++++ recipes/standalone_complete.rb | 9 + recipes/upgrade.rb | 24 ++ resources/catalog.rb | 74 +---- resources/console.rb | 74 +---- resources/explorer.rb | 77 +---- resources/prerequisites.rb | 191 ++++++----- resources/proxy.rb | 310 ++++++++++++++++++ resources/server.rb | 78 +---- resources/upgrade.rb | 210 ++++++++++++ spec/unit/lwrps/veeam_prerequisites_spec.rb | 4 + spec/unit/lwrps/veeam_proxy_add_spec.rb | 134 ++++++++ spec/unit/lwrps/veeam_proxy_remove_spec.rb | 134 ++++++++ spec/unit/lwrps/veeam_upgrade_spec.rb | 117 +++++++ spec/unit/recipes/console_spec.rb | 6 +- spec/unit/recipes/proxy_remove_spec.rb | 76 +++++ spec/unit/recipes/proxy_server_spec.rb | 90 +++++ spec/unit/recipes/standalone_complete_spec.rb | 2 + spec/unit/recipes/upgrade_spec.rb | 70 ++++ templates/sql_server/sql_build_script.ps1.erb | 34 ++ 31 files changed, 2054 insertions(+), 405 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 attributes/proxy.rb create mode 100644 recipes/proxy_remove.rb create mode 100644 recipes/proxy_server.rb create mode 100644 recipes/upgrade.rb create mode 100644 resources/proxy.rb create mode 100644 resources/upgrade.rb create mode 100644 spec/unit/lwrps/veeam_proxy_add_spec.rb create mode 100644 spec/unit/lwrps/veeam_proxy_remove_spec.rb create mode 100644 spec/unit/lwrps/veeam_upgrade_spec.rb create mode 100644 spec/unit/recipes/proxy_remove_spec.rb create mode 100644 spec/unit/recipes/proxy_server_spec.rb create mode 100644 spec/unit/recipes/upgrade_spec.rb create mode 100644 templates/sql_server/sql_build_script.ps1.erb diff --git a/.kitchen.yml b/.kitchen.yml index 58d6e13..033b36f 100644 --- a/.kitchen.yml +++ b/.kitchen.yml @@ -111,3 +111,43 @@ suites: accept_eula: true console: accept_eula: true + - name: proxy_server + run_list: + - recipe[veeam::proxy_server] + verifier: + inspec_tests: + - test/inspec/9.5.0.711/console + attributes: + veeam: + version: '9.5' + console: + accept_eula: true + proxy: + register: false + - name: proxy_remove + run_list: + - recipe[veeam::proxy_remove] + verifier: + inspec_tests: + - test/inspec/9.5.0.711/console + attributes: + veeam: + version: '9.5' + console: + accept_eula: true + proxy: + register: false + - name: upgrade + run_list: + - recipe[veeam::server] + - recipe[veeam::upgrade] + verifier: + inspec_tests: + - test/inspec/9.5.0.711/server + attributes: + veeam: + version: '9.5.0.1536' + server: + accept_eula: true + console: + accept_eula: true diff --git a/.rubocop.yml b/.rubocop.yml index d078dd6..a75cf18 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,9 @@ +AllCops: + TargetRubyVersion: 2.3 Metrics/LineLength: Max: 120 +Style/FrozenStringLiteralComment: + Enabled: false Style/TrailingCommaInLiteral: # ChefAutomate will throw an error due to chages in protocol after # Rubocop 0.39.0. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8ecd574 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,46 @@ +# Change log information for Veeam Cookbook + +## Version 2.0 +2018-05-04 + +Major update to the Veeam cookbook with full testing against Chef client 14.0.202. This update includes two new custom resources - VeeamProxy and VeeamUpgrade. Along with the new resources, a complete refactor of existing resources was completed to consolidate the helper methods into a single library. + +### Included: +- Veeam Backup and Replication ProxyServer deployments +- Support for automatic Veeam Backup and Replication component Updates +- Refactor of existing Custom Resources and Helper Library + +### Information on new resources: + +* **VeeamProxy** - Configures a Windows server as either a VMware or HyperV Backup Proxy by connecting to the Veeam Backup and Replication server via the Veeam PowerShell toolkit. This resource will add a Veeam credential object if one does not exist and then register the server as a Proxy Type. +* **VeeamUpgrade** - The process to perform upgrades requires that the appropriate installation media is provided which contains the updates from Veeam. This cookbook will initiate an upgrade if the currently installed versions are less than the desired Build version as defined by the attribute `node['veeam']['build']`. When the installed version does not match the requested build version, the process will mount the ISO or extract the ZIP that contains the update and then perform an automatic upgrade of each service installed on the host. + +### Major Updates: +- Veeam::Helper refactor +- SQL Express installation via Windows Scheduled task (resolves issues with remote builds on domain joined servers) + + +This cookbook is not officially supported by Veeam Software + +## Version 1.0 +2017-11-05 + +Initial release of the Veeam Cookbook including support for the new provisioning + +### Included: +- Veeam Backup and Replication Server deployments +- Veeam Backup and Replication Catalog deployments +- Veeam Backup and Replication Console deployments +- Veeam Backup and Replication Prerequisite deployments +- Custom Resources and Helper Library + + +### Information on new resources: + +* **VeeamPrerequisites** - Deploys all Veeam Prerequisite packages including SQL Tools, .NET 4.5, and SQL Express. +* **VeeamConsole** - Installs the Veeam Backup and Replication Console +* **VeeamCatalog** - Installs the Veeam Backup and Replication Catalog +* **VeeamServer** - Installs the Veeam Backup and Replication Server +* **VeeamExplorers** - Installs the Veeam Backup and Replication Explorers based on those available in the Installation Media. + +This cookbook is not officially supported by Veeam Software diff --git a/README.md b/README.md index 52f9eb6..3925b50 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ This cookbook installs and configures Veeam Backup and Replication based on docu _Note: Veeam prerequisites requires that Microsoft .NET Framework 4.5.2 be installed on the host. As part of the installation, a reboot is required and will automatically be handled by the resource_ ## Table of Contents - - *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [Requirements](#requirements) @@ -16,17 +14,27 @@ _Note: Veeam prerequisites requires that Microsoft .NET Framework 4.5.2 be insta - [Cookbooks](#cookbooks) - [Attributes](#attributes) - [Installation Media](#installation-media) + - [Upgrade Details](#upgrade-details) - [Catalog](#catalog) - [Server](#server) - [Console](#console) -- [Veeam Backup and Replication ISO](#veeam-backup-and-replication-iso) -- [Veeam Backup and Replication License file](#veeam-backup-and-replication-license-file) + - [Proxy](#proxy) +- [Veeam Media and Licenses](#veeam-media-and-licenses) + - [Veeam Backup and Replication ISO](#veeam-backup-and-replication-iso) + - [Veeam Backup and Replication Update Zip files](#veeam-backup-and-replication-update-zip-files) + - [Veeam Backup and Replication License file](#veeam-backup-and-replication-license-file) +- [Veeam Upgrade Procedures and Details](#veeam-upgrade-procedures-and-details) + - [Configuring the Updates](#configuring-the-updates) + - [Upgrade Process and Warnings](#upgrade-process-and-warnings) + - [Recipes that perform automatic upgrades](#recipes-that-perform-automatic-upgrades) - [Resource/Provider](#resourceprovider) - [Veeam_Prerequisites](#veeam_prerequisites) - [Veeam_Catalog](#veeam_catalog) - [Veeam_Console](#veeam_console) - [Veeam_Server](#veeam_server) - [Veeam_Explorer](#veeam_explorer) + - [Veeam_Upgrade](#veeam_upgrade) + - [Veeam_Proxy](#veeam_proxy) - [Usage](#usage) - [default](#default) - [catalog recipe](#catalog-recipe) @@ -35,6 +43,9 @@ _Note: Veeam prerequisites requires that Microsoft .NET Framework 4.5.2 be insta - [server_with_catalog recipe](#server_with_catalog-recipe) - [server_with_console recipe](#server_with_console-recipe) - [standalone_complete recipe](#standalone_complete-recipe) + - [proxy recipe](#proxy-recipe) + - [proxy_remove recipe](#proxy_remove-recipe) + - [upgrade recipe](#upgrade-recipe) - [Upload to Chef Server](#upload-to-chef-server) - [Matchers/Helpers](#matchershelpers) - [Matchers](#matchers) @@ -49,8 +60,6 @@ _Note: Veeam prerequisites requires that Microsoft .NET Framework 4.5.2 be insta - [Contribute](#contribute) - [License and Author](#license-and-author) - - ## Requirements ### Platforms @@ -60,6 +69,9 @@ _Note: Veeam prerequisites requires that Microsoft .NET Framework 4.5.2 be insta Windows 2008R2 and lower is _not_ supported. +#### _Note regarding SQL Express Requirements:_ +The installation of SQL Express requires that a temporary Scheduled Task be created within Windows to perform the installation. The reason for this workaround is due to a known limitation with Microsoft SQL installations via remote powershell executions. This enhancement has been added to allow for the ability to perform the installation via Terraform, Knife Bootstrap, or any other remote powershell based execution process. + ### Chef - Chef 12.5+ @@ -75,9 +87,18 @@ Windows 2008R2 and lower is _not_ supported. | --- | --- | --- | --- | --- | | `node['veeam']['version']` | String. | Base version of Veeam to install and used to download the appropriate ISO. Supported versions are '9.0' and '9.5' | '9.5' | | | `node['veeam']['installer']['package_url']` | String. | Custom URL for the Veeam Backup and Replication ISO. If not provided, then the ISO will be downloaded directly from Veeam | nil | | -| `node['veeam']['installer']['package_checksum']` | String. | Sha256 hash of the remote ISO file. | nil | | +| `node['veeam']['installer']['package_checksum']` | String. | Sha256 hash of the remote ISO file. Required when setting the `node['veeam']['installer']['package_url']`| nil | | | `node['veeam']['license_url']` | String. | URL for downloading the license filed used by this server. If not provided, the [license data_bag](#veeam-backup-and-replication-license-file) will be checked or the software will be installed in evaluation mode. | nil | | +### Upgrade Details +| Attribute | Type | Description | Default Value | Mandatory | +| --- | --- | --- | --- | --- | +| `node['veeam']['installer']['update_url']` | String. | Custom URL for the Veeam Backup and Replication ISO or Upgrade ZIP. If not provided, then the `node['veeam']['installer']['package_url']` will be used. | nil | | +| `node['veeam']['installer']['update_checksum']` | String. | Sha256 hash of the remote ISO or ZIP file. Required when setting the `node['veeam']['installer']['update_url']` | nil | +| `node['veeam']['build']` | String | Current Veeam Build ID to be used when performing upgrades. Will default to the value of the build found in the attribute `node['veeam']['installer']['update_url']` unless not set. If no `node['veeam']['installer']['update_url']` provided then the value of the Build in `node['veeam']['installer']['package_url']` will be used. Otherwise, the Value of the `node['veeam']['version']` will be assigned. | Multi | | +| `node['veeam']['reboot_on_upgrade']` | TrueFalse | When performing an upgrade, the Veeam process will sometimes require a reboot. This key will control if the reboot should be automatically performed at the end of the upgrade. | true | | +| `node['veeam']['upgrade']['keep_media']` | TrueFalse | Determines if the recipe should keep the media at the end of the upgrade. | false | | + ### Catalog | Attribute | Type | Description | Default Value | Mandatory | | --- | --- | --- | --- | --- | @@ -86,7 +107,7 @@ Windows 2008R2 and lower is _not_ supported. | `node['veeam']['catalog']['vbrc_service_user']` | String. | Specifies a user account under which the Veeam Guest Catalog Service will run. The account must have full control NTFS permissions on the `VM_CATALOGPATH` folder where index files are stored. If you do not specify this parameter, the Veeam Guest Catalog Service will run under the Local System account. NOTE: The account must be in Domain\User or Computer\User format. If using a local account, then use either the `hostname\username` or use `.\username` | nil | | | `node['veeam']['catalog']['vbrc_service_password']` | String. | Specifies a password for the account under which the Veeam Guest Catalog Service will run. This parameter must be used if you have specified the `VBRC_SERVICE_USER` parameter. | nil | | | `node['veeam']['catalog']['vbrc_service_port']` | Integer. | Specifies a TCP port that will be used by the Veeam Guest Catalog Service. By default, port number 9393 is used. | 9393 | | -| `node['veeam']['catalog']['keep_media']` | TrueFalse. | Determines if the recipe should remove the media at the end of the installation. | false | | +| `node['veeam']['catalog']['keep_media']` | TrueFalse. | Determines if the recipe should keep the media at the end of the installation. | false | | ### Server | Attribute | Type | Description | Default Value | Mandatory | @@ -105,7 +126,7 @@ Windows 2008R2 and lower is _not_ supported. | `node['veeam']['server']['vbr_sqlserver_username']` | String | This parameter must be used if you have specified the `VBR_SQLSERVER_AUTHENTICATION` parameter. Specifies a LoginID to connect to the Microsoft SQL Server in the SQL Server authentication mode. | nil | | | `node['veeam']['server']['vbr_sqlserver_password']` | String | This parameter must be used if you have specified the `VBR_SQLSERVER_AUTHENTICATION` parameter. Specifies a password to connect to the Microsoft SQL Server in the SQL Server authentication mode. | nil | | | `node['veeam']['server']['pf_ad_nfsdatastore']` | String | Specifies the vPower NFS root folder to which Instant VM Recovery cache will be stored. | C:\ProgramData\Veeam\Backup\NfsDatastore\ | | -| `node['veeam']['server']['keep_media']` | TrueFalse | Determines if the recipe should remove the media at the end of the installation. | false | | +| `node['veeam']['server']['keep_media']` | TrueFalse | Determines if the recipe should keep the media at the end of the installation. | false | | | `node['sql_server']['server_sa_password']` | String | Configures the SQL Admin password for the SQLExpress instance. | 'Veeam1234' | | | `node['veeam']['server']['explorers']` | Array. List of Veeam Explorers to install. | 'ActiveDirectory','Exchange','SQL','Oracle','SharePoint' | | @@ -114,17 +135,47 @@ Windows 2008R2 and lower is _not_ supported. | --- | --- | --- | --- | --- | | `node['veeam']['console']['accept_eula']` | TrueFalse | Must be set to true or the server will not install. Since we can download the media directly, it is a good idea to enforce the EULA. | false | X | | `node['veeam']['console']['install_dir']` | String | Installs the component to the specified location. By default, Veeam Backup & Replication uses the Backup Console subfolder in the `C:\Program Files\Veeam\Backup and Replication\` folder. | C:\Program Files\Veeam\Backup and Replication\ | | -| `node['veeam']['console']['keep_media']` | TrueFalse | Determines if the recipe should remove the media at the end of the installation. | false | | +| `node['veeam']['console']['keep_media']` | TrueFalse | Determines if the recipe should keep the media at the end of the installation. | false | | -## Veeam Backup and Replication ISO +### Proxy +| Attribute | Type | Description | Default Value | Mandatory | +| --- | --- | --- | --- | --- | +| `node['veeam']['proxy']['vbr_server']` | String | DNS or IP Address of the Veeam Backup and Replication Server | | X | +| `node['veeam']['proxy']['vbr_port']` | String | Veeam Backup and Replication Server Port | 9392 | | +| `node['veeam']['proxy']['vbr_username']` | String | Username with permissions to add Servers and Proxies within Veeam Backup and Replication server | | X | +| `node['veeam']['proxy']['vbr_password']` | String | Password for the user provided | | X | +| `node['veeam']['proxy']['proxy_username']` | String | Required when Adding a new Proxy. Username with Access to the Proxy Server. Will generate a new set of credentials within Veeam Backup and Replication server if none exist.| | X | +| `node['veeam']['proxy']['proxy_password']` | String | Required when Adding a new Proxy. Password for the user provided.| | = nil +| `node['veeam']['proxy']['description']` | String | Description for the Proxy Server. Will automatically start with "ADDED by CHEF: ". Defaults to "ADDED by CHEF: Proxy Server" | | X | +| `node['veeam']['proxy']['max_tasks']` | String | Specifies the number of concurrent tasks that can be assigned to the proxy simultaneously. Permitted values: 1-100 | 2 | X | +| `node['veeam']['proxy']['transport_mode']` | String | Specifies the transport mode used by the backup proxy. Supported Values 'Auto','DirectStorageAccess','HotAdd','Nbd' | 'Auto' | X | +| `node['veeam']['proxy']['use_ip_address']` | String | Register to Veeam using the host IP and not the Hostname. | false | | +| `node['veeam']['proxy']['register']` | String | Determines if the proxy_server recipe should initiate a Proxy registration | true | | + +## Veeam Media and Licenses + +### Veeam Backup and Replication ISO The attribute `node['veeam']['version']` is used to evaluate the ISO download path and checksum for the installation media. When provided, the version selected will be downloaded based on the value found in `libraries/helper.rb`. This media path can be overridden by providing the appropriate installation media attributes - `node['veeam']['installer']['package_url']` and `node['veeam']['installer']['package_checksum']`. By default, these attributes are `nil` and the system will download the ISO every time. | Version | ISO URL | SHA256 | | ------------- |-------------|-------------| -| **9.0** | [VeeamBackup&Replication_9.0.0.902.iso](http://download2.veeam.com/VeeamBackup&Replication_9.0.0.902.iso) | 21f9d2c318911e668511990b8bbd2800141a7764cc97a8b78d4c2200c1225c88 | -| **9.5** | [VeeamBackup&Replication_9.5.0.711.iso](http://download2.veeam.com/VeeamBackup&Replication_9.5.0.711.iso) | af3e3f6db9cb4a711256443894e6fb56da35d48c0b2c32d051960c52c5bc2f00 | +| **9.0** | [VeeamBackup&Replication_9.0.0.902.iso](http://download.veeam.com/VeeamBackup&Replication_9.0.0.902.iso) | 21f9d2c318911e668511990b8bbd2800141a7764cc97a8b78d4c2200c1225c88 | +| **9.5** | [VeeamBackup&Replication_9.5.0.711.iso](http://download.veeam.com/VeeamBackup&Replication_9.5.0.711.iso) | af3e3f6db9cb4a711256443894e6fb56da35d48c0b2c32d051960c52c5bc2f00 | +| **9.5.0.711** | [VeeamBackup&Replication_9.5.0.711.iso](http://download.veeam.com/VeeamBackup&Replication_9.5.0.711.iso) | af3e3f6db9cb4a711256443894e6fb56da35d48c0b2c32d051960c52c5bc2f00 | +| **9.5.0.1038** | [VeeamBackup&Replication_9.5.0.1038.Update2.iso](http://download.veeam.com/VeeamBackup&Replication_9.5.0.1038.Update2.iso) | 180b142c1092c89001ba840fc97158cc9d3a37d6c7b25c93a311115b33454977 | +| **9.5.0.1536** | [VeeamBackup&Replication_9.5.0.1536.Update3.iso](http://download.veeam.com/VeeamBackup&Replication_9.5.0.1536.Update3.iso) | 5020ef015e4d9ff7070d43cf477511a2b562d8044975552fd08f82bdcf556a43 | + + +### Veeam Backup and Replication Update Zip files +The attribute `node['veeam']['build']` is used to evaluate the Zip download path and checksum for the installation media. When provided, the build selected will be downloaded based on the value found in `libraries/helper.rb`. This media path can be overridden by providing the appropriate installation media attributes - `node['veeam']['installer']['update_url']` and `node['veeam']['installer']['update_checksum']`. By default, these attributes are matching their corresponding `node['veeam']['installer']['package_url']` and `node['veeam']['installer']['package_checksum']` values and the system will download the Zip every time. + +| Version | ISO URL | SHA256 | +| ------------- |-------------|-------------| +| **Update 1** | [VeeamBackup&Replication_9.5.0.823_Update1.zip](https://download.veeam.com/VeeamBackup&Replication_9.5.0.823_Update1.zip) | c07bdfb3b90cc609d21ba94584ba19d8eaba16faa31f74ad80814ec9288df492 | +| **Update 2** | [VeeamBackup&Replication_9.5.0.1038.Update2.zip](http://download.veeam.com/VeeamBackup&Replication_9.5.0.1038.Update2.zip) | d800bf5414f1bde95fba5fddbd86146c75a5a2414b967404792cc32841cb4ffb | +| **Update 3** | [VeeamBackup&Replication_9.5.0.1536.Update3.zip](http://download.veeam.com/VeeamBackup&Replication_9.5.0.1536.Update3.zip) | 38ed6a30aa271989477684fdfe7b98895affc19df7e1272ee646bb50a059addc | -## Veeam Backup and Replication License file +### Veeam Backup and Replication License file The server must be licensed to unlock the full potential of the application. The attribute `node['veeam']['server']['evaluation']` should be configured as `false`. To license, choose one of the below options. 1. Save the license file on a web server to which the Veeam Backup and Replication server can access. Set the `node['veeam']['license_url']` attribute to include the full path to the license file. @@ -136,6 +187,35 @@ The server must be licensed to unlock the full potential of the application. Th "license": "base64_encoded_license" } ``` +## Veeam Upgrade Procedures and Details +The process to perform upgrades requires that the appropriate installation media is provided which contains the updates from Veeam. This cookbook will initiate an upgrade if the currently installed versions are less than the desired Build version as defined by the attribute `node['veeam']['build']`. When the installed version does not match the requested build version, the process will mount the ISO or extract the ZIP that contains the update and then perform an automatic upgrade of each service installed on the host. + +### Configuring the Updates +Updates are identified by passing one of the following to the attributes for the server: +1. `node['veeam']['installer']['update_url']` attribute should contain either the full installation ISO or the update ZIP link. The file name must include the full build name like such: + - VeeamBackup&Replication_9.5.0.1536.Update3.iso + - VeeamBackup&Replication_9.5.0.1536.Update3.zip +2. `node['veeam']['installer']['package_url']` attribute should contain either the full installation ISO. The file name must include the full build name like such: + - VeeamBackup&Replication_9.5.0.1536.Update3.iso +3. `node['veeam']['build']` attribute must be a valid and cookbook supported build version like such: + - 9.5 (defaults to GA) + - 9.5.0.711 (GA) + - 9.5.0.1038 (Update2) + - 9.5.0.1536 (Update3) + +### Upgrade Process and Warnings +*Warning* +When the upgrade is performed, the installation will upgrade all components included in the server to which the update is applied. If the upgrade requires a reboot, this process will be automatic and initiate a reboot of the server upon completion of the work. + +*NOTE:* +If the automatic upgrade should be skipped then set the following attribute on the server prior to running the upgrade: +- `node['veeam']['reboot_on_upgrade']` = false + +### Recipes that perform automatic upgrades +Ongoing updates are automatically handled by the following included recipes: +- standalone_complete +- proxy_server +- upgrade ## Resource/Provider @@ -186,6 +266,7 @@ Installs the Veeam Catalog Service #### Properties: _NOTE: properties in bold are required_ + * **`version`** - Installation version. Will determine ISO download path if `package_url` is nil * `package_url` - Full URL to the installation media * `package_checksum` - sha256 checksum of the installation media @@ -234,6 +315,7 @@ Installs the Veeam Backup and Replication Console #### Properties: _NOTE: properties in bold are required_ + * **`version`** - Installation version. Will determine ISO download path if `package_url` is nil * `package_url` - Full URL to the installation media * `package_checksum` - sha256 checksum of the installation media @@ -280,6 +362,7 @@ Installs the Veeam Backup and Replication Service #### Properties: _NOTE: properties in bold are required_ + * **`version`** - Installation version. Will determine ISO download path if `package_url` is nil * `package_url` - Full URL to the installation media * `package_checksum` - sha256 checksum of the installation media @@ -336,10 +419,11 @@ end Installs the Veeam Backup and Replication Explorers #### Actions: -* `:install` - Installs the Veeam Backup and Replication Console service +* `:install` - Installs the Veeam Backup and Replication Explorers #### Properties: _NOTE: properties in bold are required_ + * **`version`** - Installation version. Will determine ISO download path if `package_url` is nil * `package_url` - Full URL to the installation media * `package_checksum` - sha256 checksum of the installation media @@ -367,6 +451,100 @@ veeam_explorer 'Veeam Explorer for Microsoft SQL Server' do end ``` +### Veeam_Upgrade +Installs the Veeam Backup and Replication Update. Requires that the host have an installation of one of the following: +- Veeam Backup & Replication Console +- Veeam Backup & Replication Server +- Veeam Backup & Replication Catalog + +#### Actions: +* `:install` - Installs the Veeam Backup and Replication Upgrade + +#### Properties: +_NOTE: properties in bold are required_ + +* `build` - Identifies the requested build to install. Will determine Zip download path if `package_url` is nil. If not set, then will be determined based on the `package_url` +* `package_url` - Full URL to the installation media. Can be an ISO or Update zip +* `package_checksum` - sha256 checksum of the installation media +* `keep_media` - When set to true, the downloaded ISO or Update Zip will not be deleted. This is helpful if you are installing multiple services on a single node. +* `auto_reboot` - When set to true, the system will automatically reboot if required after performing the update. +* `package_name` - FUTURE property +* `share_path` - FUTURE property + +#### Examples: +```ruby +# Automatically upgrade to Update3 by downloading the Zip from Veeam directly and reboot upon completion +veeam_upgrade '9.5.0.1536' do + action :install +end +``` +```ruby +# Automatically upgrade to Update3 using a custom update url and skip the automatic reboot +veeam_explorer 'Veeam Upgrade' do + build '9.5.0.1536' + package_url 'http://myartifactory/Veeam/installationmedia_9.5.0.1536.Update3.zip' + package_checksum 'sha256checksum' + auto_reboot false + action :install +end +``` + +### Veeam_Proxy +Configures the host as a Proxy Server + +#### Actions: +* `:add` - Registers the Windows server to VBR and Configures as a Proxy +* `:remove` - Removes the Proxy registration and unregisters the Windows Server from VBR + +#### Properties: +_NOTE: properties in bold are required_ + +* `package_name` - FUTURE property +* `share_path` - FUTURE property + +* `*hostname*` - DNS or IP Address of the server to register as the Proxy +* `*vbr_server*` - DNS or IP Address of the Veeam Backup and Replication Server +* `vbr_server_port` - Veeam Backup and Replication Server Port. Default: 9392 +* `*vbr_username*` - Username with permissions to add Servers and Proxies within Veeam Backup and Replication server +* `*vbr_password*` - Password for the user provided +* `*proxy_username*` - Username with Access to the Proxy Server. Will generate a new set of credentials within Veeam Backup and Replication server if none exist. +* `*proxy_password*` - Password for the user provided +* `proxy_type` - Type of Proxy Server to Add. Supported types - vmware or hyperv +* `description` - Description for the Proxy Server. Will automatically start with "ADDED by CHEF: ". Defaults to "ADDED by CHEF: Proxy Server" +* `max_tasks` - Specifies the number of concurrent tasks that can be assigned to the proxy simultaneously. Permitted values: 1-100. default: 2 +* `transport_mode` - Specifies the transport mode used by the backup proxy. Supported Values 'Auto','DirectStorageAccess','HotAdd','Nbd'. Default: 'Auto' + + +_Future Properties_ +* `datastore_mode` - Specifies the mode the proxy will use to connect to datastores. Supported Values 'Auto' or 'Manual'. Default: 'Auto' +* `datastore` - Specifies the list of datastores to which the backup proxy has a direct SAN or NFS connection. +* `enable_failover_to_ndb` - Indicates if the backup proxy must fail over to the Network transport mode if it fails to transport data in the Direct storage access or Virtual appliance transport mode. Default: false +* `host_encryption` - Indicates if VM data must be transported over an encrypted SSL connection in the Network transport mode. Default: false + + +#### Examples: +```ruby +# Add a new VMware Proxy and register +veeam_proxy 'proxy01.demo.lab' do + vbr_server 'veeam.demo.lab' + vbr_username 'demo\\veeamuser' + vbr_password 'mysecretpassword' + proxy_username 'demo\\administrator' + proxy_password 'myextrapassword' + proxy_type 'vmware' + action :add +end +``` +```ruby +# Remove the current Veeam Proxy and Server registration +veeam_proxy 'proxy01.demo.lab' do + vbr_server 'veeam.demo.lab' + vbr_username 'demo\\veeamuser' + vbr_password 'mysecretpassword' + action :remove +end +``` + ## Usage ### default @@ -394,7 +572,17 @@ Installs and configures Veeam Backup and Replication Server & Console using the ### standalone_complete recipe -Installs and configures Veeam Backup and Replication Server, Console & the Catalog service using the default configuration including pre-requisites and SQLExpress. Also installs all of the Veeam Backup Explorers +Installs and configures Veeam Backup and Replication Server, Console & the Catalog service using the default configuration including pre-requisites and SQLExpress. Also installs all of the Veeam Backup Explorers and performs an upgrade to the requested Build level. For more information, see [Veeam Upgrade Procedures and Details](#Veeam-Upgrade-Procedures-and-Details) + +### proxy recipe + +Installs and configures Veeam Backup and Replication Console using the default configuration including pre-requisites and performs an upgrade to the requested Build level. For more information, see [Veeam Upgrade Procedures and Details](#Veeam-Upgrade-Procedures-and-Details). Also registers the Proxy Server to the Veeam Backup and Replication Server. + +### proxy_remove recipe +Unregisters the Proxy Server and removes the Server registration from the Veeam Backup and Replication Server. + +### upgrade recipe +Performs an upgrade of Veeam components to the requested Build level. For more information, see [Veeam Upgrade Procedures and Details](#Veeam-Upgrade-Procedures-and-Details) ## Upload to Chef Server This cookbook should be included in each organization of your CHEF environment. When importing, leverage Berkshelf: @@ -416,6 +604,9 @@ _Note: Matchers should always be created in `libraries/matchers.rb` and used for * `install_veeam_server(resource_name)` * `install_veeam_prerequisites(resource_name)` * `install_veeam_explorer(resource_name)` +* `add_veeam_proxy(resource_name)` +* `remove_veeam_proxy(resource_name)` +* `install_veeam_upgrade(resource_name)` ### Veeam::Helper _Note: A helper to handle common and repeated functions_ @@ -424,42 +615,88 @@ _Note: A helper to handle common and repeated functions_ Determines if the current node meets the OS type and requirements. If False, then raise an Argument Errors depending if the node['platform_version'] or node['kernel']['machine'] are wrong. ``` # usage in a custom_resource -veeam = Veeam::Helper -veeam.check_os_version(node) +::Chef::Provider.send(:include, Veeam::Helper) +check_os_version(node) ``` #### find_package_url(version) Uses the supplied version identifier to return the stored URL location of the installation media. This method calls the package_list method to identify the correct information ``` # usage in a custom_resource -veeam = Veeam::Helper -package_url = veeam.find_package_url(new_resource.version) +::Chef::Provider.send(:include, Veeam::Helper) +package_url = find_package_url(new_resource.version) ``` #### find_package_checksum(version) Uses the supplied version identifier to return the stored checksum for the installation media. This method calls the package_list method to identify the correct information ``` # usage in a custom_resource -veeam = Veeam::Helper -package_checksum = veeam.find_package_checksum(new_resource.version) +::Chef::Provider.send(:include, Veeam::Helper) +package_checksum = find_package_checksum(new_resource.version) +``` + +#### find_update_url(version) +Uses the supplied version identifier to return the stored URL location of the update media. This method calls the update_list method to identify the correct information +``` +# usage in a custom_resource +::Chef::Provider.send(:include, Veeam::Helper) +update_url = find_update_url(new_resource.version) +``` + +#### find_update_checksum(version) +Uses the supplied version identifier to return the stored checksum for the update media. This method calls the update_list method to identify the correct information +``` +# usage in a custom_resource +::Chef::Provider.send(:include, Veeam::Helper) +update_checksum = find_update_checksum(new_resource.version) ``` #### prerequisites_list Returns an array of version specific prerequisite package versions ``` # usage in a custom_resource -veeam = Veeam::Helper -prerequisites_list = veeam.prerequisites_list(new_resource.version) +::Chef::Provider.send(:include, Veeam::Helper) +prerequisites_list = prerequisites_list(new_resource.version) ``` #### explorers_list Returns an array of version specific explorer package versions ``` # usage in a custom_resource -veeam = Veeam::Helper -explorers_list = veeam.explorers_list(new_resource.version) +::Chef::Provider.send(:include, Veeam::Helper) +explorers_list = explorers_list(new_resource.version) ``` +#### find_current_dotnet +Returns the current installed version of .NET + +#### validate_powershell_out(script, timeout: nil) +Sends command data to the powershell_out method and validates the output contains no errors + +#### find_current_veeam_solutions(package_name) +Determine the install location based on the supplied Package Name. Expected values are: +- Veeam Backup & Replication Console +- Veeam Backup & Replication Server +- Veeam Backup & Replication Catalog + +#### find_current_veeam_version(package_name) +Determine the build version for the installed packages. Expected values are: +- Veeam Backup & Replication Console +- Veeam Backup & Replication Server +- Veeam Backup & Replication Catalog + +#### iso_installer(downloaded_file_name, new_resource) +Helper method to download and mount the ISO media + +#### extract_installer(downloaded_file_name, new_resource) +Helper method to download and extract the Update Zip files. The files will be save in the default :file_cache location in a subdirectory called `Veeam//Updates` + +#### unmount_installer(downloaded_file_name) +Helper method to find and unmount the ISO media + +#### get_media_installer_location(downloaded_file_name) +Helper method to determine the drive letter of the mounted ISO media + ### Windows_Helper Testing with ChefSpec on Linux or Mac for specific Windows items such as register, Win32, etc can cause failures in the testing. Included in this library is a helper file designed to stub and mock out these calls. @@ -521,6 +758,12 @@ This repo includes a **Rakefile** for common tasks | **rake integration:kitchen:server-with-console-windows-2016** | Run server-with-console-windows-2016 test instance | | **rake integration:kitchen:standalone-complete-windows-2012r2** | Run standalone-complete-windows-2012r2 test instance | | **rake integration:kitchen:standalone-complete-windows-2016** | Run standalone-complete-windows-2016 test instance | +| **rake integration:kitchen:proxy-server-2012r2** | Run proxy-server-2012r2 test instance | +| **rake integration:kitchen:proxy-server-2016** | Run proxy-server-2016 test instance | +| **rake integration:kitchen:proxy-remove-2012r2** | Run proxy-remove-2012r2 test instance | +| **rake integration:kitchen:proxy-remove-2016** | Run proxy-remove-2016 test instance | +| **rake integration:kitchen:upgrade-2012r2** | Run upgrade-2012r2 test instance | +| **rake integration:kitchen:upgrade-2016** | Run upgrade-2016 test instance | | **rake maintainers:generate** | Generate MarkDown version of MAINTAINERS file | ### Chefspec and Test-Kitchen @@ -551,13 +794,19 @@ Included in this cookbook is a set of Inspec profile tests used for the Windows ## License and Author +_Note: This cookbook is not officially supported by or released by Veeam Software, Inc._ + - Author:: Exosphere Data, LLC ([chef@exospheredata.com](mailto:chef@exospheredata.com)) ```text Copyright 2017 Exosphere Data, LLC -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 +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. +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. ``` diff --git a/Rakefile b/Rakefile index 289a449..0b98da3 100644 --- a/Rakefile +++ b/Rakefile @@ -75,3 +75,9 @@ task integration: ['integration:kitchen:all'] # Default task default: %w(style unit) + +desc 'Run Local Development tests: Style, Foodcritic, Maintainers, Unit Tests, and Test-Kitchen' +task local: %w(style unit integration) + +desc 'Autocorrect Rubocop Style errors' +task auto_correct: %w(style:ruby:auto_correct) diff --git a/attributes/default.rb b/attributes/default.rb index 3191541..5a0f2c9 100644 --- a/attributes/default.rb +++ b/attributes/default.rb @@ -8,3 +8,15 @@ default['veeam']['installer']['package_url'] = nil # Local or custom URL location for ISO default['veeam']['installer']['package_checksum'] = nil # Sha256 checksum of ISO default['veeam']['license_url'] = nil +default['veeam']['installer']['update_url'] = node['veeam']['installer']['package_url'] # Local or custom URL location for ISO +default['veeam']['installer']['update_checksum'] = node['veeam']['installer']['package_checksum'] # Sha256 checksum of ISO + +default['veeam']['build'] = if node['veeam']['installer']['update_url'].nil? && node['veeam']['installer']['package_url'].nil? + node['veeam']['version'] + elsif node['veeam']['installer']['update_url'] == node['veeam']['installer']['package_url'] + /(\d+.\d+.\d+.\d+)/.match(node['veeam']['installer']['package_url'].split('/')[-1]).captures[0] + else + /(\d+.\d+.\d+.\d+)/.match(node['veeam']['installer']['update_url'].split('/')[-1]).captures[0] + end +default['veeam']['reboot_on_upgrade'] = true +default['veeam']['upgrade']['keep_media'] = false diff --git a/attributes/proxy.rb b/attributes/proxy.rb new file mode 100644 index 0000000..fac5262 --- /dev/null +++ b/attributes/proxy.rb @@ -0,0 +1,21 @@ +# +# Cookbook Name:: veeam +# Attributes:: proxy +# +# Copyright (c) 2017 Exosphere Data LLC, All Rights Reserved. + +default['veeam']['proxy']['vbr_server'] = nil +default['veeam']['proxy']['vbr_port'] = 9392 +default['veeam']['proxy']['vbr_username'] = nil +default['veeam']['proxy']['vbr_password'] = nil + +default['veeam']['proxy']['proxy_username'] = nil +default['veeam']['proxy']['proxy_password'] = nil + +default['veeam']['proxy']['description'] = nil + +default['veeam']['proxy']['max_tasks'] = 2 +default['veeam']['proxy']['transport_mode'] = 'Auto' + +default['veeam']['proxy']['use_ip_address'] = false +default['veeam']['proxy']['register'] = true diff --git a/libraries/helper.rb b/libraries/helper.rb index 95726a2..a53e42c 100644 --- a/libraries/helper.rb +++ b/libraries/helper.rb @@ -24,25 +24,23 @@ require 'chef/mixin/shell_out' module Veeam - class Helper - extend Chef::Mixin::ShellOut - - def self.check_os_version(node) + module Helper + def check_os_version(node) # '6.1.' is the numeric platform_version for Windows 2008R2. If the node OS version is below that value, we must abort. raise ArgumentError, 'Veeam Backup and Replication management requires a Windows 2008R2 or higher host!' if node['platform_version'].to_f < '6.1'.to_f # If the kernel is not 64bit then raise an error, as we cannot proceed. raise ArgumentError, 'Veeam Backup and Replication requires an x86_64 host and cannot be installed on this machine' unless node['kernel']['machine'] =~ /x86_64/ end - def self.find_package_url(version) + def find_package_url(version) package_list(version)['package_url'] if package_list(version) end - def self.find_package_checksum(version) + def find_package_checksum(version) package_list(version)['package_checksum'] if package_list(version) end - def self.package_list(version) + def package_list(version) case version.to_s # to_s to make sure someone didn't pass us an int when '9.0' then { 'package_url' => 'http://download2.veeam.com/VeeamBackup&Replication_9.0.0.902.iso', @@ -52,10 +50,59 @@ def self.package_list(version) 'package_url' => 'http://download2.veeam.com/VeeamBackup&Replication_9.5.0.711.iso', 'package_checksum' => 'af3e3f6db9cb4a711256443894e6fb56da35d48c0b2c32d051960c52c5bc2f00' } + when '9.5.0.711' then { + 'package_url' => 'http://download2.veeam.com/VeeamBackup&Replication_9.5.0.711.iso', + 'package_checksum' => 'af3e3f6db9cb4a711256443894e6fb56da35d48c0b2c32d051960c52c5bc2f00' + } + when '9.5.0.1038' then { + 'package_url' => 'http://download2.veeam.com/VeeamBackup&Replication_9.5.0.1038.Update2.iso', + 'package_checksum' => '180b142c1092c89001ba840fc97158cc9d3a37d6c7b25c93a311115b33454977' + } + when '9.5.0.1536' then { + 'package_url' => 'http://download2.veeam.com/VeeamBackup&Replication_9.5.0.1536.Update3.iso', + 'package_checksum' => '5020ef015e4d9ff7070d43cf477511a2b562d8044975552fd08f82bdcf556a43' + } end end - def self.prerequisites_list(version) + def find_update_url(version) + update_list(version)['update_url'] if update_list(version) + end + + def find_update_checksum(version) + update_list(version)['update_checksum'] if update_list(version) + end + + def update_list(version) + case version.to_s # to_s to make sure someone didn't pass us an int + when '9.0' then { + 'update_url' => 'http://download2.veeam.com/VeeamBackup&Replication_9.0.0.902.iso', + 'update_checksum' => '21f9d2c318911e668511990b8bbd2800141a7764cc97a8b78d4c2200c1225c88' + } + when '9.5' then { + 'update_url' => 'http://download2.veeam.com/VeeamBackup&Replication_9.5.0.711.iso', + 'update_checksum' => 'af3e3f6db9cb4a711256443894e6fb56da35d48c0b2c32d051960c52c5bc2f00' + } + when '9.5.0.711' then { + 'update_url' => 'http://download2.veeam.com/VeeamBackup&Replication_9.5.0.711.iso', + 'update_checksum' => 'af3e3f6db9cb4a711256443894e6fb56da35d48c0b2c32d051960c52c5bc2f00' + } + when '9.5.0.823' then { + 'update_url' => 'https://download.veeam.com/VeeamBackup&Replication_9.5.0.823_Update1.zip', + 'update_checksum' => 'c07bdfb3b90cc609d21ba94584ba19d8eaba16faa31f74ad80814ec9288df492' + } + when '9.5.0.1038' then { + 'update_url' => 'http://download2.veeam.com/VeeamBackup&Replication_9.5.0.1038.Update2.zip', + 'update_checksum' => 'd800bf5414f1bde95fba5fddbd86146c75a5a2414b967404792cc32841cb4ffb' + } + when '9.5.0.1536' then { + 'update_url' => 'http://download2.veeam.com/VeeamBackup&Replication_9.5.0.1536.Update3.zip', + 'update_checksum' => '38ed6a30aa271989477684fdfe7b98895affc19df7e1272ee646bb50a059addc' + } + end + end + + def prerequisites_list(version) case version.to_s # to_s to make sure someone didn't pass us an int when '9.0' then { '0' => { 'Microsoft System CLR Types for SQL Server 2012 (x64)' => 'SQLSysClrTypes.msi' }, @@ -70,7 +117,7 @@ def self.prerequisites_list(version) end end - def self.explorers_list(version) + def explorers_list(version) case version.to_s # to_s to make sure someone didn't pass us an int when '9.0' then { 'ActiveDirectory' => 'Veeam Explorer for Microsoft Active Directory', @@ -88,5 +135,137 @@ def self.explorers_list(version) } end end + + def find_current_dotnet + installed_version = nil + installed_version_reg_key = registry_get_values('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full') + unless installed_version_reg_key.nil? + installed_version_reg_key.each do |key| + installed_version = key[:data] if key[:name] == 'Release' + end + end + installed_version.nil? ? 0 : installed_version + end + + def validate_powershell_out(script, timeout: nil) + # This seemed like the DRYest way to handle the output handling from PowerShell. + cmd = powershell_out(script) if timeout.nil? + cmd = powershell_out(script, timeout: timeout) unless timeout.nil? + # Only return the output if there were no errors. + return cmd.stdout.chomp if cmd.stderr == '' || cmd.stderr.nil? + raise cmd.inspect if cmd.stderr != '' + end + + def find_current_veeam_solutions(package_name) + cmd_str = <<-EOH + $program = '#{package_name}' + $x86 = (Get-ChildItem "HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall" | gp ) + $x64 = (Get-ChildItem "HKLM:\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall" | gp ) + + $x86version = ($x86 | ?{$_.DisplayName -eq $program}) + $x64version = ($x64 | ?{$_.DisplayName -eq $program}) + + if($x86version) { + return ($x86version.InstallLocation) + } elseif($x64version) { + return ($x64version.InstallLocation) + } else { + # Return nothing + } + EOH + output = validate_powershell_out(cmd_str) + raise ArgumentError, 'Unable to find the Veeam installation' unless output + output + end + + def find_current_veeam_version(package_name) + veeam_package = find_current_veeam_solutions(package_name) + veeam_exe = case package_name + when /Console/ + "#{veeam_package}\\Console\\veeam.backup.shell.exe" + when /Server/ + "#{veeam_package}\\Packages\\VeeamDeploymentDll.dll" + when /Catalog/ + "#{veeam_package}\\Backup Catalog\\VeeamDeploymentDll.dll" + else + raise "Unknown Package name: #{package_name}" + end + cmd_str = <<-EOH + $File = Get-Item -Path '#{veeam_exe}' + $File.VersionInfo.ProductVersion + EOH + output = validate_powershell_out(cmd_str) + output = nil if output == '' + output + end + + def iso_installer(downloaded_file_name, new_resource) + remote_file downloaded_file_name do + source new_resource.package_url + checksum new_resource.package_checksum + use_conditional_get true # this should allow us to prevent duplicate downloads + action :create + end + + # Mounting the Veeam backup ISO. + powershell_script 'Load Veeam media' do + code <<-EOH + Mount-DiskImage -ImagePath "#{downloaded_file_name}" + EOH + action :run + guard_interpreter :powershell_script + not_if "[boolean] (Get-DiskImage -ImagePath '#{downloaded_file_name}').DevicePath" + end + end + + def extract_installer(downloaded_file_name, new_resource) + package_name = downloaded_file_name.split('\\').last + package_type = ::File.extname(package_name) + install_media_path = win_friendly_path(::File.join(::Chef::Config[:file_cache_path], "Veeam/#{package_name.gsub(package_type, '')}")) + update_path = win_friendly_path(::File.join(install_media_path, '/Updates')) + + remote_file downloaded_file_name do + source new_resource.package_url + checksum new_resource.package_checksum + use_conditional_get true # this should allow us to prevent duplicate downloads + action :create + not_if { ::File.exist?(update_path) } + end + + windows_zipfile win_friendly_path(::File.join(install_media_path, '/Updates')) do + source downloaded_file_name + action :unzip + not_if { ::File.exist?(update_path) } + end + + install_media_path + end + + def unmount_installer(downloaded_file_name) + # Unmount the Veeam backup ISO. + powershell_script 'Dismount Veeam media' do + code <<-EOH + Dismount-DiskImage -ImagePath "#{downloaded_file_name}" + EOH + action :run + guard_interpreter :powershell_script + only_if "[boolean] (Get-DiskImage -ImagePath '#{downloaded_file_name}').DevicePath" + end + end + + def get_media_installer_location(downloaded_file_name) + # When downloading and mounting the ISO, we need to track back to the Drive Letter. This method will handle + # the look-up and keep the logic out of the main installation code. + Chef::Log.debug 'Searching for the Veeam installation media Drive Letter...' + cmd_str = <<-EOH + $DriveLetter = (Get-DiskImage -ImagePath '#{downloaded_file_name}' | Get-Volume).DriveLetter; + if ( [string]::IsNullOrEmpty($DriveLetter) ){ throw 'The ISO did not mount and we have no idea why.' } + return ( $DriveLetter +':\' ) + EOH + output = validate_powershell_out(cmd_str) + raise ArgumentError, 'Unable to find the Veeam installation media' unless output + Chef::Log.debug "Found the Veeam installation media at Drive Letter [#{output}]" + output + end end end diff --git a/libraries/matchers.rb b/libraries/matchers.rb index b70e4e9..0c1ccef 100644 --- a/libraries/matchers.rb +++ b/libraries/matchers.rb @@ -4,6 +4,8 @@ ChefSpec.define_matcher(:veeam_console) ChefSpec.define_matcher(:veeam_server) ChefSpec.define_matcher(:veeam_prerequisites) + ChefSpec.define_matcher(:veeam_proxy) + ChefSpec.define_matcher(:veeam_upgrade) def install_veeam_catalog(resource_name) ChefSpec::Matchers::ResourceMatcher.new(:veeam_catalog, :install, resource_name) @@ -25,4 +27,16 @@ def install_veeam_explorer(resource_name) ChefSpec::Matchers::ResourceMatcher.new(:veeam_explorer, :install, resource_name) end + def install_veeam_upgrade(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:veeam_upgrade, :install, resource_name) + end + + def add_veeam_proxy(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:veeam_proxy, :add, resource_name) + end + + def remove_veeam_proxy(resource_name) + ChefSpec::Matchers::ResourceMatcher.new(:veeam_proxy, :remove, resource_name) + end + end diff --git a/metadata.rb b/metadata.rb index aa0903e..4bc9756 100644 --- a/metadata.rb +++ b/metadata.rb @@ -4,7 +4,7 @@ license 'Apache-2.0' description 'Installs/Configures Veeam Backup and Recovery' long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) -version '1.0.0' +version '2.0.0' chef_version '>= 12.5' if respond_to?(:chef_version) supports 'windows' diff --git a/recipes/proxy_remove.rb b/recipes/proxy_remove.rb new file mode 100644 index 0000000..8af1a4a --- /dev/null +++ b/recipes/proxy_remove.rb @@ -0,0 +1,30 @@ +# +# Cookbook:: veeam +# Recipe:: proxy_remove +# +# maintainer:: Exosphere Data, LLC +# maintainer_email:: chef@exospheredata.com +# +# Copyright:: 2018, Exosphere Data, LLC, All Rights Reserved. + +error_message = 'This recipe requires a Windows 2012 or higher host!' + +# If this host is not Windows, then abort +raise ArgumentError, error_message unless node['platform'] == 'windows' + +# If this host is older than Windows 2012, we should abort the process for an unsupported platform +raise ArgumentError, error_message if node['platform_version'].to_f < '6.2.9200'.to_f # '6.2.9200' is the numeric platform_version for Windows 2012 + +proxy_server = if node['veeam']['proxy']['use_ip_address'] + node['ipaddress'] + else + node['hostname'] + end + +veeam_proxy proxy_server do + vbr_server node['veeam']['proxy']['vbr_server'] + vbr_server_port node['veeam']['proxy']['vbr_port'] + vbr_username node['veeam']['proxy']['vbr_username'] + vbr_password node['veeam']['proxy']['vbr_password'] + action :remove +end diff --git a/recipes/proxy_server.rb b/recipes/proxy_server.rb new file mode 100644 index 0000000..b813a15 --- /dev/null +++ b/recipes/proxy_server.rb @@ -0,0 +1,68 @@ +# +# Cookbook:: veeam +# Recipe:: proxy_server +# +# maintainer:: Exosphere Data, LLC +# maintainer_email:: chef@exospheredata.com +# +# Copyright:: 2018, Exosphere Data, LLC, All Rights Reserved. + +error_message = 'This recipe requires a Windows 2012 or higher host!' + +# If this host is not Windows, then abort +raise ArgumentError, error_message unless node['platform'] == 'windows' + +# If this host is older than Windows 2012, we should abort the process for an unsupported platform +raise ArgumentError, error_message if node['platform_version'].to_f < '6.2.9200'.to_f # '6.2.9200' is the numeric platform_version for Windows 2012 + +%w(FS-FileServer Print-Server).each do |feature| + windows_feature feature do + install_method :windows_feature_powershell + action :install + end +end + +veeam_prerequisites 'Install Veeam Prerequisites' do + package_url node['veeam']['installer']['package_url'] + package_checksum node['veeam']['installer']['package_checksum'] + version node['veeam']['version'] + install_sql false + action :install +end + +veeam_console 'Install Veeam Backup console' do + package_url node['veeam']['installer']['package_url'] + package_checksum node['veeam']['installer']['package_checksum'] + version node['veeam']['version'] + accept_eula node['veeam']['console']['accept_eula'] + install_dir node['veeam']['console']['install_dir'] + keep_media true + action :install +end + +veeam_upgrade node['veeam']['build'] do + package_url node['veeam']['installer']['update_url'] + package_checksum node['veeam']['installer']['update_checksum'] + keep_media node['veeam']['upgrade']['keep_media'] + action :install +end + +proxy_server = if node['veeam']['proxy']['use_ip_address'] + node['ipaddress'] + else + node['hostname'] + end + +veeam_proxy proxy_server do + vbr_server node['veeam']['proxy']['vbr_server'] + vbr_server_port node['veeam']['proxy']['vbr_port'] + vbr_username node['veeam']['proxy']['vbr_username'] + vbr_password node['veeam']['proxy']['vbr_password'] + proxy_username node['veeam']['proxy']['proxy_username'] + proxy_password node['veeam']['proxy']['proxy_password'] + description node['veeam']['proxy']['description'] + max_tasks node['veeam']['proxy']['max_tasks'] + transport_mode node['veeam']['proxy']['transport_mode'] + action :add + only_if { node['veeam']['proxy']['register'] } +end diff --git a/recipes/standalone_complete.rb b/recipes/standalone_complete.rb index f13dba7..c0f1c7c 100644 --- a/recipes/standalone_complete.rb +++ b/recipes/standalone_complete.rb @@ -53,6 +53,7 @@ vbr_service_user node['veeam']['server']['vbr_service_user'] vbr_service_password node['veeam']['server']['vbr_service_password'] vbr_service_port node['veeam']['server']['vbr_service_port'] + vbr_check_updates true keep_media true action :install end @@ -65,3 +66,11 @@ keep_media node['veeam']['console']['keep_media'] || node['veeam']['server']['keep_media'] action :install end + +veeam_upgrade node['veeam']['build'] do + package_url node['veeam']['installer']['update_url'] + package_checksum node['veeam']['installer']['update_checksum'] + keep_media node['veeam']['upgrade']['keep_media'] + auto_reboot node['veeam']['reboot_on_upgrade'] + action :install +end diff --git a/recipes/upgrade.rb b/recipes/upgrade.rb new file mode 100644 index 0000000..e348602 --- /dev/null +++ b/recipes/upgrade.rb @@ -0,0 +1,24 @@ +# +# Cookbook:: veeam +# Recipe:: upgrade +# +# maintainer:: Exosphere Data, LLC +# maintainer_email:: chef@exospheredata.com +# +# Copyright:: 2018, Exosphere Data, LLC, All Rights Reserved. + +error_message = 'This recipe requires a Windows 2012 or higher host!' + +# If this host is not Windows, then abort +raise ArgumentError, error_message unless node['platform'] == 'windows' + +# If this host is older than Windows 2012, we should abort the process for an unsupported platform +raise ArgumentError, error_message if node['platform_version'].to_f < '6.2.9200'.to_f # '6.2.9200' is the numeric platform_version for Windows 2012 + +veeam_upgrade node['veeam']['build'] do + package_url node['veeam']['installer']['update_url'] + package_checksum node['veeam']['installer']['update_checksum'] + keep_media node['veeam']['upgrade']['keep_media'] + auto_reboot node['veeam']['reboot_on_upgrade'] + action :install +end diff --git a/resources/catalog.rb b/resources/catalog.rb index 930f482..91e5c09 100644 --- a/resources/catalog.rb +++ b/resources/catalog.rb @@ -40,10 +40,10 @@ # We need to include the windows helpers to keep things dry ::Chef::Provider.send(:include, Windows::Helper) +::Chef::Provider.send(:include, Veeam::Helper) action :install do - veeam = Veeam::Helper # Library of helper methods - veeam.check_os_version(node) + check_os_version(node) # We will use the Windows Helper 'is_package_installed?' to see if the Catalog Server is installed. If it is installed, then # we should report no change back. By returning 'false', Chef will report that the resource is up-to-date. @@ -65,8 +65,8 @@ # Call the Veeam::Helper to find the correct URL based on the version of the Veeam Backup and Recovery edition passed # as an attribute. unless new_resource.package_url - new_resource.package_url = veeam.find_package_url(new_resource.version) - new_resource.package_checksum = veeam.find_package_checksum(new_resource.version) + new_resource.package_url = find_package_url(new_resource.version) + new_resource.package_checksum = find_package_checksum(new_resource.version) Chef::Log.info(new_resource.package_url) end @@ -80,7 +80,7 @@ Chef::Log.debug('Downloading Veeam Backup and Recovery software via URL') package_name = new_resource.package_url.split('/').last installer_file_name = win_friendly_path(::File.join(package_save_dir, package_name)) - download_installer(installer_file_name) + iso_installer(installer_file_name, new_resource) ruby_block 'Install the Backup Catalog application' do block do @@ -111,70 +111,6 @@ def whyrun_supported? true end - def find_current_dotnet - installed_version = nil - installed_version_reg_key = registry_get_values('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full') - unless installed_version_reg_key.nil? - installed_version_reg_key.each do |key| - installed_version = key[:data] if key[:name] == 'Release' - end - end - installed_version.nil? ? 0 : installed_version - end - - def validate_powershell_out(script) - # This seemed like the DRYest way to handle the output handling from PowerShell. - cmd = powershell_out(script) - # Check powershell output - raise cmd.inspect if cmd.stderr != '' - cmd.stdout.chop - end - - def download_installer(downloaded_file_name) - # Download the Installer media - remote_file downloaded_file_name do - source new_resource.package_url - checksum new_resource.package_checksum - use_conditional_get true # this should allow us to prevent duplicate downloads - action :create - end - - # Mounting the Veeam backup ISO. - powershell_script 'Load Veeam media' do - code <<-EOH - Mount-DiskImage -ImagePath "#{downloaded_file_name}" - EOH - guard_interpreter :powershell_script - not_if "[boolean] (Get-DiskImage -ImagePath '#{downloaded_file_name}').DevicePath" - end - end - - def unmount_installer(downloaded_file_name) - # Unmount the Veeam backup ISO. - powershell_script 'Dismount Veeam media' do - code <<-EOH - Dismount-DiskImage -ImagePath "#{downloaded_file_name}" - EOH - guard_interpreter :powershell_script - only_if "[boolean] (Get-DiskImage -ImagePath '#{downloaded_file_name}').DevicePath" - end - end - - def get_media_installer_location(downloaded_file_name) - # When downloading and mounting the ISO, we need to track back to the Drive Letter. This method will handle - # the look-up and keep the logic out of the main installation code. - Chef::Log.debug 'Searching for the Veeam installation media Drive Letter...' - cmd_str = <<-EOH - $DriveLetter = (Get-DiskImage -ImagePath '#{downloaded_file_name}' | Get-Volume).DriveLetter; - if ( [string]::IsNullOrEmpty($DriveLetter) ){ throw 'The ISO did not mount and we have no idea where why.' } - return ( $DriveLetter +':\' ) - EOH - output = validate_powershell_out(cmd_str) - raise ArgumentError, 'Unable to find the Veeam installation media' unless output - Chef::Log.debug "Found the Veeam installation media at Drive Letter [#{output}]" - output - end - def perform_catalog_install(install_media_path) Chef::Log.debug 'Installing Veeam Backup Catalog service... begin' # In this case, we have many possible combinations of extra arugments that would need to be passed to the installer. diff --git a/resources/console.rb b/resources/console.rb index c6da56a..b6b8523 100644 --- a/resources/console.rb +++ b/resources/console.rb @@ -37,10 +37,10 @@ # We need to include the windows helpers to keep things dry ::Chef::Provider.send(:include, Windows::Helper) +::Chef::Provider.send(:include, Veeam::Helper) action :install do - veeam = Veeam::Helper # Library of helper methods - veeam.check_os_version(node) + check_os_version(node) # We will use the Windows Helper 'is_package_installed?' to see if the Console is installed. If it is installed, then # we should report no change back. By returning 'false', Chef will report that the resource is up-to-date. @@ -63,8 +63,8 @@ # Call the Veeam::Helper to find the correct URL based on the version of the Veeam Backup and Recovery edition passed # as an attribute. unless new_resource.package_url - new_resource.package_url = veeam.find_package_url(new_resource.version) - new_resource.package_checksum = veeam.find_package_checksum(new_resource.version) + new_resource.package_url = find_package_url(new_resource.version) + new_resource.package_checksum = find_package_checksum(new_resource.version) Chef::Log.info(new_resource.package_url) end @@ -78,7 +78,7 @@ Chef::Log.debug('Downloading Veeam Backup and Recovery software via URL') package_name = new_resource.package_url.split('/').last installer_file_name = win_friendly_path(::File.join(package_save_dir, package_name)) - download_installer(installer_file_name) + iso_installer(installer_file_name, new_resource) ruby_block 'Install the Backup console application' do block do @@ -109,70 +109,6 @@ def whyrun_supported? true end - def find_current_dotnet - installed_version = nil - installed_version_reg_key = registry_get_values('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full') - unless installed_version_reg_key.nil? - installed_version_reg_key.each do |key| - installed_version = key[:data] if key[:name] == 'Release' - end - end - installed_version.nil? ? 0 : installed_version - end - - def validate_powershell_out(script) - # This seemed like the DRYest way to handle the output handling from PowerShell. - cmd = powershell_out(script) - # Check powershell output - raise cmd.inspect if cmd.stderr != '' - cmd.stdout.chop - end - - def download_installer(downloaded_file_name) - # Download the Installer media - remote_file downloaded_file_name do - source new_resource.package_url - checksum new_resource.package_checksum - use_conditional_get true # this should allow us to prevent duplicate downloads - action :create - end - - # Mounting the Veeam backup ISO. - powershell_script 'Load Veeam media' do - code <<-EOH - Mount-DiskImage -ImagePath "#{downloaded_file_name}" - EOH - guard_interpreter :powershell_script - not_if "[boolean] (Get-DiskImage -ImagePath '#{downloaded_file_name}').DevicePath" - end - end - - def unmount_installer(downloaded_file_name) - # Unmount the Veeam backup ISO. - powershell_script 'Dismount Veeam media' do - code <<-EOH - Dismount-DiskImage -ImagePath "#{downloaded_file_name}" - EOH - guard_interpreter :powershell_script - only_if "[boolean] (Get-DiskImage -ImagePath '#{downloaded_file_name}').DevicePath" - end - end - - def get_media_installer_location(downloaded_file_name) - # When downloading and mounting the ISO, we need to track back to the Drive Letter. This method will handle - # the look-up and keep the logic out of the main installation code. - Chef::Log.debug 'Searching for the Veeam installation media Drive Letter...' - cmd_str = <<-EOH - $DriveLetter = (Get-DiskImage -ImagePath '#{downloaded_file_name}' | Get-Volume).DriveLetter; - if ( [string]::IsNullOrEmpty($DriveLetter) ){ throw 'The ISO did not mount and we have no idea where why.' } - return ( $DriveLetter +':\' ) - EOH - output = validate_powershell_out(cmd_str) - raise ArgumentError, 'Unable to find the Veeam installation media' unless output - Chef::Log.debug "Found the Veeam installation media at Drive Letter [#{output}]" - output - end - def perform_console_install(install_media_path) Chef::Log.debug 'Installing Veeam Backup console service... begin' # In this case, we have many possible combinations of extra arugments that would need to be passed to the installer. diff --git a/resources/explorer.rb b/resources/explorer.rb index c44382b..6dbe20e 100644 --- a/resources/explorer.rb +++ b/resources/explorer.rb @@ -36,10 +36,10 @@ # We need to include the windows helpers to keep things dry ::Chef::Provider.send(:include, Windows::Helper) +::Chef::Provider.send(:include, Veeam::Helper) action :install do - veeam = Veeam::Helper # Library of helper methods - veeam.check_os_version(node) + check_os_version(node) raise 'The Veeam Backup and Replication Server must be installed before you can install Veeam Explorers' unless is_package_installed?('Veeam Backup & Replication Server') raise 'The Veeam Backup and Replication Console must be installed before you can install Veeam Explorers' unless is_package_installed?('Veeam Backup & Replication Console') @@ -47,8 +47,8 @@ # Call the Veeam::Helper to find the correct URL based on the version of the Veeam Backup and Replication edition passed # as an attribute. unless new_resource.package_url - new_resource.package_url = veeam.find_package_url(new_resource.version) - new_resource.package_checksum = veeam.find_package_checksum(new_resource.version) + new_resource.package_url = find_package_url(new_resource.version) + new_resource.package_checksum = find_package_checksum(new_resource.version) Chef::Log.info(new_resource.package_url) end @@ -57,7 +57,7 @@ # Determine if all of the Veeam Explorers are installed and if so, then skip the processing. installed_explorers = [] - explorers_list = veeam.explorers_list(new_resource.version) + explorers_list = explorers_list(new_resource.version) new_resource.explorers.each do |explorer| installed_explorers.push(explorer) if is_package_installed?(explorers_list[explorer]) @@ -82,7 +82,7 @@ Chef::Log.debug('Downloading Veeam Backup and Replication software via URL') package_name = new_resource.package_url.split('/').last installer_file_name = win_friendly_path(::File.join(package_save_dir, package_name)) - download_installer(installer_file_name) + iso_installer(installer_file_name, new_resource) # We need to delay the evaluation of this so that we can properly get the value during run time. ruby_block 'Install Veeam Explorers' do @@ -121,69 +121,4 @@ def whyrun_supported? true end - - def find_current_dotnet - installed_version = nil - installed_version_reg_key = registry_get_values('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full') - unless installed_version_reg_key.nil? - installed_version_reg_key.each do |key| - installed_version = key[:data] if key[:name] == 'Release' - end - end - installed_version.nil? ? 0 : installed_version - end - - def validate_powershell_out(script) - # This seemed like the DRYest way to handle the output handling from PowerShell. - cmd = powershell_out(script) - # Only return the output if there were no errors. - return cmd.stdout.chomp if cmd.stderr == '' || cmd.stderr.nil? - raise cmd.inspect if cmd.stderr != '' - end - - def download_installer(downloaded_file_name) - # Download the Installer media - remote_file downloaded_file_name do - source new_resource.package_url - checksum new_resource.package_checksum - use_conditional_get true # this should allow us to prevent duplicate downloads - provider Chef::Provider::File::RemoteFile - action :create - end - - # Mounting the Veeam backup ISO. - powershell_script 'Load Veeam media' do - code <<-EOH - Mount-DiskImage -ImagePath "#{downloaded_file_name}" - EOH - guard_interpreter :powershell_script - not_if "[boolean] (Get-DiskImage -ImagePath '#{downloaded_file_name}').DevicePath" - end - end - - def unmount_installer(downloaded_file_name) - # Unmount the Veeam backup ISO. - powershell_script 'Dismount Veeam media' do - code <<-EOH - Dismount-DiskImage -ImagePath "#{downloaded_file_name}" - EOH - guard_interpreter :powershell_script - only_if "[boolean] (Get-DiskImage -ImagePath '#{downloaded_file_name}').DevicePath" - end - end - - def get_media_installer_location(downloaded_file_name) - # When downloading and mounting the ISO, we need to track back to the Drive Letter. This method will handle - # the look-up and keep the logic out of the main installation code. - Chef::Log.debug 'Searching for the Veeam installation media Drive Letter...' - cmd_str = <<-EOH - $DriveLetter = (Get-DiskImage -ImagePath '#{downloaded_file_name}' | Get-Volume).DriveLetter; - if ( [string]::IsNullOrEmpty($DriveLetter) ){ throw 'The ISO did not mount and we have no idea where why.' } - return ( $DriveLetter +':\' ) - EOH - output = validate_powershell_out(cmd_str) - raise ArgumentError, 'Unable to find the Veeam installation media' unless output - Chef::Log.debug "Found the Veeam installation media at Drive Letter [#{output}]" - output - end end diff --git a/resources/prerequisites.rb b/resources/prerequisites.rb index 04a80ca..b16dd6b 100644 --- a/resources/prerequisites.rb +++ b/resources/prerequisites.rb @@ -34,37 +34,37 @@ # We need to include the windows helpers to keep things dry ::Chef::Provider.send(:include, Windows::Helper) +::Chef::Provider.send(:include, Veeam::Helper) action :install do - veeam = Veeam::Helper # Library of helper methods - veeam.check_os_version(node) + check_os_version(node) # Call the Veeam::Helper to find the correct URL based on the version of the Veeam Backup and Recovery edition passed # as an attribute. unless new_resource.package_url - new_resource.package_url = veeam.find_package_url(new_resource.version) - new_resource.package_checksum = veeam.find_package_checksum(new_resource.version) + new_resource.package_url = find_package_url(new_resource.version) + new_resource.package_checksum = find_package_checksum(new_resource.version) end # Halt this process now. There is no URL for the package. raise ArgumentError, 'You must provide a package URL or choose a valid version' unless new_resource.package_url # Determine if all of the Veeam pre-requisites are installed and if so, then skip the processing. - prerequisites_list = [] + prerequisites_required = [] installed_prerequisites = [] - prerequisites_hash = veeam.prerequisites_list(new_resource.version) + prerequisites_hash = prerequisites_list(new_resource.version) prerequisites_hash.each do |item, prerequisites| package_name = prerequisites.map { |k, _v| k }.join(',') unless item == 'SQL' && new_resource.install_sql == false - prerequisites_list.push(package_name) + prerequisites_required.push(package_name) installed_prerequisites.push(package_name) if is_package_installed?(package_name) end end # Compare the required Prerequisites with those installed. If all are installed, then # we should report no change back. By returning 'false', Chef will report that the resource is up-to-date. - return false if (prerequisites_list - installed_prerequisites).empty? && find_current_dotnet >= 379893 + return false if (prerequisites_required - installed_prerequisites).empty? && find_current_dotnet >= 379893 package_save_dir = win_friendly_path(::File.join(::Chef::Config[:file_cache_path], 'package')) @@ -81,7 +81,7 @@ Chef::Log.debug('Downloading Veeam Backup and Recovery software via URL') package_name = new_resource.package_url.split('/').last installer_file_name = win_friendly_path(::File.join(package_save_dir, package_name)) - download_installer(installer_file_name) + iso_installer(installer_file_name, new_resource) install_dotnet(installer_file_name) install_sql_tools(installer_file_name) @@ -99,71 +99,6 @@ def whyrun_supported? true end - def find_current_dotnet - installed_version = nil - installed_version_reg_key = registry_get_values('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full') - unless installed_version_reg_key.nil? - installed_version_reg_key.each do |key| - installed_version = key[:data] if key[:name] == 'Release' - end - end - installed_version.nil? ? 0 : installed_version - end - - def validate_powershell_out(script) - # This seemed like the DRYest way to handle the output handling from PowerShell. - cmd = powershell_out(script) - # Check powershell output - raise cmd.inspect if cmd.stderr != '' - cmd.stdout.chop - end - - def download_installer(downloaded_file_name) - # Download the Installer media - remote_file downloaded_file_name do - source new_resource.package_url - checksum new_resource.package_checksum - provider Chef::Provider::RemoteFile - use_conditional_get true # this should allow us to prevent duplicate downloads - action :create - end - - # Mounting the Veeam backup ISO. - powershell_script 'Load Veeam media' do - code <<-EOH - Mount-DiskImage -ImagePath "#{downloaded_file_name}" - EOH - guard_interpreter :powershell_script - not_if "[boolean] (Get-DiskImage -ImagePath '#{downloaded_file_name}').DevicePath" - end - end - - def unmount_installer(downloaded_file_name) - # Unmount the Veeam backup ISO. - powershell_script 'Dismount Veeam media' do - code <<-EOH - Dismount-DiskImage -ImagePath "#{downloaded_file_name}" - EOH - guard_interpreter :powershell_script - only_if "[boolean] (Get-DiskImage -ImagePath '#{downloaded_file_name}').DevicePath" - end - end - - def get_media_installer_location(downloaded_file_name) - # When downloading and mounting the ISO, we need to track back to the Drive Letter. This method will handle - # the look-up and keep the logic out of the main installation code. - Chef::Log.debug 'Searching for the Veeam installation media Drive Letter...' - cmd_str = <<-EOH - $DriveLetter = (Get-DiskImage -ImagePath '#{downloaded_file_name}' | Get-Volume).DriveLetter; - if ( [string]::IsNullOrEmpty($DriveLetter) ){ throw 'The ISO did not mount and we have no idea why.' } - return ( $DriveLetter +':\' ) - EOH - output = validate_powershell_out(cmd_str) - raise ArgumentError, 'Unable to find the Veeam installation media' unless output - Chef::Log.debug "Found the Veeam installation media at Drive Letter [#{output}]" - output - end - def install_dotnet(downloaded_file_name) return 'Already installed' if find_current_dotnet >= 379893 reboot 'DotNet Install Complete' do @@ -188,7 +123,7 @@ def install_dotnet(downloaded_file_name) end def install_sql_tools(downloaded_file_name) - prerequisites_hash = Veeam::Helper.prerequisites_list(new_resource.version) + prerequisites_hash = prerequisites_list(new_resource.version) prerequisites = {} prerequisites_hash.each do |item, prereq| @@ -219,8 +154,10 @@ def install_sql_express(downloaded_file_name) installed_version_reg_key = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\\MSSQL11.SQLEXPRESS\MSSQLServer\CurrentVersion' return 'Already Installed' if registry_key_exists?(installed_version_reg_key, :machine) config_file_path = win_friendly_path(::File.join(::Chef::Config[:file_cache_path], 'ConfigurationFile.ini')) + output_file = win_friendly_path(::File.join(Chef::Config[:file_cache_path], 'sql_install.log')) + sql_build_script = win_friendly_path(::File.join(Chef::Config[:file_cache_path], 'sql_build_script.ps1')) - sql_sys_admin_list = 'NT AUTHORITY\SYSTEM' + sql_sys_admin_list = "NT AUTHORITY\\SYSTEM\" \"#{node['hostname']}\\#{ENV['USERNAME']}" sql_sys_admin_list = node['veeam']['server']['vbr_service_user'] if node['veeam']['server']['vbr_service_user'] template config_file_path do @@ -232,19 +169,105 @@ def install_sql_express(downloaded_file_name) sqlSysAdminList: sql_sys_admin_list ) end + ruby_block 'Install the SQL Express' do block do install_media_path = get_media_installer_location(downloaded_file_name) - windows_package 'Microsoft SQL Server 2014 (64-bit)' do - source "#{install_media_path}\\Redistr\\x64\\SQLEXPR_x64_ENU.exe" - timeout 1500 - installer_type :custom - provider Chef::Provider::Package::Windows - options "/q /ConfigurationFile=#{config_file_path}" - action :install - returns [0, 42, 127, 3010] + sql_installer = "#{install_media_path}\\Redistr\\x64\\SQLEXPR_x64_ENU.exe" + + template sql_build_script do + backup false + sensitive true + source ::File.join('sql_server', 'sql_build_script.ps1.erb') + variables( + sql_build_command: "#{sql_installer} /q /ConfigurationFile=#{config_file_path}", + outputFilePath: output_file + ) + action :create end + + setup_task(sql_build_script) end end + + ruby_block 'Check SQL Install State' do + block do + monitor_task_status('Setup SQL Install Task') + Chef::Log.debug 'Check the status of the install' + cmd_str = <<-EOH + $results = ( Get-Content -Path #{output_file} | Out-String | ConvertFrom-Json ); + if (-not [string]::IsNullOrEmpty($results.error) ){ throw $results.error } + EOH + cmd = powershell_out(cmd_str) + # Check powershell output + raise cmd.stderr if cmd.stderr != '' + end + action :run + end + + [config_file_path, sql_build_script].each do |filename| + file filename do + action :delete + backup false + end + end + + windows_task 'Remove SQL Install Task' do + task_name 'Setup SQL Install Task' + action :delete + end + end + + def setup_task(sql_build_script) + windows_task 'Setup SQL Install Task' do + command "C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Bypass -File #{sql_build_script}" + run_level :highest + frequency :onstart + action :create + end + + powershell_script 'Modify Task to allow execution on laptop' do + code <<-EOH + $TaskName = 'Setup SQL Install Task' + $Task = Get-ScheduledTask -TaskName $TaskName + if($Task){ + $Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -Compatibility 'Win8' + Set-ScheduledTask -TaskName $TaskName -Settings $Settings + } + EOH + action :run + notifies :run, 'windows_task[Setup SQL Install Task]', :immediately + end + end + + def monitor_task_status(task_name) + Chef::Log.info "#{task_name}: Monitoring Task until completion" + cmd_str = <<-EOH + $TaskName = "#{task_name}"; + $Task = Get-ScheduledTask -TaskName $TaskName + while($Task.State -eq "Running") + { + Start-Sleep -s 5 + $Task = Get-ScheduledTask -TaskName $TaskName + } + if($Task.State -eq "Ready"){ + $TaskResults = ($Task | Get-ScheduledTaskInfo) + if($TaskResults.LastTaskResult -ne 0){ + throw $($TaskName + ": failed to execute. Error code {0}" -f $TaskResults.LastTaskResult) + } else { + return # Success + } + } else { + throw $($Task | Get-ScheduledTaskInfo).LastTaskResult + } + EOH + # We need to extend this time in the event there is a long running task that we have to wait to complete. + # The default is 600 but we will bump to 3600 + # + # TODO: make the timeout on tasks variable + cmd = powershell_out(cmd_str, timeout: 5400) + # Check powershell output + raise cmd.stderr unless cmd.stderr.nil? || cmd.stderr.empty? + Chef::Log.debug "#{task_name}: Monitoring Task completed" end end diff --git a/resources/proxy.rb b/resources/proxy.rb new file mode 100644 index 0000000..191b927 --- /dev/null +++ b/resources/proxy.rb @@ -0,0 +1,310 @@ +# Cookbook Name:: veeam +# Resource:: proxy +# +# Author:: Jeremy Goodrum +# Email:: chef@exospheredata.com +# +# Version:: 1.0.0 +# Date:: 2018-04-29 +# +# Copyright (c) 2016 Exosphere Data LLC, All Rights Reserved. +# +# 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. + +default_action :add + +property :hostname, String, name_property: true, required: true + +# VBR Server Connection Properties +property :vbr_server, String, required: true +property :vbr_server_port, [Integer, String], default: 9392 +property :vbr_username, String, sensitive: true, required: true +property :vbr_password, String, sensitive: true, required: true + +# Proxy Server Credentials +property :proxy_username, String, sensitive: true +property :proxy_password, String, sensitive: true + +property :proxy_type, String, equal_to: %w(vmware hyperv), default: 'vmware' + +property :description, [String, nil] + +# => Specifies the number of concurrent tasks that can be assigned to the proxy simultaneously. +# => Permitted values: 1-100. +property :max_tasks, Integer, regex: [/(?:\b|-)([1-9]{1,2}[0]?|100)\b/], default: 2 + +# => Specifies the transport mode used by the backup proxy +property :transport_mode, String, equal_to: %w(Auto DirectStorageAccess HotAdd Nbd), default: 'Auto' + +# *** Future Properties *** +# => Specifies the mode the proxy will use to connect to datastores +property :datastore_mode, String, equal_to: %w(Auto Manual), default: 'Auto' + +# => Specifies the list of datastores to which the backup proxy has a direct SAN or NFS connection. +property :datastore, String + +# => Indicates if the backup proxy must fail over to the Network transport mode if it fails +# => to transport data in the Direct storage access or Virtual appliance transport mode. +property :enable_failover_to_ndb, [TrueClass, FalseClass], default: false + +# => Indicates if VM data must be transported over an encrypted SSL connection in the Network transport mode. +property :host_encryption, [TrueClass, FalseClass], default: false +# ************************* + +# We need to include the windows helpers to keep things dry +::Chef::Provider.send(:include, Windows::Helper) +::Chef::Provider.send(:include, Veeam::Helper) + +action :add do + check_os_version(node) + # We will use the Windows Helper 'is_package_installed?' to see if the Console is installed. If it is installed, then + # we should report no change back. By returning 'false', Chef will report that the resource is up-to-date. + no_console_error = 'This resource requires that the Veeam Backup & Replication Console be installed on this host' + raise ArgumentError, no_console_error unless is_package_installed?('Veeam Backup & Replication Console') + + return false if proxy_currently_registered && server_currently_registered + + raise ArgumentError, 'The Proxy Username is a required attribute' if new_resource.proxy_username.nil? + raise ArgumentError, 'The Proxy Password is a required attribute' if new_resource.proxy_password.nil? + + powershell_script 'Register Windows Server' do + code <<-EOH + Add-PSSnapin VeeamPSSnapin + try { + Connect-VBRServer ` + -Server #{new_resource.vbr_server} ` + -User #{new_resource.vbr_username} ` + -Password #{new_resource.vbr_password} ` + -Port #{new_resource.vbr_server_port} ` + -ErrorAction Stop + + $VbrCredentials = (Get-VBRCredentials -Name #{new_resource.proxy_username}) + if ($VbrCredentials -is [array]){ + $VbrCredentials = $VbrCredentials[0] + } + if (!$VbrCredentials){ + $VbrCredentials = (Add-VBRCredentials ` + -User #{new_resource.proxy_username} ` + -Password #{new_resource.proxy_password} ` + -Description "ADDED BY CHEF: Proxy Server Credentials" ` + -Type Windows) + } + + $VbrServer = Get-VBRServer -Name #{new_resource.hostname} + if(!$VbrServer) { + Add-VBRWinServer -Name #{new_resource.hostname} ` + -Description "#{new_resource.description ? "ADDED by CHEF: #{new_resource.description}" : 'ADDED by CHEF: Proxy Server'}" ` + -Credentials $VbrCredentials + } + } catch { + throw $_.Exception.message + } finally { + Disconnect-VBRServer + } + EOH + action :run + not_if { server_currently_registered } + end + + powershell_script 'Register Veeam Proxy' do + code <<-EOH + Add-PSSnapin VeeamPSSnapin + try { + Connect-VBRServer ` + -Server #{new_resource.vbr_server} ` + -User #{new_resource.vbr_username} ` + -Password #{new_resource.vbr_password} ` + -Port #{new_resource.vbr_server_port} ` + -ErrorAction Stop + + $VbrCredentials = (Get-VBRCredentials -Name #{new_resource.proxy_username}) + if (!$VbrCredentials){ + throw "No stored Credentials found for User: #{new_resource.proxy_username}" + } + if ($VbrCredentials -is [array]){ + $VbrCredentials = $VbrCredentials[0] + } + + if("#{new_resource.proxy_type}" -eq "vmware"){ + $VbrViProxy = Get-VBRViProxy -Name #{new_resource.hostname} + if(!$VbrViProxy) { + Add-VBRViProxy -Server #{new_resource.hostname} ` + -Description "#{new_resource.description ? "ADDED by CHEF: #{new_resource.description}" : 'ADDED by CHEF: Proxy Server'}" ` + -MaxTasks #{new_resource.max_tasks} ` + -TransportMode #{new_resource.transport_mode} + } + } + elseif ("#{new_resource.proxy_type}" -eq "hyperv"){ + $VbrHvProxy = Get-VBRHvProxy -Name #{new_resource.hostname} + if(!$VbrHvProxy) { + Add-VBRHvProxy -Server #{new_resource.hostname} ` + -Description "#{new_resource.description ? "ADDED by CHEF: #{new_resource.description}" : 'ADDED by CHEF: Proxy Server'}" ` + -MaxTasks #{new_resource.max_tasks} ` + } + } + else { + throw "Invalid Proxy Type provided: #{new_resource.proxy_type}" + } + } catch { + throw $_.Exception.message + } finally { + Disconnect-VBRServer + } + EOH + action :run + not_if { proxy_currently_registered } + end +end + +action :remove do + check_os_version(node) + # We will use the Windows Helper 'is_package_installed?' to see if the Console is installed. If it is installed, then + # we should report no change back. By returning 'false', Chef will report that the resource is up-to-date. + no_console_error = 'This resource requires that the Veeam Backup & Replication Console be installed on this host' + raise ArgumentError, no_console_error unless is_package_installed?('Veeam Backup & Replication Console') + + return false if !proxy_currently_registered && !server_currently_registered + + powershell_script 'Remove Veeam Proxy' do + code <<-EOH + Add-PSSnapin VeeamPSSnapin + try { + Connect-VBRServer ` + -Server #{new_resource.vbr_server} ` + -User #{new_resource.vbr_username} ` + -Password #{new_resource.vbr_password} ` + -Port #{new_resource.vbr_server_port} ` + -ErrorAction Stop + + if("#{new_resource.proxy_type}" -eq "vmware"){ + $VbrViProxy = Get-VBRViProxy -Name #{new_resource.hostname} + if($VbrViProxy) { + Remove-VBRViProxy -Proxy #{new_resource.hostname} -Confirm:$False + } + } + elseif ("#{new_resource.proxy_type}" -eq "hyperv"){ + $VbrHvProxy = Get-VBRHvProxy -Name #{new_resource.hostname} + if($VbrHvProxy) { + Remove-VBRHvProxy -Proxy #{new_resource.hostname} -Confirm:$False + } + } + else { + throw "Invalid Proxy Type provided: #{new_resource.proxy_type}" + } + } catch { + throw $_.Exception.message + } finally { + Disconnect-VBRServer + } + EOH + action :run + only_if { proxy_currently_registered } + end + + powershell_script 'Remove Windows Server' do + code <<-EOH + Add-PSSnapin VeeamPSSnapin + try { + Connect-VBRServer ` + -Server #{new_resource.vbr_server} ` + -User #{new_resource.vbr_username} ` + -Password #{new_resource.vbr_password} ` + -Port #{new_resource.vbr_server_port} ` + -ErrorAction Stop + + $VbrServer = Get-VBRServer -Name #{new_resource.hostname} + if($VbrServer) { + Remove-VBRServer -Server #{new_resource.hostname} -Confirm:$False + } + } catch { + throw $_.Exception.message + } finally { + Disconnect-VBRServer + } + EOH + action :run + only_if { server_currently_registered } + end +end + +action_class do + def whyrun_supported? + true + end + + def server_currently_registered + cmd_str = <<-EOH + # Check if Host is registered + Add-PSSnapin VeeamPSSnapin + try { + Connect-VBRServer ` + -Server #{new_resource.vbr_server} ` + -User #{new_resource.vbr_username} ` + -Password #{new_resource.vbr_password} ` + -Port #{new_resource.vbr_server_port} ` + -ErrorAction Stop + $VbrServer = Get-VBRServer -Name "#{new_resource.name}" -ErrorAction SilentlyContinue + if($VbrServer){ + return $True + } + return $False + } catch { + throw $_.Exception.message + } finally { + Disconnect-VBRServer + } + EOH + output = validate_powershell_out(cmd_str) + return false if output == 'False' + true + end + + def proxy_currently_registered + cmd_str = <<-EOH + # Check if Proxy is registered + Add-PSSnapin VeeamPSSnapin + try { + Connect-VBRServer ` + -Server #{new_resource.vbr_server} ` + -User #{new_resource.vbr_username} ` + -Password #{new_resource.vbr_password} ` + -Port #{new_resource.vbr_server_port} ` + -ErrorAction Stop + + if("#{new_resource.proxy_type}" -eq "vmware"){ + $VbrViProxy = Get-VBRViProxy -Name "#{new_resource.name}" -ErrorAction SilentlyContinue + if($VbrViProxy){ + return $True + } + } + elseif ("#{new_resource.proxy_type}" -eq "hyperv"){ + $VbrHvProxy = Get-VBRHvProxy -Name #{new_resource.hostname} -ErrorAction SilentlyContinue + if(!$VbrHvProxy) { + return $True + } + } + else { + throw "Invalid Proxy Type provided: #{new_resource.proxy_type}" + } + return $False + } catch { + throw $_.Exception.message + } finally { + Disconnect-VBRServer + } + EOH + output = validate_powershell_out(cmd_str) + return false if output == 'False' + true + end +end diff --git a/resources/server.rb b/resources/server.rb index 1e91e7f..64420e7 100644 --- a/resources/server.rb +++ b/resources/server.rb @@ -23,8 +23,8 @@ default_action :install -property :package_name, String -property :share_path, String +property :package_name, String # Future Property +property :share_path, String # Future Property property :package_url, String property :package_checksum, String @@ -56,10 +56,10 @@ # We need to include the windows helpers to keep things dry ::Chef::Provider.send(:include, Windows::Helper) +::Chef::Provider.send(:include, Veeam::Helper) action :install do - veeam = Veeam::Helper # Library of helper methods - veeam.check_os_version(node) + check_os_version(node) # We will use the Windows Helper 'is_package_installed?' to see if the Server is installed. If it is installed, then # we should report no change back. By returning 'false', Chef will report that the resource is up-to-date. @@ -82,8 +82,8 @@ # Call the Veeam::Helper to find the correct URL based on the version of the Veeam Backup and Replication edition passed # as an attribute. unless new_resource.package_url - new_resource.package_url = veeam.find_package_url(new_resource.version) - new_resource.package_checksum = veeam.find_package_checksum(new_resource.version) + new_resource.package_url = find_package_url(new_resource.version) + new_resource.package_checksum = find_package_checksum(new_resource.version) Chef::Log.info(new_resource.package_url) end @@ -97,7 +97,7 @@ Chef::Log.debug('Downloading Veeam Backup and Replication software via URL') package_name = new_resource.package_url.split('/').last installer_file_name = win_friendly_path(::File.join(package_save_dir, package_name)) - download_installer(installer_file_name) + iso_installer(installer_file_name, new_resource) new_resource.vbr_license_file = find_vbr_license unless new_resource.evaluation @@ -129,70 +129,6 @@ def whyrun_supported? true end - def find_current_dotnet - installed_version = nil - installed_version_reg_key = registry_get_values('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full') - unless installed_version_reg_key.nil? - installed_version_reg_key.each do |key| - installed_version = key[:data] if key[:name] == 'Release' - end - end - installed_version.nil? ? 0 : installed_version - end - - def validate_powershell_out(script) - # This seemed like the DRYest way to handle the output handling from PowerShell. - cmd = powershell_out(script) - # Only return the output if there were no errors. - return cmd.stdout.chomp if cmd.stderr == '' || cmd.stderr.nil? - raise cmd.inspect if cmd.stderr != '' - end - - def download_installer(downloaded_file_name) - # Download the Installer media - remote_file downloaded_file_name do - source new_resource.package_url - checksum new_resource.package_checksum - use_conditional_get true # this should allow us to prevent duplicate downloads - action :create - end - - # Mounting the Veeam backup ISO. - powershell_script 'Load Veeam media' do - code <<-EOH - Mount-DiskImage -ImagePath "#{downloaded_file_name}" - EOH - guard_interpreter :powershell_script - not_if "[boolean] (Get-DiskImage -ImagePath '#{downloaded_file_name}').DevicePath" - end - end - - def unmount_installer(downloaded_file_name) - # Unmount the Veeam backup ISO. - powershell_script 'Dismount Veeam media' do - code <<-EOH - Dismount-DiskImage -ImagePath "#{downloaded_file_name}" - EOH - guard_interpreter :powershell_script - only_if "[boolean] (Get-DiskImage -ImagePath '#{downloaded_file_name}').DevicePath" - end - end - - def get_media_installer_location(downloaded_file_name) - # When downloading and mounting the ISO, we need to track back to the Drive Letter. This method will handle - # the look-up and keep the logic out of the main installation code. - Chef::Log.debug 'Searching for the Veeam installation media Drive Letter...' - cmd_str = <<-EOH - $DriveLetter = (Get-DiskImage -ImagePath '#{downloaded_file_name}' | Get-Volume).DriveLetter; - if ( [string]::IsNullOrEmpty($DriveLetter) ){ throw 'The ISO did not mount and we have no idea where why.' } - return ( $DriveLetter +':\' ) - EOH - output = validate_powershell_out(cmd_str) - raise ArgumentError, 'Unable to find the Veeam installation media' unless output - Chef::Log.debug "Found the Veeam installation media at Drive Letter [#{output}]" - output - end - def find_vbr_license license_file = win_friendly_path(::File.join(Chef::Config[:file_cache_path], 'Veeam-license-file.lic')) diff --git a/resources/upgrade.rb b/resources/upgrade.rb new file mode 100644 index 0000000..a2b5b1b --- /dev/null +++ b/resources/upgrade.rb @@ -0,0 +1,210 @@ +# Cookbook Name:: veeam +# Resource:: upgrade +# +# Author:: Jeremy Goodrum +# Email:: chef@exospheredata.com +# +# Version:: 1.0.0 +# Date:: 2018-04-29 +# +# Copyright (c) 2016 Exosphere Data LLC, All Rights Reserved. +# +# 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. + +default_action :install + +property :build, String, name_property: true + +property :package_url, String +property :package_checksum, String + +property :version, String +property :keep_media, [TrueClass, FalseClass], default: false +property :auto_reboot, [TrueClass, FalseClass], default: true + +property :package_name, String # Future Property +property :share_path, String # Future Property + +# => We need to include the windows helpers to keep things dry +::Chef::Provider.send(:include, Windows::Helper) +::Chef::Provider.send(:include, Veeam::Helper) + +action :install do + # => Verify that the node passes the OS version checks. + check_os_version(node) + + # => Call the Veeam::Helper to find the correct URL based on the build or version of the Veeam + # => Backup and Replication edition passed as an attribute. + unless new_resource.package_url + new_resource.build = new_resource.version if new_resource.build.nil? + # => If there is no url, no version, and no build then what are we doing here? + raise ArgumentError, 'You must provide a package URL or choose a valid build' unless new_resource.build + new_resource.package_url = find_update_url(new_resource.build) + new_resource.package_checksum = find_update_checksum(new_resource.build) + # => Halt this process now. There is no URL for the package. + raise ArgumentError, 'You must provide a package URL or choose a valid build' unless new_resource.package_url + Chef::Log.debug("Dynamically found the Build Upgrade Url: #{new_resource.package_url}") + end + + # => So without a build we will have a hard time determining how to upgrade. Since we have the update url + # => we can extract the build from this. + new_resource.build = /(\d+.\d+.\d+.\d+)/.match(new_resource.package_url.split('/')[-1]).captures[0] unless new_resource.build + + # => We need to determine the actual build of installed Veeam Software. Since there are three main packages + # => we will need to iterate through their possible locations. + current_build = nil + ['Veeam Backup & Replication Console', 'Veeam Backup & Replication Server', 'Veeam Backup & Replication Catalog'].each do |package| + current_build = find_current_veeam_version(package) + break unless current_build.nil? + end + # => If there is no currently installed software then we should gracefully handle this + # => by returning false + return false unless current_build + + # => If the current installed version is greater or equal to the requested build version + # => then return false to notify everyone that we are up to date. + return false unless Gem::Version.new(new_resource.build) > Gem::Version.new(current_build) + + converge_by "Upgrading Veeam Installation to Version #{new_resource.build}" do + # We need to verify that .NET Framework 4.5.2 or higher has been installed on the machine + raise 'The Veeam Backup and Replication Server requires that Microsoft .NET Framework 4.5.2 or higher be installed. Please install the Veeam pre-requisites' if find_current_dotnet < 379893 + + package_save_dir = win_friendly_path(::File.join(::Chef::Config[:file_cache_path], 'package')) + + # This will only create the directory if it does not exist which is likely the case if we have + # never performed a remote_file install. + directory package_save_dir do + action :create + end + + # Since we are passing a URL, it is important that we handle the pull of the file as well as extraction. + # We likely will receive an ISO but it is possible that we will have a ZIP or other compressed file type. + # This is easy to handle as long as we add a method to check for the file base type. + + Chef::Log.debug('Downloading Veeam Backup and Replication software via URL') + package_name = new_resource.package_url.split('/').last + %w(& $).each do |special_char| + package_name = package_name.gsub(special_char, '_') + end + package_type = ::File.extname(package_name) + installer_file_name = win_friendly_path(::File.join(package_save_dir, package_name)) + + install_media_path = if package_type == '.iso' + iso_installer(installer_file_name, new_resource) + else + extract_installer(installer_file_name, new_resource) + end + + ruby_block 'Perform Upgrade Procedure' do + block do + install_media_path = get_media_installer_location(installer_file_name) if package_type == '.iso' + perform_server_upgrade(install_media_path) + end + end + + unmount_installer(installer_file_name) + + # If the 'keep_media' property is True, we should report our success but skip the file deletion code below. + return if new_resource.keep_media + + # Since the property 'keep_media' was set to false, we will need to remove it + + # We will want to remove the tmp downloaded file later to save space + file installer_file_name do + backup false + action :delete + end + end +end + +action_class do + def whyrun_supported? + true + end + + def perform_server_upgrade(install_media_path) + Chef::Log.debug 'Upgrading Veeam Backup server service... begin' + # VBR Service Configuration + cmd_str = <<~EOH + function Sort-Naturally + # Great Sorting Function + # https://stackoverflow.com/a/48333846 + { + PARAM( + [string[]]$files + ) + + Add-Type -TypeDefinition @' + using System; + using System.Collections; + using System.Collections.Generic; + using System.Runtime.InteropServices; + + namespace NaturalSort { + public static class NaturalSort + { + [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] + public static extern int StrCmpLogicalW(string psz1, string psz2); + + public static System.Collections.ArrayList Sort(System.Collections.ArrayList foo) + { + foo.Sort(new NaturalStringComparer()); + return foo; + } + } + + public class NaturalStringComparer : IComparer + { + public int Compare(object x, object y) + { + return NaturalSort.StrCmpLogicalW(x.ToString(), y.ToString()); + } + } + } + '@ + + return [NaturalSort.NaturalSort]::Sort($files) + } + $all_veeam_updates = (Get-ChildItem -Path "#{install_media_path}\\Updates" -ErrorAction SilentlyContinue | %{$_.Name}) + if($all_veeam_updates){ + $sorted_files = (Sort-Naturally -files $all_veeam_updates) # Grab the most recent Update + $latest_veeam_updates = if($sorted_files.count -gt 1){ $sorted_files[-1] }else{ $sorted_files } + $veeam_backup_server_installer = ( "#{install_media_path}\\Updates\\$latest_veeam_updates") + $log_file = $latest_veeam_updates.replace(".exe",".log") + Write-Host ($veeam_backup_server_installer + ' /silent /norestart /log #{::Chef::Config[:file_cache_path]}\\' + $log_file + ' VBR_AUTO_UPGRADE=1') + $output = (Start-Process -FilePath $veeam_backup_server_installer -ArgumentList $(' /silent /noreboot /log #{::Chef::Config[:file_cache_path]}\\' + $log_file + ' VBR_AUTO_UPGRADE=1') -Wait -Passthru -ErrorAction Stop) + switch ( $output.ExitCode){ + 0 { Write-Host "Update Completed Successfully" } + 3010 { Write-Host "Need Reboot"} + default { + throw ("The updates failed with ExitCode [{0}]. The package is {1}" -f $output.ExitCode, $veeam_backup_server_installer ) + } + } + } else { + Write-Host 'No update' + } + EOH + output = validate_powershell_out(cmd_str, timeout: 1800) + if output == 'No update' + Chef::Log.warn 'Upgrade Skipped as no Upgrade files found' + return false + end + + reboot 'Required Reboot after Veeam Upgrade' do + action :request_reboot + only_if { reboot_pending? } + only_if { new_resource.auto_reboot } + end + Chef::Log.debug 'Upgrading Veeam Backup server service... success' + end +end diff --git a/spec/unit/lwrps/veeam_prerequisites_spec.rb b/spec/unit/lwrps/veeam_prerequisites_spec.rb index 45d1b0d..9109c8d 100644 --- a/spec/unit/lwrps/veeam_prerequisites_spec.rb +++ b/spec/unit/lwrps/veeam_prerequisites_spec.rb @@ -46,6 +46,10 @@ expect(chef_run).to run_ruby_block('Install the SQL Management Tools') expect(chef_run).to create_template(win_friendly_path(::File.join(Chef::Config[:file_cache_path], 'ConfigurationFile.ini'))) expect(chef_run).to run_ruby_block('Install the SQL Express') + expect(chef_run).to run_ruby_block('Check SQL Install State') + expect(chef_run).to delete_file(win_friendly_path(::File.join(Chef::Config[:file_cache_path], 'ConfigurationFile.ini'))) + expect(chef_run).to delete_file(win_friendly_path(::File.join(Chef::Config[:file_cache_path], 'sql_build_script.ps1'))) + expect(chef_run).to delete_windows_task('Remove SQL Install Task') reboot_handler = chef_run.reboot('DotNet Install Complete') expect(reboot_handler).to do_nothing diff --git a/spec/unit/lwrps/veeam_proxy_add_spec.rb b/spec/unit/lwrps/veeam_proxy_add_spec.rb new file mode 100644 index 0000000..46ad673 --- /dev/null +++ b/spec/unit/lwrps/veeam_proxy_add_spec.rb @@ -0,0 +1,134 @@ +# +# Cookbook Name:: veeam +# Spec:: proxy +# +# Copyright (c) 2016 Exosphere Data LLC, All Rights Reserved. + +require 'spec_helper' + +describe 'veeam::proxy_server' do + before do + mock_windows_system_framework # Windows Framework Helper from 'spec/windows_helper.rb' + stub_command('sc.exe query W3SVC').and_return 1 + stub_command(/Get-DiskImage/).and_return(false) + allow_any_instance_of(Chef::DSL::RegistryHelper) + .to receive(:registry_get_values) + .and_return([{}, {}, {}, {}, {}, {}, { name: 'Release', data: 379893 }]) + allow_any_instance_of(Chef::Provider) + .to receive(:is_package_installed?) + .and_return(true) + end + context 'Install Veeam Backup and Recovery Console' do + platforms = { + 'windows' => { + 'versions' => %w(2012) + # 'versions' => %w(2012 2012R2 2016) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + before do + Fauxhai.mock(platform: platform, version: version) + allow(Mixlib::ShellOut).to receive(:new).and_return(shellout) + node.normal['veeam']['proxy']['vbr_server'] = 'veeam' + node.normal['veeam']['proxy']['vbr_username'] = 'admin' + node.normal['veeam']['proxy']['vbr_password'] = 'password' + node.normal['veeam']['proxy']['proxy_username'] = 'admin' + node.normal['veeam']['proxy']['proxy_password'] = 'password' + node.normal['veeam']['build'] = '9.5.0.1536' + end + let(:shellout) do + # Creating a double allows us to stub out the response from Mixlib::ShellOut + double(run_command: nil, error!: nil, stdout: '', stderr: '', exitstatus: 0, live_stream: '') + end + let(:false_shell) do + # We need to have a seperate double that we can use rather than trying to reuse the same one. + # This prevents odd failures when calling two or more stubs. + double(run_command: nil, error!: nil, stdout: 'False', stderr: '', exitstatus: 0, live_stream: '') + end + let(:true_shell) do + # We need to have a seperate double that we can use rather than trying to reuse the same one. + # This prevents odd failures when calling two or more stubs. + double(run_command: nil, error!: nil, stdout: 'True', stderr: '', exitstatus: 0, live_stream: '') + end + let(:environment_var) do + # When declaring a :new Mixlib::ShellOut, we need to pass this environment hash + { environment: { 'LC_ALL' => 'en_US.UTF-8', 'LANGUAGE' => 'en_US.UTF-8', 'LANG' => 'en_US.UTF-8', 'PATH' => /.+/ } } + end + + let(:runner) do + ChefSpec::SoloRunner.new(platform: platform, version: version, file_cache_path: '/tmp/cache', step_into: ['veeam_proxy']) + end + let(:node) { runner.node } + let(:chef_run) { runner.converge(described_recipe) } + let(:package_save_dir) { win_friendly_path(::File.join(Chef::Config[:file_cache_path], 'package')) } + let(:downloaded_file_name) { win_friendly_path(::File.join(package_save_dir, 'VeeamBackup_Replication_9.5.0.711.iso')) } + + it 'converges successfully' do + expect { chef_run }.not_to raise_error + expect(chef_run).to install_windows_feature('FS-FileServer') + expect(chef_run).to install_windows_feature('Print-Server') + expect(chef_run).to install_veeam_prerequisites('Install Veeam Prerequisites') + expect(chef_run).to install_veeam_console('Install Veeam Backup console') + expect(chef_run).to install_veeam_upgrade('9.5.0.1536') + expect(chef_run).to add_veeam_proxy(node['hostname']) + end + it 'Step into LWRP - veeam_proxy' do + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Host is registered/, environment_var).and_return(false_shell) + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Proxy is registered/, environment_var).and_return(false_shell) + expect { chef_run }.not_to raise_error + expect(chef_run).to run_powershell_script('Register Windows Server') + expect(chef_run).to run_powershell_script('Register Veeam Proxy') + end + it 'Should not register the host if already done' do + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Host is registered/, environment_var).and_return(true_shell) + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Proxy is registered/, environment_var).and_return(true_shell) + expect { chef_run }.not_to raise_error + expect(chef_run).to_not run_powershell_script('Register Windows Server') + expect(chef_run).to_not run_powershell_script('Register Veeam Proxy') + end + it 'Should not register the host as Proxy if Registration disabled' do + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Host is registered/, environment_var).and_return(true_shell) + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Proxy is registered/, environment_var).and_return(true_shell) + expect { chef_run }.not_to raise_error + expect(chef_run).to_not run_powershell_script('Register Windows Server') + expect(chef_run).to_not run_powershell_script('Register Veeam Proxy') + end + it 'Should raise an exception if the Veeam Console is not installed' do + allow_any_instance_of(Chef::Provider) + .to receive(:is_package_installed?) + .and_return(false) + expect { chef_run }.to raise_error(ArgumentError, /This resource requires that the Veeam Backup & Replication Console be installed on this host/) + end + end + end + end + end + context 'Does not install' do + platforms = { + 'windows' => { + 'versions' => %w(2008R2) # Unable to test plain Win2008 since Fauxhai doesn't have a template for 2008 + }, + 'ubuntu' => { + 'versions' => %w(16.04) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + before do + Fauxhai.mock(platform: platform, version: version) + end + + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: platform, version: version).converge(described_recipe) + end + it 'raises an exception' do + expect { chef_run }.to raise_error(ArgumentError, 'This recipe requires a Windows 2012 or higher host!') + end + end + end + end + end +end diff --git a/spec/unit/lwrps/veeam_proxy_remove_spec.rb b/spec/unit/lwrps/veeam_proxy_remove_spec.rb new file mode 100644 index 0000000..ba8d741 --- /dev/null +++ b/spec/unit/lwrps/veeam_proxy_remove_spec.rb @@ -0,0 +1,134 @@ +# +# Cookbook Name:: veeam +# Spec:: proxy +# +# Copyright (c) 2016 Exosphere Data LLC, All Rights Reserved. + +require 'spec_helper' + +describe 'veeam::proxy_remove' do + before do + mock_windows_system_framework # Windows Framework Helper from 'spec/windows_helper.rb' + stub_command('sc.exe query W3SVC').and_return 1 + stub_command(/Get-DiskImage/).and_return(false) + allow_any_instance_of(Chef::DSL::RegistryHelper) + .to receive(:registry_get_values) + .and_return([{}, {}, {}, {}, {}, {}, { name: 'Release', data: 379893 }]) + allow_any_instance_of(Chef::Provider) + .to receive(:is_package_installed?) + .and_return(true) + end + context 'Install Veeam Backup and Recovery Console' do + platforms = { + 'windows' => { + 'versions' => %w(2012) + # 'versions' => %w(2012 2012R2 2016) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + before do + Fauxhai.mock(platform: platform, version: version) + node.normal['veeam']['proxy']['vbr_server'] = 'veeam' + node.normal['veeam']['proxy']['vbr_username'] = 'admin' + node.normal['veeam']['proxy']['vbr_password'] = 'password' + end + let(:shellout) do + # Creating a double allows us to stub out the response from Mixlib::ShellOut + double(run_command: nil, error!: nil, stdout: '', stderr: '', exitstatus: 0, live_stream: '') + end + let(:false_shell) do + # We need to have a seperate double that we can use rather than trying to reuse the same one. + # This prevents odd failures when calling two or more stubs. + double(run_command: nil, error!: nil, stdout: 'False', stderr: '', exitstatus: 0, live_stream: '') + end + let(:true_shell) do + # We need to have a seperate double that we can use rather than trying to reuse the same one. + # This prevents odd failures when calling two or more stubs. + double(run_command: nil, error!: nil, stdout: 'True', stderr: '', exitstatus: 0, live_stream: '') + end + let(:environment_var) do + # When declaring a :new Mixlib::ShellOut, we need to pass this environment hash + { environment: { 'LC_ALL' => 'en_US.UTF-8', 'LANGUAGE' => 'en_US.UTF-8', 'LANG' => 'en_US.UTF-8', 'PATH' => /.+/ } } + end + + let(:runner) do + ChefSpec::SoloRunner.new(platform: platform, version: version, file_cache_path: '/tmp/cache', step_into: ['veeam_proxy']) + end + let(:node) { runner.node } + let(:chef_run) { runner.converge(described_recipe) } + let(:package_save_dir) { win_friendly_path(::File.join(Chef::Config[:file_cache_path], 'package')) } + let(:downloaded_file_name) { win_friendly_path(::File.join(package_save_dir, 'VeeamBackup_Replication_9.5.0.711.iso')) } + + it 'converges successfully' do + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Host is registered/, environment_var).and_return(true_shell) + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Proxy is registered/, environment_var).and_return(true_shell) + expect { chef_run }.not_to raise_error + expect(chef_run).to remove_veeam_proxy(node['hostname']) + end + it 'Step into LWRP - veeam_proxy' do + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Host is registered/, environment_var).and_return(true_shell) + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Proxy is registered/, environment_var).and_return(true_shell) + expect { chef_run }.not_to raise_error + expect(chef_run).to run_powershell_script('Remove Veeam Proxy') + expect(chef_run).to run_powershell_script('Remove Windows Server') + end + it 'Should not unregister the host if not found in Veeam' do + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Host is registered/, environment_var).and_return(false_shell) + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Proxy is registered/, environment_var).and_return(false_shell) + expect { chef_run }.not_to raise_error + expect(chef_run).to_not run_powershell_script('Remove Veeam Proxy') + expect(chef_run).to_not run_powershell_script('Remove Windows Server') + end + it 'Should unregister the host but skip the Proxy if not configured' do + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Host is registered/, environment_var).and_return(true_shell) + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Proxy is registered/, environment_var).and_return(false_shell) + expect { chef_run }.not_to raise_error + expect(chef_run).to_not run_powershell_script('Remove Veeam Proxy') + expect(chef_run).to run_powershell_script('Remove Windows Server') + end + it 'Should remove the Proxy but skip Host if not configured' do + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Host is registered/, environment_var).and_return(false_shell) + allow(Mixlib::ShellOut).to receive(:new).with(/Check if Proxy is registered/, environment_var).and_return(true_shell) + expect { chef_run }.not_to raise_error + expect(chef_run).to run_powershell_script('Remove Veeam Proxy') + expect(chef_run).to_not run_powershell_script('Remove Windows Server') + end + it 'Should raise an exception if the Veeam Console is not installed' do + allow_any_instance_of(Chef::Provider) + .to receive(:is_package_installed?) + .and_return(false) + expect { chef_run }.to raise_error(ArgumentError, /This resource requires that the Veeam Backup & Replication Console be installed on this host/) + end + end + end + end + end + context 'Does not install' do + platforms = { + 'windows' => { + 'versions' => %w(2008R2) # Unable to test plain Win2008 since Fauxhai doesn't have a template for 2008 + }, + 'ubuntu' => { + 'versions' => %w(16.04) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + before do + Fauxhai.mock(platform: platform, version: version) + end + + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: platform, version: version).converge(described_recipe) + end + it 'raises an exception' do + expect { chef_run }.to raise_error(ArgumentError, 'This recipe requires a Windows 2012 or higher host!') + end + end + end + end + end +end diff --git a/spec/unit/lwrps/veeam_upgrade_spec.rb b/spec/unit/lwrps/veeam_upgrade_spec.rb new file mode 100644 index 0000000..606668f --- /dev/null +++ b/spec/unit/lwrps/veeam_upgrade_spec.rb @@ -0,0 +1,117 @@ +# +# Cookbook Name:: veeam +# Spec:: upgrade +# +# Copyright (c) 2016 Exosphere Data LLC, All Rights Reserved. + +require 'spec_helper' + +describe 'veeam::upgrade' do + before do + mock_windows_system_framework # Windows Framework Helper from 'spec/windows_helper.rb' + stub_command('sc.exe query W3SVC').and_return 1 + stub_command(/Get-DiskImage/).and_return(false) + end + context 'Install Veeam Backup and Recovery Console' do + platforms = { + 'windows' => { + 'versions' => %w(2012) # 2012R2 2016) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + before do + Fauxhai.mock(platform: platform, version: version) + # Need to set a valid .NET Framework version + allow_any_instance_of(Chef::DSL::RegistryHelper) + .to receive(:registry_get_values) + .and_return([{}, {}, {}, {}, {}, {}, { name: 'Release', data: 379893 }]) + allow_any_instance_of(Chef::Provider) + .to receive(:find_current_veeam_version) + .and_return('9.5') + node.normal['veeam']['build'] = '9.5.0.1536' + end + + let(:runner) do + ChefSpec::SoloRunner.new(platform: platform, version: version, file_cache_path: '/tmp/cache', step_into: ['veeam_upgrade']) + end + let(:node) { runner.node } + let(:chef_run) { runner.converge(described_recipe) } + let(:package_save_dir) { win_friendly_path(::File.join(Chef::Config[:file_cache_path], 'package')) } + let(:downloaded_file_name) { win_friendly_path(::File.join(package_save_dir, 'VeeamBackup_Replication_9.5.0.1536.Update3.iso')) } + + it 'converges successfully' do + expect { chef_run }.not_to raise_error + expect(chef_run).to install_veeam_upgrade('9.5.0.1536') + end + it 'converges successfully but skips upgrade if versions matches or higher' do + allow_any_instance_of(Chef::Provider) + .to receive(:find_current_veeam_version) + .and_return('9.5.0.1536') + expect { chef_run }.not_to raise_error + expect(chef_run).to install_veeam_upgrade('9.5.0.1536') + expect(chef_run).to_not run_ruby_block('Perform Upgrade Procedure') + end + it 'Step into LWRP - veeam_upgrade' do + node.normal['veeam']['installer']['update_url'] = 'http://download/VeeamBackup&Replication_9.5.0.1536.Update3.iso' + expect { chef_run }.not_to raise_error + expect(chef_run).to create_directory(package_save_dir) + expect(chef_run).to create_remote_file(downloaded_file_name) + expect(chef_run).to run_powershell_script('Load Veeam media') + expect(chef_run).to run_ruby_block('Perform Upgrade Procedure') + expect(chef_run).to delete_file(downloaded_file_name) + end + it 'should unmount the media' do + stub_command(/Get-DiskImage/).and_return(true) + expect(chef_run).to run_powershell_script('Dismount Veeam media') + end + it 'should extract the media from a zip' do + node.normal['veeam']['installer']['update_url'] = 'http://download/VeeamBackup&Replication_9.5.0.1536.Update3.zip' + downloaded_file_name = win_friendly_path(::File.join(package_save_dir, 'VeeamBackup_Replication_9.5.0.1536.Update3.zip')) + installer_path = win_friendly_path(::File.join(::Chef::Config[:file_cache_path], 'Veeam/VeeamBackup_Replication_9.5.0.1536.Update3/Updates')) + expect(chef_run).to create_remote_file(downloaded_file_name) + expect(chef_run).to unzip_windows_zipfile(installer_path) + expect(chef_run).to delete_file(downloaded_file_name) + end + it 'raises an error about .NET Framework' do + allow_any_instance_of(Chef::DSL::RegistryHelper) + .to receive(:registry_get_values) + .and_return(nil) + expect { chef_run }.to raise_error(RuntimeError, /Microsoft .NET Framework 4.5.2 or higher be installed/) + end + it 'returns an Argument error when invalid Veeam version supplied' do + node.normal['veeam']['build'] = '1.0' + expect { chef_run }.to raise_error(ArgumentError, /You must provide a package URL or choose a valid build/) + end + end + end + end + end + context 'Does not install' do + platforms = { + 'windows' => { + 'versions' => %w(2008R2) # Unable to test plain Win2008 since Fauxhai doesn't have a template for 2008 + }, + 'ubuntu' => { + 'versions' => %w(16.04) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + before do + Fauxhai.mock(platform: platform, version: version) + end + + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: platform, version: version).converge(described_recipe) + end + it 'raises an exception' do + expect { chef_run }.to raise_error(ArgumentError, 'This recipe requires a Windows 2012 or higher host!') + end + end + end + end + end +end diff --git a/spec/unit/recipes/console_spec.rb b/spec/unit/recipes/console_spec.rb index 3c12fc2..380af3c 100644 --- a/spec/unit/recipes/console_spec.rb +++ b/spec/unit/recipes/console_spec.rb @@ -1,12 +1,12 @@ # # Cookbook Name:: veeam -# Spec:: catalog +# Spec:: console # # Copyright (c) 2016 Exosphere Data LLC, All Rights Reserved. require 'spec_helper' -describe 'veeam::catalog' do +describe 'veeam::console' do before do mock_windows_system_framework # Windows Framework Helper from 'spec/windows_helper.rb' stub_command('sc.exe query W3SVC').and_return 1 @@ -32,7 +32,7 @@ it 'converges successfully' do expect { chef_run }.not_to raise_error expect(chef_run).to install_veeam_prerequisites('Install Veeam Prerequisites') - expect(chef_run).to install_veeam_catalog('Install Veeam Backup Catalog') + expect(chef_run).to install_veeam_console('Install Veeam Backup console') end end end diff --git a/spec/unit/recipes/proxy_remove_spec.rb b/spec/unit/recipes/proxy_remove_spec.rb new file mode 100644 index 0000000..7ed0d47 --- /dev/null +++ b/spec/unit/recipes/proxy_remove_spec.rb @@ -0,0 +1,76 @@ +# +# Cookbook:: veeam +# Spec:: proxy_remove_spec +# +# maintainer:: Exosphere Data, LLC +# maintainer_email:: chef@exospheredata.com +# +# Copyright:: 2018, Exosphere Data, LLC, All Rights Reserved. + +require 'spec_helper' + +describe 'veeam::proxy_remove' do + before do + mock_windows_system_framework # Windows Framework Helper from 'spec/windows_helper.rb' + stub_command('sc.exe query W3SVC').and_return 1 + end + context 'Run recipe' do + platforms = { + 'windows' => { + 'versions' => %w(2012 2012R2 2016) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + before do + Fauxhai.mock(platform: platform, version: version) + node.normal['veeam']['proxy']['vbr_server'] = 'veeam' + node.normal['veeam']['proxy']['vbr_username'] = 'admin' + node.normal['veeam']['proxy']['vbr_password'] = 'password' + end + let(:runner) do + ChefSpec::SoloRunner.new(platform: platform, version: version, file_cache_path: '/tmp/cache') + end + let(:node) { runner.node } + let(:chef_run) { runner.converge(described_recipe) } + + it 'converges successfully' do + expect(chef_run).to remove_veeam_proxy(node['hostname']) + end + + it 'register using IP Address' do + node.normal['veeam']['proxy']['use_ip_address'] = true + expect(chef_run).to remove_veeam_proxy(node['ipaddress']) + end + end + end + end + end + context 'Does not install' do + platforms = { + 'windows' => { + 'versions' => %w(2008R2) # Unable to test plain Win2008 since Fauxhai doesn't have a template for 2008 + }, + 'ubuntu' => { + 'versions' => %w(16.04) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + before do + Fauxhai.mock(platform: platform, version: version) + end + + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: platform, version: version).converge(described_recipe) + end + it 'raises an exception' do + expect { chef_run }.to raise_error(ArgumentError, 'This recipe requires a Windows 2012 or higher host!') + end + end + end + end + end +end diff --git a/spec/unit/recipes/proxy_server_spec.rb b/spec/unit/recipes/proxy_server_spec.rb new file mode 100644 index 0000000..4162d89 --- /dev/null +++ b/spec/unit/recipes/proxy_server_spec.rb @@ -0,0 +1,90 @@ +# +# Cookbook:: veeam +# Spec:: proxy_server_spec +# +# maintainer:: Exosphere Data, LLC +# maintainer_email:: chef@exospheredata.com +# +# Copyright:: 2018, Exosphere Data, LLC, All Rights Reserved. + +require 'spec_helper' + +describe 'veeam::proxy_server' do + before do + mock_windows_system_framework # Windows Framework Helper from 'spec/windows_helper.rb' + stub_command('sc.exe query W3SVC').and_return 1 + end + context 'Run recipe' do + platforms = { + 'windows' => { + 'versions' => %w(2012 2012R2 2016) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + before do + Fauxhai.mock(platform: platform, version: version) + node.normal['veeam']['proxy']['vbr_server'] = 'veeam' + node.normal['veeam']['proxy']['vbr_username'] = 'admin' + node.normal['veeam']['proxy']['vbr_password'] = 'password' + node.normal['veeam']['proxy']['proxy_username'] = 'admin' + node.normal['veeam']['proxy']['proxy_password'] = 'password' + node.normal['veeam']['build'] = '9.5.0.1536' + end + let(:runner) do + ChefSpec::SoloRunner.new(platform: platform, version: version, file_cache_path: '/tmp/cache') + end + let(:node) { runner.node } + let(:chef_run) { runner.converge(described_recipe) } + + it 'converges successfully' do + expect { chef_run }.not_to raise_error + expect(chef_run).to install_windows_feature('FS-FileServer') + expect(chef_run).to install_windows_feature('Print-Server') + expect(chef_run).to install_veeam_prerequisites('Install Veeam Prerequisites') + expect(chef_run).to install_veeam_console('Install Veeam Backup console') + expect(chef_run).to install_veeam_upgrade('9.5.0.1536').with(package_url: node['veeam']['installer']['update_url']) + expect(chef_run).to add_veeam_proxy(node['hostname']) + end + + it 'register using IP Address' do + node.normal['veeam']['proxy']['use_ip_address'] = true + expect(chef_run).to add_veeam_proxy(node['ipaddress']) + end + + it 'does not register if attribute configured to false' do + node.normal['veeam']['proxy']['register'] = false + expect(chef_run).to_not add_veeam_proxy(node['hostname']) + end + end + end + end + end + context 'Does not install' do + platforms = { + 'windows' => { + 'versions' => %w(2008R2) # Unable to test plain Win2008 since Fauxhai doesn't have a template for 2008 + }, + 'ubuntu' => { + 'versions' => %w(16.04) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + before do + Fauxhai.mock(platform: platform, version: version) + end + + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: platform, version: version).converge(described_recipe) + end + it 'raises an exception' do + expect { chef_run }.to raise_error(ArgumentError, 'This recipe requires a Windows 2012 or higher host!') + end + end + end + end + end +end diff --git a/spec/unit/recipes/standalone_complete_spec.rb b/spec/unit/recipes/standalone_complete_spec.rb index 4d5253a..76c5c33 100644 --- a/spec/unit/recipes/standalone_complete_spec.rb +++ b/spec/unit/recipes/standalone_complete_spec.rb @@ -23,6 +23,7 @@ context "On #{platform} #{version}" do before do Fauxhai.mock(platform: platform, version: version) + node.normal['veeam']['build'] = '9.5.0.1536' end let(:runner) do ChefSpec::SoloRunner.new(platform: platform, version: version, file_cache_path: '/tmp/cache') @@ -37,6 +38,7 @@ expect(chef_run).to install_veeam_console('Install Veeam Backup Console') expect(chef_run).to install_veeam_server('Install Veeam Backup Server') expect(chef_run).to install_veeam_explorer('Install Veeam Backup Explorers') + expect(chef_run).to install_veeam_upgrade('9.5.0.1536').with(package_url: node['veeam']['installer']['update_url']) end end end diff --git a/spec/unit/recipes/upgrade_spec.rb b/spec/unit/recipes/upgrade_spec.rb new file mode 100644 index 0000000..b8bdb09 --- /dev/null +++ b/spec/unit/recipes/upgrade_spec.rb @@ -0,0 +1,70 @@ +# +# Cookbook:: veeam +# Spec:: upgrade_spec +# +# maintainer:: Exosphere Data, LLC +# maintainer_email:: chef@exospheredata.com +# +# Copyright:: 2018, Exosphere Data, LLC, All Rights Reserved. + +require 'spec_helper' + +describe 'veeam::upgrade' do + before do + mock_windows_system_framework # Windows Framework Helper from 'spec/windows_helper.rb' + stub_command('sc.exe query W3SVC').and_return 1 + end + context 'Run recipe' do + platforms = { + 'windows' => { + 'versions' => %w(2012 2012R2 2016) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + before do + Fauxhai.mock(platform: platform, version: version) + node.normal['veeam']['build'] = '9.5.0.1536' + end + let(:runner) do + ChefSpec::SoloRunner.new(platform: platform, version: version, file_cache_path: '/tmp/cache') + end + let(:node) { runner.node } + let(:chef_run) { runner.converge(described_recipe) } + + it 'converges successfully' do + expect { chef_run }.not_to raise_error + expect(chef_run).to install_veeam_upgrade('9.5.0.1536').with(package_url: node['veeam']['installer']['update_url']) + end + end + end + end + end + context 'Does not install' do + platforms = { + 'windows' => { + 'versions' => %w(2008R2) # Unable to test plain Win2008 since Fauxhai doesn't have a template for 2008 + }, + 'ubuntu' => { + 'versions' => %w(16.04) + } + } + platforms.each do |platform, components| + components['versions'].each do |version| + context "On #{platform} #{version}" do + before do + Fauxhai.mock(platform: platform, version: version) + end + + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: platform, version: version).converge(described_recipe) + end + it 'raises an exception' do + expect { chef_run }.to raise_error(ArgumentError, 'This recipe requires a Windows 2012 or higher host!') + end + end + end + end + end +end diff --git a/templates/sql_server/sql_build_script.ps1.erb b/templates/sql_server/sql_build_script.ps1.erb new file mode 100644 index 0000000..d2fc482 --- /dev/null +++ b/templates/sql_server/sql_build_script.ps1.erb @@ -0,0 +1,34 @@ +# Install SQL Server + +<%= @sql_build_command %> | Out-File <%= @outputFilePath %> + +$results = @{} +$results.Add('output','Successfully installed SQL Server') +$results.Add('error',$null) + +$error_list = Select-String -Path <%= @outputFilePath %> -Pattern Error +if ($error_list) { + $results.output = "Failure to install SQL Server" + $StdErr = $(Get-Content -Path <%= @outputFilePath %> ) + $line_no = 0 # Sets the line counter to the first element of the array + $start_line = 0 # Sets the default line as the first in the element + $stop_line = $StdErr.length # If something crazy happens, we can fall back to return the remainder of the file. + + # The output comes back as an array and we need to extract only the valid error message. I expect + # that there will only be one error to correct as the fatal so since I haven't been able to reproduce + # multiple crashes, this will only report the first error. + foreach ($line in $StdErr){ + if($line -match "The following error occurred:"){ + $start_line = $line_no + } + if($line -match "Result error code:"){ + $stop_line = $line_no + } + $line_no++ + } + $results.error = $($StdErr[$start_line..$stop_line]) -Join("`r`n") +} + +Remove-Item <%= @outputFilePath %> -Confirm:$False -ErrorAction SilentlyContinue | Out-Null + +Set-Content -Path <%= @outputFilePath %> -Value ($results | ConvertTo-Json)