Skip to content

Commit

Permalink
Merge pull request #1 from chocolatey/testing-actual
Browse files Browse the repository at this point in the history
Initial Playbooks, Roles, Etc
  • Loading branch information
steviecoaster authored Nov 17, 2023
2 parents 18c806a + 6c93641 commit 5c48718
Show file tree
Hide file tree
Showing 37 changed files with 4,687 additions and 1 deletion.
15 changes: 15 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "Ansible",
"dockerFile": "dockerfile",
// "mounts": [
// "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
// ],
"forwardPorts": [],
"customizations": {
"vscode": {
"extensions": [
"redhat.ansible"
]
}
}
}
62 changes: 62 additions & 0 deletions .devcontainer/dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#-------------------------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#-------------------------------------------------------------------------------------------------------------
FROM ubuntu:latest
# This Dockerfile adds a non-root user with sudo access. Use the "remoteUser"
# property in devcontainer.json to use it. On Linux, the container user's GID/UIDs
# will be updated to match your local UID/GID (when using the dockerFile property).
# See https://aka.ms/vscode-remote/containers/non-root-user for details.
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID

# To ensure filesystem encoding is proper, add these.
# See: https://github.com/pypa/pip/issues/10219#issuecomment-887337037
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8

# To make it easier for build and release pipelines to run apt-get,
# configure apt to not require confirmation (assume the -y argument by default)
ENV DEBIAN_FRONTEND noninteractive
RUN echo "APT::Get::Assume-Yes \"true\";" > /etc/apt/apt.conf.d/90assumeyes

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
apt-utils \
software-properties-common \
build-essential \
jq \
git \
python3 \
python3-dev \
python3-pip && \
rm -rf /var/lib/apt/lists/*

RUN add-apt-repository universe \
&& add-apt-repository multiverse \
&& apt update

RUN pip3 install --upgrade \
setuptools \
pip

ADD requirements.txt /requirements.txt

RUN pip3 install --upgrade -r /requirements.txt

RUN ln -s /usr/bin/python3 /usr/bin/python \
# Create a non-root user to use if preferred - see https://aka.ms/vscode-remote/containers/non-root-user.
&& groupadd --gid $USER_GID $USERNAME \
&& useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \
# [Optional] Add sudo support for the non-root user
&& apt-get install sudo \
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\
&& chmod 0440 /etc/sudoers.d/$USERNAME \
#
# Clean up
&& apt-get autoremove \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

RUN ansible-galaxy collection install chocolatey.chocolatey --upgrade
11 changes: 11 additions & 0 deletions .devcontainer/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ansible>=2.8.0
pywinrm>=0.2.2
pip>=19.1
psutil
netaddr
ansible-lint
pylint
jsonschema
molecule
jmespath
dnspython
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/credentials
/files/download/
/files/*.nupkg
/files/*.zip
__pycache__
chocolatey.license.xml
*.pfx
*.nupkg
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"ansible.python.interpreterPath": "/bin/python3"
}
141 changes: 141 additions & 0 deletions OfflineInstallPreparation.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<#
.Synopsis
Prepares the repository for an offline deployment.
.Description
These playbooks can be run from a network without access to the internet,
but it needs to prepare packages to be run offline.
This script downloads and internalizes packages for such usage.
.Notes
This must be run on a Windows system with access to the internet because
it uses Chocolatey for Business' Package Internalizer.
.Notes
Instead of using this script, you can internalize all required packages manually,
zip them, and drop them in the files directory as shown below.
.Example
.\OfflineInstallPreparation.ps1 -LicensePath C:\ProgramData\chocolatey\license\chocolatey.license.xml
#>
[CmdletBinding()]
param(
[ValidateScript({
if (-not (Test-Path (Convert-Path $_))) {
throw "License file does not exist at '$($_)'. Please provide a valid -LicensePath"
}
$true
})]
[string]$LicensePath = "C:\ProgramData\chocolatey\license\chocolatey.license.xml",

[ValidateScript({
if (-not (Test-Path (Convert-Path $_))) {
throw "Certificate file does not exist at '$($_)'. Please provide a valid -CertificatePath"
}
$true
})]
[Parameter(Mandatory)]
[string]$CertificatePath,

[Parameter(Mandatory)]
[securestring]$CertificatePassword,

[string]$WorkingDirectory = $(Join-Path $env:Temp "choco-offline")
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$LicensePath = Convert-Path $LicensePath
$CertificatePath = Convert-Path $CertificatePath

# Validate License
try {
[xml]$License = Get-Content $LicensePath
$Expiry = Get-Date $License.license.expiration
if (-not $Expiry -or $Expiry -lt (Get-Date)) {throw}
} catch {
throw "License '$($LicensePath)' is not valid.$(if ($Expiry) {" It expired at '$($Expiry)'."})"
}

# Validate Certificate and Password
try {
$null = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new(
$CertificatePath,
$CertificatePassword,
"EphemeralKeySet"
)
} catch {
throw "Certificate '$($CertificatePath)' failed to import with the provided CertificatePassword. Please ensure the Certificate Path and Password are correct."
}

if (-not (Get-Command choco.exe)) {
Get-Content $PSScriptRoot\templates\ChocolateyInstall.ps1.j2 | Invoke-Expression
}

# Initialize environment, ensure Chocolatey For Business, etc.
$Licensed = ($($(choco)[0] -match "^Chocolatey (?<Version>\S+)\s*(?<LicenseType>Business)?$") -and $Matches.LicenseType)
$InstalledLicensePath = "$env:ChocolateyInstall\license\chocolatey.license.xml"
if (-not $Licensed) {
if (-not (Test-Path $InstalledLicensePath)) {
if (-not (Test-Path $env:ChocolateyInstall\license)) {
$null = New-Item $env:ChocolateyInstall\license -ItemType Directory
}
Copy-Item $LicensePath $InstalledLicensePath -Force
}
choco install chocolatey.extension --source https://licensedpackages.chocolatey.org/api/v2/ --confirm
}

# Download each set of packages to the output directories
$PackageWorkingDirectory = Join-Path $WorkingDirectory "Packages"
if (-not (Test-Path $PackageWorkingDirectory)) {
$null = New-Item -Path $PackageWorkingDirectory -ItemType Directory -Force
}
foreach ($Package in (Get-Content $PSScriptRoot\files\chocolatey.json | ConvertFrom-Json).packages) {
$ChocoArgs = @(
"download", "$($Package.Name)"
"--output-directory", $PackageWorkingDirectory
)
$ChocoArgs += switch ($Package.Keys) {
"Version" { "--version", $Package.Version }
"Args" { $Package.Args }
}
if ($Package.Internalize -or $Package.PSObject.Properties.Name -notcontains "Internalize") {
$ChocoArgs += "--internalize" # Default to internalizing
}

try {
if (-not (Test-Path "$($PackageWorkingDirectory)\$($Package.Name)*.nupkg") -and -not (Test-Path "$PSScriptRoot\files\$($Package.Name)*.nupkg")) {
Write-Verbose "Downloading '$($Package.Name)'"
$Output = choco @ChocoArgs
if ($LASTEXITCODE -ne 0) {
$Output
}
}
} catch {
throw $_
}
}
Move-Item -Path $PackageWorkingDirectory\*.nupkg -Destination $PSScriptRoot\files\

# Jenkins Plugins
$PluginsWorkingDirectory = Join-Path $WorkingDirectory "JenkinsPlugins"
if (-not (Test-Path $PluginsWorkingDirectory)) {
$null = New-Item -Path $PluginsWorkingDirectory -ItemType Directory -Force
}
$ProgressPreference = "Ignore"
foreach ($Plugin in (Get-Content $PSScriptRoot\files\jenkins.json | ConvertFrom-Json).plugins) {
$RestArgs = @{
Uri = "https://updates.jenkins-ci.org/latest/$($Plugin.Name).hpi"
OutFile = Join-Path $PluginsWorkingDirectory "$($Plugin.Name).hpi"
}
if ($Plugin.Version -and $Plugin.Version -ne 'latest') {
$RestArgs.Uri = "https://updates.jenkins-ci.org/download/plugins/$($Plugin.Name)/$($Plugin.Version)/$($Plugin.Name).hpi"
}
if (-not (Test-Path $RestArgs.OutFile)) {
Invoke-WebRequest @RestArgs -UseBasicParsing
}
}
Compress-Archive -Path $PluginsWorkingDirectory\* -Destination $PSScriptRoot\files\JenkinsPlugins.zip -Force

# License and Certificate
Copy-Item -Path (Convert-Path $LicensePath) -Destination $PSScriptRoot\files\chocolatey.license.xml
Copy-Item -Path (Convert-Path $CertificatePath) -Destination $PSScriptRoot\files\certificate.pfx
76 changes: 75 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,75 @@
# repository-template
# Chocolatey for Business Ansible Environment

## Deployment

To deploy the Chocolatey for Business Ansible Environment, first, clone the repository to your chosen Ansible environment, make the following modifications, and deploy the `c4b-environment.yml` playbook.

Create hosts. Depending on your chosen configuration, you may not need to create all of them:

| Group | Purpose |
| --------------- | ---------------------------------------------------------------------------- |
| ccm_server | Runs the Chocolatey Central Management service and administration interface. |
| nexus_server | Runs Sonatype Nexus Repository, to store and distribute packages. |
| jenkins_server | Runs Jenkins, to run jobs to updating packages in the Nexus repository. |
| database_server | Runs SQL Server Express, to store information from the CCM service. |

By default, any non-specified service will be installed on the ccm_server host.

You should provide the following arguments:

| Argument | Purpose |
| -------------------------- | -------------------------------------------------- |
| license_path | Your Chocolatey for Business license file. |
| certificate_path | The PFX certificate to use for all HTTPS services. |
| certificate_password | The password for the PFX certificate. |

Finally, you can deploy the playbook as follows (using the example hosts file):

`ansible-playbook ./c4b-environment.yml -i ./hosts.yml`

You will be prompted for any values you have not provided in `--extra-vars` or another fashion. An example of passing a variable on the command-line is as follows:

`ansible-playbook ./c4b-environment --extra-vars "license_path=/path/to/chocolatey.license.xml certificate_path=/path/to/certificate.pfx"`

You can also define variables in AWX, or within a file. For further details, see [Defining variables at runtime](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#passing-variables-on-the-command-line).

For further information on deploying this environment, see [the docs page](https://docs.chocolatey.org/en-us/c4b-environments/ansible/).

## Hardware Recommendations

We recommend the following configuration if deploying to a single Ansible host:

- Windows Server 2019+
- 4+ CPU cores
- 16 GB+ RAM (8GB as a bare minimum; 4GB of RAM is reserved specifically for Nexus)
- 500 GB+ of free space for local NuGet package artifact storage

If deploying to multiple hosts, please refer to the recommended specifications for:

- [Chocolatey Central Management](https://docs.chocolatey.org/en-us/central-management/setup/#high-level-requirements)
- [Sonatype Nexus Repository](https://help.sonatype.com/repomanager3/product-information/sonatype-nexus-repository-system-requirements)
- [Jenkins](https://www.jenkins.io/doc/book/installing/windows/#prerequisites)
- [SQL Server Express](https://www.microsoft.com/en-us/download/details.aspx?id=104781)

## Offline Installation

To install in an air-gapped environment, you can download this repository to a local machine and run the `OfflineInstallPreparation.ps1` script.

This script downloads all the required files and packages to ensure a successful installation. Please note that you will require a Windows machine with a licensed copy of Chocolatey, as it utilises the Package Internalizer feature.

After the script has run, copy the directory to your Ansible environment and deploy it.

## Storing Secrets

After the playbook has run, various secrets will have been created and stored in the `/credentials` directory. To keep these secure, you should use [Ansible Vault](https://docs.ansible.com/ansible/latest/vault_guide/index.html) or something similar to store and inject them instead of the password lookup files, as `lookup('ansible.builtin.password'` does not support encryption or Ansible Vault. To do so, follow these steps for each secret (using `ccm_client_salt` as the example):

- In a terminal on your Ansible machine, run `ansible-vault encrypt /path/to/repository/credentials/ccm_client_salt`.
- Open the `/path/to/repository/credentials/ccm_client_salt` file and copy the new contents of the file.
- Open the `./group_vars/all.yml` file and overwrite the value of `ccm_client_salt` with the vaulted value.

This will result in re-deployment of the environment using this secret, going forward.

If you want to re-deploy the environment having changed your passwords, or initially deploy it using your own generated values, you can use `ansible-vault` and the `all.yml` file to deploy using those values.

- In a terminal on your Ansible machine, run `ansible-vault encrypt_string 'some-secure-password-here' --name 'ccm_client_salt'`.
- Open the `./group_vars/all.yml` file and overwrite the line beginning `ccm_client_salt:` with the output of the `ansible-vault` command.
Loading

0 comments on commit 5c48718

Please sign in to comment.