New Upstream Snapshot - puppet-module-puppetlabs-apt

Ready changes

Summary

Merged new upstream version: 9.0.1+git20230201.1.790df53 (was: 9.0.1).

Resulting package

Built on 2023-02-09T16:47 (took 15m18s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-snapshots puppet-module-puppetlabs-apt

Lintian Result

Diff

diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 0000000..12ed4ff
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,6 @@
+FROM puppet/pdk:latest
+
+# [Optional] Uncomment this section to install additional packages.
+# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
+#     && apt-get -y install --no-install-recommends <your-package-list-here>
+
diff --git a/.devcontainer/README.md b/.devcontainer/README.md
new file mode 100644
index 0000000..a719361
--- /dev/null
+++ b/.devcontainer/README.md
@@ -0,0 +1,38 @@
+# devcontainer
+
+
+For format details, see https://aka.ms/devcontainer.json. 
+
+For config options, see the README at:
+https://github.com/microsoft/vscode-dev-containers/tree/v0.140.1/containers/puppet
+ 
+``` json
+{
+	"name": "Puppet Development Kit (Community)",
+	"dockerFile": "Dockerfile",
+
+	// Set *default* container specific settings.json values on container create.
+	"settings": {
+		"terminal.integrated.profiles.linux": {
+			"bash": {
+				"path": "bash",
+			}
+		}
+	},
+
+	// Add the IDs of extensions you want installed when the container is created.
+	"extensions": [
+		"puppet.puppet-vscode",
+		"rebornix.Ruby"
+	],
+
+	// Use 'forwardPorts' to make a list of ports inside the container available locally.
+	"forwardPorts": [],
+
+	// Use 'postCreateCommand' to run commands after the container is created.
+	"postCreateCommand": "pdk --version",
+}
+```
+
+
+
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..fe7a8b1
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,17 @@
+{
+	"name": "Puppet Development Kit (Community)",
+	"dockerFile": "Dockerfile",
+
+	"settings": {
+		"terminal.integrated.profiles.linux": {
+			"bash": {
+				"path": "bash",
+			}
+		}
+	},
+
+	"extensions": [
+		"puppet.puppet-vscode",
+		"rebornix.Ruby"
+	]
+}
diff --git a/.fixtures.yml b/.fixtures.yml
new file mode 100644
index 0000000..0157d6a
--- /dev/null
+++ b/.fixtures.yml
@@ -0,0 +1,9 @@
+fixtures:
+  repositories:
+    "stdlib":
+      "repo": "https://github.com/puppetlabs/puppetlabs-stdlib.git"
+    facts: 'https://github.com/puppetlabs/puppetlabs-facts.git'
+    puppet_agent: 'https://github.com/puppetlabs/puppetlabs-puppet_agent.git'
+    provision: 'https://github.com/puppetlabs/provision.git'
+  symlinks:
+    "apt": "#{source_dir}"
diff --git a/.github/workflows/auto_release.yml b/.github/workflows/auto_release.yml
deleted file mode 100644
index f4aed44..0000000
--- a/.github/workflows/auto_release.yml
+++ /dev/null
@@ -1,90 +0,0 @@
-name: "Auto release"
-
-on:
-  workflow_dispatch:
-
-env:
-  HONEYCOMB_WRITEKEY: 7f3c63a70eecc61d635917de46bea4e6 
-  HONEYCOMB_DATASET: litmus tests
-  CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-jobs:
-  auto_release:
-    name: "Automatic release prep"
-    runs-on: ubuntu-20.04
-
-    steps:
-    
-    - name: "Honeycomb: Start recording"
-      uses: puppetlabs/kvrhdn-gha-buildevents@pdk-templates-v1
-      with:
-        apikey: ${{ env.HONEYCOMB_WRITEKEY }}
-        dataset: ${{ env.HONEYCOMB_DATASET }}
-        job-status: ${{ job.status }}
-
-    - name: "Honeycomb: start first step"
-      run: |
-        echo STEP_ID="auto-release" >> $GITHUB_ENV
-        echo STEP_START=$(date +%s) >> $GITHUB_ENV
-    - name: "Checkout Source"
-      if: ${{ github.repository_owner == 'puppetlabs' }}
-      uses: actions/checkout@v2
-      with:
-        fetch-depth: 0
-        persist-credentials: false
-
-    - name: "PDK Release prep"
-      uses: docker://puppet/iac_release:ci
-      with:
-        args: 'release prep --force'
-      env:
-        CHANGELOG_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-    - name: "Get Version"
-      if: ${{ github.repository_owner == 'puppetlabs' }}
-      id: gv
-      run: |
-        echo "::set-output name=ver::$(jq --raw-output .version metadata.json)"
-
-    - name: "Check if a release is necessary"
-      if: ${{ github.repository_owner == 'puppetlabs' }}
-      id: check
-      run: |
-        git diff --quiet CHANGELOG.md && echo "::set-output name=release::false" || echo "::set-output name=release::true"
-
-    - name: "Commit changes"
-      if: ${{ github.repository_owner == 'puppetlabs' && steps.check.outputs.release == 'true' }}
-      run: |
-        git config --local user.email "${{ github.repository_owner }}@users.noreply.github.com"
-        git config --local user.name "GitHub Action"
-        git add .
-        git commit -m "Release prep v${{ steps.gv.outputs.ver }}"
-
-    - name: Create Pull Request
-      id: cpr
-      uses: puppetlabs/peter-evans-create-pull-request@v3
-      if: ${{ github.repository_owner == 'puppetlabs' && steps.check.outputs.release == 'true' }}
-      with:
-        token: ${{ secrets.GITHUB_TOKEN }}
-        commit-message: "Release prep v${{ steps.gv.outputs.ver }}"
-        branch: "release-prep"
-        delete-branch: true
-        title: "Release prep v${{ steps.gv.outputs.ver }}"
-        body: |
-          Automated release-prep through [pdk-templates](https://github.com/puppetlabs/pdk-templates/blob/main/moduleroot/.github/workflows/auto_release.yml.erb) from commit ${{ github.sha }}. 
-          Please verify before merging:
-          - [ ] last [nightly](https://github.com/${{ github.repository }}/actions/workflows/nightly.yml) run is green
-          - [ ] [Changelog](https://github.com/${{ github.repository }}/blob/release-prep/CHANGELOG.md) is readable and has no unlabeled pull requests
-          - [ ] Ensure the [changelog](https://github.com/${{ github.repository }}/blob/release-prep/CHANGELOG.md) version and [metadata](https://github.com/${{ github.repository }}/blob/release-prep/metadata.json) version match
-        labels: "maintenance"
-
-    - name: PR outputs
-      if: ${{ github.repository_owner == 'puppetlabs' && steps.check.outputs.release == 'true' }}
-      run: |
-        echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
-        echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
- 
-    - name: "Honeycomb: Record finish step"
-      if: ${{ always() }}
-      run: |
-        buildevents step $TRACE_ID $STEP_ID $STEP_START 'Finished auto release workflow'
diff --git a/.github/workflows/labeller.yml b/.github/workflows/labeller.yml
deleted file mode 100644
index 5434d3f..0000000
--- a/.github/workflows/labeller.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: community-labeller
-
-on:
-  issues:
-    types:
-      - opened
-  pull_request_target:
-    types:
-      - opened
-
-jobs:
-  label:
-    runs-on: ubuntu-latest
-    steps:
-
-      - uses: puppetlabs/community-labeller@v0
-        name: Label issues or pull requests
-        with:
-          label_name: community
-          label_color: '5319e7'
-          org_membership: puppetlabs
-          token: ${{ secrets.IAC_COMMUNITY_LABELER }}
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
deleted file mode 100644
index 42816e7..0000000
--- a/.github/workflows/nightly.yml
+++ /dev/null
@@ -1,204 +0,0 @@
-name: "nightly"
-
-on:
-  schedule:
-    - cron: '0 0 * * *'
-
-
-env:
-  HONEYCOMB_WRITEKEY: 7f3c63a70eecc61d635917de46bea4e6
-  HONEYCOMB_DATASET: litmus tests
-
-jobs:
-  setup_matrix:
-    if: ${{ github.repository_owner == 'puppetlabs' }}
-    name: "Setup Test Matrix"
-    runs-on: ubuntu-20.04
-    outputs:
-      matrix: ${{ steps.get-matrix.outputs.matrix }}
-
-    steps:
-     
-    - name: "Honeycomb: Start recording"
-      uses: puppetlabs/kvrhdn-gha-buildevents@pdk-templates-v1
-      with:
-        apikey: ${{ env.HONEYCOMB_WRITEKEY }}
-        dataset: ${{ env.HONEYCOMB_DATASET }}
-        job-status: ${{ job.status }}
-
-    - name: "Honeycomb: Start first step"
-      run: |
-        echo STEP_ID=setup-environment >> $GITHUB_ENV
-        echo STEP_START=$(date +%s) >> $GITHUB_ENV
-    - name: Checkout Source
-      uses: actions/checkout@v2
-      if: ${{ github.repository_owner == 'puppetlabs' }}
-
-    - name: Activate Ruby 2.7
-      uses: ruby/setup-ruby@v1
-      if: ${{ github.repository_owner == 'puppetlabs' }}
-      with:
-        ruby-version: "2.7"
-        bundler-cache: true
-
-    - name: Print bundle environment
-      if: ${{ github.repository_owner == 'puppetlabs' }}
-      run: |
-        echo ::group::bundler environment
-        buildevents cmd $TRACE_ID $STEP_ID 'bundle env' -- bundle env
-        echo ::endgroup::
-   
-    - name: "Honeycomb: Record Setup Environment time"
-      if: ${{ github.repository_owner == 'puppetlabs' }}
-      run: |
-        buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Environment'
-        echo STEP_ID=Setup-Acceptance-Test-Matrix >> $GITHUB_ENV
-        echo STEP_START=$(date +%s) >> $GITHUB_ENV
-    - name: Setup Acceptance Test Matrix
-      id: get-matrix
-      if: ${{ github.repository_owner == 'puppetlabs' }}
-      run: |
-        if [ '${{ github.repository_owner }}' == 'puppetlabs' ]; then
-          buildevents cmd $TRACE_ID $STEP_ID matrix_from_metadata -- bundle exec matrix_from_metadata_v2 
-        else
-          echo  "::set-output name=matrix::{}"
-        fi
-  
-    - name: "Honeycomb: Record Setup Test Matrix time"
-      if: ${{ always() }}
-      run: |
-        buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Test Matrix'
-  Acceptance:
-    name: "${{matrix.platforms.label}}, ${{matrix.collection}}"
-    needs:
-      - setup_matrix
-
-    runs-on: ubuntu-20.04
-    strategy:
-      fail-fast: false
-      matrix: ${{fromJson(needs.setup_matrix.outputs.matrix)}}
-
-    env:
-      BUILDEVENT_FILE: '../buildevents.txt'
-
-    steps:
-    - run: |
-        echo 'platform=${{ matrix.platforms.image }}' >> $BUILDEVENT_FILE
-        echo 'collection=${{ matrix.collection }}' >> $BUILDEVENT_FILE
-        echo 'label=${{ matrix.platforms.label }}' >> $BUILDEVENT_FILE
-
-
-    - name: "Honeycomb: Start recording"
-      uses: puppetlabs/kvrhdn-gha-buildevents@pdk-templates-v1
-      with:
-        apikey: ${{ env.HONEYCOMB_WRITEKEY }}
-        dataset: ${{ env.HONEYCOMB_DATASET }}
-        job-status: ${{ job.status }}
-        matrix-key: ${{ matrix.platforms.label }}-${{ matrix.collection }}
-
-    - name: "Honeycomb: start first step"
-      run: |
-        echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-1 >> $GITHUB_ENV
-        echo STEP_START=$(date +%s) >> $GITHUB_ENV
-
-    - name: Checkout Source
-      uses: actions/checkout@v2
-
-    - name: Activate Ruby 2.7
-      uses: ruby/setup-ruby@v1
-      with:
-        ruby-version: "2.7"
-        bundler-cache: true
-
-    - name: Print bundle environment
-      run: |
-        echo ::group::bundler environment
-        buildevents cmd $TRACE_ID $STEP_ID 'bundle env' -- bundle env
-        echo ::endgroup::
-
-    - name: "Honeycomb: Record Setup Environment time"
-      if: ${{ always() }}
-      run: |
-        buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Environment'
-        echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-2 >> $GITHUB_ENV
-        echo STEP_START=$(date +%s) >> $GITHUB_ENV
-
-    - name: Provision test environment
-      run: |
-        buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:provision ${{ matrix.platforms.image }}' -- bundle exec rake 'litmus:provision[${{matrix.platforms.provider}},${{ matrix.platforms.image }}]'
-        echo ::group::=== REQUEST ===
-        cat request.json || true
-        echo
-        echo ::endgroup::
-        echo ::group::=== INVENTORY ===
-        if [ -f 'spec/fixtures/litmus_inventory.yaml' ];
-        then
-          FILE='spec/fixtures/litmus_inventory.yaml'
-        elif [ -f 'inventory.yaml' ];
-        then
-          FILE='inventory.yaml'
-        fi
-        sed -e 's/password: .*/password: "[redacted]"/' < $FILE || true
-        echo ::endgroup::
-
-    - name: Install agent
-      run: |
-        buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:install_agent ${{ matrix.collection }}' -- bundle exec rake 'litmus:install_agent[${{ matrix.collection }}]'
-
-    - name: Install module
-      run: |
-        buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:install_module' -- bundle exec rake 'litmus:install_module'
-
-    - name: "Honeycomb: Record deployment times"
-      if: ${{ always() }}
-      run: |
-        echo ::group::honeycomb step
-        buildevents step $TRACE_ID $STEP_ID $STEP_START 'Deploy test system'
-        echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-3 >> $GITHUB_ENV
-        echo STEP_START=$(date +%s) >> $GITHUB_ENV
-        echo ::endgroup::
-
-    - name: Run acceptance tests
-      run: |
-        buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:acceptance:parallel' -- bundle exec rake 'litmus:acceptance:parallel'
-
-    - name: "Honeycomb: Record acceptance testing times"
-      if: ${{ always() }}
-      run: |
-        buildevents step $TRACE_ID $STEP_ID $STEP_START 'Run acceptance tests'
-        echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-4 >> $GITHUB_ENV
-        echo STEP_START=$(date +%s) >> $GITHUB_ENV
-
-    - name: Remove test environment
-      if: ${{ always() }}
-      continue-on-error: true
-      run: |
-        if [[ -f inventory.yaml || -f spec/fixtures/litmus_inventory.yaml ]]; then
-          buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:tear_down' -- bundle exec rake 'litmus:tear_down'
-          echo ::group::=== REQUEST ===
-          cat request.json || true
-          echo
-          echo ::endgroup::
-        fi
-
-    - name: "Honeycomb: Record removal times"
-      if: ${{ always() }}
-      run: |
-        buildevents step $TRACE_ID $STEP_ID $STEP_START 'Remove test environment'
-
-  slack-workflow-status:
-    if: ${{ github.repository_owner == 'puppetlabs' }}
-    name: Post Workflow Status To Slack
-    needs:
-      - Acceptance
-    runs-on: ubuntu-20.04
-    steps:
-      - name: Slack Workflow Notification
-        uses: puppetlabs/Gamesight-slack-workflow-status@pdk-templates-v1
-        with:
-          # Required Input
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          slack_webhook_url: ${{ secrets.SLACK_WEBHOOK }}
-          # Optional Input
-          channel: '#team-cat-bots'
-          name: 'GABot'
diff --git a/.github/workflows/pr_test.yml b/.github/workflows/pr_test.yml
deleted file mode 100644
index fd310e6..0000000
--- a/.github/workflows/pr_test.yml
+++ /dev/null
@@ -1,185 +0,0 @@
-name: "PR Testing"
-
-on: [pull_request]
-
-
-env:
- 
-  HONEYCOMB_WRITEKEY: 7f3c63a70eecc61d635917de46bea4e6
-  HONEYCOMB_DATASET: litmus tests
-
-jobs:
-  setup_matrix:
-    name: "Setup Test Matrix"
-    runs-on: ubuntu-20.04
-    outputs:
-      matrix: ${{ steps.get-matrix.outputs.matrix }}
-
-    steps:
-    
-    - name: "Honeycomb: Start recording"
-      uses: puppetlabs/kvrhdn-gha-buildevents@pdk-templates-v1
-      with:
-        apikey: ${{ env.HONEYCOMB_WRITEKEY }}
-        dataset: ${{ env.HONEYCOMB_DATASET }}
-        job-status: ${{ job.status }}
-
-    - name: "Honeycomb: Start first step"
-      run: |
-        echo STEP_ID=setup-environment >> $GITHUB_ENV
-        echo STEP_START=$(date +%s) >> $GITHUB_ENV
-    - name: Checkout Source
-      uses: actions/checkout@v2
-      if: ${{ github.repository_owner == 'puppetlabs' }}
-
-    - name: Activate Ruby 2.7
-      uses: ruby/setup-ruby@v1
-      if: ${{ github.repository_owner == 'puppetlabs' }}
-      with:
-        ruby-version: "2.7"
-        bundler-cache: true
-
-    - name: Print bundle environment
-      if: ${{ github.repository_owner == 'puppetlabs' }}
-      run: |
-        echo ::group::bundler environment
-        buildevents cmd $TRACE_ID $STEP_ID 'bundle env' -- bundle env
-        echo ::endgroup::
-  
-    - name: "Honeycomb: Record Setup Environment time"
-      if: ${{ github.repository_owner == 'puppetlabs' }}
-      run: |
-        buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Environment'
-        echo STEP_ID=Setup-Acceptance-Test-Matrix >> $GITHUB_ENV
-        echo STEP_START=$(date +%s) >> $GITHUB_ENV
-    - name: Run validation steps
-      run: |
-        bundle exec rake validate
-      if: ${{ github.repository_owner == 'puppetlabs' }}
-
-    - name: Setup Acceptance Test Matrix
-      id: get-matrix
-      run: |
-        if [ '${{ github.repository_owner }}' == 'puppetlabs' ]; then
-          buildevents cmd $TRACE_ID $STEP_ID matrix_from_metadata -- bundle exec matrix_from_metadata_v2 
-        else
-          echo  "::set-output name=matrix::{}"
-        fi
- 
-    - name: "Honeycomb: Record Setup Test Matrix time"
-      if: ${{ always() }}
-      run: |
-        buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Test Matrix'
-  Acceptance:
-    name: "${{matrix.platforms.label}}, ${{matrix.collection}}"
-    needs:
-      - setup_matrix
-    if: ${{ needs.setup_matrix.outputs.matrix != '{}' }}
-
-    runs-on: ubuntu-20.04
-    strategy:
-      fail-fast: false
-      matrix: ${{fromJson(needs.setup_matrix.outputs.matrix)}}
-
-    env:
-      BUILDEVENT_FILE: '../buildevents.txt'
-
-    steps:
-    - run: |
-        echo 'platform=${{ matrix.platforms.image }}' >> $BUILDEVENT_FILE
-        echo 'collection=${{ matrix.collection }}' >> $BUILDEVENT_FILE
-        echo 'label=${{ matrix.platforms.label }}' >> $BUILDEVENT_FILE
- 
-    - name: "Honeycomb: Start recording"
-      uses: puppetlabs/kvrhdn-gha-buildevents@pdk-templates-v1
-      with:
-        apikey: ${{ env.HONEYCOMB_WRITEKEY }}
-        dataset: ${{ env.HONEYCOMB_DATASET }}
-        job-status: ${{ job.status }}
-        matrix-key: ${{ matrix.platforms.label }}-${{ matrix.collection }}
-
-    - name: "Honeycomb: start first step"
-      run: |
-        echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-1 >> $GITHUB_ENV
-        echo STEP_START=$(date +%s) >> $GITHUB_ENV
-    - name: Checkout Source
-      uses: actions/checkout@v2
-
-    - name: Activate Ruby 2.7
-      uses: ruby/setup-ruby@v1
-      with:
-        ruby-version: "2.7"
-        bundler-cache: true
-
-    - name: Print bundle environment
-      run: |
-        echo ::group::bundler environment
-        buildevents cmd $TRACE_ID $STEP_ID 'bundle env' -- bundle env
-        echo ::endgroup::
- 
-    - name: "Honeycomb: Record Setup Environment time"
-      if: ${{ always() }}
-      run: |
-        buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Environment'
-        echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-2 >> $GITHUB_ENV
-        echo STEP_START=$(date +%s) >> $GITHUB_ENV
-    - name: Provision test environment
-      run: |
-        buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:provision ${{ matrix.platforms.image }}' -- bundle exec rake 'litmus:provision[${{matrix.platforms.provider}},${{ matrix.platforms.image }}]'
-        echo ::group::=== REQUEST ===
-        cat request.json || true
-        echo
-        echo ::endgroup::
-        echo ::group::=== INVENTORY ===
-        if [ -f 'spec/fixtures/litmus_inventory.yaml' ];
-        then
-          FILE='spec/fixtures/litmus_inventory.yaml'
-        elif [ -f 'inventory.yaml' ];
-        then
-          FILE='inventory.yaml'
-        fi
-        sed -e 's/password: .*/password: "[redacted]"/' < $FILE || true
-        echo ::endgroup::
-
-    - name: Install agent
-      run: |
-        buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:install_agent ${{ matrix.collection }}' -- bundle exec rake 'litmus:install_agent[${{ matrix.collection }}]'
-
-    - name: Install module
-      run: |
-        buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:install_module' -- bundle exec rake 'litmus:install_module'
- 
-    - name: "Honeycomb: Record deployment times"
-      if: ${{ always() }}
-      run: |
-        echo ::group::honeycomb step
-        buildevents step $TRACE_ID $STEP_ID $STEP_START 'Deploy test system'
-        echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-3 >> $GITHUB_ENV
-        echo STEP_START=$(date +%s) >> $GITHUB_ENV
-        echo ::endgroup::
-    - name: Run acceptance tests
-      run: |
-        buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:acceptance:parallel' -- bundle exec rake 'litmus:acceptance:parallel'
- 
-    - name: "Honeycomb: Record acceptance testing times"
-      if: ${{ always() }}
-      run: |
-        buildevents step $TRACE_ID $STEP_ID $STEP_START 'Run acceptance tests'
-        echo STEP_ID=${{ matrix.platforms.image }}-${{ matrix.collection }}-4 >> $GITHUB_ENV
-        echo STEP_START=$(date +%s) >> $GITHUB_ENV
-    - name: Remove test environment
-      if: ${{ always() }}
-      continue-on-error: true
-      run: |
-        if [[ -f inventory.yaml || -f spec/fixtures/litmus_inventory.yaml ]]; then
-          buildevents cmd $TRACE_ID $STEP_ID 'rake litmus:tear_down' -- bundle exec rake 'litmus:tear_down'
-          echo ::group::=== REQUEST ===
-          cat request.json || true
-          echo
-          echo ::endgroup::
-        fi
- 
-    - name: "Honeycomb: Record removal times"
-      if: ${{ always() }}
-      run: |
-        buildevents step $TRACE_ID $STEP_ID $STEP_START 'Remove test environment'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
deleted file mode 100644
index 1509f6e..0000000
--- a/.github/workflows/release.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-name: "Publish module"
-
-on:
-  workflow_dispatch:
-  
-jobs:
-  create-github-release:
-    name: Deploy GitHub Release
-    runs-on: ubuntu-20.04
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v2
-        with:
-          ref: ${{ github.ref }}
-          clean: true
-          fetch-depth: 0
-      - name: Get Version
-        id: gv
-        run: |
-          echo "::set-output name=ver::$(jq --raw-output .version metadata.json)"
-      - name: Create Release
-        uses: actions/create-release@v1
-        id: create_release
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-        with:
-          tag_name: "v${{ steps.gv.outputs.ver }}"
-          draft: false
-          prerelease: false
-
-  deploy-forge:
-    name: Deploy to Forge
-    runs-on: ubuntu-20.04
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v2
-        with:
-          ref: ${{ github.ref }}
-          clean: true
-      - name: "PDK Build"
-        uses: docker://puppet/pdk:nightly
-        with:
-          args: 'build'
-      - name: "Push to Forge"
-        uses: docker://puppet/pdk:nightly
-        with:
-          args: 'release publish --forge-token ${{ secrets.FORGE_API_KEY }} --force'
diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml
deleted file mode 100644
index 6c1ae10..0000000
--- a/.github/workflows/spec.yml
+++ /dev/null
@@ -1,126 +0,0 @@
-name: "Spec Tests"
-
-on:
-  schedule:
-    - cron: '0 0 * * *'
-  workflow_dispatch:
-  pull_request:
-
-
-env:
-  HONEYCOMB_WRITEKEY: 7f3c63a70eecc61d635917de46bea4e6
-  HONEYCOMB_DATASET: litmus tests
-
-jobs:
-  setup_matrix:
-    name: "Setup Test Matrix"
-    runs-on: ubuntu-20.04
-    outputs:
-      spec_matrix: ${{ steps.get-matrix.outputs.spec_matrix }}
-
-    steps:
-    
-      - name: "Honeycomb: Start recording"
-        uses: puppetlabs/kvrhdn-gha-buildevents@pdk-templates-v1
-        with:
-          apikey: ${{ env.HONEYCOMB_WRITEKEY }}
-          dataset: ${{ env.HONEYCOMB_DATASET }}
-          job-status: ${{ job.status }}
-
-      - name: "Honeycomb: Start first step"
-        run: |
-          echo STEP_ID=setup-environment >> $GITHUB_ENV
-          echo STEP_START=$(date +%s) >> $GITHUB_ENV
-      - name: Checkout Source
-        uses: actions/checkout@v2
-        if: ${{ github.repository_owner == 'puppetlabs' }}
-
-      - name: Activate Ruby 2.7
-        uses: ruby/setup-ruby@v1
-        if: ${{ github.repository_owner == 'puppetlabs' }}
-        with:
-          ruby-version: "2.7"
-          bundler-cache: true
-
-      - name: Print bundle environment
-        if: ${{ github.repository_owner == 'puppetlabs' }}
-        run: |
-          echo ::group::bundler environment
-          buildevents cmd $TRACE_ID $STEP_ID 'bundle env' -- bundle env
-          echo ::endgroup::
-      - name: "Honeycomb: Record Setup Environment time"
-        if: ${{ github.repository_owner == 'puppetlabs' }}
-        run: |
-          buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Environment'
-          echo STEP_ID=Setup-Acceptance-Test-Matrix >> $GITHUB_ENV
-          echo STEP_START=$(date +%s) >> $GITHUB_ENV
-      - name: Run Static & Syntax Tests
-        if: ${{ github.repository_owner == 'puppetlabs' }}
-        run: |
-          buildevents cmd $TRACE_ID $STEP_ID 'static_syntax_checks' -- bundle exec rake syntax lint metadata_lint check:symlinks check:git_ignore check:dot_underscore check:test_file rubocop
-
-      - name: Setup Spec Test Matrix
-        id: get-matrix
-        run: |
-          if [ '${{ github.repository_owner }}' == 'puppetlabs' ]; then
-            buildevents cmd $TRACE_ID $STEP_ID matrix_from_metadata -- bundle exec matrix_from_metadata_v2
-          else
-            echo  "::set-output name=spec_matrix::{}"
-          fi
-      - name: "Honeycomb: Record Setup Test Matrix time"
-        if: ${{ always() }}
-        run: |
-          buildevents step $TRACE_ID $STEP_ID $STEP_START 'Setup Test Matrix'
-  Spec:
-    name: "Spec Tests (Puppet: ${{matrix.puppet_version}}, Ruby Ver: ${{matrix.ruby_version}})"
-    needs:
-      - setup_matrix
-    if: ${{ needs.setup_matrix.outputs.spec_matrix != '{}' }}
-
-    runs-on: ubuntu-20.04
-    strategy:
-      fail-fast: false
-      matrix: ${{fromJson(needs.setup_matrix.outputs.spec_matrix)}}
-
-    env:
-      BUILDEVENT_FILE: '../buildevents.txt'
-      PUPPET_GEM_VERSION: ${{ matrix.puppet_version }}
-      FACTER_GEM_VERSION: 'https://github.com/puppetlabs/facter#main'
-
-    steps:
-      - run: |
-          echo "SANITIZED_PUPPET_VERSION=$(echo '${{ matrix.puppet_version }}' | sed 's/~> //g')" >> $GITHUB_ENV
-
-      - run: |
-          echo 'puppet_version=${{ env.SANITIZED_PUPPET_VERSION }}' >> $BUILDEVENT_FILE
-      - name: "Honeycomb: Start first step"
-        run: |
-          echo "STEP_ID=${{ env.SANITIZED_PUPPET_VERSION }}-spec" >> $GITHUB_ENV
-          echo STEP_START=$(date +%s) >> $GITHUB_ENV
-
-      - name: "Honeycomb: Start recording"
-        uses: puppetlabs/kvrhdn-gha-buildevents@pdk-templates-v1
-        with:
-          apikey: ${{ env.HONEYCOMB_WRITEKEY }}
-          dataset: ${{ env.HONEYCOMB_DATASET }}
-          job-status: ${{ job.status }}
-          matrix-key: ${{ env.SANITIZED_PUPPET_VERSION }}
-      - name: Checkout Source
-        uses: actions/checkout@v2
-
-      - name: "Activate Ruby ${{ matrix.ruby_version }}"
-        uses: ruby/setup-ruby@v1
-        with:
-          ruby-version: ${{matrix.ruby_version}}
-          bundler-cache: true
-
-      - name: Print bundle environment
-        run: |
-          echo ::group::bundler environment
-          buildevents cmd $TRACE_ID $STEP_ID 'bundle env' -- bundle env
-          echo ::endgroup::
-
-
-      - name: Run parallel_spec tests
-        run: |
-          buildevents cmd $TRACE_ID $STEP_ID 'rake parallel_spec Puppet ${{ matrix.puppet_version }}, Ruby ${{ matrix.ruby_version }}' -- bundle exec rake parallel_spec
diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile
deleted file mode 100644
index 0814c5e..0000000
--- a/.gitpod.Dockerfile
+++ /dev/null
@@ -1,18 +0,0 @@
-FROM gitpod/workspace-full
-RUN sudo wget https://apt.puppet.com/puppet-tools-release-bionic.deb && \
-    wget https://apt.puppetlabs.com/puppet6-release-bionic.deb && \
-    sudo dpkg -i puppet6-release-bionic.deb && \
-    sudo dpkg -i puppet-tools-release-bionic.deb && \
-    sudo apt-get update && \
-    sudo apt-get install -y pdk zsh puppet-agent && \
-    sudo apt-get clean && \
-    sudo rm -rf /var/lib/apt/lists/*
-RUN sudo usermod -s $(which zsh) gitpod && \
-    sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" && \
-    echo "plugins=(git gitignore github gem pip bundler python ruby docker docker-compose)" >> /home/gitpod/.zshrc && \
-    echo 'PATH="$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/opt/puppetlabs/bin:/opt/puppetlabs/puppet/bin"'  >> /home/gitpod/.zshrc && \
-    sudo /opt/puppetlabs/puppet/bin/gem install puppet-debugger hub -N && \
-    mkdir -p /home/gitpod/.config/puppet && \
-    /opt/puppetlabs/puppet/bin/ruby -r yaml -e "puts ({'disabled' => true}).to_yaml" > /home/gitpod/.config/puppet/analytics.yml
-RUN rm -f puppet6-release-bionic.deb  puppet-tools-release-bionic.deb
-ENTRYPOINT /usr/bin/zsh
diff --git a/.gitpod.yml b/.gitpod.yml
deleted file mode 100644
index 9d89d9f..0000000
--- a/.gitpod.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-image:
-  file: .gitpod.Dockerfile
-
-tasks:
-  - init: pdk bundle install
-
-vscode:
-  extensions:
-    - puppet.puppet-vscode@1.2.0:f5iEPbmOj6FoFTOV6q8LTg==
diff --git a/.pdkignore b/.pdkignore
new file mode 100644
index 0000000..c538bea
--- /dev/null
+++ b/.pdkignore
@@ -0,0 +1,47 @@
+.git/
+.*.sw[op]
+.metadata
+.yardoc
+.yardwarns
+*.iml
+/.bundle/
+/.idea/
+/.vagrant/
+/coverage/
+/bin/
+/doc/
+/Gemfile.local
+/Gemfile.lock
+/junit/
+/log/
+/pkg/
+/spec/fixtures/manifests/
+/spec/fixtures/modules/
+/tmp/
+/vendor/
+/convert_report.txt
+/update_report.txt
+.DS_Store
+.project
+.envrc
+/inventory.yaml
+/spec/fixtures/litmus_inventory.yaml
+/appveyor.yml
+/.editorconfig
+/.fixtures.yml
+/Gemfile
+/.gitattributes
+/.gitignore
+/.gitlab-ci.yml
+/.pdkignore
+/.puppet-lint.rc
+/Rakefile
+/rakelib/
+/.rspec
+/.rubocop.yml
+/.travis.yml
+/.yardopts
+/spec/
+/.vscode/
+/.sync.yml
+/.devcontainer/
diff --git a/.puppet-lint.rc b/.puppet-lint.rc
new file mode 100644
index 0000000..6d61fb0
--- /dev/null
+++ b/.puppet-lint.rc
@@ -0,0 +1,2 @@
+--relative
+--no-anchor_resource-check
diff --git a/.rspec b/.rspec
new file mode 100644
index 0000000..16f9cdb
--- /dev/null
+++ b/.rspec
@@ -0,0 +1,2 @@
+--color
+--format documentation
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 0000000..31e8248
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,519 @@
+---
+require:
+- rubocop-performance
+- rubocop-rspec
+AllCops:
+  DisplayCopNames: true
+  TargetRubyVersion: '2.5'
+  Include:
+  - "**/*.rb"
+  Exclude:
+  - bin/*
+  - ".vendor/**/*"
+  - "**/Gemfile"
+  - "**/Rakefile"
+  - pkg/**/*
+  - spec/fixtures/**/*
+  - vendor/**/*
+  - "**/Puppetfile"
+  - "**/Vagrantfile"
+  - "**/Guardfile"
+Layout/LineLength:
+  Description: People have wide screens, use them.
+  Max: 200
+RSpec/BeforeAfterAll:
+  Description: Beware of using after(:all) as it may cause state to leak between tests.
+    A necessary evil in acceptance testing.
+  Exclude:
+  - spec/acceptance/**/*.rb
+RSpec/HookArgument:
+  Description: Prefer explicit :each argument, matching existing module's style
+  EnforcedStyle: each
+RSpec/DescribeSymbol:
+  Exclude:
+  - spec/unit/facter/**/*.rb
+Style/BlockDelimiters:
+  Description: Prefer braces for chaining. Mostly an aesthetical choice. Better to
+    be consistent then.
+  EnforcedStyle: braces_for_chaining
+Style/ClassAndModuleChildren:
+  Description: Compact style reduces the required amount of indentation.
+  EnforcedStyle: compact
+Style/EmptyElse:
+  Description: Enforce against empty else clauses, but allow `nil` for clarity.
+  EnforcedStyle: empty
+Style/FormatString:
+  Description: Following the main puppet project's style, prefer the % format format.
+  EnforcedStyle: percent
+Style/FormatStringToken:
+  Description: Following the main puppet project's style, prefer the simpler template
+    tokens over annotated ones.
+  EnforcedStyle: template
+Style/Lambda:
+  Description: Prefer the keyword for easier discoverability.
+  EnforcedStyle: literal
+Style/RegexpLiteral:
+  Description: Community preference. See https://github.com/voxpupuli/modulesync_config/issues/168
+  EnforcedStyle: percent_r
+Style/TernaryParentheses:
+  Description: Checks for use of parentheses around ternary conditions. Enforce parentheses
+    on complex expressions for better readability, but seriously consider breaking
+    it up.
+  EnforcedStyle: require_parentheses_when_complex
+Style/TrailingCommaInArguments:
+  Description: Prefer always trailing comma on multiline argument lists. This makes
+    diffs, and re-ordering nicer.
+  EnforcedStyleForMultiline: comma
+Style/TrailingCommaInArrayLiteral:
+  Description: Prefer always trailing comma on multiline literals. This makes diffs,
+    and re-ordering nicer.
+  EnforcedStyleForMultiline: comma
+Style/SymbolArray:
+  Description: Using percent style obscures symbolic intent of array's contents.
+  EnforcedStyle: brackets
+RSpec/MessageSpies:
+  EnforcedStyle: receive
+Style/Documentation:
+  Exclude:
+  - lib/puppet/parser/functions/**/*
+  - spec/**/*
+Style/WordArray:
+  EnforcedStyle: brackets
+Performance/AncestorsInclude:
+  Enabled: true
+Performance/BigDecimalWithNumericArgument:
+  Enabled: true
+Performance/BlockGivenWithExplicitBlock:
+  Enabled: true
+Performance/CaseWhenSplat:
+  Enabled: true
+Performance/ConstantRegexp:
+  Enabled: true
+Performance/MethodObjectAsBlock:
+  Enabled: true
+Performance/RedundantSortBlock:
+  Enabled: true
+Performance/RedundantStringChars:
+  Enabled: true
+Performance/ReverseFirst:
+  Enabled: true
+Performance/SortReverse:
+  Enabled: true
+Performance/Squeeze:
+  Enabled: true
+Performance/StringInclude:
+  Enabled: true
+Performance/Sum:
+  Enabled: true
+Style/CollectionMethods:
+  Enabled: true
+Style/MethodCalledOnDoEndBlock:
+  Enabled: true
+Style/StringMethods:
+  Enabled: true
+Bundler/InsecureProtocolSource:
+  Enabled: false
+Gemspec/DuplicatedAssignment:
+  Enabled: false
+Gemspec/OrderedDependencies:
+  Enabled: false
+Gemspec/RequiredRubyVersion:
+  Enabled: false
+Gemspec/RubyVersionGlobalsUsage:
+  Enabled: false
+Layout/ArgumentAlignment:
+  Enabled: false
+Layout/BeginEndAlignment:
+  Enabled: false
+Layout/ClosingHeredocIndentation:
+  Enabled: false
+Layout/EmptyComment:
+  Enabled: false
+Layout/EmptyLineAfterGuardClause:
+  Enabled: false
+Layout/EmptyLinesAroundArguments:
+  Enabled: false
+Layout/EmptyLinesAroundAttributeAccessor:
+  Enabled: false
+Layout/EndOfLine:
+  Enabled: false
+Layout/FirstArgumentIndentation:
+  Enabled: false
+Layout/HashAlignment:
+  Enabled: false
+Layout/HeredocIndentation:
+  Enabled: false
+Layout/LeadingEmptyLines:
+  Enabled: false
+Layout/SpaceAroundMethodCallOperator:
+  Enabled: false
+Layout/SpaceInsideArrayLiteralBrackets:
+  Enabled: false
+Layout/SpaceInsideReferenceBrackets:
+  Enabled: false
+Lint/BigDecimalNew:
+  Enabled: false
+Lint/BooleanSymbol:
+  Enabled: false
+Lint/ConstantDefinitionInBlock:
+  Enabled: false
+Lint/DeprecatedOpenSSLConstant:
+  Enabled: false
+Lint/DisjunctiveAssignmentInConstructor:
+  Enabled: false
+Lint/DuplicateElsifCondition:
+  Enabled: false
+Lint/DuplicateRequire:
+  Enabled: false
+Lint/DuplicateRescueException:
+  Enabled: false
+Lint/EmptyConditionalBody:
+  Enabled: false
+Lint/EmptyFile:
+  Enabled: false
+Lint/ErbNewArguments:
+  Enabled: false
+Lint/FloatComparison:
+  Enabled: false
+Lint/HashCompareByIdentity:
+  Enabled: false
+Lint/IdentityComparison:
+  Enabled: false
+Lint/InterpolationCheck:
+  Enabled: false
+Lint/MissingCopEnableDirective:
+  Enabled: false
+Lint/MixedRegexpCaptureTypes:
+  Enabled: false
+Lint/NestedPercentLiteral:
+  Enabled: false
+Lint/NonDeterministicRequireOrder:
+  Enabled: false
+Lint/OrderedMagicComments:
+  Enabled: false
+Lint/OutOfRangeRegexpRef:
+  Enabled: false
+Lint/RaiseException:
+  Enabled: false
+Lint/RedundantCopEnableDirective:
+  Enabled: false
+Lint/RedundantRequireStatement:
+  Enabled: false
+Lint/RedundantSafeNavigation:
+  Enabled: false
+Lint/RedundantWithIndex:
+  Enabled: false
+Lint/RedundantWithObject:
+  Enabled: false
+Lint/RegexpAsCondition:
+  Enabled: false
+Lint/ReturnInVoidContext:
+  Enabled: false
+Lint/SafeNavigationConsistency:
+  Enabled: false
+Lint/SafeNavigationWithEmpty:
+  Enabled: false
+Lint/SelfAssignment:
+  Enabled: false
+Lint/SendWithMixinArgument:
+  Enabled: false
+Lint/ShadowedArgument:
+  Enabled: false
+Lint/StructNewOverride:
+  Enabled: false
+Lint/ToJSON:
+  Enabled: false
+Lint/TopLevelReturnWithArgument:
+  Enabled: false
+Lint/TrailingCommaInAttributeDeclaration:
+  Enabled: false
+Lint/UnreachableLoop:
+  Enabled: false
+Lint/UriEscapeUnescape:
+  Enabled: false
+Lint/UriRegexp:
+  Enabled: false
+Lint/UselessMethodDefinition:
+  Enabled: false
+Lint/UselessTimes:
+  Enabled: false
+Metrics/AbcSize:
+  Enabled: false
+Metrics/BlockLength:
+  Enabled: false
+Metrics/BlockNesting:
+  Enabled: false
+Metrics/ClassLength:
+  Enabled: false
+Metrics/CyclomaticComplexity:
+  Enabled: false
+Metrics/MethodLength:
+  Enabled: false
+Metrics/ModuleLength:
+  Enabled: false
+Metrics/ParameterLists:
+  Enabled: false
+Metrics/PerceivedComplexity:
+  Enabled: false
+Migration/DepartmentName:
+  Enabled: false
+Naming/AccessorMethodName:
+  Enabled: false
+Naming/BlockParameterName:
+  Enabled: false
+Naming/HeredocDelimiterCase:
+  Enabled: false
+Naming/HeredocDelimiterNaming:
+  Enabled: false
+Naming/MemoizedInstanceVariableName:
+  Enabled: false
+Naming/MethodParameterName:
+  Enabled: false
+Naming/RescuedExceptionsVariableName:
+  Enabled: false
+Naming/VariableNumber:
+  Enabled: false
+Performance/BindCall:
+  Enabled: false
+Performance/DeletePrefix:
+  Enabled: false
+Performance/DeleteSuffix:
+  Enabled: false
+Performance/InefficientHashSearch:
+  Enabled: false
+Performance/UnfreezeString:
+  Enabled: false
+Performance/UriDefaultParser:
+  Enabled: false
+RSpec/Be:
+  Enabled: false
+RSpec/Capybara/CurrentPathExpectation:
+  Enabled: false
+RSpec/Capybara/FeatureMethods:
+  Enabled: false
+RSpec/Capybara/VisibilityMatcher:
+  Enabled: false
+RSpec/ContextMethod:
+  Enabled: false
+RSpec/ContextWording:
+  Enabled: false
+RSpec/DescribeClass:
+  Enabled: false
+RSpec/EmptyHook:
+  Enabled: false
+RSpec/EmptyLineAfterExample:
+  Enabled: false
+RSpec/EmptyLineAfterExampleGroup:
+  Enabled: false
+RSpec/EmptyLineAfterHook:
+  Enabled: false
+RSpec/ExampleLength:
+  Enabled: false
+RSpec/ExampleWithoutDescription:
+  Enabled: false
+RSpec/ExpectChange:
+  Enabled: false
+RSpec/ExpectInHook:
+  Enabled: false
+RSpec/FactoryBot/AttributeDefinedStatically:
+  Enabled: false
+RSpec/FactoryBot/CreateList:
+  Enabled: false
+RSpec/FactoryBot/FactoryClassName:
+  Enabled: false
+RSpec/HooksBeforeExamples:
+  Enabled: false
+RSpec/ImplicitBlockExpectation:
+  Enabled: false
+RSpec/ImplicitSubject:
+  Enabled: false
+RSpec/LeakyConstantDeclaration:
+  Enabled: false
+RSpec/LetBeforeExamples:
+  Enabled: false
+RSpec/MissingExampleGroupArgument:
+  Enabled: false
+RSpec/MultipleExpectations:
+  Enabled: false
+RSpec/MultipleMemoizedHelpers:
+  Enabled: false
+RSpec/MultipleSubjects:
+  Enabled: false
+RSpec/NestedGroups:
+  Enabled: false
+RSpec/PredicateMatcher:
+  Enabled: false
+RSpec/ReceiveCounts:
+  Enabled: false
+RSpec/ReceiveNever:
+  Enabled: false
+RSpec/RepeatedExampleGroupBody:
+  Enabled: false
+RSpec/RepeatedExampleGroupDescription:
+  Enabled: false
+RSpec/RepeatedIncludeExample:
+  Enabled: false
+RSpec/ReturnFromStub:
+  Enabled: false
+RSpec/SharedExamples:
+  Enabled: false
+RSpec/StubbedMock:
+  Enabled: false
+RSpec/UnspecifiedException:
+  Enabled: false
+RSpec/VariableDefinition:
+  Enabled: false
+RSpec/VoidExpect:
+  Enabled: false
+RSpec/Yield:
+  Enabled: false
+Security/Open:
+  Enabled: false
+Style/AccessModifierDeclarations:
+  Enabled: false
+Style/AccessorGrouping:
+  Enabled: false
+Style/AsciiComments:
+  Enabled: false
+Style/BisectedAttrAccessor:
+  Enabled: false
+Style/CaseLikeIf:
+  Enabled: false
+Style/ClassEqualityComparison:
+  Enabled: false
+Style/ColonMethodDefinition:
+  Enabled: false
+Style/CombinableLoops:
+  Enabled: false
+Style/CommentedKeyword:
+  Enabled: false
+Style/Dir:
+  Enabled: false
+Style/DoubleCopDisableDirective:
+  Enabled: false
+Style/EmptyBlockParameter:
+  Enabled: false
+Style/EmptyLambdaParameter:
+  Enabled: false
+Style/Encoding:
+  Enabled: false
+Style/EvalWithLocation:
+  Enabled: false
+Style/ExpandPathArguments:
+  Enabled: false
+Style/ExplicitBlockArgument:
+  Enabled: false
+Style/ExponentialNotation:
+  Enabled: false
+Style/FloatDivision:
+  Enabled: false
+Style/FrozenStringLiteralComment:
+  Enabled: false
+Style/GlobalStdStream:
+  Enabled: false
+Style/HashAsLastArrayItem:
+  Enabled: false
+Style/HashLikeCase:
+  Enabled: false
+Style/HashTransformKeys:
+  Enabled: false
+Style/HashTransformValues:
+  Enabled: false
+Style/IfUnlessModifier:
+  Enabled: false
+Style/KeywordParametersOrder:
+  Enabled: false
+Style/MinMax:
+  Enabled: false
+Style/MixinUsage:
+  Enabled: false
+Style/MultilineWhenThen:
+  Enabled: false
+Style/NegatedUnless:
+  Enabled: false
+Style/NumericPredicate:
+  Enabled: false
+Style/OptionalBooleanParameter:
+  Enabled: false
+Style/OrAssignment:
+  Enabled: false
+Style/RandomWithOffset:
+  Enabled: false
+Style/RedundantAssignment:
+  Enabled: false
+Style/RedundantCondition:
+  Enabled: false
+Style/RedundantConditional:
+  Enabled: false
+Style/RedundantFetchBlock:
+  Enabled: false
+Style/RedundantFileExtensionInRequire:
+  Enabled: false
+Style/RedundantRegexpCharacterClass:
+  Enabled: false
+Style/RedundantRegexpEscape:
+  Enabled: false
+Style/RedundantSelfAssignment:
+  Enabled: false
+Style/RedundantSort:
+  Enabled: false
+Style/RescueStandardError:
+  Enabled: false
+Style/SingleArgumentDig:
+  Enabled: false
+Style/SlicingWithRange:
+  Enabled: false
+Style/SoleNestedConditional:
+  Enabled: false
+Style/StderrPuts:
+  Enabled: false
+Style/StringConcatenation:
+  Enabled: false
+Style/Strip:
+  Enabled: false
+Style/SymbolProc:
+  Enabled: false
+Style/TrailingBodyOnClass:
+  Enabled: false
+Style/TrailingBodyOnMethodDefinition:
+  Enabled: false
+Style/TrailingBodyOnModule:
+  Enabled: false
+Style/TrailingCommaInHashLiteral:
+  Enabled: false
+Style/TrailingMethodEndStatement:
+  Enabled: false
+Style/UnpackFirst:
+  Enabled: false
+Lint/DuplicateBranch:
+  Enabled: false
+Lint/DuplicateRegexpCharacterClassElement:
+  Enabled: false
+Lint/EmptyBlock:
+  Enabled: false
+Lint/EmptyClass:
+  Enabled: false
+Lint/NoReturnInBeginEndBlocks:
+  Enabled: false
+Lint/ToEnumArguments:
+  Enabled: false
+Lint/UnexpectedBlockArity:
+  Enabled: false
+Lint/UnmodifiedReduceAccumulator:
+  Enabled: false
+Performance/CollectionLiteralInLoop:
+  Enabled: false
+Style/ArgumentsForwarding:
+  Enabled: false
+Style/CollectionCompact:
+  Enabled: false
+Style/DocumentDynamicEvalDefinition:
+  Enabled: false
+Style/NegatedIfElseCondition:
+  Enabled: false
+Style/NilLambda:
+  Enabled: false
+Style/RedundantArgument:
+  Enabled: false
+Style/SwapValues:
+  Enabled: false
diff --git a/.sync.yml b/.sync.yml
new file mode 100644
index 0000000..a096152
--- /dev/null
+++ b/.sync.yml
@@ -0,0 +1,37 @@
+---
+".gitlab-ci.yml":
+  delete: true
+appveyor.yml:
+  delete: true
+
+Gemfile:
+  optional:
+    ":development":
+    - gem: github_changelog_generator
+      version: '= 1.15.2'
+Rakefile:
+  changelog_max_issues: 500
+spec/spec_helper.rb:
+  mock_with: ":rspec"
+  coverage_report: true
+.gitpod.Dockerfile:
+  unmanaged: false
+.gitpod.yml:
+  unmanaged: false
+.github/workflows/pr_test.yml:
+  unmanaged: false
+.github/workflows/nightly.yml:
+  unmanaged: false
+.github/workflows/spec.yml:
+  checks: 'syntax lint metadata_lint check:symlinks check:git_ignore check:dot_underscore check:test_file rubocop'
+  unmanaged: false
+.github/workflows/release.yml:
+  unmanaged: false
+.github/workflows/auto_release.yml:
+  unmanaged: false
+.travis.yml:
+  delete: true
+changelog_since_tag: '5.0.1'
+Rakefile:
+  extra_disabled_lint_checks:
+    - anchor_resource
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..2f1e4f7
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,6 @@
+{
+  "recommendations": [
+    "puppet.puppet-vscode",
+    "rebornix.Ruby"
+  ]
+}
diff --git a/.yardopts b/.yardopts
new file mode 100644
index 0000000..29c933b
--- /dev/null
+++ b/.yardopts
@@ -0,0 +1 @@
+--markup markdown
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..a84b5ee
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,73 @@
+source ENV['GEM_SOURCE'] || 'https://rubygems.org'
+
+def location_for(place_or_version, fake_version = nil)
+  git_url_regex = %r{\A(?<url>(https?|git)[:@][^#]*)(#(?<branch>.*))?}
+  file_url_regex = %r{\Afile:\/\/(?<path>.*)}
+
+  if place_or_version && (git_url = place_or_version.match(git_url_regex))
+    [fake_version, { git: git_url[:url], branch: git_url[:branch], require: false }].compact
+  elsif place_or_version && (file_url = place_or_version.match(file_url_regex))
+    ['>= 0', { path: File.expand_path(file_url[:path]), require: false }]
+  else
+    [place_or_version, { require: false }]
+  end
+end
+
+group :development do
+  gem "json", '= 2.1.0',                               require: false if Gem::Requirement.create(['>= 2.5.0', '< 2.7.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup))
+  gem "json", '= 2.3.0',                               require: false if Gem::Requirement.create(['>= 2.7.0', '< 3.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup))
+  gem "json", '= 2.5.1',                               require: false if Gem::Requirement.create(['>= 3.0.0', '< 3.0.5']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup))
+  gem "json", '= 2.6.1',                               require: false if Gem::Requirement.create(['>= 3.1.0', '< 3.1.3']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup))
+  gem "json", '= 2.6.3',                               require: false if Gem::Requirement.create(['>= 3.2.0', '< 4.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup))
+  gem "voxpupuli-puppet-lint-plugins", '~> 3.1',       require: false
+  gem "facterdb", '~> 1.18',                           require: false
+  gem "metadata-json-lint", '>= 2.0.2', '< 4.0.0',     require: false
+  gem "puppetlabs_spec_helper", '>= 3.0.0', '< 5.0.0', require: false
+  gem "rspec-puppet-facts", '~> 2.0',                  require: false
+  gem "codecov", '~> 0.2',                             require: false
+  gem "dependency_checker", '~> 0.2',                  require: false
+  gem "parallel_tests", '~> 3.4',                      require: false
+  gem "pry", '~> 0.10',                                require: false
+  gem "simplecov-console", '~> 0.5',                   require: false
+  gem "puppet-debugger", '~> 1.0',                     require: false
+  gem "rubocop", '= 1.6.1',                            require: false
+  gem "rubocop-performance", '= 1.9.1',                require: false
+  gem "rubocop-rspec", '= 2.0.1',                      require: false
+  gem "rb-readline", '= 0.5.5',                        require: false, platforms: [:mswin, :mingw, :x64_mingw]
+  gem "github_changelog_generator", '= 1.15.2',        require: false
+end
+group :system_tests do
+  gem "puppet_litmus", '< 1.0.0', require: false, platforms: [:ruby]
+  gem "serverspec", '~> 2.41',    require: false
+end
+
+puppet_version = ENV['PUPPET_GEM_VERSION']
+facter_version = ENV['FACTER_GEM_VERSION']
+hiera_version = ENV['HIERA_GEM_VERSION']
+
+gems = {}
+
+gems['puppet'] = location_for(puppet_version)
+
+# If facter or hiera versions have been specified via the environment
+# variables
+
+gems['facter'] = location_for(facter_version) if facter_version
+gems['hiera'] = location_for(hiera_version) if hiera_version
+
+gems.each do |gem_name, gem_params|
+  gem gem_name, *gem_params
+end
+
+# Evaluate Gemfile.local and ~/.gemfile if they exist
+extra_gemfiles = [
+  "#{__FILE__}.local",
+  File.join(Dir.home, '.gemfile'),
+]
+
+extra_gemfiles.each do |gemfile|
+  if File.file?(gemfile) && File.readable?(gemfile)
+    eval(File.read(gemfile), binding)
+  end
+end
+# vim: syntax=ruby
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..8fdf11b
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require 'bundler'
+require 'puppet_litmus/rake_tasks' if Bundler.rubygems.find_name('puppet_litmus').any?
+require 'puppetlabs_spec_helper/rake_tasks'
+require 'puppet-syntax/tasks/puppet-syntax'
+require 'puppet_blacksmith/rake_tasks' if Bundler.rubygems.find_name('puppet-blacksmith').any?
+require 'github_changelog_generator/task' if Bundler.rubygems.find_name('github_changelog_generator').any?
+require 'puppet-strings/tasks' if Bundler.rubygems.find_name('puppet-strings').any?
+
+def changelog_user
+  return unless Rake.application.top_level_tasks.include? "changelog"
+  returnVal = nil || JSON.load(File.read('metadata.json'))['author']
+  raise "unable to find the changelog_user in .sync.yml, or the author in metadata.json" if returnVal.nil?
+  puts "GitHubChangelogGenerator user:#{returnVal}"
+  returnVal
+end
+
+def changelog_project
+  return unless Rake.application.top_level_tasks.include? "changelog"
+
+  returnVal = nil
+  returnVal ||= begin
+    metadata_source = JSON.load(File.read('metadata.json'))['source']
+    metadata_source_match = metadata_source && metadata_source.match(%r{.*\/([^\/]*?)(?:\.git)?\Z})
+
+    metadata_source_match && metadata_source_match[1]
+  end
+
+  raise "unable to find the changelog_project in .sync.yml or calculate it from the source in metadata.json" if returnVal.nil?
+
+  puts "GitHubChangelogGenerator project:#{returnVal}"
+  returnVal
+end
+
+def changelog_future_release
+  return unless Rake.application.top_level_tasks.include? "changelog"
+  returnVal = "v%s" % JSON.load(File.read('metadata.json'))['version']
+  raise "unable to find the future_release (version) in metadata.json" if returnVal.nil?
+  puts "GitHubChangelogGenerator future_release:#{returnVal}"
+  returnVal
+end
+
+PuppetLint.configuration.send('disable_relative')
+PuppetLint.configuration.send('disable_anchor_resource')
+
+
+if Bundler.rubygems.find_name('github_changelog_generator').any?
+  GitHubChangelogGenerator::RakeTask.new :changelog do |config|
+    raise "Set CHANGELOG_GITHUB_TOKEN environment variable eg 'export CHANGELOG_GITHUB_TOKEN=valid_token_here'" if Rake.application.top_level_tasks.include? "changelog" and ENV['CHANGELOG_GITHUB_TOKEN'].nil?
+    config.user = "#{changelog_user}"
+    config.project = "#{changelog_project}"
+    config.since_tag = "5.0.1"
+    config.future_release = "#{changelog_future_release}"
+    config.exclude_labels = ['maintenance']
+    config.header = "# Change log\n\nAll notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org)."
+    config.add_pr_wo_labels = true
+    config.issues = false
+    config.merge_prefix = "### UNCATEGORIZED PRS; LABEL THEM ON GITHUB"
+    config.configure_sections = {
+      "Changed" => {
+        "prefix" => "### Changed",
+        "labels" => ["backwards-incompatible"],
+      },
+      "Added" => {
+        "prefix" => "### Added",
+        "labels" => ["enhancement", "feature"],
+      },
+      "Fixed" => {
+        "prefix" => "### Fixed",
+        "labels" => ["bug", "documentation", "bugfix"],
+      },
+    }
+  end
+else
+  desc 'Generate a Changelog from GitHub'
+  task :changelog do
+    raise <<EOM
+The changelog tasks depends on recent features of the github_changelog_generator gem.
+Please manually add it to your .sync.yml for now, and run `pdk update`:
+---
+Gemfile:
+  optional:
+    ':development':
+      - gem: 'github_changelog_generator'
+        version: '~> 1.15'
+        condition: "Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.3.0')"
+EOM
+  end
+end
+
diff --git a/debian/changelog b/debian/changelog
index ad253ed..ac33738 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+puppet-module-puppetlabs-apt (9.0.1+git20230201.1.790df53-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 09 Feb 2023 16:38:04 -0000
+
 puppet-module-puppetlabs-apt (9.0.1-1) unstable; urgency=medium
 
   * Team upload.
diff --git a/manifests/mark.pp b/manifests/mark.pp
index 08e396c..b2146b0 100644
--- a/manifests/mark.pp
+++ b/manifests/mark.pp
@@ -8,7 +8,7 @@
 define apt::mark (
   Enum['auto','manual','hold','unhold'] $setting,
 ) {
-  if $title !~ /^[a-zA-Z0-9\-_]+$/ {
+  if $title !~ /^[a-z0-9][a-z0-9.+\-]+$/ {
     fail("Invalid package name: ${title}")
   }
 
diff --git a/manifests/ppa.pp b/manifests/ppa.pp
index 4e5e775..2a5b4a8 100644
--- a/manifests/ppa.pp
+++ b/manifests/ppa.pp
@@ -40,7 +40,7 @@ define apt::ppa (
   }
 
   # Validate the resource name
-  if $name !~ /^ppa:([a-zA-Z0-9\-_]+)\/([a-zA-z0-9\-_\.]+)$/ {
+  if $name !~ /^ppa:([a-zA-Z0-9\-_.]+)\/([a-zA-z0-9\-_\.]+)$/ {
     fail("Invalid PPA name: ${name}")
   }
 
diff --git a/manifests/source.pp b/manifests/source.pp
index b1be300..420d19d 100644
--- a/manifests/source.pp
+++ b/manifests/source.pp
@@ -143,7 +143,7 @@ define apt::source (
       'comment'          => $comment,
       'includes'         => $includes,
       'options'          => delete_undef_values({
-          'arch'              => $architecture,
+          'arch'              => $_architecture,
           'trusted'           => $allow_unsigned ? { true => 'yes', false => undef },
           'allow-insecure'    => $allow_insecure ? { true => 'yes', false => undef },
           'signed-by'         => $keyring,
diff --git a/manifests/update.pp b/manifests/update.pp
index ef37f52..a9f2486 100644
--- a/manifests/update.pp
+++ b/manifests/update.pp
@@ -5,7 +5,7 @@
 class apt::update {
   assert_private()
 
-  #TODO: to catch if $apt_update_last_success has the value of -1 here. If we
+  #TODO: to catch if apt_update_last_success has the value of -1 here. If we
   #opt to do this, a info/warn would likely be all you'd need likely to happen
   #on the first run, but if it's not run in awhile something is likely borked
   #with apt and we'd want to know about it.
@@ -18,8 +18,8 @@ class apt::update {
       #compare current date with the apt_update_last_success fact to determine
       #if we should kick apt_update.
       $daily_threshold = (Integer(Timestamp().strftime('%s')) - 86400)
-      if $apt::apt_update_last_success {
-        if $apt::apt_update_last_success + 0 < $daily_threshold {
+      if $facts['apt_update_last_success'] {
+        if $facts['apt_update_last_success'] + 0 < $daily_threshold {
           $_kick_apt = true
         } else {
           $_kick_apt = false
@@ -33,8 +33,8 @@ class apt::update {
       #compare current date with the apt_update_last_success fact to determine
       #if we should kick apt_update.
       $weekly_threshold = (Integer(Timestamp().strftime('%s')) - 604800)
-      if $apt::apt_update_last_success {
-        if ( $apt::apt_update_last_success + 0 < $weekly_threshold ) {
+      if $facts['apt_update_last_success'] {
+        if $facts['apt_update_last_success'] + 0 < $weekly_threshold {
           $_kick_apt = true
         } else {
           $_kick_apt = false
diff --git a/metadata.json b/metadata.json
index ace916f..4de8a1c 100644
--- a/metadata.json
+++ b/metadata.json
@@ -37,6 +37,6 @@
     }
   ],
   "template-url": "https://github.com/puppetlabs/pdk-templates.git#main",
-  "template-ref": "tags/2.6.0-0-gd0490b9",
+  "template-ref": "2.7.1-0-g9a16c87",
   "pdk-version": "2.5.0"
 }
diff --git a/spec/acceptance/01_apt_class_spec.rb b/spec/acceptance/01_apt_class_spec.rb
new file mode 100644
index 0000000..1b50c7b
--- /dev/null
+++ b/spec/acceptance/01_apt_class_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper_acceptance'
+
+describe 'apt class' do
+  context 'with default parameters' do
+    # Using puppet_apply as a helper
+    it 'works with no errors' do
+      pp = <<-MANIFEST
+      class { 'apt': }
+      MANIFEST
+
+      # Run it twice and test for idempotency
+      apply_manifest(pp, catch_failures: true)
+      apply_manifest(pp, catch_changes: true)
+    end
+  end
+end
diff --git a/spec/acceptance/apt_backports_spec.rb b/spec/acceptance/apt_backports_spec.rb
new file mode 100644
index 0000000..5c4dcce
--- /dev/null
+++ b/spec/acceptance/apt_backports_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper_acceptance'
+
+describe 'apt::backports' do
+  context 'when using defaults' do
+    let(:pp) do
+      <<-MANIFEST
+        include apt::backports
+      MANIFEST
+    end
+
+    it 'applies idempotently' do
+      idempotent_apply(pp)
+    end
+
+    it 'provides backports apt sources' do
+      run_shell('apt policy | grep --quiet backports')
+    end
+  end
+end
diff --git a/spec/acceptance/apt_key_provider_spec.rb b/spec/acceptance/apt_key_provider_spec.rb
new file mode 100644
index 0000000..ee4a4c8
--- /dev/null
+++ b/spec/acceptance/apt_key_provider_spec.rb
@@ -0,0 +1,915 @@
+# frozen_string_literal: true
+
+require 'spec_helper_acceptance'
+
+PUPPETLABS_GPG_KEY_SHORT_ID         = 'EF8D349F'
+PUPPETLABS_GPG_KEY_LONG_ID          = '7F438280EF8D349F'
+PUPPETLABS_GPG_KEY_FINGERPRINT      = '6F6B15509CF8E59E6E469F327F438280EF8D349F'
+PUPPETLABS_APT_URL                  = 'apt.puppetlabs.com'
+PUPPETLABS_GPG_KEY_FILE             = 'DEB-GPG-KEY-puppet'
+CENTOS_GPG_KEY_SHORT_ID             = 'C105B9DE'
+CENTOS_GPG_KEY_LONG_ID              = '0946FCA2C105B9DE'
+CENTOS_GPG_KEY_FINGERPRINT          = 'C1DAC52D1664E8A4386DBA430946FCA2C105B9DE'
+CENTOS_REPO_URL                     = 'ftp.cvut.cz/centos'
+CENTOS_GPG_KEY_FILE                 = 'RPM-GPG-KEY-CentOS-6'
+PUPPETLABS_EXP_KEY_LONG_ID          = '47B320EB4C7C375AA9DAE1A01054B7A24BD6EC30'
+PUPPETLABS_EXP_KEY_DATES            = 'pub:e:4096:1:1054B7A24BD6EC30:2010-07-10:2017-01-05::-:Puppet Labs Release Key'
+SHOULD_NEVER_EXIST_ID               = 'EF8D349F'
+KEY_CHECK_COMMAND                   = 'apt-key adv --no-tty --list-keys --with-colons --fingerprint | grep '
+PUPPETLABS_KEY_CHECK_COMMAND        = "#{KEY_CHECK_COMMAND} #{PUPPETLABS_GPG_KEY_FINGERPRINT}"
+CENTOS_KEY_CHECK_COMMAND            = "#{KEY_CHECK_COMMAND} #{CENTOS_GPG_KEY_FINGERPRINT}"
+PUPPETLABS_EXP_CHECK_COMMAND        = 'apt-key list | grep -F -A 1 \'pub   rsa4096 2010-07-10 [SC] [expired: 2017-01-05]\' | grep \'47B3 20EB 4C7C 375A A9DA  E1A0 1054 B7A2 4BD6 EC30\''
+
+def install_key(key)
+  retry_on_error_matching do
+    run_shell("apt-key adv --no-tty --keyserver pgp.mit.edu --recv-keys #{key}")
+  end
+end
+
+def apply_manifest_twice(manifest_pp)
+  retry_on_error_matching do
+    apply_manifest(manifest_pp, catch_failures: true)
+  end
+  retry_on_error_matching do
+    apply_manifest(manifest_pp, catch_changes: true)
+  end
+end
+
+refresh_pp = <<-MANIFEST
+        apt_key { '#{PUPPETLABS_EXP_KEY_LONG_ID}':
+          id      => '#{PUPPETLABS_EXP_KEY_LONG_ID}',
+          ensure  => 'present',
+          content => '-----BEGIN PGP PUBLIC KEY BLOCK-----
+  Version: GnuPG v1
+
+  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
+  fBt3Tu1IYpw=
+  =cXcR
+  -----END PGP PUBLIC KEY BLOCK-----'
+          }
+  MANIFEST
+
+gpg_key_pp = <<-MANIFEST
+          apt_key { 'puppetlabs':
+            id      => '#{PUPPETLABS_GPG_KEY_FINGERPRINT}',
+            ensure  => 'present',
+            content => "-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+  mQINBFe2Iz4BEADqbv/nWmR26bsivTDOLqrfBEvRu9kSfDMzYh9Bmik1A8Z036Eg
+  h5+TZD8Rrd5TErLQ6eZFmQXk9yKFoa9/C4aBjmsL/u0yeMmVb7/66i+x3eAYGLzV
+  FyunArjtefZyxq0B2mdRHE8kwl5XGl8015T5RGHCTEhpX14O9yigI7gtliRoZcl3
+  hfXtedcvweOf9VrV+t5LF4PrZejom8VcB5CE2pdQ+23KZD48+Cx/sHSLHDtahOTQ
+  5HgwOLK7rBll8djFgIqP/UvhOqnZGIsg4MzTvWd/vwanocfY8BPwwodpX6rPUrD2
+  aXPsaPeM3Q0juDnJT03c4i0jwCoYPg865sqBBrpOQyefxWD6UzGKYkZbaKeobrTB
+  xUKUlaz5agSK12j4N+cqVuZUBAWcokXLRrcftt55B8jz/Mwhx8kl6Qtrnzco9tBG
+  T5JN5vXMkETDjN/TqfB0D0OsLTYOp3jj4hpMpG377Q+6D71YuwfAsikfnpUtEBxe
+  NixXuKAIqrgG8trfODV+yYYWzfdM2vuuYiZW9pGAdm8ao+JalDZss3HL7oVYXSJp
+  MIjjhi78beuNflkdL76ACy81t2TvpxoPoUIG098kW3xd720oqQkyWJTgM+wV96bD
+  ycmRgNQpvqHYKWtZIyZCTzKzTTIdqg/sbE/D8cHGmoy0eHUDshcE0EtxsQARAQAB
+  tEhQdXBwZXQsIEluYy4gUmVsZWFzZSBLZXkgKFB1cHBldCwgSW5jLiBSZWxlYXNl
+  IEtleSkgPHJlbGVhc2VAcHVwcGV0LmNvbT6JAj4EEwECACgFAle2Iz4CGwMFCQlm
+  AYAGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEH9DgoDvjTSfIN0P/jcCRzK8
+  WIdhcNz5dkj7xRZb8Oft2yDfenQmzb1SwGGa96IwJFcjF4Nq7ymcDUqunS2DEDb2
+  gCucsqmW1ubkaggsYbc9voz/SQwhsQpBjfWbuyOX9DWmW6av/aB1F85wP79gyfqT
+  uidTGxQE6EhDbLe7tuvxOHfM1bKsUtI+0n9TALLLHfXUEdtaXCwMlJuO1IIn1PWa
+  H7HzyEjw6OW/cy73oM9nuErBIio1O60slPLOW2XNhdWZJCRWkcXyuumRjoepz7WN
+  1JgsLOTcB7rcQaBP3pDN0O/Om5dlDQ6oYitoJs/F0gfEgwK68Uy8k8sUR+FLLJqM
+  o0CwOg6CeWU4ShAEd1xZxVYW6VOOKlz9x9dvjIVDn2SlTBDmLS99ySlQS57rjGPf
+  GwlRUnuZP4OeSuoFNNJNb9PO6XFSP66eNHFbEpIoBU7phBzwWpTXNsW+kAcY8Rno
+  8GzKR/2FRsxe5Nhfh8xy88U7BA0tqxWdqpk/ym+wDcgHBfSRt0dPFnbaHAiMRlgX
+  J/NPHBQtkoEdQTKA+ICxcNTUMvsPDQgZcU1/ViLMN+6kZaGNDVcPeMgDvqxu0e/T
+  b3uYiId38HYbHmD6rDrOQL/2VPPXbdGbxDGQUgX1DfdOuFXw1hSTilwI1KdXxUXD
+  sCsZbchgliqGcI1l2En62+6pI2x5XQqqiJ7+uQINBFe2Iz4BEADzbs8WhdBxBa0t
+  JBl4Vz0brDgU3YDqNkqnra/T17kVPI7s27VEhoHERmZJ17pKqb2pElpr9mN/FzuN
+  0N9wvUaumd9gxzsOCam7DPTmuSIvwysk391mjCJkboo01bhuVXe2FBkgOPFzAJEH
+  YFPxmu7tWOmCxNYiuuYtxLywU7lC/Zp6CZuq57xJqUWK47I5wDK9/iigkwSb3nDs
+  6A2LpkDmCr+rcOwLh5bxDSei7vYW+3TNOkPlC/h6fO9dPeC9AfyW6qPdVFQq1mpZ
+  Zcj1ALz7zFiciIB4NrD3PTjDlRnaJCWKPafVSsMbyIWmQaJ01ifuE0Owianrau8c
+  I264VXmI5pA9C8k9f2aVBuJiLsXaLEb03CzFWz9JpBLttA9ccaam3feU2EmnC3sb
+  9xD+Ibkxq5mKFN3lEzUAAIqbI1QYGZXPgLxMY7JSvoUxAqeHwpf/dO2LIUqYUpx0
+  bF/GWRV9Uql8omNQbhwP0p2X/0Gfxj9Abg2IJM8LeOu3Xk0HACwwyVXgxcgk5FO+
+  +KZpTN3iynjmbIzB9qcd9TeSzjVh/RDPSdn5K6Ao5ynubGYmaPwCk+DdVBRDlgWo
+  7yNIF4N9rFuSMAEJxA1nS5TYFgIN9oDF3/GHngVGfFCv4EG3yS08Hk1tDV0biKdK
+  ypcx402TAwVRWP5Pzmxc6/ZXU4ZhZQARAQABiQIlBBgBAgAPBQJXtiM+AhsMBQkJ
+  ZgGAAAoJEH9DgoDvjTSfbWYQALwafIQK9avVNIuhMsyYPa/yHf6rUOLqrYO1GCmj
+  vyG4cYmryzdxyfcXEmuE5QAIbEKSISrcO6Nvjt9PwLCjR/dUvco0f0YFTPv+kamn
+  +Bwp2Zt6d3MenXC6mLXPHR4OqFjzCpUT8kFwycvGPsuqZQ/CO0qzLDmAGTY+4ly3
+  9aQEsQyFhV3P+6SWnaC2TldWpfG/2pCSaSa8dbYbRe3SUNKXwT8kw3WoQYNofF6n
+  or8oFVA+UIVlvHc5h7L3tfFylRy5CwtR5rBQtoBicRVxEQc7ARNmB1XWuPntMQl/
+  N1Fcfc+KSILFblAR6eVv+6BhMvRqzxqe81AEAP+oKVVwJ7H+wTQun2UKAgZATDWP
+  /LQsYinmLADpraDPqxT2WJe8kjszMDQZCK+jhsVrhZdkiw9EHAM0z7BKz6JERmLu
+  TIEcickkTfzbJWXZgv40Bvl99yPMswnR1lQHD7TKxyHYrI7dzJQri4mbORg4lOnZ
+  3Tyodv21Ocf4as2No1p6esZW+M46zjZeO8zzExmmENI2+P7/VUt+LWyQFiqRM0iW
+  zGioYMWgVePywFGaTV51/0uF9ymHHC7BDIcLgUWHdg/1B67jR5YQfzPJUqLhnylt
+  1sjDRQIlf+3U+ddvre2YxX/rYUI2gBT32QzQrv016KsiZO+N+Iya3B4D68s6xxQS
+  3xJn
+  =mMjt
+  -----END PGP PUBLIC KEY BLOCK-----",
+            }
+  MANIFEST
+
+multiple_keys_pp = <<-MANIFEST
+          apt_key { 'puppetlabs':
+            id      => '#{PUPPETLABS_GPG_KEY_FINGERPRINT}',
+            ensure  => 'present',
+            content => "-----BEGIN PGP PUBLIC KEY BLOCK-----
+  Version: GnuPG v1
+
+  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
+  FPfZDNCu/TXoqyJk7434jJrcHgPryzrHFBLfEmc=
+  =TREp
+  -----END PGP PUBLIC KEY BLOCK----- ",
+            }
+  MANIFEST
+
+bogus_key_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id      => '#{PUPPETLABS_GPG_KEY_LONG_ID}',
+          ensure  => 'present',
+          content => 'For posterity: such content, much bogus, wow',
+        }
+  MANIFEST
+
+hkp_pool_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => '#{PUPPETLABS_GPG_KEY_FINGERPRINT}',
+          ensure => 'present',
+          server => 'hkp://keyserver.ubuntu.com:80',
+        }
+  MANIFEST
+
+hkps_protocol_supported = host_inventory['facter']['os']['family'] =~ %r{Ubuntu}i && \
+                          host_inventory['facter']['os']['release']['major'] =~ %r{^18\.04}
+
+if hkps_protocol_supported
+  hkps_ubuntu_pp = <<-MANIFEST
+          apt_key { 'puppetlabs':
+            id     => '#{PUPPETLABS_GPG_KEY_LONG_ID}',
+            ensure => 'present',
+            server => 'hkps://keyserver.ubuntu.com',
+          }
+    MANIFEST
+end
+
+nonexistant_key_server_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => '#{PUPPETLABS_GPG_KEY_LONG_ID}',
+          ensure => 'present',
+          server => 'nonexistant.key.server',
+        }
+  MANIFEST
+
+dot_server_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => '#{PUPPETLABS_GPG_KEY_LONG_ID}',
+          ensure => 'present',
+          server => '.pgp.key.server',
+        }
+  MANIFEST
+
+http_works_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => '#{PUPPETLABS_GPG_KEY_LONG_ID}',
+          ensure => 'present',
+          source => 'http://#{PUPPETLABS_APT_URL}/#{PUPPETLABS_GPG_KEY_FILE}',
+        }
+  MANIFEST
+
+http_works_userinfo_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => '#{PUPPETLABS_GPG_KEY_LONG_ID}',
+          ensure => 'present',
+          source => 'http://dummyuser:dummypassword@#{PUPPETLABS_APT_URL}/#{PUPPETLABS_GPG_KEY_FILE}',
+        }
+  MANIFEST
+
+four_oh_four_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => '#{PUPPETLABS_GPG_KEY_LONG_ID}',
+          ensure => 'present',
+          source => 'http://#{PUPPETLABS_APT_URL}/herpderp.gpg',
+        }
+  MANIFEST
+
+socket_error_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => '#{PUPPETLABS_GPG_KEY_LONG_ID}',
+          ensure => 'present',
+          source => 'http://apt.puppetlabss.com/herpderp.gpg',
+        }
+  MANIFEST
+
+ftp_works_pp = <<-MANIFEST
+        apt_key { 'CentOS 6':
+          id     => '#{CENTOS_GPG_KEY_LONG_ID}',
+          ensure => 'present',
+          source => 'ftp://#{CENTOS_REPO_URL}/#{CENTOS_GPG_KEY_FILE}',
+        }
+  MANIFEST
+
+ftp_550_pp = <<-MANIFEST
+        apt_key { 'CentOS 6':
+          id     => '#{SHOULD_NEVER_EXIST_ID}',
+          ensure => 'present',
+          source => 'ftp://#{CENTOS_REPO_URL}/herpderp.gpg',
+        }
+  MANIFEST
+
+ftp_socket_error_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => '#{PUPPETLABS_GPG_KEY_LONG_ID}',
+          ensure => 'present',
+          source => 'ftp://apt.puppetlabss.com/herpderp.gpg',
+        }
+  MANIFEST
+
+https_works_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => '#{PUPPETLABS_GPG_KEY_LONG_ID}',
+          ensure => 'present',
+          source => 'https://#{PUPPETLABS_APT_URL}/#{PUPPETLABS_GPG_KEY_FILE}',
+        }
+  MANIFEST
+
+https_with_weak_ssl_works_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => '#{PUPPETLABS_GPG_KEY_LONG_ID}',
+          ensure => 'present',
+          source => 'https://#{PUPPETLABS_APT_URL}/#{PUPPETLABS_GPG_KEY_FILE}',
+          weak_ssl => true,
+        }
+  MANIFEST
+
+https_userinfo_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => '#{PUPPETLABS_GPG_KEY_LONG_ID}',
+          ensure => 'present',
+          source => 'https://dummyuser:dummypassword@#{PUPPETLABS_APT_URL}/#{PUPPETLABS_GPG_KEY_FILE}',
+        }
+  MANIFEST
+
+https_404_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => '#{SHOULD_NEVER_EXIST_ID}',
+          ensure => 'present',
+          source => 'https://#{PUPPETLABS_APT_URL}/herpderp.gpg',
+        }
+  MANIFEST
+
+https_socket_error_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => '#{SHOULD_NEVER_EXIST_ID}',
+          ensure => 'present',
+          source => 'https://apt.puppetlabss.com/herpderp.gpg',
+        }
+  MANIFEST
+
+path_exists_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => 'EF8D349F',
+          ensure => 'present',
+          source => '/tmp/puppetlabs-pubkey.gpg',
+        }
+  MANIFEST
+
+path_does_not_exist_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => '#{PUPPETLABS_GPG_KEY_LONG_ID}',
+          ensure => 'present',
+          source => '/tmp/totally_bogus.file',
+        }
+  MANIFEST
+
+path_bogus_content_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id     => '#{PUPPETLABS_GPG_KEY_LONG_ID}',
+          ensure => 'present',
+          source => '/tmp/fake-key.gpg',
+        }
+  MANIFEST
+
+debug_works_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id      => '#{PUPPETLABS_GPG_KEY_LONG_ID}',
+          ensure  => 'present',
+          options => 'debug',
+        }
+  MANIFEST
+
+fingerprint_match_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id      => '#{PUPPETLABS_GPG_KEY_FINGERPRINT}',
+          ensure  => 'present',
+          source  => 'https://#{PUPPETLABS_APT_URL}/#{PUPPETLABS_GPG_KEY_FILE}',
+        }
+  MANIFEST
+
+fingerprint_does_not_match_pp = <<-MANIFEST
+        apt_key { 'puppetlabs':
+          id      => '6F6B15509CF8E59E6E469F327F438280EF8D9999',
+          ensure  => 'present',
+          source  => 'https://#{PUPPETLABS_APT_URL}/#{PUPPETLABS_GPG_KEY_FILE}',
+        }
+  MANIFEST
+
+refresh_true_pp = <<-MANIFEST
+        apt_key { '#{PUPPETLABS_EXP_KEY_LONG_ID}':
+          id      => '#{PUPPETLABS_EXP_KEY_LONG_ID}',
+          ensure  => 'present',
+          refresh => true,
+        }
+  MANIFEST
+
+refresh_false_pp = <<-MANIFEST
+        apt_key { '#{PUPPETLABS_EXP_KEY_LONG_ID}':
+          id      => '#{PUPPETLABS_EXP_KEY_LONG_ID}',
+          ensure  => 'present',
+          refresh => false,
+        }
+MANIFEST
+
+refresh_del_key_pp = <<-MANIFEST
+        apt_key { '#{PUPPETLABS_EXP_KEY_LONG_ID}':
+          ensure  => 'absent',
+        }
+MANIFEST
+
+refresh_check_for_dirmngr_pp = <<-MANIFEST
+        package { 'dirmngr':
+          ensure  => 'present',
+        }
+MANIFEST
+
+describe 'apt_key' do
+  before(:each) do
+    # Delete twice to make sure everything is cleaned
+    # up after the short key collision
+    run_shell("apt-key del #{PUPPETLABS_GPG_KEY_SHORT_ID}", expect_failures: true)
+    run_shell("apt-key del #{PUPPETLABS_GPG_KEY_SHORT_ID}", expect_failures: true)
+  end
+
+  describe 'ensure =>' do
+    ensure_present_pp = <<-MANIFEST
+            apt_key { 'centos':
+              id     => '#{CENTOS_GPG_KEY_LONG_ID}',
+              ensure => 'present',
+            }
+      MANIFEST
+
+    ensure_absent_pp = <<-MANIFEST
+            apt_key { 'centos':
+              id     => '#{CENTOS_GPG_KEY_LONG_ID}',
+              ensure => 'absent',
+            }
+      MANIFEST
+
+    it 'add an apt_key resource' do
+      apply_manifest_twice(ensure_present_pp)
+    end
+    it 'remove the apt_key resource' do
+      apply_manifest_twice(ensure_absent_pp)
+    end
+  end
+
+  describe 'content =>' do
+    context 'with puppetlabs gpg key' do
+      it 'works' do
+        # Apply the manifest (Retry if timeout error is received from key pool)
+        retry_on_error_matching do
+          apply_manifest(gpg_key_pp, catch_failures: true)
+        end
+
+        apply_manifest(gpg_key_pp, catch_changes: true)
+        run_shell(PUPPETLABS_KEY_CHECK_COMMAND)
+      end
+    end
+
+    context 'with multiple keys' do
+      it 'runs without errors' do
+        apply_manifest_twice(multiple_keys_pp)
+        run_shell(PUPPETLABS_KEY_CHECK_COMMAND)
+      end
+    end
+
+    context 'with bogus key' do
+      it 'fails' do
+        apply_manifest(bogus_key_pp, expect_failures: true) do |r|
+          expect(r.stderr).to match(%r{no valid OpenPGP data found})
+        end
+      end
+    end
+  end
+
+  describe 'server =>' do
+    context 'with hkp://pgp.mit.edu:80' do
+      it 'works' do
+        retry_on_error_matching do
+          apply_manifest(hkp_pool_pp, catch_failures: true)
+        end
+
+        apply_manifest(hkp_pool_pp, catch_changes: true)
+        run_shell(PUPPETLABS_KEY_CHECK_COMMAND)
+      end
+    end
+
+    if hkps_protocol_supported
+      context 'with hkps://keyserver.ubuntu.com' do
+        it 'works' do
+          retry_on_error_matching do
+            apply_manifest(hkps_ubuntu_pp, catch_failures: true)
+          end
+
+          apply_manifest(hkps_ubuntu_pp, catch_changes: true)
+          run_shell(PUPPETLABS_KEY_CHECK_COMMAND)
+        end
+      end
+    end
+
+    context 'with nonexistant.key.server' do
+      it 'fails' do
+        apply_manifest(nonexistant_key_server_pp, expect_failures: true) do |r|
+          expect(r.stderr).to match(%r{(Host not found|Couldn't resolve host|No name)})
+        end
+      end
+    end
+
+    context 'with key server start with dot' do
+      it 'fails' do
+        apply_manifest(dot_server_pp, expect_failures: true) do |r|
+          expect(r.stderr).to match(%r{Invalid value ".pgp.key.server"})
+        end
+      end
+    end
+  end
+
+  describe 'source =>' do
+    context 'with http://' do
+      it 'works' do
+        apply_manifest_twice(http_works_pp)
+        run_shell(PUPPETLABS_KEY_CHECK_COMMAND)
+      end
+
+      it 'works with userinfo' do
+        apply_manifest_twice(http_works_userinfo_pp)
+        run_shell(PUPPETLABS_KEY_CHECK_COMMAND)
+      end
+
+      it 'fails with a 404' do
+        apply_manifest(four_oh_four_pp, expect_failures: true) do |r|
+          expect(r.stderr).to match(%r{404 Not Found})
+        end
+      end
+
+      it 'fails with a socket error' do
+        apply_manifest(socket_error_pp, expect_failures: true) do |r|
+          expect(r.stderr).to match(%r{could not resolve})
+        end
+      end
+    end
+
+    # disabled when running in travis, security issues prevent FTP
+    context 'with ftp://', unless: (ENV['TRAVIS'] == 'true') do
+      before(:each) do
+        run_shell("apt-key del #{CENTOS_GPG_KEY_LONG_ID}", expect_failures: true)
+      end
+
+      it 'works' do
+        apply_manifest_twice(ftp_works_pp)
+        run_shell(CENTOS_KEY_CHECK_COMMAND)
+      end
+
+      it 'fails with a 550' do
+        apply_manifest(ftp_550_pp, expect_failures: true) do |r|
+          expect(r.stderr).to match(%r{550 Failed to open})
+        end
+      end
+
+      it 'fails with a socket error' do
+        apply_manifest(ftp_socket_error_pp, expect_failures: true) do |r|
+          expect(r.stderr).to match(%r{could not resolve})
+        end
+      end
+    end
+
+    context 'with https://' do
+      it 'works' do
+        apply_manifest_twice(https_works_pp)
+        run_shell(PUPPETLABS_KEY_CHECK_COMMAND)
+      end
+
+      it 'works with weak ssl' do
+        apply_manifest_twice(https_with_weak_ssl_works_pp)
+        run_shell(PUPPETLABS_KEY_CHECK_COMMAND)
+      end
+
+      it 'works with userinfo' do
+        apply_manifest_twice(https_userinfo_pp)
+        run_shell(PUPPETLABS_KEY_CHECK_COMMAND)
+      end
+
+      it 'fails with a 404' do
+        apply_manifest(https_404_pp, expect_failures: true) do |r|
+          expect(r.stderr).to match(%r{404 Not Found})
+        end
+      end
+
+      it 'fails with a socket error' do
+        apply_manifest(https_socket_error_pp, expect_failures: true) do |r|
+          expect(r.stderr).to match(%r{could not resolve})
+        end
+      end
+    end
+
+    context 'with /path/that/exists' do
+      before(:each) do
+        run_shell("curl -o /tmp/puppetlabs-pubkey.gpg \
+              http://#{PUPPETLABS_APT_URL}/#{PUPPETLABS_GPG_KEY_FILE}")
+      end
+
+      after(:each) do
+        run_shell('rm /tmp/puppetlabs-pubkey.gpg')
+      end
+
+      it 'works' do
+        apply_manifest_twice(path_exists_pp)
+        run_shell(PUPPETLABS_KEY_CHECK_COMMAND)
+      end
+    end
+
+    context 'with /path/that/does/not/exist' do
+      it 'fails' do
+        apply_manifest(path_does_not_exist_pp, expect_failures: true) do |r|
+          expect(r.stderr).to match(%r{does not exist})
+        end
+      end
+    end
+
+    context 'with /path/that/exists/with/bogus/content' do
+      before(:each) do
+        run_shell('echo "here be dragons" > /tmp/fake-key.gpg')
+      end
+
+      after(:each) do
+        run_shell('rm /tmp/fake-key.gpg')
+      end
+      it 'fails' do
+        apply_manifest(path_bogus_content_pp, expect_failures: true) do |r|
+          expect(r.stderr).to match(%r{no valid OpenPGP data found})
+        end
+      end
+    end
+  end
+
+  describe 'options =>' do
+    context 'with debug' do
+      it 'works' do
+        apply_manifest_twice(debug_works_pp)
+        run_shell(PUPPETLABS_KEY_CHECK_COMMAND)
+      end
+    end
+  end
+
+  describe 'fingerprint validation against source/content' do
+    context 'with fingerprint in id matches fingerprint from remote key' do
+      it 'works' do
+        apply_manifest_twice(fingerprint_match_pp)
+      end
+    end
+
+    context 'with fingerprint in id does NOT match fingerprint from remote key' do
+      it 'works' do
+        apply_manifest(fingerprint_does_not_match_pp, expect_failures: true) do |r|
+          expect(r.stderr).to match(%r{don't match})
+        end
+      end
+    end
+  end
+
+  describe 'refresh' do
+    # Ensure dirmngr package is installed
+    apply_manifest(refresh_check_for_dirmngr_pp, acceptable_exit_codes: [0, 2])
+
+    before(:each) do
+      # Delete the Puppet Labs Release Key and install an expired version of the key
+      apply_manifest(refresh_del_key_pp)
+      apply_manifest(refresh_pp, catch_failures: true)
+    end
+    context 'when refresh => true' do
+      it 'updates an expired key' do
+        apply_manifest(refresh_true_pp)
+        # Check key has been updated to new version
+        run_shell(PUPPETLABS_EXP_CHECK_COMMAND.to_s)
+      end
+    end
+    context 'when refresh => false' do
+      it 'does not replace an expired key' do
+        apply_manifest(refresh_false_pp)
+        # Expired key is present and has not been updated by the new version
+        run_shell(PUPPETLABS_EXP_CHECK_COMMAND.to_s, expect_failures: true)
+      end
+    end
+  end
+end
diff --git a/spec/acceptance/apt_spec.rb b/spec/acceptance/apt_spec.rb
new file mode 100644
index 0000000..7e4fe37
--- /dev/null
+++ b/spec/acceptance/apt_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper_acceptance'
+
+everything_everything_pp = <<-MANIFEST
+      $sources = {
+        'puppetlabs' => {
+          'ensure'   => present,
+          'location' => 'http://apt.puppetlabs.com',
+          'repos'    => 'main',
+          'key'      => {
+            'id'     => '6F6B15509CF8E59E6E469F327F438280EF8D349F',
+            'server' => 'keyserver.ubuntu.com',
+          },
+        },
+      }
+      class { 'apt':
+        update => {
+          'frequency' => 'always',
+          'timeout'   => 400,
+          'tries'     => 3,
+        },
+        purge => {
+          'sources.list'   => true,
+          'sources.list.d' => true,
+          'preferences'    => true,
+          'preferences.d'  => true,
+          'apt.conf.d'     => true,
+        },
+        sources => $sources,
+      }
+  MANIFEST
+
+describe 'apt class' do
+  context 'with reset' do
+    it 'fixes the sources.list' do
+      run_shell('cp /etc/apt/sources.list /tmp')
+    end
+  end
+
+  context 'with all the things' do
+    it 'works with no errors' do
+      # Apply the manifest (Retry if timeout error is received from key pool)
+      retry_on_error_matching do
+        apply_manifest(everything_everything_pp, catch_failures: true)
+      end
+    end
+    it 'stills work' do
+      run_shell('apt-get update')
+      run_shell('apt-get -y --force-yes upgrade')
+    end
+  end
+
+  context 'with reset' do
+    it 'fixes the sources.list' do
+      run_shell('cp /tmp/sources.list /etc/apt')
+    end
+  end
+end
diff --git a/spec/acceptance/init_task_spec.rb b/spec/acceptance/init_task_spec.rb
new file mode 100644
index 0000000..02b3f34
--- /dev/null
+++ b/spec/acceptance/init_task_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+# run a test task
+require 'spec_helper_acceptance'
+
+describe 'apt tasks' do
+  describe 'update' do
+    it 'updates package lists' do
+      result = run_bolt_task('apt', 'action' => 'update')
+      expect(result.stdout).to contain(%r{Reading package lists})
+    end
+  end
+  describe 'upgrade' do
+    it 'upgrades packages' do
+      result = run_bolt_task('apt', 'action' => 'upgrade')
+      expect(result.stdout).to contain(%r{\d+ upgraded, \d+ newly installed, \d+ to remove and \d+ not upgraded})
+    end
+  end
+  describe 'dist-upgrade' do
+    it 'dist-upgrades packages' do
+      result = run_bolt_task('apt', 'action' => 'dist-upgrade')
+      expect(result.stdout).to contain(%r{\d+ upgraded, \d+ newly installed, \d+ to remove and \d+ not upgraded})
+    end
+  end
+  describe 'autoremove' do
+    it 'autoremoves obsolete packages' do
+      result = run_bolt_task('apt', 'action' => 'autoremove')
+      expect(result.stdout).to contain(%r{\d+ upgraded, \d+ newly installed, \d+ to remove and \d+ not upgraded})
+    end
+  end
+end
diff --git a/spec/classes/apt_backports_spec.rb b/spec/classes/apt_backports_spec.rb
new file mode 100644
index 0000000..6f6d530
--- /dev/null
+++ b/spec/classes/apt_backports_spec.rb
@@ -0,0 +1,292 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt::backports', type: :class do
+  let(:pre_condition) { "class{ '::apt': }" }
+
+  describe 'debian/ubuntu tests' do
+    context 'with defaults on deb' do
+      let(:facts) do
+        {
+          os: {
+            family: 'Debian',
+            name: 'Debian',
+            release: {
+              major: '9',
+              full: '9.0',
+            },
+            distro: {
+              codename: 'stretch',
+              id: 'Debian',
+            },
+          },
+        }
+      end
+
+      it {
+        is_expected.to contain_apt__source('backports').with(location: 'http://deb.debian.org/debian',
+                                                             repos: 'main contrib non-free',
+                                                             release: 'stretch-backports',
+                                                             pin: { 'priority' => 200, 'release' => 'stretch-backports' })
+      }
+    end
+    context 'with defaults on ubuntu' do
+      let(:facts) do
+        {
+          os: {
+            family: 'Debian',
+            name: 'Ubuntu',
+            release: {
+              major: '18',
+              full: '18.04',
+            },
+            distro: {
+              codename: 'bionac',
+              id: 'Ubuntu',
+            },
+          },
+        }
+      end
+
+      it {
+        is_expected.to contain_apt__source('backports').with(location: 'http://archive.ubuntu.com/ubuntu',
+                                                             key: '630239CC130E1A7FD81A27B140976EAF437D05B5',
+                                                             repos: 'main universe multiverse restricted',
+                                                             release: 'bionac-backports',
+                                                             pin: { 'priority' => 200, 'release' => 'bionac-backports' })
+      }
+    end
+    context 'with everything set' do
+      let(:facts) do
+        {
+          os: {
+            family: 'Debian',
+            name: 'Ubuntu',
+            release: {
+              major: '18',
+              full: '18.04',
+            },
+            distro: {
+              codename: 'bionac',
+              id: 'Ubuntu',
+            },
+          },
+        }
+      end
+      let(:params) do
+        {
+          location: 'http://archive.ubuntu.com/ubuntu-test',
+          release: 'vivid',
+          repos: 'main',
+          key: 'A1BD8E9D78F7FE5C3E65D8AF8B48AD6246925553',
+          pin: '90',
+        }
+      end
+
+      it {
+        is_expected.to contain_apt__source('backports').with(location: 'http://archive.ubuntu.com/ubuntu-test',
+                                                             key: 'A1BD8E9D78F7FE5C3E65D8AF8B48AD6246925553',
+                                                             repos: 'main',
+                                                             release: 'vivid',
+                                                             pin: { 'priority' => 90, 'release' => 'vivid' })
+      }
+    end
+    context 'when set things with hashes' do
+      let(:facts) do
+        {
+          os: {
+            family: 'Debian',
+            name: 'Ubuntu',
+            release: {
+              major: '18',
+              full: '18.04',
+            },
+            distro: {
+              codename: 'bionac',
+              id: 'Ubuntu',
+            },
+          },
+        }
+      end
+      let(:params) do
+        {
+          key: {
+            'id' => 'A1BD8E9D78F7FE5C3E65D8AF8B48AD6246925553',
+          },
+          pin: {
+            'priority' => '90',
+          },
+        }
+      end
+
+      it {
+        is_expected.to contain_apt__source('backports').with(key: { 'id' => 'A1BD8E9D78F7FE5C3E65D8AF8B48AD6246925553' },
+                                                             pin: { 'priority' => '90' })
+      }
+    end
+  end
+  describe 'mint tests' do
+    let(:facts) do
+      {
+        os: {
+          family: 'Debian',
+          name: 'LinuxMint',
+          release: {
+            major: '17',
+            full: '17',
+          },
+          distro: {
+            codename: 'qiana',
+            id: 'LinuxMint',
+          },
+        },
+      }
+    end
+
+    context 'with all the needed things set' do
+      let(:params) do
+        {
+          location: 'http://archive.ubuntu.com/ubuntu',
+          release: 'trusty-backports',
+          repos: 'main universe multiverse restricted',
+          key: '630239CC130E1A7FD81A27B140976EAF437D05B5',
+        }
+      end
+
+      it {
+        is_expected.to contain_apt__source('backports').with(location: 'http://archive.ubuntu.com/ubuntu',
+                                                             key: '630239CC130E1A7FD81A27B140976EAF437D05B5',
+                                                             repos: 'main universe multiverse restricted',
+                                                             release: 'trusty-backports',
+                                                             pin: { 'priority' => 200, 'release' => 'trusty-backports' })
+      }
+    end
+    context 'with missing location' do
+      let(:params) do
+        {
+          release: 'trusty-backports',
+          repos: 'main universe multiverse restricted',
+          key: '630239CC130E1A7FD81A27B140976EAF437D05B5',
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{If not on Debian or Ubuntu, you must explicitly pass location, release, repos, and key})
+      end
+    end
+    context 'with missing release' do
+      let(:params) do
+        {
+          location: 'http://archive.ubuntu.com/ubuntu',
+          repos: 'main universe multiverse restricted',
+          key: '630239CC130E1A7FD81A27B140976EAF437D05B5',
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{If not on Debian or Ubuntu, you must explicitly pass location, release, repos, and key})
+      end
+    end
+    context 'with missing repos' do
+      let(:params) do
+        {
+          location: 'http://archive.ubuntu.com/ubuntu',
+          release: 'trusty-backports',
+          key: '630239CC130E1A7FD81A27B140976EAF437D05B5',
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{If not on Debian or Ubuntu, you must explicitly pass location, release, repos, and key})
+      end
+    end
+    context 'with missing key' do
+      let(:params) do
+        {
+          location: 'http://archive.ubuntu.com/ubuntu',
+          release: 'trusty-backports',
+          repos: 'main universe multiverse restricted',
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{If not on Debian or Ubuntu, you must explicitly pass location, release, repos, and key})
+      end
+    end
+  end
+  describe 'validation' do
+    let(:facts) do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'bionac',
+            id: 'Ubuntu',
+          },
+        },
+      }
+    end
+
+    context 'with invalid location' do
+      let(:params) do
+        {
+          location: true,
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{expects a})
+      end
+    end
+    context 'with invalid release' do
+      let(:params) do
+        {
+          release: true,
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{expects a})
+      end
+    end
+    context 'with invalid repos' do
+      let(:params) do
+        {
+          repos: true,
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{expects a})
+      end
+    end
+    context 'with invalid key' do
+      let(:params) do
+        {
+          key: true,
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{expects a})
+      end
+    end
+    context 'with invalid pin' do
+      let(:params) do
+        {
+          pin: true,
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{expects a})
+      end
+    end
+  end
+end
diff --git a/spec/classes/apt_spec.rb b/spec/classes/apt_spec.rb
new file mode 100644
index 0000000..1a7513c
--- /dev/null
+++ b/spec/classes/apt_spec.rb
@@ -0,0 +1,713 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+sources_list = {  ensure: 'file',
+                  path: '/etc/apt/sources.list',
+                  owner: 'root',
+                  group: 'root',
+                  notify: 'Class[Apt::Update]' }
+
+sources_list_d = { ensure: 'directory',
+                   path: '/etc/apt/sources.list.d',
+                   owner: 'root',
+                   group: 'root',
+                   purge: false,
+                   recurse: false,
+                   notify: 'Class[Apt::Update]' }
+
+preferences = { ensure: 'file',
+                path: '/etc/apt/preferences',
+                owner: 'root',
+                group: 'root',
+                notify: 'Class[Apt::Update]' }
+
+preferences_d = { ensure: 'directory',
+                  path: '/etc/apt/preferences.d',
+                  owner: 'root',
+                  group: 'root',
+                  purge: false,
+                  recurse: false,
+                  notify: 'Class[Apt::Update]' }
+
+apt_conf_d = {    ensure: 'directory',
+                  path: '/etc/apt/apt.conf.d',
+                  owner: 'root',
+                  group: 'root',
+                  purge: false,
+                  recurse: false,
+                  notify: 'Class[Apt::Update]' }
+
+describe 'apt' do
+  let(:facts) do
+    {
+      os: {
+        family: 'Debian',
+        name: 'Debian',
+        release: {
+          major: '9',
+          full: '9.0',
+        },
+        distro: {
+          codename: 'stretch',
+          id: 'Debian',
+        },
+      },
+    }
+  end
+
+  context 'with defaults' do
+    it {
+      is_expected.to contain_file('sources.list').that_notifies('Class[Apt::Update]').only_with(sources_list)
+    }
+
+    it {
+      is_expected.to contain_file('sources.list.d').that_notifies('Class[Apt::Update]').only_with(sources_list_d)
+    }
+
+    it {
+      is_expected.to contain_file('preferences').that_notifies('Class[Apt::Update]').only_with(preferences)
+    }
+
+    it {
+      is_expected.to contain_file('preferences.d').that_notifies('Class[Apt::Update]').only_with(preferences_d)
+    }
+
+    it {
+      is_expected.to contain_file('apt.conf.d').that_notifies('Class[Apt::Update]').only_with(apt_conf_d)
+    }
+
+    it { is_expected.to contain_file('/etc/apt/auth.conf').with_ensure('absent') }
+
+    it 'lays down /etc/apt/apt.conf.d/15update-stamp' do
+      is_expected.to contain_file('/etc/apt/apt.conf.d/15update-stamp').with(group: 'root',
+                                                                             owner: 'root').with_content(
+                                                                               %r{APT::Update::Post-Invoke-Success {"touch /var/lib/apt/periodic/update-success-stamp 2>/dev/null || true";};},
+                                                                             )
+    end
+
+    it {
+      is_expected.to contain_exec('apt_update').with(refreshonly: 'true')
+    }
+
+    it { is_expected.not_to contain_apt__setting('conf-proxy') }
+  end
+
+  describe 'proxy=' do
+    context 'when host=localhost' do
+      let(:params) { { proxy: { 'host' => 'localhost' } } }
+
+      it {
+        is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
+          %r{Acquire::http::proxy "http://localhost:8080/";},
+        ).without_content(
+          %r{Acquire::https::proxy },
+        )
+      }
+    end
+
+    context 'when host=localhost and per-host[proxyscope]=proxyhost' do
+      let(:params) { { proxy: { 'host' => 'localhost', 'perhost' => [{ 'scope' => 'proxyscope', 'host' => 'proxyhost' }] } } }
+
+      it {
+        is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
+          %r{Acquire::http::proxy::proxyscope "http://proxyhost:8080/";},
+        )
+      }
+    end
+
+    context 'when host=localhost and per-host[proxyscope]=proxyhost:8081' do
+      let(:params) { { proxy: { 'host' => 'localhost', 'perhost' => [{ 'scope' => 'proxyscope', 'host' => 'proxyhost', 'port' => 8081 }] } } }
+
+      it {
+        is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
+          %r{Acquire::http::proxy::proxyscope "http://proxyhost:8081/";},
+        )
+      }
+    end
+
+    context 'when host=localhost and per-host[proxyscope]=[https]proxyhost' do
+      let(:params) { { proxy: { 'host' => 'localhost', 'perhost' => [{ 'scope' => 'proxyscope', 'host' => 'proxyhost', 'https' => true }] } } }
+
+      it {
+        is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
+          %r{Acquire::https::proxy::proxyscope "https://proxyhost:8080/";},
+        )
+      }
+    end
+
+    context 'when host=localhost and per-host[proxyscope]=[direct]' do
+      let(:params) { { proxy: { 'host' => 'localhost', 'perhost' => [{ 'scope' => 'proxyscope', 'direct' => true }] } } }
+
+      it {
+        is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
+          %r{Acquire::http::proxy::proxyscope "DIRECT";},
+        )
+      }
+    end
+
+    context 'when host=localhost and per-host[proxyscope]=[https][direct]' do
+      let(:params) { { proxy: { 'host' => 'localhost', 'perhost' => [{ 'scope' => 'proxyscope', 'https' => true, 'direct' => true }] } } }
+
+      it {
+        is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
+          %r{Acquire::https::proxy::proxyscope "DIRECT";},
+        )
+      }
+    end
+
+    context 'when host=localhost and per-host[proxyscope]=proxyhost and per-host[proxyscope2]=proxyhost2' do
+      let(:params) { { proxy: { 'host' => 'localhost', 'perhost' => [{ 'scope' => 'proxyscope', 'host' => 'proxyhost' }, { 'scope' => 'proxyscope2', 'host' => 'proxyhost2' }] } } }
+
+      it {
+        is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
+          %r{Acquire::http::proxy::proxyscope "http://proxyhost:8080/";},
+        ).with_content(
+          %r{Acquire::http::proxy::proxyscope2 "http://proxyhost2:8080/";},
+        )
+      }
+    end
+
+    context 'when host=localhost and port=8180' do
+      let(:params) { { proxy: { 'host' => 'localhost', 'port' => 8180 } } }
+
+      it {
+        is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
+          %r{Acquire::http::proxy "http://localhost:8180/";},
+        ).without_content(
+          %r{Acquire::https::proxy },
+        )
+      }
+    end
+
+    context 'when host=localhost and https=true' do
+      let(:params) { { proxy: { 'host' => 'localhost', 'https' => true } } }
+
+      it {
+        is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
+          %r{Acquire::http::proxy "http://localhost:8080/";},
+        ).with_content(
+          %r{Acquire::https::proxy "https://localhost:8080/";},
+        )
+      }
+    end
+
+    context 'when host=localhost and direct=true' do
+      let(:params) { { proxy: { 'host' => 'localhost', 'direct' => true } } }
+
+      it {
+        is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
+          %r{Acquire::http::proxy "http://localhost:8080/";},
+        ).with_content(
+          %r{Acquire::https::proxy "DIRECT";},
+        )
+      }
+    end
+
+    context 'when host=localhost and https=true and direct=true' do
+      let(:params) { { proxy: { 'host' => 'localhost', 'https' => true, 'direct' => true } } }
+
+      it {
+        is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
+          %r{Acquire::http::proxy "http://localhost:8080/";},
+        ).with_content(
+          %r{Acquire::https::proxy "https://localhost:8080/";},
+        )
+      }
+      it {
+        is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
+          %r{Acquire::http::proxy "http://localhost:8080/";},
+        ).without_content(
+          %r{Acquire::https::proxy "DIRECT";},
+        )
+      }
+    end
+
+    context 'when ensure=absent' do
+      let(:params) { { proxy: { 'ensure' => 'absent' } } }
+
+      it {
+        is_expected.to contain_apt__setting('conf-proxy').with(ensure: 'absent',
+                                                               priority: '01')
+      }
+    end
+  end
+  context 'with lots of non-defaults' do
+    let :params do
+      {
+        update: { 'frequency' => 'always', 'timeout' => 1, 'tries' => 3 },
+        purge: { 'sources.list' => false, 'sources.list.d' => false,
+                 'preferences' => false, 'preferences.d' => false,
+                 'apt.conf.d' => false },
+      }
+    end
+
+    it {
+      is_expected.to contain_file('sources.list').with(content: nil)
+    }
+
+    it {
+      is_expected.to contain_file('sources.list.d').with(purge: false,
+                                                         recurse: false)
+    }
+
+    it {
+      is_expected.to contain_file('preferences').with(ensure: 'file')
+    }
+
+    it {
+      is_expected.to contain_file('preferences.d').with(purge: false,
+                                                        recurse: false)
+    }
+
+    it {
+      is_expected.to contain_file('apt.conf.d').with(purge: false,
+                                                     recurse: false)
+    }
+
+    it {
+      is_expected.to contain_exec('apt_update').with(refreshonly: false,
+                                                     timeout: 1,
+                                                     tries: 3)
+    }
+  end
+
+  context 'with lots of non-defaults' do
+    let :params do
+      {
+        update: { 'frequency' => 'always', 'timeout' => 1, 'tries' => 3 },
+        purge: { 'sources.list' => true, 'sources.list.d' => true,
+                 'preferences' => true, 'preferences.d' => true,
+                 'apt.conf.d' => true },
+      }
+    end
+
+    it {
+      is_expected.to contain_file('sources.list').with(content: "# Repos managed by puppet.\n")
+    }
+
+    it {
+      is_expected.to contain_file('sources.list.d').with(purge: true,
+                                                         recurse: true)
+    }
+
+    it {
+      is_expected.to contain_file('preferences').with(ensure: 'absent')
+    }
+
+    it {
+      is_expected.to contain_file('preferences.d').with(purge: true,
+                                                        recurse: true)
+    }
+
+    it {
+      is_expected.to contain_file('apt.conf.d').with(purge: true,
+                                                     recurse: true)
+    }
+
+    it {
+      is_expected.to contain_exec('apt_update').with(refreshonly: false,
+                                                     timeout: 1,
+                                                     tries: 3)
+    }
+  end
+
+  context 'with defaults for sources_list_force' do
+    let :params do
+      {
+        update: { 'frequency' => 'always', 'timeout' => 1, 'tries' => 3 },
+        purge: { 'sources.list' => true },
+        sources_list_force: false,
+      }
+    end
+
+    it {
+      is_expected.to contain_file('sources.list').with(content: "# Repos managed by puppet.\n")
+    }
+  end
+
+  context 'with non defaults for sources_list_force' do
+    let :params do
+      {
+        update: { 'frequency' => 'always', 'timeout' => 1, 'tries' => 3 },
+        purge: { 'sources.list' => true },
+        sources_list_force: true,
+      }
+    end
+
+    it {
+      is_expected.to contain_file('sources.list').with(ensure: 'absent')
+    }
+  end
+
+  context 'with entries for /etc/apt/auth.conf' do
+    facts_hash = {
+      'Ubuntu 18.04' => {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'bionic',
+            id: 'Ubuntu',
+          },
+        },
+      },
+      'Debian 9.0' => {
+        os: {
+          family: 'Debian',
+          name: 'Debian',
+          release: {
+            major: '9',
+            full: '9.0',
+          },
+          distro: {
+            codename: 'stretch',
+            id: 'Debian',
+          },
+        },
+      },
+      'Debian 10.0' => {
+        os: {
+          family: 'Debian',
+          name: 'Debian',
+          release: {
+            major: '10',
+            full: '10.0',
+          },
+          distro: {
+            codename: 'buster',
+            id: 'Debian',
+          },
+        },
+      },
+    }
+
+    facts_hash.each do |os, facts|
+      context "on #{os}" do
+        let(:facts) do
+          facts
+        end
+        let(:params) do
+          {
+            auth_conf_entries: [
+              {
+                machine: 'deb.example.net',
+                login: 'foologin',
+                password: 'secret',
+              },
+              {
+                machine: 'apt.example.com',
+                login: 'aptlogin',
+                password: 'supersecret',
+              },
+            ],
+          }
+        end
+
+        context 'with manage_auth_conf => true' do
+          let(:params) do
+            super().merge(manage_auth_conf: true)
+          end
+
+          auth_conf_content = "// This file is managed by Puppet. DO NOT EDIT.
+machine deb.example.net login foologin password secret
+machine apt.example.com login aptlogin password supersecret
+"
+
+          it {
+            is_expected.to contain_file('/etc/apt/auth.conf').with(ensure: 'present',
+                                                                   owner: '_apt',
+                                                                   group: 'root',
+                                                                   mode: '0600',
+                                                                   notify: 'Class[Apt::Update]',
+                                                                   content: sensitive(auth_conf_content))
+          }
+        end
+
+        context 'with manage_auth_conf => false' do
+          let(:params) do
+            super().merge(manage_auth_conf: false)
+          end
+
+          it {
+            is_expected.not_to contain_file('/etc/apt/auth.conf')
+          }
+        end
+      end
+
+      context 'with improperly specified entries for /etc/apt/auth.conf' do
+        let(:params) do
+          {
+            auth_conf_entries: [
+              {
+                machinn: 'deb.example.net',
+                username: 'foologin',
+                password: 'secret',
+              },
+              {
+                machine: 'apt.example.com',
+                login: 'aptlogin',
+                password: 'supersecret',
+              },
+            ],
+          }
+        end
+
+        it { is_expected.to raise_error(Puppet::Error) }
+      end
+    end
+  end
+
+  context 'with sources defined on valid os.family' do
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'bionic',
+            id: 'Ubuntu',
+          },
+        },
+      }
+    end
+    let(:params) do
+      { sources: {
+        'debian_unstable' => {
+          'location'          => 'http://debian.mirror.iweb.ca/debian/',
+          'release'           => 'unstable',
+          'repos'             => 'main contrib non-free',
+          'key'               => { 'id' => '150C8614919D8446E01E83AF9AA38DCD55BE302B', 'server' => 'subkeys.pgp.net' },
+          'pin'               => '-10',
+          'include'           => { 'src' => true },
+        },
+        'puppetlabs' => {
+          'location' => 'http://apt.puppetlabs.com',
+          'repos'      => 'main',
+          'key'        => { 'id' => '6F6B15509CF8E59E6E469F327F438280EF8D349F', 'server' => 'pgp.mit.edu' },
+        },
+      } }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-debian_unstable').with(ensure: 'present')
+    }
+
+    it { is_expected.to contain_file('/etc/apt/sources.list.d/debian_unstable.list').with_content(%r{^deb http://debian.mirror.iweb.ca/debian/ unstable main contrib non-free$}) }
+    it { is_expected.to contain_file('/etc/apt/sources.list.d/debian_unstable.list').with_content(%r{^deb-src http://debian.mirror.iweb.ca/debian/ unstable main contrib non-free$}) }
+
+    it {
+      is_expected.to contain_apt__setting('list-puppetlabs').with(ensure: 'present')
+    }
+
+    it { is_expected.to contain_file('/etc/apt/sources.list.d/puppetlabs.list').with_content(%r{^deb http://apt.puppetlabs.com bionic main$}) }
+  end
+
+  context 'with confs defined on valid os.family' do
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'bionic',
+            id: 'Ubuntu',
+          },
+        },
+      }
+    end
+    let(:params) do
+      { confs: {
+        'foo' => {
+          'content' => 'foo',
+        },
+        'bar' => {
+          'content' => 'bar',
+        },
+      } }
+    end
+
+    it {
+      is_expected.to contain_apt__conf('foo').with(content: 'foo')
+    }
+
+    it {
+      is_expected.to contain_apt__conf('bar').with(content: 'bar')
+    }
+  end
+
+  context 'with keys defined on valid os.family' do
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'bionic',
+            id: 'Ubuntu',
+          },
+        },
+      }
+    end
+    let(:params) do
+      { keys: {
+        '55BE302B' => {
+          'server' => 'subkeys.pgp.net',
+        },
+        'EF8D349F' => {
+          'server' => 'pgp.mit.edu',
+        },
+      } }
+    end
+
+    it {
+      is_expected.to contain_apt__key('55BE302B').with(server: 'subkeys.pgp.net')
+    }
+
+    it {
+      is_expected.to contain_apt__key('EF8D349F').with(server: 'pgp.mit.edu')
+    }
+  end
+
+  context 'with ppas defined on valid os.family' do
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'bionic',
+            id: 'Ubuntu',
+          },
+        },
+      }
+    end
+    let(:params) do
+      { ppas: {
+        'ppa:drizzle-developers/ppa' => {},
+        'ppa:nginx/stable' => {},
+      } }
+    end
+
+    it { is_expected.to contain_apt__ppa('ppa:drizzle-developers/ppa') }
+    it { is_expected.to contain_apt__ppa('ppa:nginx/stable') }
+  end
+
+  context 'with settings defined on valid os.family' do
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'bionic',
+            id: 'Ubuntu',
+          },
+        },
+      }
+    end
+    let(:params) do
+      { settings: {
+        'conf-banana' => { 'content' => 'banana' },
+        'pref-banana' => { 'content' => 'banana' },
+      } }
+    end
+
+    it { is_expected.to contain_apt__setting('conf-banana') }
+    it { is_expected.to contain_apt__setting('pref-banana') }
+  end
+
+  context 'with pins defined on valid os.family' do
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'bionic',
+            id: 'Ubuntu',
+          },
+        },
+      }
+    end
+    let(:params) do
+      { pins: {
+        'stable' => { 'priority' => 600, 'order' => 50 },
+        'testing' =>  { 'priority' => 700, 'order' => 100 },
+      } }
+    end
+
+    it { is_expected.to contain_apt__pin('stable') }
+    it { is_expected.to contain_apt__pin('testing') }
+  end
+
+  describe 'failing tests' do
+    context "with purge['sources.list']=>'banana'" do
+      let(:params) { { purge: { 'sources.list' => 'banana' } } }
+
+      it do
+        is_expected.to raise_error(Puppet::Error)
+      end
+    end
+
+    context "with purge['sources.list.d']=>'banana'" do
+      let(:params) { { purge: { 'sources.list.d' => 'banana' } } }
+
+      it do
+        is_expected.to raise_error(Puppet::Error)
+      end
+    end
+
+    context "with purge['preferences']=>'banana'" do
+      let(:params) { { purge: { 'preferences' => 'banana' } } }
+
+      it do
+        is_expected.to raise_error(Puppet::Error)
+      end
+    end
+
+    context "with purge['preferences.d']=>'banana'" do
+      let(:params) { { purge: { 'preferences.d' => 'banana' } } }
+
+      it do
+        is_expected.to raise_error(Puppet::Error)
+      end
+    end
+
+    context "with purge['apt.conf.d']=>'banana'" do
+      let(:params) { { purge: { 'apt.conf.d' => 'banana' } } }
+
+      it do
+        is_expected.to raise_error(Puppet::Error)
+      end
+    end
+  end
+end
diff --git a/spec/classes/apt_update_spec.rb b/spec/classes/apt_update_spec.rb
new file mode 100644
index 0000000..7177455
--- /dev/null
+++ b/spec/classes/apt_update_spec.rb
@@ -0,0 +1,233 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt::update', type: :class do
+  context "when apt::update['frequency']='always'" do
+    {
+      'a recent run'                                 => Time.now.to_i,
+      'we are due for a run'                         => 1_406_660_561,
+      'the update-success-stamp file does not exist' => -1,
+    }.each_pair do |desc, factval|
+      context "when $apt_update_last_success indicates #{desc}" do
+        let(:facts) do
+          {
+            os: {
+              family: 'Debian',
+              name: 'Debian',
+              release: {
+                major: '9',
+                full: '9.0',
+              },
+              distro: {
+                codename: 'stretch',
+                id: 'Debian',
+              },
+            },
+            'apt_update_last_success': factval,
+          }
+        end
+        let(:pre_condition) do
+          "class{'::apt': update => {'frequency' => 'always' },}"
+        end
+
+        it 'triggers an apt-get update run' do
+          # set the apt_update exec's refreshonly attribute to false
+          is_expected.to contain_exec('apt_update').with('refreshonly' => false)
+        end
+      end
+    end
+    context 'when $apt_update_last_success is nil' do
+      let(:facts) do
+        {
+          os: {
+            family: 'Debian',
+            name: 'Debian',
+            release: {
+              major: '9',
+              full: '9.0',
+            },
+            distro: {
+              codename: 'stretch',
+              id: 'Debian',
+            },
+          },
+        }
+      end
+      let(:pre_condition) { "class{ '::apt': update => {'frequency' => 'always' },}" }
+
+      it 'triggers an apt-get update run' do
+        # set the apt_update exec\'s refreshonly attribute to false
+        is_expected.to contain_exec('apt_update').with('refreshonly' => false)
+      end
+    end
+    context 'and Exec[apt_update] refreshonly is overridden to true and has recent run' do
+      let(:facts) do
+        {
+          os: {
+            family: 'Debian',
+            name: 'Debian',
+            release: {
+              major: '9',
+              full: '9.0',
+            },
+            distro: {
+              codename: 'stretch',
+              id: 'Debian',
+            },
+          },
+          'apt_update_last_success': Time.now.to_i,
+        }
+      end
+      let(:pre_condition) do
+        "
+        class{'::apt': update => {'frequency' => 'always' },}
+        Exec <| title=='apt_update' |> { refreshonly => true }
+        "
+      end
+
+      it 'skips an apt-get update run' do
+        # set the apt_update exec's refreshonly attribute to false
+        is_expected.to contain_exec('apt_update').with('refreshonly' => true)
+      end
+    end
+  end
+  context "when apt::update['frequency']='reluctantly'" do
+    {
+      'a recent run'                                 => Time.now.to_i,
+      'we are due for a run'                         => 1_406_660_561,
+      'the update-success-stamp file does not exist' => -1,
+    }.each_pair do |desc, factval|
+      context "when $apt_update_last_success indicates #{desc}" do
+        let(:facts) do
+          {
+            os: {
+              family: 'Debian',
+              name: 'Debian',
+              release: {
+                major: '9',
+                full: '9.0',
+              },
+              distro: {
+                codename: 'stretch',
+                id: 'Debian',
+              },
+            },
+            'apt_update_last_success': factval,
+          }
+        end
+        let(:pre_condition) { "class{ '::apt': update => {'frequency' => 'reluctantly' },}" }
+
+        it 'does not trigger an apt-get update run' do
+          # don't change the apt_update exec's refreshonly attribute. (it should be true)
+          is_expected.to contain_exec('apt_update').with('refreshonly' => true)
+        end
+      end
+    end
+    context 'when $apt_update_last_success is nil' do
+      let(:facts) do
+        {
+          os: {
+            family: 'Debian',
+            name: 'Debian',
+            release: {
+              major: '9',
+              full: '9.0',
+            },
+            distro: {
+              codename: 'stretch',
+              id: 'Debian',
+            },
+          },
+        }
+      end
+      let(:pre_condition) { "class{ '::apt': update => {'frequency' => 'reluctantly' },}" }
+
+      it 'does not trigger an apt-get update run' do
+        # don't change the apt_update exec's refreshonly attribute. (it should be true)
+        is_expected.to contain_exec('apt_update').with('refreshonly' => true)
+      end
+    end
+  end
+  ['daily', 'weekly'].each do |update_frequency|
+    context "when apt::update['frequency'] has the value of #{update_frequency}" do
+      { 'we are due for a run' => 1_406_660_561, 'the update-success-stamp file does not exist' => -1 }.each_pair do |desc, factval|
+        context "when $apt_update_last_success indicates #{desc}" do
+          let(:facts) do
+            {
+              os: {
+                family: 'Debian',
+                name: 'Debian',
+                release: {
+                  major: '9',
+                  full: '9.0',
+                },
+                distro: {
+                  codename: 'stretch',
+                  id: 'Debian',
+                },
+              },
+              'apt_update_last_success': factval,
+            }
+          end
+          let(:pre_condition) { "class{ '::apt': update => {'frequency' => '#{update_frequency}',} }" }
+
+          it 'triggers an apt-get update run' do
+            # set the apt_update exec\'s refreshonly attribute to false
+            is_expected.to contain_exec('apt_update').with('refreshonly' => false)
+          end
+        end
+      end
+      context 'when the $apt_update_last_success fact has a recent value' do
+        let(:facts) do
+          {
+            os: {
+              family: 'Debian',
+              name: 'Debian',
+              release: {
+                major: '9',
+                full: '9.0',
+              },
+              distro: {
+                codename: 'stretch',
+                id: 'Debian',
+              },
+            },
+            'apt_update_last_success': Time.now.to_i,
+          }
+        end
+        let(:pre_condition) { "class{ '::apt': update => {'frequency' => '#{update_frequency}',} }" }
+
+        it 'does not trigger an apt-get update run' do
+          # don't change the apt_update exec\'s refreshonly attribute. (it should be true)
+          is_expected.to contain_exec('apt_update').with('refreshonly' => true)
+        end
+      end
+      context 'when $apt_update_last_success is nil' do
+        let(:facts) do
+          {
+            os: {
+              family: 'Debian',
+              name: 'Debian',
+              release: {
+                major: '9',
+                full: '9.0',
+              },
+              distro: {
+                codename: 'stretch',
+                id: 'Debian',
+              },
+            },
+            'apt_update_last_success': nil,
+          }
+        end
+        let(:pre_condition) { "class{ '::apt': update => {'frequency' => '#{update_frequency}',} }" }
+
+        it 'triggers an apt-get update run' do
+          # set the apt_update exec\'s refreshonly attribute to false
+          is_expected.to contain_exec('apt_update').with('refreshonly' => false)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/default_facts.yml b/spec/default_facts.yml
new file mode 100644
index 0000000..f777abf
--- /dev/null
+++ b/spec/default_facts.yml
@@ -0,0 +1,8 @@
+# Use default_module_facts.yml for module specific facts.
+#
+# Facts specified here will override the values provided by rspec-puppet-facts.
+---
+ipaddress: "172.16.254.254"
+ipaddress6: "FE80:0000:0000:0000:AAAA:AAAA:AAAA"
+is_pe: false
+macaddress: "AA:AA:AA:AA:AA:AA"
diff --git a/spec/defines/conf_spec.rb b/spec/defines/conf_spec.rb
new file mode 100644
index 0000000..f1e42b5
--- /dev/null
+++ b/spec/defines/conf_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+describe 'apt::conf', type: :define do
+  let :pre_condition do
+    'class { "apt": }'
+  end
+  let(:facts) do
+    {
+      os: {
+        family: 'Debian',
+        name: 'Debian',
+        release: {
+          major: '9',
+          full: '9.0',
+        },
+        distro: {
+          codename: 'stretch',
+          id: 'Debian',
+        },
+      },
+    }
+  end
+  let :title do
+    'norecommends'
+  end
+
+  describe 'when creating an apt preference' do
+    let :default_params do
+      {
+        priority: '00',
+        content: "Apt::Install-Recommends 0;\nApt::AutoRemove::InstallRecommends 1;\n",
+      }
+    end
+    let :params do
+      default_params
+    end
+
+    let :filename do
+      '/etc/apt/apt.conf.d/00norecommends'
+    end
+
+    it {
+      is_expected.to contain_file(filename).with('ensure' => 'present',
+                                                 'content'   => %r{Apt::Install-Recommends 0;\nApt::AutoRemove::InstallRecommends 1;},
+                                                 'owner'     => 'root',
+                                                 'group'     => 'root')
+    }
+
+    context 'with notify_update = true (default)' do
+      let :params do
+        default_params
+      end
+
+      it { is_expected.to contain_apt__setting("conf-#{title}").with_notify_update(true) }
+    end
+
+    context 'with notify_update = false' do
+      let :params do
+        default_params.merge(notify_update: false)
+      end
+
+      it { is_expected.to contain_apt__setting("conf-#{title}").with_notify_update(false) }
+    end
+  end
+
+  describe 'when creating a preference without content' do
+    let :params do
+      {
+        priority: '00',
+      }
+    end
+
+    it 'fails' do
+      is_expected.to raise_error(%r{pass in content})
+    end
+  end
+
+  describe 'when removing an apt preference' do
+    let :params do
+      {
+        ensure: 'absent',
+        priority: '00',
+      }
+    end
+
+    let :filename do
+      '/etc/apt/apt.conf.d/00norecommends'
+    end
+
+    it {
+      is_expected.to contain_file(filename).with('ensure' => 'absent',
+                                                 'owner'     => 'root',
+                                                 'group'     => 'root')
+    }
+  end
+end
diff --git a/spec/defines/key_compat_spec.rb b/spec/defines/key_compat_spec.rb
new file mode 100644
index 0000000..d477937
--- /dev/null
+++ b/spec/defines/key_compat_spec.rb
@@ -0,0 +1,370 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+def contains_apt_key_example(title)
+  { id: title,
+    ensure: 'present',
+    source: 'http://apt.puppetlabs.com/pubkey.gpg',
+    server: 'pgp.mit.edu',
+    content: params[:content],
+    options: 'debug' }
+end
+
+def apt_key_example(title)
+  { id: title,
+    ensure: 'present',
+    source: nil,
+    server: 'keyserver.ubuntu.com',
+    content: nil,
+    keyserver_options: nil }
+end
+
+describe 'apt::key', type: :define do
+  GPG_KEY_ID = '6F6B15509CF8E59E6E469F327F438280EF8D349F'
+
+  let(:facts) do
+    {
+      os: {
+        family: 'Debian',
+        name: 'Debian',
+        release: {
+          major: '9',
+          full: '9.0',
+        },
+        distro: {
+          codename: 'stretch',
+          id: 'Debian',
+        },
+      },
+    }
+  end
+
+  let :title do
+    GPG_KEY_ID
+  end
+
+  let :pre_condition do
+    'include apt'
+  end
+
+  describe 'normal operation' do
+    describe 'default options' do
+      it {
+        is_expected.to contain_apt_key(title).with(id: title,
+                                                   ensure: 'present',
+                                                   source: nil,
+                                                   server: 'keyserver.ubuntu.com',
+                                                   content: nil)
+      }
+      it 'contains the apt_key present anchor' do
+        is_expected.to contain_anchor("apt_key #{title} present")
+      end
+    end
+
+    describe 'title and key =>' do
+      let :title do
+        'puppetlabs'
+      end
+
+      let :params do
+        {
+          id: GPG_KEY_ID,
+        }
+      end
+
+      it 'contains the apt_key' do
+        is_expected.to contain_apt_key(title).with(id: GPG_KEY_ID,
+                                                   ensure: 'present',
+                                                   source: nil,
+                                                   server: 'keyserver.ubuntu.com',
+                                                   content: nil)
+      end
+      it 'contains the apt_key present anchor' do
+        is_expected.to contain_anchor("apt_key #{GPG_KEY_ID} present")
+      end
+    end
+
+    describe 'ensure => absent' do
+      let :params do
+        {
+          ensure: 'absent',
+        }
+      end
+
+      it 'contains the apt_key' do
+        is_expected.to contain_apt_key(title).with(id: title,
+                                                   ensure: 'absent',
+                                                   source: nil,
+                                                   server: 'keyserver.ubuntu.com',
+                                                   content: nil)
+      end
+      it 'contains the apt_key absent anchor' do
+        is_expected.to contain_anchor("apt_key #{title} absent")
+      end
+    end
+
+    describe 'set a bunch of things!' do
+      let :params do
+        {
+          content: 'GPG key content',
+          source: 'http://apt.puppetlabs.com/pubkey.gpg',
+          server: 'pgp.mit.edu',
+          options: 'debug',
+        }
+      end
+
+      it 'contains the apt_key' do
+        is_expected.to contain_apt_key(title).with(contains_apt_key_example(title))
+      end
+      it 'contains the apt_key present anchor' do
+        is_expected.to contain_anchor("apt_key #{title} present")
+      end
+    end
+
+    context 'when domain has dash' do
+      let(:params) do
+        {
+          server: 'p-gp.m-it.edu',
+        }
+      end
+
+      it 'contains the apt_key' do
+        is_expected.to contain_apt_key(title).with(id: title,
+                                                   server: 'p-gp.m-it.edu')
+      end
+    end
+
+    context 'with url' do
+      let :params do
+        {
+          server: 'hkp://pgp.mit.edu',
+        }
+      end
+
+      it 'contains the apt_key' do
+        is_expected.to contain_apt_key(title).with(id: title,
+                                                   server: 'hkp://pgp.mit.edu')
+      end
+    end
+    context 'with url and port number' do
+      let :params do
+        {
+          server: 'hkp://pgp.mit.edu:80',
+        }
+      end
+
+      it 'contains the apt_key' do
+        is_expected.to contain_apt_key(title).with(id: title,
+                                                   server: 'hkp://pgp.mit.edu:80')
+      end
+    end
+  end
+
+  describe 'validation' do
+    context 'when domain begins with a dash' do
+      let(:params) do
+        {
+          server: '-pgp.mit.edu',
+        }
+      end
+
+      it 'fails' do
+        is_expected .to raise_error(%r{expects a match})
+      end
+    end
+
+    context 'when domain begins with dot' do
+      let(:params) do
+        {
+          server: '.pgp.mit.edu',
+        }
+      end
+
+      it 'fails' do
+        is_expected .to raise_error(%r{expects a match})
+      end
+    end
+
+    context 'when domain ends with dot' do
+      let(:params) do
+        {
+          server: 'pgp.mit.edu.',
+        }
+      end
+
+      it 'fails' do
+        is_expected .to raise_error(%r{expects a match})
+      end
+    end
+    context 'when url character limit is exceeded' do
+      let :params do
+        {
+          server: 'hkp://pgpiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii.mit.edu',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+    context 'with incorrect port number url' do
+      let :params do
+        {
+          server: 'hkp://pgp.mit.edu:8008080',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+    context 'with incorrect protocol for  url' do
+      let :params do
+        {
+          server: 'abc://pgp.mit.edu:80',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+    context 'with missing port number url' do
+      let :params do
+        {
+          server: 'hkp://pgp.mit.edu:',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+    context 'with url ending with a dot' do
+      let :params do
+        {
+          server: 'hkp://pgp.mit.edu.',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+    context 'with url begin with a dash' do
+      let(:params) do
+        {
+          server: 'hkp://-pgp.mit.edu',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+    context 'with invalid key' do
+      let :title do
+        'Out of rum. Why? Why are we out of rum?'
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+
+    context 'with invalid source' do
+      let :params do
+        {
+          source: 'afp://puppetlabs.com/key.gpg',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{evaluating a Resource})
+      end
+    end
+
+    context 'with invalid content' do
+      let :params do
+        {
+          content: [],
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a})
+      end
+    end
+
+    context 'with invalid server' do
+      let :params do
+        {
+          server: 'two bottles of rum',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+
+    context 'with invalid keyserver_options' do
+      let :params do
+        {
+          options: {},
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a})
+      end
+    end
+
+    context 'with invalid ensure' do
+      let :params do
+        {
+          ensure: 'foo',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{Enum\['absent', 'present', 'refreshed'\]})
+      end
+    end
+
+    describe 'duplication - two apt::key resources for same key, different titles' do
+      let :pre_condition do
+        "#{super()}\napt::key { 'duplicate': id => '#{title}', }"
+      end
+
+      it 'contains the duplicate apt::key resource' do
+        is_expected.to contain_apt__key('duplicate').with(id: title,
+                                                          ensure: 'present')
+      end
+
+      it 'contains the original apt::key resource' do
+        is_expected.to contain_apt__key(title).with(id: title,
+                                                    ensure: 'present')
+      end
+
+      it 'contains the native apt_key' do
+        is_expected.to contain_apt_key('duplicate').with(apt_key_example(title))
+      end
+
+      it 'does not contain the original apt_key' do
+        is_expected.not_to contain_apt_key(title)
+      end
+    end
+
+    describe 'duplication - two apt::key resources, different ensure' do
+      let :pre_condition do
+        "#{super()}\napt::key { 'duplicate': id => '#{title}', ensure => 'absent', }"
+      end
+
+      it 'informs the user of the impossibility' do
+        is_expected.to raise_error(%r{already ensured as absent})
+      end
+    end
+  end
+end
diff --git a/spec/defines/key_spec.rb b/spec/defines/key_spec.rb
new file mode 100644
index 0000000..fd63965
--- /dev/null
+++ b/spec/defines/key_spec.rb
@@ -0,0 +1,418 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+GPG_KEY_ID = '6F6B15509CF8E59E6E469F327F438280EF8D349F'
+
+title_key_example = { id: GPG_KEY_ID,
+                      ensure: 'present',
+                      source: nil,
+                      server: 'keyserver.ubuntu.com',
+                      content: nil,
+                      options: nil }
+
+def default_apt_key_example(title)
+  { id: title,
+    ensure: 'present',
+    source: nil,
+    server: 'keyserver.ubuntu.com',
+    content: nil,
+    options: nil,
+    refresh: false }
+end
+
+def bunch_things_apt_key_example(title, params)
+  { id: title,
+    ensure: 'present',
+    source: 'http://apt.puppetlabs.com/pubkey.gpg',
+    server: 'pgp.mit.edu',
+    content: params[:content],
+    options: 'debug' }
+end
+
+def absent_apt_key(title)
+  { id: title,
+    ensure: 'absent',
+    source: nil,
+    server: 'keyserver.ubuntu.com',
+    content: nil,
+    keyserver: nil }
+end
+
+describe 'apt::key' do
+  let :pre_condition do
+    'class { "apt": }'
+  end
+
+  let(:facts) do
+    {
+      os: {
+        family: 'Debian',
+        name: 'Debian',
+        release: {
+          major: '9',
+          full: '9.0',
+        },
+        distro: {
+          codename: 'stretch',
+          id: 'Debian',
+        },
+      },
+    }
+  end
+
+  let :title do
+    GPG_KEY_ID
+  end
+
+  describe 'normal operation' do
+    describe 'default options' do
+      it 'contains the apt_key' do
+        is_expected.to contain_apt_key(title).with(default_apt_key_example(title))
+      end
+      it 'contains the apt_key present anchor' do
+        is_expected.to contain_anchor("apt_key #{title} present")
+      end
+    end
+
+    describe 'title and key =>' do
+      let :title do
+        'puppetlabs'
+      end
+
+      let :params do
+        {
+          id: GPG_KEY_ID,
+        }
+      end
+
+      it 'contains the apt_key' do
+        is_expected.to contain_apt_key(title).with(title_key_example)
+      end
+      it 'contains the apt_key present anchor' do
+        is_expected.to contain_anchor("apt_key #{GPG_KEY_ID} present")
+      end
+    end
+
+    describe 'ensure => absent' do
+      let :params do
+        {
+          ensure: 'absent',
+        }
+      end
+
+      it 'contains the apt_key' do
+        is_expected.to contain_apt_key(title).with(absent_apt_key(title))
+      end
+      it 'contains the apt_key absent anchor' do
+        is_expected.to contain_anchor("apt_key #{title} absent")
+      end
+    end
+
+    describe 'ensure => refreshed' do
+      let :params do
+        {
+          ensure: 'refreshed',
+        }
+      end
+
+      it 'contains the apt_key with refresh => true' do
+        is_expected.to contain_apt_key(title).with(
+          ensure: 'present',
+          refresh: true,
+        )
+      end
+    end
+
+    describe 'set a bunch of things!' do
+      let :params do
+        {
+          content: 'GPG key content',
+          source: 'http://apt.puppetlabs.com/pubkey.gpg',
+          server: 'pgp.mit.edu',
+          options: 'debug',
+        }
+      end
+
+      it 'contains the apt_key' do
+        is_expected.to contain_apt_key(title).with(bunch_things_apt_key_example(title, params))
+      end
+      it 'contains the apt_key present anchor' do
+        is_expected.to contain_anchor("apt_key #{title} present")
+      end
+    end
+
+    context 'when domain with dash' do
+      let(:params) do
+        {
+          server: 'p-gp.m-it.edu',
+        }
+      end
+
+      it 'contains the apt_key' do
+        is_expected.to contain_apt_key(title).with(id: title,
+                                                   server: 'p-gp.m-it.edu')
+      end
+    end
+
+    context 'with url' do
+      let :params do
+        {
+          server: 'hkp://pgp.mit.edu',
+        }
+      end
+
+      it 'contains the apt_key' do
+        is_expected.to contain_apt_key(title).with(id: title,
+                                                   server: 'hkp://pgp.mit.edu')
+      end
+    end
+    context 'when url with port number' do
+      let :params do
+        {
+          server: 'hkp://pgp.mit.edu:80',
+        }
+      end
+
+      it 'contains the apt_key' do
+        is_expected.to contain_apt_key(title).with(id: title,
+                                                   server: 'hkp://pgp.mit.edu:80')
+      end
+    end
+  end
+
+  describe 'validation' do
+    context 'when domain begin with dash' do
+      let(:params) do
+        {
+          server: '-pgp.mit.edu',
+        }
+      end
+
+      it 'fails' do
+        is_expected .to raise_error(%r{expects a match})
+      end
+    end
+
+    context 'when domain begin with dot' do
+      let(:params) do
+        {
+          server: '.pgp.mit.edu',
+        }
+      end
+
+      it 'fails' do
+        is_expected .to raise_error(%r{expects a match})
+      end
+    end
+
+    context 'when domain end with dot' do
+      let(:params) do
+        {
+          server: 'pgp.mit.edu.',
+        }
+      end
+
+      it 'fails' do
+        is_expected .to raise_error(%r{expects a match})
+      end
+    end
+    context 'when character url exceeded' do
+      let :params do
+        {
+          server: 'hkp://pgpiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii.mit.edu',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+    context 'with incorrect port number url' do
+      let :params do
+        {
+          server: 'hkp://pgp.mit.edu:8008080',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+    context 'with incorrect protocol for url' do
+      let :params do
+        {
+          server: 'abc://pgp.mit.edu:80',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+    context 'with missing port number url' do
+      let :params do
+        {
+          server: 'hkp://pgp.mit.edu:',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+    context 'with url ending with a dot' do
+      let :params do
+        {
+          server: 'hkp://pgp.mit.edu.',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+    context 'when url begins with a dash' do
+      let(:params) do
+        {
+          server: 'hkp://-pgp.mit.edu',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+    context 'with invalid key' do
+      let :title do
+        'Out of rum. Why? Why are we out of rum?'
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+
+    context 'with invalid source' do
+      let :params do
+        {
+          source: 'afp://puppetlabs.com/key.gpg',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{evaluating a Resource})
+      end
+    end
+
+    context 'with invalid content' do
+      let :params do
+        {
+          content: [],
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a})
+      end
+    end
+
+    context 'with invalid server' do
+      let :params do
+        {
+          server: 'two bottles of rum',
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a match})
+      end
+    end
+
+    context 'with invalid options' do
+      let :params do
+        {
+          options: {},
+        }
+      end
+
+      it 'fails' do
+        is_expected.to raise_error(%r{expects a})
+      end
+    end
+
+    context 'with invalid ensure' do
+      ['foo', 'aabsent', 'absenta', 'apresent', 'presenta', 'refresh', 'arefreshed', 'refresheda'].each do |param|
+        let :params do
+          {
+            ensure: param,
+          }
+        end
+
+        it 'fails' do
+          is_expected.to raise_error(%r{for Enum\['absent', 'present', 'refreshed'\], got})
+        end
+      end
+    end
+
+    describe 'duplication - two apt::key resources for same key, different titles' do
+      let :pre_condition do
+        "class { 'apt': }
+        apt::key { 'duplicate': id => '#{title}', }"
+      end
+
+      it 'contains two apt::key resource - duplicate' do
+        is_expected.to contain_apt__key('duplicate').with(id: title,
+                                                          ensure: 'present')
+      end
+      it 'contains two apt::key resource - title' do
+        is_expected.to contain_apt__key(title).with(id: title,
+                                                    ensure: 'present')
+      end
+
+      it 'contains only a single apt_key - duplicate' do
+        is_expected.to contain_apt_key('duplicate').with(default_apt_key_example(title))
+      end
+      it 'contains only a single apt_key - no title' do
+        is_expected.not_to contain_apt_key(title)
+      end
+    end
+
+    describe 'duplication - two apt::key resources, different ensure' do
+      let :pre_condition do
+        "class { 'apt': }
+        apt::key { 'duplicate': id => '#{title}', ensure => 'absent', }"
+      end
+
+      it 'informs the user of the impossibility' do
+        is_expected.to raise_error(%r{already ensured as absent})
+      end
+    end
+  end
+
+  describe 'defaults' do
+    context 'when setting keyserver on the apt class' do
+      let :pre_condition do
+        'class { "apt":
+          keyserver => "keyserver.example.com",
+        }'
+      end
+
+      it 'uses default keyserver' do
+        is_expected.to contain_apt_key(title).with_server('keyserver.example.com')
+      end
+    end
+
+    context 'when setting key_options on the apt class' do
+      let :pre_condition do
+        'class { "apt":
+          key_options => "http-proxy=http://proxy.example.com:8080",
+        }'
+      end
+
+      it 'uses default keyserver' do
+        is_expected.to contain_apt_key(title).with_options('http-proxy=http://proxy.example.com:8080')
+      end
+    end
+  end
+end
diff --git a/spec/defines/mark_spec.rb b/spec/defines/mark_spec.rb
new file mode 100644
index 0000000..5375fe2
--- /dev/null
+++ b/spec/defines/mark_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt::mark', type: :define do
+  let :title do
+    'mysource'
+  end
+
+  let :facts do
+    {
+      os: {
+        family: 'Debian',
+        name: 'Debian',
+        release: {
+          major: '9',
+          full: '9.0',
+        },
+        distro: {
+          codename: 'stretch',
+          id: 'Debian',
+        },
+      },
+    }
+  end
+
+  context 'with correct seting' do
+    let :params do
+      {
+        'setting' => 'manual',
+      }
+    end
+
+    it {
+      is_expected.to contain_exec('apt-mark manual mysource')
+    }
+  end
+
+  describe 'with wrong setting' do
+    let :params do
+      {
+        'setting' => 'foobar',
+      }
+    end
+
+    it do
+      is_expected.to raise_error(Puppet::PreformattedError, %r{expects a match for Enum\['auto', 'hold', 'manual', 'unhold'\], got 'foobar'})
+    end
+  end
+
+  [
+    'package',
+    'package1',
+    'package.name',
+    'package-name',
+    'package+name',
+    'p.ackagename',
+    'p+ackagename',
+    'p+',
+  ].each do |value|
+    describe 'with a valid resource title' do
+      let :title do
+        value
+      end
+
+      let :params do
+        {
+          'setting' => 'manual',
+        }
+      end
+
+      it do
+        is_expected.to contain_exec("apt-mark manual #{title}")
+      end
+    end
+  end
+
+  # packagenames starting with + are not valid as the title according to puppet
+  # good thing this is also an illegal name for debian packages
+  [
+    '|| ls -la ||',
+    'packakge with space',
+    'package<>|',
+    '|| touch /tmp/foo.txt ||',
+    'package_name',
+    'PackageName',
+    '.p',
+    'p',
+  ].each do |value|
+    describe "with an invalid resource title [#{value}]" do
+      let :title do
+        value
+      end
+
+      let :params do
+        {
+          'setting' => 'manual',
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::PreformattedError, %r{Invalid package name: #{title}})
+      end
+    end
+  end
+end
diff --git a/spec/defines/pin_spec.rb b/spec/defines/pin_spec.rb
new file mode 100644
index 0000000..5edc79c
--- /dev/null
+++ b/spec/defines/pin_spec.rb
@@ -0,0 +1,157 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+describe 'apt::pin', type: :define do
+  let :pre_condition do
+    'class { "apt": }'
+  end
+  let(:facts) do
+    {
+      os: {
+        family: 'Debian',
+        name: 'Debian',
+        release: {
+          major: '9',
+          full: '9.0',
+        },
+        distro: {
+          codename: 'stretch',
+          id: 'Debian',
+        },
+      },
+    }
+  end
+  let(:title) { 'my_pin' }
+
+  context 'with defaults' do
+    it { is_expected.to contain_apt__setting('pref-my_pin').with_content(%r{Explanation: : my_pin\nPackage: \*\nPin: release a=my_pin\nPin-Priority: 0\n}) }
+  end
+
+  context 'with set version' do
+    let :params do
+      {
+        'packages' => 'vim',
+        'version'  => '1',
+      }
+    end
+
+    it { is_expected.to contain_apt__setting('pref-my_pin').with_content(%r{Explanation: : my_pin\nPackage: vim\nPin: version 1\nPin-Priority: 0\n}) }
+  end
+
+  context 'with set origin' do
+    let :params do
+      {
+        'packages' => 'vim',
+        'origin'   => 'test',
+      }
+    end
+
+    it { is_expected.to contain_apt__setting('pref-my_pin').with_content(%r{Explanation: : my_pin\nPackage: vim\nPin: origin test\nPin-Priority: 0\n}) }
+  end
+
+  context 'without defaults' do
+    let :params do
+      {
+        'explanation'     => 'foo',
+        'order'           => 99,
+        'release'         => '1',
+        'codename'        => 'bar',
+        'release_version' => '2',
+        'component'       => 'baz',
+        'originator'      => 'foobar',
+        'label'           => 'foobaz',
+        'priority'        => 10,
+      }
+    end
+
+    it { is_expected.to contain_apt__setting('pref-my_pin').with_content(%r{Explanation: foo\nPackage: \*\nPin: release a=1, n=bar, v=2, c=baz, o=foobar, l=foobaz\nPin-Priority: 10\n}) }
+    it {
+      is_expected.to contain_apt__setting('pref-my_pin').with('priority' => 99)
+    }
+  end
+
+  context 'with ensure absent' do
+    let :params do
+      {
+        'ensure' => 'absent',
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('pref-my_pin').with('ensure' => 'absent')
+    }
+  end
+
+  context 'with bad characters' do
+    let(:title) { 'such  bad && wow!' }
+
+    it { is_expected.to contain_apt__setting('pref-such__bad____wow_') }
+  end
+
+  describe 'validation' do
+    context 'with invalid order' do
+      let :params do
+        {
+          'order' => 'foo',
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{expects an Integer value, got String})
+      end
+    end
+
+    context 'with packages == * and version' do
+      let :params do
+        {
+          'version' => '1',
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{parameter version cannot be used in general form})
+      end
+    end
+
+    context 'with packages == * and release and origin' do
+      let :params do
+        {
+          'origin'  => 'test',
+          'release' => 'foo',
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{parameters release and origin are mutually exclusive})
+      end
+    end
+
+    context 'with specific release and origin' do
+      let :params do
+        {
+          'release'  => 'foo',
+          'origin'   => 'test',
+          'packages' => 'vim',
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{parameters release, origin, and version are mutually exclusive})
+      end
+    end
+
+    context 'with specific version and origin' do
+      let :params do
+        {
+          'version'  => '1',
+          'origin'   => 'test',
+          'packages' => 'vim',
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{parameters release, origin, and version are mutually exclusive})
+      end
+    end
+  end
+end
diff --git a/spec/defines/ppa_spec.rb b/spec/defines/ppa_spec.rb
new file mode 100644
index 0000000..00424dc
--- /dev/null
+++ b/spec/defines/ppa_spec.rb
@@ -0,0 +1,475 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+def ppa_exec_params(user, repo, distro = 'trusty', environment = [])
+  [
+    environment: environment,
+    command: "/opt/puppetlabs/puppet/cache/add-apt-repository-#{user}-ubuntu-#{repo}-#{distro}.sh",
+    logoutput: 'on_failure',
+  ]
+end
+
+describe 'apt::ppa' do
+  let :pre_condition do
+    'class { "apt": }'
+  end
+
+  describe 'defaults' do
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'trusty',
+            id: 'Ubuntu',
+          },
+        },
+        puppet_vardir: '/opt/puppetlabs/puppet/cache'
+      }
+    end
+
+    let(:title) { 'ppa:needs/substitution' }
+
+    it { is_expected.not_to contain_package('python-software-properties') }
+    it {
+      is_expected.to contain_exec('add-apt-repository-ppa:needs/substitution')
+        .that_notifies('Class[Apt::Update]')
+        .with(*ppa_exec_params('needs', 'substitution'))
+    }
+  end
+
+  [
+    'ppa:foo/bar',
+    'ppa:foo/bar1.0',
+    'ppa:foo10/bar10',
+    'ppa:foo-/bar_',
+  ].each do |value|
+    describe 'valid resource names' do
+      let :facts do
+        {
+          os: {
+            family: 'Debian',
+            name: 'Ubuntu',
+            release: {
+              major: '18',
+              full: '18.04',
+            },
+            distro: {
+              codename: 'trusty',
+              id: 'Ubuntu',
+            },
+          },
+        }
+      end
+
+      let(:title) { value }
+
+      it { is_expected.not_to raise_error }
+      it { is_expected.to contain_exec("add-apt-repository-#{value}") }
+    end
+  end
+
+  [
+    'ppa:foo!/bar',
+    'ppa:foo/bar!',
+    'ppa:foo1,0/bar',
+    'ppa:foo/bar/foobar',
+    '|| ls -la ||',
+    '|| touch /tmp/foo.txt ||',
+  ].each do |value|
+    describe 'invalid resource names' do
+      let :facts do
+        {
+          os: {
+            family: 'Debian',
+            name: 'Ubuntu',
+            release: {
+              major: '18',
+              full: '18.04',
+            },
+            distro: {
+              codename: 'trusty',
+              id: 'Ubuntu',
+            },
+          },
+        }
+      end
+
+      let(:title) { value }
+
+      it { is_expected.to raise_error(Puppet::PreformattedError, %r{Invalid PPA name: #{value}}) }
+    end
+  end
+
+  describe 'Ubuntu 15.10 sources.list filename' do
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '15',
+            full: '15.10',
+          },
+          distro: {
+            codename: 'wily',
+            id: 'Ubuntu',
+          },
+        },
+        puppet_vardir: '/opt/puppetlabs/puppet/cache',
+      }
+    end
+
+    let(:title) { 'ppa:user/foo' }
+
+    it {
+      is_expected.to contain_exec('add-apt-repository-ppa:user/foo')
+        .that_notifies('Class[Apt::Update]')
+        .with(*ppa_exec_params('user', 'foo', 'wily'))
+    }
+  end
+
+  describe 'package_name => software-properties-common' do
+    let :pre_condition do
+      'class { "apt": }'
+    end
+
+    let :params do
+      {
+        package_name: 'software-properties-common',
+        package_manage: true,
+      }
+    end
+
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'trusty',
+            id: 'Ubuntu',
+          },
+        },
+        puppet_vardir: '/opt/puppetlabs/puppet/cache',
+      }
+    end
+
+    let(:title) { 'ppa:needs/substitution' }
+
+    it { is_expected.to contain_package('software-properties-common') }
+    it {
+      is_expected.to contain_exec('add-apt-repository-ppa:needs/substitution')
+        .that_notifies('Class[Apt::Update]')
+        .with(*ppa_exec_params('needs', 'substitution'))
+    }
+  end
+
+  describe 'package_manage => false' do
+    let :pre_condition do
+      'class { "apt": }'
+    end
+
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'trusty',
+            id: 'Ubuntu',
+          },
+        },
+        puppet_vardir: '/opt/puppetlabs/puppet/cache',
+      }
+    end
+
+    let :params do
+      {
+        package_manage: false,
+      }
+    end
+
+    let(:title) { 'ppa:needs/substitution' }
+
+    it { is_expected.not_to contain_package('python-software-properties') }
+    it {
+      is_expected.to contain_exec('add-apt-repository-ppa:needs/substitution')
+        .that_notifies('Class[Apt::Update]')
+        .with(*ppa_exec_params('needs', 'substitution'))
+    }
+  end
+
+  describe 'apt included, no proxy' do
+    let :pre_condition do
+      'class { "apt": }
+      apt::ppa { "ppa:user/foo2": }
+      '
+    end
+
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'trusty',
+            id: 'Ubuntu',
+          },
+        },
+        puppet_vardir: '/opt/puppetlabs/puppet/cache',
+      }
+    end
+
+    let :params do
+      {
+        package_manage: true,
+        require: 'Apt::Ppa[ppa:user/foo2]',
+      }
+    end
+
+    let(:title) { 'ppa:user/foo' }
+
+    it { is_expected.to compile.with_all_deps }
+    it { is_expected.to contain_package('software-properties-common') }
+    it {
+      is_expected.to contain_exec('add-apt-repository-ppa:user/foo')
+        .that_notifies('Class[Apt::Update]')
+        .with(*ppa_exec_params('user', 'foo'))
+    }
+  end
+
+  describe 'apt included, proxy host' do
+    let :pre_condition do
+      'class { "apt":
+        proxy => { "host" => "localhost" },
+      }'
+    end
+
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'trusty',
+            id: 'Ubuntu',
+          },
+        },
+        puppet_vardir: '/opt/puppetlabs/puppet/cache',
+      }
+    end
+
+    let :params do
+      {
+        'package_manage' => true,
+      }
+    end
+
+    let(:title) { 'ppa:user/foo' }
+
+    it { is_expected.to contain_package('software-properties-common') }
+    it {
+      is_expected.to contain_exec('add-apt-repository-ppa:user/foo')
+        .that_notifies('Class[Apt::Update]')
+        .with(*ppa_exec_params('user', 'foo', 'trusty', ['http_proxy=http://localhost:8080']))
+    }
+  end
+
+  describe 'apt included, proxy host and port' do
+    let :pre_condition do
+      'class { "apt":
+        proxy => { "host" => "localhost", "port" => 8180 },
+      }'
+    end
+
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'trusty',
+            id: 'Ubuntu',
+          },
+        },
+        puppet_vardir: '/opt/puppetlabs/puppet/cache',
+      }
+    end
+
+    let :params do
+      {
+        package_manage: true,
+      }
+    end
+
+    let(:title) { 'ppa:user/foo' }
+
+    it { is_expected.to contain_package('software-properties-common') }
+    it {
+      is_expected.to contain_exec('add-apt-repository-ppa:user/foo')
+        .that_notifies('Class[Apt::Update]')
+        .with(*ppa_exec_params('user', 'foo', 'trusty', ['http_proxy=http://localhost:8180']))
+    }
+  end
+
+  describe 'apt included, proxy host and port and https' do
+    let :pre_condition do
+      'class { "apt":
+        proxy => { "host" => "localhost", "port" => 8180, "https" => true },
+      }'
+    end
+
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'trusty',
+            id: 'Ubuntu',
+          },
+        },
+        puppet_vardir: '/opt/puppetlabs/puppet/cache',
+      }
+    end
+
+    let :params do
+      {
+        package_manage: true,
+      }
+    end
+
+    let(:title) { 'ppa:user/foo' }
+
+    it { is_expected.to contain_package('software-properties-common') }
+    it {
+      is_expected.to contain_exec('add-apt-repository-ppa:user/foo')
+        .that_notifies('Class[Apt::Update]')
+        .with(*ppa_exec_params('user', 'foo', 'trusty', ['http_proxy=http://localhost:8180', 'https_proxy=https://localhost:8180']))
+    }
+  end
+
+  describe 'ensure absent' do
+    let :pre_condition do
+      'class { "apt": }'
+    end
+
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Ubuntu',
+          release: {
+            major: '18',
+            full: '18.04',
+          },
+          distro: {
+            codename: 'trusty',
+            id: 'Ubuntu',
+          },
+        },
+        puppet_vardir: '/opt/puppetlabs/puppet/cache',
+      }
+    end
+
+    let(:title) { 'ppa:user/foo' }
+
+    let :params do
+      {
+        ensure: 'absent',
+      }
+    end
+
+    it {
+      is_expected.to contain_tidy("remove-apt-repository-script-#{title}")
+        .with('path' => '/opt/puppetlabs/puppet/cache/add-apt-repository-user-ubuntu-foo-trusty.sh')
+
+      is_expected.to contain_tidy("remove-apt-repository-#{title}")
+        .with('path' => '/etc/apt/sources.list.d/user-ubuntu-foo-trusty.list')
+        .that_notifies('Class[Apt::Update]')
+    }
+  end
+
+  context 'with validation' do
+    describe 'no release' do
+      let :facts do
+        {
+          os: {
+            family: 'Debian',
+            name: 'Ubuntu',
+            release: {
+              major: '18',
+              full: '18.04',
+            },
+            distro: {
+              codename: nil,
+              id: 'Ubuntu',
+            },
+          },
+        }
+      end
+
+      let(:title) { 'ppa:user/foo' }
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{os.distro.codename fact not available: release parameter required})
+      end
+    end
+
+    describe 'not ubuntu' do
+      let :facts do
+        {
+          os: {
+            family: 'Debian',
+            name: 'Debian',
+            release: {
+              major: '6',
+              full: '6.0.7',
+            },
+            distro: {
+              codename: 'wheezy',
+              id: 'Debian',
+            },
+          },
+        }
+      end
+
+      let(:title) { 'ppa:user/foo' }
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{not currently supported on Debian})
+      end
+    end
+  end
+end
diff --git a/spec/defines/setting_spec.rb b/spec/defines/setting_spec.rb
new file mode 100644
index 0000000..97ebdc4
--- /dev/null
+++ b/spec/defines/setting_spec.rb
@@ -0,0 +1,153 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt::setting' do
+  let(:pre_condition) { 'class { "apt": }' }
+  let :facts do
+    {
+      os: {
+        family: 'Debian',
+        name: 'Debian',
+        release: {
+          major: '9',
+          full: '9.0',
+        },
+        distro: {
+          codename: 'stretch',
+          id: 'Debian',
+        },
+      },
+    }
+  end
+  let(:title) { 'conf-teddybear' }
+
+  let(:default_params) { { content: 'di' } }
+
+  describe 'when using the defaults' do
+    context 'without source or content' do
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{needs either of })
+      end
+    end
+
+    context 'with title=conf-teddybear ' do
+      let(:params) { default_params }
+
+      it { is_expected.to contain_file('/etc/apt/apt.conf.d/50teddybear').that_notifies('Class[Apt::Update]') }
+    end
+
+    context 'with title=pref-teddybear' do
+      let(:title) { 'pref-teddybear' }
+      let(:params) { default_params }
+
+      it { is_expected.to contain_file('/etc/apt/preferences.d/teddybear.pref').that_notifies('Class[Apt::Update]') }
+    end
+
+    context 'with title=list-teddybear' do
+      let(:title) { 'list-teddybear' }
+      let(:params) { default_params }
+
+      it { is_expected.to contain_file('/etc/apt/sources.list.d/teddybear.list').that_notifies('Class[Apt::Update]') }
+    end
+
+    context 'with source' do
+      let(:params) { { source: 'puppet:///la/die/dah' } }
+
+      it {
+        is_expected.to contain_file('/etc/apt/apt.conf.d/50teddybear').that_notifies('Class[Apt::Update]').with(ensure: 'file',
+                                                                                                                owner: 'root',
+                                                                                                                group: 'root',
+                                                                                                                source: params[:source].to_s)
+      }
+    end
+
+    context 'with content' do
+      let(:params) { default_params }
+
+      it {
+        is_expected.to contain_file('/etc/apt/apt.conf.d/50teddybear').that_notifies('Class[Apt::Update]').with(ensure: 'file',
+                                                                                                                owner: 'root',
+                                                                                                                group: 'root',
+                                                                                                                content: params[:content].to_s)
+      }
+    end
+  end
+
+  describe 'settings requiring settings, MODULES-769' do
+    let(:pre_condition) do
+      'class { "apt": }
+      apt::setting { "list-teddybear": content => "foo" }
+      '
+    end
+    let(:facts) do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Debian',
+          release: {
+            major: '9',
+            full: '9.0',
+          },
+          distro: {
+            codename: 'stretch',
+            id: 'Debian',
+          },
+        },
+      }
+    end
+    let(:title) { 'conf-teddybear' }
+    let(:default_params) { { content: 'di' } }
+
+    let(:params) { default_params.merge(require: 'Apt::Setting[list-teddybear]') }
+
+    it { is_expected.to compile.with_all_deps }
+  end
+
+  describe 'when trying to pull one over' do
+    context 'with source and content' do
+      let(:params) { default_params.merge(source: 'la') }
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{cannot have both })
+      end
+    end
+
+    context 'with title=ext-teddybear' do
+      let(:title) { 'ext-teddybear' }
+      let(:params) { default_params }
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{must start with either})
+      end
+    end
+
+    context 'with ensure=banana' do
+      let(:params) { default_params.merge(ensure: 'banana') }
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{Enum\['absent', 'file', 'present'\]})
+      end
+    end
+
+    context 'with priority=1.2' do
+      let(:params) { default_params.merge(priority: 1.2) }
+
+      it { is_expected.to compile.and_raise_error(%r{expects a value of type}) }
+    end
+  end
+
+  describe 'with priority=100' do
+    let(:params) { default_params.merge(priority: 100) }
+
+    it { is_expected.to contain_file('/etc/apt/apt.conf.d/100teddybear').that_notifies('Class[Apt::Update]') }
+  end
+
+  describe 'with ensure=absent' do
+    let(:params) { default_params.merge(ensure: 'absent') }
+
+    it {
+      is_expected.to contain_file('/etc/apt/apt.conf.d/50teddybear').that_notifies('Class[Apt::Update]').with(ensure: 'absent')
+    }
+  end
+end
diff --git a/spec/defines/source_compat_spec.rb b/spec/defines/source_compat_spec.rb
new file mode 100644
index 0000000..d7dfb78
--- /dev/null
+++ b/spec/defines/source_compat_spec.rb
@@ -0,0 +1,146 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt::source', type: :define do
+  GPG_KEY_ID = '6F6B15509CF8E59E6E469F327F438280EF8D349F'
+
+  let :title do
+    'my_source'
+  end
+
+  let :facts do
+    {
+      os: {
+        family: 'Debian',
+        name: 'Debian',
+        release: {
+          major: '9',
+          full: '9.0',
+        },
+        distro: {
+          codename: 'stretch',
+          id: 'Debian',
+        },
+      },
+    }
+  end
+
+  context 'with mostly defaults' do
+    let :params do
+      {
+        'include' => { 'deb' => false, 'src' => true },
+        'location' => 'http://debian.mirror.iweb.ca/debian/',
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source').with_content(%r{# my_source\ndeb-src http://debian.mirror.iweb.ca/debian/ stretch main\n})
+    }
+  end
+
+  context 'with no defaults' do
+    let :params do
+      {
+        'comment'        => 'foo',
+        'location'       => 'http://debian.mirror.iweb.ca/debian/',
+        'release'        => 'sid',
+        'repos'          => 'testing',
+        'include'        => { 'src' => false },
+        'key'            => GPG_KEY_ID,
+        'pin'            => '10',
+        'architecture'   => 'x86_64',
+        'allow_unsigned' => true,
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source').with_content(%r{# foo\ndeb \[arch=x86_64 trusted=yes\] http://debian.mirror.iweb.ca/debian/ sid testing\n})
+                                                           .without_content(%r{deb-src})
+    }
+
+    it {
+      is_expected.to contain_apt__pin('my_source').that_comes_before('Apt::Setting[list-my_source]').with('ensure' => 'present',
+                                                                                                          'priority' => '10',
+                                                                                                          'origin'   => 'debian.mirror.iweb.ca')
+    }
+
+    it {
+      is_expected.to contain_apt__key("Add key: #{GPG_KEY_ID} from Apt::Source my_source").that_comes_before('Apt::Setting[list-my_source]').with('ensure' => 'present',
+                                                                                                                                                  'id' => GPG_KEY_ID)
+    }
+  end
+
+  context 'when allow_insecure true' do
+    let :params do
+      {
+        'include'        => { 'src' => false },
+        'location'       => 'http://debian.mirror.iweb.ca/debian/',
+        'allow_insecure' => true,
+      }
+    end
+
+    it { is_expected.to contain_apt__setting('list-my_source').with_content(%r{# my_source\ndeb \[allow-insecure=yes\] http://debian.mirror.iweb.ca/debian/ stretch main\n}) }
+  end
+
+  context 'when allow_unsigned true' do
+    let :params do
+      {
+        'include'        => { 'src' => false },
+        'location'       => 'http://debian.mirror.iweb.ca/debian/',
+        'allow_unsigned' => true,
+      }
+    end
+
+    it { is_expected.to contain_apt__setting('list-my_source').with_content(%r{# my_source\ndeb \[trusted=yes\] http://debian.mirror.iweb.ca/debian/ stretch main\n}) }
+  end
+
+  context 'with architecture equals x86_64' do
+    let :params do
+      {
+        'location'     => 'http://debian.mirror.iweb.ca/debian/',
+        'architecture' => 'x86_64',
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source').with_content(%r{# my_source\ndeb \[arch=x86_64\] http://debian.mirror.iweb.ca/debian/ stretch main\n})
+    }
+  end
+
+  context 'with ensure => absent' do
+    let :params do
+      {
+        'ensure' => 'absent',
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source').with('ensure' => 'absent')
+    }
+  end
+
+  describe 'validation' do
+    context 'with no release' do
+      let :facts do
+        {
+          os: {
+            family: 'Debian',
+            name: 'Debian',
+            release: {
+              major: '8',
+              full: '8.0',
+            },
+            distro: {
+              id: 'Debian',
+            },
+          },
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{os.distro.codename fact not available: release parameter required})
+      end
+    end
+  end
+end
diff --git a/spec/defines/source_spec.rb b/spec/defines/source_spec.rb
new file mode 100644
index 0000000..6410895
--- /dev/null
+++ b/spec/defines/source_spec.rb
@@ -0,0 +1,479 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt::source' do
+  GPG_KEY_ID = '6F6B15509CF8E59E6E469F327F438280EF8D349F'
+
+  let :pre_condition do
+    'class { "apt": }'
+  end
+
+  let :title do
+    'my_source'
+  end
+
+  let :facts do
+    {
+      os: {
+        family: 'Debian',
+        name: 'Debian',
+        release: {
+          major: '9',
+          full: '9.0',
+        },
+        distro: {
+          codename: 'stretch',
+          id: 'Debian',
+        },
+      },
+    }
+  end
+
+  context 'with defaults' do
+    context 'without location' do
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{source entry without specifying a location})
+      end
+    end
+    context 'with location' do
+      let(:params) { { location: 'hello.there' } }
+
+      it {
+        is_expected.to contain_apt__setting('list-my_source').with(ensure: 'present').without_content(%r{# my_source\ndeb-src hello.there wheezy main\n})
+        is_expected.not_to contain_package('apt-transport-https')
+      }
+    end
+  end
+
+  describe 'no defaults' do
+    context 'with complex pin' do
+      let :params do
+        {
+          location: 'hello.there',
+          pin: { 'release' => 'wishwash',
+                 'explanation' => 'wishwash',
+                 'priority'    => 1001 },
+        }
+      end
+
+      it {
+        is_expected.to contain_apt__setting('list-my_source').with(ensure: 'present').with_content(%r{hello.there stretch main\n})
+      }
+
+      it { is_expected.to contain_file('/etc/apt/sources.list.d/my_source.list').that_notifies('Class[Apt::Update]') }
+
+      it {
+        is_expected.to contain_apt__pin('my_source').that_comes_before('Apt::Setting[list-my_source]').with(ensure: 'present',
+                                                                                                            priority: 1001,
+                                                                                                            explanation: 'wishwash',
+                                                                                                            release: 'wishwash')
+      }
+    end
+
+    context 'with simple key' do
+      let :params do
+        {
+          comment: 'foo',
+          location: 'http://debian.mirror.iweb.ca/debian/',
+          release: 'sid',
+          repos: 'testing',
+          key: GPG_KEY_ID,
+          pin: '10',
+          architecture: 'x86_64',
+          allow_unsigned: true,
+        }
+      end
+
+      it {
+        is_expected.to contain_apt__setting('list-my_source').with(ensure: 'present').with_content(%r{# foo\ndeb \[arch=x86_64 trusted=yes\] http://debian.mirror.iweb.ca/debian/ sid testing\n})
+                                                             .without_content(%r{deb-src})
+      }
+
+      it {
+        is_expected.to contain_apt__pin('my_source').that_comes_before('Apt::Setting[list-my_source]').with(ensure: 'present',
+                                                                                                            priority: '10',
+                                                                                                            origin: 'debian.mirror.iweb.ca')
+      }
+
+      it {
+        is_expected.to contain_apt__key("Add key: #{GPG_KEY_ID} from Apt::Source my_source").that_comes_before('Apt::Setting[list-my_source]').with(ensure: 'present',
+                                                                                                                                                    id: GPG_KEY_ID)
+      }
+    end
+
+    context 'with complex key' do
+      let :params do
+        {
+          comment: 'foo',
+          location: 'http://debian.mirror.iweb.ca/debian/',
+          release: 'sid',
+          repos: 'testing',
+          key: {
+            'ensure' => 'refreshed',
+            'id' => GPG_KEY_ID,
+            'server' => 'pgp.mit.edu',
+            'content' => 'GPG key content',
+            'source'  => 'http://apt.puppetlabs.com/pubkey.gpg',
+            'weak_ssl' => true,
+          },
+          pin: '10',
+          architecture: 'x86_64',
+          allow_unsigned: true,
+        }
+      end
+
+      it {
+        is_expected.to contain_apt__setting('list-my_source').with(ensure: 'present').with_content(%r{# foo\ndeb \[arch=x86_64 trusted=yes\] http://debian.mirror.iweb.ca/debian/ sid testing\n})
+                                                             .without_content(%r{deb-src})
+      }
+
+      it {
+        is_expected.to contain_apt__pin('my_source').that_comes_before('Apt::Setting[list-my_source]').with(ensure: 'present',
+                                                                                                            priority: '10',
+                                                                                                            origin: 'debian.mirror.iweb.ca')
+      }
+
+      it {
+        is_expected.to contain_apt__key("Add key: #{GPG_KEY_ID} from Apt::Source my_source").that_comes_before('Apt::Setting[list-my_source]').with(ensure: 'refreshed',
+                                                                                                                                                    id: GPG_KEY_ID,
+                                                                                                                                                    server: 'pgp.mit.edu',
+                                                                                                                                                    content: 'GPG key content',
+                                                                                                                                                    source: 'http://apt.puppetlabs.com/pubkey.gpg',
+                                                                                                                                                    weak_ssl: true)
+      }
+    end
+  end
+
+  context 'with allow_insecure true' do
+    let :params do
+      {
+        location: 'hello.there',
+        allow_insecure: true,
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source').with(ensure: 'present').with_content(%r{# my_source\ndeb \[allow-insecure=yes\] hello.there stretch main\n})
+    }
+  end
+
+  context 'with allow_unsigned true' do
+    let :params do
+      {
+        location: 'hello.there',
+        allow_unsigned: true,
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source').with(ensure: 'present').with_content(%r{# my_source\ndeb \[trusted=yes\] hello.there stretch main\n})
+    }
+  end
+
+  context 'with check_valid_until false' do
+    let :params do
+      {
+        location: 'hello.there',
+        check_valid_until: false,
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source').with(ensure: 'present').with_content(%r{# my_source\ndeb \[check-valid-until=false\] hello.there stretch main\n})
+    }
+  end
+
+  context 'with check_valid_until true' do
+    let :params do
+      {
+        location: 'hello.there',
+        check_valid_until: true,
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source').with(ensure: 'present').with_content(%r{# my_source\ndeb hello.there stretch main\n})
+    }
+  end
+
+  context 'with keyring set' do
+    let :params do
+      {
+        location: 'hello.there',
+        keyring: '/usr/share/keyrings/foo-archive-keyring.gpg',
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source')
+        .with(ensure: 'present')
+        .with_content(%r{# my_source\ndeb \[signed-by=/usr/share/keyrings/foo-archive-keyring.gpg\] hello.there stretch main\n})
+    }
+  end
+
+  context 'with keyring, architecture and allow_unsigned set' do
+    let :params do
+      {
+        location: 'hello.there',
+        architecture: 'amd64',
+        allow_unsigned: true,
+        keyring: '/usr/share/keyrings/foo-archive-keyring.gpg',
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source')
+        .with(ensure: 'present')
+        .with_content(%r{# my_source\ndeb \[arch=amd64 trusted=yes signed-by=/usr/share/keyrings/foo-archive-keyring.gpg\] hello.there stretch main\n})
+    }
+  end
+
+  context 'with a https location, install apt-transport-https' do
+    let :params do
+      {
+        location: 'HTTPS://foo.bar',
+        allow_unsigned: false,
+      }
+    end
+
+    it {
+      is_expected.to contain_package('apt-transport-https')
+    }
+  end
+
+  context 'with a https location and custom release, install apt-transport-https' do
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Debian',
+          release: {
+            major: '9',
+            full: '9.0',
+          },
+          distro: {
+            codename: 'stretch',
+            id: 'Debian',
+          },
+        },
+        puppetversion: Puppet.version,
+      }
+    end
+    let :params do
+      {
+        location: 'HTTPS://foo.bar',
+        allow_unsigned: false,
+        release: 'customrelease',
+      }
+    end
+
+    it {
+      is_expected.to contain_package('apt-transport-https')
+    }
+  end
+
+  context 'with a https location, do not install apt-transport-https on oses not in list eg buster' do
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Debian',
+          release: {
+            major: '10',
+            full: '10.0',
+          },
+          distro: {
+            codename: 'buster',
+            id: 'Debian',
+          },
+        },
+      }
+    end
+    let :params do
+      {
+        location: 'https://foo.bar',
+        allow_unsigned: false,
+      }
+    end
+
+    it {
+      is_expected.not_to contain_package('apt-transport-https')
+    }
+  end
+
+  context 'with architecture equals x86_64' do
+    let :facts do
+      {
+        os: {
+          family: 'Debian',
+          name: 'Debian',
+          release: {
+            major: '7',
+            full: '7.0',
+          },
+          distro: {
+            codename: 'wheezy',
+            id: 'Debian',
+          },
+        },
+      }
+    end
+    let :params do
+      {
+        location: 'hello.there',
+        include: { 'deb' => false, 'src' => true },
+        architecture: 'x86_64',
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source').with(ensure: 'present').with_content(%r{# my_source\ndeb-src \[arch=x86_64\] hello.there wheezy main\n})
+    }
+  end
+
+  context 'with architecture fact and unset architecture parameter' do
+    let :facts do
+      super().merge(architecture: 'amd64')
+    end
+    let :params do
+      {
+        location: 'hello.there',
+        include: { 'deb' => false, 'src' => true },
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source').with(ensure: 'present').with_content(%r{# my_source\ndeb-src hello.there stretch main\n})
+    }
+  end
+
+  context 'with include_src => true' do
+    let :params do
+      {
+        location: 'hello.there',
+        include: { 'src' => true },
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source').with(ensure: 'present').with_content(%r{# my_source\ndeb hello.there stretch main\ndeb-src hello.there stretch main\n})
+    }
+  end
+
+  context 'with include deb => false' do
+    let :params do
+      {
+        include: { 'deb' => false },
+        location: 'hello.there',
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source').with(ensure: 'present').without_content(%r{deb-src hello.there wheezy main\n})
+    }
+    it { is_expected.to contain_apt__setting('list-my_source').without_content(%r{deb hello.there wheezy main\n}) }
+  end
+
+  context 'with include src => true and include deb => false' do
+    let :params do
+      {
+        include: { 'deb' => false, 'src' => true },
+        location: 'hello.there',
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source').with(ensure: 'present').with_content(%r{deb-src hello.there stretch main\n})
+    }
+    it { is_expected.to contain_apt__setting('list-my_source').without_content(%r{deb hello.there stretch main\n}) }
+  end
+
+  context 'with ensure => absent' do
+    let :params do
+      {
+        ensure: 'absent',
+      }
+    end
+
+    it {
+      is_expected.to contain_apt__setting('list-my_source').with(ensure: 'absent')
+    }
+  end
+
+  describe 'validation' do
+    context 'with no release' do
+      let :facts do
+        {
+          os: {
+            family: 'Debian',
+            name: 'Debian',
+            release: {
+              major: '8',
+              full: '8.0',
+            },
+            distro: {
+              id: 'Debian',
+            },
+          },
+        }
+      end
+      let(:params) { { location: 'hello.there' } }
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{os.distro.codename fact not available: release parameter required})
+      end
+    end
+
+    context 'with release is empty string' do
+      let(:params) { { location: 'hello.there', release: '' } }
+
+      it { is_expected.to contain_apt__setting('list-my_source').with_content(%r{hello\.there  main}) }
+    end
+
+    context 'with invalid pin' do
+      let :params do
+        {
+          location: 'hello.there',
+          pin: true,
+        }
+      end
+
+      it do
+        is_expected.to raise_error(Puppet::Error, %r{expects a value})
+      end
+    end
+
+    context 'with notify_update = undef (default)' do
+      let :params do
+        {
+          location: 'hello.there',
+        }
+      end
+
+      it { is_expected.to contain_apt__setting("list-#{title}").with_notify_update(true) }
+    end
+
+    context 'with notify_update = true' do
+      let :params do
+        {
+          location: 'hello.there',
+          notify_update: true,
+        }
+      end
+
+      it { is_expected.to contain_apt__setting("list-#{title}").with_notify_update(true) }
+    end
+
+    context 'with notify_update = false' do
+      let :params do
+        {
+          location: 'hello.there',
+          notify_update: false,
+        }
+      end
+
+      it { is_expected.to contain_apt__setting("list-#{title}").with_notify_update(false) }
+    end
+  end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..07db734
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+RSpec.configure do |c|
+  c.mock_with :rspec
+end
+
+require 'puppetlabs_spec_helper/module_spec_helper'
+require 'rspec-puppet-facts'
+
+require 'spec_helper_local' if File.file?(File.join(File.dirname(__FILE__), 'spec_helper_local.rb'))
+
+include RspecPuppetFacts
+
+default_facts = {
+  puppetversion: Puppet.version,
+  facterversion: Facter.version,
+}
+
+default_fact_files = [
+  File.expand_path(File.join(File.dirname(__FILE__), 'default_facts.yml')),
+  File.expand_path(File.join(File.dirname(__FILE__), 'default_module_facts.yml')),
+]
+
+default_fact_files.each do |f|
+  next unless File.exist?(f) && File.readable?(f) && File.size?(f)
+
+  begin
+    default_facts.merge!(YAML.safe_load(File.read(f), [], [], true))
+  rescue => e
+    RSpec.configuration.reporter.message "WARNING: Unable to load #{f}: #{e}"
+  end
+end
+
+# read default_facts and merge them over what is provided by facterdb
+default_facts.each do |fact, value|
+  add_custom_fact fact, value
+end
+
+RSpec.configure do |c|
+  c.default_facts = default_facts
+  c.before :each do
+    # set to strictest setting for testing
+    # by default Puppet runs at warning level
+    Puppet.settings[:strict] = :warning
+    Puppet.settings[:strict_variables] = true
+  end
+  c.filter_run_excluding(bolt: true) unless ENV['GEM_BOLT']
+  c.after(:suite) do
+    RSpec::Puppet::Coverage.report!(0)
+  end
+
+  # Filter backtrace noise
+  backtrace_exclusion_patterns = [
+    %r{spec_helper},
+    %r{gems},
+  ]
+
+  if c.respond_to?(:backtrace_exclusion_patterns)
+    c.backtrace_exclusion_patterns = backtrace_exclusion_patterns
+  elsif c.respond_to?(:backtrace_clean_patterns)
+    c.backtrace_clean_patterns = backtrace_exclusion_patterns
+  end
+end
+
+# Ensures that a module is defined
+# @param module_name Name of the module
+def ensure_module_defined(module_name)
+  module_name.split('::').reduce(Object) do |last_module, next_module|
+    last_module.const_set(next_module, Module.new) unless last_module.const_defined?(next_module, false)
+    last_module.const_get(next_module, false)
+  end
+end
+
+# 'spec_overrides' from sync.yml will appear below this line
diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb
new file mode 100644
index 0000000..73a0238
--- /dev/null
+++ b/spec/spec_helper_acceptance.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+require 'puppet_litmus'
+PuppetLitmus.configure!
+
+require 'spec_helper_acceptance_local' if File.file?(File.join(File.dirname(__FILE__), 'spec_helper_acceptance_local.rb'))
diff --git a/spec/spec_helper_acceptance_local.rb b/spec/spec_helper_acceptance_local.rb
new file mode 100644
index 0000000..9f0cd18
--- /dev/null
+++ b/spec/spec_helper_acceptance_local.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+UNSUPPORTED_PLATFORMS = ['RedHat', 'Suse', 'windows', 'AIX', 'Solaris'].freeze
+RETRY_WAIT            = 3
+ERROR_MATCHER         = %r{(no valid OpenPGP data found|keyserver timed out|keyserver receive failed)}.freeze
+MAX_RETRY_COUNT       = 10
+
+RSpec.configure do |c|
+  c.before :suite do
+    # lsb-release is needed for facter 3 (puppet 6) to resolve os.distro facts. Not needed with facter
+    # 4 (puppet 7).
+    lsb_package = <<-MANIFEST
+package { 'lsb-release':
+  ensure => installed,
+}
+MANIFEST
+    include PuppetLitmus
+    extend PuppetLitmus
+    apply_manifest(lsb_package)
+  end
+end
+
+# This method allows a block to be passed in and if an exception is raised
+# that matches the 'error_matcher' matcher, the block will wait a set number
+# of seconds before retrying.
+# Params:
+# - max_retry_count - Max number of retries
+# - retry_wait_interval_secs - Number of seconds to wait before retry
+# - error_matcher - Matcher which the exception raised must match to allow retry
+# Example Usage:
+# retry_on_error_matching(3, 5, /OpenGPG Error/) do
+#   apply_manifest(pp, :catch_failures => true)
+# end
+
+def retry_on_error_matching(max_retry_count = MAX_RETRY_COUNT, retry_wait_interval_secs = RETRY_WAIT, error_matcher = ERROR_MATCHER)
+  try = 0
+  begin
+    puts "retry_on_error_matching: try #{try}" unless try.zero?
+    try += 1
+    yield
+  rescue StandardError => e
+    raise('Attempted this %{value0} times. Raising %{value1}' % { value0: max_retry_count, value1: e }) unless try < max_retry_count && (error_matcher.nil? || e.message =~ error_matcher)
+    sleep retry_wait_interval_secs
+    retry
+  end
+end
diff --git a/spec/spec_helper_local.rb b/spec/spec_helper_local.rb
new file mode 100644
index 0000000..ce4d062
--- /dev/null
+++ b/spec/spec_helper_local.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+if ENV['COVERAGE'] == 'yes'
+  require 'simplecov'
+  require 'simplecov-console'
+  require 'codecov'
+
+  SimpleCov.formatters = [
+    SimpleCov::Formatter::HTMLFormatter,
+    SimpleCov::Formatter::Console,
+    SimpleCov::Formatter::Codecov,
+  ]
+  SimpleCov.start do
+    track_files 'lib/**/*.rb'
+
+    add_filter '/spec'
+
+    # do not track vendored files
+    add_filter '/vendor'
+    add_filter '/.vendor'
+
+    # do not track gitignored files
+    # this adds about 4 seconds to the coverage check
+    # this could definitely be optimized
+    add_filter do |f|
+      # system returns true if exit status is 0, which with git-check-ignore means file is ignored
+      system("git check-ignore --quiet #{f.filename}")
+    end
+  end
+end
diff --git a/spec/unit/facter/apt_dist_has_updates_spec.rb b/spec/unit/facter/apt_dist_has_updates_spec.rb
new file mode 100644
index 0000000..3ed4f3b
--- /dev/null
+++ b/spec/unit/facter/apt_dist_has_updates_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt_has_dist_updates fact' do
+  subject { Facter.fact(:apt_has_dist_updates).value }
+
+  before(:each) { Facter.clear }
+
+  describe 'on non-Debian distro' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).once.and_return('Redhat')
+    end
+    it { is_expected.to be_nil }
+  end
+
+  describe 'on Debian based distro missing apt-get' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).once.and_return('Debian')
+      allow(File).to receive(:executable?) # Stub all other calls
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(false)
+    end
+    it { is_expected.to be_nil }
+  end
+
+  describe 'on Debian based distro' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).once.and_return('Debian')
+      allow(File).to receive(:executable?) # Stub all other calls
+      allow(Facter::Core::Execution).to receive(:execute) # Catch all other calls
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(true)
+      allow(Facter::Core::Execution).to receive(:execute).with('/usr/bin/apt-get -s -o Debug::NoLocking=true upgrade 2>&1').and_return('test')
+      apt_output = "Inst extremetuxracer [2015f-0+deb8u1] (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+                   "Conf extremetuxracer (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+                   "Inst planet.rb [13-1.1] (22-2~bpo8+1 Debian Backports:stretch-backports [all])\n" \
+                   "Conf planet.rb (22-2~bpo8+1 Debian Backports:stretch-backports [all])\n"
+      allow(Facter::Core::Execution).to receive(:execute).with('/usr/bin/apt-get -s -o Debug::NoLocking=true dist-upgrade 2>&1').and_return(apt_output)
+    end
+    it { is_expected.to be true }
+  end
+end
diff --git a/spec/unit/facter/apt_dist_package_security_updates_spec.rb b/spec/unit/facter/apt_dist_package_security_updates_spec.rb
new file mode 100644
index 0000000..f16ff52
--- /dev/null
+++ b/spec/unit/facter/apt_dist_package_security_updates_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt_package_security_dist_updates fact' do
+  subject { Facter.fact(:apt_package_security_dist_updates).value }
+
+  before(:each) { Facter.clear }
+
+  describe 'when apt has no updates' do
+    before(:each) do
+      allow(Facter.fact(:apt_has_dist_updates)).to receive(:value).and_return(false)
+    end
+    it { is_expected.to be nil }
+  end
+
+  describe 'when apt has updates' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).and_return('Debian')
+      allow(File).to receive(:executable?) # Stub all other calls
+      allow(Facter::Core::Execution).to receive(:execute) # Catch all other calls
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(true)
+      allow(Facter::Core::Execution).to receive(:execute).with('/usr/bin/apt-get -s -o Debug::NoLocking=true upgrade 2>&1').and_return('test')
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(true)
+      allow(Facter::Core::Execution).to receive(:execute).with('/usr/bin/apt-get -s -o Debug::NoLocking=true dist-upgrade 2>&1').and_return(apt_get_upgrade_output)
+    end
+
+    describe 'on Debian' do
+      let(:apt_get_upgrade_output) do
+        "Inst extremetuxracer [2015f-0+deb8u1] (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+          "Conf extremetuxracer (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+          "Inst planet.rb [13-1.1] (22-2~bpo8+1 Debian Backports:-backports [all])\n" \
+          "Conf planet.rb (22-2~bpo8+1 Debian Backports:-backports [all])\n" \
+          "Inst vim [7.52.1-5] (7.52.1-5+deb9u2 Debian-Security:9/stable [amd64]) []\n" \
+          "Conf vim (7.52.1-5+deb9u2 Debian-Security:9/stable [amd64])\n" \
+      end
+
+      it { is_expected.to eq(['vim']) }
+    end
+
+    describe 'on Ubuntu' do
+      let(:apt_get_upgrade_output) do
+        "Inst extremetuxracer [2016f-0ubuntu0.18.04] (2016j-0ubuntu0.18.04 Ubuntu:18.04/xenial-security, Ubuntu:18.04/xenial-updates [all])\n" \
+          "Conf extremetuxracer (2016j-0ubuntu0.18.04 Ubuntu:18.04/xenial-security, Ubuntu:18.04/xenial-updates [all])\n" \
+          "Inst vim [7.47.0-1ubuntu2] (7.47.0-1ubuntu2.2 Ubuntu:18.04/xenial-security [amd64]) []\n" \
+          "Conf vim (7.47.0-1ubuntu2.2 Ubuntu:18.04/xenial-security [amd64])\n" \
+          "Inst onioncircuits [2:3.3.10-4ubuntu2] (2:3.3.10-4ubuntu2.3 Ubuntu:18.04/xenial-updates [amd64])\n" \
+          "Conf onioncircuits (2:3.3.10-4ubuntu2.3 Ubuntu:18.04/xenial-updates [amd64])\n"
+      end
+
+      it { is_expected.to eq(['extremetuxracer', 'vim']) }
+    end
+  end
+end
diff --git a/spec/unit/facter/apt_dist_package_updates_spec.rb b/spec/unit/facter/apt_dist_package_updates_spec.rb
new file mode 100644
index 0000000..a27d607
--- /dev/null
+++ b/spec/unit/facter/apt_dist_package_updates_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt_package_dist_updates fact' do
+  subject { Facter.fact(:apt_package_dist_updates).value }
+
+  before(:each) { Facter.clear }
+
+  describe 'when apt has no updates' do
+    before(:each) do
+      allow(Facter.fact(:apt_has_dist_updates)).to receive(:value).and_return(false)
+    end
+    it { is_expected.to be nil }
+  end
+
+  describe 'when apt has updates' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).and_return('Debian')
+      allow(File).to receive(:executable?) # Stub all other calls
+      allow(Facter::Core::Execution).to receive(:execute) # Catch all other calls
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(true)
+      allow(Facter::Core::Execution).to receive(:execute).with('/usr/bin/apt-get -s -o Debug::NoLocking=true upgrade 2>&1').and_return('test')
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(true)
+      apt_output = "Inst extremetuxracer [2015f-0+deb8u1] (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+                   "Conf extremetuxracer (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+                   "Inst planet.rb [13-1.1] (22-2~bpo8+1 Debian Backports:-backports [all])\n" \
+                   "Conf planet.rb (22-2~bpo8+1 Debian Backports:-backports [all])\n"
+      allow(Facter::Core::Execution).to receive(:execute).with('/usr/bin/apt-get -s -o Debug::NoLocking=true dist-upgrade 2>&1').and_return(apt_output)
+    end
+    it { is_expected.to eq(['extremetuxracer', 'planet.rb']) }
+  end
+end
diff --git a/spec/unit/facter/apt_dist_security_updates_spec.rb b/spec/unit/facter/apt_dist_security_updates_spec.rb
new file mode 100644
index 0000000..aa21e64
--- /dev/null
+++ b/spec/unit/facter/apt_dist_security_updates_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt_security_updates fact' do
+  subject { Facter.fact(:apt_security_dist_updates).value }
+
+  before(:each) { Facter.clear }
+
+  describe 'when apt has no updates' do
+    before(:each) do
+      allow(Facter.fact(:apt_has_dist_updates)).to receive(:value).and_return(false)
+    end
+    it { is_expected.to be nil }
+  end
+
+  describe 'when apt has security updates' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).and_return('Debian')
+      allow(File).to receive(:executable?) # Stub all other calls
+      allow(Facter::Core::Execution).to receive(:execute) # Catch all other calls
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(true)
+      allow(Facter::Core::Execution).to receive(:execute).with('/usr/bin/apt-get -s -o Debug::NoLocking=true upgrade 2>&1').and_return('test')
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(true)
+      allow(Facter::Core::Execution).to receive(:execute).with('/usr/bin/apt-get -s -o Debug::NoLocking=true dist-upgrade 2>&1').and_return(apt_get_upgrade_output)
+    end
+
+    describe 'on Debian' do
+      let(:apt_get_upgrade_output) do
+        "Inst extremetuxracer [2015f-0+deb8u1] (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+          "Conf extremetuxracer (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+          "Inst planet.rb [13-1.1] (22-2~bpo8+1 Debian Backports:-backports [all])\n" \
+          "Conf planet.rb (22-2~bpo8+1 Debian Backports:-backports [all])\n" \
+          "Inst vim [7.52.1-5] (7.52.1-5+deb9u2 Debian-Security:9/stable [amd64]) []\n" \
+          "Conf vim (7.52.1-5+deb9u2 Debian-Security:9/stable [amd64])\n" \
+      end
+
+      it { is_expected.to eq(1) }
+    end
+
+    describe 'on Ubuntu' do
+      let(:apt_get_upgrade_output) do
+        "Inst extremetuxracer [2016f-0ubuntu0.18.04] (2016j-0ubuntu0.18.04 Ubuntu:18.04/xenial-security, Ubuntu:18.04/xenial-updates [all])\n" \
+          "Conf extremetuxracer (2016j-0ubuntu0.18.04 Ubuntu:18.04/xenial-security, Ubuntu:18.04/xenial-updates [all])\n" \
+          "Inst vim [7.47.0-1ubuntu2] (7.47.0-1ubuntu2.2 Ubuntu:18.04/xenial-security [amd64]) []\n" \
+          "Conf vim (7.47.0-1ubuntu2.2 Ubuntu:18.04/xenial-security [amd64])\n" \
+          "Inst onioncircuits [2:3.3.10-4ubuntu2] (2:3.3.10-4ubuntu2.3 Ubuntu:18.04/xenial-updates [amd64])\n" \
+          "Conf onioncircuits (2:3.3.10-4ubuntu2.3 Ubuntu:18.04/xenial-updates [amd64])\n"
+      end
+
+      it { is_expected.to eq(2) }
+    end
+  end
+end
diff --git a/spec/unit/facter/apt_dist_updates_spec.rb b/spec/unit/facter/apt_dist_updates_spec.rb
new file mode 100644
index 0000000..21352b5
--- /dev/null
+++ b/spec/unit/facter/apt_dist_updates_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt_updates fact' do
+  subject { Facter.fact(:apt_dist_updates).value }
+
+  before(:each) { Facter.clear }
+
+  describe 'when apt has no updates' do
+    before(:each) do
+      allow(Facter.fact(:apt_has_dist_updates)).to receive(:value).and_return(false)
+    end
+    it { is_expected.to be nil }
+  end
+
+  describe 'when apt has updates' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).and_return('Debian')
+      allow(File).to receive(:executable?) # Stub all other calls
+      allow(Facter::Core::Execution).to receive(:execute) # Catch all other calls
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(true)
+      allow(Facter::Core::Execution).to receive(:execute).with('/usr/bin/apt-get -s -o Debug::NoLocking=true upgrade 2>&1').and_return('test')
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(true)
+      apt_output = "Inst extremetuxracer [2015f-0+deb8u1] (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+                   "Conf extremetuxracer (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+                   "Inst planet.rb [13-1.1] (22-2~bpo8+1 Debian Backports:-backports [all])\n" \
+                   "Conf planet.rb (22-2~bpo8+1 Debian Backports:-backports [all])\n"
+      allow(Facter::Core::Execution).to receive(:execute).with('/usr/bin/apt-get -s -o Debug::NoLocking=true dist-upgrade 2>&1').and_return(apt_output)
+    end
+    it { is_expected.to eq(2) }
+  end
+end
diff --git a/spec/unit/facter/apt_has_updates_spec.rb b/spec/unit/facter/apt_has_updates_spec.rb
new file mode 100644
index 0000000..0f0a740
--- /dev/null
+++ b/spec/unit/facter/apt_has_updates_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt_has_updates fact' do
+  subject { Facter.fact(:apt_has_updates).value }
+
+  before(:each) { Facter.clear }
+
+  describe 'on non-Debian distro' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).once.and_return('Redhat')
+    end
+    it { is_expected.to be_nil }
+  end
+
+  describe 'on Debian based distro missing apt-get' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).once.and_return('Debian')
+      allow(File).to receive(:executable?) # Stub all other calls
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(false)
+    end
+    it { is_expected.to be_nil }
+  end
+
+  describe 'on Debian based distro' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).and_return('Debian')
+      allow(File).to receive(:executable?) # Stub all other calls
+      allow(Facter::Core::Execution).to receive(:execute) # Catch all other calls
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(true)
+      apt_output = "Inst tzdata [2015f-0+deb8u1] (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+                   "Conf tzdata (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+                   "Inst unhide.rb [13-1.1] (22-2~bpo8+1 Debian Backports:-backports [all])\n" \
+                   "Conf unhide.rb (22-2~bpo8+1 Debian Backports:-backports [all])\n"
+      allow(Facter::Core::Execution).to receive(:execute).with('/usr/bin/apt-get -s -o Debug::NoLocking=true upgrade 2>&1').and_return(apt_output)
+    end
+    it { is_expected.to be true }
+  end
+end
diff --git a/spec/unit/facter/apt_package_security_updates_spec.rb b/spec/unit/facter/apt_package_security_updates_spec.rb
new file mode 100644
index 0000000..28516ca
--- /dev/null
+++ b/spec/unit/facter/apt_package_security_updates_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt_package_security_updates fact' do
+  subject { Facter.fact(:apt_package_security_updates).value }
+
+  before(:each) { Facter.clear }
+
+  describe 'when apt has no updates' do
+    before(:each) do
+      allow(Facter.fact(:apt_has_updates)).to receive(:value).and_return(false)
+    end
+    it { is_expected.to be nil }
+  end
+
+  describe 'when apt has updates' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).and_return('Debian')
+      allow(File).to receive(:executable?) # Stub all other calls
+      allow(Facter::Core::Execution).to receive(:execute) # Catch all other calls
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(true)
+      allow(Facter::Core::Execution).to receive(:execute).with('/usr/bin/apt-get -s -o Debug::NoLocking=true upgrade 2>&1').and_return(apt_get_upgrade_output)
+    end
+
+    describe 'on Debian' do
+      let(:apt_get_upgrade_output) do
+        "Inst tzdata [2015f-0+deb8u1] (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+          "Conf tzdata (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+          "Inst unhide.rb [13-1.1] (22-2~bpo8+1 Debian Backports:-backports [all])\n" \
+          "Conf unhide.rb (22-2~bpo8+1 Debian Backports:-backports [all])\n" \
+          "Inst curl [7.52.1-5] (7.52.1-5+deb9u2 Debian-Security:9/stable [amd64]) []\n" \
+          "Conf curl (7.52.1-5+deb9u2 Debian-Security:9/stable [amd64])\n" \
+      end
+
+      it { is_expected.to eq(['curl']) }
+    end
+
+    describe 'on Ubuntu' do
+      let(:apt_get_upgrade_output) do
+        "Inst tzdata [2016f-0ubuntu0.18.04] (2016j-0ubuntu0.18.04 Ubuntu:18.04/xenial-security, Ubuntu:18.04/xenial-updates [all])\n" \
+          "Conf tzdata (2016j-0ubuntu0.18.04 Ubuntu:18.04/xenial-security, Ubuntu:18.04/xenial-updates [all])\n" \
+          "Inst curl [7.47.0-1ubuntu2] (7.47.0-1ubuntu2.2 Ubuntu:18.04/xenial-security [amd64]) []\n" \
+          "Conf curl (7.47.0-1ubuntu2.2 Ubuntu:18.04/xenial-security [amd64])\n" \
+          "Inst procps [2:3.3.10-4ubuntu2] (2:3.3.10-4ubuntu2.3 Ubuntu:18.04/xenial-updates [amd64])\n" \
+          "Conf procps (2:3.3.10-4ubuntu2.3 Ubuntu:18.04/xenial-updates [amd64])\n"
+      end
+
+      it { is_expected.to eq(['tzdata', 'curl']) }
+    end
+  end
+end
diff --git a/spec/unit/facter/apt_package_updates_spec.rb b/spec/unit/facter/apt_package_updates_spec.rb
new file mode 100644
index 0000000..69db490
--- /dev/null
+++ b/spec/unit/facter/apt_package_updates_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt_package_updates fact' do
+  subject { Facter.fact(:apt_package_updates).value }
+
+  before(:each) { Facter.clear }
+
+  describe 'when apt has no updates' do
+    before(:each) do
+      allow(Facter.fact(:apt_has_updates)).to receive(:value).and_return(false)
+    end
+    it { is_expected.to be nil }
+  end
+
+  describe 'when apt has updates' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).and_return('Debian')
+      allow(File).to receive(:executable?) # Stub all other calls
+      allow(Facter::Core::Execution).to receive(:execute) # Catch all other calls
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(true)
+      apt_output = "Inst tzdata [2015f-0+deb8u1] (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+                   "Conf tzdata (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+                   "Inst unhide.rb [13-1.1] (22-2~bpo8+1 Debian Backports:-backports [all])\n" \
+                   "Conf unhide.rb (22-2~bpo8+1 Debian Backports:-backports [all])\n"
+      allow(Facter::Core::Execution).to receive(:execute).with('/usr/bin/apt-get -s -o Debug::NoLocking=true upgrade 2>&1').and_return(apt_output)
+    end
+    it { is_expected.to eq(['tzdata', 'unhide.rb']) }
+  end
+end
diff --git a/spec/unit/facter/apt_reboot_required_spec.rb b/spec/unit/facter/apt_reboot_required_spec.rb
new file mode 100644
index 0000000..7621872
--- /dev/null
+++ b/spec/unit/facter/apt_reboot_required_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt_reboot_required fact' do
+  subject { Facter.fact(:apt_reboot_required).value }
+
+  before(:each) { Facter.clear }
+
+  describe 'if a reboot is required' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).and_return('Debian')
+      allow(File).to receive(:file?).and_return(true)
+      allow(File).to receive(:file?).once.with('/var/run/reboot-required').and_return(true)
+    end
+    it { is_expected.to eq true }
+  end
+
+  describe 'if a reboot is not required' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).and_return('Debian')
+      allow(File).to receive(:file?).and_return(true)
+      allow(File).to receive(:file?).once.with('/var/run/reboot-required').and_return(false)
+    end
+    it { is_expected.to eq false }
+  end
+end
diff --git a/spec/unit/facter/apt_security_updates_spec.rb b/spec/unit/facter/apt_security_updates_spec.rb
new file mode 100644
index 0000000..9d3e587
--- /dev/null
+++ b/spec/unit/facter/apt_security_updates_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt_security_updates fact' do
+  subject { Facter.fact(:apt_security_updates).value }
+
+  before(:each) { Facter.clear }
+
+  describe 'when apt has no updates' do
+    before(:each) do
+      allow(Facter.fact(:apt_has_updates)).to receive(:value).and_return(false)
+    end
+    it { is_expected.to be nil }
+  end
+
+  describe 'when apt has security updates' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).and_return('Debian')
+      allow(File).to receive(:executable?) # Stub all other calls
+      allow(Facter::Core::Execution).to receive(:execute) # Catch all other calls
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(true)
+      allow(Facter::Core::Execution).to receive(:execute).with('/usr/bin/apt-get -s -o Debug::NoLocking=true upgrade 2>&1').and_return(apt_get_upgrade_output)
+    end
+
+    describe 'on Debian' do
+      let(:apt_get_upgrade_output) do
+        "Inst tzdata [2015f-0+deb8u1] (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+          "Conf tzdata (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+          "Inst unhide.rb [13-1.1] (22-2~bpo8+1 Debian Backports:-backports [all])\n" \
+          "Conf unhide.rb (22-2~bpo8+1 Debian Backports:-backports [all])\n" \
+          "Inst curl [7.52.1-5] (7.52.1-5+deb9u2 Debian-Security:9/stable [amd64]) []\n" \
+          "Conf curl (7.52.1-5+deb9u2 Debian-Security:9/stable [amd64])\n" \
+      end
+
+      it { is_expected.to eq(1) }
+    end
+
+    describe 'on Ubuntu' do
+      let(:apt_get_upgrade_output) do
+        "Inst tzdata [2016f-0ubuntu0.18.04] (2016j-0ubuntu0.18.04 Ubuntu:18.04/xenial-security, Ubuntu:18.04/xenial-updates [all])\n" \
+          "Conf tzdata (2016j-0ubuntu0.18.04 Ubuntu:18.04/xenial-security, Ubuntu:18.04/xenial-updates [all])\n" \
+          "Inst curl [7.47.0-1ubuntu2] (7.47.0-1ubuntu2.2 Ubuntu:18.04/xenial-security [amd64]) []\n" \
+          "Conf curl (7.47.0-1ubuntu2.2 Ubuntu:18.04/xenial-security [amd64])\n" \
+          "Inst procps [2:3.3.10-4ubuntu2] (2:3.3.10-4ubuntu2.3 Ubuntu:18.04/xenial-updates [amd64])\n" \
+          "Conf procps (2:3.3.10-4ubuntu2.3 Ubuntu:18.04/xenial-updates [amd64])\n"
+      end
+
+      it { is_expected.to eq(2) }
+    end
+  end
+end
diff --git a/spec/unit/facter/apt_sources_spec.rb b/spec/unit/facter/apt_sources_spec.rb
new file mode 100644
index 0000000..32d431a
--- /dev/null
+++ b/spec/unit/facter/apt_sources_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt_sources fact' do
+  subject { Facter.fact(:apt_sources).value }
+
+  before(:each) { Facter.clear }
+
+  describe 'returns a list of .list files' do
+    let(:sources_raw) { ['/etc/apt/sources.list.d/puppet-tools.list', '/etc/apt/sources.list.d/some-cli.list'] }
+    let(:sources_want) { ['sources.list', 'puppet-tools.list', 'some-cli.list'] }
+
+    before(:each) do
+      allow(Dir).to receive(:glob).and_return(sources_raw)
+    end
+
+    it { is_expected.to eq(sources_want) }
+  end
+end
diff --git a/spec/unit/facter/apt_update_last_success_spec.rb b/spec/unit/facter/apt_update_last_success_spec.rb
new file mode 100644
index 0000000..c0b028a
--- /dev/null
+++ b/spec/unit/facter/apt_update_last_success_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt_update_last_success fact' do
+  subject { Facter.fact(:apt_update_last_success).value }
+
+  before(:each) { Facter.clear }
+
+  describe 'on Debian based distro which has not yet created the update-success-stamp file' do
+    it 'has a value of -1' do
+      allow(Facter.fact(:osfamily)).to receive(:value).and_return('Debian')
+      allow(File).to receive(:exist?).with('/var/lib/apt/periodic/update-success-stamp').and_return(false)
+      is_expected.to eq(-1)
+    end
+  end
+
+  describe 'on Debian based distro which has created the update-success-stamp' do
+    it 'has the value of the mtime of the file' do
+      allow(Facter.fact(:osfamily)).to receive(:value).and_return('Debian')
+      allow(File).to receive(:exist?).and_return(true)
+      allow(File).to receive(:mtime).and_return(1_407_660_561)
+      is_expected.to eq(1_407_660_561)
+    end
+  end
+end
diff --git a/spec/unit/facter/apt_updates_spec.rb b/spec/unit/facter/apt_updates_spec.rb
new file mode 100644
index 0000000..a8eacbf
--- /dev/null
+++ b/spec/unit/facter/apt_updates_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'apt_updates fact' do
+  subject { Facter.fact(:apt_updates).value }
+
+  before(:each) { Facter.clear }
+
+  describe 'when apt has no updates' do
+    before(:each) do
+      allow(Facter.fact(:apt_has_updates)).to receive(:value).and_return(false)
+    end
+    it { is_expected.to be nil }
+  end
+
+  describe 'when apt has updates' do
+    before(:each) do
+      allow(Facter.fact(:osfamily)).to receive(:value).and_return('Debian')
+      allow(File).to receive(:executable?) # Stub all other calls
+      allow(Facter::Core::Execution).to receive(:execute) # Catch all other calls
+      allow(File).to receive(:executable?).with('/usr/bin/apt-get').and_return(true)
+      apt_output = "Inst tzdata [2015f-0+deb8u1] (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+                   "Conf tzdata (2015g-0+deb8u1 Debian:stable-updates [all])\n" \
+                   "Inst unhide.rb [13-1.1] (22-2~bpo8+1 Debian Backports:-backports [all])\n" \
+                   "Conf unhide.rb (22-2~bpo8+1 Debian Backports:-backports [all])\n"
+      allow(Facter::Core::Execution).to receive(:execute).with('/usr/bin/apt-get -s -o Debug::NoLocking=true upgrade 2>&1').and_return(apt_output)
+    end
+    it { is_expected.to eq(2) }
+  end
+end
diff --git a/spec/unit/puppet/provider/apt_key_spec.rb b/spec/unit/puppet/provider/apt_key_spec.rb
new file mode 100644
index 0000000..b895817
--- /dev/null
+++ b/spec/unit/puppet/provider/apt_key_spec.rb
@@ -0,0 +1,218 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Puppet::Type.type(:apt_key).provider(:apt_key) do
+  describe 'instances' do
+    it 'has an instance method' do
+      expect(described_class).to respond_to :instances
+    end
+  end
+
+  describe 'prefetch' do
+    it 'has a prefetch method' do
+      expect(described_class).to respond_to :prefetch
+    end
+  end
+
+  context 'self.instances no key' do
+    before :each do
+      # Unable to remove `master` from below terminology as it relies on outside code
+      allow(described_class).to receive(:apt_key).with(
+        ['adv', '--no-tty', '--list-keys', '--with-colons', '--fingerprint', '--fixed-list-mode'],
+      ).and_return('uid:-::::1284991450::07BEBE04F4AE4A8E885A761325717D8509D9C1DC::Ubuntu Extras Archive Automatic Signing Key <ftpmaster@ubuntu.com>::::::::::0:')
+    end
+    it 'returns no resources' do
+      expect(described_class.instances.size).to eq(0)
+    end
+  end
+
+  context 'self.instances multiple keys' do
+    before :each do
+      command_output = <<-OUTPUT
+Executing: gpg --ignore-time-conflict --no-options --no-default-keyring --homedir /tmp/tmp.DU0GdRxjmE --no-auto-check-trustdb --trust-model always --keyring /etc/apt/trusted.gpg --primary-keyring /etc/apt/trusted.gpg --keyring /etc/apt/trusted.gpg.d/puppetlabs-pc1-keyring.gpg --no-tty --list-keys --with-colons --fingerprint --fixed-list-mode
+tru:t:1:1549900774:0:3:1:5
+pub:-:1024:17:40976EAF437D05B5:1095016255:::-:::scESC:
+fpr:::::::::630239CC130E1A7FD81A27B140976EAF437D05B5:
+uid:-::::1095016255::B84AE656F4F5A826C273A458512EF8E282754CE1::Ubuntu Archive Automatic Signing Key <ftpmaster@ubuntu.com>:
+sub:-:2048:16:251BEFF479164387:1095016263::::::e:
+pub:-:1024:17:46181433FBB75451:1104433784:::-:::scSC:
+fpr:::::::::C5986B4F1257FFA86632CBA746181433FBB75451:
+OUTPUT
+      allow(described_class).to receive(:apt_key).with(
+        ['adv', '--no-tty', '--list-keys', '--with-colons', '--fingerprint', '--fixed-list-mode'],
+      ).and_return(command_output)
+    end
+    it 'returns 2 resources' do
+      expect(described_class.instances.size).to eq(2)
+      expect(described_class.instances[0].name).to eq('630239CC130E1A7FD81A27B140976EAF437D05B5')
+      expect(described_class.instances[0].id).to eq('40976EAF437D05B5')
+      expect(described_class.instances[1].name).to eq('C5986B4F1257FFA86632CBA746181433FBB75451')
+      expect(described_class.instances[1].id).to eq('46181433FBB75451')
+    end
+  end
+
+  context 'create apt_key resource' do
+    it 'apt_key with content set and source nil' do
+      expect(described_class).to receive(:apt_key).with(['adv', '--no-tty',
+                                                         '--keyserver',
+                                                         :"keyserver.ubuntu.com",
+                                                         '--recv-keys',
+                                                         'C105B9DE'])
+      resource = Puppet::Type::Apt_key.new(name: 'source and content nil',
+                                           id: 'C105B9DE',
+                                           ensure: 'present')
+
+      provider = described_class.new(resource)
+      expect(provider).not_to be_exist
+      provider.create
+      expect(provider).to be_exist
+    end
+
+    it 'apt_key content and source nil, options set' do
+      expect(described_class).to receive(:apt_key).with(['adv', '--no-tty',
+                                                         '--keyserver',
+                                                         :"keyserver.ubuntu.com",
+                                                         '--keyserver-options',
+                                                         'jimno',
+                                                         '--recv-keys',
+                                                         'C105B9DE'])
+      resource = Puppet::Type::Apt_key.new(name: 'source and content nil',
+                                           id: 'C105B9DE',
+                                           options: 'jimno',
+                                           ensure: 'present')
+
+      provider = described_class.new(resource)
+      expect(provider).not_to be_exist
+      provider.create
+      expect(provider).to be_exist
+    end
+
+    it 'apt_key with content set' do
+      expect(described_class).to receive(:apt_key).with(array_including('add', kind_of(String)))
+      resource = Puppet::Type::Apt_key.new(name: 'gsd',
+                                           id: 'C105B9DE',
+                                           content: 'asad',
+                                           ensure: 'present')
+
+      provider = described_class.new(resource)
+      expect(provider).not_to be_exist
+      expect(provider).to receive(:tempfile).and_return(Tempfile.new('foo'))
+      provider.create
+      expect(provider).to be_exist
+    end
+
+    it 'apt_key with source set' do
+      expect(described_class).to receive(:apt_key).with(array_including('add', kind_of(String)))
+      resource = Puppet::Type::Apt_key.new(name: 'gsd',
+                                           id: 'C105B9DE',
+                                           source: 'ftp://bla/herpderp.gpg',
+                                           ensure: 'present')
+
+      provider = described_class.new(resource)
+      expect(provider).not_to be_exist
+      expect(provider).to receive(:source_to_file).and_return(Tempfile.new('foo'))
+      provider.create
+      expect(provider).to be_exist
+    end
+
+    it 'apt_key with source and weak ssl verify set' do
+      expect(described_class).to receive(:apt_key).with(array_including('add', kind_of(String)))
+      resource = Puppet::Type::Apt_key.new(name: 'gsd',
+                                           id: 'C105B9DE',
+                                           source: 'https://bla/herpderp.gpg',
+                                           ensure: 'present',
+                                           weak_ssl: true)
+
+      provider = described_class.new(resource)
+      expect(provider).not_to be_exist
+      expect(provider).to receive(:source_to_file).and_return(Tempfile.new('foo'))
+      provider.create
+      expect(provider).to be_exist
+    end
+
+    describe 'different valid id keys' do
+      hash_of_keys = {
+        '32bit key id' => 'EF8D349F',
+        '64bit key id' => '7F438280EF8D349F',
+        '160bit key fingerprint' => '6F6B15509CF8E59E6E469F327F438280EF8D349F',
+        '32bit key id lowercase' =>   'EF8D349F'.downcase,
+        '64bit key id lowercase' =>   '7F438280EF8D349F'.downcase,
+        '160bit key fingerprint lowercase' => '6F6B15509CF8E59E6E469F327F438280EF8D349F'.downcase,
+        '32bit key id 0x formatted' =>   '0xEF8D349F',
+        '64bit key id 0x formatted' =>   '0x7F438280EF8D349F',
+        '160bit key fingerprint 0x formatted' => '0x6F6B15509CF8E59E6E469F327F438280EF8D349F',
+      }
+      hash_of_keys.each do |key_type, value|
+        it "#{key_type} #{value} is valid" do
+          expect(described_class).to receive(:apt_key).with(array_including('adv', '--no-tty',
+                                                                            '--keyserver',
+                                                                            :"keyserver.ubuntu.com",
+                                                                            '--recv-keys'))
+          resource = Puppet::Type::Apt_key.new(name: 'source and content nil',
+                                               id: value,
+                                               ensure: 'present')
+
+          provider = described_class.new(resource)
+          expect(provider).not_to be_exist
+          provider.create
+          expect(provider).to be_exist
+        end
+      end
+    end
+
+    it 'apt_key with invalid key length' do
+      expect {
+        Puppet::Type::Apt_key.new(name: 'source and content nil',
+                                  id: '1',
+                                  ensure: 'present')
+      }.to raise_error(Puppet::ResourceError, %r{Parameter id failed on Apt_key})
+    end
+  end
+
+  context 'key_line_hash function' do
+    it 'matches rsa' do
+      expect(described_class.key_line_hash('pub:-:1024:1:40976EAF437D05B5:1095016255:::-:::scESC:', 'fpr:::::::::630239CC130E1A7FD81A27B140976EAF437D05B5:')).to include(
+        key_expiry: nil,
+        key_fingerprint: '630239CC130E1A7FD81A27B140976EAF437D05B5',
+        key_long: '40976EAF437D05B5',
+        key_short: '437D05B5',
+        key_size: '1024',
+        key_type: :rsa,
+      )
+    end
+
+    it 'matches dsa' do
+      expect(described_class.key_line_hash('pub:-:1024:17:40976EAF437D05B5:1095016255:::-:::scESC:', 'fpr:::::::::630239CC130E1A7FD81A27B140976EAF437D05B5:')).to include(
+        key_expiry: nil,
+        key_fingerprint: '630239CC130E1A7FD81A27B140976EAF437D05B5',
+        key_long: '40976EAF437D05B5',
+        key_short: '437D05B5',
+        key_size: '1024',
+        key_type: :dsa,
+      )
+    end
+
+    it 'matches ecc' do
+      expect(described_class.key_line_hash('pub:-:1024:18:40976EAF437D05B5:1095016255:::-:::scESC:', 'fpr:::::::::630239CC130E1A7FD81A27B140976EAF437D05B5:')).to include(
+        key_expiry: nil,
+        key_fingerprint: '630239CC130E1A7FD81A27B140976EAF437D05B5',
+        key_long: '40976EAF437D05B5',
+        key_short: '437D05B5',
+        key_size: '1024',
+        key_type: :ecc,
+      )
+    end
+
+    it 'matches ecdsa' do
+      expect(described_class.key_line_hash('pub:-:1024:19:40976EAF437D05B5:1095016255:::-:::scESC:', 'fpr:::::::::630239CC130E1A7FD81A27B140976EAF437D05B5:')).to include(
+        key_expiry: nil,
+        key_fingerprint: '630239CC130E1A7FD81A27B140976EAF437D05B5',
+        key_long: '40976EAF437D05B5',
+        key_short: '437D05B5',
+        key_size: '1024',
+        key_type: :ecdsa,
+      )
+    end
+  end
+end
diff --git a/spec/unit/puppet/type/apt_key_spec.rb b/spec/unit/puppet/type/apt_key_spec.rb
new file mode 100644
index 0000000..b20a09a
--- /dev/null
+++ b/spec/unit/puppet/type/apt_key_spec.rb
@@ -0,0 +1,245 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'puppet'
+
+describe Puppet::Type.type(:apt_key) do
+  context 'with only namevar 32bit key id' do
+    let(:resource) do
+      Puppet::Type.type(:apt_key).new(
+        id: 'EF8D349F',
+      )
+    end
+
+    it 'id is set' do
+      expect(resource[:id]).to eq 'EF8D349F'
+    end
+
+    it 'name is set to id' do
+      expect(resource[:name]).to eq 'EF8D349F'
+    end
+
+    it 'keyserver is default' do
+      expect(resource[:server]).to eq :'keyserver.ubuntu.com'
+    end
+
+    it 'source is not set' do
+      expect(resource[:source]).to eq nil
+    end
+
+    it 'content is not set' do
+      expect(resource[:content]).to eq nil
+    end
+
+    it 'refresh is not set' do
+      expect(resource[:refresh]).to eq nil
+    end
+
+    it 'weak_ssl is not set' do
+      expect(resource[:weak_ssl]).to eq nil
+    end
+  end
+
+  context 'with a lowercase 32bit key id' do
+    let(:resource) do
+      Puppet::Type.type(:apt_key).new(
+        id: 'ef8d349f',
+      )
+    end
+
+    it 'id is set' do
+      expect(resource[:id]).to eq 'EF8D349F'
+    end
+  end
+
+  context 'with a 64bit key id' do
+    let(:resource) do
+      Puppet::Type.type(:apt_key).new(
+        id: 'FFFFFFFFEF8D349F',
+      )
+    end
+
+    it 'id is set' do
+      expect(resource[:id]).to eq 'FFFFFFFFEF8D349F'
+    end
+  end
+
+  context 'with a 0x formatted key id' do
+    let(:resource) do
+      Puppet::Type.type(:apt_key).new(
+        id: '0xEF8D349F',
+      )
+    end
+
+    it 'id is set' do
+      expect(resource[:id]).to eq 'EF8D349F'
+    end
+  end
+
+  context 'with a 0x formatted lowercase key id' do
+    let(:resource) do
+      Puppet::Type.type(:apt_key).new(
+        id: '0xef8d349f',
+      )
+    end
+
+    it 'id is set' do
+      expect(resource[:id]).to eq 'EF8D349F'
+    end
+  end
+
+  context 'with a 0x formatted 64bit key id' do
+    let(:resource) do
+      Puppet::Type.type(:apt_key).new(
+        id: '0xFFFFFFFFEF8D349F',
+      )
+    end
+
+    it 'id is set' do
+      expect(resource[:id]).to eq 'FFFFFFFFEF8D349F'
+    end
+  end
+
+  context 'with source' do
+    let(:resource) do
+      Puppet::Type.type(:apt_key).new(
+        id: 'EF8D349F',
+        source: 'http://apt.puppetlabs.com/pubkey.gpg',
+      )
+    end
+
+    it 'source is set to the URL' do
+      expect(resource[:source]).to eq 'http://apt.puppetlabs.com/pubkey.gpg'
+    end
+  end
+
+  context 'with source and weak_ssl' do
+    let(:resource) do
+      Puppet::Type.type(:apt_key).new(
+        id: 'EF8D349F',
+        source: 'https://apt.puppetlabs.com/pubkey.gpg',
+        weak_ssl: true,
+      )
+    end
+
+    it 'source is set to the URL' do
+      expect(resource[:source]).to eq 'https://apt.puppetlabs.com/pubkey.gpg'
+    end
+  end
+
+  context 'with content' do
+    let(:resource) do
+      Puppet::Type.type(:apt_key).new(
+        id: 'EF8D349F',
+        content: 'http://apt.puppetlabs.com/pubkey.gpg',
+      )
+    end
+
+    it 'content is set to the string' do
+      expect(resource[:content]).to eq 'http://apt.puppetlabs.com/pubkey.gpg'
+    end
+  end
+
+  context 'with keyserver' do
+    let(:resource) do
+      Puppet::Type.type(:apt_key).new(
+        id: 'EF8D349F',
+        server: 'http://keyring.debian.org',
+      )
+    end
+
+    it 'keyserver is set to Debian' do
+      expect(resource[:server]).to eq 'http://keyring.debian.org'
+    end
+  end
+
+  context 'with validation' do
+    it 'raises an error if content and source are set' do
+      expect {
+        Puppet::Type.type(:apt_key).new(id: 'EF8D349F',
+                                        source: 'http://apt.puppetlabs.com/pubkey.gpg',
+                                        content: 'Completely invalid as a GPG key')
+      }.to raise_error(%r{content and source are mutually exclusive})
+    end
+
+    it 'raises an error if refresh => true and ensure => absent' do
+      expect {
+        Puppet::Type.type(:apt_key).new(id:       'EF8D349F',
+                                        source:   'http://apt.puppetlabs.com/pubkey.gpg',
+                                        ensure:   :absent,
+                                        refresh:  :true)
+      }.to raise_error(%r{ensure => absent and refresh => true are mutually exclusive})
+    end
+
+    it 'raises an error if a weird length key is used' do
+      expect {
+        Puppet::Type.type(:apt_key).new(id: 'FEF8D349F',
+                                        source: 'http://apt.puppetlabs.com/pubkey.gpg',
+                                        content: 'Completely invalid as a GPG key')
+      }.to raise_error(%r{Valid values match})
+    end
+
+    it 'raises an error when an invalid URI scheme is used in source' do
+      expect {
+        Puppet::Type.type(:apt_key).new(id: 'EF8D349F',
+                                        source: 'hkp://pgp.mit.edu')
+      }.to raise_error(%r{Valid values match})
+    end
+
+    it 'allows the http URI scheme in source' do
+      expect {
+        Puppet::Type.type(:apt_key).new(id: 'EF8D349F',
+                                        source: 'http://pgp.mit.edu')
+      }.not_to raise_error
+    end
+
+    it 'allows the http URI with username and password' do
+      expect {
+        Puppet::Type.type(:apt_key).new(id: '4BD6EC30',
+                                        source: 'http://testme:Password2@pgp.mit.edu')
+      }.not_to raise_error
+    end
+
+    it 'allows the https URI scheme in source' do
+      expect {
+        Puppet::Type.type(:apt_key).new(id: 'EF8D349F',
+                                        source: 'https://pgp.mit.edu')
+      }.not_to raise_error
+    end
+
+    it 'allows the https URI with username and password' do
+      expect {
+        Puppet::Type.type(:apt_key).new(id: 'EF8D349F',
+                                        source: 'https://testme:Password2@pgp.mit.edu')
+      }.not_to raise_error
+    end
+
+    it 'allows the ftp URI scheme in source' do
+      expect {
+        Puppet::Type.type(:apt_key).new(id: 'EF8D349F',
+                                        source: 'ftp://pgp.mit.edu')
+      }.not_to raise_error
+    end
+
+    it 'allows an absolute path in source' do
+      expect {
+        Puppet::Type.type(:apt_key).new(id: 'EF8D349F',
+                                        source: '/path/to/a/file')
+      }.not_to raise_error
+    end
+
+    it 'allows 5-digit ports' do
+      expect {
+        Puppet::Type.type(:apt_key).new(id: 'EF8D349F',
+                                        source: 'http://pgp.mit.edu:12345/key')
+      }.not_to raise_error
+    end
+
+    it 'allows 5-digit ports when using key servers' do
+      expect {
+        Puppet::Type.type(:apt_key).new(id: 'EF8D349F',
+                                        server: 'http://pgp.mit.edu:12345')
+      }.not_to raise_error
+    end
+  end
+end

Debdiff

File lists identical (after any substitutions)

No differences were encountered in the control files

More details

Full run details