diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 048d2b5..8b466cf 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -124,7 +124,7 @@ If you have Ruby 2.x or want a specific version of Puppet,
you must set an environment variable such as:
```sh
-export PUPPET_VERSION="~> 5.5.6"
+export PUPPET_GEM_VERSION="~> 6.1.0"
```
You can install all needed gems for spec tests into the modules directory by
@@ -232,17 +232,16 @@ simple tests against it after applying the module. You can run this
with:
```sh
-BEAKER_setfile=debian10-x64 bundle exec rake beaker
+BEAKER_setfile=debian11-64 bundle exec rake beaker
```
You can replace the string `debian10` with any common operating system.
The following strings are known to work:
-* ubuntu1604
* ubuntu1804
* ubuntu2004
-* debian9
* debian10
+* debian11
* centos7
* centos8
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d08d05e..8a07791 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,84 +7,12 @@ name: CI
on: pull_request
concurrency:
- group: ${{ github.head_ref }}
+ group: ${{ github.ref_name }}
cancel-in-progress: true
jobs:
- setup_matrix:
- name: 'Setup Test Matrix'
- runs-on: ubuntu-latest
- timeout-minutes: 40
- outputs:
- puppet_unit_test_matrix: ${{ steps.get-outputs.outputs.puppet_unit_test_matrix }}
- github_action_test_matrix: ${{ steps.get-outputs.outputs.github_action_test_matrix }}
- env:
- BUNDLE_WITHOUT: development:system_tests:release
- steps:
- - uses: actions/checkout@v2
- - name: Setup ruby
- uses: ruby/setup-ruby@v1
- with:
- ruby-version: '3.0'
- bundler-cache: true
- - name: Run static validations
- run: bundle exec rake validate lint check
- - name: Run rake rubocop
- run: bundle exec rake rubocop
- - name: Setup Test Matrix
- id: get-outputs
- run: bundle exec metadata2gha --use-fqdn --pidfile-workaround false
-
- unit:
- needs: setup_matrix
- runs-on: ubuntu-latest
- timeout-minutes: 40
- strategy:
- fail-fast: false
- matrix:
- include: ${{fromJson(needs.setup_matrix.outputs.puppet_unit_test_matrix)}}
- env:
- BUNDLE_WITHOUT: development:system_tests:release
- PUPPET_VERSION: "~> ${{ matrix.puppet }}.0"
- name: Puppet ${{ matrix.puppet }} (Ruby ${{ matrix.ruby }})
- steps:
- - uses: actions/checkout@v2
- - name: Setup ruby
- uses: ruby/setup-ruby@v1
- with:
- ruby-version: ${{ matrix.ruby }}
- bundler-cache: true
- - name: Run tests
- run: bundle exec rake parallel_spec
-
- acceptance:
- needs: setup_matrix
- runs-on: ubuntu-latest
- env:
- BUNDLE_WITHOUT: development:test:release
- strategy:
- fail-fast: false
- matrix:
- include: ${{fromJson(needs.setup_matrix.outputs.github_action_test_matrix)}}
- name: ${{ matrix.puppet.name }} - ${{ matrix.setfile.name }}
- steps:
- - uses: actions/checkout@v2
- - name: Setup ruby
- uses: ruby/setup-ruby@v1
- with:
- ruby-version: '3.0'
- bundler-cache: true
- - name: Run tests
- run: bundle exec rake beaker
- env:
- BEAKER_PUPPET_COLLECTION: ${{ matrix.puppet.collection }}
- BEAKER_setfile: ${{ matrix.setfile.value }}
-
- tests:
- needs:
- - unit
- - acceptance
- runs-on: ubuntu-latest
- name: Test suite
- steps:
- - run: echo Test suite completed
+ puppet:
+ name: Puppet
+ uses: voxpupuli/gha-puppet/.github/workflows/beaker.yml@v1
+ with:
+ pidfile_workaround: 'false'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9edb616..37438ee 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -9,26 +9,14 @@ on:
tags:
- '*'
-env:
- BUNDLE_WITHOUT: development:test:system_tests
-
jobs:
- deploy:
- name: 'deploy to forge'
- runs-on: ubuntu-latest
- if: github.repository_owner == 'root-expert'
- steps:
- - name: Checkout repository
- uses: actions/checkout@v2
- - name: Setup Ruby
- uses: ruby/setup-ruby@v1
- with:
- ruby-version: '2.7'
- bundler-cache: true
- - name: Build and Deploy
- env:
- # Configure secrets here:
- # https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets
- BLACKSMITH_FORGE_USERNAME: '${{ secrets.PUPPET_FORGE_USERNAME }}'
- BLACKSMITH_FORGE_API_KEY: '${{ secrets.PUPPET_FORGE_API_KEY }}'
- run: bundle exec rake module:push
+ release:
+ name: Release
+ uses: voxpupuli/gha-puppet/.github/workflows/release.yml@v1
+ with:
+ allowed_owner: 'root-expert'
+ secrets:
+ # Configure secrets here:
+ # https://docs.github.com/en/actions/security-guides/encrypted-secrets
+ username: ${{ secrets.PUPPET_FORGE_USERNAME }}
+ api_key: ${{ secrets.PUPPET_FORGE_API_KEY }}
diff --git a/.msync.yml b/.msync.yml
index ab186de..968a936 100644
--- a/.msync.yml
+++ b/.msync.yml
@@ -2,4 +2,4 @@
# Managed by modulesync - DO NOT EDIT
# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/
-modulesync_config_version: '5.0.1'
+modulesync_config_version: '5.2.0'
diff --git a/.sync.yml b/.sync.yml
index 130aaeb..d71e85b 100644
--- a/.sync.yml
+++ b/.sync.yml
@@ -1,4 +1,8 @@
---
+Gemfile:
+ optional:
+ ':test':
+ - gem: 'net_http_unix'
.puppet-lint.rc:
enabled_lint_checks:
- parameter_documentation
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..eba5c43
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,17 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+Each new release typically also includes the latest modulesync defaults.
+These should not affect the functionality of the module.
+
+## [v1.0.0](https://github.com/root-expert/puppet-snap/tree/v1.0.0) (2022-03-26)
+
+[Full Changelog](https://github.com/root-expert/puppet-snap/compare/613d2068319841ea636da5f22c16665311001304...v1.0.0)
+
+**Implemented enhancements:**
+
+- Add snap\_conf resource/Add CentOS 9 [\#4](https://github.com/root-expert/puppet-snap/pull/4) ([root-expert](https://github.com/root-expert))
+
+
+
+\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
diff --git a/Dockerfile b/Dockerfile
index e3cf307..8dd82d6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,7 +8,7 @@ WORKDIR /opt/puppet
# https://github.com/puppetlabs/puppet/blob/06ad255754a38f22fb3a22c7c4f1e2ce453d01cb/lib/puppet/provider/service/runit.rb#L39
RUN mkdir -p /etc/sv
-ARG PUPPET_VERSION="~> 6.0"
+ARG PUPPET_GEM_VERSION="~> 6.0"
ARG PARALLEL_TEST_PROCESSORS=4
# Cache gems
diff --git a/Gemfile b/Gemfile
index 2b731b9..342c26f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,14 +1,14 @@
# Managed by modulesync - DO NOT EDIT
# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/
-source ENV['GEM_SOURCE'] || "https://rubygems.org"
+source ENV['GEM_SOURCE'] || 'https://rubygems.org'
group :test do
- gem 'voxpupuli-test', '~> 4.0', :require => false
+ gem 'voxpupuli-test', '~> 5.0', :require => false
gem 'coveralls', :require => false
gem 'simplecov-console', :require => false
gem 'puppet_metadata', '~> 1.0', :require => false
- gem 'puppet-lint-param-docs', :require => false
+ gem 'net_http_unix', :require => false
end
group :development do
@@ -29,7 +29,7 @@ end
gem 'rake', :require => false
gem 'facter', ENV['FACTER_GEM_VERSION'], :require => false, :groups => [:test]
-puppetversion = ENV['PUPPET_VERSION'] || '>= 6.0'
+puppetversion = ENV['PUPPET_GEM_VERSION'] || '>= 6.0'
gem 'puppet', puppetversion, :require => false, :groups => [:test]
# vim: syntax=ruby
diff --git a/README.md b/README.md
index 5a455bd..566b7f2 100644
--- a/README.md
+++ b/README.md
@@ -2,11 +2,11 @@
[![Build Status](https://github.com/root-expert/puppet-snap/workflows/CI/badge.svg)](https://github.com/root-expert/puppet-snap/actions?query=workflow%3ACI)
[![Release](https://github.com/root-expert/puppet-snap/actions/workflows/release.yml/badge.svg)](https://github.com/root-expert/puppet-snap/actions/workflows/release.yml)
-[![Puppet Forge](https://img.shields.io/puppetforge/v/puppet/snap.svg)](https://forge.puppetlabs.com/puppet/snap)
-[![Puppet Forge - downloads](https://img.shields.io/puppetforge/dt/puppet/snap.svg)](https://forge.puppetlabs.com/puppet/snap)
-[![Puppet Forge - endorsement](https://img.shields.io/puppetforge/e/puppet/snap.svg)](https://forge.puppetlabs.com/puppet/snap)
-[![Puppet Forge - scores](https://img.shields.io/puppetforge/f/puppet/snap.svg)](https://forge.puppetlabs.com/puppet/snap)
-[![puppetmodule.info docs](http://www.puppetmodule.info/images/badge.png)](http://www.puppetmodule.info/m/puppet-snap)
+[![Puppet Forge](https://img.shields.io/puppetforge/v/rootexpert/snap.svg)](https://forge.puppet.com/modules/rootexpert/snap)
+[![Puppet Forge - downloads](https://img.shields.io/puppetforge/dt/rootexpert/snap.svg)](https://forge.puppet.com/modules/rootexpert/snap)
+[![Puppet Forge - endorsement](https://img.shields.io/puppetforge/e/rootexpert/snap.svg)](https://forge.puppet.com/modules/rootexpert/snap)
+[![Puppet Forge - scores](https://img.shields.io/puppetforge/f/rootexpert/snap.svg)](https://forge.puppet.com/modules/rootexpert/snap)
+[![puppetmodule.info docs](http://www.puppetmodule.info/images/badge.png)](http://www.puppetmodule.info/m/rootexpert-snap)
[![Apache-2 License](https://img.shields.io/github/license/root-expert/puppet-snap.svg)](LICENSE)
#### Table of Contents
@@ -40,7 +40,7 @@ the [Snapd REST API](https://snapcraft.io/docs/snapd-api) for managing snaps.
To install Snap and the core package:
```puppet
-class { 'snap': }
+include snap
```
If you are using a RedHat family OS you need to additionally install [puppet-epel](https://github.com/voxpupuli/puppet-epel)
@@ -89,9 +89,8 @@ To install from specific channel:
```puppet
package { 'hello-world':
- ensure => installed,
- provider => 'snap',
- install_options => ['channel=beta'],
+ ensure => 'beta',
+ provider => 'snap',
}
```
@@ -104,9 +103,19 @@ package { 'hello-world':
install_options => ['classic'],
}
```
-
Same applies for options `jailmode` and `devmode`
+This snippet
+```puppet
+package { 'hello-world':
+ ensure => latest,
+ provider => 'snap',
+ install_options => ['classic'],
+}
+```
+
+installs by default the `latest/stable` channel
+
## Reference
See [REFERENCE](https://github.com/root-expert/puppet-snap/blob/master/REFERENCE.md)
diff --git a/REFERENCE.md b/REFERENCE.md
index 30ab256..16f4529 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -8,6 +8,10 @@
* [`snap`](#snap)
+### Resource types
+
+* [`snap_conf`](#snap_conf): Manage snap configuration both system wide and snap specific.
+
## Classes
### `snap`
@@ -23,6 +27,7 @@ The following parameters are available in the `snap` class:
* [`service_enable`](#service_enable)
* [`core_snap_ensure`](#core_snap_ensure)
* [`manage_repo`](#manage_repo)
+* [`net_http_unix_ensure`](#net_http_unix_ensure)
##### `package_ensure`
@@ -62,5 +67,68 @@ Data type: `Boolean`
Whether we should manage EPEL repo or not.
-Default value: ``true``
+Default value: ``false``
+
+##### `net_http_unix_ensure`
+
+Data type: `Enum['present', 'installed', 'absent']`
+
+The state of net_http_unix gem.
+
+Default value: `'installed'`
+
+## Resource types
+
+### `snap_conf`
+
+Manage snap configuration both system wide and snap specific.
+
+#### Properties
+
+The following properties are available in the `snap_conf` type.
+
+##### `ensure`
+
+Valid values: `present`, `absent`
+
+The desired state of the snap configuration.
+
+Default value: `present`
+
+#### Parameters
+
+The following parameters are available in the `snap_conf` type.
+
+* [`conf`](#conf)
+* [`name`](#name)
+* [`provider`](#provider)
+* [`snap`](#snap)
+* [`value`](#value)
+
+##### `conf`
+
+Name of configuration option.
+
+Default value: `''`
+
+##### `name`
+
+namevar
+
+An unique name for this define.
+
+##### `provider`
+
+The specific backend to use for this `snap_conf` resource. You will seldom need to specify this --- Puppet will usually
+discover the appropriate provider for your platform.
+
+##### `snap`
+
+The snap to configure the value for. This can be the reserved name system for system wide configurations.
+
+Default value: `''`
+
+##### `value`
+
+Value of configuration option.
diff --git a/Rakefile b/Rakefile
index 80b799d..52a5be2 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,7 +1,7 @@
# Managed by modulesync - DO NOT EDIT
# https://voxpupuli.org/docs/updating-files-managed-with-modulesync/
-# Attempt to load voxupuli-test (which pulls in puppetlabs_spec_helper),
+# Attempt to load voxpupuli-test (which pulls in puppetlabs_spec_helper),
# otherwise attempt to load it directly.
begin
require 'voxpupuli/test/rake'
@@ -51,7 +51,7 @@ begin
config.future_release = "v#{metadata.version}" if metadata.version =~ /^\d+\.\d+.\d+$/
config.header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\nEach new release typically also includes the latest modulesync defaults.\nThese should not affect the functionality of the module."
config.exclude_labels = %w{duplicate question invalid wontfix wont-fix modulesync skip-changelog}
- config.user = 'voxpupuli'
+ config.user = 'root-expert'
config.project = metadata.metadata['name']
end
diff --git a/data/os/Fedora.yaml b/data/os/Fedora.yaml
new file mode 100644
index 0000000..4ab3ddb
--- /dev/null
+++ b/data/os/Fedora.yaml
@@ -0,0 +1,2 @@
+---
+snap::manage_repo: false
diff --git a/lib/puppet/feature/net_http_unix_lib.rb b/lib/puppet/feature/net_http_unix_lib.rb
new file mode 100644
index 0000000..6735fda
--- /dev/null
+++ b/lib/puppet/feature/net_http_unix_lib.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+require 'puppet/util/feature'
+
+Puppet.features.add(:net_http_unix_lib, libs: 'net_http_unix')
diff --git a/lib/puppet/feature/snapd_socket.rb b/lib/puppet/feature/snapd_socket.rb
new file mode 100644
index 0000000..87a6c0c
--- /dev/null
+++ b/lib/puppet/feature/snapd_socket.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'puppet/util/feature'
+
+Puppet.features.add(:snapd_socket) do
+ true if File.exist?('/run/snapd.socket')
+end
diff --git a/lib/puppet/provider/package/snap.rb b/lib/puppet/provider/package/snap.rb
index cf95083..1850467 100644
--- a/lib/puppet/provider/package/snap.rb
+++ b/lib/puppet/provider/package/snap.rb
@@ -1,191 +1,146 @@
# frozen_string_literal: true
require 'puppet/provider/package'
-require 'net/http'
-require 'socket'
-require 'json'
+require 'puppet_x/snap/api'
Puppet::Type.type(:package).provide :snap, parent: Puppet::Provider::Package do
desc "Package management via Snap.
This provider supports the `install_options` attribute, which allows snap's flags to be
- passed to Snap. Namely `classic`, `dangerous`, `devmode`, `jailmode`, `channel`."
+ passed to Snap. Namely `classic`, `dangerous`, `devmode`, `jailmode`.
+
+ The 'channel' install option is deprecated and will be removed in a future release.
+ "
commands snap_cmd: '/usr/bin/snap'
- has_feature :installable, :install_options, :uninstallable, :purgeable
+ has_feature :installable, :versionable, :install_options, :uninstallable, :purgeable, :upgradeable
+ confine feature: %i[net_http_unix_lib snapd_socket]
def self.instances
- instances = []
- snaps = installed_snaps
-
- snaps.each do |snap|
- instances << new(name: snap['name'], ensure: snap['version'], provider: 'snap')
+ Puppet.info('called instances')
+ @installed_snaps ||= installed_snaps
+ Puppet.info("installed_snaps = #{@installed_snaps}")
+ @installed_snaps.map do |snap|
+ new(name: snap['name'], ensure: snap['tracking-channel'], provider: 'snap')
end
-
- instances
end
def query
- instances = self.class.instances
- instances.each do |instance|
- return instance if instance.name == @resource[:name]
- end
-
- nil
+ Puppet.info('called query')
+ Puppet.info("@installed_snaps = #{installed_snaps}")
+ installed = @installed_snaps&.find { |it| it.name == @resource['name'] }
+ Puppet.info("installed #{installed}")
+ { ensure: installed.ensure, name: @resource[:name] } if installed
end
def install
- self.class.modify_snap('install', @resource[:name], @resource[:install_options])
+ Puppet.info('called install')
+ current_ensure = query&.dig(:ensure)
+ current_ensure ||= :absent
+
+ Puppet.info("current_ensure = #{current_ensure}")
+ # Refresh the snap if we changed the channel
+ if current_ensure != @resource[:ensure] && current_ensure != :absent
+ Puppet.info('modify snap')
+ modify_snap('refresh') # Refresh will switch the channel AND trigger a refresh immediately. TODO Implement switch?
+ else
+ Puppet.info('install snap')
+ modify_snap('install')
+ end
end
def update
- self.class.modify_snap('refresh', @resource[:name], @resource[:install_options])
+ install
end
def latest
- res = self.class.call_api('GET', "/v2/find?name=#{@resource[:name]}")
+ Puppet.info('called latest')
+ params = URI.encode_www_form(name: @resource[:name])
+ res = PuppetX::Snap::API.get("/v2/find?#{params}")
raise Puppet::Error, "Couldn't find latest version" if res['status-code'] != 200
- # Search latest version for the specified channel. If unspecified fallback to stable.c
+ # Search latest version for the specified channel. If channel is unspecified, fallback to latest/stable
channel = if @resource[:install_options].nil?
- 'stable'
+ 'latest/stable'
else
self.class.parse_channel(@resource[:install_options])
end
- selected_channel = res['result'][0]['channels']["latest/#{channel}"]
+
+ Puppet.info("channel = #{channel}")
+ selected_channel = res['result'].first&.dig('channels', channel)
+ Puppet.info("selected_channel = #{selected_channel}")
raise Puppet::Error, "No version in channel #{channel}" unless selected_channel
+ Puppet.info('Evaluating version')
+ Puppet.info("version = #{selected_channel['version']}")
# Return version
selected_channel['version']
end
def uninstall
- self.class.modify_snap('remove', @resource[:name])
+ modify_snap('remove', nil)
end
- # Purge differs from remove as it doesn't save snapshot with snap's data.
+ # Purge differs from remove as it doesn't save a snapshot with snap's data.
def purge
- self.class.modify_snap('remove', @resource[:name], ['purge'])
+ modify_snap('remove', ['purge'])
end
- def self.call_api(method, url, data = nil)
- socket = Net::BufferedIO.new(UNIXSocket.new('/run/snapd.socket'))
-
- request = if method == 'POST'
- req = Net::HTTP::Post.new(url)
- req.body = data.to_json
- req
- else
- Net::HTTP::Get.new(url)
- end
-
- request['Host'] = 'localhost'
- request['Accept'] = 'application/json'
- request['Content-Type'] = 'application/json'
- request.exec(socket, '1.1', url)
-
- response = nil
- retried = 0
- max_retries = 5
- # Read timeout can happen while installing core snap. The snap daemon briefly restarts
- # which drops the connection to the socket.
- loop do
- response = Net::HTTPResponse.read_new(socket)
- break unless response.is_a?(Net::HTTPContinue)
- rescue Net::ReadTimeout, Net::OpenTimeout
- raise Puppet::Error, "Got timeout wile calling the api #{retried} times! Giving up..." if retried > max_retries
-
- Puppet.debug('Got timeout while calling the api, retrying...')
- retried += 1
- retry
- end
- # rubocop:disable Lint/EmptyBlock
- response.reading_body(socket, request.response_body_permitted?) {}
- # rubocop:enable Lint/EmptyBlock
-
- JSON.parse(response.body)
+ def modify_snap(action, options = @resource[:install_options])
+ body = self.class.generate_request(action, determine_channel, options)
+ response = PuppetX::Snap::API.post("/v2/snaps/#{@resource[:name]}", body)
+ change_id = PuppetX::Snap::API.get_id_from_async_req(response)
+ PuppetX::Snap::API.complete(change_id)
end
- def self.installed_snaps
- res = call_api('GET', '/v2/snaps')
-
- raise Puppet::Error, "Could not find installed snaps (code: #{res['status-code']})" unless [200, 404].include?(res['status-code'])
-
- res['result'].map { |hash| hash.slice('name', 'version') } if res['status-code'] == 200
+ def determine_channel
+ channel = self.class.channel_from_ensure(@resource[:ensure])
+ channel ||= self.class.channel_from_options(@resource[:install_options])
+ channel ||= 'latest/stable'
+ channel
end
- # Helper method to return the change ID from a asynchronous request response.
- def self.get_id_from_async_req(request)
- # If the request failed raise an error
- raise Puppet::Error, "Request failed with #{request['result']['message']}" if request['type'] == 'error'
-
- request['change']
- end
-
- # Get the status of a change
- #
- # @param id The change ID to search for.
- def self.get_status(id)
- call_api('GET', "/v2/changes/#{id}")
- end
-
- # Queries the API for a specific change and waits until it has
- # been completed.
- #
- # @param id The change ID to search for.
- def self.complete(id)
- completed = false
- until completed
- res = get_status(id)
- case res['result']['status']
- when 'Do', 'Doing', 'Undoing', 'Undo'
- # Still running
- # Wait a little bit before hitting the API again!
- sleep(1)
- next
- when 'Abort', 'Hold', 'Error'
- raise Puppet::Error, "Error while executing the request #{res}"
- when 'Done'
- completed = true
- else
- raise Puppet::Error, "Unknown status #{res}"
- end
- end
- end
-
- def self.generate_request(action, options)
+ def self.generate_request(action, channel, options)
request = { 'action' => action }
+ request['channel'] = channel unless channel.nil?
if options
- channel = parse_channel(options)
- request['channel'] = channel unless channel.nil?
-
- # classic, devmode and jailmode params are only available for install, refresh, revert actions.
- if %w[install refresh revert].include?(action)
+ # classic, devmode and jailmode params are only
+ # available for install, refresh, revert actions.
+ case action
+ when 'install', 'refresh', 'revert'
request['classic'] = true if options.include?('classic')
request['devmode'] = true if options.include?('devmode')
request['jailmode'] = true if options.include?('jailmode')
+ when 'remove'
+ request['purge'] = true if options.include?('purge')
end
-
- request['purge'] = true if action == 'remove' && options.include?('purge')
end
request
end
- def self.modify_snap(action, name, options = nil)
- req = generate_request(action, options)
- response = call_api('POST', "/v2/snaps/#{name}", req)
- change_id = get_id_from_async_req(response)
- complete(change_id)
+ def self.channel_from_ensure(value)
+ value = value.to_s
+ case value
+ when 'present', 'absent', 'purged', 'installed', 'latest'
+ nil
+ else
+ value
+ end
end
- def self.parse_channel(options)
- if (channel = options.find { |e| %r{channel} =~ e })
- return channel.split('=')[1]
+ def self.channel_from_options(options)
+ options&.find { |e| %r{channel} =~ e }&.split('=')&.last&.tap do |ch|
+ Puppet.warning("Install option 'channel' is deprecated, use ensure => '#{ch}' instead.")
end
+ end
+
+ def self.installed_snaps
+ res = PuppetX::Snap::API.get('/v2/snaps')
+ raise Puppet::Error, "Could not find installed snaps (code: #{res['status-code']})" unless [200, 404].include?(res['status-code'])
- nil
+ res['status-code'] == 200 ? res['result'].map { |hash| hash.slice('name', 'tracking-channel') } : []
end
end
diff --git a/lib/puppet/provider/snap_conf/snap_conf.rb b/lib/puppet/provider/snap_conf/snap_conf.rb
new file mode 100644
index 0000000..6ffb6cd
--- /dev/null
+++ b/lib/puppet/provider/snap_conf/snap_conf.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'puppet_x/snap/api'
+
+Puppet::Type.type(:snap_conf).provide(:snap_conf) do
+ desc 'Manage snap configuration both system wide and snap specific.'
+
+ confine feature: %i[net_http_unix_lib snapd_socket]
+
+ def create
+ save_conf
+ end
+
+ def destroy
+ save_conf
+ end
+
+ def exists?
+ params = URI.encode_www_form(keys: @resource[:conf])
+ res = PuppetX::Snap::API.get("/v2/snaps/#{@resource[:snap]}/conf?#{params}")
+
+ case res['status-code']
+ when 200
+ # If we reached here the resource exists. If ensure == absent then return true in order to remove it
+ true if res['result'][@resource[:conf]] == @resource[:value] || @resource[:ensure] == :absent
+ when 400
+ return false if res['result']['kind'] == 'option-not-found'
+
+ raise Puppet::Error, "Error while executing the request #{res}"
+ else
+ raise Puppet::Error, "Error while executing the request #{res}"
+ end
+ end
+
+ def save_conf
+ value = if @resource[:ensure] == :absent
+ nil
+ else
+ @resource[:value]
+ end
+
+ data = {
+ @resource[:conf] => value
+ }
+
+ res = PuppetX::Snap::API.put("/v2/snaps/#{@resource[:snap]}/conf", data)
+ change_id = PuppetX::Snap::API.get_id_from_async_req(res)
+ PuppetX::Snap::API.complete(change_id)
+ end
+
+ def snap
+ @resource[:snap]
+ end
+
+ def snap=(value)
+ @resource[:snap] = value
+ save_conf
+ end
+
+ def conf
+ @resource[:conf]
+ end
+
+ def conf=(value)
+ @resource[:conf] = value
+ save_conf
+ end
+
+ def value
+ params = URI.encode_www_form(keys: @resource[:conf])
+ res = PuppetX::Snap::API.get("/v2/snaps/#{@resource[:snap]}/conf?#{params}")
+
+ case res['status-code']
+ when 200
+ res['result'][@resource[:conf]]
+ when 400
+ return nil if res['result']['kind'] == 'option-not-found'
+
+ raise Puppet::Error, "Error while executing the request #{res}"
+ else
+ raise Puppet::Error, "Error while executing the request #{res}"
+ end
+ end
+
+ def value=(value)
+ @resource[:value] = value
+ save_conf
+ end
+end
diff --git a/lib/puppet/type/snap_conf.rb b/lib/puppet/type/snap_conf.rb
new file mode 100644
index 0000000..305a4f0
--- /dev/null
+++ b/lib/puppet/type/snap_conf.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+Puppet::Type.newtype(:snap_conf) do
+ @doc = 'Manage snap configuration both system wide and snap specific.'
+
+ ensurable do
+ defaultvalues
+ defaultto :present
+ desc 'The desired state of the snap configuration.'
+ end
+
+ newparam(:name, namevar: true) do
+ desc 'An unique name for this define.'
+ end
+
+ newparam(:snap) do
+ desc 'The snap to configure the value for. This can be the reserved name system for system wide configurations.'
+ defaultto ''
+
+ validate do |value|
+ raise ArgumentError, 'snap parameter must not be empty!' if value == ''
+ end
+ end
+
+ newparam(:conf) do
+ desc 'Name of configuration option.'
+ defaultto ''
+
+ validate do |value|
+ raise ArgumentError, 'conf parameter must not be empty!' if value == ''
+ end
+ end
+
+ newparam(:value) do
+ desc 'Value of configuration option.'
+ end
+end
diff --git a/lib/puppet_x/snap/api.rb b/lib/puppet_x/snap/api.rb
new file mode 100644
index 0000000..ca32816
--- /dev/null
+++ b/lib/puppet_x/snap/api.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'net_http_unix' if Puppet.features.net_http_unix_lib?
+require 'socket'
+require 'json'
+
+module PuppetX
+ module Snap
+ module API
+ class << self
+ %w[post put get].each do |method|
+ define_method(method) do |url, data = nil|
+ request = Object.const_get("Net::HTTP::#{method.capitalize}").new(url)
+
+ request.body = data.to_json if data
+
+ request['Host'] = 'localhost'
+ request['Accept'] = 'application/json'
+ request['Content-Type'] = 'application/json'
+
+ call_api(request)
+ end
+ end
+
+ def call_api(request)
+ client = ::NetX::HTTPUnix.new('unix:///run/snapd.socket')
+
+ response = nil
+ retried = 0
+ max_retries = 5
+ # Read timeout can happen while installing core snap. The snap daemon briefly restarts
+ # which drops the connection to the socket.
+ loop do
+ response = client.request(request)
+ break unless response.is_a?(Net::HTTPContinue)
+ rescue Net::ReadTimeout, Net::OpenTimeout
+ raise Puppet::Error, "Got timeout wile calling the api #{retried} times! Giving up..." if retried > max_retries
+
+ Puppet.debug('Got timeout while calling the api, retrying...')
+ retried += 1
+ retry
+ end
+
+ JSON.parse(response.body)
+ end
+
+ # Helper method to return the change ID from a asynchronous request response.
+ def get_id_from_async_req(request)
+ # If the request failed raise an error
+ raise Puppet::Error, "Request failed with #{request['result']['message']}" if request['type'] == 'error'
+
+ request['change']
+ end
+
+ # Get the status of a change
+ #
+ # @param id The change ID to search for.
+ def get_status(id)
+ get("/v2/changes/#{id}")
+ end
+
+ # Queries the API for a specific change and waits until it has
+ # been completed.
+ #
+ # @param id The change ID to search for.
+ def complete(id)
+ completed = false
+ until completed
+ res = get_status(id)
+ case res['result']['status']
+ when 'Do', 'Doing', 'Undoing', 'Undo'
+ # Still running
+ # Wait a little bit before hitting the API again!
+ sleep(1)
+ next
+ when 'Abort', 'Hold', 'Error'
+ raise Puppet::Error, "Error while executing the request #{res}"
+ when 'Done'
+ completed = true
+ else
+ raise Puppet::Error, "Unknown status #{res}"
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/manifests/init.pp b/manifests/init.pp
index b7a4b93..c8b15aa 100644
--- a/manifests/init.pp
+++ b/manifests/init.pp
@@ -5,12 +5,14 @@
# @param service_enable Run the system service on boot.
# @param core_snap_ensure The state of the snap `core`.
# @param manage_repo Whether we should manage EPEL repo or not.
+# @param net_http_unix_ensure The state of net_http_unix gem.
class snap (
- String[1] $package_ensure = 'installed',
- Enum['stopped', 'running'] $service_ensure = 'running',
- Boolean $service_enable = true,
- String[1] $core_snap_ensure = 'installed',
- Boolean $manage_repo = false,
+ String[1] $package_ensure = 'installed',
+ Enum['stopped', 'running'] $service_ensure = 'running',
+ Boolean $service_enable = true,
+ String[1] $core_snap_ensure = 'installed',
+ Boolean $manage_repo = false,
+ Enum['present', 'installed', 'absent'] $net_http_unix_ensure = 'installed',
) {
if $manage_repo {
include epel
@@ -35,9 +37,13 @@
require => Package['snapd'],
}
- package { 'core':
+ -> package { 'net_http_unix':
+ ensure => $net_http_unix_ensure,
+ provider => 'puppet_gem',
+ }
+
+ -> package { 'core':
ensure => $core_snap_ensure,
provider => 'snap',
- require => Service['snapd'],
}
}
diff --git a/metadata.json b/metadata.json
index ca7e317..8a7cd00 100644
--- a/metadata.json
+++ b/metadata.json
@@ -1,6 +1,6 @@
{
- "name": "puppet-snap",
- "version": "0.1.0-rc0",
+ "name": "rootexpert-snap",
+ "version": "1.0.1-rc0",
"author": "root-expert",
"summary": "Puppet module for installing Snap and managing snap packages.",
"license": "Apache-2.0",
@@ -14,28 +14,39 @@
"operatingsystem": "RedHat",
"operatingsystemrelease": [
"7",
- "8"
+ "8",
+ "9"
]
},
{
"operatingsystem": "CentOS",
"operatingsystemrelease": [
"7",
- "8"
+ "8",
+ "9"
]
},
{
"operatingsystem": "OracleLinux",
"operatingsystemrelease": [
"7",
- "8"
+ "8",
+ "9"
]
},
{
"operatingsystem": "Scientific",
"operatingsystemrelease": [
"7",
- "8"
+ "8",
+ "9"
+ ]
+ },
+ {
+ "operatingsystem": "Fedora",
+ "operatingsystemrelease": [
+ "33",
+ "34"
]
},
{
diff --git a/spec/acceptance/01_snapd_spec.rb b/spec/acceptance/01_snapd_spec.rb
new file mode 100644
index 0000000..96f6ea2
--- /dev/null
+++ b/spec/acceptance/01_snapd_spec.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+require 'spec_helper_acceptance'
+
+describe 'snapd class' do
+ context 'with default parameters' do
+ let(:manifest) { "class {'snap': }" }
+
+ it_behaves_like 'an idempotent resource'
+
+ describe package('snapd') do
+ it { is_expected.to be_installed }
+ end
+
+ describe service('snapd') do
+ it { is_expected.to be_running }
+ it { is_expected.to be_enabled }
+ end
+
+ describe file('/run/snapd.socket') do
+ it { is_expected.to be_socket }
+ end
+ end
+
+ context 'package resource' do
+ describe 'installs package' do
+ let(:manifest) do
+ <<-PUPPET
+ package { 'hello-world':
+ ensure => installed,
+ provider => snap,
+ }
+ PUPPET
+ end
+
+ it_behaves_like 'an idempotent resource'
+
+ describe command('snap list --unicode=never --color=never') do
+ its(:stdout) { is_expected.to match(%r{hello-world}) }
+ end
+ end
+
+ describe 'uninstalls package' do
+ let(:manifest) do
+ <<-PUPPET
+ package { 'hello-world':
+ ensure => absent,
+ provider => snap,
+ }
+ PUPPET
+ end
+
+ it_behaves_like 'an idempotent resource'
+
+ describe command('snap list --unicode=never --color=never') do
+ its(:stdout) { is_expected.not_to match(%r{hello-world}) }
+ end
+ end
+
+ describe 'installs package with specified version' do
+ let(:manifest) do
+ <<-PUPPET
+ package { 'hello-world':
+ ensure => 'latest/candidate',
+ provider => snap,
+ }
+ PUPPET
+ end
+
+ it_behaves_like 'an idempotent resource'
+
+ describe command('snap list --unicode=never --color=never') do
+ its(:stdout) do
+ is_expected.to match(%r{hello-world})
+ is_expected.to match(%r{candidate})
+ end
+ end
+ end
+
+ describe 'changes installed channel' do
+ let(:manifest) do
+ <<-PUPPET
+ package { 'hello-world':
+ ensure => 'latest/beta',
+ provider => snap,
+ }
+ PUPPET
+ end
+
+ it_behaves_like 'an idempotent resource'
+
+ describe command('snap list --unicode=never --color=never') do
+ its(:stdout) do
+ is_expected.to match(%r{hello-world})
+ is_expected.to match(%r{beta})
+ end
+ end
+ end
+ end
+
+ describe 'purges the package' do
+ let(:manifest) do
+ <<-PUPPET
+ package { 'hello-world':
+ ensure => purged,
+ provider => snap,
+ }
+ PUPPET
+ end
+
+ it_behaves_like 'an idempotent resource'
+
+ describe command('snap list --unicode=never --color=never') do
+ its(:stdout) { is_expected.not_to match(%r{hello-world}) }
+ end
+ end
+
+ describe 'installs latest/stable when ensure: latest' do
+ let(:manifest) do
+ <<-PUPPET
+ package { 'hello-world':
+ ensure => latest,
+ provider => snap,
+ }
+ PUPPET
+ end
+
+ it_behaves_like 'an idempotent resource'
+
+ describe command('snap list --unicode=never --color=never') do
+ its(:stdout) do
+ is_expected.to match(%r{hello-world})
+ is_expected.to match(%r{stable})
+ end
+ end
+ end
+end
diff --git a/spec/acceptance/02_snap_conf_spec.rb b/spec/acceptance/02_snap_conf_spec.rb
new file mode 100644
index 0000000..6d20ae0
--- /dev/null
+++ b/spec/acceptance/02_snap_conf_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper_acceptance'
+
+describe 'snap_conf resource' do
+ context 'snap_conf' do
+ let(:manifest) do
+ <<-EOS
+ snap_conf { 'test1':
+ ensure => present,
+ snap => 'system',
+ conf => 'refresh.retain',
+ value => '3'
+ }
+ EOS
+ end
+
+ it_behaves_like 'an idempotent resource'
+
+ describe command('snap get system refresh.retain') do
+ its(:stdout) { is_expected.to match %r{3} }
+ end
+ end
+
+ context 'destroy resource' do
+ let(:manifest) do
+ <<-EOS
+ snap_conf { 'test1':
+ ensure => absent,
+ snap => 'system',
+ conf => 'refresh.retain',
+ }
+ EOS
+ end
+
+ it_behaves_like 'an idempotent resource'
+
+ describe command('snap get system refresh.retain') do
+ its(:stderr) { is_expected.to match %r{error: snap "core" has no "refresh.retain" configuration option} }
+ end
+ end
+end
diff --git a/spec/acceptance/snapd_spec.rb b/spec/acceptance/snapd_spec.rb
deleted file mode 100644
index 66c0ab9..0000000
--- a/spec/acceptance/snapd_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper_acceptance'
-
-describe 'snapd class' do
- context 'with default parameters' do
- let(:manifest) { "class {'snap': }" }
-
- it_behaves_like 'an idempotent resource'
-
- describe package('snapd') do
- it { is_expected.to be_installed }
- end
-
- describe service('snapd') do
- it { is_expected.to be_running }
- it { is_expected.to be_enabled }
- end
-
- describe file('/run/snapd.socket') do
- it { is_expected.to be_socket }
- end
- end
-end
diff --git a/spec/classes/init_spec.rb b/spec/classes/init_spec.rb
index a25e324..1b931f7 100644
--- a/spec/classes/init_spec.rb
+++ b/spec/classes/init_spec.rb
@@ -15,7 +15,8 @@
it { is_expected.to contain_package('snapd').with_ensure('installed') }
it { is_expected.to contain_service('snapd').with_ensure('running').with_enable(true).that_requires('Package[snapd]') }
- it { is_expected.to contain_package('core').with_ensure('installed').with_provider('snap').that_requires('Service[snapd]') }
+ it { is_expected.to contain_package('net_http_unix').with_ensure('installed').with_provider('puppet_gem').that_requires('Service[snapd]') }
+ it { is_expected.to contain_package('core').with_ensure('installed').with_provider('snap').that_requires(%w[Service[snapd] Package[net_http_unix]]) }
end
end
end
diff --git a/spec/fixtures/responses/find_res.json b/spec/fixtures/responses/find_res.json
deleted file mode 100644
index 43238af..0000000
--- a/spec/fixtures/responses/find_res.json
+++ /dev/null
@@ -1,128 +0,0 @@
-{
- "type": "sync",
- "status-code": 200,
- "status": "OK",
- "result": [
- {
- "id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
- "title": "Hello World",
- "summary": "The 'hello-world' of snaps",
- "description": "This is a simple hello world example.",
- "download-size": 20480,
- "icon": "https://dashboard.snapcraft.io/site_media/appmedia/2015/03/hello.svg_NZLfWbh.png",
- "name": "hello-world",
- "publisher": {
- "id": "canonical",
- "username": "canonical",
- "display-name": "Canonical",
- "validation": "verified"
- },
- "store-url": "https://snapcraft.io/hello-world",
- "developer": "canonical",
- "status": "available",
- "type": "app",
- "version": "6.4",
- "channel": "stable",
- "ignore-validation": false,
- "revision": "29",
- "confinement": "strict",
- "private": false,
- "devmode": false,
- "jailmode": false,
- "contact": "mailto:snaps@canonical.com",
- "license": "MIT",
- "media": [
- {
- "type": "icon",
- "url": "https://dashboard.snapcraft.io/site_media/appmedia/2015/03/hello.svg_NZLfWbh.png",
- "width": 256,
- "height": 256
- },
- {
- "type": "screenshot",
- "url": "https://dashboard.snapcraft.io/site_media/appmedia/2018/06/Screenshot_from_2018-06-14_09-33-31.png",
- "width": 199,
- "height": 118
- },
- {
- "type": "video",
- "url": "https://vimeo.com/194577403"
- }
- ],
- "channels": {
- "latest/beta": {
- "revision": "29",
- "confinement": "strict",
- "version": "6.0",
- "channel": "latest/beta",
- "epoch": {
- "read": [
- 0
- ],
- "write": [
- 0
- ]
- },
- "size": 20480,
- "released-at": "2019-04-17T16:48:09.90685Z"
- },
- "latest/candidate": {
- "revision": "29",
- "confinement": "strict",
- "version": "6.4",
- "channel": "latest/candidate",
- "epoch": {
- "read": [
- 0
- ],
- "write": [
- 0
- ]
- },
- "size": 20480,
- "released-at": "2019-04-17T16:47:59.117114Z"
- },
- "latest/edge": {
- "revision": "29",
- "confinement": "strict",
- "version": "6.4",
- "channel": "latest/edge",
- "epoch": {
- "read": [
- 0
- ],
- "write": [
- 0
- ]
- },
- "size": 20480,
- "released-at": "2019-04-17T16:44:33.84163Z"
- },
- "latest/stable": {
- "revision": "29",
- "confinement": "strict",
- "version": "6.4",
- "channel": "latest/stable",
- "epoch": {
- "read": [
- 0
- ],
- "write": [
- 0
- ]
- },
- "size": 20480,
- "released-at": "2019-04-17T16:47:59.117114Z"
- }
- },
- "tracks": [
- "latest"
- ],
- "install-date": "2021-09-14T13:32:22.871938558+03:00"
- }
- ],
- "sources": [
- "store"
- ],
- "suggested-currency": "USD"
-}
diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb
index 2d49afa..fa9d1b9 100644
--- a/spec/spec_helper_acceptance.rb
+++ b/spec/spec_helper_acceptance.rb
@@ -27,7 +27,7 @@
apply_manifest_on(host, pp, catch_failures: true)
apply_manifest_on(host, debian, catch_failures: true) if fact('os.family') == 'Debian'
- install_module_from_forge_on(host, 'puppet/epel', '>= 3.1.0 < 4.0.0') if fact('os.family') == 'RedHat'
+ install_module_from_forge_on(host, 'puppet/epel', '>= 3.1.0 < 5.0.0') if fact('os.family') == 'RedHat'
end
Dir['./spec/support/acceptance/**/*.rb'].sort.each { |f| require f }
diff --git a/spec/unit/puppet/provider/package/snap_spec.rb b/spec/unit/puppet/provider/package/snap_spec.rb
index 1e444ae..329ca57 100644
--- a/spec/unit/puppet/provider/package/snap_spec.rb
+++ b/spec/unit/puppet/provider/package/snap_spec.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
+require 'puppet_x/snap/api'
describe Puppet::Type.type(:package).provider(:snap) do
let(:name) { 'hello-world' }
@@ -16,18 +17,17 @@
resource.provider
end
- async_change_id_res = JSON.parse(File.read('spec/fixtures/responses/async_change_id_res.json'))
- error_res = JSON.parse(File.read('spec/fixtures/responses/error_res.json'))
- change_status_doing = JSON.parse(File.read('spec/fixtures/responses/change_status_doing.json'))
- change_status_done = JSON.parse(File.read('spec/fixtures/responses/change_status_done.json'))
- change_status_error = JSON.parse(File.read('spec/fixtures/responses/change_status_error.json'))
- find_res = JSON.parse(File.read('spec/fixtures/responses/find_res.json'))
+ before do
+ allow(PuppetX::Snap::API).to receive(:get).with('/v2/snaps').and_return('[]')
+ end
context 'should have provider features' do
it { is_expected.to be_installable }
+ it { is_expected.to be_versionable }
it { is_expected.to be_install_options }
it { is_expected.to be_uninstallable }
it { is_expected.to be_purgeable }
+ it { is_expected.to be_upgradable }
end
context 'should respond to' do
@@ -50,64 +50,47 @@
context 'installing without any option' do
it 'generates correct request' do
- response = provider.class.generate_request('install', nil)
+ response = provider.class.generate_request('install', nil, nil)
expect(response).to eq('action' => 'install')
end
end
- context 'installing with channel option' do
+ context 'installing with channel' do
it 'generates correct request' do
- response = provider.class.generate_request('install', ['channel=beta'])
+ response = provider.class.generate_request('install', 'beta', nil)
expect(response).to eq('action' => 'install', 'channel' => 'beta')
end
end
context 'installing with classic option' do
it 'generates correct request' do
- response = provider.class.generate_request('install', ['classic'])
+ response = provider.class.generate_request('install', nil, ['classic'])
expect(response).to eq('action' => 'install', 'classic' => true)
end
end
- context 'calling async operations' do
- it 'raises an error if response is an error' do
- expect { provider.class.get_id_from_async_req(error_res) }.to raise_error(Puppet::Error)
- end
-
- it 'gets correct change id from response' do
- id = provider.class.get_id_from_async_req(async_change_id_res)
- expect(id).to eq('77')
- end
- end
-
- context 'completing async operations' do
- it 'raises an error if response is an error' do
- allow(described_class).to receive(:get_status).with('10').and_return(change_status_error)
-
- expect { provider.class.complete('10') }.to raise_error(Puppet::Error)
+ context 'decides the correct channel usage' do
+ it 'with no channel specified returns correct ensure value' do
+ expect(provider.determine_channel).to eq('latest/stable')
end
- it 'sleeps for 1 second if response hasn\'t completed' do
- allow(described_class).to receive(:get_status).with('10').and_return(change_status_doing, change_status_done)
- allow(described_class).to receive(:sleep)
- provider.class.complete('10')
+ it 'with channel specified in ensure returns correct ensure value' do
+ resource[:ensure] = 'latest/beta'
- expect(described_class).to have_received(:sleep).with(1)
+ expect(provider.determine_channel).to eq('latest/beta')
end
- end
- context 'querying for latest version' do
- it 'with no channel specified returns correct version from stable channel' do
- allow(described_class).to receive(:call_api).with('GET', '/v2/find?name=hello-world').and_return(find_res)
+ it 'with channel specified in install options returns correct ensure value' do
+ resource[:install_options] = ['channel=latest/beta']
- expect(provider.latest).to eq('6.4')
+ expect(provider.determine_channel).to eq('latest/beta')
end
- it 'with channel specified returns correct version from specified channel' do
- resource[:install_options] = ['channel=beta']
- allow(described_class).to receive(:call_api).with('GET', '/v2/find?name=hello-world').and_return(find_res)
+ it 'with channel specified in both ensure install options returns correct ensure value' do
+ resource[:install_options] = ['channel=latest/beta']
+ resource[:ensure] = 'latest/candidate' # this should be preferred
- expect(provider.latest).to eq('6.0')
+ expect(provider.determine_channel).to eq('latest/candidate')
end
end
end
diff --git a/spec/unit/puppet/provider/snap_conf/snap_conf_spec.rb b/spec/unit/puppet/provider/snap_conf/snap_conf_spec.rb
new file mode 100644
index 0000000..e059cbe
--- /dev/null
+++ b/spec/unit/puppet/provider/snap_conf/snap_conf_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'puppet_x/snap/api'
+
+describe Puppet::Type.type(:snap_conf) do
+ let(:name) { 'hello-world' }
+ let(:resource) do
+ Puppet::Type.type(:snap_conf).new(
+ name: name,
+ snap: 'system',
+ conf: 'refresh.retain',
+ value: '3'
+ )
+ end
+ let(:provider) do
+ resource.provider
+ end
+
+ it 'defaults to ensure => present' do
+ expect(resource[:ensure]).to eq :present
+ end
+end
diff --git a/spec/unit/puppet_x/snap/api_spec.rb b/spec/unit/puppet_x/snap/api_spec.rb
new file mode 100644
index 0000000..ad89b27
--- /dev/null
+++ b/spec/unit/puppet_x/snap/api_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'puppet_x/snap/api'
+
+module PuppetX::Snap
+ describe API do
+ async_change_id_res = JSON.parse(File.read('spec/fixtures/responses/async_change_id_res.json'))
+ error_res = JSON.parse(File.read('spec/fixtures/responses/error_res.json'))
+ change_status_doing = JSON.parse(File.read('spec/fixtures/responses/change_status_doing.json'))
+ change_status_done = JSON.parse(File.read('spec/fixtures/responses/change_status_done.json'))
+ change_status_error = JSON.parse(File.read('spec/fixtures/responses/change_status_error.json'))
+
+ context 'calling async operations' do
+ it 'raises an error if response is an error' do
+ expect { described_class.get_id_from_async_req(error_res) }.to raise_error(Puppet::Error)
+ end
+
+ it 'gets correct change id from response' do
+ id = described_class.get_id_from_async_req(async_change_id_res)
+ expect(id).to eq('77')
+ end
+ end
+
+ context 'completing async operations' do
+ it 'raises an error if response is an error' do
+ allow(described_class).to receive(:get_status).with('10').and_return(change_status_error)
+
+ expect { described_class.complete('10') }.to raise_error(Puppet::Error)
+ end
+
+ it 'sleeps for 1 second if response hasn\'t completed' do
+ allow(described_class).to receive(:get_status).with('10').and_return(change_status_doing, change_status_done)
+ allow(described_class).to receive(:sleep)
+ described_class.complete('10')
+
+ expect(described_class).to have_received(:sleep).with(1)
+ end
+ end
+ end
+end