├── .rspec ├── features ├── examples │ ├── test │ │ ├── manifests │ │ │ └── init.pp │ │ └── metadata.json │ ├── metadata_syntax │ │ ├── manifests │ │ │ └── init.pp │ │ ├── Puppetfile │ │ └── metadata.json │ ├── path_dependencies │ │ ├── manifests │ │ │ └── init.pp │ │ ├── Puppetfile │ │ └── metadata.json │ ├── with_puppetfile │ │ ├── manifests │ │ │ └── init.pp │ │ ├── metadata.json │ │ └── Puppetfile │ ├── duplicated_dependencies │ │ ├── manifests │ │ │ └── init.pp │ │ ├── Puppetfile │ │ └── metadata.json │ ├── dependency_without_version │ │ ├── manifests │ │ │ └── init.pp │ │ └── metadata.json │ ├── duplicated_dependencies_transitive │ │ ├── manifests │ │ │ └── init.pp │ │ ├── metadata.json │ │ └── Puppetfile │ └── with_puppetfile_and_metadata_json │ │ ├── manifests │ │ └── init.pp │ │ ├── Puppetfile │ │ └── metadata.json ├── version.feature ├── help.feature ├── init.feature ├── support │ └── env.rb ├── step_definitions │ └── convergence_steps.rb ├── install │ ├── github_tarball.feature │ ├── path.feature │ ├── git.feature │ └── forge.feature ├── outdated.feature ├── package.feature ├── install.feature └── update.feature ├── .github ├── labeler.yml ├── dependabot.yml ├── release.yml └── workflows │ ├── test.yml │ └── release.yml ├── lib └── librarian │ ├── puppet │ ├── version.rb │ ├── action.rb │ ├── source.rb │ ├── extension.rb │ ├── source │ │ ├── path.rb │ │ ├── repo.rb │ │ ├── git.rb │ │ ├── forge │ │ │ ├── repo_v3.rb │ │ │ ├── repo_v1.rb │ │ │ └── repo.rb │ │ ├── githubtarball.rb │ │ ├── local.rb │ │ ├── forge.rb │ │ └── githubtarball │ │ │ └── repo.rb │ ├── action │ │ ├── install.rb │ │ └── resolve.rb │ ├── resolver.rb │ ├── templates │ │ └── Puppetfile │ ├── dependency.rb │ ├── environment.rb │ ├── util.rb │ ├── lockfile.rb │ ├── dsl.rb │ └── cli.rb │ └── puppet.rb ├── .rubocop.yml ├── .gitignore ├── spec ├── spec_helper.rb ├── util_spec.rb ├── source │ └── forge_spec.rb ├── action │ └── resolve_spec.rb └── receiver_spec.rb ├── test ├── test_helper.rb └── librarian │ └── puppet │ └── source │ └── githubtarball_test.rb ├── bin └── librarian-puppet ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── librarian-puppet.gemspec ├── Rakefile ├── README.md ├── HISTORY.md ├── .rubocop_todo.yml └── CHANGELOG.md /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /features/examples/test/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class test {} 2 | -------------------------------------------------------------------------------- /features/examples/metadata_syntax/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class test {} 2 | -------------------------------------------------------------------------------- /features/examples/path_dependencies/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class test {} 2 | -------------------------------------------------------------------------------- /features/examples/with_puppetfile/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class test {} 2 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | skip-changelog: 3 | - head-branch: ['^rel*'] 4 | -------------------------------------------------------------------------------- /features/examples/duplicated_dependencies/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class test {} 2 | -------------------------------------------------------------------------------- /features/examples/dependency_without_version/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class test {} 2 | -------------------------------------------------------------------------------- /features/examples/duplicated_dependencies_transitive/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class test {} 2 | -------------------------------------------------------------------------------- /features/examples/with_puppetfile_and_metadata_json/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class test {} 2 | -------------------------------------------------------------------------------- /features/examples/metadata_syntax/Puppetfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | forge 'https://forgeapi.puppet.com' 4 | 5 | metadata 6 | -------------------------------------------------------------------------------- /features/examples/path_dependencies/Puppetfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | mod 'librarian/test', path: '../../features/examples/test' 4 | -------------------------------------------------------------------------------- /features/examples/duplicated_dependencies/Puppetfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | forge 'https://forgeapi.puppetlabs.com' 4 | 5 | metadata 6 | -------------------------------------------------------------------------------- /lib/librarian/puppet/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Librarian 4 | module Puppet 5 | VERSION = '6.0.0' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | inherit_from: .rubocop_todo.yml 3 | 4 | inherit_gem: 5 | voxpupuli-rubocop: rubocop.yml 6 | 7 | AllCops: 8 | TargetRubyVersion: 3.2 9 | -------------------------------------------------------------------------------- /lib/librarian/puppet/action.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'librarian/puppet/action/install' 4 | require 'librarian/puppet/action/resolve' 5 | -------------------------------------------------------------------------------- /features/examples/with_puppetfile_and_metadata_json/Puppetfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | forge 'https://forgeapi.puppet.com' 4 | 5 | mod 'maestrodev/test' 6 | -------------------------------------------------------------------------------- /features/examples/with_puppetfile/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "librarian-with_puppetfile", 3 | "version": "0.0.1", 4 | "license": "Apache 2.0", 5 | "dependencies": [] 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /pkg/ 2 | Gemfile.lock 3 | /tmp/ 4 | /coverage/ 5 | *.gem 6 | /.bundle/ 7 | /.vendor/ 8 | /.librarian/ 9 | /.tmp/ 10 | /Puppetfile 11 | /Puppetfile.lock 12 | /modules/ 13 | -------------------------------------------------------------------------------- /features/examples/with_puppetfile/Puppetfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | mod 'librarian/test', git: 'https://github.com/voxpupuli/librarian-puppet.git', path: 'features/examples/test' 4 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'simplecov' 4 | SimpleCov.start 5 | 6 | require 'rubygems' 7 | require 'rspec' 8 | 9 | require 'librarian/puppet' 10 | -------------------------------------------------------------------------------- /features/version.feature: -------------------------------------------------------------------------------- 1 | Feature: cli/version 2 | 3 | Scenario: Getting the version 4 | When I successfully run `librarian-puppet version` 5 | And the output should contain "librarian-" 6 | -------------------------------------------------------------------------------- /features/examples/path_dependencies/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "librarian-path_dependencies", 3 | "version": "0.0.1", 4 | "license": "Apache 2.0", 5 | "dependencies": [], 6 | "requirements": [] 7 | } 8 | -------------------------------------------------------------------------------- /features/examples/duplicated_dependencies_transitive/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "librarian-duplicated_dependencies_transitive", 3 | "version": "0.0.1", 4 | "license": "Apache 2.0", 5 | "dependencies": [] 6 | } 7 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'minitest/autorun' 4 | require 'minitest/spec' 5 | require 'mocha/minitest' 6 | 7 | $LOAD_PATH << 'vendor/librarian/lib' 8 | require 'librarian/puppet' 9 | -------------------------------------------------------------------------------- /bin/librarian-puppet: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | lib = File.expand_path('../lib', __dir__) 5 | $:.unshift(lib) unless $:.include?(lib) 6 | 7 | require 'librarian/puppet/cli' 8 | Librarian::Puppet::Cli.bin! 9 | -------------------------------------------------------------------------------- /lib/librarian/puppet/source.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'librarian/puppet/source/path' 4 | require 'librarian/puppet/source/git' 5 | require 'librarian/puppet/source/forge' 6 | require 'librarian/puppet/source/githubtarball' 7 | -------------------------------------------------------------------------------- /features/examples/dependency_without_version/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "librarian-test", 3 | "version": "0.0.1", 4 | "license": "Apache 2.0", 5 | "dependencies": [ 6 | { 7 | "name": "puppetlabs/stdlib" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /lib/librarian/puppet/extension.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'librarian/puppet/environment' 4 | require 'librarian/action/base' 5 | 6 | module Librarian 7 | module Puppet 8 | extend self 9 | extend Librarian 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /features/examples/test/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "librarian-test", 3 | "version": "0.0.1", 4 | "license": "Apache 2.0", 5 | "dependencies": [ 6 | { 7 | "name": "puppetlabs/stdlib", 8 | "version_requirement": ">= 0" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /lib/librarian/puppet.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'librarian' 4 | require 'fileutils' 5 | 6 | require 'librarian/puppet/extension' 7 | require 'librarian/puppet/version' 8 | 9 | require 'librarian/action/install' 10 | 11 | module Librarian 12 | module Puppet 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /features/examples/with_puppetfile_and_metadata_json/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "librarian-with_puppetfile_and_metadata_json", 3 | "version": "0.0.1", 4 | "license": "Apache 2.0", 5 | "dependencies": [ 6 | { 7 | "name": "maestrodev/test", 8 | "version_requirement": ">= 0" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /lib/librarian/puppet/source/path.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'librarian/source/path' 4 | require 'librarian/puppet/source/local' 5 | 6 | module Librarian 7 | module Puppet 8 | module Source 9 | class Path < Librarian::Source::Path 10 | include Local 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /features/examples/duplicated_dependencies_transitive/Puppetfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | forge 'https://forgeapi.puppet.com' 4 | 5 | metadata 6 | 7 | mod 'librarian-duplicated_dependencies', git: 'https://github.com/voxpupuli/librarian-puppet.git', 8 | path: 'features/examples/duplicated_dependencies' 9 | -------------------------------------------------------------------------------- /features/examples/duplicated_dependencies/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "librarian-duplicated_dependencies", 3 | "version": "0.0.1", 4 | "license": "Apache 2.0", 5 | "dependencies": [ 6 | { 7 | "name": "ripienaar-concat", 8 | "version_requirement": ">= 0" 9 | }, 10 | { 11 | "name": "puppetlabs-concat", 12 | "version_requirement": "1.2.0" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # raise PRs for gem updates 4 | - package-ecosystem: bundler 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | time: "13:00" 9 | open-pull-requests-limit: 10 10 | 11 | # Maintain dependencies for GitHub Actions 12 | - package-ecosystem: github-actions 13 | directory: "/" 14 | schedule: 15 | interval: daily 16 | time: "13:00" 17 | open-pull-requests-limit: 10 18 | -------------------------------------------------------------------------------- /spec/util_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Librarian::Puppet::Util do 6 | subject { Class.new { include Librarian::Puppet::Util }.new } 7 | 8 | it 'gets organization name' do 9 | expect(subject.module_name('puppetlabs-xy')).to eq('xy') 10 | end 11 | 12 | it 'gets organization name when org contains dashes' do 13 | expect(subject.module_name('puppet-labs-xy')).to eq('xy') 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /features/help.feature: -------------------------------------------------------------------------------- 1 | Feature: displays help if no subcommand is passed 2 | In order to get started using librarian-puppet 3 | A user should be able to run librarian-puppet without any subcommands or options 4 | Then the exit status should be 0 5 | And a useful help screen should be displayed 6 | 7 | Scenario: App defaults to help subcommand 8 | When I successfully run `librarian-puppet` 9 | And the output should contain "librarian-puppet version" 10 | -------------------------------------------------------------------------------- /features/init.feature: -------------------------------------------------------------------------------- 1 | Feature: init subcommand should generate a Puppetfile 2 | In order to start using librarian-puppet in a project 3 | A project will need a Puppetfile. 4 | If a user runs "librarian-puppet init" 5 | Then the exit status should be 0 6 | And a file named "Puppetfile" should exist 7 | 8 | 9 | Scenario: init subcommand should generate a Puppetfile 10 | When I successfully run `librarian-puppet init` 11 | Then a file named "Puppetfile" should exist 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Librarian-puppet 2 | 3 | ## Reporting Issues 4 | 5 | Bug reports to the github issue tracker please. 6 | Please include: 7 | 8 | * Relevant `Puppetfile` and `Puppetfile.lock` files 9 | * Version of ruby, librarian-puppet, and puppet 10 | * What distro 11 | * Please run the `librarian-puppet` commands in verbose mode by using the 12 | `--verbose` flag, and include the verbose output in the bug report as well. 13 | 14 | 15 | ## How to Contribute 16 | 17 | * Pull requests please. 18 | * Bonus points for feature branches. 19 | 20 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | # mention puppet here and in the gemspec, so we can provide a specific version in CI 6 | 7 | gem 'openvox', ENV.fetch('OPENVOX_VERSION', ['>= 8.22', '< 9']) 8 | 9 | gemspec 10 | 11 | group :release, optional: true do 12 | gem 'faraday-retry', '~> 2.1', require: false 13 | gem 'github_changelog_generator', '~> 1.16.4', require: false 14 | end 15 | 16 | # https://github.com/OpenVoxProject/puppet/issues/90 17 | gem 'syslog', '>= 0.2', '< 1' if RUBY_VERSION >= '3.4' 18 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'aruba/cucumber' 4 | require 'fileutils' 5 | 6 | Before do 7 | @aruba_timeout_seconds = 120 8 | end 9 | 10 | Before('@spaces') do 11 | @dirs = ['tmp/aruba with spaces'] 12 | @dirs.each { |dir| FileUtils.rm_rf dir } 13 | end 14 | 15 | Given(/^PENDING/) do 16 | pending 17 | end 18 | 19 | Given(/^there is no Puppetfile$/) do 20 | in_current_directory do 21 | raise "Puppetfile exists at #{File.expand_path('Puppetfile')}" if File.exist?('Puppetfile') 22 | end 23 | end 24 | 25 | ENV['LIBRARIAN_PUPPET_TMP'] = '.tmp' 26 | -------------------------------------------------------------------------------- /lib/librarian/puppet/action/install.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'librarian/action/install' 4 | 5 | module Librarian 6 | module Puppet 7 | module Action 8 | class Install < Librarian::Action::Install 9 | private 10 | 11 | def create_install_path 12 | install_path.rmtree if install_path.exist? && destructive? 13 | install_path.mkpath 14 | end 15 | 16 | def destructive? 17 | environment.config_db.local['destructive'] == 'true' 18 | end 19 | 20 | def check_specfile 21 | # don't fail if Puppetfile doesn't exist as we'll use metadata.json 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/librarian/puppet/resolver.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'librarian/resolver' 4 | 5 | module Librarian 6 | module Puppet 7 | class Resolver < Librarian::Resolver 8 | class Implementation < Librarian::Resolver::Implementation 9 | def sourced_dependency_for(dependency) 10 | return dependency if dependency.source 11 | 12 | source = dependency_source_map[dependency.name] || default_source 13 | dependency.class.new(dependency.name, dependency.requirement, source, dependency.parent) 14 | end 15 | end 16 | 17 | def implementation(spec) 18 | Implementation.new(self, spec, cyclic: cyclic) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/librarian/puppet/templates/Puppetfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # ^syntax detection 5 | 6 | forge 'https://forgeapi.puppet.com' 7 | 8 | # use dependencies defined in metadata.json 9 | metadata 10 | 11 | # A module from the Puppet Forge 12 | # mod 'puppetlabs-stdlib' 13 | 14 | # A module from git 15 | # mod 'puppetlabs-ntp', 16 | # :git => 'git://github.com/puppetlabs/puppetlabs-ntp.git' 17 | 18 | # A module from a git branch/tag 19 | # mod 'puppetlabs-apt', 20 | # :git => 'https://github.com/puppetlabs/puppetlabs-apt.git', 21 | # :ref => '1.4.x' 22 | 23 | # A module from Github pre-packaged tarball 24 | # mod 'puppetlabs-apache', '0.6.0', :github_tarball => 'puppetlabs/puppetlabs-apache' 25 | -------------------------------------------------------------------------------- /lib/librarian/puppet/dependency.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Librarian 4 | module Puppet 5 | class Dependency < Librarian::Dependency 6 | include Librarian::Puppet::Util 7 | 8 | attr_accessor :parent 9 | private :parent= 10 | 11 | def initialize(name, requirement, source, parent = nil) 12 | # Issue #235 fail if forge source is not defined 13 | raise Error, 'forge entry is not defined in Puppetfile' if source.instance_of?(Array) && source.empty? 14 | 15 | self.parent = parent 16 | super(normalize_name(name), requirement, source) 17 | end 18 | 19 | def to_s 20 | "#{name} (#{requirement}) <#{source}> (from #{parent.nil? ? '' : parent})" 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/source/forge_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'librarian/puppet/source/forge' 4 | require 'librarian/puppet/environment' 5 | require 'librarian/puppet/extension' 6 | 7 | include Librarian::Puppet::Source 8 | 9 | describe Forge do 10 | subject { Forge.new(environment, uri) } 11 | 12 | let(:environment) { Librarian::Puppet::Environment.new } 13 | let(:uri) { 'https://forgeapi.puppet.com' } 14 | 15 | describe '#manifests' do 16 | let(:manifests) { [] } 17 | 18 | before do 19 | expect_any_instance_of(Librarian::Puppet::Source::Forge::RepoV3).to receive(:get_versions).at_least(:once) { 20 | manifests 21 | } 22 | end 23 | 24 | it 'returns the manifests' do 25 | expect(subject.manifests('x')).to eq(manifests) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/librarian/puppet/action/resolve.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'librarian/action/resolve' 4 | require 'librarian/puppet/resolver' 5 | 6 | module Librarian 7 | module Puppet 8 | module Action 9 | class Resolve < Librarian::Action::Resolve 10 | include Librarian::Puppet::Util 11 | 12 | def run 13 | super 14 | manifests = environment.lock.manifests.select { |m| m.name } 15 | dupes = manifests.group_by { |m| module_name(m.name) }.select { |_k, v| v.size > 1 } 16 | dupes.each do |k, v| 17 | warn("Dependency on module '#{k}' is fullfilled by multiple modules and only one will be used: #{v.map do |m| 18 | m.name 19 | end}") 20 | end 21 | end 22 | 23 | def resolver 24 | Resolver.new(environment) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes 3 | 4 | changelog: 5 | exclude: 6 | labels: 7 | - duplicate 8 | - invalid 9 | - modulesync 10 | - question 11 | - skip-changelog 12 | - wont-fix 13 | - wontfix 14 | - github_actions 15 | 16 | categories: 17 | - title: Breaking Changes 🛠 18 | labels: 19 | - backwards-incompatible 20 | 21 | - title: New Features 🎉 22 | labels: 23 | - enhancement 24 | 25 | - title: Bug Fixes 🐛 26 | labels: 27 | - bug 28 | - bugfix 29 | 30 | - title: Documentation Updates 📚 31 | labels: 32 | - documentation 33 | - docs 34 | 35 | - title: Dependency Updates ⬆️ 36 | labels: 37 | - dependencies 38 | 39 | - title: Other Changes 40 | labels: 41 | - "*" 42 | -------------------------------------------------------------------------------- /lib/librarian/puppet/source/repo.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # parent class for githubtarball and forge source Repos 4 | module Librarian 5 | module Puppet 6 | module Source 7 | class Repo 8 | attr_accessor :source, :name 9 | private :source=, :name= 10 | 11 | def initialize(source, name) 12 | self.source = source 13 | self.name = name 14 | end 15 | 16 | def environment 17 | source.environment 18 | end 19 | 20 | def cache_path 21 | @cache_path ||= source.cache_path.join(name) 22 | end 23 | 24 | def version_unpacked_cache_path(version) 25 | cache_path.join(version.to_s) 26 | end 27 | 28 | def vendored?(name, version) 29 | vendored_path(name, version).exist? 30 | end 31 | 32 | def vendored_path(name, version) 33 | environment.vendor_cache.join("#{name}-#{version}.tar.gz") 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2014 Tim Sharpe, Carlos Sanchez and others 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /spec/action/resolve_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require_relative '../../lib/librarian/puppet/action/resolve' 5 | require 'librarian/ui' 6 | require 'thor' 7 | 8 | describe 'Librarian::Puppet::Action::Resolve' do 9 | let(:path) { File.expand_path('../../features/examples/test', __dir__) } 10 | let(:environment) { Librarian::Puppet::Environment.new(project_path: path) } 11 | 12 | before do 13 | # run with DEBUG=true envvar to get debug output 14 | environment.ui = Librarian::UI::Shell.new(Thor::Shell::Basic.new) 15 | end 16 | 17 | after do 18 | File.delete('features/examples/test/Puppetfile.lock') 19 | end 20 | 21 | describe '#run' do 22 | it 'resolves dependencies' do 23 | Librarian::Puppet::Action::Resolve.new(environment, force: true).run 24 | resolution = environment.lock.manifests.map do |m| 25 | { name: m.name, version: m.version.to_s, source: m.source.to_s } 26 | end 27 | expect(resolution.size).to eq(1) 28 | expect(resolution.first[:name]).to eq('puppetlabs-stdlib') 29 | expect(resolution.first[:source]).to eq('https://forgeapi.puppet.com') 30 | expect(resolution.first[:version]).to match(/\d+\.\d+\.\d+/) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/receiver_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'Librarian::Puppet::Dsl::Receiver' do 6 | let(:dsl) { Librarian::Puppet::Dsl.new({}) } 7 | let(:target) { Librarian::Dsl::Target.new(dsl) } 8 | let(:receiver) { Librarian::Puppet::Dsl::Receiver.new(target) } 9 | let(:environment) { Librarian::Puppet::Environment.new(project_path: '/tmp/tmp_module') } 10 | 11 | describe '#run' do 12 | it 'gets working_dir from pwd when specfile is nil' do 13 | receiver.run(nil) {} 14 | expect(receiver.working_path).to eq(Pathname.new(Dir.pwd)) 15 | end 16 | 17 | it 'gets working_dir from pwd with default specfile' do 18 | receiver.run(dsl.default_specfile) {} 19 | expect(receiver.working_path).to eq(Pathname.new(Dir.pwd)) 20 | end 21 | 22 | it 'gets working_dir from given path' do 23 | receiver.run(Pathname.new('/tmp/tmp_module/Puppetfile')) {} 24 | expect(receiver.working_path).to eq(Pathname.new('/tmp/tmp_module')) 25 | end 26 | 27 | it 'test receiver run' do 28 | error_message = 'Metadata file does not exist: ' + File.join(environment.project_path, 'metadata.json') 29 | expect { environment.dsl(environment.specfile.path, []) }.to raise_error(Librarian::Error, error_message) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /features/step_definitions/convergence_steps.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Then(/^the file "([^"]*)" should have an inode and ctime$/) do |file| 4 | cd('.') do 5 | stat = File.stat(File.expand_path(file)) 6 | @before_inode = { 'ino' => stat.ino, 'ctime' => stat.ctime } 7 | expect(@before_inode['ino']).not_to eq nil 8 | expect(@before_inode['ctime']).not_to eq nil 9 | end 10 | end 11 | 12 | Then(/^the file "([^"]*)" should have the same inode and ctime as before$/) do |file| 13 | cd('.') do 14 | stat = File.stat(File.expand_path(file)) 15 | expect(stat.ino).to eq @before_inode['ino'] 16 | expect(stat.ctime).to eq @before_inode['ctime'] 17 | end 18 | end 19 | 20 | Then(/^the file "([^"]*)" should not have the same inode or ctime as before$/) do |file| 21 | cd('.') do 22 | stat = File.stat(File.expand_path(file)) 23 | 24 | begin 25 | expect(stat.ino).not_to eq @before_inode['ino'] 26 | rescue RSpec::Expectations::ExpectationNotMetError 27 | expect(stat.ctime).not_to eq @before_inode['ctime'] 28 | end 29 | end 30 | end 31 | 32 | Then(/^the git revision of module "([^"]*)" should be "([0-9a-f]*)"$/) do |module_name, rev| 33 | cd("modules/#{module_name}") do 34 | cmd = 'git rev-parse HEAD' 35 | run_command_and_stop(cmd) 36 | expect(last_command_started.output.strip).to eq(rev) 37 | end 38 | end 39 | 40 | Given(/^I wait for (\d+) seconds?$/) do |n| 41 | sleep(n.to_i) 42 | end 43 | -------------------------------------------------------------------------------- /lib/librarian/puppet/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'librarian/environment' 4 | require 'librarian/puppet/dsl' 5 | require 'librarian/puppet/source' 6 | require 'librarian/puppet/lockfile' 7 | 8 | module Librarian 9 | module Puppet 10 | class Environment < Librarian::Environment 11 | def adapter_name 12 | 'puppet' 13 | end 14 | 15 | def lockfile 16 | Lockfile.new(self, lockfile_path) 17 | end 18 | 19 | def ephemeral_lockfile 20 | Lockfile.new(self, nil) 21 | end 22 | 23 | def tmp_path 24 | part = config_db['tmp'] || '.tmp' 25 | project_path.join(part) 26 | end 27 | 28 | def install_path 29 | part = config_db['path'] || 'modules' 30 | project_path.join(part) 31 | end 32 | 33 | def vendor_path 34 | project_path.join('vendor/puppet') 35 | end 36 | 37 | def vendor_cache 38 | vendor_path.join('cache') 39 | end 40 | 41 | def vendor_source 42 | vendor_path.join('source') 43 | end 44 | 45 | def vendor! 46 | vendor_cache.mkpath unless vendor_cache.exist? 47 | vendor_source.mkpath unless vendor_source.exist? 48 | end 49 | 50 | def vendor? 51 | vendor_path.exist? 52 | end 53 | 54 | def local? 55 | config_db['mode'] == 'local' 56 | end 57 | 58 | def use_v1_api 59 | config_db['use-v1-api'] 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /features/install/github_tarball.feature: -------------------------------------------------------------------------------- 1 | Feature: cli/install/github_tarball 2 | Puppet librarian needs to install tarballed modules from github repositories 3 | 4 | @github 5 | Scenario: Installing a module from github tarballs 6 | Given a file named "Puppetfile" with: 7 | """ 8 | forge "https://forgeapi.puppet.com" 9 | 10 | mod 'puppetlabs/apache', '0.6.0', :github_tarball => 'puppetlabs/puppetlabs-apache' 11 | mod 'puppetlabs/stdlib', '2.3.0', :github_tarball => 'puppetlabs/puppetlabs-stdlib' 12 | """ 13 | When I successfully run `librarian-puppet install --verbose` 14 | And the output should contain "Downloading 'puppetlabs/puppetlabs-stdlib' 27 | """ 28 | When I successfully run `librarian-puppet install` 29 | And the file "modules/stdlib/Modulefile" should match /name *'puppetlabs-stdlib'/ 30 | -------------------------------------------------------------------------------- /librarian-puppet.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $:.push File.expand_path('lib', __dir__) 4 | 5 | require 'librarian/puppet/version' 6 | 7 | Gem::Specification.new do |s| 8 | s.name = 'librarian-puppet' 9 | s.version = Librarian::Puppet::VERSION 10 | s.platform = Gem::Platform::RUBY 11 | s.authors = ['Tim Sharpe', 'Carlos Sanchez'] 12 | s.license = 'MIT' 13 | s.email = ['tim@sharpe.id.au', 'carlos@apache.org'] 14 | s.homepage = 'https://github.com/voxpupuli/librarian-puppet' 15 | s.summary = 'Bundler for your Puppet modules' 16 | s.description = 'Simplify deployment of your Puppet infrastructure by 17 | automatically pulling in modules from the forge and git repositories with 18 | a single command.' 19 | 20 | s.required_ruby_version = '>= 3.2', '< 4' 21 | 22 | s.files = [ 23 | '.gitignore', 24 | 'LICENSE', 25 | 'README.md', 26 | ] + Dir['{bin,lib}/**/*'] 27 | 28 | s.executables = ['librarian-puppet'] 29 | 30 | s.add_dependency 'librarianp', '~> 1.1' 31 | s.add_dependency 'puppet_forge', '>= 2', '< 7' 32 | s.add_dependency 'rsync', '~> 1.0' 33 | 34 | s.add_development_dependency 'aruba', '>= 1.0', '< 3' 35 | s.add_development_dependency 'concurrent-ruby', '~> 1.3' 36 | s.add_development_dependency 'cucumber', '~> 9.2' 37 | s.add_development_dependency 'minitest', '~> 5' 38 | s.add_development_dependency 'mocha', '~> 2.7' 39 | s.add_development_dependency 'openvox', '~> 8.22' 40 | s.add_development_dependency 'rake', '~> 13.2' 41 | s.add_development_dependency 'rspec', '~> 3.13' 42 | s.add_development_dependency 'simplecov', '~> 0.22.0' 43 | s.add_development_dependency 'voxpupuli-rubocop', '~> 4.2.0' 44 | end 45 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'cucumber/rake/task' 4 | require 'rspec/core/rake_task' 5 | require 'bundler/gem_tasks' 6 | require 'rake/testtask' 7 | require 'rake/clean' 8 | 9 | CLEAN.include('pkg/', 'tmp/') 10 | CLOBBER.include('Gemfile.lock') 11 | 12 | RSpec::Core::RakeTask.new 13 | Cucumber::Rake::Task.new(:features) 14 | 15 | Rake::TestTask.new do |test| 16 | test.pattern = 'test/**/*_test.rb' 17 | test.verbose = true 18 | end 19 | 20 | task default: %i[test spec features] 21 | 22 | desc 'Bump version to the next minor' 23 | task :bump do 24 | path = 'lib/librarian/puppet/version.rb' 25 | version_file = File.read(path) 26 | version = version_file.match(/VERSION = "(.*)"/)[1] 27 | v = Gem::Version.new("#{version}.0") 28 | new_version = v.bump.to_s 29 | version_file = version_file.gsub(/VERSION = ".*"/, "VERSION = \"#{new_version}\"") 30 | File.write(version_file) 31 | sh "git add #{path}" 32 | sh "git commit -m \"Bump version to #{new_version}\"" 33 | end 34 | 35 | begin 36 | require 'rubygems' 37 | require 'github_changelog_generator/task' 38 | rescue LoadError 39 | # Part of an optional group 40 | else 41 | GitHubChangelogGenerator::RakeTask.new :changelog do |config| 42 | config.exclude_labels = %w[duplicate question invalid wontfix wont-fix skip-changelog github_actions] 43 | config.user = 'voxpupuli' 44 | config.project = 'librarian-puppet' 45 | config.since_tag = 'v3.0.1' 46 | gem_version = Gem::Specification.load("#{config.project}.gemspec").version 47 | config.future_release = "v#{gem_version}" 48 | end 49 | end 50 | 51 | begin 52 | require 'voxpupuli/rubocop/rake' 53 | rescue LoadError 54 | # the voxpupuli-rubocop gem is optional 55 | end 56 | -------------------------------------------------------------------------------- /features/outdated.feature: -------------------------------------------------------------------------------- 1 | Feature: cli/outdated 2 | Puppet librarian needs to print outdated modules 3 | 4 | Scenario: Running outdated with forge modules 5 | Given a file named "Puppetfile" with: 6 | """ 7 | forge "https://forgeapi.puppet.com" 8 | 9 | mod 'puppetlabs/stdlib', '>=3.1.x' 10 | """ 11 | And a file named "Puppetfile.lock" with: 12 | """ 13 | FORGE 14 | remote: https://forgeapi.puppet.com 15 | specs: 16 | puppetlabs/stdlib (3.1.0) 17 | 18 | DEPENDENCIES 19 | puppetlabs/stdlib (~> 3.0) 20 | """ 21 | When I successfully run `librarian-puppet outdated` 22 | And the output should match: 23 | """ 24 | ^puppetlabs-stdlib \(3\.1\.0 -> [\.\d]+\)$ 25 | """ 26 | 27 | Scenario: Running outdated with git modules 28 | Given a file named "Puppetfile" with: 29 | """ 30 | forge "https://forgeapi.puppet.com" 31 | 32 | mod 'test', :git => 'https://github.com/voxpupuli/librarian-puppet.git', :path => 'features/examples/test' 33 | """ 34 | And a file named "Puppetfile.lock" with: 35 | """ 36 | FORGE 37 | remote: https://forgeapi.puppet.com 38 | specs: 39 | puppetlabs/stdlib (3.1.0) 40 | 41 | GIT 42 | remote: https://github.com/voxpupuli/librarian-puppet.git 43 | path: features/examples/test 44 | ref: master 45 | sha: 10fdf98190a7a22e479628b3616f17f48a857e81 46 | specs: 47 | test (0.0.1) 48 | puppetlabs/stdlib (>= 0) 49 | 50 | DEPENDENCIES 51 | test (>= 0) 52 | """ 53 | When I successfully run `librarian-puppet outdated` 54 | And PENDING the output should match: 55 | # """ 56 | # ^puppetlabs-stdlib \(3\.1\.0 -> [\.\d]+\)$ 57 | # ^test .*$ 58 | # """ 59 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Test 3 | 4 | on: 5 | pull_request: {} 6 | push: 7 | branches: 8 | - master 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | rubocop_and_matrix: 15 | runs-on: ubuntu-24.04 16 | outputs: 17 | ruby: ${{ steps.ruby.outputs.versions }} 18 | steps: 19 | - uses: actions/checkout@v6 20 | - name: Install Ruby ${{ matrix.ruby }} 21 | uses: ruby/setup-ruby@v1 22 | with: 23 | ruby-version: "3.4" 24 | bundler-cache: true 25 | - name: Run Rubocop 26 | run: bundle exec rake rubocop 27 | - id: ruby 28 | uses: voxpupuli/ruby-version@v1 29 | 30 | test: 31 | needs: rubocop_and_matrix 32 | runs-on: ubuntu-latest 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | ruby: ${{ fromJSON(needs.rubocop_and_matrix.outputs.ruby) }} 37 | openvox: 38 | - "8" 39 | name: Ruby ${{ matrix.ruby }} + OpenVox ${{ matrix.openvox }} 40 | env: 41 | OPENVOX_VERSION: "~> ${{ matrix.openvox }}.0" 42 | steps: 43 | - uses: actions/checkout@v6 44 | - uses: ruby/setup-ruby@v1 45 | with: 46 | ruby-version: ${{ matrix.ruby }} 47 | bundler-cache: true 48 | - name: Show Puppet version 49 | run: bundle exec puppet --version 50 | - name: Run tests 51 | run: bundle exec rake 52 | - name: Verify gem builds 53 | run: gem build --strict --verbose *.gemspec 54 | 55 | tests: 56 | if: always() 57 | needs: 58 | - rubocop_and_matrix 59 | - test 60 | runs-on: ubuntu-24.04 61 | name: Test suite 62 | steps: 63 | - name: Decide whether the needed jobs succeeded or failed 64 | uses: re-actors/alls-green@release/v1 65 | with: 66 | jobs: ${{ toJSON(needs) }} 67 | -------------------------------------------------------------------------------- /features/package.feature: -------------------------------------------------------------------------------- 1 | Feature: cli/package 2 | Puppet librarian needs to package modules 3 | 4 | Scenario: Packaging a forge module 5 | Given a file named "Puppetfile" with: 6 | """ 7 | forge "https://forgeapi.puppet.com" 8 | 9 | mod 'puppetlabs/apt', '1.4.0' 10 | mod 'puppetlabs/stdlib', '4.1.0' 11 | """ 12 | When I successfully run `librarian-puppet package --verbose` 13 | And the file "modules/apt/Modulefile" should match /name *'puppetlabs-apt'/ 14 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 15 | And the following files should exist: 16 | | vendor/puppet/cache/puppetlabs-apt-1.4.0.tar.gz | 17 | | vendor/puppet/cache/puppetlabs-stdlib-4.1.0.tar.gz | 18 | 19 | Scenario: Packaging a git module 20 | Given a file named "Puppetfile" with: 21 | """ 22 | forge "https://forgeapi.puppet.com" 23 | 24 | mod 'puppetlabs/apt', '1.5.0', :git => 'https://github.com/puppetlabs/puppetlabs-apt.git', :ref => '1.5.0' 25 | mod 'puppetlabs/stdlib', '4.1.0' 26 | """ 27 | When I successfully run `librarian-puppet package --verbose` 28 | And the file "modules/apt/Modulefile" should match /name *'puppetlabs-apt'/ 29 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 30 | And the following files should exist: 31 | | vendor/puppet/source/e5657a61b9ac0dd3c00002c777b0d3c615bb98a5.tar.gz | 32 | | vendor/puppet/cache/puppetlabs-stdlib-4.1.0.tar.gz | 33 | 34 | @github 35 | Scenario: Packaging a github tarball module 36 | Given a file named "Puppetfile" with: 37 | """ 38 | forge "https://forgeapi.puppet.com" 39 | 40 | mod 'puppetlabs/apt', '1.4.0', :github_tarball => 'puppetlabs/puppetlabs-apt' 41 | mod 'puppetlabs/stdlib', '4.1.0' 42 | """ 43 | When I successfully run `librarian-puppet package --verbose` 44 | And the file "modules/apt/Modulefile" should match /name *'puppetlabs-apt'/ 45 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 46 | And the following files should exist: 47 | | vendor/puppet/cache/puppetlabs-puppetlabs-apt-1.4.0.tar.gz | 48 | | vendor/puppet/cache/puppetlabs-stdlib-4.1.0.tar.gz | 49 | -------------------------------------------------------------------------------- /features/examples/metadata_syntax/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "operatingsystem_support": [ 3 | { 4 | "operatingsystem": "RedHat", 5 | "operatingsystemrelease": [ 6 | "4", 7 | "5", 8 | "6" 9 | ] 10 | }, 11 | { 12 | "operatingsystem": "CentOS", 13 | "operatingsystemrelease": [ 14 | "4", 15 | "5", 16 | "6" 17 | ] 18 | }, 19 | { 20 | "operatingsystem": "OracleLinux", 21 | "operatingsystemrelease": [ 22 | "4", 23 | "5", 24 | "6" 25 | ] 26 | }, 27 | { 28 | "operatingsystem": "Scientific", 29 | "operatingsystemrelease": [ 30 | "4", 31 | "5", 32 | "6" 33 | ] 34 | }, 35 | { 36 | "operatingsystem": "SLES", 37 | "operatingsystemrelease": [ 38 | "11 SP1" 39 | ] 40 | }, 41 | { 42 | "operatingsystem": "Debian", 43 | "operatingsystemrelease": [ 44 | "6", 45 | "7" 46 | ] 47 | }, 48 | { 49 | "operatingsystem": "Ubuntu", 50 | "operatingsystemrelease": [ 51 | "10.04", 52 | "12.04" 53 | ] 54 | }, 55 | { 56 | "operatingsystem": "Solaris", 57 | "operatingsystemrelease": [ 58 | "10", 59 | "11" 60 | ] 61 | }, 62 | { 63 | "operatingsystem": "Windows", 64 | "operatingsystemrelease": [ 65 | "Server 2003", 66 | "Server 2003 R2", 67 | "Server 2008", 68 | "Server 2008 R2", 69 | "Server 2012", 70 | "Server 2012 R2", 71 | "7", 72 | "8" 73 | ] 74 | }, 75 | { 76 | "operatingsystem": "AIX", 77 | "operatingsystemrelease": [ 78 | "5.3", 79 | "6.1", 80 | "7.1" 81 | ] 82 | } 83 | ], 84 | "requirements": [ 85 | { 86 | "name": "pe", 87 | "version_requirement": "3.2.x" 88 | }, 89 | { 90 | "name": "puppet", 91 | "version_requirement": ">=2.7.20 <4.0.0" 92 | } 93 | ], 94 | "name": "librarian-metadata_syntax", 95 | "version": "0.0.1", 96 | "license": "Apache 2.0", 97 | "dependencies": [ 98 | { 99 | "name": "maestrodev/test", 100 | "version_requirement": ">= 0.0.1" 101 | } 102 | ] 103 | } 104 | -------------------------------------------------------------------------------- /lib/librarian/puppet/source/git.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'librarian/source/git' 4 | require 'librarian/puppet/source/local' 5 | 6 | module Librarian 7 | module Source 8 | class Git 9 | class Repository 10 | def hash_from(remote, reference) 11 | branch_names = remote_branch_names[remote] 12 | reference = "#{remote}/#{reference}" if branch_names.include?(reference) 13 | 14 | command = %W[rev-parse #{reference}^{commit} --quiet] 15 | run!(command, chdir: true).strip 16 | end 17 | end 18 | end 19 | end 20 | 21 | module Puppet 22 | module Source 23 | class Git < Librarian::Source::Git 24 | include Local 25 | include Librarian::Puppet::Util 26 | 27 | def cache! 28 | return vendor_checkout! if vendor_cached? 29 | 30 | if environment.local? 31 | raise Error, "Could not find a local copy of #{uri}#{" at #{sha}" unless sha.nil?}." 32 | end 33 | 34 | begin 35 | super 36 | rescue Librarian::Posix::CommandFailure => e 37 | raise Error, "Could not checkout #{uri}#{" at #{sha}" unless sha.nil?}: #{e}" 38 | end 39 | 40 | if strip_dot_git? and repository.git? 41 | repository.path.join('.git').rmtree 42 | end 43 | 44 | cache_in_vendor(repository.path) if environment.vendor? 45 | end 46 | 47 | private 48 | 49 | def vendor_tar 50 | environment.vendor_source.join("#{sha}.tar") 51 | end 52 | 53 | def vendor_tgz 54 | environment.vendor_source.join("#{sha}.tar.gz") 55 | end 56 | 57 | def vendor_cached? 58 | vendor_tgz.exist? 59 | end 60 | 61 | def strip_dot_git? 62 | environment.config_db.local['install.strip-dot-git'] == '1' 63 | end 64 | 65 | def vendor_checkout! 66 | repository.path.rmtree if repository.path.exist? 67 | repository.path.mkpath 68 | 69 | Librarian::Posix.run!(%W[tar xzf #{vendor_tgz}], chdir: repository.path.to_s) 70 | 71 | repository_cached! 72 | end 73 | 74 | def cache_in_vendor(tmp_path) 75 | Librarian::Posix.run!(%W[git archive -o #{vendor_tar} #{sha}], chdir: tmp_path.to_s) 76 | Librarian::Posix.run!(%W[gzip #{vendor_tar}], chdir: tmp_path.to_s) 77 | end 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/librarian/puppet/source/forge/repo_v3.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'librarian/puppet/source/forge/repo' 4 | require 'puppet_forge' 5 | require 'librarian/puppet/version' 6 | 7 | module Librarian 8 | module Puppet 9 | module Source 10 | class Forge 11 | class RepoV3 < Librarian::Puppet::Source::Forge::Repo 12 | PuppetForge.user_agent = "librarian-puppet/#{Librarian::Puppet::VERSION}" 13 | 14 | def initialize(source, name) 15 | PuppetForge.host = source.uri.clone 16 | super 17 | end 18 | 19 | def get_versions 20 | get_module.releases.select { |r| r.deleted_at.nil? }.map { |r| r.version } 21 | end 22 | 23 | def dependencies(version) 24 | array = get_release(version).metadata[:dependencies].map { |d| [d[:name], d[:version_requirement]] } 25 | Hash[*array.flatten(1)] 26 | end 27 | 28 | def url(name, version) 29 | if name == "#{get_module.owner.username}/#{get_module.name}" 30 | release = get_release(version) 31 | else 32 | # should never get here as we use one repo object for each module (to be changed in the future) 33 | debug { "Looking up url for #{name}@#{version}" } 34 | release = PuppetForge::V3::Release.find("#{name}-#{version}") 35 | end 36 | "#{source}#{release.file_uri}" 37 | end 38 | 39 | private 40 | 41 | def get_module 42 | begin 43 | @module ||= PuppetForge::V3::Module.find(name) 44 | # https://github.com/puppetlabs/forge-ruby/pull/104/files starting with version 5, PuppetForge::ModuleNotFound is raised 45 | # previous versions raise Faraday::ResourceNotFound 46 | rescue Faraday::ResourceNotFound, PuppetForge::ModuleNotFound 47 | raise(Error, "Unable to find module '#{name}' on #{source}") 48 | end 49 | @module 50 | end 51 | 52 | def get_release(version) 53 | release = get_module.releases.find { |r| r.version == version.to_s } 54 | if release.nil? 55 | versions = get_module.releases.map { |r| r.version } 56 | raise Error, "Unable to find version '#{version}' for module '#{name}' on #{source} amongst #{versions}" 57 | end 58 | release 59 | end 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /features/install/path.feature: -------------------------------------------------------------------------------- 1 | Feature: cli/install/path 2 | Puppet librarian needs to install modules from local paths 3 | 4 | Scenario: Install a module with dependencies specified in a Puppetfile 5 | Given a file named "Puppetfile" with: 6 | """ 7 | mod 'librarian/with_puppetfile', :path => '../../features/examples/with_puppetfile' 8 | """ 9 | When I successfully run `librarian-puppet install` 10 | And the file "modules/with_puppetfile/metadata.json" should match /"name": "librarian-with_puppetfile"/ 11 | And the file "modules/test/metadata.json" should match /"name": "librarian-test"/ 12 | 13 | Scenario: Install a module with recursive path dependencies 14 | Given a file named "Puppetfile" with: 15 | """ 16 | mod 'librarian/path_dependencies', :path => '../../features/examples/path_dependencies' 17 | """ 18 | When I successfully run `librarian-puppet install` 19 | And the file "modules/path_dependencies/metadata.json" should match /"name": "librarian-path_dependencies"/ 20 | And the file "modules/test/metadata.json" should match /"name": "librarian-test"/ 21 | And a file named "modules/stdlib/metadata.json" should exist 22 | 23 | Scenario: Install a module with dependencies specified in a Puppetfile and metadata.json 24 | Given a file named "Puppetfile" with: 25 | """ 26 | mod 'librarian/with_puppetfile', :path => '../../features/examples/with_puppetfile_and_metadata_json' 27 | """ 28 | When I successfully run `librarian-puppet install` 29 | And the file "modules/with_puppetfile/metadata.json" should match /"name": "librarian-with_puppetfile_and_metadata_json"/ 30 | And the file "modules/test/metadata.json" should match /"name": "maestrodev-test"/ 31 | 32 | Scenario: Install a module from path without version 33 | Given a file named "Puppetfile" with: 34 | """ 35 | forge "https://forgeapi.puppet.com" 36 | 37 | mod 'test', :path => '../../features/examples/dependency_without_version' 38 | """ 39 | When I successfully run `librarian-puppet install` 40 | And the file "modules/test/metadata.json" should match /"version": "0\.0\.1"/ 41 | And a file named "modules/stdlib/metadata.json" should exist 42 | 43 | @spaces 44 | Scenario: Installing a module in a path with spaces 45 | Given a file named "Puppetfile" with: 46 | """ 47 | mod 'librarian/test', :path => '../../features/examples/test' 48 | mod 'puppetlabs/stdlib', :git => 'https://github.com/puppetlabs/puppetlabs-stdlib', :ref => 'main' 49 | """ 50 | When I successfully run `librarian-puppet install` 51 | And the file "modules/test/metadata.json" should match /"name": "librarian-test"/ 52 | -------------------------------------------------------------------------------- /lib/librarian/puppet/util.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rsync' 4 | 5 | module Librarian 6 | module Puppet 7 | module Util 8 | def debug(...) 9 | environment.logger.debug(...) 10 | end 11 | 12 | def info(...) 13 | environment.logger.info(...) 14 | end 15 | 16 | def warn(...) 17 | environment.logger.warn(...) 18 | end 19 | 20 | def rsync? 21 | environment.config_db.local['rsync'] == 'true' 22 | end 23 | 24 | # workaround Issue #173 FileUtils.cp_r will fail if there is a symlink that points to a missing file 25 | # or when the symlink is copied before the target file when preserve is true 26 | # see also https://tickets.opscode.com/browse/CHEF-833 27 | # 28 | # If the rsync configuration parameter is set, use rsync instead of FileUtils 29 | def cp_r(src, dest) 30 | if rsync? 31 | if Gem.win_platform? 32 | src_clean = "#{src}".gsub(/^([a-z]):/i, '/cygdrive/\1') 33 | dest_clean = "#{dest}".gsub(/^([a-z]):/i, '/cygdrive/\1') 34 | else 35 | src_clean = src 36 | dest_clean = dest 37 | end 38 | debug { "Copying #{src_clean}/ to #{dest_clean}/ with rsync -avz --delete" } 39 | result = Rsync.run(File.join(src_clean, '/'), File.join(dest_clean, '/'), ['-avz', '--delete']) 40 | if result.success? 41 | debug { "Rsync from #{src_clean}/ to #{dest_clean}/ successful" } 42 | else 43 | msg = "Failed to rsync from #{src_clean}/ to #{dest_clean}/: " + result.error 44 | raise Error, msg 45 | end 46 | else 47 | begin 48 | FileUtils.cp_r(src, dest, preserve: true) 49 | rescue Errno::ENOENT, Errno::EACCES 50 | debug do 51 | "Failed to copy from #{src} to #{dest} preserving file types, trying again without preserving them" 52 | end 53 | FileUtils.rm_rf(dest) 54 | FileUtils.cp_r(src, dest) 55 | end 56 | end 57 | end 58 | 59 | # Remove user and password from a URI object 60 | def clean_uri(uri) 61 | new_uri = uri.clone 62 | new_uri.user = nil 63 | new_uri.password = nil 64 | new_uri 65 | end 66 | 67 | # normalize module name to use organization-module instead of organization/module 68 | def normalize_name(name) 69 | name.sub('/', '-') 70 | end 71 | 72 | # get the module name from organization-module 73 | def module_name(name) 74 | # module name can't have dashes, so let's assume it is everything after the last dash 75 | name.rpartition('-').last 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/librarian/puppet/lockfile.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Extend Lockfile to normalize module names from acme/mod to acme-mod 4 | module Librarian 5 | module Puppet 6 | class Lockfile < Librarian::Lockfile 7 | # Extend the parser to normalize module names in old .lock files, converting / to - 8 | class Parser < Librarian::Lockfile::Parser 9 | include Librarian::Puppet::Util 10 | 11 | def extract_and_parse_sources(lines) 12 | sources = super 13 | sources.each do |source| 14 | source[:manifests] = Hash[source[:manifests].map do |name, manifest| 15 | [normalize_name(name), manifest] 16 | end] 17 | end 18 | sources 19 | end 20 | 21 | def extract_and_parse_dependencies(lines, manifests_index) 22 | # when looking up in manifests_index normalize the name beforehand 23 | class << manifests_index 24 | include Librarian::Puppet::Util 25 | 26 | alias_method :old_lookup, :[] 27 | define_method(:[]) { |k| old_lookup(normalize_name(k)) } 28 | end 29 | dependencies = [] 30 | while lines.first =~ %r{^ {2}([\w\-/]+)(?: \((.*)\))?$} 31 | lines.shift 32 | name = ::Regexp.last_match(1) 33 | requirement = ::Regexp.last_match(2).split(/,\s*/) 34 | dependencies << environment.dsl_class.dependency_type.new(name, requirement, manifests_index[name].source, 35 | 'lockfile') 36 | end 37 | dependencies 38 | end 39 | 40 | def compile_placeholder_manifests(sources_ast) 41 | manifests = {} 42 | sources_ast.each do |source_ast| 43 | source_type = source_ast[:type] 44 | source = source_type.from_lock_options(environment, source_ast[:options]) 45 | source_ast[:manifests].each do |manifest_name, manifest_ast| 46 | manifests[manifest_name] = ManifestPlaceholder.new( 47 | source, 48 | manifest_name, 49 | manifest_ast[:version], 50 | manifest_ast[:dependencies].map do |k, v| 51 | environment.dsl_class.dependency_type.new(k, v, nil, manifest_name) 52 | end, 53 | ) 54 | end 55 | end 56 | manifests 57 | end 58 | 59 | def compile(sources_ast) 60 | manifests = compile_placeholder_manifests(sources_ast) 61 | manifests = manifests.map do |name, manifest| 62 | manifest.dependencies.map do |d| 63 | environment.dsl_class.dependency_type.new(d.name, d.requirement, manifests[d.name].source, name) 64 | end 65 | real = Manifest.new(manifest.source, manifest.name) 66 | real.version = manifest.version 67 | real.dependencies = manifest.dependencies 68 | real 69 | end 70 | ManifestSet.sort(manifests) 71 | end 72 | end 73 | 74 | def load(string) 75 | Parser.new(environment).parse(string) 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /test/librarian/puppet/source/githubtarball_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path('../../../test_helper', __dir__) 4 | require 'librarian/puppet/source/githubtarball' 5 | 6 | describe Librarian::Puppet::Source::GitHubTarball::Repo do 7 | def assert_exact_error(klass, message) 8 | yield 9 | rescue Exception => e 10 | _(e.class).must_equal klass 11 | _(e.message).must_equal message 12 | else 13 | raise 'No exception was raised!' 14 | end 15 | 16 | class FakeResponse 17 | attr_accessor :code, :body 18 | 19 | def initialize(code, body) 20 | @code = code 21 | @body = body 22 | end 23 | 24 | def [](_key) 25 | nil 26 | end 27 | end 28 | 29 | describe '#api_call' do 30 | let(:environment) { Librarian::Puppet::Environment.new } 31 | let(:source) { Librarian::Puppet::Source::GitHubTarball.new(environment, 'foo') } 32 | let(:repo) { Librarian::Puppet::Source::GitHubTarball::Repo.new(source, 'bar') } 33 | let(:headers) { { 'User-Agent' => "librarian-puppet v#{Librarian::Puppet::VERSION}" } } 34 | let(:url) { 'https://api.github.com/foo?page=1&per_page=100' } 35 | let(:url_with_token) { 'https://api.github.com/foo?page=1&per_page=100&access_token=bar' } 36 | ENV['GITHUB_API_TOKEN'] = '' 37 | 38 | it 'succeeds' do 39 | response = [] 40 | repo.expects(:http_get).with(url, { headers: headers }).returns(FakeResponse.new(200, JSON.dump(response))) 41 | 42 | _(repo.send(:api_call, '/foo')).must_equal(response) 43 | end 44 | 45 | it 'adds GITHUB_API_TOKEN if present' do 46 | ENV['GITHUB_API_TOKEN'] = 'bar' 47 | response = [] 48 | repo.expects(:http_get).with(url_with_token, 49 | { headers: headers }).returns(FakeResponse.new(200, JSON.dump(response))) 50 | 51 | _(repo.send(:api_call, '/foo')).must_equal(response) 52 | ENV['GITHUB_API_TOKEN'] = '' 53 | end 54 | 55 | it 'fails when we hit api limit' do 56 | response = { 'message' => 'Oh boy! API rate limit exceeded!!!' } 57 | repo.expects(:http_get).with(url, { headers: headers }).returns(FakeResponse.new(403, JSON.dump(response))) 58 | message = 'Oh boy! API rate limit exceeded!!! -- increase limit by authenticating via GITHUB_API_TOKEN=your-token' 59 | assert_exact_error Librarian::Error, message do 60 | repo.send(:api_call, '/foo') 61 | end 62 | end 63 | 64 | it 'fails with unknown error message' do 65 | repo.expects(:http_get).with(url, { headers: headers }).returns(FakeResponse.new(403, '')) 66 | assert_exact_error Librarian::Error, "Error fetching #{url}: [403] " do 67 | repo.send(:api_call, '/foo') 68 | end 69 | end 70 | 71 | it 'fails with html' do 72 | repo.expects(:http_get).with(url, { headers: headers }).returns(FakeResponse.new(403, 'Oh boy!')) 73 | assert_exact_error Librarian::Error, "Error fetching #{url}: [403] Oh boy!" do 74 | repo.send(:api_call, '/foo') 75 | end 76 | end 77 | 78 | it 'fails with unknown code' do 79 | repo.expects(:http_get).with(url, { headers: headers }).returns(FakeResponse.new(500, '')) 80 | assert_exact_error Librarian::Error, "Error fetching #{url}: [500] " do 81 | repo.send(:api_call, '/foo') 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Gem Release 3 | 4 | on: 5 | push: 6 | tags: 7 | - '*' 8 | 9 | permissions: {} 10 | 11 | jobs: 12 | build-release: 13 | # Prevent releases from forked repositories 14 | if: github.repository_owner == 'voxpupuli' 15 | name: Build the gem 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v6 19 | - name: Install Ruby 20 | uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: 'ruby' 23 | - name: Build gem 24 | shell: bash 25 | run: gem build --verbose *.gemspec 26 | - name: Upload gem to GitHub cache 27 | uses: actions/upload-artifact@v6 28 | with: 29 | name: gem-artifact 30 | path: '*.gem' 31 | retention-days: 1 32 | compression-level: 0 33 | 34 | create-github-release: 35 | needs: build-release 36 | name: Create GitHub release 37 | runs-on: ubuntu-24.04 38 | permissions: 39 | contents: write # clone repo and create release 40 | steps: 41 | - name: Download gem from GitHub cache 42 | uses: actions/download-artifact@v7 43 | with: 44 | name: gem-artifact 45 | - name: Create Release 46 | shell: bash 47 | env: 48 | GH_TOKEN: ${{ github.token }} 49 | run: gh release create --repo ${{ github.repository }} ${{ github.ref_name }} --generate-notes *.gem 50 | 51 | release-to-github: 52 | needs: build-release 53 | name: Release to GitHub 54 | runs-on: ubuntu-24.04 55 | permissions: 56 | packages: write # publish to rubygems.pkg.github.com 57 | steps: 58 | - name: Download gem from GitHub cache 59 | uses: actions/download-artifact@v7 60 | with: 61 | name: gem-artifact 62 | - name: Publish gem to GitHub packages 63 | run: gem push --host https://rubygems.pkg.github.com/${{ github.repository_owner }} *.gem 64 | env: 65 | GEM_HOST_API_KEY: ${{ secrets.GITHUB_TOKEN }} 66 | 67 | release-to-rubygems: 68 | needs: build-release 69 | name: Release gem to rubygems.org 70 | runs-on: ubuntu-24.04 71 | environment: release # recommended by rubygems.org 72 | permissions: 73 | id-token: write # rubygems.org authentication 74 | steps: 75 | - name: Download gem from GitHub cache 76 | uses: actions/download-artifact@v7 77 | with: 78 | name: gem-artifact 79 | - uses: rubygems/configure-rubygems-credentials@v1.0.0 80 | - name: Publish gem to rubygems.org 81 | shell: bash 82 | run: gem push *.gem 83 | 84 | release-verification: 85 | name: Check that all releases are done 86 | runs-on: ubuntu-24.04 87 | permissions: 88 | contents: read # minimal permissions that we have to grant 89 | needs: 90 | - create-github-release 91 | - release-to-github 92 | - release-to-rubygems 93 | steps: 94 | - name: Download gem from GitHub cache 95 | uses: actions/download-artifact@v7 96 | with: 97 | name: gem-artifact 98 | - name: Install Ruby 99 | uses: ruby/setup-ruby@v1 100 | with: 101 | ruby-version: 'ruby' 102 | - name: Wait for release to propagate 103 | shell: bash 104 | run: | 105 | gem install rubygems-await 106 | gem await *.gem 107 | -------------------------------------------------------------------------------- /lib/librarian/puppet/dsl.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'librarian/dsl' 4 | require 'librarian/dsl/target' 5 | require 'librarian/puppet/source' 6 | require 'librarian/puppet/dependency' 7 | 8 | module Librarian 9 | module Puppet 10 | class Dsl < Librarian::Dsl 11 | FORGE_URL = 'https://forgeapi.puppet.com' 12 | 13 | dependency :mod 14 | 15 | source forge: Source::Forge 16 | source git: Source::Git 17 | source path: Source::Path 18 | source github_tarball: Source::GitHubTarball 19 | 20 | def default_specfile 21 | proc do 22 | forge FORGE_URL 23 | metadata 24 | end 25 | end 26 | 27 | def self.dependency_type 28 | Librarian::Puppet::Dependency 29 | end 30 | 31 | def post_process_target(target) 32 | # save the default forge defined 33 | default_forge = target.sources.select { |s| s.is_a? Librarian::Puppet::Source::Forge }.first 34 | Librarian::Puppet::Source::Forge.default = default_forge || Librarian::Puppet::Source::Forge.from_lock_options( 35 | environment, remote: FORGE_URL 36 | ) 37 | end 38 | 39 | def receiver(target) 40 | Receiver.new(target) 41 | end 42 | 43 | def run(specfile = nil, sources = []) 44 | if specfile.is_a?(Array) && sources.empty? 45 | sources = specfile 46 | specfile = nil 47 | end 48 | 49 | Target.new(self).tap do |target| 50 | target.precache_sources(sources) 51 | debug_named_source_cache('Pre-Cached Sources', target) 52 | 53 | specfile ||= Proc.new if block_given? 54 | 55 | if specfile.is_a?(Pathname) and !File.exist?(specfile) 56 | debug { "Specfile #{specfile} not found, using defaults" } unless specfile.nil? 57 | receiver(target).run(specfile, &default_specfile) 58 | else 59 | receiver(target).run(specfile) 60 | end 61 | 62 | post_process_target(target) 63 | 64 | debug_named_source_cache('Post-Cached Sources', target) 65 | end.to_spec 66 | end 67 | 68 | class Target < Librarian::Dsl::Target 69 | def dependency(name, *args) 70 | options = args.last.is_a?(Hash) ? args.pop : {} 71 | source = source_from_options(options) || @source 72 | dep = dependency_type.new(name, args, source, 'Puppetfile') 73 | @dependencies << dep 74 | end 75 | end 76 | 77 | class Receiver < Librarian::Dsl::Receiver 78 | attr_reader :specfile, :working_path 79 | 80 | # save the specfile and call librarian 81 | def run(specfile = nil) 82 | @working_path = specfile.is_a?(Pathname) ? specfile.parent : Pathname.new(Dir.pwd) 83 | @specfile = specfile 84 | super 85 | end 86 | 87 | # implement the 'metadata' syntax for Puppetfile 88 | def metadata 89 | f = working_path.join('metadata.json') 90 | raise Error, "Metadata file does not exist: #{f}" unless File.exist?(f) 91 | 92 | begin 93 | json = JSON.parse(File.read(f)) 94 | rescue JSON::ParserError => e 95 | raise Error, "Unable to parse json file #{f}: #{e}" 96 | end 97 | dependencyList = json['dependencies'] 98 | dependencyList.each do |d| 99 | mod(d['name'], d['version_requirement']) 100 | end 101 | end 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/librarian/puppet/source/githubtarball.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'uri' 4 | require 'librarian/puppet/util' 5 | require 'librarian/puppet/source/githubtarball/repo' 6 | 7 | module Librarian 8 | module Puppet 9 | module Source 10 | class GitHubTarball 11 | include Librarian::Puppet::Util 12 | 13 | class << self 14 | LOCK_NAME = 'GITHUBTARBALL' 15 | 16 | def lock_name 17 | LOCK_NAME 18 | end 19 | 20 | def from_lock_options(environment, options) 21 | new(environment, options[:remote], options.reject { |k, _v| k == :remote }) 22 | end 23 | 24 | def from_spec_args(environment, uri, options) 25 | recognised_options = [] 26 | unrecognised_options = options.keys - recognised_options 27 | raise Error, "unrecognised options: #{unrecognised_options.join(', ')}" unless unrecognised_options.empty? 28 | 29 | new(environment, uri, options) 30 | end 31 | end 32 | 33 | attr_accessor :environment 34 | private :environment= 35 | attr_reader :uri 36 | 37 | def initialize(environment, uri, _options = {}) 38 | self.environment = environment 39 | @uri = URI.parse(uri) 40 | @cache_path = nil 41 | end 42 | 43 | def to_s 44 | clean_uri(uri).to_s 45 | end 46 | 47 | def ==(other) 48 | other && 49 | self.class == other.class && 50 | uri == other.uri 51 | end 52 | 53 | alias eql? == 54 | 55 | def hash 56 | to_s.hash 57 | end 58 | 59 | def to_spec_args 60 | [clean_uri(uri).to_s, {}] 61 | end 62 | 63 | def to_lock_options 64 | { remote: clean_uri(uri).to_s } 65 | end 66 | 67 | def pinned? 68 | false 69 | end 70 | 71 | def unpin!; end 72 | 73 | def install!(manifest) 74 | manifest.source == self or raise ArgumentError 75 | 76 | debug { "Installing #{manifest}" } 77 | 78 | name = manifest.name 79 | version = manifest.version 80 | install_path = install_path(name) 81 | repo = repo(name) 82 | 83 | repo.install_version! version, install_path 84 | end 85 | 86 | def manifest(name, version, dependencies) 87 | manifest = Manifest.new(self, name) 88 | manifest.version = version 89 | manifest.dependencies = dependencies 90 | manifest 91 | end 92 | 93 | def cache_path 94 | @cache_path ||= environment.cache_path.join("source/puppet/githubtarball/#{uri.host}#{uri.path}") 95 | end 96 | 97 | def install_path(name) 98 | environment.install_path.join(module_name(name)) 99 | end 100 | 101 | def fetch_version(name, version_uri) 102 | versions = repo(name).versions 103 | if versions.include? version_uri 104 | version_uri 105 | else 106 | versions.first 107 | end 108 | end 109 | 110 | def fetch_dependencies(_name, _version, _version_uri) 111 | {} 112 | end 113 | 114 | def manifests(name) 115 | repo(name).manifests 116 | end 117 | 118 | private 119 | 120 | def repo(name) 121 | @repo ||= {} 122 | @repo[name] ||= Repo.new(self, name) 123 | end 124 | end 125 | end 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /lib/librarian/puppet/source/forge/repo_v1.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'json' 4 | require 'open-uri' 5 | require 'librarian/puppet/source/forge/repo' 6 | 7 | module Librarian 8 | module Puppet 9 | module Source 10 | class Forge 11 | class RepoV1 < Librarian::Puppet::Source::Forge::Repo 12 | def initialize(source, name) 13 | super 14 | # API returned data for this module including all versions and dependencies, indexed by module name 15 | # from https://forge.puppetlabs.com/api/v1/releases.json?module=#{name} 16 | @api_data = nil 17 | # API returned data for this module and a specific version, indexed by version 18 | # from https://forge.puppetlabs.com/api/v1/releases.json?module=#{name}&version=#{version} 19 | @api_version_data = {} 20 | end 21 | 22 | def get_versions 23 | api_data(name).map { |r| r['version'] }.reverse 24 | end 25 | 26 | def dependencies(version) 27 | api_version_data(name, version)['dependencies'] 28 | end 29 | 30 | def url(name, version) 31 | info = api_version_data(name, version) 32 | "#{source}#{info[name].first['file']}" 33 | end 34 | 35 | private 36 | 37 | # convert organization/modulename to organization-modulename 38 | def normalize_dependencies(data) 39 | return nil if data.nil? 40 | 41 | # convert organization/modulename to organization-modulename 42 | data.keys.each do |m| 43 | if %r{.*/.*}.match?(m) 44 | data[normalize_name(m)] = data[m] 45 | data.delete(m) 46 | end 47 | end 48 | data 49 | end 50 | 51 | # get and cache the API data for a specific module with all its versions and dependencies 52 | def api_data(module_name) 53 | return @api_data[module_name] if @api_data 54 | 55 | # call API and cache data 56 | @api_data = normalize_dependencies(api_call(module_name)) 57 | raise Error, "Unable to find module '#{name}' on #{source}" if @api_data.nil? 58 | 59 | @api_data[module_name] 60 | end 61 | 62 | # get and cache the API data for a specific module and version 63 | def api_version_data(module_name, version) 64 | # if we already got all the versions, find in cached data 65 | return @api_data[module_name].detect { |x| x['version'] == version.to_s } if @api_data 66 | 67 | # otherwise call the api for this version if not cached already 68 | if @api_version_data[version].nil? 69 | @api_version_data[version] = 70 | normalize_dependencies(api_call(name, version)) 71 | end 72 | @api_version_data[version] 73 | end 74 | 75 | def api_call(module_name, version = nil) 76 | url = source.uri.clone 77 | url.path += "#{'/' if url.path.empty? or url.path[-1] != '/'}api/v1/releases.json" 78 | url.query = "module=#{module_name.sub('-', '/')}" # v1 API expects "organization/module" 79 | url.query += "&version=#{version}" unless version.nil? 80 | debug { "Querying Forge API for module #{name}#{" and version #{version}" unless version.nil?}: #{url}" } 81 | 82 | begin 83 | data = URI.open(url) { |f| f.read } 84 | JSON.parse(data) 85 | rescue OpenURI::HTTPError => e 86 | case e.io.status[0].to_i 87 | when 404, 410 88 | nil 89 | else 90 | raise e, "Error requesting #{url}: #{e}" 91 | end 92 | end 93 | end 94 | end 95 | end 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/librarian/puppet/cli.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'librarian/helpers' 4 | 5 | require 'librarian/cli' 6 | require 'librarian/puppet' 7 | require 'librarian/puppet/action' 8 | 9 | module Librarian 10 | module Puppet 11 | class Cli < Librarian::Cli 12 | include Librarian::Puppet::Util 13 | 14 | module Particularity 15 | def root_module 16 | Puppet 17 | end 18 | end 19 | 20 | include Particularity 21 | extend Particularity 22 | 23 | source_root Pathname.new(__FILE__).dirname.join('templates') 24 | 25 | def init 26 | copy_file environment.specfile_name 27 | 28 | gitignore = if File.exist? '.gitignore' 29 | File.read('.gitignore').split("\n") 30 | else 31 | [] 32 | end 33 | 34 | gitignore << '.tmp/' unless gitignore.include? '.tmp/' 35 | gitignore << 'modules/' unless gitignore.include? 'modules/' 36 | 37 | File.open('.gitignore', 'w') do |f| 38 | f.puts gitignore.join("\n") 39 | end 40 | end 41 | 42 | desc 'install', 'Resolves and installs all of the dependencies you specify.' 43 | option 'quiet', type: :boolean, default: false 44 | option 'verbose', type: :boolean, default: false 45 | option 'line-numbers', type: :boolean, default: false 46 | option 'clean', type: :boolean, default: false 47 | option 'strip-dot-git', type: :boolean 48 | option 'path', type: :string 49 | option 'destructive', type: :boolean, default: false 50 | option 'local', type: :boolean, default: false 51 | option 'use-v1-api', type: :boolean, default: false 52 | def install 53 | ensure! 54 | clean! if options['clean'] 55 | environment.config_db.local['destructive'] = options['destructive'].to_s unless options['destructive'].nil? 56 | if options.include?('strip-dot-git') 57 | strip_dot_git_val = options['strip-dot-git'] ? '1' : nil 58 | environment.config_db.local['install.strip-dot-git'] = strip_dot_git_val 59 | end 60 | environment.config_db.local['path'] = options['path'] if options.include?('path') 61 | 62 | environment.config_db.local['use-v1-api'] = options['use-v1-api'] ? '1' : nil 63 | environment.config_db.local['mode'] = options['local'] ? 'local' : nil 64 | 65 | resolve! 66 | debug { 'Install: dependencies resolved' } 67 | install! 68 | end 69 | 70 | desc 'update', 'Updates and installs the dependencies you specify.' 71 | option 'verbose', type: :boolean, default: false 72 | option 'line-numbers', type: :boolean, default: false 73 | option 'use-v1-api', type: :boolean, default: false 74 | def update(*names) 75 | environment.config_db.local['use-v1-api'] = options['use-v1-api'] ? '1' : nil 76 | 77 | warn('Usage of module/name is deprecated, use module-name') if names.any? { |n| n.include?('/') } 78 | # replace / to - in the module names 79 | super(*names.map { |n| normalize_name(n) }) 80 | end 81 | 82 | desc 'package', 'Cache the puppet modules in vendor/puppet/cache.' 83 | option 'quiet', type: :boolean, default: false 84 | option 'verbose', type: :boolean, default: false 85 | option 'line-numbers', type: :boolean, default: false 86 | option 'clean', type: :boolean, default: false 87 | option 'strip-dot-git', type: :boolean 88 | option 'path', type: :string 89 | option 'destructive', type: :boolean, default: false 90 | def package 91 | environment.vendor! 92 | install 93 | end 94 | 95 | def version 96 | say "librarian-puppet v#{Librarian::Puppet::VERSION}" 97 | end 98 | 99 | private 100 | 101 | # override the actions to use our own 102 | 103 | def install!(options = {}) 104 | Action::Install.new(environment, options).run 105 | end 106 | 107 | def resolve!(options = {}) 108 | Action::Resolve.new(environment, options).run 109 | end 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /lib/librarian/puppet/source/local.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'librarian/puppet/util' 4 | 5 | module Librarian 6 | module Puppet 7 | module Source 8 | module Local 9 | include Librarian::Puppet::Util 10 | 11 | def install!(manifest) 12 | manifest.source == self or raise ArgumentError 13 | 14 | debug { "Installing #{manifest}" } 15 | 16 | name = manifest.name 17 | _ = manifest.version 18 | found_path = found_path(name) 19 | raise Error, "Path for #{name} doesn't contain a puppet module" if found_path.nil? 20 | 21 | unless name.include? '/' or name.include? '-' 22 | warn do 23 | "Invalid module name '#{name}', you should qualify it with 'ORGANIZATION-#{name}' for resolution to work correctly" 24 | end 25 | end 26 | 27 | install_path = environment.install_path.join(module_name(name)) 28 | if install_path.exist? && rsync? != true 29 | debug { "Deleting #{relative_path_to(install_path)}" } 30 | install_path.rmtree 31 | end 32 | 33 | install_perform_step_copy!(found_path, install_path) 34 | end 35 | 36 | def fetch_version(name, _extra) 37 | cache! 38 | found_path(name) 39 | module_version 40 | end 41 | 42 | def fetch_dependencies(name, _version, _extra) 43 | dependencies = Set.new 44 | 45 | if specfile? 46 | spec = environment.dsl(Pathname(specfile)) 47 | dependencies.merge spec.dependencies 48 | end 49 | 50 | parsed_metadata['dependencies'].each do |d| 51 | gem_requirement = Librarian::Dependency::Requirement.new(d['version_requirement']).to_gem_requirement 52 | new_dependency = Dependency.new(d['name'], gem_requirement, forge_source, name) 53 | dependencies << new_dependency 54 | end 55 | 56 | dependencies 57 | end 58 | 59 | def forge_source 60 | Forge.default 61 | end 62 | 63 | private 64 | 65 | # Naming this method 'version' causes an exception to be raised. 66 | def module_version 67 | if parsed_metadata['version'] 68 | parsed_metadata['version'] 69 | else 70 | warn { "Module #{self} does not have version, defaulting to 0.0.1" } 71 | '0.0.1' 72 | end 73 | end 74 | 75 | def parsed_metadata 76 | if @metadata.nil? 77 | @metadata = if metadata? 78 | begin 79 | JSON.parse(File.read(metadata)) 80 | rescue JSON::ParserError => e 81 | raise Error, "Unable to parse json file #{metadata}: #{e}" 82 | end 83 | else 84 | {} 85 | end 86 | @metadata['dependencies'] ||= [] 87 | end 88 | @metadata 89 | end 90 | 91 | def metadata 92 | File.join(filesystem_path, 'metadata.json') 93 | end 94 | 95 | def metadata? 96 | File.exist?(metadata) 97 | end 98 | 99 | def specfile 100 | File.join(filesystem_path, environment.specfile_name) 101 | end 102 | 103 | def specfile? 104 | File.exist?(specfile) 105 | end 106 | 107 | def install_perform_step_copy!(found_path, install_path) 108 | debug { "Copying #{relative_path_to(found_path)} to #{relative_path_to(install_path)}" } 109 | cp_r(found_path, install_path) 110 | end 111 | 112 | def manifest?(_name, path) 113 | return true if path.join('manifests').exist? 114 | return true if path.join('lib').join('puppet').exist? 115 | return true if path.join('lib').join('facter').exist? 116 | 117 | debug { "Could not find manifests, lib/puppet or lib/facter under #{path}, maybe it is not a puppet module" } 118 | true 119 | end 120 | end 121 | end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /lib/librarian/puppet/source/forge.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'uri' 4 | require 'librarian/puppet/util' 5 | require 'librarian/puppet/source/forge/repo_v1' 6 | require 'librarian/puppet/source/forge/repo_v3' 7 | 8 | module Librarian 9 | module Puppet 10 | module Source 11 | class Forge 12 | include Librarian::Puppet::Util 13 | 14 | class << self 15 | LOCK_NAME = 'FORGE' 16 | 17 | def default=(source) 18 | @@default = source 19 | end 20 | 21 | def default 22 | @@default 23 | end 24 | 25 | def lock_name 26 | LOCK_NAME 27 | end 28 | 29 | def from_lock_options(environment, options) 30 | new(environment, options[:remote], options.reject { |k, _v| k == :remote }) 31 | end 32 | 33 | def from_spec_args(environment, uri, options) 34 | recognised_options = [] 35 | unrecognised_options = options.keys - recognised_options 36 | raise Error, "unrecognised options: #{unrecognised_options.join(', ')}" unless unrecognised_options.empty? 37 | 38 | new(environment, uri, options) 39 | end 40 | end 41 | 42 | attr_accessor :environment 43 | private :environment= 44 | attr_reader :uri 45 | 46 | def initialize(environment, uri, _options = {}) 47 | self.environment = environment 48 | 49 | @uri = URI.parse(uri) 50 | @cache_path = nil 51 | end 52 | 53 | def to_s 54 | clean_uri(uri).to_s 55 | end 56 | 57 | def ==(other) 58 | other && 59 | self.class == other.class && 60 | uri == other.uri 61 | end 62 | 63 | alias eql? == 64 | 65 | def hash 66 | to_s.hash 67 | end 68 | 69 | def to_spec_args 70 | [clean_uri(uri).to_s, {}] 71 | end 72 | 73 | def to_lock_options 74 | { remote: uri.to_s } 75 | end 76 | 77 | def pinned? 78 | false 79 | end 80 | 81 | def unpin!; end 82 | 83 | def install!(manifest) 84 | manifest.source == self or raise ArgumentError 85 | 86 | debug { "Installing #{manifest}" } 87 | 88 | name = manifest.name 89 | version = manifest.version 90 | install_path = install_path(name) 91 | repo = repo(name) 92 | 93 | repo.install_version! version, install_path 94 | end 95 | 96 | def manifest(name, version, dependencies) 97 | manifest = Manifest.new(self, name) 98 | manifest.version = version 99 | manifest.dependencies = dependencies 100 | manifest 101 | end 102 | 103 | def cache_path 104 | @cache_path ||= begin 105 | dir = "#{uri.host}#{uri.path}".gsub(/[^0-9a-z\-_]/i, '_') 106 | environment.cache_path.join("source/puppet/forge/#{dir}") 107 | end 108 | end 109 | 110 | def install_path(name) 111 | environment.install_path.join(module_name(name)) 112 | end 113 | 114 | def fetch_version(name, version_uri) 115 | versions = repo(name).versions 116 | if versions.include? version_uri 117 | version_uri 118 | else 119 | versions.first 120 | end 121 | end 122 | 123 | def fetch_dependencies(name, version, _version_uri) 124 | repo(name).dependencies(version).map do |k, v| 125 | v = Librarian::Dependency::Requirement.new(v).to_gem_requirement 126 | Dependency.new(k, v, nil, name) 127 | end 128 | end 129 | 130 | def manifests(name) 131 | repo(name).manifests 132 | end 133 | 134 | private 135 | 136 | def repo(name) 137 | @repo ||= {} 138 | 139 | unless @repo[name] 140 | # If we are using the official Forge then use API v3, otherwise use the preferred api 141 | # as defined by the CLI option use_v1_api 142 | @repo[name] = if environment.use_v1_api 143 | RepoV1.new(self, name) 144 | else 145 | RepoV3.new(self, name) 146 | end 147 | end 148 | @repo[name] 149 | end 150 | end 151 | end 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /lib/librarian/puppet/source/forge/repo.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'json' 4 | require 'open-uri' 5 | require 'librarian/puppet/util' 6 | require 'librarian/puppet/source/repo' 7 | 8 | module Librarian 9 | module Puppet 10 | module Source 11 | class Forge 12 | class Repo < Librarian::Puppet::Source::Repo 13 | include Librarian::Puppet::Util 14 | 15 | def versions 16 | return @versions if @versions 17 | 18 | @versions = get_versions 19 | if @versions.empty? 20 | info { "No versions found for module #{name}" } 21 | else 22 | debug { " Module #{name} found versions: #{@versions.join(', ')}" } 23 | end 24 | @versions 25 | end 26 | 27 | # fetch list of versions ordered for newer to older 28 | def get_versions 29 | # implement in subclasses 30 | end 31 | 32 | # return map with dependencies in the form {module_name => version,...} 33 | # version: Librarian::Manifest::Version 34 | def dependencies(version) 35 | # implement in subclasses 36 | end 37 | 38 | # return the url for a specific version tarball 39 | # version: Librarian::Manifest::Version 40 | def url(name, version) 41 | # implement in subclasses 42 | end 43 | 44 | def manifests 45 | versions.map do |version| 46 | Manifest.new(source, name, version) 47 | end 48 | end 49 | 50 | def install_version!(version, install_path) 51 | if environment.local? && !vendored?(name, version) 52 | raise Error, "Could not find a local copy of #{name} at #{version}." 53 | end 54 | 55 | vendor_cache(name, version) if environment.vendor? && !vendored?(name, version) 56 | 57 | cache_version_unpacked! version 58 | 59 | install_path.rmtree if install_path.exist? && rsync? != true 60 | 61 | unpacked_path = version_unpacked_cache_path(version).join(module_name(name)) 62 | 63 | unless unpacked_path.exist? 64 | raise Error, "#{unpacked_path} does not exist, something went wrong. Try removing it manually" 65 | end 66 | 67 | cp_r(unpacked_path, install_path) 68 | end 69 | 70 | def cache_version_unpacked!(version) 71 | path = version_unpacked_cache_path(version) 72 | return if path.directory? 73 | 74 | path.mkpath 75 | 76 | target = vendored?(name, version) ? vendored_path(name, version).to_s : name 77 | 78 | module_repository = source.uri.to_s 79 | 80 | command = %W[puppet module install --version #{version} --target-dir] 81 | command.push(path.to_s, '--module_repository', module_repository, '--modulepath', path.to_s, 82 | '--module_working_dir', path.to_s, '--ignore-dependencies', target) 83 | debug do 84 | "Executing puppet module install for #{name} #{version}: #{command.join(' ').gsub(module_repository, 85 | source.to_s)}" 86 | end 87 | 88 | begin 89 | Librarian::Posix.run!(command) 90 | rescue Posix::CommandFailure => e 91 | # Rollback the directory if the puppet module had an error 92 | begin 93 | path.unlink 94 | rescue StandardError => u 95 | debug("Unable to rollback path #{path}: #{u}") 96 | end 97 | tar = Dir[File.join(path.to_s, '**/*.tar.gz')] 98 | msg = '' 99 | if e.message =~ /Unexpected EOF in archive/ and !tar.empty? 100 | file = tar.first 101 | msg = " (looks like an incomplete download of #{file})" 102 | end 103 | raise Error, 104 | "Error executing puppet module install#{msg}. Check that this command succeeds:\n#{command.join(' ')}\nError:\n#{e.message}" 105 | end 106 | end 107 | 108 | def vendor_cache(name, version) 109 | url = url(name, version) 110 | path = vendored_path(name, version).to_s 111 | debug { "Downloading #{url} into #{path}" } 112 | environment.vendor! 113 | File.open(path, 'wb') do |f| 114 | URI.open(url, 'rb') do |input| 115 | f.write(input.read) 116 | end 117 | end 118 | end 119 | end 120 | end 121 | end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /features/install.feature: -------------------------------------------------------------------------------- 1 | Feature: cli/install 2 | In order to be worth anything 3 | Puppet librarian needs to install modules properly 4 | 5 | Scenario: Running install with no Puppetfile nor metadata.json 6 | Given there is no Puppetfile 7 | When I run `librarian-puppet install` 8 | Then the output should match /^Metadata file does not exist: .*metadata.json$/ 9 | And the exit status should be 1 10 | 11 | Scenario: Running install with bad metadata.json 12 | Given a file named "metadata.json" with: 13 | """ 14 | """ 15 | When I run `librarian-puppet install` 16 | Then the output should match /^Unable to parse json file .*metadata.json: .*$/ 17 | And the exit status should be 1 18 | 19 | Scenario: Install a module transitive dependency from git and forge should be deterministic 20 | Given a file named "Puppetfile" with: 21 | """ 22 | forge "https://forgeapi.puppet.com" 23 | 24 | mod 'puppetlabs/stdlib', :git => 'https://github.com/puppetlabs/puppetlabs-stdlib.git', :ref => '4.6.0' 25 | mod 'librarian/test', :git => 'https://github.com/voxpupuli/librarian-puppet.git', :path => 'features/examples/test' 26 | """ 27 | When I successfully run `librarian-puppet install --verbose` 28 | And the file "modules/stdlib/metadata.json" should match /"version": "4\.6\.0"/ 29 | And the output should not match /Executing puppet module install for puppetlabs.stdlib/ 30 | 31 | Scenario: Install duplicated dependencies from git and forge, last one wins 32 | Given a file named "Puppetfile" with: 33 | """ 34 | forge "https://forgeapi.puppet.com" 35 | 36 | metadata 37 | mod 'puppetlabs-stdlib', :git => 'https://github.com/puppetlabs/puppetlabs-stdlib.git', :ref => '4.6.0' 38 | """ 39 | And a file named "metadata.json" with: 40 | """ 41 | { 42 | "name": "random name", 43 | "dependencies": [ 44 | { 45 | "name": "puppetlabs/stdlib", 46 | "version_requirement": ">= 0" 47 | } 48 | ] 49 | } 50 | """ 51 | When I successfully run `librarian-puppet install --verbose` 52 | And the file "modules/stdlib/metadata.json" should match /"version": "4\.6\.0"/ 53 | And the output should not match /Executing puppet module install for puppetlabs.stdlib/ 54 | 55 | Scenario: Installing two modules with same name and using exclusions 56 | Given a file named "Puppetfile" with: 57 | """ 58 | forge "https://forgeapi.puppet.com" 59 | 60 | mod 'librarian-duplicated_dependencies', :path => '../../features/examples/duplicated_dependencies' 61 | exclusion 'ripienaar-concat' 62 | """ 63 | When I successfully run `librarian-puppet install --verbose` 64 | And the file "modules/concat/metadata.json" should match /"name": "puppetlabs-concat"/ 65 | And the output should contain "Excluding dependency ripienaar-concat from" 66 | 67 | Scenario: Installing two modules with same name and using exclusions, apply transitively 68 | Given a file named "Puppetfile" with: 69 | """ 70 | forge "https://forgeapi.puppet.com" 71 | 72 | mod 'librarian-duplicated_dependencies_transitive', :path => '../../features/examples/duplicated_dependencies_transitive' 73 | """ 74 | When PENDING I run `librarian-puppet install --verbose` 75 | Then the exit status should be 0 76 | And the file "modules/concat/metadata.json" should match /"name": "puppetlabs-concat"/ 77 | 78 | Scenario: Install a module with the rsync configuration using the --clean flag 79 | Given a file named "Puppetfile" with: 80 | """ 81 | forge "https://forgeapi.puppet.com" 82 | 83 | mod 'maestrodev/test' 84 | """ 85 | And a file named ".librarian/puppet/config" with: 86 | """ 87 | --- 88 | LIBRARIAN_PUPPET_RSYNC: 'true' 89 | """ 90 | When I successfully run `librarian-puppet config` 91 | And the output should contain "rsync: true" 92 | When I successfully run `librarian-puppet install` 93 | And a directory named "modules/test" should exist 94 | And the file "modules/test" should have an inode and ctime 95 | When I successfully run `librarian-puppet install --clean` 96 | And a directory named "modules/test" should exist 97 | And the file "modules/test" should not have the same inode or ctime as before 98 | 99 | Scenario: Install a module with the rsync configuration using the --destructive flag 100 | Given a file named "Puppetfile" with: 101 | """ 102 | forge "https://forgeapi.puppet.com" 103 | 104 | mod 'maestrodev/test' 105 | """ 106 | And a file named ".librarian/puppet/config" with: 107 | """ 108 | --- 109 | LIBRARIAN_PUPPET_RSYNC: 'true' 110 | """ 111 | When I successfully run `librarian-puppet config` 112 | And the output should contain "rsync: true" 113 | When I successfully run `librarian-puppet install` 114 | And a directory named "modules/test" should exist 115 | And the file "modules/test" should have an inode and ctime 116 | Given I wait for 1 second 117 | When I successfully run `librarian-puppet install --destructive` 118 | And a directory named "modules/test" should exist 119 | And the file "modules/test" should not have the same inode or ctime as before 120 | 121 | Scenario: Install a module with the rsync configuration 122 | Given a file named "Puppetfile" with: 123 | """ 124 | forge "https://forgeapi.puppet.com" 125 | 126 | mod 'maestrodev/test' 127 | """ 128 | And a file named ".librarian/puppet/config" with: 129 | """ 130 | --- 131 | LIBRARIAN_PUPPET_RSYNC: 'true' 132 | """ 133 | When I successfully run `librarian-puppet config` 134 | And the output should contain "rsync: true" 135 | When I successfully run `librarian-puppet install` 136 | And a directory named "modules/test" should exist 137 | And the file "modules/test" should have an inode and ctime 138 | Given I wait for 1 second 139 | When I successfully run `librarian-puppet install` 140 | And a directory named "modules/test" should exist 141 | And the file "modules/test" should have the same inode and ctime as before 142 | -------------------------------------------------------------------------------- /lib/librarian/puppet/source/githubtarball/repo.rb: -------------------------------------------------------------------------------- 1 | require 'uri' 2 | require 'net/https' 3 | require 'open-uri' 4 | require 'json' 5 | 6 | require 'librarian/puppet/version' 7 | require 'librarian/puppet/source/repo' 8 | 9 | module Librarian 10 | module Puppet 11 | module Source 12 | class GitHubTarball 13 | class Repo < Librarian::Puppet::Source::Repo 14 | include Librarian::Puppet::Util 15 | 16 | TOKEN_KEY = 'GITHUB_API_TOKEN' 17 | 18 | def versions 19 | return @versions if @versions 20 | 21 | data = api_call("/repos/#{source.uri}/tags") 22 | raise Error, "Unable to find module '#{source.uri}' on https://github.com" if data.nil? 23 | 24 | all_versions = data.map { |r| r['name'].gsub(/^v/, '') }.sort.reverse 25 | 26 | all_versions.delete_if do |version| 27 | version !~ /\A\d+\.\d+(\.\d+.*)?\z/ 28 | end 29 | 30 | @versions = all_versions.compact 31 | debug { " Module #{name} found versions: #{@versions.join(', ')}" } 32 | @versions 33 | end 34 | 35 | def manifests 36 | versions.map do |version| 37 | Manifest.new(source, name, version) 38 | end 39 | end 40 | 41 | def install_version!(version, install_path) 42 | if environment.local? && !vendored?(vendored_name, version) 43 | raise Error, "Could not find a local copy of #{source.uri} at #{version}." 44 | end 45 | 46 | vendor_cache(source.uri.to_s, version) unless vendored?(vendored_name, version) 47 | 48 | cache_version_unpacked! version 49 | 50 | install_path.rmtree if install_path.exist? && rsync? != true 51 | 52 | unpacked_path = version_unpacked_cache_path(version).children.first 53 | cp_r(unpacked_path, install_path) 54 | end 55 | 56 | def cache_version_unpacked!(version) 57 | path = version_unpacked_cache_path(version) 58 | return if path.directory? 59 | 60 | path.mkpath 61 | 62 | target = vendored?(vendored_name, version) ? vendored_path(vendored_name, version) : name 63 | 64 | Librarian::Posix.run!(%W[tar xzf #{target} -C #{path}]) 65 | end 66 | 67 | def vendor_cache(name, version) 68 | clean_up_old_cached_versions(vendored_name(name)) 69 | 70 | url = "https://api.github.com/repos/#{name}/tarball/#{version}" 71 | add_api_token_to_url(url) 72 | 73 | environment.vendor! 74 | File.open(vendored_path(vendored_name(name), version).to_s, 'wb') do |f| 75 | debug { "Downloading <#{url}> to <#{f.path}>" } 76 | URI.open(url, 77 | 'User-Agent' => "librarian-puppet v#{Librarian::Puppet::VERSION}") do |res| 78 | while buffer = res.read(8192) 79 | f.write(buffer) 80 | end 81 | end 82 | rescue OpenURI::HTTPError => e 83 | raise e, "Error requesting <#{url}>: #{e}" 84 | end 85 | end 86 | 87 | def clean_up_old_cached_versions(name) 88 | Dir["#{environment.vendor_cache}/#{name}*.tar.gz"].each do |old_version| 89 | FileUtils.rm old_version 90 | end 91 | end 92 | 93 | def token_key_value 94 | ENV.fetch(TOKEN_KEY, nil) 95 | end 96 | 97 | def token_key_nil? 98 | token_key_value.nil? || token_key_value.empty? 99 | end 100 | 101 | def add_api_token_to_url(url) 102 | if token_key_nil? 103 | debug { "#{TOKEN_KEY} environment value is empty or missing" } 104 | elsif url.include? '?' 105 | url << "&access_token=#{ENV.fetch(TOKEN_KEY, nil)}" 106 | else 107 | url << "?access_token=#{ENV.fetch(TOKEN_KEY, nil)}" 108 | end 109 | url 110 | end 111 | 112 | private 113 | 114 | def api_call(path) 115 | tags = [] 116 | url = "https://api.github.com#{path}?page=1&per_page=100" 117 | while true 118 | debug { " Module #{name} getting tags at: #{url}" } 119 | add_api_token_to_url(url) 120 | response = http_get(url, headers: { 121 | 'User-Agent' => "librarian-puppet v#{Librarian::Puppet::VERSION}", 122 | }) 123 | 124 | code = response.code.to_i 125 | data = response.body 126 | 127 | if code == 200 128 | tags.concat JSON.parse(data) 129 | else 130 | begin 131 | message = JSON.parse(data)['message'] 132 | if code == 403 && message && message.include?('API rate limit exceeded') 133 | raise Error, message + " -- increase limit by authenticating via #{TOKEN_KEY}=your-token" 134 | elsif message 135 | raise Error, "Error fetching #{url}: [#{code}] #{message}" 136 | end 137 | rescue JSON::ParserError 138 | # response does not return json 139 | end 140 | raise Error, "Error fetching #{url}: [#{code}] #{response.body}" 141 | end 142 | 143 | # next page 144 | break if response['link'].nil? 145 | 146 | next_link = response['link'].split(',').select { |l| l.match(/rel=.*next.*/) } 147 | break if next_link.empty? 148 | 149 | url = next_link.first.match(/<(.*)>/)[1] 150 | end 151 | tags 152 | end 153 | 154 | def http_get(url, options) 155 | uri = URI.parse(url) 156 | http = Net::HTTP.new(uri.host, uri.port) 157 | http.use_ssl = true 158 | request = Net::HTTP::Get.new(uri.request_uri) 159 | options[:headers].each { |k, v| request.add_field k, v } 160 | http.request(request) 161 | end 162 | 163 | def vendored_name(name = source.uri.to_s) 164 | name.sub('/', '-') 165 | end 166 | end 167 | end 168 | end 169 | end 170 | end 171 | -------------------------------------------------------------------------------- /features/install/git.feature: -------------------------------------------------------------------------------- 1 | Feature: cli/install/git 2 | Puppet librarian needs to install modules from git repositories 3 | 4 | Scenario: Installing a module from git 5 | Given a file named "Puppetfile" with: 6 | """ 7 | forge "https://forgeapi.puppet.com" 8 | 9 | mod 'puppetlabs/apache', 10 | :git => 'https://github.com/puppetlabs/puppetlabs-apache.git', :ref => '1.4.0' 11 | 12 | mod 'puppetlabs/stdlib', 13 | :git => 'https://github.com/puppetlabs/puppetlabs-stdlib.git', :ref => '4.6.0' 14 | """ 15 | When I successfully run `librarian-puppet install` 16 | And the file "modules/apache/metadata.json" should match /"name": "puppetlabs-apache"/ 17 | And the file "modules/apache/metadata.json" should match /"version": "1\.4\.0"/ 18 | And the git revision of module "apache" should be "e4ec6d4985fdb23e26c809e0d5786823d0689f90" 19 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 20 | And the file "modules/stdlib/metadata.json" should match /"version": "4\.6\.0"/ 21 | And the git revision of module "stdlib" should be "73474b00b5ae3cbccec6cd0711311d6450139e51" 22 | 23 | @spaces 24 | Scenario: Installing a module in a path with spaces 25 | Given a file named "Puppetfile" with: 26 | """ 27 | mod 'puppetlabs/stdlib', '4.6.0', :git => 'https://github.com/puppetlabs/puppetlabs-stdlib.git', :ref => '4.6.0' 28 | """ 29 | When I successfully run `librarian-puppet install` 30 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 31 | 32 | Scenario: Installing a module with invalid versions in git 33 | Given a file named "Puppetfile" with: 34 | """ 35 | forge "https://forgeapi.puppet.com" 36 | 37 | mod "apache", 38 | :git => "https://github.com/puppetlabs/puppetlabs-apache.git", :ref => "1.4.0" 39 | """ 40 | When I successfully run `librarian-puppet install` 41 | And the file "modules/apache/metadata.json" should match /"name": "puppetlabs-apache"/ 42 | And the file "modules/apache/metadata.json" should match /"version": "1\.4\.0"/ 43 | 44 | Scenario: Switching a module from forge to git 45 | Given a file named "Puppetfile" with: 46 | """ 47 | forge "https://forgeapi.puppet.com" 48 | 49 | mod 'puppetlabs/postgresql', '7.4.1' 50 | """ 51 | When I successfully run `librarian-puppet install` 52 | And the file "modules/postgresql/metadata.json" should match /"name": "puppetlabs-postgresql"/ 53 | And the file "modules/postgresql/metadata.json" should match /"version": "7\.4\.1"/ 54 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 55 | When I overwrite "Puppetfile" with: 56 | """ 57 | forge "https://forgeapi.puppet.com" 58 | 59 | mod 'puppetlabs/postgresql', 60 | :git => 'https://github.com/puppetlabs/puppetlabs-postgresql.git', :ref => 'v7.5.0' 61 | """ 62 | And I run `librarian-puppet install` 63 | Then the exit status should be 0 64 | And the file "modules/postgresql/metadata.json" should match /"name": "puppetlabs-postgresql"/ 65 | And the file "modules/postgresql/metadata.json" should match /"version": "7\.5\.0"/ 66 | And the file "modules/postgresql/.git/HEAD" should match /0a2cb69ccbbb0a55d42c5da33d44b0eaf33f9546/ 67 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 68 | 69 | Scenario: Install a module with dependencies specified in metadata.json 70 | Given a file named "Puppetfile" with: 71 | """ 72 | mod 'puppetlabs-apt', :git => 'https://github.com/puppetlabs/puppetlabs-apt.git', :ref => '1.5.2' 73 | """ 74 | When I successfully run `librarian-puppet install` 75 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 76 | And the file "modules/apt/metadata.json" should match /"name": "puppetlabs-apt"/ 77 | 78 | Scenario: Install a module with dependencies specified in a Puppetfile 79 | Given a file named "Puppetfile" with: 80 | """ 81 | mod 'librarian/with_puppetfile', :git => 'https://github.com/voxpupuli/librarian-puppet.git', :path => 'features/examples/with_puppetfile' 82 | """ 83 | When I successfully run `librarian-puppet install` 84 | And the file "modules/with_puppetfile/metadata.json" should match /"name": "librarian-with_puppetfile"/ 85 | And the file "modules/test/metadata.json" should match /"name": "librarian-test"/ 86 | 87 | Scenario: Install a module with dependencies specified in a Puppetfile and metadata.json 88 | Given a file named "Puppetfile" with: 89 | """ 90 | mod 'librarian/with_puppetfile', :git => 'https://github.com/voxpupuli/librarian-puppet.git', :path => 'features/examples/with_puppetfile_and_metadata_json' 91 | """ 92 | When I successfully run `librarian-puppet install` 93 | And the file "modules/with_puppetfile/metadata.json" should match /"name": "librarian-with_puppetfile_and_metadata_json"/ 94 | And the file "modules/test/metadata.json" should match /"name": "maestrodev-test"/ 95 | 96 | Scenario: Running install without metadata.json 97 | Given a file named "Puppetfile" with: 98 | """ 99 | forge "https://forgeapi.puppet.com" 100 | 101 | mod 'puppetlabs/stdlib', :git => 'https://github.com/puppetlabs/puppetlabs-stdlib.git', :ref => '4.6.0' 102 | """ 103 | When I successfully run `librarian-puppet install` 104 | 105 | Scenario: Running install with metadata.json without dependencies 106 | Given a file named "Puppetfile" with: 107 | """ 108 | forge "https://forgeapi.puppet.com" 109 | 110 | mod 'puppetlabs/sqlite', :git => 'https://github.com/puppetlabs/puppetlabs-sqlite.git', :ref => '84a0a6' 111 | """ 112 | When I successfully run `librarian-puppet install` 113 | 114 | Scenario: Install a module using metadata syntax 115 | Given a file named "Puppetfile" with: 116 | """ 117 | mod 'librarian/metadata_syntax', :git => 'https://github.com/voxpupuli/librarian-puppet.git', :path => 'features/examples/metadata_syntax' 118 | """ 119 | When I successfully run `librarian-puppet install` 120 | And the file "modules/metadata_syntax/metadata.json" should match /"name": "librarian-metadata_syntax"/ 121 | And the file "modules/test/metadata.json" should match /"name": "maestrodev-test"/ 122 | 123 | Scenario: Install a module from git and using path 124 | Given a file named "Puppetfile" with: 125 | """ 126 | forge "https://forgeapi.puppet.com" 127 | 128 | mod 'librarian-test', :git => 'https://github.com/voxpupuli/librarian-puppet.git', :path => 'features/examples/test' 129 | """ 130 | When I successfully run `librarian-puppet install` 131 | And the file "modules/test/metadata.json" should match /"version": "0\.0\.1"/ 132 | And a file named "modules/stdlib/metadata.json" should exist 133 | 134 | Scenario: Install a module from git without version 135 | Given a file named "Puppetfile" with: 136 | """ 137 | forge "https://forgeapi.puppet.com" 138 | 139 | mod 'test', :git => 'https://github.com/voxpupuli/librarian-puppet.git', :path => 'features/examples/dependency_without_version' 140 | """ 141 | When I successfully run `librarian-puppet install` 142 | And the file "modules/test/metadata.json" should match /"version": "0\.0\.1"/ 143 | And a file named "modules/stdlib/metadata.json" should exist 144 | 145 | Scenario: Install from Puppetfile with duplicated entries 146 | Given a file named "Puppetfile" with: 147 | """ 148 | mod 'puppetlabs-stdlib', 149 | :git => 'git://github.com/puppetlabs/puppetlabs-stdlib.git', :ref => 'main' 150 | 151 | mod 'puppetlabs-stdlib', 152 | :git => 'https://github.com/puppetlabs/puppetlabs-stdlib.git', :ref => 'main' 153 | """ 154 | When I successfully run `librarian-puppet install` 155 | And the output should contain "Dependency 'puppetlabs-stdlib' duplicated for module, merging" 156 | 157 | Scenario: Installing a module from git with the --strip-dot-git flag 158 | Given a file named "Puppetfile" with: 159 | """ 160 | mod 'puppetlabs-stdlib', 161 | :git => 'https://github.com/puppetlabs/puppetlabs-stdlib.git', 162 | :ref => 'main' 163 | """ 164 | When I successfully run `librarian-puppet install --strip-dot-git` for up to 60 seconds 165 | And a directory named "modules/stdlib/.git" should not exist 166 | -------------------------------------------------------------------------------- /features/install/forge.feature: -------------------------------------------------------------------------------- 1 | Feature: cli/install/forge 2 | Puppet librarian needs to install modules from the Puppet Forge 3 | 4 | Scenario: Installing a module and its dependencies 5 | Given a file named "Puppetfile" with: 6 | """ 7 | forge "https://forgeapi.puppet.com" 8 | 9 | mod 'puppetlabs/ntp' 10 | """ 11 | When I successfully run `librarian-puppet install` 12 | And the file "modules/ntp/metadata.json" should match /"name": "puppetlabs-ntp"/ 13 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 14 | 15 | Scenario: Running install with no Puppetfile and metadata.json 16 | Given there is no Puppetfile 17 | And a file named "metadata.json" with: 18 | """ 19 | { 20 | "name": "random name", 21 | "dependencies": [ 22 | { 23 | "name": "puppetlabs/stdlib", 24 | "version_requirement": "4.1.0" 25 | } 26 | ] 27 | } 28 | """ 29 | When I successfully run `librarian-puppet install` 30 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 31 | 32 | Scenario: Installing a module without forge 33 | Given a file named "Puppetfile" with: 34 | """ 35 | mod 'puppetlabs/stdlib', '4.1.0' 36 | """ 37 | When I run `librarian-puppet install` 38 | Then the exit status should be 1 39 | And the output should contain "forge entry is not defined in Puppetfile" 40 | 41 | Scenario: Installing an exact version of a module 42 | Given a file named "Puppetfile" with: 43 | """ 44 | forge "https://forgeapi.puppet.com" 45 | 46 | mod 'puppetlabs/apt', '0.0.4' 47 | """ 48 | When I successfully run `librarian-puppet install` 49 | And the file "modules/apt/Modulefile" should match /name *'puppetlabs-apt'/ 50 | And the file "modules/apt/Modulefile" should match /version *'0\.0\.4'/ 51 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 52 | 53 | # Puppet Module tool does not support spaces 54 | # https://github.com/rodjek/librarian-puppet/issues/201 55 | # https://tickets.puppetlabs.com/browse/PUP-2278 56 | @spaces 57 | Scenario: Installing a module in a path with spaces 58 | Given a file named "Puppetfile" with: 59 | """ 60 | forge "https://forgeapi.puppet.com" 61 | mod 'puppetlabs/stdlib', '4.1.0' 62 | """ 63 | When PENDING I run `librarian-puppet install` 64 | Then the exit status should be 0 65 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 66 | 67 | Scenario: Installing a module with invalid versions in the forge 68 | Given a file named "Puppetfile" with: 69 | """ 70 | forge "https://forgeapi.puppet.com" 71 | 72 | mod 'puppetlabs/apache', '7.0.0' # compatible with stdlib 8 73 | mod 'puppetlabs/postgresql', '7.4.0' # incompatible with stdlib 8 74 | 75 | """ 76 | # Default timeout is 15 seconds but this is too short in most cases 77 | When I successfully run `librarian-puppet install --verbose` for up to 30 seconds 78 | And the file "modules/apache/metadata.json" should match /"name": "puppetlabs-apache"/ 79 | And the file "modules/apache/metadata.json" should match /"version": "7\.0\.0"/ 80 | And the file "modules/postgresql/metadata.json" should match /"name": "puppetlabs-postgresql"/ 81 | And the file "modules/postgresql/metadata.json" should match /"version": "7\.4\.0"/ 82 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 83 | And the file "modules/stdlib/metadata.json" should match /"version": "7\.1\.0"/ 84 | 85 | Scenario: Installing a module with several constraints 86 | Given a file named "Puppetfile" with: 87 | """ 88 | forge "https://forgeapi.puppet.com" 89 | 90 | mod 'puppetlabs/apt', '>=1.0.0', '<1.0.1' 91 | """ 92 | When I successfully run `librarian-puppet install` 93 | And the file "modules/apt/Modulefile" should match /name *'puppetlabs-apt'/ 94 | And the file "modules/apt/Modulefile" should match /version *'1\.0\.0'/ 95 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 96 | 97 | Scenario: Changing the path 98 | Given a directory named "puppet" 99 | And a file named "Puppetfile" with: 100 | """ 101 | forge "https://forgeapi.puppet.com" 102 | 103 | mod 'puppetlabs/ntp', '3.0.3' 104 | """ 105 | When I run `librarian-puppet install --path puppet/modules` 106 | And I run `librarian-puppet config` 107 | Then the exit status should be 0 108 | And the output from "librarian-puppet config" should contain "path: puppet/modules" 109 | And the file "puppet/modules/ntp/Modulefile" should match /name *'puppetlabs-ntp'/ 110 | And the file "puppet/modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 111 | 112 | Scenario: Handle range version numbers 113 | Given a file named "Puppetfile" with: 114 | """ 115 | forge "https://forgeapi.puppet.com" 116 | 117 | mod 'puppetlabs/postgresql', '7.4.1' 118 | mod 'puppetlabs/apt', '< 8.4.0' 119 | """ 120 | When I successfully run `librarian-puppet install` 121 | And the file "modules/postgresql/metadata.json" should match /"name": "puppetlabs-postgresql"/ 122 | And the file "modules/postgresql/metadata.json" should match /"version": "7\.4\.1"/ 123 | And the file "modules/apt/metadata.json" should match /"name": "puppetlabs-apt"/ 124 | And the file "modules/apt/metadata.json" should match /"version": "8\.3\.0"/ 125 | 126 | Given a file named "Puppetfile" with: 127 | """ 128 | forge "https://forgeapi.puppet.com" 129 | 130 | mod 'puppetlabs/postgresql', :git => 'https://github.com/puppetlabs/puppet-postgresql', :ref => 'v7.5.0' 131 | """ 132 | When I successfully run `librarian-puppet install` 133 | And the file "modules/postgresql/metadata.json" should match /"name": "puppetlabs-postgresql"/ 134 | And the file "modules/postgresql/metadata.json" should match /"version": "7\.5\.0"/ 135 | And the file "modules/apt/metadata.json" should match /"name": "puppetlabs-apt"/ 136 | And the file "modules/apt/metadata.json" should not match /"version": "8\.3\.0"/ 137 | 138 | Scenario: Installing a module that does not exist 139 | Given a file named "Puppetfile" with: 140 | """ 141 | forge "https://forgeapi.puppet.com" 142 | 143 | mod 'puppetlabs/xxxxx' 144 | """ 145 | When I run `librarian-puppet install` 146 | Then the exit status should be 1 147 | And the output should match: 148 | """ 149 | Unable to find module 'puppetlabs-xxxxx' on https://forgeapi.puppet.com 150 | """ 151 | 152 | Scenario: Install a module with conflicts 153 | Given a file named "Puppetfile" with: 154 | """ 155 | forge "https://forgeapi.puppet.com" 156 | 157 | mod 'puppetlabs/apache', '0.6.0' 158 | mod 'puppetlabs/stdlib', '<2.2.1' 159 | """ 160 | When I run `librarian-puppet install` 161 | Then the exit status should be 1 162 | And the output should contain "Could not resolve the dependencies" 163 | 164 | Scenario: Install a module from the Forge with dependencies without version 165 | Given a file named "Puppetfile" with: 166 | """ 167 | forge "https://forgeapi.puppet.com" 168 | 169 | mod 'sbadia/gitlab', '0.1.0' 170 | """ 171 | When I successfully run `librarian-puppet install` 172 | And the file "modules/gitlab/Modulefile" should match /version *'0\.1\.0'/ 173 | 174 | Scenario: Source dependencies from metadata.json 175 | Given a file named "Puppetfile" with: 176 | """ 177 | forge "https://forgeapi.puppet.com" 178 | 179 | metadata 180 | """ 181 | And a file named "metadata.json" with: 182 | """ 183 | { 184 | "name": "random name", 185 | "dependencies": [ 186 | { 187 | "name": "puppetlabs/postgresql", 188 | "version_requirement": "4.0.0" 189 | } 190 | ] 191 | } 192 | """ 193 | When I successfully run `librarian-puppet install` 194 | And the file "modules/postgresql/metadata.json" should match /"name": "puppetlabs-postgresql"/ 195 | 196 | Scenario: Installing a module with duplicated dependencies 197 | Given a file named "Puppetfile" with: 198 | """ 199 | forge "https://forgeapi.puppet.com" 200 | 201 | mod 'pdxcat/collectd', '2.1.0' 202 | """ 203 | When I successfully run `librarian-puppet install` 204 | And the file "modules/collectd/Modulefile" should match /name *'pdxcat-collectd'/ 205 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 206 | 207 | Scenario: Installing two modules with same name, alphabetical order wins 208 | Given a file named "Puppetfile" with: 209 | """ 210 | forge "https://forgeapi.puppet.com" 211 | 212 | mod 'theforeman-dhcp', '4.0.0' 213 | mod 'puppet-dhcp', '2.0.0' 214 | """ 215 | When I successfully run `librarian-puppet install --verbose` 216 | And the file "modules/dhcp/metadata.json" should match /"name": "theforeman-dhcp"/ 217 | And the output should contain "Dependency on module 'dhcp' is fullfilled by multiple modules and only one will be used" 218 | 219 | @other-forge 220 | Scenario: Installing from another forge with local reference should not try to download anything from the official forge 221 | Given a file named "Puppetfile" with: 222 | """ 223 | forge "http://127.0.0.1:8080" 224 | 225 | mod 'tester/tester', :path => './tester-tester' 226 | """ 227 | And a file named "tester-tester/metadata.json" with: 228 | """ 229 | { 230 | "name": "tester-tester", 231 | "version": "0.1.0", 232 | "author": "Basilio Vera", 233 | "summary": "Just our own test", 234 | "license": "MIT", 235 | "dependencies": [ 236 | { "name": "puppetlabs/inifile" } 237 | ] 238 | } 239 | """ 240 | 241 | When I run `librarian-puppet install --verbose` 242 | And the output should not contain "puppetlabs.com" 243 | And the output should not contain "puppet.com" 244 | And the output should contain "Resolving puppetlabs-inifile (>= 0) " 245 | -------------------------------------------------------------------------------- /features/update.feature: -------------------------------------------------------------------------------- 1 | Feature: cli/update 2 | Puppet librarian needs to update modules properly 3 | 4 | Scenario: Updating a module with no Puppetfile and with metadata.json 5 | Given a file named "metadata.json" with: 6 | """ 7 | { 8 | "name": "random name", 9 | "dependencies": [ 10 | { 11 | "name": "puppetlabs/stdlib", 12 | "version_requirement": "3.1.x" 13 | } 14 | ] 15 | } 16 | """ 17 | And a file named "Puppetfile.lock" with: 18 | """ 19 | FORGE 20 | remote: https://forgeapi.puppet.com 21 | specs: 22 | puppetlabs/stdlib (3.1.0) 23 | 24 | DEPENDENCIES 25 | puppetlabs/stdlib (~> 3.0) 26 | """ 27 | When I successfully run `librarian-puppet update puppetlabs/stdlib` 28 | And the file "Puppetfile" should not exist 29 | And the file "Puppetfile.lock" should match /puppetlabs.stdlib \(3\.1\.1\)/ 30 | And the file "modules/stdlib/Modulefile" should match /name *'puppetlabs-stdlib'/ 31 | And the file "modules/stdlib/Modulefile" should match /version *'3\.1\.1'/ 32 | 33 | Scenario: Updating a module 34 | Given a file named "Puppetfile" with: 35 | """ 36 | forge "https://forgeapi.puppet.com" 37 | 38 | mod 'puppetlabs/stdlib', '3.1.x' 39 | """ 40 | And a file named "Puppetfile.lock" with: 41 | """ 42 | FORGE 43 | remote: https://forgeapi.puppet.com 44 | specs: 45 | puppetlabs/stdlib (3.1.0) 46 | 47 | DEPENDENCIES 48 | puppetlabs/stdlib (~> 3.0) 49 | """ 50 | When I successfully run `librarian-puppet update puppetlabs-stdlib` 51 | And the file "Puppetfile.lock" should match /puppetlabs.stdlib \(3\.1\.1\)/ 52 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 53 | And the file "modules/stdlib/Modulefile" should match /version *'3\.1\.1'/ 54 | 55 | Scenario: Updating a module using organization/module 56 | Given a file named "Puppetfile" with: 57 | """ 58 | forge "https://forgeapi.puppet.com" 59 | 60 | mod 'puppetlabs/stdlib', '3.1.x' 61 | """ 62 | And a file named "Puppetfile.lock" with: 63 | """ 64 | FORGE 65 | remote: https://forgeapi.puppet.com 66 | specs: 67 | puppetlabs/stdlib (3.1.0) 68 | 69 | DEPENDENCIES 70 | puppetlabs/stdlib (~> 3.0) 71 | """ 72 | When I successfully run `librarian-puppet update --verbose puppetlabs/stdlib` 73 | And the file "Puppetfile.lock" should match /puppetlabs.stdlib \(3\.1\.1\)/ 74 | And the file "modules/stdlib/metadata.json" should match /"name": "puppetlabs-stdlib"/ 75 | And the file "modules/stdlib/Modulefile" should match /version *'3\.1\.1'/ 76 | 77 | Scenario: Updating a module from git with a branch ref 78 | Given a file named "Puppetfile" with: 79 | """ 80 | forge "https://forgeapi.puppet.com" 81 | 82 | mod "theforeman-dns", 83 | :git => "https://github.com/theforeman/puppet-dns.git", :ref => "4.1-stable" 84 | """ 85 | And a file named "Puppetfile.lock" with: 86 | """ 87 | FORGE 88 | remote: https://forgeapi.puppet.com 89 | specs: 90 | puppetlabs-concat (2.2.1) 91 | puppetlabs-stdlib (>= 4.2.0, < 5.0.0) 92 | puppetlabs-stdlib (4.25.1) 93 | 94 | GIT 95 | remote: https://github.com/theforeman/puppet-dns.git 96 | ref: 4.1-stable 97 | sha: 29150008f81c0be6de4d8913f60b9e014c3f398e 98 | specs: 99 | theforeman-dns (4.1.0) 100 | puppetlabs-concat (>= 1.0.0, < 3.0.0) 101 | puppetlabs-stdlib (>= 4.13.1, < 5.0.0) 102 | 103 | DEPENDENCIES 104 | theforeman-dns (>= 0) 105 | """ 106 | When I successfully run `librarian-puppet install` 107 | And the git revision of module "dns" should be "29150008f81c0be6de4d8913f60b9e014c3f398e" 108 | When I successfully run `librarian-puppet update` 109 | And the git revision of module "dns" should be "e530ae8b1f0d85b37a69e779d1de51d054ecc9f1" 110 | 111 | Scenario: Updating a module with invalid versions in git 112 | Given a file named "Puppetfile" with: 113 | """ 114 | forge "https://forgeapi.puppet.com" 115 | 116 | mod "apache", 117 | :git => "https://github.com/puppetlabs/puppetlabs-apache.git", :ref => "0.5.0-rc1" 118 | """ 119 | And a file named "Puppetfile.lock" with: 120 | """ 121 | FORGE 122 | remote: https://forgeapi.puppet.com 123 | specs: 124 | puppetlabs/firewall (0.0.4) 125 | puppetlabs/stdlib (3.2.0) 126 | 127 | GIT 128 | remote: https://github.com/puppetlabs/puppetlabs-apache.git 129 | ref: 0.5.0-rc1 130 | sha: 94ebca3aaaf2144a7b9ce7ca6a13837ec48a7e2a 131 | specs: 132 | apache () 133 | puppetlabs/firewall (>= 0.0.4) 134 | puppetlabs/stdlib (>= 2.2.1) 135 | 136 | DEPENDENCIES 137 | apache (>= 0) 138 | """ 139 | When I successfully run `librarian-puppet update apache` 140 | And the file "Puppetfile.lock" should match /sha: d81999533af54a6fe510575d3b143308184a5005/ 141 | And the file "modules/apache/Modulefile" should match /name *'puppetlabs-apache'/ 142 | And the file "modules/apache/Modulefile" should match /version *'0\.5\.0-rc1'/ 143 | 144 | Scenario: Updating a module that is not in the Puppetfile 145 | Given a file named "Puppetfile" with: 146 | """ 147 | forge "https://forgeapi.puppet.com" 148 | 149 | mod 'puppetlabs/stdlib', '3.1.x' 150 | """ 151 | And a file named "Puppetfile.lock" with: 152 | """ 153 | FORGE 154 | remote: https://forgeapi.puppet.com 155 | specs: 156 | puppetlabs/stdlib (3.1.0) 157 | 158 | DEPENDENCIES 159 | puppetlabs/stdlib (~> 3.0) 160 | """ 161 | When I run `librarian-puppet update stdlib` 162 | Then the exit status should be 1 163 | And the output should contain "Unable to find module stdlib" 164 | 165 | Scenario: Updating a module to a .10 release to ensure versions are correctly ordered 166 | Given a file named "Puppetfile" with: 167 | """ 168 | forge "https://forgeapi.puppet.com" 169 | 170 | mod 'maestrodev/test' 171 | """ 172 | And a file named "Puppetfile.lock" with: 173 | """ 174 | FORGE 175 | remote: https://forgeapi.puppet.com 176 | specs: 177 | maestrodev/test (1.0.2) 178 | 179 | DEPENDENCIES 180 | maestrodev/test (>= 0) 181 | """ 182 | When I successfully run `librarian-puppet update --verbose` 183 | And the file "Puppetfile.lock" should match /maestrodev.test \(1\.0\.[1-9][0-9]\)/ 184 | And the file "modules/test/Modulefile" should contain "name 'maestrodev-test'" 185 | And the file "modules/test/Modulefile" should match /version '1\.0\.[1-9][0-9]'/ 186 | 187 | Scenario: Updating a forge module with the rsync configuration 188 | Given a file named "Puppetfile" with: 189 | """ 190 | forge "https://forgeapi.puppet.com" 191 | 192 | mod 'maestrodev/test' 193 | """ 194 | And a file named "Puppetfile.lock" with: 195 | """ 196 | FORGE 197 | remote: https://forgeapi.puppet.com 198 | specs: 199 | maestrodev/test (1.0.2) 200 | 201 | DEPENDENCIES 202 | maestrodev/test (>= 0) 203 | """ 204 | And a file named ".librarian/puppet/config" with: 205 | """ 206 | --- 207 | LIBRARIAN_PUPPET_RSYNC: 'true' 208 | """ 209 | When I successfully run `librarian-puppet config` 210 | And the output should contain "rsync: true" 211 | When I successfully run `librarian-puppet update --verbose` 212 | And a directory named "modules/test" should exist 213 | And the file "modules/test" should have an inode and ctime 214 | When I successfully run `librarian-puppet update --verbose` 215 | And a directory named "modules/test" should exist 216 | And the file "modules/test" should have the same inode and ctime as before 217 | 218 | Scenario: Updating a git module with the rsync configuration 219 | Given a file named "Puppetfile" with: 220 | """ 221 | forge "https://forgeapi.puppet.com" 222 | 223 | mod "theforeman-dns", 224 | :git => "https://github.com/theforeman/puppet-dns.git", :ref => "4.1-stable" 225 | """ 226 | And a file named "Puppetfile.lock" with: 227 | """ 228 | FORGE 229 | remote: https://forgeapi.puppet.com 230 | specs: 231 | puppetlabs-concat (2.2.1) 232 | puppetlabs-stdlib (>= 4.2.0, < 5.0.0) 233 | puppetlabs-stdlib (4.25.1) 234 | 235 | GIT 236 | remote: https://github.com/theforeman/puppet-dns.git 237 | ref: 4.1-stable 238 | sha: 29150008f81c0be6de4d8913f60b9e014c3f398e 239 | specs: 240 | theforeman-dns (4.1.0) 241 | puppetlabs-concat (>= 1.0.0, < 3.0.0) 242 | puppetlabs-stdlib (>= 4.13.1, < 5.0.0) 243 | 244 | DEPENDENCIES 245 | theforeman-dns (>= 0) 246 | """ 247 | And a file named ".librarian/puppet/config" with: 248 | """ 249 | --- 250 | LIBRARIAN_PUPPET_RSYNC: 'true' 251 | """ 252 | When I successfully run `librarian-puppet config` 253 | And the output should contain "rsync: true" 254 | When I successfully run `librarian-puppet install` 255 | And the file "Puppetfile.lock" should contain "29150008f81c0be6de4d8913f60b9e014c3f398e" 256 | And the git revision of module "dns" should be "29150008f81c0be6de4d8913f60b9e014c3f398e" 257 | And a directory named "modules/dns" should exist 258 | When I successfully run `librarian-puppet update --verbose` 259 | And a directory named "modules/dns" should exist 260 | And the file "modules/dns" should have an inode and ctime 261 | And the file "Puppetfile.lock" should contain "e530ae8b1f0d85b37a69e779d1de51d054ecc9f1" 262 | And the git revision of module "dns" should be "e530ae8b1f0d85b37a69e779d1de51d054ecc9f1" 263 | When I successfully run `librarian-puppet update --verbose` 264 | And a directory named "modules/dns" should exist 265 | And the file "modules/dns" should have the same inode and ctime as before 266 | And the file "Puppetfile.lock" should contain "e530ae8b1f0d85b37a69e779d1de51d054ecc9f1" 267 | And the git revision of module "dns" should be "e530ae8b1f0d85b37a69e779d1de51d054ecc9f1" 268 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Librarian-puppet 2 | 3 | [![License](https://img.shields.io/github/license/voxpupuli/librarian-puppet.svg)](https://github.com/voxpupuli/librarian-puppet/blob/master/LICENSE) 4 | [![Test](https://github.com/voxpupuli/librarian-puppet/actions/workflows/test.yml/badge.svg)](https://github.com/voxpupuli/librarian-puppet/actions/workflows/test.yml) 5 | [![Release](https://github.com/voxpupuli/librarian-puppet/actions/workflows/release.yml/badge.svg)](https://github.com/voxpupuli/librarian-puppet/actions/workflows/release.yml) 6 | [![RubyGem Version](https://img.shields.io/gem/v/librarian-puppet.svg)](https://rubygems.org/gems/librarian-puppet) 7 | [![RubyGem Downloads](https://img.shields.io/gem/dt/librarian-puppet.svg)](https://rubygems.org/gems/librarian-puppet) 8 | [![Donated by Tim Sharpe](https://img.shields.io/badge/donated%20by-Tim%20Sharpe-fb7047.svg)](#transfer-notice) 9 | 10 | ## Introduction 11 | 12 | Librarian-puppet is a bundler for your puppet infrastructure. You can use 13 | librarian-puppet to manage the puppet modules your infrastructure depends on, 14 | whether the modules come from the [Puppet Forge](https://forge.puppet.com/), 15 | Git repositories or just a path. 16 | 17 | * Librarian-puppet can reuse the dependencies listed in your `Modulefile` or `metadata.json` 18 | * Forge modules can be installed from [Puppetlabs Forge](https://forge.puppet.com/) or an internal Forge such as [Pulp](http://www.pulpproject.org/) 19 | * Git modules can be installed from a branch, tag or specific commit, optionally using a path inside the repository 20 | * Modules can be installed from GitHub using tarballs, without needing Git installed 21 | * Modules can be installed from a filesystem path 22 | * Module dependencies are resolved transitively without needing to list all the modules explicitly 23 | 24 | 25 | Librarian-puppet manages your `modules/` directory for you based on your 26 | `Puppetfile`. Your `Puppetfile` becomes the authoritative source for what 27 | modules you require and at what version, tag or branch. 28 | 29 | Once using Librarian-puppet you should not modify the contents of your `modules` 30 | directory. The individual modules' repos should be updated, tagged with a new 31 | release and the version bumped in your Puppetfile. 32 | 33 | It is based on [Librarian](https://github.com/applicationsonline/librarian), a 34 | framework for writing bundlers, which are tools that resolve, fetch, install, 35 | and isolate a project's dependencies. 36 | 37 | ## Versions 38 | 39 | Librarian-Puppet 7 requires Ruby >= 3.2 and OpenVox 8. 40 | 41 | Librarian-Puppet 6 requires Ruby >= 2.7 and Puppet 7 or 8. 42 | 43 | Librarian-Puppet 5.0.0 and newer requires Ruby >= 2.7 and Puppet >= 6. Use version 4.0.1 if you need support for Puppet 6 or Ruby 2.6 or earlier. 44 | 45 | Librarian-Puppet 4.0.0 and newer requires Ruby >= 2.5 and Puppet >= 5. Use version 3.0.1 is you need support for Puppet 3 or Puppet 4, or Ruby 2.4 or earlier. 46 | 47 | Librarian-Puppet 3.0.0 and newer requires Ruby >= 2.0. Use version 2.2.4 if you need support for Puppet 3.7 or earlier, or Ruby 1.9 or earlier. Note that [Puppet 4.10 and newer require Ruby 2.1](https://puppet.com/docs/puppet/4.10/system_requirements.html#prerequisites) or newer. 48 | 49 | Librarian-Puppet 2.0.0 and newer requires Ruby >= 1.9 and uses Puppet Forge API v3. For Ruby 1.8 use 1.5.0. 50 | 51 | See the [Changelog](CHANGELOG.md) for more details. 52 | 53 | ## The Puppetfile 54 | 55 | Every Puppet repository that uses Librarian-puppet may have a file named 56 | `Puppetfile`, `metadata.json` or `Modulefile` in the root directory of that repository. 57 | The full specification 58 | for which modules your puppet infrastructure repository depends goes in here. 59 | 60 | ### Simple usage 61 | 62 | If no Puppetfile is present, `librarian-puppet` will download all the dependencies 63 | listed in your `metadata.json` or `Modulefile` from the Puppet Forge, 64 | as if the Puppetfile contained 65 | 66 | forge "https://forgeapi.puppet.com" 67 | 68 | metadata 69 | 70 | 71 | ### Example Puppetfile 72 | 73 | forge "https://forgeapi.puppet.com" 74 | 75 | mod 'puppetlabs-razor' 76 | mod 'puppetlabs-ntp', "0.0.3" 77 | 78 | mod 'puppetlabs-apt', 79 | :git => "git://github.com/puppetlabs/puppetlabs-apt.git" 80 | 81 | mod 'puppetlabs-stdlib', 82 | :git => "git://github.com/puppetlabs/puppetlabs-stdlib.git" 83 | 84 | mod 'puppetlabs-apache', '0.6.0', 85 | :github_tarball => 'puppetlabs/puppetlabs-apache' 86 | 87 | mod 'acme-mymodule', :path => './some_folder' 88 | 89 | exclusion 'acme-bad_module' 90 | 91 | 92 | ### Recursive module dependency resolution 93 | 94 | When fetching a module all dependencies specified in its 95 | `Modulefile`, `metadata.json` and `Puppetfile` will be resolved and installed. 96 | 97 | ### Puppetfile Breakdown 98 | 99 | forge "https://forgeapi.puppet.com" 100 | 101 | This declares that we want to use the official Puppet Labs Forge as our default 102 | source when pulling down modules. If you run your own local forge, you may 103 | want to change this. 104 | 105 | metadata 106 | 107 | Download all the dependencies listed in your `metadata.json` or `Modulefile` from the Puppet Forge. 108 | 109 | mod 'puppetlabs-razor' 110 | 111 | Pull in the latest version of the Puppet Labs Razor module from the default 112 | source. 113 | 114 | mod 'puppetlabs-ntp', "0.0.3" 115 | 116 | Pull in version 0.0.3 of the Puppet Labs NTP module from the default source. 117 | 118 | mod 'puppetlabs-apt', 119 | :git => "git://github.com/puppetlabs/puppetlabs-apt.git" 120 | 121 | Our puppet infrastructure repository depends on the `apt` module from the 122 | Puppet Labs GitHub repos and checks out the `master` branch. 123 | 124 | mod 'puppetlabs-apt', 125 | :git => "git://github.com/puppetlabs/puppetlabs-apt.git", 126 | :ref => '0.0.3' 127 | 128 | Our puppet infrastructure repository depends on the `apt` module from the 129 | Puppet Labs GitHub repos and checks out a tag of `0.0.3`. 130 | 131 | mod 'puppetlabs-apt', 132 | :git => "git://github.com/puppetlabs/puppetlabs-apt.git", 133 | :ref => 'feature/master/dans_refactor' 134 | 135 | Our puppet infrastructure repository depends on the `apt` module from the 136 | Puppet Labs GitHub repos and checks out the `dans_refactor` branch. 137 | 138 | When using a Git source, we do not have to use a `:ref =>`. 139 | If we do not, then librarian-puppet will assume we meant the `master` branch. 140 | 141 | If we use a `:ref =>`, we can use anything that Git will recognize as a ref. 142 | This includes any branch name, tag name, SHA, or SHA unique prefix. If we use a 143 | branch, we can later ask Librarian-puppet to update the module by fetching the 144 | most recent version of the module from that same branch. 145 | 146 | Note that Librarian-puppet recognizes the [r10k Puppetfile's](https://github.com/puppetlabs/r10k/blob/master/doc/puppetfile.mkd) additional 147 | options, `:tag`, `:commit`, and `:branch`, but only as aliases for `:ref`. 148 | That is, there is no implementation of r10k's optimizations around fetching 149 | these different types of git objects. 150 | 151 | The Git source also supports a `:path =>` option. If we use the path option, 152 | Librarian-puppet will navigate down into the Git repository and only use the 153 | specified subdirectory. Some people have the habit of having a single repository 154 | with many modules in it. If we need a module from such a repository, we can 155 | use the `:path =>` option here to help Librarian-puppet drill down and find the 156 | module subdirectory. 157 | 158 | mod 'puppetlabs-apt', 159 | :git => "git://github.com/fake/puppet-modules.git", 160 | :path => "modules/apt" 161 | 162 | Our puppet infrastructure repository depends on the `apt` module, which we have 163 | stored as a directory under our `puppet-modules` git repos. 164 | 165 | mod 'puppetlabs-apache', '0.6.0', 166 | :github_tarball => 'puppetlabs/puppetlabs-apache' 167 | 168 | Our puppet infrastructure repository depends on the `puppetlabs-apache` module, 169 | to be downloaded from GitHub tarball. 170 | 171 | mod 'acme-mymodule', :path => './some_folder' 172 | 173 | Our puppet infrastructure repository depends on the `acme-mymodule` module, 174 | which is already in the filesystem. 175 | 176 | exclusion 'acme-bad_module' 177 | 178 | Exclude the module `acme-bad_module` from resolution and installation. 179 | 180 | ## How to Use 181 | 182 | Install librarian-puppet: 183 | 184 | $ gem install librarian-puppet 185 | 186 | Prepare your puppet infrastructure repository: 187 | 188 | $ cd ~/path/to/puppet-inf-repos 189 | $ (git) rm -rf modules 190 | $ librarian-puppet init 191 | 192 | Librarian-puppet takes over your `modules/` directory, and will always 193 | reinstall (if missing) the modules listed the `Puppetfile.lock` into your 194 | `modules/` directory, therefore you do not need your `modules/` directory to be 195 | tracked in Git. 196 | 197 | Librarian-puppet uses a `.tmp/` directory for tempfiles and caches. You should 198 | not track this directory in Git. 199 | 200 | Running `librarian-puppet init` will create a skeleton Puppetfile for you as 201 | well as adding `tmp/` and `modules/` to your `.gitignore`. 202 | 203 | $ librarian-puppet install [--clean] [--verbose] 204 | 205 | This command looks at each `mod` declaration and fetches the module from the 206 | source specified. This command writes the complete resolution into 207 | `Puppetfile.lock` and then copies all of the fetched modules into your 208 | `modules/` directory, overwriting whatever was there before. 209 | 210 | Librarian-puppet support both v1 and v3 of the Puppet Forge API. 211 | Specify a specific API version when installing modules: 212 | 213 | $ librarian-puppet install --use-v1-api # use the v1 API 214 | $ librarian-puppet install --no-use-v1-api # use the v3 API; this is the default 215 | 216 | Get an overview of your `Puppetfile.lock` with: 217 | 218 | $ librarian-puppet show 219 | 220 | Inspect the details of specific resolved dependencies with: 221 | 222 | $ librarian-puppet show NAME1 [NAME2, ...] 223 | 224 | Find out which dependencies are outdated and may be updated: 225 | 226 | $ librarian-puppet outdated [--verbose] 227 | 228 | Update the version of a dependency: 229 | 230 | $ librarian-puppet update apt [--verbose] 231 | $ git diff Puppetfile.lock 232 | $ git add Puppetfile.lock 233 | $ git commit -m "bumped the version of apt up to 0.0.4." 234 | 235 | ## Configuration 236 | 237 | Configuration comes from three sources with the following highest-to-lowest 238 | precedence: 239 | 240 | * The local config (`./.librarian/puppet/config`) 241 | * The environment 242 | * The global config (`~/.librarian/puppet/config`) 243 | 244 | You can inspect the final configuration with: 245 | 246 | $ librarian-puppet config 247 | 248 | You can find out where a particular key is set with: 249 | 250 | $ librarian-puppet config KEY 251 | 252 | You can set a key at the global level with: 253 | 254 | $ librarian-puppet config KEY VALUE --global 255 | 256 | And remove it with: 257 | 258 | $ librarian-puppet config KEY --global --delete 259 | 260 | You can set a key at the local level with: 261 | 262 | $ librarian-puppet config KEY VALUE --local 263 | 264 | And remove it with: 265 | 266 | $ librarian-puppet config KEY --local --delete 267 | 268 | You cannot set or delete environment-level config keys with the CLI. 269 | 270 | Configuration set at either the global or local level will affect subsequent 271 | invocations of `librarian-puppet`. Configurations set at the environment level are 272 | not saved and will not affect subsequent invocations of `librarian-puppet`. 273 | 274 | You can pass a config at the environment level by taking the original config key 275 | and transforming it: replace hyphens (`-`) with underscores (`_`) and periods 276 | (`.`) with doubled underscores (`__`), uppercase, and finally prefix with 277 | `LIBRARIAN_PUPPET_`. For example, to pass a config in the environment for the key 278 | `part-one.part-two`, set the environment variable 279 | `LIBRARIAN_PUPPET_PART_ONE__PART_TWO`. 280 | 281 | Configuration affects how various commands operate. 282 | 283 | * The `path` config sets the directory to install to. If a relative 284 | path, it is relative to the directory containing the `Puppetfile`. The 285 | equivalent environment variable is `LIBRARIAN_PUPPET_PATH`. 286 | 287 | * The `tmp` config sets the cache directory for librarian. If a relative 288 | path, it is relative to the directory containing the `Puppetfile`. The 289 | equivalent environment variable is `LIBRARIAN_PUPPET_TMP`. 290 | 291 | Configuration can be set by passing specific options to other commands. 292 | 293 | * The `path` config can be set at the local level by passing the `--path` option 294 | to the `install` command. It can be unset at the local level by passing the 295 | `--no-path` option to the `install` command. Note that if this is set at the 296 | environment or global level then, even if `--no-path` is given as an option, 297 | the environment or global config will be used. 298 | 299 | 300 | ## Rsync Option 301 | 302 | The default convergence strategy between the cache and the module directory is 303 | to execute an `rm -r` on the module directory and just `cp -r` from the cache. 304 | This causes the module to be removed from the module path every time librarian 305 | puppet updates, regardless of whether the content has changed. This can cause 306 | some problems in environments with lots of change. The problem arises when the 307 | module directory gets removed while Puppet is trying to read files inside it. 308 | The `puppet master` process will lose its CWD and the catalog will fail to 309 | compile. To avoid this, you can use `rsync` to implement a more conservative 310 | convergence strategy. This will use `rsync` with the `-avz` and `--delete` 311 | flags instead of a `rm -r` and `cp -r`. To use this feature, just set the 312 | `rsync` configuration setting to `true`. 313 | 314 | $ librarian-puppet config rsync true --global 315 | 316 | Alternatively, using an environment variable: 317 | 318 | LIBRARIAN_PUPPET_RSYNC='true' 319 | 320 | Note that the directories will still be purged if you run librarian-puppet with 321 | the --clean or --destructive flags. 322 | 323 | ## How to Contribute 324 | 325 | * Pull requests please. 326 | * Bonus points for feature branches. 327 | 328 | ## Reporting Issues 329 | 330 | Bug reports to the github issue tracker please. 331 | Please include: 332 | 333 | * Relevant `Puppetfile` and `Puppetfile.lock` files 334 | * Version of ruby, librarian-puppet, and puppet 335 | * What distro 336 | * Please run the `librarian-puppet` commands in verbose mode by using the 337 | `--verbose` flag, and include the verbose output in the bug report as well. 338 | 339 | 340 | ## Transfer Notice 341 | 342 | This plugin was originally authored by [Tim Sharpe](https://github.com/rodjek). 343 | The maintainer preferred that [Vox Pupuli](https://voxpupuli.org/) take ownership of the module for future improvement and maintenance. 344 | Existing pull requests and issues were transferred, please fork and continue to contribute [here](https://github.com/voxpupuli/librarian-puppet). 345 | 346 | ## License 347 | Please see the [LICENSE](https://github.com/voxpupuli/librarian-puppet/blob/master/LICENSE) 348 | file. 349 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 3.0.1 4 | 5 | * [PR #68](https://github.com/voxpupuli/librarian-puppet/pull/68) Specify dependent module in dependencies 6 | * [PR #80](https://github.com/voxpupuli/librarian-puppet/pull/80) Fix calling issue with open-uri 7 | 8 | ## 3.0.0 9 | 10 | ### Breaking Changes 11 | 12 | Librarian-Puppet 3.0.0 and newer requires Ruby >= 2.0. Use version 2.2.4 if you need support for Puppet 3.7 or earlier, or Ruby 1.9 or earlier. Note that Puppet 4.10 and [newer require Ruby 2.1](https://puppet.com/docs/puppet/4.10/system_requirements.html#prerequisites) or newer. 13 | 14 | * [PR #1](https://github.com/voxpupuli/librarian/pull/1) Add support for r10k Puppetfile's opts 15 | * [PR #5](https://github.com/voxpupuli/librarian-puppet/pull/9) Update README for r10k syntax from PR #1. 16 | * [PR #8](https://github.com/voxpupuli/librarian-puppet/pull/8) Clean up tests 17 | * [PR #19](https://github.com/voxpupuli/librarian-puppet/pull/19) Fix rsync on Windows (path conversion, return code checking) and add trailing / 18 | * [Issue #20](https://github.com/voxpupuli/librarian-puppet/issues/20) Ignore Gem files 19 | * [Issue #25](https://github.com/voxpupuli/librarian-puppet/pull/25) Move to https and new forge url 20 | * Avoid deleted ripienaar-concat 21 | * [PR #59](https://github.com/voxpupuli/librarian-puppet/pull/59) Fix tests 22 | * [PR #61](https://github.com/voxpupuli/librarian-puppet/pull/61) Bring testing matrix up to date 23 | 24 | ## 2.2.3 25 | 26 | * [Issue #1](https://github.com/voxpupuli/librarian-puppet/pull/1) Upgrade to puppet_forge 2 27 | * [Issue #2](https://github.com/voxpupuli/librarian-puppet/pull/2) Change github user from "rodjek" to "voxpupuli" 28 | 29 | ## 2.2.1 30 | 31 | * [Issue #311](https://github.com/rodjek/librarian-puppet/issues/311) Omit versions with a deleted_at date 32 | 33 | ## 2.2.0 34 | 35 | * Add support for Puppet 4 36 | * [Issue #296](https://github.com/rodjek/librarian-puppet/issues/296) Uninitialized constant Puppet::ModuleTool::ModulefileReader using Modulefiles in Puppet 4. Ignore those dependencies 37 | 38 | ## 2.1.1 39 | 40 | * [Issue #302](https://github.com/rodjek/librarian-puppet/issues/302) Ensure path is not lost when default specfile is used 41 | * [Issue #294](https://github.com/rodjek/librarian-puppet/issues/294) Undefined variable calling Puppet version in old Puppet 2.x versions 42 | * [Issue #285](https://github.com/rodjek/librarian-puppet/issues/294) Update librarianp to allow overriding dependencies from multiple sources 43 | 44 | ## 2.1.0 45 | 46 | * Update librarian to use the new `exclusion` syntax 47 | * [Issue #282](https://github.com/rodjek/librarian-puppet/issues/282) Merge duplicated dependencies and warn the user, no more `Cannot bounce Puppetfile.lock!` errors 48 | * [Issue #217](https://github.com/rodjek/librarian-puppet/issues/217), [Issue #244](https://github.com/rodjek/librarian-puppet/issues/244) Use librarianp 0.4.0 that no longer uses recursion to avoid `stack level too deep` errors 49 | * [Issue #277](https://github.com/rodjek/librarian-puppet/issues/277) Warn when there are two dependencies with the same module name 50 | * Use `librarianp` gem instead of `librarian`, a fork with the needed improvements and fixes. 51 | 52 | ## 2.0.1 53 | 54 | * [Issue #272](https://github.com/rodjek/librarian-puppet/issues/272) Defined forge is not used when resolving dependencies 55 | * [Issue #150](https://github.com/rodjek/librarian-puppet/issues/150) Allow dependencies other than Puppet modules 56 | * [Issue #269](https://github.com/rodjek/librarian-puppet/issues/269) Better error message if metadata.json is bad 57 | * [Issue #264](https://github.com/rodjek/librarian-puppet/issues/264) Copying files can cause permission problems on Windows 58 | 59 | ## 2.0.0 60 | 61 | ### Breaking Changes 62 | 63 | Librarian-Puppet 2.0.0 and newer requires Ruby >= 1.9 and uses Puppet Forge API v3. For Ruby 1.8 use 1.5.0. 64 | 65 | * Jump from 1.3.x to 2.x to leave 1.x for Ruby 1.8 compatibility 66 | * [Issue #254](https://github.com/rodjek/librarian-puppet/issues/254) Add a rsync option to prevent deleting directories 67 | * [Issue #261](https://github.com/rodjek/librarian-puppet/issues/261) Incorrect install directory is created if the organization name contains a dash 68 | * [Issue #255](https://github.com/rodjek/librarian-puppet/issues/255) Ignored forge URL when using API v3 69 | 70 | ## 1.5.0 71 | 72 | * Update librarian to use the new `exclusion` syntax 73 | * [Issue #282](https://github.com/rodjek/librarian-puppet/issues/282) Merge duplicated dependencies and warn the user, no more `Cannot bounce Puppetfile.lock!` errors 74 | * [Issue #217](https://github.com/rodjek/librarian-puppet/issues/217), [Issue #244](https://github.com/rodjek/librarian-puppet/issues/244) Use librarianp 0.4.0 that no longer uses recursion to avoid `stack level too deep` errors 75 | * [Issue #277](https://github.com/rodjek/librarian-puppet/issues/277) Warn when there are two dependencies with the same module name 76 | * Use `librarianp` gem instead of `librarian`, a fork with the needed improvements and fixes. 77 | 78 | ## 1.4.1 79 | 80 | * [Issue #272](https://github.com/rodjek/librarian-puppet/issues/272) Defined forge is not used when resolving dependencies 81 | * [Issue #150](https://github.com/rodjek/librarian-puppet/issues/150) Allow dependencies other than Puppet modules 82 | * [Issue #269](https://github.com/rodjek/librarian-puppet/issues/269) Better error message if metadata.json is bad 83 | * [Issue #264](https://github.com/rodjek/librarian-puppet/issues/264) Copying files can cause permission problems on Windows 84 | 85 | ## 1.4.0 86 | 87 | * Jump from 1.0.x to 1.4.x to keep Ruby 1.8 compatibility in the 1.x series 88 | * [Issue #254](https://github.com/rodjek/librarian-puppet/issues/254) Add a rsync option to prevent deleting directories 89 | * [Issue #261](https://github.com/rodjek/librarian-puppet/issues/261) Incorrect install directory is created if the organization name contains a dash 90 | 91 | ## 1.3.3 92 | 93 | * [Issue #250](https://github.com/rodjek/librarian-puppet/issues/250) Fix error when module has no dependencies in `metadata.json` 94 | 95 | ## 1.3.2 96 | 97 | * [Issue #246](https://github.com/rodjek/librarian-puppet/issues/246) Do not fail if modules have no `Modulefile` nor `metadata.json` 98 | 99 | ## 1.3.1 100 | 101 | * Version in dependencies with `metadata.json` is ignored 102 | 103 | ## 1.3.0 104 | 105 | * If no Puppetfile is present default to use the `metadata.json` or `Modulefile` 106 | * [Issue #235](https://github.com/rodjek/librarian-puppet/issues/235) Error when forge is not defined in `Puppetfile` 107 | * [Issue #243](https://github.com/rodjek/librarian-puppet/issues/243) Warn if `Modulefile` doesn't contain a version 108 | 109 | ## 1.2.0 110 | 111 | * Implement `metadata` syntax for `Puppetfile` 112 | * [Issue #220](https://github.com/rodjek/librarian-puppet/issues/220) Add support for metadata.json 113 | * [Issue #242](https://github.com/rodjek/librarian-puppet/issues/242) Get organization from name correctly if name has multiple dashes 114 | 115 | ## 1.1.3 116 | 117 | * [Issue #237](https://github.com/rodjek/librarian-puppet/issues/237) [Issue #238](https://github.com/rodjek/librarian-puppet/issues/238) Unable to use a custom v3 forge: add flags `--use-v1-api` and `--no-use-v1-api` 118 | * [Issue #239](https://github.com/rodjek/librarian-puppet/issues/239) GitHub tarball: add access_token correctly to url's which are already having query parameters 119 | * [Issue #234](https://github.com/rodjek/librarian-puppet/issues/234) Use organization-module instead of organization/module by default 120 | 121 | ## 1.1.2 122 | 123 | * [Issue #231](https://github.com/rodjek/librarian-puppet/issues/231) Only use the `GITHUB_API_TOKEN` if it's not empty 124 | * [Issue #233](https://github.com/rodjek/librarian-puppet/issues/233) Fix version regex to match e.g. 1.99.15 125 | * Can't pass the Puppet Forge v1 api url to clients using v3 (3.6.0+ and PE 3.2.0+) 126 | 127 | ## 1.1.1 128 | 129 | * [Issue #227](https://github.com/rodjek/librarian-puppet/issues/227) Fix Librarian::Puppet::VERSION undefined 130 | 131 | ## 1.1.0 132 | 133 | * [Issue #210](https://github.com/rodjek/librarian-puppet/issues/210) Use forgeapi.puppetlabs.com and API v3 134 | * Accesing the v3 API requires Ruby 1.9 due to the puppet_forge library used 135 | 136 | 137 | ## 1.0.10 138 | 139 | * [Issue #250](https://github.com/rodjek/librarian-puppet/issues/250) Fix error when module has no dependencies in `metadata.json` 140 | 141 | ## 1.0.9 142 | 143 | * [Issue #246](https://github.com/rodjek/librarian-puppet/issues/246) Do not fail if modules have no `Modulefile` nor `metadata.json` 144 | 145 | ## 1.0.8 146 | 147 | * Version in dependencies with `metadata.json` is ignored 148 | 149 | ## 1.0.7 150 | 151 | * If no Puppetfile is present default to use the `metadata.json` or `Modulefile` 152 | * [Issue #235](https://github.com/rodjek/librarian-puppet/issues/235) Error when forge is not defined in `Puppetfile` 153 | * [Issue #243](https://github.com/rodjek/librarian-puppet/issues/243) Warn if `Modulefile` doesn't contain a version 154 | 155 | 156 | ## 1.0.6 157 | 158 | * Implement `metadata` syntax for `Puppetfile` 159 | * [Issue #220](https://github.com/rodjek/librarian-puppet/issues/220) Add support for metadata.json 160 | * [Issue #242](https://github.com/rodjek/librarian-puppet/issues/242) Get organization from name correctly if name has multiple dashes 161 | 162 | ## 1.0.5 163 | 164 | * [Issue #237](https://github.com/rodjek/librarian-puppet/issues/237)[Issue #238](https://github.com/rodjek/librarian-puppet/issues/238) Unable to use a custom v3 forge: add flags `--use-v1-api` and `--no-use-v1-api` 165 | * [Issue #239](https://github.com/rodjek/librarian-puppet/issues/239) GitHub tarball: add access_token correctly to url's which are already having query parameters 166 | * [Issue #234](https://github.com/rodjek/librarian-puppet/issues/234) Use organization-module instead of organization/module by default 167 | 168 | ## 1.0.4 169 | 170 | * [Issue #231](https://github.com/rodjek/librarian-puppet/issues/231) Only use the `GITHUB_API_TOKEN` if it's not empty 171 | * [Issue #233](https://github.com/rodjek/librarian-puppet/issues/233) Fix version regex to match e.g. 1.99.15 172 | * Can't pass the Puppet Forge v1 api url to clients using v3 (3.6.0+ and PE 3.2.0+) 173 | 174 | ## 1.0.3 175 | 176 | * [Issue #223](https://github.com/rodjek/librarian-puppet/issues/223) `Cannot bounce Puppetfile.lock!` error when Forge modules contain duplicated dependencies 177 | 178 | ## 1.0.2 179 | 180 | * [Issue #211](https://github.com/rodjek/librarian-puppet/issues/211) Pass the PuppetLabs Forge API v3 endpoint to `puppet module` when running on Puppet >= 3.6.0 181 | * [Issue #198](https://github.com/rodjek/librarian-puppet/issues/198) Reduce the length of tmp dirs to avoid issues in windows 182 | * [Issue #206](https://github.com/rodjek/librarian-puppet/issues/206) githubtarball call for released versions does not consider pagination 183 | * [Issue #204](https://github.com/rodjek/librarian-puppet/issues/204) Fix regex to detect Forge API v3 url 184 | * [Issue #199](https://github.com/rodjek/librarian-puppet/issues/199) undefined method run! packaging a git source 185 | * Verify SSL certificates in github calls 186 | 187 | ## 1.0.1 188 | 189 | * [Issue #190](https://github.com/rodjek/librarian-puppet/issues/190) Pass the PuppetLabs Forge API v3 endpoint to `puppet module` when running on Puppet Enterprise >= 3.2 190 | * [Issue #196](https://github.com/rodjek/librarian-puppet/issues/196) Fix error in error handling when puppet is not installed 191 | 192 | ## 1.0.0 193 | 194 | * Remove deprecation warning for github_tarball sources, some people are actually using it 195 | 196 | ## 0.9.17 197 | 198 | * [Issue #193](https://github.com/rodjek/librarian-puppet/issues/193) Support Puppet 3.5.0 199 | 200 | ## 0.9.16 201 | 202 | * [Issue #181](https://github.com/rodjek/librarian-puppet/issues/181) Should use qualified module names for resolution to work correctly 203 | * Deprecate github_tarball sources 204 | * Reduce number of API calls for github_tarball sources 205 | 206 | ## 0.9.15 207 | 208 | * [Issue #187](https://github.com/rodjek/librarian-puppet/issues/187) Fixed parallel installation issues 209 | * [Issue #185](https://github.com/rodjek/librarian-puppet/issues/185) Sanitize the gem/bundler environment before spawning (ruby 1.9+) 210 | 211 | ## 0.9.14 212 | 213 | * [Issue #182](https://github.com/rodjek/librarian-puppet/issues/182) Sanitize the environment before spawning (ruby 1.9+) 214 | * [Issue #184](https://github.com/rodjek/librarian-puppet/issues/184) Support transitive dependencies in modules using :path 215 | * Git dependencies using modulefile syntax make librarian-puppet fail 216 | * [Issue #108](https://github.com/rodjek/librarian-puppet/issues/108) Don't fail on malformed Modulefile from a git dependency 217 | 218 | ## 0.9.13 219 | 220 | * [Issue #176](https://github.com/rodjek/librarian-puppet/issues/176) Upgrade to librarian 0.1.2 221 | * [Issue #179](https://github.com/rodjek/librarian-puppet/issues/179) Need to install extra gems just in case we are in ruby 1.8 222 | * [Issue #178](https://github.com/rodjek/librarian-puppet/issues/178) Print a meaningful message if puppet gem can't be loaded for :git sources 223 | 224 | ## 0.9.12 225 | 226 | * Remove extra dependencies from gem added when 0.9.11 was released under ruby 1.8 227 | 228 | ## 0.9.11 229 | 230 | * Add modulefile dsl to reuse Modulefile dependencies 231 | * Consider Puppetfile-dependencies recursively in git-source 232 | * Support changing tmp, cache and scratch paths 233 | * librarian-puppet package causes an infinite loop 234 | * Show a message if no versions are found for a module 235 | * Make download of tarballs more robust 236 | * Require open3_backport in ruby 1.8 and install if not present 237 | * Git dependencies in both Puppetfile and Modulefile cause a Cannot bounce Puppetfile.lock! error 238 | * Better sort of github tarball versions when there are mixed tags starting with and without 'v' 239 | * Fix error if a git module has a dependency without version 240 | * Fix git dependency with :path attribute 241 | * Cleaner output when no Puppetfile found 242 | * Reduce the number of API calls to the Forge 243 | * Don't sort versions as strings. Rely on the forge returning them ordered 244 | * Pass --module_repository to `puppet module install` to install from other forges 245 | * Cache forge responses and print an error if returns an invalid response 246 | * Add a User-Agent header to all requests to the GitHub API 247 | * Convert puppet version requirements to rubygems, pessimistic and ranges 248 | * Use librarian gem 249 | 250 | ## 0.9.10 251 | 252 | * Catch GitHub API rate limit exceeded 253 | * Make Librarian::Manifest Semver 2.0.0 compatible 254 | 255 | ## 0.9.1 256 | * Proper error message when a module that is sourced from the forge does not 257 | exist. 258 | * Added support for annotated tags as git references. 259 | * `librarian-puppet init` adds `.tmp/` to gitignore instead of `tmp/`. 260 | * Fixed syntax error in the template Puppetfile created by `librarian-puppet 261 | init`. 262 | * Checks for `lib/puppet` as well as `manifests/` when checking if the git 263 | repository is a valid module. 264 | * When a user specifies `/` as the name of a module sources from a 265 | git repository, assume the module name is actually ``. 266 | * Fixed gem description and summary in gemspec. 267 | 268 | ## 0.9.0 269 | * Initial release 270 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config --no-auto-gen-timestamp` 3 | # using RuboCop version 1.79.2. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 1 10 | # This cop supports unsafe autocorrection (--autocorrect-all). 11 | # Configuration parameters: RequireParenthesesForMethodChains. 12 | Lint/AmbiguousRange: 13 | Exclude: 14 | - 'modules/postgresql/lib/puppet/util/postgresql_validator.rb' 15 | 16 | # Offense count: 1 17 | # This cop supports unsafe autocorrection (--autocorrect-all). 18 | # Configuration parameters: AllowSafeAssignment. 19 | Lint/AssignmentInCondition: 20 | Exclude: 21 | - 'lib/librarian/puppet/source/githubtarball/repo.rb' 22 | 23 | # Offense count: 8 24 | # This cop supports unsafe autocorrection (--autocorrect-all). 25 | Lint/BooleanSymbol: 26 | Exclude: 27 | - 'modules/postgresql/lib/puppet/provider/postgresql_psql/ruby.rb' 28 | - 'modules/postgresql/lib/puppet/type/postgresql_psql.rb' 29 | 30 | # Offense count: 1 31 | # Configuration parameters: AllowedMethods. 32 | # AllowedMethods: enums 33 | Lint/ConstantDefinitionInBlock: 34 | Exclude: 35 | - 'test/librarian/puppet/source/githubtarball_test.rb' 36 | 37 | # Offense count: 12 38 | Lint/CopDirectiveSyntax: 39 | Exclude: 40 | - 'modules/stdlib/lib/puppet/functions/fact.rb' 41 | - 'modules/stdlib/lib/puppet/parser/functions/any2bool.rb' 42 | - 'modules/stdlib/lib/puppet/parser/functions/fqdn_rand_string.rb' 43 | - 'modules/stdlib/lib/puppet/parser/functions/is_absolute_path.rb' 44 | - 'modules/stdlib/lib/puppet/parser/functions/load_module_metadata.rb' 45 | - 'modules/stdlib/lib/puppet/parser/functions/loadjson.rb' 46 | - 'modules/stdlib/lib/puppet/parser/functions/loadyaml.rb' 47 | - 'modules/stdlib/lib/puppet/parser/functions/parseyaml.rb' 48 | - 'modules/stdlib/lib/puppet/parser/functions/private.rb' 49 | - 'modules/stdlib/lib/puppet/parser/functions/str2bool.rb' 50 | - 'modules/stdlib/lib/puppet/parser/functions/type.rb' 51 | 52 | # Offense count: 4 53 | # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch. 54 | Lint/DuplicateBranch: 55 | Exclude: 56 | - 'modules/concat/lib/puppet/type/concat_file.rb' 57 | - 'modules/stdlib/lib/puppet/parser/functions/str2bool.rb' 58 | - 'modules/stdlib/lib/puppet/provider/file_line/ruby.rb' 59 | 60 | # Offense count: 3 61 | # Configuration parameters: AllowComments, AllowEmptyLambdas. 62 | Lint/EmptyBlock: 63 | Exclude: 64 | - 'spec/receiver_spec.rb' 65 | 66 | # Offense count: 3 67 | # This cop supports safe autocorrection (--autocorrect). 68 | Lint/RedundantCopDisableDirective: 69 | Exclude: 70 | - 'modules/apt/lib/puppet/provider/apt_key/apt_key.rb' 71 | - 'modules/stdlib/lib/puppet/parser/functions/private.rb' 72 | - 'modules/stdlib/lib/puppet/parser/functions/type.rb' 73 | 74 | # Offense count: 2 75 | # This cop supports unsafe autocorrection (--autocorrect-all). 76 | # Configuration parameters: AllowedMethods, InferNonNilReceiver, AdditionalNilMethods. 77 | # AllowedMethods: instance_of?, kind_of?, is_a?, eql?, respond_to?, equal? 78 | # AdditionalNilMethods: present?, blank?, try, try! 79 | Lint/RedundantSafeNavigation: 80 | Exclude: 81 | - 'modules/stdlib/lib/puppet/parser/functions/assert_private.rb' 82 | - 'modules/stdlib/lib/puppet/parser/functions/getparam.rb' 83 | 84 | # Offense count: 1 85 | Lint/RescueException: 86 | Exclude: 87 | - 'test/librarian/puppet/source/githubtarball_test.rb' 88 | 89 | # Offense count: 4 90 | # Configuration parameters: AllowComments, AllowNil. 91 | Lint/SuppressedException: 92 | Exclude: 93 | - 'modules/stdlib/lib/puppet/functions/merge.rb' 94 | - 'modules/stdlib/lib/puppet/parser/functions/getvar.rb' 95 | - 'modules/stdlib/lib/puppet/parser/functions/has_interface_with.rb' 96 | 97 | # Offense count: 2 98 | Lint/UriEscapeUnescape: 99 | Exclude: 100 | - 'modules/stdlib/lib/puppet/parser/functions/uriescape.rb' 101 | 102 | # Offense count: 6 103 | Naming/AccessorMethodName: 104 | Exclude: 105 | - 'lib/librarian/puppet/source/forge/repo.rb' 106 | - 'lib/librarian/puppet/source/forge/repo_v1.rb' 107 | - 'lib/librarian/puppet/source/forge/repo_v3.rb' 108 | - 'modules/concat/lib/puppet/type/concat_fragment.rb' 109 | - 'modules/postgresql/lib/puppet/type/postgresql_psql.rb' 110 | 111 | # Offense count: 3 112 | # Configuration parameters: ForbiddenDelimiters. 113 | # ForbiddenDelimiters: (?i-mx:(^|\s)(EO[A-Z]{1}|END)(\s|$)) 114 | Naming/HeredocDelimiterNaming: 115 | Exclude: 116 | - 'modules/postgresql/lib/puppet/type/postgresql_conn_validator.rb' 117 | - 'modules/postgresql/lib/puppet/type/postgresql_replication_slot.rb' 118 | - 'modules/stdlib/lib/puppet/parser/functions/pick.rb' 119 | 120 | # Offense count: 2 121 | # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. 122 | # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to 123 | Naming/MethodParameterName: 124 | Exclude: 125 | - 'modules/concat/lib/puppet/type/concat_file.rb' 126 | 127 | # Offense count: 9 128 | # Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates. 129 | # AllowedMethods: call 130 | # WaywardPredicates: nonzero? 131 | Naming/PredicateMethod: 132 | Exclude: 133 | - 'modules/postgresql/lib/puppet/type/postgresql_psql.rb' 134 | - 'modules/postgresql/lib/puppet/util/postgresql_validator.rb' 135 | - 'modules/stdlib/lib/puppet/functions/is_a.rb' 136 | - 'modules/stdlib/lib/puppet/functions/os_version_gte.rb' 137 | - 'modules/stdlib/lib/puppet/functions/stdlib/end_with.rb' 138 | - 'modules/stdlib/lib/puppet/functions/stdlib/start_with.rb' 139 | - 'modules/stdlib/lib/puppet/functions/validate_legacy.rb' 140 | 141 | # Offense count: 1 142 | # Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros, UseSorbetSigs. 143 | # NamePrefix: is_, has_, have_, does_ 144 | # ForbiddenPrefixes: is_, has_, have_, does_ 145 | # AllowedMethods: is_a? 146 | # MethodDefinitionMacros: define_method, define_singleton_method 147 | Naming/PredicatePrefix: 148 | Exclude: 149 | - 'spec/**/*' 150 | - 'modules/stdlib/lib/puppet/functions/is_a.rb' 151 | 152 | # Offense count: 2 153 | # Configuration parameters: EnforcedStyle, AllowedIdentifiers, AllowedPatterns, ForbiddenIdentifiers, ForbiddenPatterns. 154 | # SupportedStyles: snake_case, camelCase 155 | Naming/VariableName: 156 | Exclude: 157 | - 'lib/librarian/puppet/dsl.rb' 158 | 159 | # Offense count: 1 160 | # This cop supports unsafe autocorrection (--autocorrect-all). 161 | Performance/Detect: 162 | Exclude: 163 | - 'lib/librarian/puppet/dsl.rb' 164 | 165 | # Offense count: 4 166 | # This cop supports unsafe autocorrection (--autocorrect-all). 167 | Performance/MapCompact: 168 | Exclude: 169 | - 'modules/apache/lib/puppet/provider/a2mod/a2mod.rb' 170 | - 'modules/apache/lib/puppet/provider/a2mod/redhat.rb' 171 | - 'modules/concat/lib/puppet/type/concat_file.rb' 172 | - 'modules/stdlib/lib/puppet/parser/functions/values_at.rb' 173 | 174 | # Offense count: 1 175 | # This cop supports unsafe autocorrection (--autocorrect-all). 176 | # Configuration parameters: AllowRegexpMatch. 177 | Performance/RedundantEqualityComparisonBlock: 178 | Exclude: 179 | - 'modules/stdlib/lib/puppet/parser/functions/bool2str.rb' 180 | 181 | # Offense count: 1 182 | # This cop supports unsafe autocorrection (--autocorrect-all). 183 | Performance/StringInclude: 184 | Exclude: 185 | - 'lib/librarian/puppet/source/forge/repo.rb' 186 | 187 | # Offense count: 1 188 | # This cop supports unsafe autocorrection (--autocorrect-all). 189 | Performance/UnfreezeString: 190 | Exclude: 191 | - 'modules/stdlib/lib/puppet/parser/functions/pw_hash.rb' 192 | 193 | # Offense count: 1 194 | RSpec/AnyInstance: 195 | Exclude: 196 | - 'spec/source/forge_spec.rb' 197 | 198 | # Offense count: 1 199 | # This cop supports unsafe autocorrection (--autocorrect-all). 200 | # Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants. 201 | # SupportedStyles: described_class, explicit 202 | RSpec/DescribedClass: 203 | Exclude: 204 | - 'spec/source/forge_spec.rb' 205 | 206 | # Offense count: 1 207 | # Configuration parameters: CountAsOne. 208 | RSpec/ExampleLength: 209 | Max: 8 210 | 211 | # Offense count: 1 212 | RSpec/ExpectInHook: 213 | Exclude: 214 | - 'spec/source/forge_spec.rb' 215 | 216 | # Offense count: 3 217 | # Configuration parameters: EnforcedStyle, IgnoreSharedExamples. 218 | # SupportedStyles: always, named_only 219 | RSpec/NamedSubject: 220 | Exclude: 221 | - 'spec/source/forge_spec.rb' 222 | - 'spec/util_spec.rb' 223 | 224 | # Offense count: 1 225 | # Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata. 226 | # Include: **/*_spec.rb 227 | RSpec/SpecFilePathFormat: 228 | Exclude: 229 | - '**/spec/routing/**/*' 230 | - 'spec/util_spec.rb' 231 | 232 | # Offense count: 5 233 | Security/Open: 234 | Exclude: 235 | - 'lib/librarian/puppet/source/forge/repo.rb' 236 | - 'lib/librarian/puppet/source/forge/repo_v1.rb' 237 | - 'lib/librarian/puppet/source/githubtarball/repo.rb' 238 | - 'modules/apt/lib/puppet/provider/apt_key/apt_key.rb' 239 | 240 | # Offense count: 5 241 | # This cop supports unsafe autocorrection (--autocorrect-all). 242 | # Configuration parameters: EnforcedStyle. 243 | # SupportedStyles: always, conditionals 244 | Style/AndOr: 245 | Exclude: 246 | - 'lib/librarian/puppet/dsl.rb' 247 | - 'lib/librarian/puppet/source/forge/repo.rb' 248 | - 'lib/librarian/puppet/source/forge/repo_v1.rb' 249 | - 'lib/librarian/puppet/source/git.rb' 250 | - 'lib/librarian/puppet/source/local.rb' 251 | 252 | # Offense count: 1 253 | # This cop supports unsafe autocorrection (--autocorrect-all). 254 | # Configuration parameters: MinBranchesCount. 255 | Style/CaseLikeIf: 256 | Exclude: 257 | - 'modules/apt/lib/puppet/provider/apt_key/apt_key.rb' 258 | 259 | # Offense count: 133 260 | # This cop supports unsafe autocorrection (--autocorrect-all). 261 | # Configuration parameters: EnforcedStyle, EnforcedStyleForClasses, EnforcedStyleForModules. 262 | # SupportedStyles: nested, compact 263 | # SupportedStylesForClasses: ~, nested, compact 264 | # SupportedStylesForModules: ~, nested, compact 265 | Style/ClassAndModuleChildren: 266 | Enabled: false 267 | 268 | # Offense count: 1 269 | # This cop supports unsafe autocorrection (--autocorrect-all). 270 | # Configuration parameters: AllowedMethods, AllowedPatterns. 271 | # AllowedMethods: ==, equal?, eql? 272 | Style/ClassEqualityComparison: 273 | Exclude: 274 | - 'modules/stdlib/lib/puppet/parser/functions/fqdn_rotate.rb' 275 | 276 | # Offense count: 1 277 | Style/ClassVars: 278 | Exclude: 279 | - 'lib/librarian/puppet/source/forge.rb' 280 | 281 | # Offense count: 2 282 | # This cop supports unsafe autocorrection (--autocorrect-all). 283 | # Configuration parameters: AllowedReceivers. 284 | Style/CollectionCompact: 285 | Exclude: 286 | - 'modules/stdlib/lib/puppet/functions/to_json_pretty.rb' 287 | 288 | # Offense count: 2 289 | # This cop supports unsafe autocorrection (--autocorrect-all). 290 | Style/CommentedKeyword: 291 | Exclude: 292 | - 'modules/stdlib/lib/puppet/functions/is_a.rb' 293 | - 'modules/stdlib/lib/puppet/parser/functions/values_at.rb' 294 | 295 | # Offense count: 2 296 | # This cop supports unsafe autocorrection (--autocorrect-all). 297 | Style/ComparableBetween: 298 | Exclude: 299 | - 'modules/stdlib/lib/puppet/parser/functions/convert_base.rb' 300 | - 'modules/stdlib/lib/puppet/parser/functions/validate_slength.rb' 301 | 302 | # Offense count: 26 303 | # Configuration parameters: AllowedConstants. 304 | Style/Documentation: 305 | Enabled: false 306 | 307 | # Offense count: 1 308 | # This cop supports unsafe autocorrection (--autocorrect-all). 309 | # Configuration parameters: EnforcedStyle. 310 | # SupportedStyles: always, always_true, never 311 | Style/FrozenStringLiteralComment: 312 | Exclude: 313 | - '**/*.arb' 314 | - 'lib/librarian/puppet/source/githubtarball/repo.rb' 315 | 316 | # Offense count: 3 317 | # This cop supports unsafe autocorrection (--autocorrect-all). 318 | Style/GlobalStdStream: 319 | Exclude: 320 | - 'modules/apache/tasks/init.rb' 321 | - 'modules/apt/tasks/init.rb' 322 | - 'modules/postgresql/tasks/sql.rb' 323 | 324 | # Offense count: 5 325 | # This cop supports unsafe autocorrection (--autocorrect-all). 326 | # Configuration parameters: AllowSplatArgument. 327 | Style/HashConversion: 328 | Exclude: 329 | - 'lib/librarian/puppet/lockfile.rb' 330 | - 'modules/stdlib/lib/puppet/functions/sprintf_hash.rb' 331 | - 'modules/stdlib/lib/puppet/functions/to_json_pretty.rb' 332 | - 'modules/stdlib/lib/puppet/parser/functions/prefix.rb' 333 | - 'modules/stdlib/lib/puppet/parser/functions/suffix.rb' 334 | 335 | # Offense count: 1 336 | # This cop supports unsafe autocorrection (--autocorrect-all). 337 | # Configuration parameters: AllowedReceivers. 338 | # AllowedReceivers: Thread.current 339 | Style/HashEachMethods: 340 | Exclude: 341 | - 'modules/apache/lib/puppet/provider/a2mod/gentoo.rb' 342 | 343 | # Offense count: 2 344 | # This cop supports unsafe autocorrection (--autocorrect-all). 345 | Style/HashExcept: 346 | Exclude: 347 | - 'lib/librarian/puppet/source/forge.rb' 348 | - 'lib/librarian/puppet/source/githubtarball.rb' 349 | 350 | # Offense count: 2 351 | # This cop supports unsafe autocorrection (--autocorrect-all). 352 | Style/HashTransformKeys: 353 | Exclude: 354 | - 'lib/librarian/puppet/lockfile.rb' 355 | - 'modules/stdlib/lib/puppet/functions/to_json_pretty.rb' 356 | 357 | # Offense count: 84 358 | # This cop supports safe autocorrection (--autocorrect). 359 | Style/IfUnlessModifier: 360 | Enabled: false 361 | 362 | # Offense count: 2 363 | # This cop supports unsafe autocorrection (--autocorrect-all). 364 | # Configuration parameters: AllowedMethods. 365 | # AllowedMethods: nonzero? 366 | Style/IfWithBooleanLiteralBranches: 367 | Exclude: 368 | - 'modules/stdlib/lib/facter/pe_version.rb' 369 | - 'modules/stdlib/lib/puppet/provider/file_line/ruby.rb' 370 | 371 | # Offense count: 1 372 | # This cop supports unsafe autocorrection (--autocorrect-all). 373 | Style/InfiniteLoop: 374 | Exclude: 375 | - 'lib/librarian/puppet/source/githubtarball/repo.rb' 376 | 377 | # Offense count: 1 378 | # This cop supports unsafe autocorrection (--autocorrect-all). 379 | Style/MapIntoArray: 380 | Exclude: 381 | - 'modules/concat/lib/puppet/type/concat_file.rb' 382 | 383 | # Offense count: 1 384 | Style/MixinUsage: 385 | Exclude: 386 | - 'spec/source/forge_spec.rb' 387 | 388 | # Offense count: 1 389 | # This cop supports unsafe autocorrection (--autocorrect-all). 390 | # Configuration parameters: EnforcedStyle, Autocorrect. 391 | # SupportedStyles: module_function, extend_self, forbidden 392 | Style/ModuleFunction: 393 | Exclude: 394 | - 'lib/librarian/puppet/extension.rb' 395 | 396 | # Offense count: 1 397 | # This cop supports unsafe autocorrection (--autocorrect-all). 398 | # Configuration parameters: EnforcedStyle. 399 | # SupportedStyles: literals, strict 400 | Style/MutableConstant: 401 | Exclude: 402 | - 'lib/librarian/puppet/source/githubtarball/repo.rb' 403 | 404 | # Offense count: 12 405 | # This cop supports unsafe autocorrection (--autocorrect-all). 406 | # Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. 407 | # SupportedStyles: predicate, comparison 408 | Style/NumericPredicate: 409 | Exclude: 410 | - 'spec/**/*' 411 | - 'modules/postgresql/lib/puppet/type/postgresql_psql.rb' 412 | - 'modules/stdlib/lib/puppet/parser/functions/fqdn_rand_string.rb' 413 | - 'modules/stdlib/lib/puppet/parser/functions/is_email_address.rb' 414 | - 'modules/stdlib/lib/puppet/parser/functions/num2bool.rb' 415 | - 'modules/stdlib/lib/puppet/parser/functions/seeded_rand.rb' 416 | - 'modules/stdlib/lib/puppet/parser/functions/validate_slength.rb' 417 | - 'modules/stdlib/lib/puppet/provider/file_line/ruby.rb' 418 | 419 | # Offense count: 2 420 | # Configuration parameters: AllowedMethods. 421 | # AllowedMethods: respond_to_missing? 422 | Style/OptionalBooleanParameter: 423 | Exclude: 424 | - 'modules/postgresql/lib/puppet/type/postgresql_psql.rb' 425 | - 'modules/stdlib/lib/puppet/functions/to_json_pretty.rb' 426 | 427 | # Offense count: 4 428 | # This cop supports unsafe autocorrection (--autocorrect-all). 429 | # Configuration parameters: Methods. 430 | Style/RedundantArgument: 431 | Exclude: 432 | - 'modules/stdlib/lib/facter/package_provider.rb' 433 | - 'modules/stdlib/lib/puppet/parser/functions/sort.rb' 434 | - 'modules/stdlib/lib/puppet/provider/file_line/ruby.rb' 435 | 436 | # Offense count: 2 437 | # This cop supports unsafe autocorrection (--autocorrect-all). 438 | Style/RedundantInterpolation: 439 | Exclude: 440 | - 'lib/librarian/puppet/util.rb' 441 | 442 | # Offense count: 2 443 | # This cop supports unsafe autocorrection (--autocorrect-all). 444 | # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. 445 | # AllowedMethods: present?, blank?, presence, try, try! 446 | Style/SafeNavigation: 447 | Exclude: 448 | - 'lib/librarian/puppet/source/githubtarball/repo.rb' 449 | - 'modules/apt/lib/puppet/provider/apt_key/apt_key.rb' 450 | 451 | # Offense count: 1 452 | # This cop supports unsafe autocorrection (--autocorrect-all). 453 | Style/SelectByRegexp: 454 | Exclude: 455 | - 'modules/stdlib/lib/puppet/parser/functions/reject.rb' 456 | 457 | # Offense count: 3 458 | # This cop supports unsafe autocorrection (--autocorrect-all). 459 | Style/SlicingWithRange: 460 | Exclude: 461 | - 'modules/apt/lib/puppet/provider/apt_key/apt_key.rb' 462 | - 'modules/stdlib/lib/puppet/parser/functions/is_domain_name.rb' 463 | 464 | # Offense count: 3 465 | # This cop supports unsafe autocorrection (--autocorrect-all). 466 | # Configuration parameters: RequireEnglish, EnforcedStyle. 467 | # SupportedStyles: use_perl_names, use_english_names, use_builtin_english_names 468 | Style/SpecialGlobalVars: 469 | Exclude: 470 | - 'bin/librarian-puppet' 471 | - 'librarian-puppet.gemspec' 472 | 473 | # Offense count: 4 474 | # This cop supports unsafe autocorrection (--autocorrect-all). 475 | Style/StringChars: 476 | Exclude: 477 | - 'modules/stdlib/lib/puppet/parser/functions/fqdn_rotate.rb' 478 | - 'modules/stdlib/lib/puppet/parser/functions/shuffle.rb' 479 | - 'modules/stdlib/lib/puppet/parser/functions/sort.rb' 480 | - 'modules/stdlib/lib/puppet/parser/functions/unique.rb' 481 | 482 | # Offense count: 6 483 | # This cop supports unsafe autocorrection (--autocorrect-all). 484 | # Configuration parameters: Mode. 485 | Style/StringConcatenation: 486 | Exclude: 487 | - 'modules/apache/lib/puppet/functions/apache/pw_hash.rb' 488 | - 'modules/apache/lib/puppet/provider/a2mod/gentoo.rb' 489 | - 'modules/postgresql/lib/puppet/functions/postgresql/postgresql_acls_to_resources_hash.rb' 490 | - 'modules/postgresql/lib/puppet/functions/postgresql/postgresql_password.rb' 491 | - 'modules/postgresql/lib/puppet/provider/postgresql_psql/ruby.rb' 492 | - 'spec/receiver_spec.rb' 493 | 494 | # Offense count: 13 495 | # This cop supports unsafe autocorrection (--autocorrect-all). 496 | # Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments. 497 | # AllowedMethods: define_method 498 | Style/SymbolProc: 499 | Exclude: 500 | - 'lib/librarian/puppet/action/resolve.rb' 501 | - 'lib/librarian/puppet/source/forge/repo_v1.rb' 502 | - 'lib/librarian/puppet/source/forge/repo_v3.rb' 503 | - 'modules/stdlib/lib/puppet/functions/to_json_pretty.rb' 504 | - 'modules/stdlib/lib/puppet/parser/functions/camelcase.rb' 505 | - 'modules/stdlib/lib/puppet/parser/functions/clamp.rb' 506 | - 'modules/stdlib/lib/puppet/parser/functions/fqdn_rand_string.rb' 507 | - 'modules/stdlib/lib/puppet/parser/functions/fqdn_uuid.rb' 508 | - 'modules/stdlib/lib/puppet/parser/functions/shell_join.rb' 509 | - 'modules/stdlib/lib/puppet/parser/functions/squeeze.rb' 510 | 511 | # Offense count: 100 512 | # This cop supports safe autocorrection (--autocorrect). 513 | # Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. 514 | # URISchemes: http, https 515 | Layout/LineLength: 516 | Max: 234 517 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v6.0.0](https://github.com/voxpupuli/librarian-puppet/tree/v6.0.0) (2025-04-02) 4 | 5 | [Full Changelog](https://github.com/voxpupuli/librarian-puppet/compare/v5.1.0...v6.0.0) 6 | 7 | **Breaking changes:** 8 | 9 | - Drop EoL Puppet 6 testing [\#132](https://github.com/voxpupuli/librarian-puppet/pull/132) ([bastelfreak](https://github.com/bastelfreak)) 10 | 11 | **Fixed bugs:** 12 | 13 | - Fix missing action when installing with --strip-dot-git [\#127](https://github.com/voxpupuli/librarian-puppet/pull/127) ([melck](https://github.com/melck)) 14 | 15 | **Merged pull requests:** 16 | 17 | - add voxpupuli-rubocop [\#138](https://github.com/voxpupuli/librarian-puppet/pull/138) ([bastelfreak](https://github.com/bastelfreak)) 18 | - Fix minor RuboCop issues in Rakefile [\#137](https://github.com/voxpupuli/librarian-puppet/pull/137) ([bastelfreak](https://github.com/bastelfreak)) 19 | - gemspec: Add strict dependency versions & CI: Build gem with --strict [\#136](https://github.com/voxpupuli/librarian-puppet/pull/136) ([bastelfreak](https://github.com/bastelfreak)) 20 | - concurrent-ruby: Require 1.x [\#134](https://github.com/voxpupuli/librarian-puppet/pull/134) ([bastelfreak](https://github.com/bastelfreak)) 21 | - cucumber: Require 9.x [\#133](https://github.com/voxpupuli/librarian-puppet/pull/133) ([bastelfreak](https://github.com/bastelfreak)) 22 | 23 | ## [v5.1.0](https://github.com/voxpupuli/librarian-puppet/tree/v5.1.0) (2025-03-07) 24 | 25 | [Full Changelog](https://github.com/voxpupuli/librarian-puppet/compare/v5.0.0...v5.1.0) 26 | 27 | **Implemented enhancements:** 28 | 29 | - puppet\_forge: Allow 6.x [\#129](https://github.com/voxpupuli/librarian-puppet/pull/129) ([bastelfreak](https://github.com/bastelfreak)) 30 | - Switch from forgeapi.puppetlabs.com-\>forgeapi.puppet.com [\#117](https://github.com/voxpupuli/librarian-puppet/pull/117) ([bastelfreak](https://github.com/bastelfreak)) 31 | - Add Puppet 8 support [\#113](https://github.com/voxpupuli/librarian-puppet/pull/113) ([bastelfreak](https://github.com/bastelfreak)) 32 | - Add Ruby 3.1/3.2 support [\#112](https://github.com/voxpupuli/librarian-puppet/pull/112) ([bastelfreak](https://github.com/bastelfreak)) 33 | - puppet\_forge: Allow 5.x [\#111](https://github.com/voxpupuli/librarian-puppet/pull/111) ([bastelfreak](https://github.com/bastelfreak)) 34 | 35 | **Merged pull requests:** 36 | 37 | - Add ephemeral test files to .gitignore [\#130](https://github.com/voxpupuli/librarian-puppet/pull/130) ([bastelfreak](https://github.com/bastelfreak)) 38 | - GCG: Add faraday-retry dep [\#115](https://github.com/voxpupuli/librarian-puppet/pull/115) ([bastelfreak](https://github.com/bastelfreak)) 39 | - CI: Exclude release gem group [\#114](https://github.com/voxpupuli/librarian-puppet/pull/114) ([bastelfreak](https://github.com/bastelfreak)) 40 | 41 | ## [v5.0.0](https://github.com/voxpupuli/librarian-puppet/tree/v5.0.0) (2023-05-24) 42 | 43 | [Full Changelog](https://github.com/voxpupuli/librarian-puppet/compare/v4.0.1...v5.0.0) 44 | 45 | **Breaking changes:** 46 | 47 | - Remove deprecated method alias [\#109](https://github.com/voxpupuli/librarian-puppet/pull/109) ([ekohl](https://github.com/ekohl)) 48 | - Remove forge.puppetlabs.com -\> forgeapi.p.c rewrite [\#108](https://github.com/voxpupuli/librarian-puppet/pull/108) ([ekohl](https://github.com/ekohl)) 49 | - Drop Modulefile support [\#106](https://github.com/voxpupuli/librarian-puppet/pull/106) ([ekohl](https://github.com/ekohl)) 50 | - Require Ruby 2.7+ [\#103](https://github.com/voxpupuli/librarian-puppet/pull/103) ([ekohl](https://github.com/ekohl)) 51 | - Default to using API v3 [\#102](https://github.com/voxpupuli/librarian-puppet/pull/102) ([ekohl](https://github.com/ekohl)) 52 | - Start assuming Puppet 3.6+ is used [\#101](https://github.com/voxpupuli/librarian-puppet/pull/101) ([ekohl](https://github.com/ekohl)) 53 | 54 | **Implemented enhancements:** 55 | 56 | - Allow puppet\_forge 4.x [\#107](https://github.com/voxpupuli/librarian-puppet/pull/107) ([ekohl](https://github.com/ekohl)) 57 | 58 | **Merged pull requests:** 59 | 60 | - CI: run on PRs and merges to master [\#105](https://github.com/voxpupuli/librarian-puppet/pull/105) ([bastelfreak](https://github.com/bastelfreak)) 61 | - Add dummy CI job we can depend on [\#104](https://github.com/voxpupuli/librarian-puppet/pull/104) ([bastelfreak](https://github.com/bastelfreak)) 62 | 63 | ## [v4.0.1](https://github.com/voxpupuli/librarian-puppet/tree/v4.0.1) (2023-04-26) 64 | 65 | [Full Changelog](https://github.com/voxpupuli/librarian-puppet/compare/v4.0.0...v4.0.1) 66 | 67 | **Fixed bugs:** 68 | 69 | - support ruby 3.2 by renaming File.exists? to File.exist? [\#98](https://github.com/voxpupuli/librarian-puppet/pull/98) ([shamil](https://github.com/shamil)) 70 | 71 | ## [v4.0.0](https://github.com/voxpupuli/librarian-puppet/tree/v4.0.0) (2023-02-22) 72 | 73 | [Full Changelog](https://github.com/voxpupuli/librarian-puppet/compare/v3.0.1...v4.0.0) 74 | 75 | **Breaking changes:** 76 | 77 | - Drop Ruby 2.4 support [\#96](https://github.com/voxpupuli/librarian-puppet/pull/96) ([evgeni](https://github.com/evgeni)) 78 | - Drop Ruby 2.3 support and switch to Github Actions [\#86](https://github.com/voxpupuli/librarian-puppet/pull/86) ([ekohl](https://github.com/ekohl)) 79 | - Drop Puppet 3/Puppet 4 and Ruby 2.0/2.1/2.2 testing [\#82](https://github.com/voxpupuli/librarian-puppet/pull/82) ([bastelfreak](https://github.com/bastelfreak)) 80 | 81 | **Implemented enhancements:** 82 | 83 | - Mark compatible with puppet\_forge 3.x [\#85](https://github.com/voxpupuli/librarian-puppet/pull/85) ([ekohl](https://github.com/ekohl)) 84 | - Add Puppet 7 support [\#84](https://github.com/voxpupuli/librarian-puppet/pull/84) ([bastelfreak](https://github.com/bastelfreak)) 85 | - Add Ruby2.5/Puppet6 to CI matrix, disable email notifications on CI runs [\#83](https://github.com/voxpupuli/librarian-puppet/pull/83) ([bastelfreak](https://github.com/bastelfreak)) 86 | 87 | **Closed issues:** 88 | 89 | - Ubuntu 22.04 Tried to create Proc without a block [\#90](https://github.com/voxpupuli/librarian-puppet/issues/90) 90 | - Unable to retrieve dependencies [\#89](https://github.com/voxpupuli/librarian-puppet/issues/89) 91 | - Domain librarian-puppet.com in GitHub About sections is for sale [\#88](https://github.com/voxpupuli/librarian-puppet/issues/88) 92 | - Bundler 2 requires Ruby 2.3 or later. Either install bundler 1 or update to a supported Ruby version [\#72](https://github.com/voxpupuli/librarian-puppet/issues/72) 93 | - "Could not resolve the dependencies." for two modules with different version requirements [\#54](https://github.com/voxpupuli/librarian-puppet/issues/54) 94 | - buffer error \(Zlib::BufError\) when installing puppetlabs-apache [\#39](https://github.com/voxpupuli/librarian-puppet/issues/39) 95 | - Faraday::SSLError [\#32](https://github.com/voxpupuli/librarian-puppet/issues/32) 96 | - Simpler github module specs [\#31](https://github.com/voxpupuli/librarian-puppet/issues/31) 97 | - -bash: librarian-puppet: command not found [\#29](https://github.com/voxpupuli/librarian-puppet/issues/29) 98 | - Error: redirection forbidden [\#28](https://github.com/voxpupuli/librarian-puppet/issues/28) 99 | - Using librarian-puppet and custom modules [\#18](https://github.com/voxpupuli/librarian-puppet/issues/18) 100 | - Failing to install activesupport-5.0.0.beta3.gem on Ruby 2.1.8. [\#6](https://github.com/voxpupuli/librarian-puppet/issues/6) 101 | - Puppetfile syntax differences - R10K vs librarian [\#5](https://github.com/voxpupuli/librarian-puppet/issues/5) 102 | 103 | **Merged pull requests:** 104 | 105 | - Actually run acceptance tests [\#92](https://github.com/voxpupuli/librarian-puppet/pull/92) ([ekohl](https://github.com/ekohl)) 106 | - Typo [\#87](https://github.com/voxpupuli/librarian-puppet/pull/87) ([sboyd-m](https://github.com/sboyd-m)) 107 | - Add --\[no-\]use-v1-api option to update command [\#75](https://github.com/voxpupuli/librarian-puppet/pull/75) ([dwminer](https://github.com/dwminer)) 108 | - Support basic auth for forge [\#69](https://github.com/voxpupuli/librarian-puppet/pull/69) ([kimsondrup](https://github.com/kimsondrup)) 109 | 110 | ## 3.0.1 111 | 112 | * [PR #68](https://github.com/voxpupuli/librarian-puppet/pull/68) Specify dependent module in dependencies 113 | * [PR #80](https://github.com/voxpupuli/librarian-puppet/pull/80) Fix calling issue with open-uri 114 | 115 | ## 3.0.0 116 | 117 | ### Breaking Changes 118 | 119 | Librarian-Puppet 3.0.0 and newer requires Ruby >= 2.0. Use version 2.2.4 if you need support for Puppet 3.7 or earlier, or Ruby 1.9 or earlier. Note that Puppet 4.10 and [newer require Ruby 2.1](https://puppet.com/docs/puppet/4.10/system_requirements.html#prerequisites) or newer. 120 | 121 | * [PR #1](https://github.com/voxpupuli/librarian/pull/1) Add support for r10k Puppetfile's opts 122 | * [PR #5](https://github.com/voxpupuli/librarian-puppet/pull/9) Update README for r10k syntax from PR #1. 123 | * [PR #8](https://github.com/voxpupuli/librarian-puppet/pull/8) Clean up tests 124 | * [PR #19](https://github.com/voxpupuli/librarian-puppet/pull/19) Fix rsync on Windows (path conversion, return code checking) and add trailing / 125 | * [Issue #20](https://github.com/voxpupuli/librarian-puppet/issues/20) Ignore Gem files 126 | * [Issue #25](https://github.com/voxpupuli/librarian-puppet/pull/25) Move to https and new forge url 127 | * Avoid deleted ripienaar-concat 128 | * [PR #59](https://github.com/voxpupuli/librarian-puppet/pull/59) Fix tests 129 | * [PR #61](https://github.com/voxpupuli/librarian-puppet/pull/61) Bring testing matrix up to date 130 | 131 | ## 2.2.3 132 | 133 | * [Issue #1](https://github.com/voxpupuli/librarian-puppet/pull/1) Upgrade to puppet_forge 2 134 | * [Issue #2](https://github.com/voxpupuli/librarian-puppet/pull/2) Change github user from "rodjek" to "voxpupuli" 135 | 136 | ## 2.2.1 137 | 138 | * [Issue #311](https://github.com/rodjek/librarian-puppet/issues/311) Omit versions with a deleted_at date 139 | 140 | ## 2.2.0 141 | 142 | * Add support for Puppet 4 143 | * [Issue #296](https://github.com/rodjek/librarian-puppet/issues/296) Uninitialized constant Puppet::ModuleTool::ModulefileReader using Modulefiles in Puppet 4. Ignore those dependencies 144 | 145 | ## 2.1.1 146 | 147 | * [Issue #302](https://github.com/rodjek/librarian-puppet/issues/302) Ensure path is not lost when default specfile is used 148 | * [Issue #294](https://github.com/rodjek/librarian-puppet/issues/294) Undefined variable calling Puppet version in old Puppet 2.x versions 149 | * [Issue #285](https://github.com/rodjek/librarian-puppet/issues/294) Update librarianp to allow overriding dependencies from multiple sources 150 | 151 | ## 2.1.0 152 | 153 | * Update librarian to use the new `exclusion` syntax 154 | * [Issue #282](https://github.com/rodjek/librarian-puppet/issues/282) Merge duplicated dependencies and warn the user, no more `Cannot bounce Puppetfile.lock!` errors 155 | * [Issue #217](https://github.com/rodjek/librarian-puppet/issues/217), [Issue #244](https://github.com/rodjek/librarian-puppet/issues/244) Use librarianp 0.4.0 that no longer uses recursion to avoid `stack level too deep` errors 156 | * [Issue #277](https://github.com/rodjek/librarian-puppet/issues/277) Warn when there are two dependencies with the same module name 157 | * Use `librarianp` gem instead of `librarian`, a fork with the needed improvements and fixes. 158 | 159 | ## 2.0.1 160 | 161 | * [Issue #272](https://github.com/rodjek/librarian-puppet/issues/272) Defined forge is not used when resolving dependencies 162 | * [Issue #150](https://github.com/rodjek/librarian-puppet/issues/150) Allow dependencies other than Puppet modules 163 | * [Issue #269](https://github.com/rodjek/librarian-puppet/issues/269) Better error message if metadata.json is bad 164 | * [Issue #264](https://github.com/rodjek/librarian-puppet/issues/264) Copying files can cause permission problems on Windows 165 | 166 | ## 2.0.0 167 | 168 | ### Breaking Changes 169 | 170 | Librarian-Puppet 2.0.0 and newer requires Ruby >= 1.9 and uses Puppet Forge API v3. For Ruby 1.8 use 1.5.0. 171 | 172 | * Jump from 1.3.x to 2.x to leave 1.x for Ruby 1.8 compatibility 173 | * [Issue #254](https://github.com/rodjek/librarian-puppet/issues/254) Add a rsync option to prevent deleting directories 174 | * [Issue #261](https://github.com/rodjek/librarian-puppet/issues/261) Incorrect install directory is created if the organization name contains a dash 175 | * [Issue #255](https://github.com/rodjek/librarian-puppet/issues/255) Ignored forge URL when using API v3 176 | 177 | ## 1.5.0 178 | 179 | * Update librarian to use the new `exclusion` syntax 180 | * [Issue #282](https://github.com/rodjek/librarian-puppet/issues/282) Merge duplicated dependencies and warn the user, no more `Cannot bounce Puppetfile.lock!` errors 181 | * [Issue #217](https://github.com/rodjek/librarian-puppet/issues/217), [Issue #244](https://github.com/rodjek/librarian-puppet/issues/244) Use librarianp 0.4.0 that no longer uses recursion to avoid `stack level too deep` errors 182 | * [Issue #277](https://github.com/rodjek/librarian-puppet/issues/277) Warn when there are two dependencies with the same module name 183 | * Use `librarianp` gem instead of `librarian`, a fork with the needed improvements and fixes. 184 | 185 | ## 1.4.1 186 | 187 | * [Issue #272](https://github.com/rodjek/librarian-puppet/issues/272) Defined forge is not used when resolving dependencies 188 | * [Issue #150](https://github.com/rodjek/librarian-puppet/issues/150) Allow dependencies other than Puppet modules 189 | * [Issue #269](https://github.com/rodjek/librarian-puppet/issues/269) Better error message if metadata.json is bad 190 | * [Issue #264](https://github.com/rodjek/librarian-puppet/issues/264) Copying files can cause permission problems on Windows 191 | 192 | ## 1.4.0 193 | 194 | * Jump from 1.0.x to 1.4.x to keep Ruby 1.8 compatibility in the 1.x series 195 | * [Issue #254](https://github.com/rodjek/librarian-puppet/issues/254) Add a rsync option to prevent deleting directories 196 | * [Issue #261](https://github.com/rodjek/librarian-puppet/issues/261) Incorrect install directory is created if the organization name contains a dash 197 | 198 | ## 1.3.3 199 | 200 | * [Issue #250](https://github.com/rodjek/librarian-puppet/issues/250) Fix error when module has no dependencies in `metadata.json` 201 | 202 | ## 1.3.2 203 | 204 | * [Issue #246](https://github.com/rodjek/librarian-puppet/issues/246) Do not fail if modules have no `Modulefile` nor `metadata.json` 205 | 206 | ## 1.3.1 207 | 208 | * Version in dependencies with `metadata.json` is ignored 209 | 210 | ## 1.3.0 211 | 212 | * If no Puppetfile is present default to use the `metadata.json` or `Modulefile` 213 | * [Issue #235](https://github.com/rodjek/librarian-puppet/issues/235) Error when forge is not defined in `Puppetfile` 214 | * [Issue #243](https://github.com/rodjek/librarian-puppet/issues/243) Warn if `Modulefile` doesn't contain a version 215 | 216 | ## 1.2.0 217 | 218 | * Implement `metadata` syntax for `Puppetfile` 219 | * [Issue #220](https://github.com/rodjek/librarian-puppet/issues/220) Add support for metadata.json 220 | * [Issue #242](https://github.com/rodjek/librarian-puppet/issues/242) Get organization from name correctly if name has multiple dashes 221 | 222 | ## 1.1.3 223 | 224 | * [Issue #237](https://github.com/rodjek/librarian-puppet/issues/237) [Issue #238](https://github.com/rodjek/librarian-puppet/issues/238) Unable to use a custom v3 forge: add flags `--use-v1-api` and `--no-use-v1-api` 225 | * [Issue #239](https://github.com/rodjek/librarian-puppet/issues/239) GitHub tarball: add access_token correctly to url's which are already having query parameters 226 | * [Issue #234](https://github.com/rodjek/librarian-puppet/issues/234) Use organization-module instead of organization/module by default 227 | 228 | ## 1.1.2 229 | 230 | * [Issue #231](https://github.com/rodjek/librarian-puppet/issues/231) Only use the `GITHUB_API_TOKEN` if it's not empty 231 | * [Issue #233](https://github.com/rodjek/librarian-puppet/issues/233) Fix version regex to match e.g. 1.99.15 232 | * Can't pass the Puppet Forge v1 api url to clients using v3 (3.6.0+ and PE 3.2.0+) 233 | 234 | ## 1.1.1 235 | 236 | * [Issue #227](https://github.com/rodjek/librarian-puppet/issues/227) Fix Librarian::Puppet::VERSION undefined 237 | 238 | ## 1.1.0 239 | 240 | * [Issue #210](https://github.com/rodjek/librarian-puppet/issues/210) Use forgeapi.puppetlabs.com and API v3 241 | * Accesing the v3 API requires Ruby 1.9 due to the puppet_forge library used 242 | 243 | 244 | ## 1.0.10 245 | 246 | * [Issue #250](https://github.com/rodjek/librarian-puppet/issues/250) Fix error when module has no dependencies in `metadata.json` 247 | 248 | ## 1.0.9 249 | 250 | * [Issue #246](https://github.com/rodjek/librarian-puppet/issues/246) Do not fail if modules have no `Modulefile` nor `metadata.json` 251 | 252 | ## 1.0.8 253 | 254 | * Version in dependencies with `metadata.json` is ignored 255 | 256 | ## 1.0.7 257 | 258 | * If no Puppetfile is present default to use the `metadata.json` or `Modulefile` 259 | * [Issue #235](https://github.com/rodjek/librarian-puppet/issues/235) Error when forge is not defined in `Puppetfile` 260 | * [Issue #243](https://github.com/rodjek/librarian-puppet/issues/243) Warn if `Modulefile` doesn't contain a version 261 | 262 | 263 | ## 1.0.6 264 | 265 | * Implement `metadata` syntax for `Puppetfile` 266 | * [Issue #220](https://github.com/rodjek/librarian-puppet/issues/220) Add support for metadata.json 267 | * [Issue #242](https://github.com/rodjek/librarian-puppet/issues/242) Get organization from name correctly if name has multiple dashes 268 | 269 | ## 1.0.5 270 | 271 | * [Issue #237](https://github.com/rodjek/librarian-puppet/issues/237)[Issue #238](https://github.com/rodjek/librarian-puppet/issues/238) Unable to use a custom v3 forge: add flags `--use-v1-api` and `--no-use-v1-api` 272 | * [Issue #239](https://github.com/rodjek/librarian-puppet/issues/239) GitHub tarball: add access_token correctly to url's which are already having query parameters 273 | * [Issue #234](https://github.com/rodjek/librarian-puppet/issues/234) Use organization-module instead of organization/module by default 274 | 275 | ## 1.0.4 276 | 277 | * [Issue #231](https://github.com/rodjek/librarian-puppet/issues/231) Only use the `GITHUB_API_TOKEN` if it's not empty 278 | * [Issue #233](https://github.com/rodjek/librarian-puppet/issues/233) Fix version regex to match e.g. 1.99.15 279 | * Can't pass the Puppet Forge v1 api url to clients using v3 (3.6.0+ and PE 3.2.0+) 280 | 281 | ## 1.0.3 282 | 283 | * [Issue #223](https://github.com/rodjek/librarian-puppet/issues/223) `Cannot bounce Puppetfile.lock!` error when Forge modules contain duplicated dependencies 284 | 285 | ## 1.0.2 286 | 287 | * [Issue #211](https://github.com/rodjek/librarian-puppet/issues/211) Pass the PuppetLabs Forge API v3 endpoint to `puppet module` when running on Puppet >= 3.6.0 288 | * [Issue #198](https://github.com/rodjek/librarian-puppet/issues/198) Reduce the length of tmp dirs to avoid issues in windows 289 | * [Issue #206](https://github.com/rodjek/librarian-puppet/issues/206) githubtarball call for released versions does not consider pagination 290 | * [Issue #204](https://github.com/rodjek/librarian-puppet/issues/204) Fix regex to detect Forge API v3 url 291 | * [Issue #199](https://github.com/rodjek/librarian-puppet/issues/199) undefined method run! packaging a git source 292 | * Verify SSL certificates in github calls 293 | 294 | ## 1.0.1 295 | 296 | * [Issue #190](https://github.com/rodjek/librarian-puppet/issues/190) Pass the PuppetLabs Forge API v3 endpoint to `puppet module` when running on Puppet Enterprise >= 3.2 297 | * [Issue #196](https://github.com/rodjek/librarian-puppet/issues/196) Fix error in error handling when puppet is not installed 298 | 299 | ## 1.0.0 300 | 301 | * Remove deprecation warning for github_tarball sources, some people are actually using it 302 | 303 | ## 0.9.17 304 | 305 | * [Issue #193](https://github.com/rodjek/librarian-puppet/issues/193) Support Puppet 3.5.0 306 | 307 | ## 0.9.16 308 | 309 | * [Issue #181](https://github.com/rodjek/librarian-puppet/issues/181) Should use qualified module names for resolution to work correctly 310 | * Deprecate github_tarball sources 311 | * Reduce number of API calls for github_tarball sources 312 | 313 | ## 0.9.15 314 | 315 | * [Issue #187](https://github.com/rodjek/librarian-puppet/issues/187) Fixed parallel installation issues 316 | * [Issue #185](https://github.com/rodjek/librarian-puppet/issues/185) Sanitize the gem/bundler environment before spawning (ruby 1.9+) 317 | 318 | ## 0.9.14 319 | 320 | * [Issue #182](https://github.com/rodjek/librarian-puppet/issues/182) Sanitize the environment before spawning (ruby 1.9+) 321 | * [Issue #184](https://github.com/rodjek/librarian-puppet/issues/184) Support transitive dependencies in modules using :path 322 | * Git dependencies using modulefile syntax make librarian-puppet fail 323 | * [Issue #108](https://github.com/rodjek/librarian-puppet/issues/108) Don't fail on malformed Modulefile from a git dependency 324 | 325 | ## 0.9.13 326 | 327 | * [Issue #176](https://github.com/rodjek/librarian-puppet/issues/176) Upgrade to librarian 0.1.2 328 | * [Issue #179](https://github.com/rodjek/librarian-puppet/issues/179) Need to install extra gems just in case we are in ruby 1.8 329 | * [Issue #178](https://github.com/rodjek/librarian-puppet/issues/178) Print a meaningful message if puppet gem can't be loaded for :git sources 330 | 331 | ## 0.9.12 332 | 333 | * Remove extra dependencies from gem added when 0.9.11 was released under ruby 1.8 334 | 335 | ## 0.9.11 336 | 337 | * Add modulefile dsl to reuse Modulefile dependencies 338 | * Consider Puppetfile-dependencies recursively in git-source 339 | * Support changing tmp, cache and scratch paths 340 | * librarian-puppet package causes an infinite loop 341 | * Show a message if no versions are found for a module 342 | * Make download of tarballs more robust 343 | * Require open3_backport in ruby 1.8 and install if not present 344 | * Git dependencies in both Puppetfile and Modulefile cause a Cannot bounce Puppetfile.lock! error 345 | * Better sort of github tarball versions when there are mixed tags starting with and without 'v' 346 | * Fix error if a git module has a dependency without version 347 | * Fix git dependency with :path attribute 348 | * Cleaner output when no Puppetfile found 349 | * Reduce the number of API calls to the Forge 350 | * Don't sort versions as strings. Rely on the forge returning them ordered 351 | * Pass --module_repository to `puppet module install` to install from other forges 352 | * Cache forge responses and print an error if returns an invalid response 353 | * Add a User-Agent header to all requests to the GitHub API 354 | * Convert puppet version requirements to rubygems, pessimistic and ranges 355 | * Use librarian gem 356 | 357 | ## 0.9.10 358 | 359 | * Catch GitHub API rate limit exceeded 360 | * Make Librarian::Manifest Semver 2.0.0 compatible 361 | 362 | ## 0.9.1 363 | * Proper error message when a module that is sourced from the forge does not 364 | exist. 365 | * Added support for annotated tags as git references. 366 | * `librarian-puppet init` adds `.tmp/` to gitignore instead of `tmp/`. 367 | * Fixed syntax error in the template Puppetfile created by `librarian-puppet 368 | init`. 369 | * Checks for `lib/puppet` as well as `manifests/` when checking if the git 370 | repository is a valid module. 371 | * When a user specifies `/` as the name of a module sources from a 372 | git repository, assume the module name is actually ``. 373 | * Fixed gem description and summary in gemspec. 374 | 375 | ## 0.9.0 376 | * Initial release 377 | 378 | 379 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 380 | --------------------------------------------------------------------------------