packer-client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YzljNzQ3MDI0YTI4ZDk0ZWYzNGQ3NWI4NTg0NzMwZTA0NjA3YzEwZQ==
5
+ data.tar.gz: !binary |-
6
+ NzBmZmRjZmJlNjVjYTkwZjY1ODQyY2JlNzI3OTNhZTY4OGE5YTg1Mw==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZTk1MzQ5YmU1MDJmMTYwOGUwZTBjY2Q1NzVkZjRiOGY0NGFhNTE3YTM4YTIy
10
+ ZDdjYjU4YjI5YjllMmMxODUzMDhmOTQ4YmQ2ZGQyOTk4MDI1N2EyMzMzNTg4
11
+ OTQwNTRmZTYxYTIwYWM5MzZjYTZhZTFlZjdjYTc1MWMxYWFjZjI=
12
+ data.tar.gz: !binary |-
13
+ MDMzZWNlMjM3NDFkNTRiOTAwMjUwOGZmYTRhNTdhMmM5MjkxNjExNGFlMGZm
14
+ ZmE5YjE0NWIxMWYxYTE3ZTUwZTZmMmUwN2NjZjhlOGJmZWJlYTE3MjFhZmQy
15
+ ZDk3NDVhY2JjMzkwNmI5NTRkMmFiMzE4MDNmMTQwY2Q5MjA4ZWU=
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,2 @@
1
+ Metrics/LineLength:
2
+ Enabled: false
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.5
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.10.6
6
+ deploy:
7
+ provider: rubygems
8
+ on:
9
+ tags: true
10
+ api_key:
11
+ secure: E4iOts6aVaN7qavJ39NKoAhtJPXVqXG0XQIXbJKshacxzs9WdZxB8v9lxCNCq9nAaybzw++e/CybT3jv+OeujMMJRF61qQ6LxD5btBJlrMItB1RIU0Gb6I0xjb/iIZk5hQejYxxi1wSezUBwwxP+AtyaY5Dw8CB/hH+jk8EgZwWpb70fpK6IARrZNM5XpoEIB/f/ZF1JU6Mfcdvik4mG6sbqTz/3bjv6QbzDw80ejIjzlFeAx7hek4XuIV5KAHU2+ArpRuMIpm03yOBgqrMke481Zqhu6XATz/SV9MLzpVgehttxziTj9fNEkW8Lfn35s4+CxNK1f+UHRraTBXFaiHCCclKAt8NmaxnaCSzHziYzRrryY0tHICiyirdftt6exFf3a+RUECEQim/BrOq7b3T1qMDewtWOp6zH2An+QQ/mVfcx/SPuzkrkZ1zU+MkZhQmJWCgbJQT7P4aeJLUhE9oi37PZg2Kurv5Ul0ERpjVfV0Y102IegfHlp8S/67oDeeQJmh9vhd22EhVnwWYnKxlnEsCFJeaclKtNfCb6VIoyzXmpRdwJyNzMvgHv5WnATQXBhzWXAssaZyEF2FLj88/vHByjJodFfTg3YS2Vj1DbozeLbySXr21+VxHiF0xclsCseQLtogiXkUCcfbHlBe3dCK3VPUVOgA1g0FDI6sE=
12
+ notifications:
13
+ slack:
14
+ secure: I1CCQzA99RbTG/urNV+zB/fH2H6o8SiMmr4WlXmJKHDJEdVwiFmEGLy0Hch1V2Me6udT3FW8uBMshVz2oVhCpu+vuGcky5raf9moHxM3go7kocFPET+NFBexJ0ejNZn+/P9VhFokafYR27AJyTU5TRR7gysnVqmw3kJBC66JyQRaly2zsL2cxkTtCAseArnou0e1K9SqKi8vQAT24nk7Vqxl9LcnCFjGuzNjjVA39SLRa2OSA9O2TOC/nQwSqQCMc0CiOd2qDD9PHC49uSvMnNjVRU5FLHUu3muBEY2ytSVlN1kQZRd4cdYEM4JXcfyEABrH/JggdastlSOAHDjJEarq9vNdaOBoNgiKqH1hF0yW/niv8+0ux0yRfdNEw6543U3KB5YWAlEtlm0cB5HJAx3urZxask2gHyApFOGjASItxDnPH9Z8S9HTTnUEy51oY1ELXUvANXdkRHI0ACGHeBaM3Pnm5//QpUT+V0jYo82yTe8j8H8O+w8eMQ86LEZ1I8hB+H8hqS3BLU3NiVa0MPpGwOfsbpNIOlUP61LS1dcTchAdMxZHeHp+d8gmzAlJCeesZeb+zv6kmu2iN+/ESsT0CT9Bq0TGyK3C+87y7vmB1k+qibnFt11/IAm/r23fYU6ctDTDY2Z/h4uDpN7Fycqeh4xfYqsmQKuEs66aZY0=
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in packer.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'guard'
8
+ gem 'guard-rspec'
9
+ gem 'rubocop'
10
+ end
@@ -0,0 +1,5 @@
1
+ guard :rspec, cmd: 'rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { 'spec' }
5
+ end
@@ -0,0 +1,110 @@
1
+ stage('Install dependencies') {
2
+ node {
3
+ checkout scm
4
+ withRvm('ruby-2.3.1') {
5
+ sh 'bundle -v || gem install bundler'
6
+ sh 'bundle install'
7
+ stash includes: 'Gemfile.lock, .bundle', name: 'bundle'
8
+ }
9
+ }
10
+ }
11
+
12
+ stage('Style checks') {
13
+ parallel(Rubocop: {
14
+ node {
15
+ checkout scm
16
+ withRvm('ruby-2.3.1') {
17
+ unstash 'bundle'
18
+ bundle_exec 'rake style:rubocop'
19
+ }
20
+ }
21
+ })
22
+ }
23
+
24
+ stage('Tests') {
25
+ parallel(Unit: {
26
+ node {
27
+ checkout scm
28
+ withRvm('ruby-2.3.1') {
29
+ unstash 'bundle'
30
+ bundle_exec 'rake spec:unit'
31
+ }
32
+ }
33
+ }, Integration: {
34
+ node {
35
+ checkout scm
36
+ withRvm('ruby-2.3.1') {
37
+ unstash 'bundle'
38
+ bundle_exec 'rake spec:integration'
39
+ }
40
+ }
41
+ }, System: {
42
+ node {
43
+ checkout scm
44
+ withRvm('ruby-2.3.1') {
45
+ unstash 'bundle'
46
+ bundle_exec 'rake spec:system'
47
+ }
48
+ }
49
+ })
50
+ }
51
+
52
+ if (isRelease()) {
53
+ stage('Publish') {
54
+ echo 'Would publish to rubygems.org' // TODO
55
+ slackSend "Published ${name()} gem version ${version()} to the rubygems.org", color: 'good'
56
+ }
57
+ }
58
+
59
+ def bundle_exec(command) {
60
+ sh "bundle exec ${command}"
61
+ }
62
+
63
+ def isRelease() {
64
+ false // FIXME: Building git tags is not yet supported (JENKINS-34395)
65
+ }
66
+
67
+ def name() {
68
+ node {
69
+ def matcher = readFile('packer-client.gemspec') =~ "spec.name += '(.+)'"
70
+ matcher ? matcher[0][1] : null
71
+ }
72
+ }
73
+
74
+ def version() {
75
+ node {
76
+ def matcher = readFile('lib/packer/version.rb') =~ "VERSION = '(.+)'"
77
+ matcher ? matcher[0][1] : null
78
+ }
79
+ }
80
+
81
+ def withRvm(version, cl) {
82
+ withRvm(version, "executor-${env.EXECUTOR_NUMBER}") {
83
+ cl()
84
+ }
85
+ }
86
+
87
+ def withRvm(version, gemset, cl) {
88
+ RVM_HOME='$HOME/.rvm'
89
+ paths = [
90
+ "$RVM_HOME/gems/$version@$gemset/bin",
91
+ "$RVM_HOME/gems/$version@global/bin",
92
+ "$RVM_HOME/rubies/$version/bin",
93
+ "$RVM_HOME/bin",
94
+ "${env.PATH}"
95
+ ]
96
+ def path = paths.join(':')
97
+ withEnv(["PATH=${env.PATH}:$RVM_HOME", "RVM_HOME=$RVM_HOME"]) {
98
+ sh "#!/bin/bash\nset +x; source $RVM_HOME/scripts/rvm; rvm use --create --install --binary $version@$gemset"
99
+ }
100
+ withEnv([
101
+ "PATH=$path",
102
+ "GEM_HOME=$RVM_HOME/gems/$version@$gemset",
103
+ "GEM_PATH=$RVM_HOME/gems/$version@$gemset:$RVM_HOME/gems/$version@global",
104
+ "MY_RUBY_HOME=$RVM_HOME/rubies/$version",
105
+ "IRBRC=$RVM_HOME/rubies/$version/.irbrc",
106
+ "RUBY_VERSION=$version"
107
+ ]) {
108
+ cl()
109
+ }
110
+ }
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Ben Vidulich
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,95 @@
1
+ # Packer Client
2
+
3
+ [![Build Status](https://travis-ci.org/zl4bv/packer-ruby.svg?branch=master)](https://travis-ci.org/zl4bv/packer-ruby)
4
+
5
+ A ruby client for HashiCorp's [Packer](https://www.packer.io) tool.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'packer-client'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install packer-client
22
+
23
+ ## Usage
24
+
25
+ ```ruby
26
+ require 'packer'
27
+ client = Packer::Client.new
28
+
29
+ # Override path to Packer executable
30
+ client.executable_path = 'C:\HashiCorp\Packer\packer.exe'
31
+
32
+ # Override maximum time that Packer may execute for
33
+ client.execution_timeout = 7200
34
+ ```
35
+
36
+ ### Build: Build image(s) from template
37
+
38
+ ```ruby
39
+ client.build('template.json')
40
+
41
+ # Get build artifacts
42
+ client.build('template.json').artifacts
43
+ ```
44
+
45
+ ### Fix: Fix template
46
+
47
+ ```ruby
48
+ # Get the fixed template JSON
49
+ client.fix('template.json').json
50
+
51
+ # Determine if template is valid
52
+ client.fix('template.json').valid?
53
+ ```
54
+
55
+ ### Inspect: See components of a template
56
+
57
+ ```ruby
58
+ # Get user variables
59
+ client.inspect_template('template.json').template_variables
60
+
61
+ # Get builders
62
+ client.inspect_template('template.json').template_builders
63
+
64
+ # Get provisioners
65
+ client.inspect_template('template.json').template_provisioners
66
+ ```
67
+
68
+ ### Push: Push a template to a Packer build service
69
+
70
+ ```ruby
71
+ client.push('template.json')
72
+ ```
73
+
74
+ ### Validate: Check that a template is valid
75
+
76
+ ```ruby
77
+ client.validate('template.json').valid?
78
+ ```
79
+
80
+ ### Version: Get the Packer version
81
+
82
+ ```ruby
83
+ # Get version
84
+ client.version.version
85
+
86
+ # Get version commit
87
+ client.version.version_commit
88
+
89
+ # Get prerelease version
90
+ client.version.version_prerelease
91
+ ```
92
+
93
+ ## Contributing
94
+
95
+ Bug reports and pull requests are welcome on GitHub at https://github.com/zl4bv/packer-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
@@ -0,0 +1,29 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ namespace :style do
6
+ RuboCop::RakeTask.new(:rubocop) do |t|
7
+ t.options = ['--fail-level', 'warning']
8
+ end
9
+ end
10
+
11
+ desc 'Run all style checks'
12
+ task style: ['style:rubocop']
13
+
14
+ namespace :spec do
15
+ RSpec::Core::RakeTask.new(:unit) do |t|
16
+ t.pattern = 'spec/unit/**/*_spec.rb'
17
+ end
18
+
19
+ RSpec::Core::RakeTask.new(:integration) do |t|
20
+ t.pattern = 'spec/integration/**/*_spec.rb'
21
+ end
22
+
23
+ # Note you must have Packer installed to run the system tests
24
+ RSpec::Core::RakeTask.new(:system) do |t|
25
+ t.pattern = 'spec/system/**/*_spec.rb'
26
+ end
27
+ end
28
+
29
+ task default: [:style, 'spec:unit', 'spec:integration']
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'packer'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require 'pry'
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,24 @@
1
+ require 'csv'
2
+ require 'mixlib/shellout'
3
+ require 'os'
4
+
5
+ require 'packer/message/base'
6
+ require 'packer/message/artifact'
7
+ require 'packer/message/artifact_file'
8
+ require 'packer/message/error'
9
+ require 'packer/message/template_builder'
10
+ require 'packer/message/template_provisioner'
11
+ require 'packer/message/template_variable'
12
+ require 'packer/message/ui'
13
+
14
+ require 'packer/output/base'
15
+ require 'packer/output/machine_readable'
16
+ require 'packer/output/build'
17
+ require 'packer/output/fix'
18
+ require 'packer/output/inspect'
19
+ require 'packer/output/push'
20
+ require 'packer/output/validate'
21
+ require 'packer/output/version'
22
+
23
+ require 'packer/client'
24
+ require 'packer/version'
@@ -0,0 +1,187 @@
1
+ module Packer
2
+ # Ruby client for HashiCorp Packer.
3
+ class Client
4
+ # Sets the path to the Packer executable.
5
+ attr_writer :executable_path
6
+
7
+ # Sets the maximum amount of time Packer may execute for before timing
8
+ # out.
9
+ attr_writer :execution_timeout
10
+
11
+ # Executes +packer build+.
12
+ #
13
+ # Will execute multiple builds in parallel as defined in the template.
14
+ # The various artifacts created by the template will be outputted.
15
+ #
16
+ # @param [String,Packer::Template] template the Packer template
17
+ # @param [Hash] options
18
+ # @option options [Boolean] :force force a build to continue if artifacts
19
+ # exist, deletes existing artifacts
20
+ # @option options [Array<String>] :except build all builds other than
21
+ # these
22
+ # @option options [Array<String>] :only only build the given builds by
23
+ # name
24
+ # @option options [Boolean] :parallel disable parallelization (on by
25
+ # default)
26
+ # @option options [Hash] :vars variables for templates
27
+ # @option options [String] :var_file path to JSON file containing user
28
+ # variables
29
+ # @return [Packer::Output::Build]
30
+ def build(template, options = {})
31
+ args = ['build', '-machine-readable']
32
+ args << '-force' if options.key?(:force)
33
+ args << "-except=#{options[:except].join(',')}" if options.key?(:except)
34
+ args << "-only=#{options[:only].join(',')}" if options.key?(:only)
35
+ args << "-parallel=#{options[:parallel]}" if options.key?(:parallel)
36
+ args << "-var-file=#{options[:var_file]}" if options.key?(:var_file)
37
+
38
+ vars = options[:vars] || {}
39
+ vars.each { |key, val| args << "-var '#{key}=#{val}'" }
40
+
41
+ args << template
42
+
43
+ Packer::Output::Build.new(command(args))
44
+ end
45
+
46
+ # @api private
47
+ # @param [Array<String>] args to pass to Packer
48
+ def command(args)
49
+ cmd = [executable_path, args].join(' ')
50
+ so = Mixlib::ShellOut.new(cmd, timeout: execution_timeout)
51
+ so.run_command
52
+ end
53
+
54
+ # Gets the path to the Packer executable. Defaults to +packer.exe+ on
55
+ # Windows and +packer+ on other platforms. The default values expect the
56
+ # Packer executable to be available via the PATH.
57
+ #
58
+ # @return [String] path to Packer executable
59
+ def executable_path
60
+ return @executable_path if @executable_path
61
+
62
+ if OS.windows?
63
+ 'packer.exe'
64
+ else
65
+ 'packer'
66
+ end
67
+ end
68
+
69
+ # Gets the maximum amount of time Packer may execute for before timing
70
+ # out. Defaults to 2 hours.
71
+ #
72
+ # @return [Fixnum] execution timeout
73
+ def execution_timeout
74
+ @execution_timeout || 7200 # 2 hours
75
+ end
76
+
77
+ # Executes +packer fix+.
78
+ #
79
+ # Reads the JSON template and attempts to fix know backwards
80
+ # incompatibilities. The fixed template will be outputted to standard out.
81
+ #
82
+ # If the template cannot be fixed due to an error, the command will exit
83
+ # will a non-zero exist status. Error messages will appear on standard
84
+ # error.
85
+ #
86
+ # @param [String,Packer::Template] template the Packer template
87
+ # @return [Packer::Output::Fix]
88
+ def fix(template)
89
+ Packer::Output::Fix.new(command(['fix', template]))
90
+ end
91
+
92
+ # Excutes +packer inspect+
93
+ #
94
+ # Inspects a template, parsing and outputting the components a template
95
+ # defines. This does not validate the contents of a template (other than
96
+ # basic syntax by necessity).
97
+ #
98
+ # @param [String,Packer::Template] template the Packer template
99
+ # @return [Packer::Output::Inspect]
100
+ def inspect_template(template)
101
+ args = ['inspect', '-machine-readable', template]
102
+
103
+ Packer::Output::Inspect.new(command(args))
104
+ end
105
+
106
+ # Executes +packer push+.
107
+ #
108
+ # Push the given template and supporting files to a Packer build service
109
+ # such as Atlas.
110
+ #
111
+ # If a build configuration for the given template does not exist, it will
112
+ # be created automatically. If the build configuration already exists, a
113
+ # new version will be created with this template and the supporting files.
114
+ #
115
+ # Additional configuration options (such as Atlas server URL and files to
116
+ # include) may be specified in the "push" section of the Packer template.
117
+ # Please see the online documentation about these configurables.
118
+ #
119
+ # @param [String,Packer::Template] template the Packer template
120
+ # @param [Hash] options
121
+ # @option options [String] :message a message to identify the purpose of
122
+ # changes in this Packer template much like a VCS commit message
123
+ # @option options [String] :name the destination build in Atlas. This is
124
+ # in a format "username/name".
125
+ # @option options [String] :token the access token to use when uploading
126
+ # @option options [Hash] :vars variables for templates
127
+ # @option options [String] :var_file path to JSON file containing user
128
+ # variables
129
+ # @return [Packer::Output::Push]
130
+ def push(template, options = {})
131
+ args = ['push']
132
+ args << "-message=#{options[:message]}" if options.key?(:message)
133
+ args << "-name=#{options[:name]}" if options.key?(:name)
134
+ args << "-token=#{options[:token]}" if options.key?(:token)
135
+ args << "-var-file=#{options[:var_file]}" if options.key?(:var_file)
136
+
137
+ vars = options[:vars] || {}
138
+ vars.each { |key, val| args << "-var '#{key}=#{val}'" }
139
+
140
+ args << template
141
+
142
+ Packer::Output::Push.new(command(args))
143
+ end
144
+
145
+ # Executes +packer validate+
146
+ #
147
+ # Checks the template is valid by parsing the template and also checking
148
+ # the configuration with the various builders, provisioners, etc.
149
+ #
150
+ # If it is not valid, the errors will be shown and the command will exit
151
+ # with a non-zero exit status. If it is valid, it will exist with a zero
152
+ # exist status.
153
+ #
154
+ # @param [String,Packer::Template] template the Packer template
155
+ # @param [Hash] options
156
+ # @option options [Boolean] :syntax_only only check syntax. Do not verify
157
+ # config of the template.
158
+ # @option options [Array<String>] :except validate all builds other than
159
+ # these
160
+ # @option options [Array<String>] :only validate only these builds
161
+ # @option options [Hash] :vars variables for templates
162
+ # @option options [String] :var_file path to JSON file containing user
163
+ # variables
164
+ # @return [Packer::Output::Validate]
165
+ def validate(template, options = {})
166
+ args = ['validate']
167
+ args << '-syntax-only' if options.key?(:syntax_only)
168
+ args << "-except=#{options[:except].join(',')}" if options.key?(:except)
169
+ args << "-only=#{options[:only].join(',')}" if options.key?(:only)
170
+ args << "-var-file=#{options[:var_file]}" if options.key?(:var_file)
171
+
172
+ vars = options[:vars] || {}
173
+ vars.each { |key, val| args << "-var '#{key}=#{val}'" }
174
+
175
+ args << template
176
+
177
+ Packer::Output::Validate.new(command(args))
178
+ end
179
+
180
+ # Executes +packer version+
181
+ #
182
+ # @return [Packer::Output::Version]
183
+ def version
184
+ Packer::Output::Version.new(command(['version', '-machine-readable']))
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,23 @@
1
+ module Packer
2
+ module Message
3
+ # Message representing an artifact produced by a builder
4
+ class Artifact < Base
5
+ # Zero-based index of the artifact being described
6
+ attr_accessor :artifact_index
7
+
8
+ # The unique ID of the builder
9
+ attr_accessor :builder_id
10
+
11
+ attr_accessor :files
12
+
13
+ # The ID (if any) of the artifact that was built
14
+ attr_accessor :id
15
+
16
+ # If +true+, this means the artifact was nil
17
+ attr_accessor :nil
18
+
19
+ # The human-readable string description of the artifact
20
+ attr_accessor :string
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ module Packer
2
+ module Message
3
+ # Message representing a file associated with an artifact
4
+ class ArtifactFile < Base
5
+ # The zero-based index of the file
6
+ attr_accessor :file_index
7
+
8
+ # The filename
9
+ attr_accessor :filename
10
+
11
+ # @api private
12
+ # @param [Array<String>] fields
13
+ def self.from_fields(fields)
14
+ msg = new
15
+ msg.timestamp = fields[0]
16
+ msg.target = fields[1]
17
+ msg.type = fields[2]
18
+ msg.data = fields[3..-1]
19
+ msg.file_index = fields[5]
20
+ msg.filename = fields[6]
21
+ msg
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ module Packer
2
+ module Message
3
+ # Base class for all message types
4
+ class Base
5
+ # Unix timestamp in UTC of when the message was printed
6
+ attr_accessor :timestamp
7
+
8
+ # Target of the following output
9
+ attr_accessor :target
10
+
11
+ # Type of message outputted by Packer
12
+ attr_accessor :type
13
+
14
+ # Zero or more values associated with the message type
15
+ attr_accessor :data
16
+
17
+ # @api private
18
+ # @param [Array<String>] fields
19
+ def self.from_fields(fields)
20
+ msg = new
21
+ msg.timestamp = fields[0]
22
+ msg.target = fields[1]
23
+ msg.type = fields[2]
24
+ msg.data = fields[3..-1]
25
+ msg
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ module Packer
2
+ module Message
3
+ # Represents an error message
4
+ class Error < Base
5
+ # The error message
6
+ attr_accessor :error
7
+
8
+ # @api private
9
+ # @param [Array<String>] fields
10
+ def self.from_fields(fields)
11
+ msg = new
12
+ msg.timestamp = fields[0]
13
+ msg.target = fields[1]
14
+ msg.type = fields[2]
15
+ msg.data = fields[3..-1]
16
+ msg.error = fields[3]
17
+ msg
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ module Packer
2
+ module Message
3
+ # Message representing a builder defined within the template
4
+ #
5
+ # @see https://www.packer.io/docs/machine-readable/command-inspect.html
6
+ class TemplateBuilder < Base
7
+ # The name of the builder
8
+ attr_accessor :name
9
+
10
+ # The type of the builder
11
+ attr_accessor :builder_type
12
+
13
+ # @api private
14
+ # @param [Array<String>] fields
15
+ def self.from_fields(fields)
16
+ msg = new
17
+ msg.timestamp = fields[0]
18
+ msg.target = fields[1]
19
+ msg.type = fields[2]
20
+ msg.data = fields[3..-1]
21
+ msg.name = fields[3]
22
+ msg.builder_type = fields[4]
23
+ msg
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ module Packer
2
+ module Message
3
+ # Message representing a provisioner defined within the template
4
+ #
5
+ # @see https://www.packer.io/docs/machine-readable/command-inspect.html
6
+ class TemplateProvisioner < Base
7
+ # The name/type of the provisioner
8
+ attr_accessor :name
9
+
10
+ # @api private
11
+ # @param [Array<String>] fields
12
+ def self.from_fields(fields)
13
+ msg = new
14
+ msg.timestamp = fields[0]
15
+ msg.target = fields[1]
16
+ msg.type = fields[2]
17
+ msg.data = fields[3..-1]
18
+ msg.name = fields[3]
19
+ msg
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,31 @@
1
+ module Packer
2
+ module Message
3
+ # Message representing a user variable defined within the template
4
+ #
5
+ # @see https://www.packer.io/docs/machine-readable/command-inspect.html
6
+ class TemplateVariable < Base
7
+ # The name of the variable
8
+ attr_accessor :name
9
+
10
+ # The default value of the variable
11
+ attr_accessor :default
12
+
13
+ # If non-zero, then this variable is required
14
+ attr_accessor :required
15
+
16
+ # @api private
17
+ # @param [Array<String>] fields
18
+ def self.from_fields(fields)
19
+ msg = new
20
+ msg.timestamp = fields[0]
21
+ msg.target = fields[1]
22
+ msg.type = fields[2]
23
+ msg.data = fields[3..-1]
24
+ msg.name = fields[3]
25
+ msg.default = fields[4]
26
+ msg.required = fields[5]
27
+ msg
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ module Packer
2
+ module Message
3
+ # Represents a message destined for a UI
4
+ class Ui < Base
5
+ # The type of UI message
6
+ attr_accessor :ui_message_type
7
+
8
+ # The string output to be displayed by the UI
9
+ attr_accessor :output
10
+
11
+ # @api private
12
+ # @param [Array<String>] fields
13
+ def self.from_fields(fields)
14
+ msg = new
15
+ msg.timestamp = fields[0]
16
+ msg.target = fields[1]
17
+ msg.type = fields[2]
18
+ msg.data = fields[3..-1]
19
+ msg.ui_message_type = fields[3]
20
+ msg.output = fields[4]
21
+ msg
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ module Packer
2
+ module Output
3
+ # Base class for output from all Packer commands
4
+ class Base
5
+ # @param [Mixlib::ShellOut] output from the build command
6
+ def initialize(output)
7
+ @output = output
8
+ end
9
+
10
+ # Returns the raw standard error output
11
+ #
12
+ # @return [String]
13
+ def stderr
14
+ @output.stderr
15
+ end
16
+
17
+ # Returns the raw standard output
18
+ #
19
+ # @return [String]
20
+ def stdout
21
+ @output.stdout
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,51 @@
1
+ module Packer
2
+ module Output
3
+ # Represents the output from +packer build+.
4
+ #
5
+ # @see https://www.packer.io/docs/command-line/build.html
6
+ class Build < MachineReadable
7
+ # Information about an artifact of the targeted item.
8
+ #
9
+ # @return [Array<Packer::Message::Artifact]
10
+ def artifacts
11
+ afcts = []
12
+
13
+ afct ||= Packer::Message::Artifact.new
14
+ select_messages('artifact').each do |fields|
15
+ afct.timestamp ||= fields[0]
16
+ afct.target ||= fields[1]
17
+ afct.type ||= 'artifact'
18
+ afct.artifact_index ||= fields[3]
19
+
20
+ case fields[4]
21
+ when 'builder-id'
22
+ afct.builder_id = fields[5]
23
+ when 'end'
24
+ afcts << afct
25
+ afct = Packer::Message::Artifact.new
26
+ when 'file'
27
+ afct.files ||= []
28
+ afct.files << Packer::Message::ArtifactFile.from_fields(fields)
29
+ when 'files-count'
30
+ next
31
+ when 'id'
32
+ afct.id = fields[5]
33
+ when 'nil'
34
+ afct.nil = true
35
+ when 'string'
36
+ afct.string = fields[5]
37
+ end
38
+ end
39
+
40
+ afcts
41
+ end
42
+
43
+ # Build errors that occurred
44
+ #
45
+ # @return [Array<Packer::Message::Error]
46
+ def errors
47
+ select_messages('error').map { |fields| Packer::Message::Error.from_fields(fields) }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,25 @@
1
+ module Packer
2
+ module Output
3
+ # Represents the output from +packer fix+.
4
+ #
5
+ # @see https://www.packer.io/docs/command-line/fix.html
6
+ class Fix < Base
7
+ # JSON representing the fixed template or +nil+ if the fixing fails or the
8
+ # template is not valid.
9
+ #
10
+ # @return [String]
11
+ def json
12
+ return nil unless valid?
13
+
14
+ stdout
15
+ end
16
+
17
+ # Returns +true+ if the fixing was successful and the template is valid.
18
+ #
19
+ # @return [Boolean]
20
+ def valid?
21
+ @output.exitstatus.zero?
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ module Packer
2
+ module Output
3
+ # Represents the output from +packer inspect+.
4
+ #
5
+ # @see https://www.packer.io/docs/command-line/inspect.html
6
+ class Inspect < MachineReadable
7
+ # User variables defined within the template.
8
+ #
9
+ # @return [Array<Packer::Message::TemplateVariable]
10
+ def template_variables
11
+ select_messages('template-variable').map { |fields| Packer::Message::TemplateVariable.from_fields(fields) }
12
+ end
13
+
14
+ # Builders defined within the template.
15
+ #
16
+ # @return [Array<Packer::Message::TemplateVariable]
17
+ def template_builders
18
+ select_messages('template-builder').map { |fields| Packer::Message::TemplateBuilder.from_fields(fields) }
19
+ end
20
+
21
+ # Provisioners defined within the template.
22
+ #
23
+ # @return [Array<Packer::Message::TemplateVariable]
24
+ def template_provisioners
25
+ select_messages('template-provisioner').map { |fields| Packer::Message::TemplateProvisioner.from_fields(fields) }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ module Packer
2
+ module Output
3
+ # Represents commands that produce machine-readable output
4
+ class MachineReadable < Base
5
+ # Outputs that would have normally gone to the console if Packer were
6
+ # running in human-readable mode.
7
+ #
8
+ # @return [Array<Packer::Message::Ui>]
9
+ def ui_messages
10
+ select_messages('ui').map { |fields| Packer::Message::Ui.from_fields(fields) }
11
+ end
12
+
13
+ private
14
+
15
+ # @api private
16
+ # @param [String] type of message
17
+ # @return [Array<Array>] lists of message fields
18
+ def select_messages(type)
19
+ stdout
20
+ .split("\n")
21
+ .map { |line| CSV.parse(line).first }
22
+ .select { |fields| fields[2] == type }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,10 @@
1
+ module Packer
2
+ module Output
3
+ # Represents the output from +packer push+.
4
+ #
5
+ # @see https://www.packer.io/docs/command-line/push.html
6
+ class Push < Base
7
+ # TODO: Determine if output from push command needs additional processing
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ module Packer
2
+ module Output
3
+ # Represents the output from +packer validate+.
4
+ #
5
+ # @see https://www.packer.io/docs/command-line/validate.html
6
+ class Validate < Base
7
+ # Returns +true+ if the template is valid.
8
+ #
9
+ # @return [Boolean]
10
+ def valid?
11
+ @output.exitstatus.zero?
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ module Packer
2
+ module Output
3
+ # Represents the output from +packer version+.
4
+ #
5
+ # @see https://www.packer.io/docs/machine-readable/command-version.html
6
+ class Version < MachineReadable
7
+ # The version of Packer running, only including the major, minor, and
8
+ # patch versions.
9
+ #
10
+ # @return [String]
11
+ def version
12
+ msgs = select_messages('version')
13
+ msgs[0][3]
14
+ end
15
+
16
+ # The SHA1 of the Git commit that build this version of Packer.
17
+ #
18
+ # @return [String]
19
+ def version_commit
20
+ msgs = select_messages('version-commit')
21
+ msgs[0][3]
22
+ end
23
+
24
+ # The prerelease tag (if any) for the running version of packer.
25
+ #
26
+ # @return [String]
27
+ def version_prerelease
28
+ msgs = select_messages('version-prelease')
29
+ msgs[0][3]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module Packer
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'packer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'packer-client'
8
+ spec.version = Packer::VERSION
9
+ spec.authors = ['Ben Vidulich']
10
+ spec.email = ['ben@vidulich.co.nz']
11
+
12
+ spec.summary = "A Ruby client for HashiCorp's Packer tool."
13
+ spec.description = "A Ruby client for HashiCorp's Packer tool."
14
+ spec.homepage = 'https://github.com/zl4bv/packer-ruby'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_development_dependency 'bundler', '~> 1.10'
23
+ spec.add_development_dependency 'rake', '~> 10.0'
24
+ spec.add_development_dependency 'rspec'
25
+ spec.add_development_dependency 'rspec-its'
26
+
27
+ spec.add_runtime_dependency 'mixlib-shellout', '~> 2.2'
28
+ spec.add_runtime_dependency 'os', '~> 0.9'
29
+ end
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: packer-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ben Vidulich
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-10-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-its
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: mixlib-shellout
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '2.2'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '2.2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: os
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '0.9'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '0.9'
97
+ description: A Ruby client for HashiCorp's Packer tool.
98
+ email:
99
+ - ben@vidulich.co.nz
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .gitignore
105
+ - .rspec
106
+ - .rubocop.yml
107
+ - .travis.yml
108
+ - CODE_OF_CONDUCT.md
109
+ - Gemfile
110
+ - Guardfile
111
+ - Jenkinsfile
112
+ - LICENSE.txt
113
+ - README.md
114
+ - Rakefile
115
+ - bin/console
116
+ - bin/setup
117
+ - lib/packer.rb
118
+ - lib/packer/client.rb
119
+ - lib/packer/message/artifact.rb
120
+ - lib/packer/message/artifact_file.rb
121
+ - lib/packer/message/base.rb
122
+ - lib/packer/message/error.rb
123
+ - lib/packer/message/template_builder.rb
124
+ - lib/packer/message/template_provisioner.rb
125
+ - lib/packer/message/template_variable.rb
126
+ - lib/packer/message/ui.rb
127
+ - lib/packer/output/base.rb
128
+ - lib/packer/output/build.rb
129
+ - lib/packer/output/fix.rb
130
+ - lib/packer/output/inspect.rb
131
+ - lib/packer/output/machine_readable.rb
132
+ - lib/packer/output/push.rb
133
+ - lib/packer/output/validate.rb
134
+ - lib/packer/output/version.rb
135
+ - lib/packer/version.rb
136
+ - packer-client.gemspec
137
+ homepage: https://github.com/zl4bv/packer-ruby
138
+ licenses:
139
+ - MIT
140
+ metadata: {}
141
+ post_install_message:
142
+ rdoc_options: []
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ! '>='
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubyforge_project:
157
+ rubygems_version: 2.4.5
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: A Ruby client for HashiCorp's Packer tool.
161
+ test_files: []