-
Notifications
You must be signed in to change notification settings - Fork 225
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(Feature) Add PupEnt tool with puppet access and code
Adds a new executable to Bolt: 'pupent', which replaces the existing implementations of the puppet-access and puppet-code cli tools for Puppet Enterprise.
- Loading branch information
1 parent
7a10680
commit b970b04
Showing
7 changed files
with
682 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
#!/usr/bin/env ruby | ||
# frozen_string_literal: true | ||
|
||
require 'pupent/cli' | ||
require 'bolt/logger' | ||
require 'bolt/error' | ||
|
||
begin | ||
cli = PupEnt::CLI.new(ARGV) | ||
exitcode = cli.execute | ||
exit exitcode | ||
rescue PupEnt::CLIExit | ||
exit | ||
rescue Bolt::Error => e | ||
exit e.error_code | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'fileutils' | ||
require 'io/console' | ||
|
||
module PupEnt | ||
class Access | ||
RBAC_PREFIX = ":4433/rbac-api" | ||
|
||
def initialize(token_location) | ||
@token_location = token_location | ||
@logger = Bolt::Logger.logger(self) | ||
end | ||
|
||
def login(client, lifetime) | ||
$stderr.print("Enter your Puppet Enterprise credentials.\n") | ||
$stderr.print("Username: ") | ||
username = $stdin.gets.to_s.chomp | ||
$stderr.print("Password: ") | ||
# noecho ensures we don't print anything to the console | ||
# while the user is typing the password | ||
password = $stdin.noecho(&:gets).to_s.chomp! | ||
$stderr.puts | ||
|
||
body = { | ||
login: username, | ||
password: password, | ||
lifetime: lifetime | ||
} | ||
response, = client.pe_post("/v1/auth/token", body, sensitive: true) | ||
FileUtils.mkdir_p(File.dirname(token_location)) | ||
File.open(token_location, File::CREAT | File::WRONLY) do |fd| | ||
fd.write(response['token']) | ||
end | ||
end | ||
|
||
def show | ||
File.open(token_location, File::RDONLY).read | ||
rescue Errno::ENOENT | ||
msg = "No token file!" | ||
@logger.error(msg) | ||
raise Bolt::Error.new(msg, 'bolt/no-token') | ||
end | ||
|
||
def delete_token | ||
@logger.info("Deleting token...") | ||
File.delete(token_location) | ||
@logger.info("Done.") | ||
rescue Errno::ENOENT | ||
msg = "No token file!" | ||
@logger.error(msg) | ||
raise Bolt::Error.new(msg, 'bolt/no-token') | ||
end | ||
|
||
def token_location | ||
@token_location ||= File.join(ENV['HOME'], '.pupent', 'token') | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
# frozen_string_literal: true | ||
|
||
require_relative '../pupent/pupent_option_parser' | ||
require_relative '../pupent/config' | ||
require_relative '../pupent/http_client' | ||
require_relative '../pupent/access' | ||
require_relative '../pupent/code' | ||
|
||
module PupEnt | ||
class CLI | ||
def initialize(argv) | ||
@command, @action, @object, cli_options = PupEntOptionParser.parse(argv) | ||
# This merge is done in a specific order on purpose: | ||
# | ||
# The final parsed options are read from the config options on disk first, then | ||
# we merge anything sent over the CLI. This forces the precedence that CLI options | ||
# override anything set on disk. | ||
@parsed_options = Config.read_config(cli_options[:config_file]).merge(cli_options) | ||
Bolt::Logger.initialize_logging | ||
Bolt::Logger.logger(:root).add_appenders Logging.appenders.stderr( | ||
'console', | ||
layout: Bolt::Logger.console_layout(true), | ||
level: @parsed_options[:log_level] | ||
) | ||
@pe_host_url = parse_pe_host_url(@command, @parsed_options[:pe_host], @parsed_options[:service_url]) | ||
@ca_cert = @parsed_options[:ca_cert] | ||
if @parsed_options[:save_config] | ||
require_relative '../pupent/config' | ||
Config.save_config(@parsed_options) | ||
end | ||
end | ||
|
||
def parse_pe_host_url(command, pe_host, service_url) | ||
if pe_host | ||
case command | ||
when 'access' | ||
"https://" + pe_host + Access::RBAC_PREFIX | ||
when 'code' | ||
"https://" + pe_host + Code::CODE_MANAGER_PREFIX | ||
end | ||
elsif service_url | ||
service_url | ||
end | ||
end | ||
|
||
# Only create a client when we need to | ||
def new_client | ||
HttpClient.new(@pe_host_url, @ca_cert) | ||
end | ||
|
||
def execute | ||
case @command | ||
when 'access' | ||
case @action | ||
when 'login' | ||
Access.new( | ||
@parsed_options[:token_file] | ||
).login(new_client, @parsed_options[:lifetime], @parsed_options) | ||
0 | ||
when 'show' | ||
$stdout.puts Access.new(@parsed_options[:token_file]).show | ||
0 | ||
when 'delete-token-file' | ||
Access.new(@parsed_options[:token_file]).delete_token | ||
0 | ||
end | ||
when 'code' | ||
case @action | ||
when 'deploy' | ||
$stdout.puts Code.new( | ||
@parsed_options[:token_file], | ||
new_client | ||
).deploy(@object, @parsed_options[:wait], @parsed_options[:all]) | ||
0 | ||
when 'status' | ||
$stdout.puts Code.new( | ||
@parsed_options[:token_file], | ||
new_client | ||
).status | ||
0 | ||
when 'deploy-status' | ||
$stdout.puts Code.new( | ||
@parsed_options[:token_file], | ||
new_client | ||
).deploy_status(@object) | ||
0 | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'json' | ||
require_relative "../pupent/access" | ||
|
||
module PupEnt | ||
class Code | ||
CODE_MANAGER_PREFIX = ":8170/code-manager" | ||
|
||
def initialize(token_location, http_client) | ||
@token = Access.new(token_location).show | ||
@client = http_client | ||
end | ||
|
||
def deploy(environments, wait, all) | ||
body = {} | ||
if all | ||
body["deploy-all"] = true | ||
else | ||
body[:environments] = environments.split(',') | ||
end | ||
|
||
if wait | ||
body[:wait] = true | ||
end | ||
response, = @client.pe_post('/v1/deploys', body, headers: { "X-Authentication": @token }) | ||
JSON.pretty_generate(response) | ||
end | ||
|
||
def status | ||
response, = @client.pe_get('/v1/status', headers: { "X-Authentication": @token }) | ||
JSON.pretty_generate(response) | ||
end | ||
|
||
def deploy_status(deploy_id) | ||
url_path = if deploy_id && !deploy_id.empty? | ||
"/v1/deploys/status?id=#{deploy_id}" | ||
else | ||
"/v1/deploys/status" | ||
end | ||
response, = @client.pe_get(url_path, headers: { "X-Authentication": @token }) | ||
JSON.pretty_generate(response) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'json' | ||
require 'fileutils' | ||
|
||
module PupEnt | ||
module Config | ||
DEFAULT_PUPENT_LOCATION = File.join(ENV['HOME'], ".puppetlabs", "pupent") | ||
DEFAULTS = { | ||
token_file: File.join(DEFAULT_PUPENT_LOCATION, "token"), | ||
log_level: "info", | ||
# pupent access defaults | ||
lifetime: "15m" | ||
}.freeze | ||
|
||
def self.read_config(file_location) | ||
FileUtils.mkdir_p(DEFAULT_PUPENT_LOCATION) | ||
file_location ||= File.join(DEFAULT_PUPENT_LOCATION, "config.json") | ||
parsed_data = nil | ||
# Use a+ so it won't fail if the config file doesn't exist, just create an empty one | ||
File.open(file_location, 'a+') do |fd| | ||
config_data = fd.read | ||
parsed_data = if config_data && !config_data.empty? | ||
JSON.parse(config_data) | ||
else | ||
{} | ||
end | ||
end | ||
parsed_data.transform_keys! { |key| key.to_s.downcase.gsub("-", "_").to_sym } | ||
DEFAULTS.merge(parsed_data) | ||
end | ||
|
||
def self.save_config(parsed_options) | ||
file_location ||= File.join(DEFAULT_PUPENT_LOCATION, "config.json") | ||
File.open(file_location, 'w') do |fd| | ||
config_to_save = parsed_options.slice(:token_file, :ca_cert, :pe_host, :service_url) | ||
fd.write(JSON.pretty_generate(config_to_save)) | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.