Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions lib/bolt/config/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,14 @@ module Options
},
_plugin: true
},
"puppetfile" => {
description: "The path to the project's Puppetfile, used by `bolt module install` and " \
"related commands. Relative paths are resolved from the project directory; " \
"absolute paths are used as-is. Defaults to `Puppetfile` in the project directory.",
type: String,
_example: "control-repo/Puppetfile",
_plugin: true
},
"rerunfile" => {
description: "The path to the project's rerun file. The rerun file is used to store information " \
"about targets from the most recent run. Expands relative to the project directory.",
Expand Down Expand Up @@ -668,6 +676,7 @@ module Options
policies
puppetdb
puppetdb-instances
puppetfile
rerunfile
save-rerun
spinner
Expand Down
3 changes: 3 additions & 0 deletions lib/bolt/module_installer/puppetfile.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'fileutils'

require_relative '../../bolt/error'
require_relative 'puppetfile/forge_module'
require_relative 'puppetfile/git_module'
Expand Down Expand Up @@ -69,6 +71,7 @@ def self.parse(path, skip_unsupported_modules: false)
# modules.
#
def write(path, moduledir = nil)
FileUtils.mkdir_p(File.dirname(path))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we limit this to be a subdir of the current dir only maybe? I'm not sure if it's good idea to allow specifying literally any file path as a Puppetfile.

E.g. if Bob creates a repo with puppetfile set to /etc/shadow and then give that to Alice. Alice runs bolt module install and got their workstation broken.

I'm not sure what is our strategy here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch (and may god have mercy on Alice if she installs modules with sudo and --force).
I was thinking about subdirs within the project directory, so I'll restrict it to that

File.open(path, 'w') do |file|
if moduledir
file.puts "# This Puppetfile is managed by Bolt. Do not edit."
Expand Down
9 changes: 9 additions & 0 deletions lib/bolt/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ def initialize(data, path, type = 'option')
@rerunfile = File.expand_path(@data['rerunfile'], @path)
end

if @data['puppetfile'].is_a?(String)
expanded = File.expand_path(@data['puppetfile'], @path)
unless expanded.start_with?(@path.to_s + '/')
raise Bolt::ValidationError,
"Option 'puppetfile' must be a relative path within the project directory."
end
@puppetfile = Pathname.new(expanded)
end

validate if project_file?
end

Expand Down
14 changes: 14 additions & 0 deletions schemas/bolt-project.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
"puppetdb-instances": {
"$ref": "#/definitions/puppetdb-instances"
},
"puppetfile": {
"$ref": "#/definitions/puppetfile"
},
"rerunfile": {
"$ref": "#/definitions/rerunfile"
},
Expand Down Expand Up @@ -605,6 +608,17 @@
}
]
},
"puppetfile": {
"description": "The path to the project's Puppetfile, used by `bolt module install` and related commands. Relative paths are resolved from the project directory; absolute paths are used as-is. Defaults to `Puppetfile` in the project directory.",
"oneOf": [
{
"type": "string"
},
{
"$ref": "#/definitions/_plugin"
}
]
},
"rerunfile": {
"description": "The path to the project's rerun file. The rerun file is used to store information about targets from the most recent run. Expands relative to the project directory.",
"oneOf": [
Expand Down
16 changes: 16 additions & 0 deletions spec/integration/module_installer/module_installer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@
end
end

context 'with a puppetfile override in bolt-project.yaml' do
let(:project_config) { { 'puppetfile' => 'custom/Puppetfile' } }

it 'writes the puppetfile at the override path and not the default' do
output = run_cli_json(%w[module add puppetlabs-yaml], project: project)
expect(output['puppetfile']).to eq((project.path + 'custom' + 'Puppetfile').to_s)
expect(output['success']).to be

expect(File.exist?(project.path + 'custom' + 'Puppetfile')).to be true
expect(File.exist?(project.path + 'Puppetfile')).to be false

puppetfile_content = File.read(project.path + 'custom' + 'Puppetfile')
expect(puppetfile_content.lines).to include(%r{mod 'puppetlabs/yaml'})
end
end

context 'with install configuration' do
let(:base_config) do
{
Expand Down
45 changes: 45 additions & 0 deletions spec/unit/project_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,51 @@
end
end

describe "#puppetfile" do
context "without a puppetfile override" do
let(:project_config) { {} }

it "defaults to <project>/Puppetfile" do
expect(project.puppetfile).to be_a(Pathname)
expect(project.puppetfile).to eq(project.path + 'Puppetfile')
end
end

context "with a relative puppetfile override" do
let(:project_config) { { 'puppetfile' => 'custom/Puppetfile.dev' } }

it "resolves the path relative to the project directory" do
expect(project.puppetfile).to be_a(Pathname)
expect(project.puppetfile).to eq(project.path + 'custom' + 'Puppetfile.dev')
end
end

context "with an absolute puppetfile path" do
let(:project_config) { { 'puppetfile' => File.expand_path('/tmp/shared/Puppetfile') } }

it "raises a validation error" do
expect { project }.to raise_error(Bolt::ValidationError, /must be a relative path within the project directory/)
end
end

context "with a puppetfile path that escapes the project directory" do
let(:project_config) { { 'puppetfile' => '../outside/Puppetfile' } }

it "raises a validation error" do
expect { project }.to raise_error(Bolt::ValidationError, /must be a relative path within the project directory/)
end
end

context "with a non-string puppetfile value" do
let(:project_config) { { 'puppetfile' => 42 } }

it "fails project schema validation" do
expect { Bolt::Project.create_project(@project_path) }
.to raise_error(Bolt::ValidationError, /puppetfile/)
end
end
end

describe "with namespaced project names" do
let(:project_config) { { 'name' => 'puppetlabs-foo' } }

Expand Down
Loading